juk.cpp 18.5 KB
Newer Older
1 2
/***************************************************************************
    begin                : Mon Feb  4 23:40:41 EST 2002
3
    copyright            : (C) 2002 - 2004 by Scott Wheeler
4
    email                : wheeler@kde.org
5

Michael Pyne's avatar
Michael Pyne committed
6
    copyright            : (C) 2008, 2009 by Michael Pyne
7
    email                : mpyne@kde.org
8
***************************************************************************/
9 10 11 12 13 14 15 16 17 18

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

19 20
#include "juk.h"

21 22 23 24
#include <kcmdlineargs.h>
#include <kstatusbar.h>
#include <kdebug.h>
#include <kmessagebox.h>
25
#include <kstandarddirs.h>
Laurent Montel's avatar
Poirt  
Laurent Montel committed
26
#include <kactioncollection.h>
Aaron J. Seigo's avatar
Aaron J. Seigo committed
27
#include <kstandardaction.h>
Laurent Montel's avatar
Laurent Montel committed
28 29
#include <ktoggleaction.h>
#include <kactionmenu.h>
30
#include <kicon.h>
31 32
#include <kaction.h>
#include <kconfiggroup.h>
33 34 35
#include <kapplication.h>
#include <kglobalaccel.h>
#include <ktoolbarpopupaction.h>
36
#include <knotification.h>
37
#include <kdeversion.h>
38

39
#include <QCoreApplication>
40
#include <QKeyEvent>
41 42
#include <QDir>
#include <QTimer>
43

44 45 46 47 48 49
#include "slideraction.h"
#include "statuslabel.h"
#include "splashscreen.h"
#include "systemtray.h"
#include "keydialog.h"
#include "tagguesserconfigdlg.h"
50
#include "filerenamerconfigdlg.h"
51
#include "actioncollection.h"
52
#include "cache.h"
53 54
#include "playlistsplitter.h"
#include "collectionlist.h"
55
#include "covermanager.h"
56
#include "tagtransactionmanager.h"
57

58 59
using namespace ActionCollection;

60 61
JuK* JuK::m_instance;

62 63 64 65 66 67 68
template<class T>
void deleteAndClear(T *&ptr)
{
    delete ptr;
    ptr = 0;
}

69 70 71 72
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

73
JuK::JuK(QWidget *parent) :
Kevin Ottens's avatar
Kevin Ottens committed
74
    KXmlGuiWindow(parent, Qt::WDestructiveClose),
75 76
    m_splitter(0),
    m_statusLabel(0),
77
    m_systemTray(0),
78
    m_player(new PlayerManager),
79
    m_shuttingDown(false)
80
{
81 82
    // Expect segfaults if you change this order.

83 84
    m_instance = this;

85
    readSettings();
86

87
    if(m_showSplash && !m_startDocked && Cache::cacheFileExists()) {
88
        if(SplashScreen* splash = SplashScreen::instance()) {
Sebastian Sauer's avatar
Sebastian Sauer committed
89
            splash->show();
90 91
            kapp->processEvents();
        }
92 93
    }

94
    setupActions();
95
    setupLayout();
96

97
    if(QApplication::isRightToLeft())
98
        setupGUI(ToolBar | Save | Create, "jukui-rtl.rc");
99
    else
100
        setupGUI(ToolBar | Save | Create);
101

Michael Pyne's avatar
Michael Pyne committed
102
    connect(m_splitter, SIGNAL(guiReady()), SLOT(slotSetupSystemTray()));
103
    readConfig();
104
    setupGlobalAccels();
105

106 107
    connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), SLOT(slotAboutToQuit()));

Michael Pyne's avatar
Michael Pyne committed
108 109
    // slotCheckCache loads the cached entries first to populate the collection list

Scott Wheeler's avatar
Scott Wheeler committed
110
    QTimer::singleShot(0, CollectionList::instance(), SLOT(slotCheckCache()));
111
    QTimer::singleShot(0, this, SLOT(slotProcessArgs()));
112 113

    m_sliderAction->slotUpdateOrientation();
114 115 116 117 118 119
}

JuK::~JuK()
{
}

120 121 122 123 124
KActionCollection *JuK::actionCollection() const
{
    return ActionCollection::actions();
}

125 126 127 128 129
JuK* JuK::JuKInstance()
{
    return m_instance;
}

130 131 132 133 134
PlayerManager *JuK::playerManager() const
{
    return m_player;
}

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
void JuK::coverDownloaded(const QPixmap &cover)
{
    QString event(cover.isNull() ? "coverFailed" : "coverDownloaded");
    KNotification *notification = new KNotification(event, this);
    notification->setPixmap(cover);
    notification->setFlags(KNotification::CloseOnTimeout);

    if(cover.isNull())
        notification->setText(i18n("Your album art failed to download."));
    else
        notification->setText(i18n("Your album art has finished downloading."));

    notification->sendEvent();
}

150 151 152 153
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

154 155
void JuK::setupLayout()
{
156 157
    new TagTransactionManager(this);

158
    m_splitter = new PlaylistSplitter(m_player, this);
159
    setCentralWidget(m_splitter);
160

161
    m_statusLabel = new StatusLabel(m_splitter->playlist(), statusBar());
162 163
    connect(CollectionList::instance(), SIGNAL(signalCollectionChanged()),
            m_statusLabel, SLOT(updateData()));
164
    statusBar()->addWidget(m_statusLabel, 1);
165
    m_player->setStatusLabel(m_statusLabel);
166

167
    m_splitter->setFocus();
168
    resize(750, 500);
169 170
}

171 172
void JuK::setupActions()
{
173
    KActionCollection *collection = ActionCollection::actions();
Michael Pyne's avatar
Michael Pyne committed
174

175
    // Setup KDE standard actions that JuK uses.
176

Aaron J. Seigo's avatar
Aaron J. Seigo committed
177 178 179 180 181 182 183 184
    KStandardAction::quit(this, SLOT(slotQuit()), collection);
    KStandardAction::undo(this, SLOT(slotUndo()), collection);
    KStandardAction::cut(collection);
    KStandardAction::copy(collection);
    KStandardAction::paste(collection);
    KAction *clear = KStandardAction::clear(collection);
    KStandardAction::selectAll(collection);
    KStandardAction::keyBindings(this, SLOT(slotEditKeys()), collection);
Scott Wheeler's avatar
Scott Wheeler committed
185

186 187

    // Setup the menu which handles the random play options.
188 189 190
    KActionMenu *actionMenu = collection->add<KActionMenu>("actionMenu");
    actionMenu->setText(i18n("&Random Play"));
    actionMenu->setIcon(KIcon("media-playlist-shuffle"));
191
    actionMenu->setDelayed(false);
192

193
    QActionGroup* randomPlayGroup = new QActionGroup(this);
David Faure's avatar
David Faure committed
194

195 196 197
    QAction *act = collection->add<KToggleAction>("disableRandomPlay");
    act->setText(i18n("&Disable Random Play"));
    act->setIcon(KIcon("go-down"));
198
    act->setActionGroup(randomPlayGroup);
Stephan Kulow's avatar
Stephan Kulow committed
199
    actionMenu->addAction(act);
200

Laurent Montel's avatar
Port++  
Laurent Montel committed
201 202
    m_randomPlayAction = collection->add<KToggleAction>("randomPlay");
    m_randomPlayAction->setText(i18n("Use &Random Play"));
203
    m_randomPlayAction->setIcon(KIcon("media-playlist-shuffle"));
David Faure's avatar
David Faure committed
204
    m_randomPlayAction->setActionGroup(randomPlayGroup);
Stephan Kulow's avatar
Stephan Kulow committed
205
    actionMenu->addAction(m_randomPlayAction);
206

207 208 209
    act = collection->add<KToggleAction>("albumRandomPlay");
    act->setText(i18n("Use &Album Random Play"));
    act->setIcon(KIcon("media-playlist-shuffle"));
210 211
    act->setActionGroup(randomPlayGroup);
    connect(act, SIGNAL(triggered(bool)), SLOT(slotCheckAlbumNextAction(bool)));
Stephan Kulow's avatar
Stephan Kulow committed
212
    actionMenu->addAction(act);
213

214
    act = collection->addAction("removeFromPlaylist", clear, SLOT(clear()));
Laurent Montel's avatar
Port++  
Laurent Montel committed
215
    act->setText(i18n("Remove From Playlist"));
216
    act->setIcon(KIcon("list-remove"));
Laurent Montel's avatar
Port++  
Laurent Montel committed
217

218 219 220 221
    act = collection->add<KToggleAction>("crossfadeTracks");
    act->setText(i18n("Crossfade Between Tracks"));
    connect(act, SIGNAL(triggered(bool)), m_player, SLOT(setCrossfadeEnabled(bool)));

222 223 224
    act = collection->addAction("play", m_player, SLOT(play()));
    act->setText(i18n("&Play"));
    act->setIcon(KIcon("media-playback-start"));
225

226 227 228
    act = collection->addAction("pause", m_player, SLOT(pause()));
    act->setText(i18n("P&ause"));
    act->setIcon(KIcon("media-playback-pause"));
229

230 231 232
    act = collection->addAction("stop", m_player, SLOT(stop()));
    act->setText(i18n("&Stop"));
    act->setIcon(KIcon("media-playback-stop"));
Scott Wheeler's avatar
Scott Wheeler committed
233

234
    act = new KToolBarPopupAction(KIcon("media-skip-backward"), i18nc("previous track", "Previous"), collection);
235
    collection->addAction("back", act);
236 237
    connect(act, SIGNAL(triggered(bool)), m_player, SLOT(back()));

238 239 240
    act = collection->addAction("forward", m_player, SLOT(forward()));
    act->setText(i18nc("next track", "&Next"));
    act->setIcon(KIcon("media-skip-forward"));
241

242 243
    act = collection->addAction("loopPlaylist");
    act->setText(i18n("&Loop Playlist"));
244 245
    act->setCheckable(true);

246 247
    act = collection->add<KToggleAction>("resizeColumnsManually");
    act->setText(i18n("&Resize Playlist Columns Manually"));
248 249

    // the following are not visible by default
250

251 252 253
    act = collection->addAction("mute", m_player, SLOT(mute()));
    act->setText(i18nc("silence playback", "Mute"));
    act->setIcon(KIcon("audio-volume-muted"));
254

255 256 257
    act = collection->addAction("volumeUp", m_player, SLOT(volumeUp()));
    act->setText(i18n("Volume Up"));
    act->setIcon(KIcon("audio-volume-high"));
258

259 260 261
    act = collection->addAction("volumeDown", m_player, SLOT(volumeDown()));
    act->setText(i18n("Volume Down"));
    act->setIcon(KIcon("audio-volume-low"));
262

263 264 265
    act = collection->addAction("playPause", m_player, SLOT(playPause()));
    act->setText(i18n("Play / Pause"));
    act->setIcon(KIcon("media-playback-start"));
266

267 268 269
    act = collection->addAction("seekForward", m_player, SLOT(seekForward()));
    act->setText(i18n("Seek Forward"));
    act->setIcon(KIcon("media-seek-forward"));
270

271 272 273
    act = collection->addAction("seekBack", m_player, SLOT(seekBack()));
    act->setText(i18n("Seek Back"));
    act->setIcon(KIcon("media-seek-backward"));
274

275 276
    act = collection->addAction("showHide", this, SLOT(slotShowHide()));
    act->setText(i18n("Show / Hide"));
Stephan Binner's avatar
Stephan Binner committed
277

278
    //////////////////////////////////////////////////
279
    // settings menu
280 281
    //////////////////////////////////////////////////

282 283 284 285 286 287 288 289 290
    m_toggleSplashAction = collection->add<KToggleAction>("showSplashScreen");
    m_toggleSplashAction->setText(i18n("Show Splash Screen on Startup"));

    m_toggleSystemTrayAction = collection->add<KToggleAction>("toggleSystemTray");
    m_toggleSystemTrayAction->setText(i18n("&Dock in System Tray"));
    connect(m_toggleSystemTrayAction, SIGNAL(triggered(bool)), SLOT(slotToggleSystemTray(bool)));

    m_toggleDockOnCloseAction = collection->add<KToggleAction>("dockOnClose");
    m_toggleDockOnCloseAction->setText(i18n("&Stay in System Tray on Close"));
291

292 293
    m_togglePopupsAction = collection->add<KToggleAction>("togglePopups");
    m_togglePopupsAction->setText(i18n("Popup &Track Announcement"));
294

295 296
    act = collection->add<KToggleAction>("saveUpcomingTracks");
    act->setText(i18n("Save &Play Queue on Exit"));
Scott Wheeler's avatar
Scott Wheeler committed
297

298 299
    act = collection->addAction("tagGuesserConfig", this, SLOT(slotConfigureTagGuesser()));
    act->setText(i18n("&Tag Guesser..."));
300

301 302
    act = collection->addAction("fileRenamerConfig", this, SLOT(slotConfigureFileRenamer()));
    act->setText(i18n("&File Renamer..."));
303

304
    //////////////////////////////////////////////////
305
    // just in the toolbar
306 307
    //////////////////////////////////////////////////

308 309
    m_sliderAction = new SliderAction(i18n("Track Position"), this);
    collection->addAction("trackPositionAction", m_sliderAction);
310

311 312 313
    ActionCollection::actions()->addAssociatedWidget(this);
    foreach (QAction* action, ActionCollection::actions()->actions())
        action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
314 315
}

Michael Pyne's avatar
Michael Pyne committed
316
void JuK::slotSetupSystemTray()
317
{
318
    if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) {
319
        m_systemTray = new SystemTray(m_player, this);
Laurent Montel's avatar
Laurent Montel committed
320
        m_systemTray->setObjectName( QLatin1String("systemTray" ));
321

322 323
        m_toggleDockOnCloseAction->setEnabled(true);
        m_togglePopupsAction->setEnabled(true);
324
    }
325
    else {
326 327 328
        m_systemTray = 0;
        m_toggleDockOnCloseAction->setEnabled(false);
        m_togglePopupsAction->setEnabled(false);
329
    }
330 331
}

332 333
void JuK::setupGlobalAccels()
{
334 335 336 337 338 339 340 341 342 343 344 345
    KeyDialog::setupActionShortcut("play");
    KeyDialog::setupActionShortcut("playPause");
    KeyDialog::setupActionShortcut("stop");
    KeyDialog::setupActionShortcut("back");
    KeyDialog::setupActionShortcut("forward");
    KeyDialog::setupActionShortcut("seekBack");
    KeyDialog::setupActionShortcut("seekForward");
    KeyDialog::setupActionShortcut("volumeUp");
    KeyDialog::setupActionShortcut("volumeDown");
    KeyDialog::setupActionShortcut("mute");
    KeyDialog::setupActionShortcut("showHide");
    KeyDialog::setupActionShortcut("forwardAlbum");
346 347
}

348
void JuK::slotProcessArgs()
349 350 351
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
352

353
    for(int i = 0; i < args->count(); i++)
Frerich Raabe's avatar
Frerich Raabe committed
354
        files.append(args->arg(i));
355

356
    CollectionList::instance()->addFiles(files);
357 358
}

359 360 361
void JuK::keyPressEvent(QKeyEvent *e)
{
    if (e->key() >= Qt::Key_Back && e->key() <= Qt::Key_MediaLast)
Frerich Raabe's avatar
Frerich Raabe committed
362
        e->accept();
Kevin Ottens's avatar
Kevin Ottens committed
363
    KXmlGuiWindow::keyPressEvent(e);
364 365
}

366 367 368 369 370 371
/**
 * These are settings that need to be know before setting up the GUI.
 */

void JuK::readSettings()
{
372
    KConfigGroup config(KGlobal::config(), "Settings");
373 374
    m_showSplash = config.readEntry("ShowSplashScreen", true);
    m_startDocked = config.readEntry("StartDocked", false);
375 376
}

377 378
void JuK::readConfig()
{
379
    // player settings
380

381
    KConfigGroup playerConfig(KGlobal::config(), "Player");
382

383 384 385 386 387
    if(m_player)
    {
        const int maxVolume = 100;
        const int volume = playerConfig.readEntry("Volume", maxVolume);
        m_player->setVolume(volume * 0.01);
388 389 390 391

        bool enableCrossfade = playerConfig.readEntry("CrossfadeTracks", true);
        m_player->setCrossfadeEnabled(enableCrossfade);
        ActionCollection::action<KAction>("crossfadeTracks")->setChecked(enableCrossfade);
392
    }
393

394 395
    // Default to no random play

396
    ActionCollection::action<KToggleAction>("disableRandomPlay")->setChecked(true);
397

398 399
    QString randomPlayMode = playerConfig.readEntry("RandomPlay", "Disabled");
    if(randomPlayMode == "true" || randomPlayMode == "Normal")
400
        m_randomPlayAction->setChecked(true);
401
    else if(randomPlayMode == "AlbumRandomPlay")
402
        ActionCollection::action<KAction>("albumRandomPlay")->setChecked(true);
403

404
    bool loopPlaylist = playerConfig.readEntry("LoopPlaylist", false);
405
    ActionCollection::action<KAction>("loopPlaylist")->setChecked(loopPlaylist);
406

407
    // general settings
408

409 410
    KConfigGroup settingsConfig(KGlobal::config(), "Settings");

411
    bool dockInSystemTray = settingsConfig.readEntry("DockInSystemTray", true);
412 413
    m_toggleSystemTrayAction->setChecked(dockInSystemTray);

414
    bool dockOnClose = settingsConfig.readEntry("DockOnClose", true);
415 416
    m_toggleDockOnCloseAction->setChecked(dockOnClose);

417
    bool showPopups = settingsConfig.readEntry("TrackPopup", false);
418 419
    m_togglePopupsAction->setChecked(showPopups);

420
    m_toggleSplashAction->setChecked(m_showSplash);
421 422 423 424
}

void JuK::saveConfig()
{
425
    // player settings
426

427
    KConfigGroup playerConfig(KGlobal::config(), "Player");
428

429
    if (m_player)
430
    {
431
        playerConfig.writeEntry("Volume", static_cast<int>(100.0 * m_player->volume()));
432 433
    }

434 435
    playerConfig.writeEntry("RandomPlay", m_randomPlayAction->isChecked());

436
    KAction *a = ActionCollection::action<KAction>("loopPlaylist");
437 438
    playerConfig.writeEntry("LoopPlaylist", a->isChecked());

439 440 441
    a = ActionCollection::action<KAction>("crossfadeTracks");
    playerConfig.writeEntry("CrossfadeTracks", a->isChecked());

442
    a = ActionCollection::action<KAction>("albumRandomPlay");
443
    if(a->isChecked())
444
        playerConfig.writeEntry("RandomPlay", "AlbumRandomPlay");
445
    else if(m_randomPlayAction->isChecked())
446
        playerConfig.writeEntry("RandomPlay", "Normal");
447
    else
448
        playerConfig.writeEntry("RandomPlay", "Disabled");
449

450 451 452 453 454 455 456 457 458 459
    // general settings

    KConfigGroup settingsConfig(KGlobal::config(), "Settings");
    settingsConfig.writeEntry("ShowSplashScreen", m_toggleSplashAction->isChecked());
    settingsConfig.writeEntry("StartDocked", m_startDocked);
    settingsConfig.writeEntry("DockInSystemTray", m_toggleSystemTrayAction->isChecked());
    settingsConfig.writeEntry("DockOnClose", m_toggleDockOnCloseAction->isChecked());
    settingsConfig.writeEntry("TrackPopup", m_togglePopupsAction->isChecked());

    KGlobal::config()->sync();
460 461
}

462
bool JuK::queryExit()
463
{
464 465 466
    // This may actually be sent more than once in practice, but we're not
    // supposed to do end of execution destruction yet anyways, use
    // slotAboutToQuit for that.
467

468
    m_startDocked = !isVisible();
469
    saveConfig();
470

471
    return true;
472 473
}

474 475
bool JuK::queryClose()
{
476 477 478 479 480
    if(!m_shuttingDown &&
       !kapp->sessionSaving() &&
       m_systemTray &&
       m_toggleDockOnCloseAction->isChecked())
    {
481 482 483 484 485 486
        KMessageBox::information(this,
            i18n("<qt>Closing the main window will keep JuK running in the system tray. "
                 "Use Quit from the File menu to quit the application.</qt>"),
            i18n("Docking in System Tray"), "hideOnCloseInfo");
        hide();
        return false;
487 488
    }
    else
489
        return true;
490 491
}

492 493 494 495
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

496 497
void JuK::slotShowHide()
{
Stephan Kulow's avatar
Stephan Kulow committed
498
    setHidden(!isHidden());
499 500
}

501 502 503
void JuK::slotAboutToQuit()
{
    m_shuttingDown = true;
504

505 506 507 508
    deleteAndClear(m_systemTray);
    deleteAndClear(m_splitter);
    deleteAndClear(m_player);
    deleteAndClear(m_statusLabel);
509 510 511 512

    // Playlists depend on CoverManager, so CoverManager should shutdown as
    // late as possible
    CoverManager::shutdown();
513 514
}

515 516
void JuK::slotQuit()
{
Michael Pyne's avatar
Michael Pyne committed
517
    m_shuttingDown = true;
518

Michael Pyne's avatar
Michael Pyne committed
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
    // Some phonon backends will crash on shutdown unless we're deep in the middle of the StoppedState.
    // So have the PlayerManager tell us when we're stopped and we'll continue.
    if(m_player->playing()) {
        connect(m_player, SIGNAL(signalStop()), SLOT(slotPlaybackStopped()));
        m_player->stop();
    }
    else
        QTimer::singleShot(0, this, SLOT(slotPlaybackStopped()));

    // Get these started since they don't involve deleting any running objects.
    Cache::instance()->save();
    saveConfig();
}

void JuK::slotPlaybackStopped()
{
535 536 537
    kapp->quit();
}

538 539 540 541
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

542
void JuK::slotToggleSystemTray(bool enabled)
543
{
544
    if(enabled && !m_systemTray)
Michael Pyne's avatar
Michael Pyne committed
545
        slotSetupSystemTray();
546
    else if(!enabled && m_systemTray) {
547 548 549 550
        delete m_systemTray;
        m_systemTray = 0;
        m_toggleDockOnCloseAction->setEnabled(false);
        m_togglePopupsAction->setEnabled(false);
551 552 553
    }
}

554 555
void JuK::slotEditKeys()
{
556
    KeyDialog::configure(ActionCollection::actions(), this);
557 558
}

559 560
void JuK::slotConfigureTagGuesser()
{
561
    TagGuesserConfigDlg(this).exec();
562 563
}

564 565
void JuK::slotConfigureFileRenamer()
{
566
    FileRenamerConfigDlg(this).exec();
567 568
}

569 570 571 572 573
void JuK::slotUndo()
{
    TagTransactionManager::instance()->undo();
}

574 575 576 577 578 579
void JuK::slotCheckAlbumNextAction(bool albumRandomEnabled)
{
    // If album random play is enabled, then enable the Play Next Album action
    // unless we're not playing right now.

    if(albumRandomEnabled && !m_player->playing())
580
        albumRandomEnabled = false;
581 582 583 584

    action("forwardAlbum")->setEnabled(albumRandomEnabled);
}

585
#include "juk.moc"
586 587

// vim: set et sw=4 tw=0 sta: