juk.cpp 19.2 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
    playlistChanged();
56
    readConfig();
57
    setupPlayer();
58
    setupSystemTray();
59
    processArgs();
60

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

JuK::~JuK()
{
66

67 68 69 70 71 72
}

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

73 74
void JuK::setupLayout()
{
75 76
    m_splitter = new PlaylistSplitter(this, m_restore, "playlistSplitter");
    setCentralWidget(m_splitter);
77 78

    // playlist item activation connection
79 80
    connect(m_splitter, SIGNAL(signalDoubleClicked()), this, SLOT(playSelectedFile()));
    connect(m_splitter, SIGNAL(signalListBoxDoubleClicked()), this, SLOT(playFirstFile()));
81

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

86 87
    connect(m_splitter, SIGNAL(signalSelectedPlaylistCountChanged(int)), m_statusLabel, SLOT(setPlaylistCount(int)));
    connect(m_statusLabel, SIGNAL(jumpButtonClicked()), m_splitter, SLOT(slotSelectPlaying()));
88

89
    m_splitter->setFocus();
90

91
    resize(750, 500);
92 93
}

94 95
void JuK::setupActions()
{
96
    // file menu
97 98 99
    KStdAction::openNew(m_splitter, SLOT(slotCreatePlaylist()), actionCollection());
    KStdAction::open(m_splitter, SLOT(slotOpen()), actionCollection());
    new KAction(i18n("Open &Directory..."), "fileopen", 0, m_splitter, SLOT(slotOpenDirectory()), actionCollection(), "openDirectory");
100

101
    m_renamePlaylistAction = new KAction(i18n("Rename..."), 0, m_splitter, SLOT(slotRenamePlaylist()), 
102
				       actionCollection(), "renamePlaylist");
103
    new KAction(i18n("Duplicate..."), "editcopy", 0, m_splitter, SLOT(slotDuplicatePlaylist()), actionCollection(), "duplicatePlaylist");
104
    
105 106 107
    m_savePlaylistAction = KStdAction::save(m_splitter, SLOT(slotSavePlaylist()), actionCollection());
    m_saveAsPlaylistAction = KStdAction::saveAs(m_splitter, SLOT(slotSaveAsPlaylist()), actionCollection());
    m_deleteItemPlaylistAction = new KAction(i18n("Remove"), "edittrash", 0, m_splitter, SLOT(slotDeletePlaylist()), 
108 109
					   actionCollection(), "deleteItemPlaylist");

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

    // edit menu
113
    KStdAction::cut(this, SLOT(cut()), actionCollection());
114 115 116 117
    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());
118

119
    // view menu
120 121 122 123
    m_showEditorAction = new KToggleAction(i18n("Show Tag Editor"), "edit", 0, actionCollection(), "showEditor");
    connect(m_showEditorAction, SIGNAL(toggled(bool)), m_splitter, SLOT(slotSetEditorVisible(bool)));
    KStdAction::redisplay(m_splitter, SLOT(slotRefresh()), actionCollection());
    actionCollection()->insert(m_splitter->columnVisibleAction());
124
    
125
    // play menu
126 127 128 129 130 131
    m_randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
    m_playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(play()), actionCollection(), "play");
    m_pauseAction = new KAction(i18n("P&ause"), "player_pause", 0, this, SLOT(pause()), actionCollection(), "pause");
    m_stopAction = new KAction(i18n("&Stop"), "player_stop", 0, this, SLOT(stop()), actionCollection(), "stop");
    m_backAction = new KAction(i18n("Skip &Back"), "player_start", 0, this, SLOT(back()), actionCollection(), "back");
    m_forwardAction = new KAction(i18n("Skip &Forward"), "player_end", 0, this, SLOT(forward()), actionCollection(), "forward");
132

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

    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");

142
    m_restoreOnLoadAction = new KToggleAction(i18n("Restore Playlists on Load"),  0, actionCollection(), "restoreOnLoad");
143

144 145
    m_toggleSystemTrayAction = new KToggleAction(i18n("Dock in System Tray"), 0, actionCollection(), "toggleSystemTray");
    connect(m_toggleSystemTrayAction, SIGNAL(toggled(bool)), this, SLOT(toggleSystemTray(bool)));
146

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

149

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

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

    createGUI();
160 161

    // set the slider to the proper orientation and make it stay that way
162 163
    m_sliderAction->updateOrientation();
    connect(this, SIGNAL(dockWindowPositionChanged(QDockWindow *)), m_sliderAction, SLOT(updateOrientation(QDockWindow *)));
164

165
    connect(m_splitter, SIGNAL(signalPlaylistChanged()), this, SLOT(playlistChanged()));
166 167
}

168
void JuK::setupSystemTray()
169
{
170 171 172
    if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) {
	m_systemTray = new SystemTray(this, "systemTray");
	m_systemTray->show();
173
	
174 175 176 177 178
	connect(m_systemTray, SIGNAL(play()),    this, SLOT(play()));
	connect(m_systemTray, SIGNAL(stop()),    this, SLOT(stop()));
	connect(m_systemTray, SIGNAL(pause()),   this, SLOT(pause()));
	connect(m_systemTray, SIGNAL(back()),    this, SLOT(back()));
	connect(m_systemTray, SIGNAL(forward()), this, SLOT(forward()));
179

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

191 192
void JuK::setupPlayer()
{
193 194 195 196 197 198
    m_trackPositionDragging = false;
    m_noSeek = false;
    m_pauseAction->setEnabled(false);
    m_stopAction->setEnabled(false);
    m_backAction->setEnabled(false);
    m_forwardAction->setEnabled(false);
199

200 201
    m_playTimer = new QTimer(this);
    connect(m_playTimer, SIGNAL(timeout()), this, SLOT(pollPlay()));
202

203 204 205 206 207
    if(m_sliderAction && m_sliderAction->getTrackPositionSlider() && m_sliderAction->getVolumeSlider()) {
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(valueChanged(int)), this, SLOT(trackPositionSliderUpdate(int)));
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(sliderPressed()), this, SLOT(trackPositionSliderClick()));
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(sliderReleased()), this, SLOT(trackPositionSliderRelease()));
        m_sliderAction->getTrackPositionSlider()->setEnabled(false);
208

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

218
    m_player = Player::createPlayer(playerType);
219 220
}

221

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

230
    m_splitter->open(files);
231 232
}

233 234 235 236 237 238 239 240 241
/**
 * 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");
242
	m_restore = config->readBoolEntry("RestoreOnLoad", true);
243 244 245
    }
}

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

251
    KConfig *config = KGlobal::config();
252
    { // m_player settings
253
        KConfigGroupSaver saver(config, "Player");
254 255 256
        if(m_sliderAction && m_sliderAction->getVolumeSlider()) {
            int volume = config->readNumEntry("Volume", m_sliderAction->getVolumeSlider()->maxValue());
            m_sliderAction->getVolumeSlider()->setValue(volume);
257
        }
258
	if(m_randomPlayAction) {
259
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
260
	    m_randomPlayAction->setChecked(randomPlay);
261
	}
262
    }
263
    { // view settings
264
        KConfigGroupSaver saver(config, "View");
265
	bool showEditor = config->readBoolEntry("ShowEditor", false);
266 267
	m_showEditorAction->setChecked(showEditor);
	m_splitter->slotSetEditorVisible(showEditor);
268
    }
269 270
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
271
	bool dockInSystemTray = config->readBoolEntry("DockInSystemTray", true);
272
	m_toggleSystemTrayAction->setChecked(dockInSystemTray);
273
	
274 275
	if(m_outputSelectAction)
	    m_outputSelectAction->setCurrentItem(config->readNumEntry("MediaSystem", 0));
276
	
277
    }
278

279 280
    if(m_restoreOnLoadAction)
	m_restoreOnLoadAction->setChecked(m_restore);
281
    
282 283 284 285
}

void JuK::saveConfig()
{
286
    KConfig *config = KGlobal::config();
287
    { // m_player settings
288
        KConfigGroupSaver saver(config, "Player");
289 290 291 292
        if(m_sliderAction && m_sliderAction->getVolumeSlider())
            config->writeEntry("Volume", m_sliderAction->getVolumeSlider()->value());
	if(m_randomPlayAction)
	    config->writeEntry("RandomPlay", m_randomPlayAction->isChecked());
293
    }
294 295
    { // view settings
        KConfigGroupSaver saver(config, "View");
296
	config->writeEntry("ShowEditor", m_showEditorAction->isChecked());
297
    }
298 299
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
300 301 302 303 304 305
	if(m_restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", m_restoreOnLoadAction->isChecked());
	if(m_toggleSystemTrayAction)
	    config->writeEntry("DockInSystemTray", m_toggleSystemTrayAction->isChecked());
	if(m_outputSelectAction)
	    config->writeEntry("MediaSystem", m_outputSelectAction->currentItem());
306
    }
307 308
}

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

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
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);
}

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

    return s;
}

349 350 351 352
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

353
void JuK::playlistChanged()
354
{
355 356 357 358 359
    if(m_splitter->collectionListSelected()) {
	m_savePlaylistAction->setEnabled(false);
	m_saveAsPlaylistAction->setEnabled(false);
	m_renamePlaylistAction->setEnabled(false);
	m_deleteItemPlaylistAction->setEnabled(false);	
360 361
    }
    else {
362 363 364 365
	m_savePlaylistAction->setEnabled(true);
	m_saveAsPlaylistAction->setEnabled(true);
	m_renamePlaylistAction->setEnabled(true);
	m_deleteItemPlaylistAction->setEnabled(true);
366
    }
367 368 369 370 371 372

    updatePlaylistInfo();
}

void JuK::updatePlaylistInfo()
{
373
    m_statusLabel->setPlaylistInfo(m_splitter->selectedPlaylistName(), m_splitter->selectedPlaylistCount());
374 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
////////////////////////////////////////////////////////////////////////////////
// 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()));
}

405
////////////////////////////////////////////////////////////////////////////////
406
// m_player menu
407 408
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
409
void JuK::play()
410
{
411
    if(!m_player)
412 413
	return;

414 415
    if(m_player->paused()) {
        m_player->play();
416

417
	// Here, before doing anything, we want to make sure that the m_player did
418 419
	// in fact start.

420 421 422 423 424 425
        if(m_player->playing()) {
            m_pauseAction->setEnabled(true);
            m_stopAction->setEnabled(true);
            m_playTimer->start(pollInterval);
	    if(m_systemTray)
		m_systemTray->slotPlay();
426 427
        }
    }
428 429
    else if(m_player->playing())
	m_player->seekPosition(0);
430
    else
431
	play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
432 433
}

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

439 440 441 442 443
    m_playTimer->stop();
    m_player->pause();
    m_pauseAction->setEnabled(false);
    if(m_systemTray)
	m_systemTray->slotPause();
444 445
}

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

451 452
    m_playTimer->stop();
    m_player->stop();
453

454 455 456 457
    m_pauseAction->setEnabled(false);
    m_stopAction->setEnabled(false);
    m_backAction->setEnabled(false);
    m_forwardAction->setEnabled(false);
458

459 460
    m_sliderAction->getTrackPositionSlider()->setValue(0);
    m_sliderAction->getTrackPositionSlider()->setEnabled(false);
461

462
    m_splitter->stop();
463

464
    m_statusLabel->clear();
465
    
466 467
    if(m_systemTray)
	m_systemTray->slotStop();
468 469
}

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

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

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

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

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

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

507
////////////////////////////////////////////////////////////////////////////////
508
// additional m_player slots
509 510
////////////////////////////////////////////////////////////////////////////////

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

void JuK::trackPositionSliderRelease()
{
518
    if(!m_player)
519 520
	return;

521 522
    m_trackPositionDragging = false;
    m_player->seekPosition(m_sliderAction->getTrackPositionSlider()->value());
523 524 525 526
}

void JuK::trackPositionSliderUpdate(int position)
{
527
    if(!m_player)
528 529
	return;

530 531
    if(m_player->playing() && !m_trackPositionDragging && !m_noSeek)
        m_player->seekPosition(position);
532 533
}

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

536 537
void JuK::pollPlay()
{
538
    if(!m_player)
539 540
	return;

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

545
    if(!m_player->playing()) {
546

547
        m_playTimer->stop();
548

549 550
	if(!m_player->paused())
	    play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
551

552
    }
553 554 555 556
    else if(!m_trackPositionDragging) {
        m_sliderAction->getTrackPositionSlider()->setValue(m_player->position());
	m_statusLabel->setItemTotalTime(m_player->totalTime());
	m_statusLabel->setItemCurrentTime(m_player->currentTime());
557 558 559 560 561 562
    }

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

564 565
    if(m_player->playing() && m_player->totalTime() > 0 && float(m_player->totalTime() - m_player->currentTime()) < pollInterval * 2)
        m_playTimer->changeInterval(50);
566

567
    m_noSeek = false;
568 569 570 571
}

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

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

585
    float volume = float(m_sliderAction->getVolumeSlider()->value()) / float(m_sliderAction->getVolumeSlider()->maxValue());
586

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

592
    // Make sure that the m_player actually starts before doing anything.
593

594 595 596
    if(m_player->playing()) {
	m_pauseAction->setEnabled(true);
	m_stopAction->setEnabled(true);
597
	
598 599
	m_backAction->setEnabled(true);
	m_forwardAction->setEnabled(true);
600
	
601 602 603
	m_sliderAction->getTrackPositionSlider()->setValue(0);
	m_sliderAction->getTrackPositionSlider()->setEnabled(true);
	m_playTimer->start(pollInterval);
604

605
	m_statusLabel->setPlayingItemInfo(playingString(), m_splitter->playingList());
606 607

	emit newSongSignal(playingString());
608

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

616
#include "juk.moc"