juk.cpp 15.8 KB
Newer Older
1 2 3 4 5
/***************************************************************************
                          juk.cpp  -  description
                             -------------------
    begin                : Mon Feb  4 23:40:41 EST 2002
    copyright            : (C) 2002 by Scott Wheeler
6
    email                : wheeler@kde.org
7
***************************************************************************/
8 9 10 11 12 13 14 15 16 17

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

18
#include <kapplication.h>
19
#include <klocale.h>
20
#include <kiconloader.h>
21
#include <kcmdlineargs.h>
22
#include <kstatusbar.h>
23 24
#include <kdebug.h>

25 26
#include <qinputdialog.h>

27
#include "juk.h"
28 29 30
#include "playlist.h"
#include "playlistsplitter.h"
#include "collectionlist.h"
31
#include "slideraction.h"
32
#include "cache.h"
33
#include "statuslabel.h"
34 35
#include "splashscreen.h"
#include "genrelisteditor.h"
36 37 38 39 40

////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

41
JuK::JuK(QWidget *parent, const char *name) : KMainWindow(parent, name, WDestructiveClose)
42
{
43 44 45
    SplashScreen::instance()->show();
    kapp->processEvents();
 
46 47
    // Expect segfaults if you change this order.

48
    readSettings();
49
    setupLayout();
50
    setupActions();
51 52
    setupPlayer();
    readConfig();
53
    processArgs();
54 55

    SplashScreen::finishedLoading();
56 57 58 59
}

JuK::~JuK()
{
60
    delete(playTimer);
61 62 63 64 65 66
}

////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

67 68
void JuK::setupLayout()
{
69
    splitter = new PlaylistSplitter(this, restore, "playlistSplitter");
70 71 72 73 74
    setCentralWidget(splitter);

    // playlist item activation connection
    connect(splitter, SIGNAL(playlistDoubleClicked(QListViewItem *)), this, SLOT(playItem(QListViewItem *)));

75
    // create status bar
76
    statusLabel = new StatusLabel(statusBar());
77 78
    statusBar()->addWidget(statusLabel, 1);

79 80 81
    splitter->setFocus();
}

82 83
void JuK::setupActions()
{
84
    // file menu
85 86 87 88
    KStdAction::open(splitter, SLOT(open()), actionCollection());
    new KAction(i18n("Open &Directory..."), "fileopen", 0, splitter, SLOT(openDirectory()), actionCollection(), "openDirectory");
    KStdAction::save(splitter, SLOT(save()), actionCollection());
    new KAction(i18n("Delete"), "editdelete", 0, this, SLOT(remove()), actionCollection(), "remove");
89 90 91 92 93 94
    KStdAction::quit(this, SLOT(quit()), actionCollection());

    // edit menu
    KStdAction::cut(this, SLOT(cut()), actionCollection());
    KStdAction::copy(this, SLOT(copy()), actionCollection());
    KStdAction::paste(this, SLOT(paste()), actionCollection());
95
    KStdAction::selectAll(splitter, SLOT(selectAll()), actionCollection());
96

97 98
    // view menu
    showEditorAction = new KToggleAction(i18n("Show Tag Editor"), 0, actionCollection(), "showEditor");
99 100
    connect(showEditorAction, SIGNAL(toggled(bool)), splitter, SLOT(setEditorVisible(bool)));
    KStdAction::redisplay(splitter, SLOT(refresh()), actionCollection());
101

102
    // play menu
103
    randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
104
    playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(playFile()), actionCollection(), "playFile");
105 106
    pauseAction = new KAction(i18n("P&ause"), "player_pause", 0, this, SLOT(pauseFile()), actionCollection(), "pauseFile");
    stopAction = new KAction(i18n("&Stop"), "player_stop", 0, this, SLOT(stopFile()), actionCollection(), "stopFile");
107 108
    backAction = new KAction(i18n("Skip &Back"), "player_start", 0, this, SLOT(backFile()), actionCollection(), "backFile");
    forwardAction = new KAction(i18n("Skip &Forward"), "player_end", 0, this, SLOT(forwardFile()), actionCollection(), "forwardFile");
109

110
    // playlist menu
111 112 113 114 115 116 117 118 119
    new KAction(i18n("New..."), "filenew", 0, splitter, SLOT(createPlaylist()), actionCollection(), "createPlaylist");
    new KAction(i18n("Open..."), "fileopen", 0, splitter, SLOT(openPlaylist()), actionCollection(), "openPlaylist");

    savePlaylistAction = new KAction(i18n("Save"), "filesave", 0, splitter, SLOT(savePlaylist()), actionCollection(), "savePlaylist");
    saveAsPlaylistAction = new KAction(i18n("Save As..."), "filesaveas", 0, splitter, SLOT(saveAsPlaylist()), 
				       actionCollection(), "saveAsPlaylist");
    renamePlaylistAction = new KAction(i18n("Rename..."), 0, splitter, SLOT(renamePlaylist()), 
				       actionCollection(), "renamePlaylist");
    new KAction(i18n("Duplicate..."), "editcopy", 0, splitter, SLOT(duplicatePlaylist()), actionCollection(), "duplicatePlaylist");
120 121
    deleteItemPlaylistAction = new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(deleteItemPlaylist()), 
					   actionCollection(), "deleteItemPlaylist");
122
    
123 124
    // settings menu
    restoreOnLoadAction = new KToggleAction(i18n("Restored Playlists on Load"),  0, actionCollection(), "restoreOnLoad"); 
125
    new KAction(i18n("Genre List Editor"), 0, this, SLOT(showGenreListEditor()), actionCollection(), "showGenreListEditor");
126

127 128 129
    playlistChanged(0);
    connect(splitter, SIGNAL(playlistChanged(Playlist *)), this, SLOT(playlistChanged(Playlist *)));

130

131 132 133 134
    // just in the toolbar
    sliderAction = new SliderAction(i18n("Track Position"), actionCollection(), "trackPositionAction");

    createGUI();
135 136 137 138

    // set the slider to the proper orientation and make it stay that way
    sliderAction->updateOrientation();
    connect(this, SIGNAL(dockWindowPositionChanged(QDockWindow *)), sliderAction, SLOT(updateOrientation(QDockWindow *)));
139 140 141 142
}

void JuK::setupPlayer()
{
143 144
    playingItem = 0;

145 146
    trackPositionDragging = false;
    noSeek = false;
147 148
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
149 150
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
151

152
    playTimer = new QTimer(this);
153 154 155 156 157 158 159
    connect(playTimer, SIGNAL(timeout()), this, SLOT(pollPlay()));

    if(sliderAction && sliderAction->getTrackPositionSlider() && sliderAction->getVolumeSlider()) {
        connect(sliderAction->getTrackPositionSlider(), SIGNAL(valueChanged(int)), this, SLOT(trackPositionSliderUpdate(int)));
        connect(sliderAction->getTrackPositionSlider(), SIGNAL(sliderPressed()), this, SLOT(trackPositionSliderClick()));
        connect(sliderAction->getTrackPositionSlider(), SIGNAL(sliderReleased()), this, SLOT(trackPositionSliderRelease()));
        sliderAction->getTrackPositionSlider()->setEnabled(false);
160

161 162
        connect(sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
    }
163 164
}

165

166 167 168 169 170 171 172 173
void JuK::processArgs()
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
    
    for(int i = 0; i < args->count(); i++)
	files.append(args->arg(i));

174
    splitter->open(files);
175 176
}

177 178
void JuK::readConfig()
{
179 180 181
    // Automagically save and restore many window settings.
    setAutoSaveSettings();

182 183 184 185 186 187 188
    KConfig *config = KGlobal::config();
    { // player settings
        KConfigGroupSaver saver(config, "Player");
        if(sliderAction && sliderAction->getVolumeSlider()) {
            int volume = config->readNumEntry("Volume", sliderAction->getVolumeSlider()->maxValue());
            sliderAction->getVolumeSlider()->setValue(volume);
        }
189 190 191 192
	if(randomPlayAction) {
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
	    randomPlayAction->setChecked(randomPlay);
	}
193
    }
194
    { // view settings
195 196 197 198 199
        KConfigGroupSaver saver(config, "View");
	bool showEditor = config->readBoolEntry("ShowEditor", true);
	showEditorAction->setChecked(showEditor);
	splitter->setEditorVisible(showEditor);
    }
200 201 202 203 204 205 206 207 208 209 210 211

    if(restoreOnLoadAction)
	restoreOnLoadAction->setChecked(restore);
}

void JuK::readSettings()
{
    KConfig *config = KGlobal::config();
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	restore = config->readBoolEntry("RestoreOnLoad", true);
    }
212 213 214 215
}

void JuK::saveConfig()
{
216 217 218 219 220
    KConfig *config = KGlobal::config();
    { // player settings
        KConfigGroupSaver saver(config, "Player");
        if(sliderAction && sliderAction->getVolumeSlider())
            config->writeEntry("Volume", sliderAction->getVolumeSlider()->value());
221 222
	if(randomPlayAction)
	    config->writeEntry("RandomPlay", randomPlayAction->isChecked());
223
    }
224 225 226 227
    { // view settings
        KConfigGroupSaver saver(config, "View");
	config->writeEntry("ShowEditor", showEditorAction->isChecked());
    }
228 229 230 231 232
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	if(restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", restoreOnLoadAction->isChecked());
    }
233 234
}

235 236
bool JuK::queryClose()
{
237
    Cache::instance()->save();
238
    saveConfig();
239
    delete(splitter);
240 241 242
    return(true);
}

243 244 245 246
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

247
void JuK::playlistChanged(Playlist *list)
248
{
249 250 251 252 253 254 255 256 257 258 259 260
    if(!list || list == CollectionList::instance()) {
	savePlaylistAction->setEnabled(false);
	saveAsPlaylistAction->setEnabled(false);
	renamePlaylistAction->setEnabled(false);
	deleteItemPlaylistAction->setEnabled(false);	
    }
    else {
	savePlaylistAction->setEnabled(true);
	saveAsPlaylistAction->setEnabled(true);
	renamePlaylistAction->setEnabled(true);
	deleteItemPlaylistAction->setEnabled(true);
    }
261 262
}

263 264 265
////////////////////////////////////////////////////////////////////////////////
// private action slot implementations - file menu
////////////////////////////////////////////////////////////////////////////////
266

267
void JuK::remove()
268
{
269
    PlaylistItemList items(splitter->playlistSelection());
270
    PlaylistItem *item = items.first();
271
    while(item) {
272 273 274
	if(item == playingItem)
	    playingItem = 0;
	item = items.next();
275
    }
276 277
    
    splitter->remove();
278 279
}

280 281
void JuK::quit()
{
282 283 284
    // delete(this);
    // kapp->quit();
    close();
285 286
}

287 288 289 290 291 292
////////////////////////////////////////////////////////////////////////////////
// edit menu
////////////////////////////////////////////////////////////////////////////////

void JuK::cut()
{
293
    PlaylistItemList items = splitter->playlistSelection();
Scott Wheeler's avatar
Scott Wheeler committed
294

295 296 297 298 299
    PlaylistItem *item = items.first();
    while(item) {
	if(item == playingItem)
	    playingItem = 0;
	item = items.next();
300 301
    }

302
    splitter->clearSelectedItems();
303 304
}

305 306 307 308 309 310
////////////////////////////////////////////////////////////////////////////////
// player menu
////////////////////////////////////////////////////////////////////////////////

void JuK::playFile()
{
311 312
    if(player.paused()) {
        player.play();
313 314 315 316

	// Here, before doing anything, we want to make sure that the player did
	// in fact start.

317 318 319 320 321 322
        if(player.playing()) {
            pauseAction->setEnabled(true);
            stopAction->setEnabled(true);
            playTimer->start(pollInterval);
        }
    }
323 324
    else if(player.playing())
	player.seekPosition(0);
325
    else if(splitter) {
326
        PlaylistItemList items = splitter->playlistSelection();
327
        if(items.count() > 0)
328
            playItem(items.at(0));
329
        else
330
            playItem(splitter->playlistFirstItem());
331
    }
332 333 334 335
}

void JuK::pauseFile()
{
336 337 338
    playTimer->stop();
    player.pause();
    pauseAction->setEnabled(false);
339 340 341 342
}

void JuK::stopFile()
{
343 344 345 346
    playTimer->stop();
    player.stop();
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
347 348
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
349 350
    sliderAction->getTrackPositionSlider()->setValue(0);
    sliderAction->getTrackPositionSlider()->setEnabled(false);
351 352
    if(playingItem)
        playingItem->setPixmap(0, 0);
353
    playingItem = 0;
354

355
    statusLabel->clear();
356 357
}

358 359 360 361 362 363 364 365 366 367 368 369 370
void JuK::backFile()
{
    PlaylistItem *i = Playlist::previousItem(playingItem, randomPlayAction->isChecked());
    if(i)
	playItem(i);
}

void JuK::forwardFile()
{
    PlaylistItem *i = Playlist::nextItem(playingItem, randomPlayAction->isChecked());
    playItem(i);
}

371 372 373 374 375 376 377 378 379
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

void JuK::showGenreListEditor()
{
    GenreListEditor * editor = new GenreListEditor();
    editor->exec();
}
380

381 382 383 384
////////////////////////////////////////////////////////////////////////////////
// additional player slots
////////////////////////////////////////////////////////////////////////////////

385 386
void JuK::trackPositionSliderClick()
{
387
    trackPositionDragging = true;
388 389 390 391
}

void JuK::trackPositionSliderRelease()
{
392
    trackPositionDragging = false;
393
    player.seekPosition(sliderAction->getTrackPositionSlider()->value());
394 395 396 397
}

void JuK::trackPositionSliderUpdate(int position)
{
398
    if(player.playing() && !trackPositionDragging && !noSeek)
399
        player.seekPosition(position);
400 401 402 403
}

void JuK::pollPlay()
{
404 405
    noSeek = true;
    if(!player.playing()) {
406

407
        playTimer->stop();
408

409
        if(player.paused())
410
            pauseFile();
411
        else if(playingItem) {
412

413
	    PlaylistItem *next = Playlist::nextItem(playingItem, randomPlayAction->isChecked());
414 415
	    playingItem->setPixmap(0, 0);

416 417
	    if(next) {
		playingItem = next;
418 419 420
		
		backAction->setEnabled(true);
		
421 422 423 424 425 426 427
		sliderAction->getTrackPositionSlider()->setValue(0);
		player.play(playingItem->absFilePath(), player.getVolume());
		if(player.playing()) {
		    playTimer->start(pollInterval);
		    playingItem->setPixmap(0, QPixmap(UserIcon("playing")));
		}
	    }
428
	    statusLabel->setPlayingItem(playingItem);
429 430 431
	}
	else
	    stopFile();
432
    }
433
    else if(!trackPositionDragging) {
434
        sliderAction->getTrackPositionSlider()->setValue(player.position());
435 436 437 438 439 440 441 442
	statusLabel->setItemTotalTime(player.totalTime());
	statusLabel->setItemCurrentTime(player.currentTime());
    }

    // Ok, this is weird stuff, but it works pretty well.  Ordinarily we don't
    // need to check up on our playing time very often, but in the span of the 
    // last interval, we want to check a lot -- to figure out that we've hit the
    // end of the song as soon as possible.
443

444 445
    if(player.playing() && float(player.totalTime() - player.currentTime()) < pollInterval * 2)
        playTimer->changeInterval(50);
446

447
    noSeek = false;
448 449 450 451
}

void JuK::setVolume(int volume)
{
452 453 454
    if(sliderAction && sliderAction->getVolumeSlider() &&
       sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && sliderAction->getVolumeSlider()->maxValue() >= volume)
455
    {
456
        player.setVolume(float(volume) / float(sliderAction->getVolumeSlider()->maxValue()));
457 458
    }
}
459 460 461

void JuK::playItem(QListViewItem *item)
{
462
    playItem(dynamic_cast<PlaylistItem *>(item));
463 464
}

465
void JuK::playItem(PlaylistItem *item)
466
{
467 468 469 470 471
    if(player.playing() || player.paused())
        stopFile();

    if(item) {
        float volume = float(sliderAction->getVolumeSlider()->value()) / float(sliderAction->getVolumeSlider()->maxValue());
472 473 474 475
        player.play(item->absFilePath(), volume);

	// Make sure that the player actually starts before doing anything.

476 477 478
        if(player.playing()) {
            pauseAction->setEnabled(true);
            stopAction->setEnabled(true);
479 480 481 482 483 484

	    backAction->setEnabled(true);
	    forwardAction->setEnabled(true);

	    playingItem = item;

485
            sliderAction->getTrackPositionSlider()->setEnabled(true);
486
            item->setPixmap(0, QPixmap(UserIcon("playing")));
487
            playTimer->start(pollInterval);
488

489
	    statusLabel->setPlayingItem(item);
490
        }
491 492
    }
}
493

494
#include "juk.moc"