juk.cpp 16.5 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 "juk.h"
32
#include "slideraction.h"
33
#include "cache.h"
34
#include "statuslabel.h"
35 36
#include "splashscreen.h"
#include "genrelisteditor.h"
37
#include "systemtray.h"
38 39 40 41 42

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

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

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

58
    SplashScreen::finishedLoading();
59 60 61 62
}

JuK::~JuK()
{
63
    delete(player);
64 65 66 67 68 69
}

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

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

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

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

83
    connect(splitter, SIGNAL(selectedPlaylistCountChanged(int)), statusLabel, SLOT(setPlaylistCount(int)));
84
    connect(statusLabel, SIGNAL(jumpButtonClicked()), splitter, SLOT(selectPlaying()));
85

86 87
    updatePlaylistInfo();

88 89 90
    splitter->setFocus();
}

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

    renamePlaylistAction = new KAction(i18n("Rename..."), 0, splitter, SLOT(renamePlaylist()), 
				       actionCollection(), "renamePlaylist");
    new KAction(i18n("Duplicate..."), "editcopy", 0, splitter, SLOT(duplicatePlaylist()), actionCollection(), "duplicatePlaylist");
101
    
102 103 104 105 106
    savePlaylistAction = KStdAction::save(splitter, SLOT(savePlaylist()), actionCollection());
    saveAsPlaylistAction = KStdAction::saveAs(splitter, SLOT(saveAsPlaylist()), actionCollection());
    deleteItemPlaylistAction = new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(deleteItemPlaylist()), 
					   actionCollection(), "deleteItemPlaylist");

107
    KStdAction::quit(this, SLOT(close()), actionCollection());
108 109

    // edit menu
110 111 112
    KStdAction::cut(this, SLOT(cut()), actionCollection());
    KStdAction::copy(splitter, SLOT(copy()), actionCollection());
    KStdAction::paste(splitter, SLOT(paste()), actionCollection());
113
    KStdAction::selectAll(splitter, SLOT(selectAll()), actionCollection());
114

115
    // view menu
116
    showEditorAction = new KToggleAction(i18n("Show Tag Editor"), "edit", 0, actionCollection(), "showEditor");
117 118
    connect(showEditorAction, SIGNAL(toggled(bool)), splitter, SLOT(setEditorVisible(bool)));
    KStdAction::redisplay(splitter, SLOT(refresh()), actionCollection());
119

120
    // play menu
121
    randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
122
    playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(playFile()), actionCollection(), "playFile");
123 124
    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");
125 126
    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");
127

128
    // tagger menu
129
    new KAction(i18n("Save"), "filesave", "CTRL+t", splitter, SLOT(saveItem()), actionCollection(), "saveItem");
130
    new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(removeSelectedItems()), actionCollection(), "removeItem");
131
    
132
    // settings menu
Scott Wheeler's avatar
Scott Wheeler committed
133
    restoreOnLoadAction = new KToggleAction(i18n("Restore Playlists on Load"),  0, actionCollection(), "restoreOnLoad");
134

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

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

140

141 142 143 144 145
    outputSelectAction = Player::playerSelectAction(actionCollection());
    if(outputSelectAction) {
	outputSelectAction->setCurrentItem(0);
	connect(outputSelectAction, SIGNAL(activated(int)), this, SLOT(setOutput(int)));
    }
146

147 148 149 150
    // just in the toolbar
    sliderAction = new SliderAction(i18n("Track Position"), actionCollection(), "trackPositionAction");

    createGUI();
151 152 153 154

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

    connect(splitter, SIGNAL(playlistChanged()), this, SLOT(playlistChanged()));
157 158
}

159
void JuK::setupSystemTray()
160 161 162 163 164 165 166 167 168 169 170
{
    if(toggleSystemTrayAction && toggleSystemTrayAction->isChecked()) {
	systemTray = new SystemTray(this, "systemTray");
	systemTray->show();
	
	connect(systemTray, SIGNAL(play()),    this, SLOT(playFile()));
	connect(systemTray, SIGNAL(stop()),    this, SLOT(stopFile()));
	connect(systemTray, SIGNAL(pause()),   this, SLOT(pauseFile()));
	connect(systemTray, SIGNAL(back()),    this, SLOT(backFile()));
	connect(systemTray, SIGNAL(forward()), this, SLOT(forwardFile()));
	
171
	if(player && player->paused())
172
	    systemTray->slotPause();
173
	else if(player && player->playing())
174 175 176 177
	    systemTray->slotPlay();
    }
    else
	systemTray = 0;
178 179
}

180 181
void JuK::setupPlayer()
{
182 183
    player = Player::createPlayer();

184 185
    trackPositionDragging = false;
    noSeek = false;
186 187
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
188 189
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
190

191
    playTimer = new QTimer(this);
192 193 194 195 196 197 198
    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);
199

200 201
        connect(sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
    }
202 203
}

204

205 206 207 208 209 210 211 212
void JuK::processArgs()
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
    
    for(int i = 0; i < args->count(); i++)
	files.append(args->arg(i));

213
    splitter->open(files);
214 215
}

216 217 218 219 220 221 222 223 224 225 226 227 228
/**
 * 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);
    }
}

229 230
void JuK::readConfig()
{
231 232 233
    // Automagically save and restore many window settings.
    setAutoSaveSettings();

234 235 236 237 238 239 240
    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);
        }
241 242 243 244
	if(randomPlayAction) {
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
	    randomPlayAction->setChecked(randomPlay);
	}
245
    }
246
    { // view settings
247
        KConfigGroupSaver saver(config, "View");
248
	bool showEditor = config->readBoolEntry("ShowEditor", false);
249 250 251
	showEditorAction->setChecked(showEditor);
	splitter->setEditorVisible(showEditor);
    }
252 253
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
254 255
	bool dockInSystemTray = config->readBoolEntry("DockInSystemTray", true);
	toggleSystemTrayAction->setChecked(dockInSystemTray);
256
    }
257 258 259 260

    if(restoreOnLoadAction)
	restoreOnLoadAction->setChecked(restore);
    
261 262 263 264
}

void JuK::saveConfig()
{
265 266 267 268 269
    KConfig *config = KGlobal::config();
    { // player settings
        KConfigGroupSaver saver(config, "Player");
        if(sliderAction && sliderAction->getVolumeSlider())
            config->writeEntry("Volume", sliderAction->getVolumeSlider()->value());
270 271
	if(randomPlayAction)
	    config->writeEntry("RandomPlay", randomPlayAction->isChecked());
272
    }
273 274 275 276
    { // view settings
        KConfigGroupSaver saver(config, "View");
	config->writeEntry("ShowEditor", showEditorAction->isChecked());
    }
277 278 279 280
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	if(restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", restoreOnLoadAction->isChecked());
281 282
	if(toggleSystemTrayAction)
	    config->writeEntry("DockInSystemTray", toggleSystemTrayAction->isChecked());
283
    }
284 285
}

286 287
bool JuK::queryClose()
{
288
    Cache::instance()->save();
289
    saveConfig();
290
    delete(splitter);
291 292 293
    return(true);
}

294 295 296 297
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

298
void JuK::playlistChanged()
299
{
300
    if(splitter->collectionListSelected()) {
301 302 303 304 305 306 307 308 309 310 311
	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);
    }
312 313 314 315 316 317

    updatePlaylistInfo();
}

void JuK::updatePlaylistInfo()
{
318
    statusLabel->setPlaylistInfo(splitter->selectedPlaylistName(), splitter->selectedPlaylistCount());
319 320 321 322 323 324 325 326
}

////////////////////////////////////////////////////////////////////////////////
// player menu
////////////////////////////////////////////////////////////////////////////////

void JuK::playFile()
{
327 328 329 330 331
    if(!player)
	return;

    if(player->paused()) {
        player->play();
332 333 334 335

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

336
        if(player->playing()) {
337 338 339
            pauseAction->setEnabled(true);
            stopAction->setEnabled(true);
            playTimer->start(pollInterval);
340 341
	    if(systemTray)
		systemTray->slotPlay();
342 343
        }
    }
344 345
    else if(player->playing())
	player->seekPosition(0);
346 347
    else
	playFile(splitter->playNextFile(randomPlayAction->isChecked()));
348 349 350 351
}

void JuK::pauseFile()
{
352 353 354
    if(!player)
	return;

355
    playTimer->stop();
356
    player->pause();
357
    pauseAction->setEnabled(false);
358 359
    if(systemTray)
	systemTray->slotPause();
360 361 362 363
}

void JuK::stopFile()
{
364 365 366
    if(!player)
	return;

367
    playTimer->stop();
368
    player->stop();
369

370 371
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
372 373
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
374

375 376
    sliderAction->getTrackPositionSlider()->setValue(0);
    sliderAction->getTrackPositionSlider()->setEnabled(false);
377 378

    splitter->stop();
379

380
    statusLabel->clear();
381 382 383
    
    if(systemTray)
	systemTray->slotStop();
384 385
}

386 387
void JuK::backFile()
{
388
    playFile(splitter->playPreviousFile(randomPlayAction->isChecked()));
389 390 391 392
}

void JuK::forwardFile()
{
393
    playFile(splitter->playNextFile(randomPlayAction->isChecked()));
394 395
}

396 397 398 399 400 401 402 403 404
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

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

406 407 408 409 410 411 412 413 414 415
void JuK::toggleSystemTray(bool enabled)
{
    if(enabled && !systemTray)
	setupSystemTray();
    else if(!enabled && systemTray) {
	delete(systemTray);
	systemTray = 0;
    }
}

416 417 418 419 420 421 422
void JuK::setOutput(int output)
{
    stopFile();
    delete(player);
    player = Player::createPlayer(output);
}

423 424 425 426
////////////////////////////////////////////////////////////////////////////////
// additional player slots
////////////////////////////////////////////////////////////////////////////////

427 428
void JuK::trackPositionSliderClick()
{
429
    trackPositionDragging = true;
430 431 432 433
}

void JuK::trackPositionSliderRelease()
{
434 435 436
    if(!player)
	return;

437
    trackPositionDragging = false;
438
    player->seekPosition(sliderAction->getTrackPositionSlider()->value());
439 440 441 442
}

void JuK::trackPositionSliderUpdate(int position)
{
443 444 445 446 447
    if(!player)
	return;

    if(player->playing() && !trackPositionDragging && !noSeek)
        player->seekPosition(position);
448 449
}

450 451
// This method is called when the play timer has expired.

452 453
void JuK::pollPlay()
{
454 455 456
    if(!player)
	return;

457 458
    // Our locking mechanism.  Since this method adjusts the play slider, we 
    // want to make sure that our adjustments
459
    noSeek = true;
460

461
    if(!player->playing()) {
462

463
        playTimer->stop();
464

465
	if(!player->paused())
466 467
	    playFile(splitter->playNextFile(randomPlayAction->isChecked()));

468
    }
469
    else if(!trackPositionDragging) {
470 471 472
        sliderAction->getTrackPositionSlider()->setValue(player->position());
	statusLabel->setItemTotalTime(player->totalTime());
	statusLabel->setItemCurrentTime(player->currentTime());
473 474 475 476 477 478
    }

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

480
    if(player->playing() && player->totalTime() > 0 && float(player->totalTime() - player->currentTime()) < pollInterval * 2)
481
        playTimer->changeInterval(50);
482

483
    noSeek = false;
484 485 486 487
}

void JuK::setVolume(int volume)
{
488
    if(player && sliderAction && sliderAction->getVolumeSlider() &&
489 490
       sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && sliderAction->getVolumeSlider()->maxValue() >= volume)
491
    {
492
        player->setVolume(float(volume) / float(sliderAction->getVolumeSlider()->maxValue()));
493 494
    }
}
495

496
void JuK::playFile(const QString &file)
497
{
498 499 500
    if(!player)
	return;

501
    float volume = float(sliderAction->getVolumeSlider()->value()) / float(sliderAction->getVolumeSlider()->maxValue());
502

503 504
    if(player->paused())
	player->stop();
505
    
506
    player->play(file, volume);
507

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

510
    if(player->playing()) {
511 512 513 514 515 516
	pauseAction->setEnabled(true);
	stopAction->setEnabled(true);
	
	backAction->setEnabled(true);
	forwardAction->setEnabled(true);
	
517
	sliderAction->getTrackPositionSlider()->setValue(0);
518 519
	sliderAction->getTrackPositionSlider()->setEnabled(true);
	playTimer->start(pollInterval);
520

521
	statusLabel->setPlayingItemInfo(splitter->playingTrack(), splitter->playingArtist(), splitter->playingList());
522

523 524
	if(systemTray)
	    systemTray->slotPlay();
525
    }
526 527
    else
	stopFile();
528
}
529

530
#include "juk.moc"