juk.cpp 19.4 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
    slotPlaylistChanged();
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(slotPlaySelectedFile()));
    connect(m_splitter, SIGNAL(signalListBoxDoubleClicked()), this, SLOT(slotPlayFirstFile()));
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
    m_randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
127 128 129 130 131
    m_playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(slotPlay()), actionCollection(), "play");
    m_pauseAction = new KAction(i18n("P&ause"), "player_pause", 0, this, SLOT(slotPause()), actionCollection(), "pause");
    m_stopAction = new KAction(i18n("&Stop"), "player_stop", 0, this, SLOT(slotStop()), actionCollection(), "stop");
    m_backAction = new KAction(i18n("Skip &Back"), "player_start", 0, this, SLOT(slotBack()), actionCollection(), "back");
    m_forwardAction = new KAction(i18n("Skip &Forward"), "player_end", 0, this, SLOT(slotForward()), 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
    m_toggleSystemTrayAction = new KToggleAction(i18n("Dock in System Tray"), 0, actionCollection(), "toggleSystemTray");
145
    connect(m_toggleSystemTrayAction, SIGNAL(toggled(bool)), this, SLOT(slotToggleSystemTray(bool)));
146

147
    new KAction(i18n("Genre List Editor..."), 0, this, SLOT(slotShowGenreListEditor()), actionCollection(), "showGenreListEditor");
148

149

150 151 152
    m_outputSelectAction = Player::playerSelectAction(actionCollection());
    if(m_outputSelectAction) {
	m_outputSelectAction->setCurrentItem(0);
153
	connect(m_outputSelectAction, SIGNAL(activated(int)), this, SLOT(slotSetOutput(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->slotUpdateOrientation();
    connect(this, SIGNAL(dockWindowPositionChanged(QDockWindow *)), m_sliderAction, SLOT(slotUpdateOrientation(QDockWindow *)));
164

165
    connect(m_splitter, SIGNAL(signalPlaylistChanged()), this, SLOT(slotPlaylistChanged()));
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(signalPlay()),    this, SLOT(slotPlay()));
	connect(m_systemTray, SIGNAL(signalStop()),    this, SLOT(slotStop()));
	connect(m_systemTray, SIGNAL(signalPause()),   this, SLOT(slotPause()));
	connect(m_systemTray, SIGNAL(signalBack()),    this, SLOT(slotBack()));
	connect(m_systemTray, SIGNAL(signalForward()), this, SLOT(slotForward()));
179

180
	connect(this, SIGNAL(signalNewSong(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
    m_playTimer = new QTimer(this);
201
    connect(m_playTimer, SIGNAL(timeout()), this, SLOT(slotPollPlay()));
202

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

209
        connect(m_sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(slotSetVolume(int)));
210
    }
211 212
    
    int playerType = 0;
213 214
    if(m_outputSelectAction) {
	playerType = m_outputSelectAction->currentItem();
215
	connect(m_outputSelectAction, SIGNAL(activated(int)), this, SLOT(slotSetOutput(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()
{
311
    slotStop();
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
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;
    
332 333 334
    connect(this, SIGNAL(signalEdit()), object, slot);
    emit signalEdit();
    disconnect(this, SIGNAL(signalEdit()), object, slot);
335 336
}

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 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
void JuK::updatePlaylistInfo()
{
    m_statusLabel->setPlaylistInfo(m_splitter->selectedPlaylistName(), m_splitter->selectedPlaylistCount());
}

void JuK::play(const QString &file)
{
    if(!m_player)
	return;

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

    if(m_player->paused())
	m_player->stop();
    
    m_player->play(file, volume);

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

    if(m_player->playing()) {
	m_pauseAction->setEnabled(true);
	m_stopAction->setEnabled(true);
	
	m_backAction->setEnabled(true);
	m_forwardAction->setEnabled(true);
	
	m_sliderAction->getTrackPositionSlider()->setValue(0);
	m_sliderAction->getTrackPositionSlider()->setEnabled(true);
	m_playTimer->start(m_pollInterval);

	m_statusLabel->setPlayingItemInfo(playingString(), m_splitter->playingList());

	emit signalNewSong(playingString());

	if(m_systemTray)
	    m_systemTray->slotPlay();
    }
    else
	slotStop();
}

390 391 392 393
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

394
void JuK::slotPlaylistChanged()
395
{
396 397 398 399 400
    if(m_splitter->collectionListSelected()) {
	m_savePlaylistAction->setEnabled(false);
	m_saveAsPlaylistAction->setEnabled(false);
	m_renamePlaylistAction->setEnabled(false);
	m_deleteItemPlaylistAction->setEnabled(false);	
401 402
    }
    else {
403 404 405 406
	m_savePlaylistAction->setEnabled(true);
	m_saveAsPlaylistAction->setEnabled(true);
	m_renamePlaylistAction->setEnabled(true);
	m_deleteItemPlaylistAction->setEnabled(true);
407
    }
408 409 410 411

    updatePlaylistInfo();
}

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
////////////////////////////////////////////////////////////////////////////////
// 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()));
}

441
////////////////////////////////////////////////////////////////////////////////
442
// player menu
443 444
////////////////////////////////////////////////////////////////////////////////

445
void JuK::slotPlay()
446
{
447
    if(!m_player)
448 449
	return;

450 451
    if(m_player->paused()) {
        m_player->play();
452

453
	// Here, before doing anything, we want to make sure that the m_player did
454 455
	// in fact start.

456 457 458
        if(m_player->playing()) {
            m_pauseAction->setEnabled(true);
            m_stopAction->setEnabled(true);
459
            m_playTimer->start(m_pollInterval);
460 461
	    if(m_systemTray)
		m_systemTray->slotPlay();
462 463
        }
    }
464 465
    else if(m_player->playing())
	m_player->seekPosition(0);
466
    else
467
	play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
468 469
}

470
void JuK::slotPause()
471
{
472
    if(!m_player)
473 474
	return;

475 476 477 478 479
    m_playTimer->stop();
    m_player->pause();
    m_pauseAction->setEnabled(false);
    if(m_systemTray)
	m_systemTray->slotPause();
480 481
}

482
void JuK::slotStop()
483
{
484
    if(!m_player)
485 486
	return;

487 488
    m_playTimer->stop();
    m_player->stop();
489

490 491 492 493
    m_pauseAction->setEnabled(false);
    m_stopAction->setEnabled(false);
    m_backAction->setEnabled(false);
    m_forwardAction->setEnabled(false);
494

495 496
    m_sliderAction->getTrackPositionSlider()->setValue(0);
    m_sliderAction->getTrackPositionSlider()->setEnabled(false);
497

498
    m_splitter->stop();
499

500
    m_statusLabel->clear();
501
    
502 503
    if(m_systemTray)
	m_systemTray->slotStop();
504 505
}

506
void JuK::slotBack()
507
{
508
    play(m_splitter->playPreviousFile(m_randomPlayAction->isChecked()));
509 510
}

511
void JuK::slotForward()
512
{
513
    play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
514 515
}

516 517 518 519
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

520
void JuK::slotShowGenreListEditor()
521 522 523 524
{
    GenreListEditor * editor = new GenreListEditor();
    editor->exec();
}
525

526
void JuK::slotToggleSystemTray(bool enabled)
527
{
528
    if(enabled && !m_systemTray)
529
	setupSystemTray();
530 531 532
    else if(!enabled && m_systemTray) {
	delete m_systemTray;
	m_systemTray = 0;
533 534 535
    }
}

536
void JuK::slotSetOutput(int output)
537
{
538
    slotStop();
539 540
    delete m_player;
    m_player = Player::createPlayer(output);
541 542
}

543
////////////////////////////////////////////////////////////////////////////////
544
// additional player slots
545 546
////////////////////////////////////////////////////////////////////////////////

547
void JuK::slotTrackPositionSliderClicked()
548
{
549
    m_trackPositionDragging = true;
550 551
}

552
void JuK::slotTrackPositionSliderReleased()
553
{
554
    if(!m_player)
555 556
	return;

557 558
    m_trackPositionDragging = false;
    m_player->seekPosition(m_sliderAction->getTrackPositionSlider()->value());
559 560
}

561
void JuK::slotTrackPositionSliderUpdate(int position)
562
{
563
    if(!m_player)
564 565
	return;

566 567
    if(m_player->playing() && !m_trackPositionDragging && !m_noSeek)
        m_player->seekPosition(position);
568 569
}

570 571
// This method is called when the play timer has expired.

572
void JuK::slotPollPlay()
573
{
574
    if(!m_player)
575 576
	return;

577 578
    // Our locking mechanism.  Since this method adjusts the play slider, we 
    // want to make sure that our adjustments
579
    m_noSeek = true;
580

581
    if(!m_player->playing()) {
582

583
        m_playTimer->stop();
584

585 586
	if(!m_player->paused())
	    play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
587

588
    }
589 590 591 592
    else if(!m_trackPositionDragging) {
        m_sliderAction->getTrackPositionSlider()->setValue(m_player->position());
	m_statusLabel->setItemTotalTime(m_player->totalTime());
	m_statusLabel->setItemCurrentTime(m_player->currentTime());
593 594 595 596 597 598
    }

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

600
    if(m_player->playing() && m_player->totalTime() > 0 && float(m_player->totalTime() - m_player->currentTime()) < m_pollInterval * 2)
601
        m_playTimer->changeInterval(50);
602

603
    m_noSeek = false;
604 605
}

606
void JuK::slotSetVolume(int volume)
607
{
608 609 610
    if(m_player && m_sliderAction && m_sliderAction->getVolumeSlider() &&
       m_sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && m_sliderAction->getVolumeSlider()->maxValue() >= volume)
611
    {
612
        m_player->setVolume(float(volume) / float(m_sliderAction->getVolumeSlider()->maxValue()));
613 614
    }
}
615

616
#include "juk.moc"