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();
Scott Wheeler's avatar
Scott Wheeler committed
62
    QTimer::singleShot(0, CollectionList::instance(), SLOT(slotCheckCache()));
63 64 65 66
}

JuK::~JuK()
{
67

68 69 70 71 72 73
}

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

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

    // playlist item activation connection
80 81
    connect(m_splitter, SIGNAL(signalDoubleClicked()), this, SLOT(slotPlaySelectedFile()));
    connect(m_splitter, SIGNAL(signalListBoxDoubleClicked()), this, SLOT(slotPlayFirstFile()));
82

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

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

90
    m_splitter->setFocus();
91

92
    resize(750, 500);
93 94
}

95 96
void JuK::setupActions()
{
97
    // file menu
98 99 100
    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");
101

102
    m_renamePlaylistAction = new KAction(i18n("Rename..."), 0, m_splitter, SLOT(slotRenamePlaylist()), 
103
				       actionCollection(), "renamePlaylist");
104
    new KAction(i18n("Duplicate..."), "editcopy", 0, m_splitter, SLOT(slotDuplicatePlaylist()), actionCollection(), "duplicatePlaylist");
105
    
106 107 108
    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()), 
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 122 123 124
    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());
125
    
126
    // play menu
127
    m_randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
128 129 130 131 132
    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");
133

134
    // tagger menu
135 136
    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");
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");

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

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

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

150

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

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

    createGUI();
161 162

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

166
    connect(m_splitter, SIGNAL(signalPlaylistChanged()), this, SLOT(slotPlaylistChanged()));
167 168
}

169
void JuK::setupSystemTray()
170
{
171 172 173
    if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) {
	m_systemTray = new SystemTray(this, "systemTray");
	m_systemTray->show();
174
	
175 176 177 178 179
	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()));
180

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

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

201
    m_playTimer = new QTimer(this);
202
    connect(m_playTimer, SIGNAL(timeout()), this, SLOT(slotPollPlay()));
203

204
    if(m_sliderAction && m_sliderAction->getTrackPositionSlider() && m_sliderAction->getVolumeSlider()) {
205 206 207
        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()));
208
        m_sliderAction->getTrackPositionSlider()->setEnabled(false);
209

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

219
    m_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
    m_splitter->open(files);
232 233
}

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

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

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

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

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

310 311
bool JuK::queryClose()
{
312
    slotStop();
313
    delete m_player;
314
    Cache::instance()->save();
315
    saveConfig();
316
    delete m_splitter;
317
    return true;
318 319
}

320 321 322 323 324 325 326 327 328 329 330 331 332
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;
    
333 334 335
    connect(this, SIGNAL(signalEdit()), object, slot);
    emit signalEdit();
    disconnect(this, SIGNAL(signalEdit()), object, slot);
336 337
}

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

    return s;
}

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 390
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();
}

391 392 393 394
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

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

    updatePlaylistInfo();
}

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 441
////////////////////////////////////////////////////////////////////////////////
// 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()));
}

442
////////////////////////////////////////////////////////////////////////////////
443
// player menu
444 445
////////////////////////////////////////////////////////////////////////////////

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

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

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

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

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

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

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

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

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

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

499
    m_splitter->stop();
500

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

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

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

517 518 519 520
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

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

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

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

544
////////////////////////////////////////////////////////////////////////////////
545
// additional player slots
546 547
////////////////////////////////////////////////////////////////////////////////

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

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

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

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

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

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

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

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

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

584
        m_playTimer->stop();
585

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

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

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

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

604
    m_noSeek = false;
605 606
}

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

617
#include "juk.moc"