juk.cpp 18.6 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
#include <kconfig.h>
24 25
#include <kdebug.h>

26 27
#include <qtimer.h>
#include <qlistview.h>
28
#include <qinputdialog.h>
29
#include <qslider.h>
30 31
#include <qstrlist.h>
#include <qmetaobject.h>
32

33
#include "juk.h"
34
#include "slideraction.h"
35
#include "cache.h"
36
#include "statuslabel.h"
37 38
#include "splashscreen.h"
#include "genrelisteditor.h"
39
#include "systemtray.h"
40 41 42 43 44

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

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

52
    readSettings();
53
    setupLayout();
54
    setupActions();
55
    readConfig();
56
    setupPlayer();
57
    setupSystemTray();
58
    processArgs();
59

60
    SplashScreen::finishedLoading();
61 62 63 64
}

JuK::~JuK()
{
65

66 67 68 69 70 71
}

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

72 73
void JuK::setupLayout()
{
74
    splitter = new PlaylistSplitter(this, restore, "playlistSplitter");
75 76 77
    setCentralWidget(splitter);

    // playlist item activation connection
78 79
    connect(splitter, SIGNAL(doubleClicked()), this, SLOT(playSelectedFile()));
    connect(splitter, SIGNAL(listBoxDoubleClicked()), this, SLOT(playFirstFile()));
80

81
    // create status bar
82
    statusLabel = new StatusLabel(statusBar());
83 84
    statusBar()->addWidget(statusLabel, 1);

85
    connect(splitter, SIGNAL(selectedPlaylistCountChanged(int)), statusLabel, SLOT(setPlaylistCount(int)));
86
    connect(statusLabel, SIGNAL(jumpButtonClicked()), splitter, SLOT(selectPlaying()));
87

88 89
    updatePlaylistInfo();

90
    splitter->setFocus();
91

92
    resize(750, 500);
93 94
}

95 96
void JuK::setupActions()
{
97
    // file menu
98
    KStdAction::openNew(splitter, SLOT(createPlaylist()), actionCollection());
99 100
    KStdAction::open(splitter, SLOT(open()), actionCollection());
    new KAction(i18n("Open &Directory..."), "fileopen", 0, splitter, SLOT(openDirectory()), actionCollection(), "openDirectory");
101 102 103 104

    renamePlaylistAction = new KAction(i18n("Rename..."), 0, splitter, SLOT(renamePlaylist()), 
				       actionCollection(), "renamePlaylist");
    new KAction(i18n("Duplicate..."), "editcopy", 0, splitter, SLOT(duplicatePlaylist()), actionCollection(), "duplicatePlaylist");
105
    
106 107
    savePlaylistAction = KStdAction::save(splitter, SLOT(savePlaylist()), actionCollection());
    saveAsPlaylistAction = KStdAction::saveAs(splitter, SLOT(saveAsPlaylist()), actionCollection());
108
    deleteItemPlaylistAction = new KAction(i18n("Remove"), "edittrash", 0, splitter, SLOT(deleteItemPlaylist()), 
109 110
					   actionCollection(), "deleteItemPlaylist");

111
    KStdAction::quit(this, SLOT(close()), actionCollection());
112 113

    // edit menu
114
    KStdAction::cut(this, SLOT(cut()), actionCollection());
115 116 117 118
    KStdAction::copy(this, SLOT(copy()), actionCollection());
    KStdAction::paste(this, SLOT(paste()), actionCollection());
    new KAction(i18n("Clear"), "editclear", 0, this, SLOT(clear()), actionCollection(), "clear");
    KStdAction::selectAll(this, SLOT(selectAll()), actionCollection());
119

120
    // view menu
121
    showEditorAction = new KToggleAction(i18n("Show Tag Editor"), "edit", 0, actionCollection(), "showEditor");
122 123
    connect(showEditorAction, SIGNAL(toggled(bool)), splitter, SLOT(setEditorVisible(bool)));
    KStdAction::redisplay(splitter, SLOT(refresh()), actionCollection());
124 125
    actionCollection()->insert(splitter->columnVisibleAction());
    
126
    // play menu
127
    randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
Scott Wheeler's avatar
Scott Wheeler committed
128 129 130 131 132
    playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(play()), actionCollection(), "play");
    pauseAction = new KAction(i18n("P&ause"), "player_pause", 0, this, SLOT(pause()), actionCollection(), "pause");
    stopAction = new KAction(i18n("&Stop"), "player_stop", 0, this, SLOT(stop()), actionCollection(), "stop");
    backAction = new KAction(i18n("Skip &Back"), "player_start", 0, this, SLOT(back()), actionCollection(), "back");
    forwardAction = new KAction(i18n("Skip &Forward"), "player_end", 0, this, SLOT(forward()), actionCollection(), "forward");
133

134
    // tagger menu
135
    new KAction(i18n("Save"), "filesave", "CTRL+t", splitter, SLOT(saveItem()), actionCollection(), "saveItem");
136
    new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(removeSelectedItems()), actionCollection(), "removeItem");
137
    
138
    // settings menu
139 140 141 142

    new KToggleAction(i18n("Show Menu Bar"), "CTRL+m", this, SLOT(slotToggleMenuBar()), actionCollection(), "toggleMenuBar");
    new KToggleAction(i18n("Show Tool Bar"), "CTRL+b", this, SLOT(slotToggleToolBar()), actionCollection(), "toggleToolBar");

Scott Wheeler's avatar
Scott Wheeler committed
143
    restoreOnLoadAction = new KToggleAction(i18n("Restore Playlists on Load"),  0, actionCollection(), "restoreOnLoad");
144

Scott Wheeler's avatar
Scott Wheeler committed
145
    toggleSystemTrayAction = new KToggleAction(i18n("Dock in System Tray"), 0, actionCollection(), "toggleSystemTray");
146
    connect(toggleSystemTrayAction, SIGNAL(toggled(bool)), this, SLOT(toggleSystemTray(bool)));
147

Scott Wheeler's avatar
Scott Wheeler committed
148
    new KAction(i18n("Genre List Editor..."), 0, this, SLOT(showGenreListEditor()), actionCollection(), "showGenreListEditor");
149

150

151 152 153 154 155
    outputSelectAction = Player::playerSelectAction(actionCollection());
    if(outputSelectAction) {
	outputSelectAction->setCurrentItem(0);
	connect(outputSelectAction, SIGNAL(activated(int)), this, SLOT(setOutput(int)));
    }
156

157 158 159 160
    // just in the toolbar
    sliderAction = new SliderAction(i18n("Track Position"), actionCollection(), "trackPositionAction");

    createGUI();
161 162 163 164

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

    connect(splitter, SIGNAL(playlistChanged()), this, SLOT(playlistChanged()));
167 168
}

169
void JuK::setupSystemTray()
170 171 172 173 174
{
    if(toggleSystemTrayAction && toggleSystemTrayAction->isChecked()) {
	systemTray = new SystemTray(this, "systemTray");
	systemTray->show();
	
Scott Wheeler's avatar
Scott Wheeler committed
175 176 177 178 179
	connect(systemTray, SIGNAL(play()),    this, SLOT(play()));
	connect(systemTray, SIGNAL(stop()),    this, SLOT(stop()));
	connect(systemTray, SIGNAL(pause()),   this, SLOT(pause()));
	connect(systemTray, SIGNAL(back()),    this, SLOT(back()));
	connect(systemTray, SIGNAL(forward()), this, SLOT(forward()));
180 181

	connect(this, SIGNAL(newSongSignal(const QString&)), systemTray, SLOT(slotNewSong(const QString&)));
182
	
183
	if(player && player->paused())
184
	    systemTray->slotPause();
185
	else if(player && player->playing())
186 187 188 189
	    systemTray->slotPlay();
    }
    else
	systemTray = 0;
190 191
}

192 193
void JuK::setupPlayer()
{
194 195
    trackPositionDragging = false;
    noSeek = false;
196 197
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
198 199
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
200

201
    playTimer = new QTimer(this);
202 203 204 205 206 207 208
    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);
209

210 211
        connect(sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
    }
212 213 214 215 216 217 218 219
    
    int playerType = 0;
    if(outputSelectAction) {
	playerType = outputSelectAction->currentItem();
	connect(outputSelectAction, SIGNAL(activated(int)), this, SLOT(setOutput(int)));
    }

    player = Player::createPlayer(playerType);
220 221
}

222

223 224 225 226 227 228 229 230
void JuK::processArgs()
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
    
    for(int i = 0; i < args->count(); i++)
	files.append(args->arg(i));

231
    splitter->open(files);
232 233
}

234 235 236 237 238 239 240 241 242 243 244 245 246
/**
 * These are settings that need to be know before setting up the GUI.
 */

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

247 248
void JuK::readConfig()
{
249 250 251
    // Automagically save and restore many window settings.
    setAutoSaveSettings();

252 253 254 255 256 257 258
    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);
        }
259 260 261 262
	if(randomPlayAction) {
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
	    randomPlayAction->setChecked(randomPlay);
	}
263
    }
264
    { // view settings
265
        KConfigGroupSaver saver(config, "View");
266
	bool showEditor = config->readBoolEntry("ShowEditor", false);
267 268 269
	showEditorAction->setChecked(showEditor);
	splitter->setEditorVisible(showEditor);
    }
270 271
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
272 273
	bool dockInSystemTray = config->readBoolEntry("DockInSystemTray", true);
	toggleSystemTrayAction->setChecked(dockInSystemTray);
274 275 276 277
	
	if(outputSelectAction)
	    outputSelectAction->setCurrentItem(config->readNumEntry("MediaSystem", 0));
	
278
    }
279 280 281 282

    if(restoreOnLoadAction)
	restoreOnLoadAction->setChecked(restore);
    
283 284 285 286
}

void JuK::saveConfig()
{
287 288 289 290 291
    KConfig *config = KGlobal::config();
    { // player settings
        KConfigGroupSaver saver(config, "Player");
        if(sliderAction && sliderAction->getVolumeSlider())
            config->writeEntry("Volume", sliderAction->getVolumeSlider()->value());
292 293
	if(randomPlayAction)
	    config->writeEntry("RandomPlay", randomPlayAction->isChecked());
294
    }
295 296 297 298
    { // view settings
        KConfigGroupSaver saver(config, "View");
	config->writeEntry("ShowEditor", showEditorAction->isChecked());
    }
299 300 301 302
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	if(restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", restoreOnLoadAction->isChecked());
303 304
	if(toggleSystemTrayAction)
	    config->writeEntry("DockInSystemTray", toggleSystemTrayAction->isChecked());
305 306
	if(outputSelectAction)
	    config->writeEntry("MediaSystem", outputSelectAction->currentItem());
307
    }
308 309
}

310 311
bool JuK::queryClose()
{
Scott Wheeler's avatar
Scott Wheeler committed
312
    stop();
313
    delete player;
314
    Cache::instance()->save();
315
    saveConfig();
316
    delete splitter;
317
    return true;
318 319
}

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
void JuK::invokeEditSlot( const char *slotName, const char *slot )
{
    QObject *object = focusWidget();
    
    if(!object || !slotName || !slot)
	return;
    
    QMetaObject *meta = object->metaObject();
    QStrList l = meta->slotNames(true);
  
    if(l.find(slotName) == -1)
	return;
    
    connect(this, SIGNAL( editSignal() ), object, slot);
    emit editSignal();
    disconnect(this, SIGNAL(editSignal()), object, slot);
}

338 339 340 341 342 343 344 345 346 347 348 349
QString JuK::playingString() const
{
    QString s;
    
    if(splitter->playingArtist().isEmpty())
	s = splitter->playingTrack().simplifyWhiteSpace();
    else
	s = splitter->playingArtist().simplifyWhiteSpace() + " - " + splitter->playingTrack().simplifyWhiteSpace();

    return s;
}

350 351 352 353
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

354
void JuK::playlistChanged()
355
{
356
    if(splitter->collectionListSelected()) {
357 358 359 360 361 362 363 364 365 366 367
	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);
    }
368 369 370 371 372 373

    updatePlaylistInfo();
}

void JuK::updatePlaylistInfo()
{
374
    statusLabel->setPlaylistInfo(splitter->selectedPlaylistName(), splitter->selectedPlaylistCount());
375 376
}

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
////////////////////////////////////////////////////////////////////////////////
// edit menu
////////////////////////////////////////////////////////////////////////////////

void JuK::cut()
{
    invokeEditSlot("cut()", SLOT(cut()));
}

void JuK::copy()
{
    invokeEditSlot("copy()", SLOT(copy()));
}

void JuK::paste()
{
    invokeEditSlot("paste()", SLOT(paste()));
}

void JuK::clear()
{
    invokeEditSlot("clear()", SLOT(clear()));
}

void JuK::selectAll()
{
    invokeEditSlot("selectAll()", SLOT(selectAll()));
}

406 407 408 409
////////////////////////////////////////////////////////////////////////////////
// player menu
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
410
void JuK::play()
411
{
412 413 414 415 416
    if(!player)
	return;

    if(player->paused()) {
        player->play();
417 418 419 420

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

421
        if(player->playing()) {
422 423 424
            pauseAction->setEnabled(true);
            stopAction->setEnabled(true);
            playTimer->start(pollInterval);
425 426
	    if(systemTray)
		systemTray->slotPlay();
427 428
        }
    }
429 430
    else if(player->playing())
	player->seekPosition(0);
431
    else
Scott Wheeler's avatar
Scott Wheeler committed
432
	play(splitter->playNextFile(randomPlayAction->isChecked()));
433 434
}

Scott Wheeler's avatar
Scott Wheeler committed
435
void JuK::pause()
436
{
437 438 439
    if(!player)
	return;

440
    playTimer->stop();
441
    player->pause();
442
    pauseAction->setEnabled(false);
443 444
    if(systemTray)
	systemTray->slotPause();
445 446
}

Scott Wheeler's avatar
Scott Wheeler committed
447
void JuK::stop()
448
{
449 450 451
    if(!player)
	return;

452
    playTimer->stop();
453
    player->stop();
454

455 456
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
457 458
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
459

460 461
    sliderAction->getTrackPositionSlider()->setValue(0);
    sliderAction->getTrackPositionSlider()->setEnabled(false);
462 463

    splitter->stop();
464

465
    statusLabel->clear();
466 467 468
    
    if(systemTray)
	systemTray->slotStop();
469 470
}

Scott Wheeler's avatar
Scott Wheeler committed
471
void JuK::back()
472
{
Scott Wheeler's avatar
Scott Wheeler committed
473
    play(splitter->playPreviousFile(randomPlayAction->isChecked()));
474 475
}

Scott Wheeler's avatar
Scott Wheeler committed
476
void JuK::forward()
477
{
Scott Wheeler's avatar
Scott Wheeler committed
478
    play(splitter->playNextFile(randomPlayAction->isChecked()));
479 480
}

481 482 483 484 485 486 487 488 489
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

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

491 492 493 494 495
void JuK::toggleSystemTray(bool enabled)
{
    if(enabled && !systemTray)
	setupSystemTray();
    else if(!enabled && systemTray) {
496
	delete systemTray;
497 498 499 500
	systemTray = 0;
    }
}

501 502
void JuK::setOutput(int output)
{
Scott Wheeler's avatar
Scott Wheeler committed
503
    stop();
504
    delete player;
505 506 507
    player = Player::createPlayer(output);
}

508 509 510 511
////////////////////////////////////////////////////////////////////////////////
// additional player slots
////////////////////////////////////////////////////////////////////////////////

512 513
void JuK::trackPositionSliderClick()
{
514
    trackPositionDragging = true;
515 516 517 518
}

void JuK::trackPositionSliderRelease()
{
519 520 521
    if(!player)
	return;

522
    trackPositionDragging = false;
523
    player->seekPosition(sliderAction->getTrackPositionSlider()->value());
524 525 526 527
}

void JuK::trackPositionSliderUpdate(int position)
{
528 529 530 531 532
    if(!player)
	return;

    if(player->playing() && !trackPositionDragging && !noSeek)
        player->seekPosition(position);
533 534
}

535 536
// This method is called when the play timer has expired.

537 538
void JuK::pollPlay()
{
539 540 541
    if(!player)
	return;

542 543
    // Our locking mechanism.  Since this method adjusts the play slider, we 
    // want to make sure that our adjustments
544
    noSeek = true;
545

546
    if(!player->playing()) {
547

548
        playTimer->stop();
549

550
	if(!player->paused())
Scott Wheeler's avatar
Scott Wheeler committed
551
	    play(splitter->playNextFile(randomPlayAction->isChecked()));
552

553
    }
554
    else if(!trackPositionDragging) {
555 556 557
        sliderAction->getTrackPositionSlider()->setValue(player->position());
	statusLabel->setItemTotalTime(player->totalTime());
	statusLabel->setItemCurrentTime(player->currentTime());
558 559 560 561 562 563
    }

    // 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.
564

565
    if(player->playing() && player->totalTime() > 0 && float(player->totalTime() - player->currentTime()) < pollInterval * 2)
566
        playTimer->changeInterval(50);
567

568
    noSeek = false;
569 570 571 572
}

void JuK::setVolume(int volume)
{
573
    if(player && sliderAction && sliderAction->getVolumeSlider() &&
574 575
       sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && sliderAction->getVolumeSlider()->maxValue() >= volume)
576
    {
577
        player->setVolume(float(volume) / float(sliderAction->getVolumeSlider()->maxValue()));
578 579
    }
}
580

Scott Wheeler's avatar
Scott Wheeler committed
581
void JuK::play(const QString &file)
582
{
583 584 585
    if(!player)
	return;

586
    float volume = float(sliderAction->getVolumeSlider()->value()) / float(sliderAction->getVolumeSlider()->maxValue());
587

588 589
    if(player->paused())
	player->stop();
590
    
591
    player->play(file, volume);
592

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

595
    if(player->playing()) {
596 597 598 599 600 601
	pauseAction->setEnabled(true);
	stopAction->setEnabled(true);
	
	backAction->setEnabled(true);
	forwardAction->setEnabled(true);
	
602
	sliderAction->getTrackPositionSlider()->setValue(0);
603 604
	sliderAction->getTrackPositionSlider()->setEnabled(true);
	playTimer->start(pollInterval);
605

606 607 608
	statusLabel->setPlayingItemInfo(playingString(), splitter->playingList());

	emit newSongSignal(playingString());
609

610 611
	if(systemTray)
	    systemTray->slotPlay();
612
    }
613
    else
Scott Wheeler's avatar
Scott Wheeler committed
614
	stop();
615
}
616

617
#include "juk.moc"