juk.cpp 14.9 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 38 39 40 41

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

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

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

    SplashScreen::finishedLoading();
57 58 59 60
}

JuK::~JuK()
{
61
    delete(playTimer);
62 63 64 65 66 67
}

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

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

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

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

81 82
    connect(splitter, SIGNAL(selectedPlaylistCountChanged(int)), statusLabel, SLOT(setPlaylistCount(int)));

83 84
    updatePlaylistInfo();

85 86 87
    splitter->setFocus();
}

88 89
void JuK::setupActions()
{
90
    // file menu
91 92 93
    KStdAction::open(splitter, SLOT(open()), actionCollection());
    new KAction(i18n("Open &Directory..."), "fileopen", 0, splitter, SLOT(openDirectory()), actionCollection(), "openDirectory");
    KStdAction::save(splitter, SLOT(save()), actionCollection());
94
    new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(removeSelectedItems()), actionCollection(), "remove");
95 96 97
    KStdAction::quit(this, SLOT(quit()), actionCollection());

    // edit menu
98
    KStdAction::cut(splitter, SLOT(clearSelectedItems()), actionCollection());
99 100
    KStdAction::copy(this, SLOT(copy()), actionCollection());
    KStdAction::paste(this, SLOT(paste()), actionCollection());
101
    KStdAction::selectAll(splitter, SLOT(selectAll()), actionCollection());
102

103 104
    // view menu
    showEditorAction = new KToggleAction(i18n("Show Tag Editor"), 0, actionCollection(), "showEditor");
105 106
    connect(showEditorAction, SIGNAL(toggled(bool)), splitter, SLOT(setEditorVisible(bool)));
    KStdAction::redisplay(splitter, SLOT(refresh()), actionCollection());
107

108
    // play menu
109
    randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
110
    playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(playFile()), actionCollection(), "playFile");
111 112
    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");
113 114
    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");
115

116
    // playlist menu
117 118 119 120 121 122 123 124 125
    new KAction(i18n("New..."), "filenew", 0, splitter, SLOT(createPlaylist()), actionCollection(), "createPlaylist");
    new KAction(i18n("Open..."), "fileopen", 0, splitter, SLOT(openPlaylist()), actionCollection(), "openPlaylist");

    savePlaylistAction = new KAction(i18n("Save"), "filesave", 0, splitter, SLOT(savePlaylist()), actionCollection(), "savePlaylist");
    saveAsPlaylistAction = new KAction(i18n("Save As..."), "filesaveas", 0, splitter, SLOT(saveAsPlaylist()), 
				       actionCollection(), "saveAsPlaylist");
    renamePlaylistAction = new KAction(i18n("Rename..."), 0, splitter, SLOT(renamePlaylist()), 
				       actionCollection(), "renamePlaylist");
    new KAction(i18n("Duplicate..."), "editcopy", 0, splitter, SLOT(duplicatePlaylist()), actionCollection(), "duplicatePlaylist");
126 127
    deleteItemPlaylistAction = new KAction(i18n("Delete"), "editdelete", 0, splitter, SLOT(deleteItemPlaylist()), 
					   actionCollection(), "deleteItemPlaylist");
128
    
129 130
    // settings menu
    restoreOnLoadAction = new KToggleAction(i18n("Restored Playlists on Load"),  0, actionCollection(), "restoreOnLoad"); 
131
    new KAction(i18n("Genre List Editor"), 0, this, SLOT(showGenreListEditor()), actionCollection(), "showGenreListEditor");
132

133
    connect(splitter, SIGNAL(playlistChanged()), this, SLOT(playlistChanged()));
134

135

136 137 138 139
    // just in the toolbar
    sliderAction = new SliderAction(i18n("Track Position"), actionCollection(), "trackPositionAction");

    createGUI();
140 141 142 143

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

void JuK::setupPlayer()
{
148 149
    trackPositionDragging = false;
    noSeek = false;
150 151
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
152 153
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
154

155
    playTimer = new QTimer(this);
156 157 158 159 160 161 162
    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);
163

164 165
        connect(sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
    }
166 167
}

168

169 170 171 172 173 174 175 176
void JuK::processArgs()
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
    
    for(int i = 0; i < args->count(); i++)
	files.append(args->arg(i));

177
    splitter->open(files);
178 179
}

180 181
void JuK::readConfig()
{
182 183 184
    // Automagically save and restore many window settings.
    setAutoSaveSettings();

185 186 187 188 189 190 191
    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);
        }
192 193 194 195
	if(randomPlayAction) {
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
	    randomPlayAction->setChecked(randomPlay);
	}
196
    }
197
    { // view settings
198 199 200 201 202
        KConfigGroupSaver saver(config, "View");
	bool showEditor = config->readBoolEntry("ShowEditor", true);
	showEditorAction->setChecked(showEditor);
	splitter->setEditorVisible(showEditor);
    }
203 204 205 206 207 208 209 210 211 212 213 214

    if(restoreOnLoadAction)
	restoreOnLoadAction->setChecked(restore);
}

void JuK::readSettings()
{
    KConfig *config = KGlobal::config();
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	restore = config->readBoolEntry("RestoreOnLoad", true);
    }
215 216 217 218
}

void JuK::saveConfig()
{
219 220 221 222 223
    KConfig *config = KGlobal::config();
    { // player settings
        KConfigGroupSaver saver(config, "Player");
        if(sliderAction && sliderAction->getVolumeSlider())
            config->writeEntry("Volume", sliderAction->getVolumeSlider()->value());
224 225
	if(randomPlayAction)
	    config->writeEntry("RandomPlay", randomPlayAction->isChecked());
226
    }
227 228 229 230
    { // view settings
        KConfigGroupSaver saver(config, "View");
	config->writeEntry("ShowEditor", showEditorAction->isChecked());
    }
231 232 233 234 235
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
	if(restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", restoreOnLoadAction->isChecked());
    }
236 237
}

238 239
bool JuK::queryClose()
{
240
    Cache::instance()->save();
241
    saveConfig();
242
    delete(splitter);
243 244 245
    return(true);
}

246 247 248 249
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

250
void JuK::playlistChanged()
251
{
252
    if(splitter->collectionListSelected()) {
253 254 255 256 257 258 259 260 261 262 263
	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);
    }
264 265 266 267 268 269

    updatePlaylistInfo();
}

void JuK::updatePlaylistInfo()
{
270
    statusLabel->setPlaylistInfo(splitter->selectedPlaylistName(), splitter->selectedPlaylistCount());
271 272
}

273 274 275
////////////////////////////////////////////////////////////////////////////////
// private action slot implementations - file menu
////////////////////////////////////////////////////////////////////////////////
276

277 278
void JuK::quit()
{
279
    close();
280 281 282 283 284 285 286 287
}

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

void JuK::playFile()
{
288 289
    if(player.paused()) {
        player.play();
290 291 292 293

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

294 295 296 297 298 299
        if(player.playing()) {
            pauseAction->setEnabled(true);
            stopAction->setEnabled(true);
            playTimer->start(pollInterval);
        }
    }
300 301
    else if(player.playing())
	player.seekPosition(0);
302 303
    else
	playFile(splitter->playNextFile(randomPlayAction->isChecked()));
304 305 306 307
}

void JuK::pauseFile()
{
308 309 310
    playTimer->stop();
    player.pause();
    pauseAction->setEnabled(false);
311 312 313 314
}

void JuK::stopFile()
{
315 316
    playTimer->stop();
    player.stop();
317

318 319
    pauseAction->setEnabled(false);
    stopAction->setEnabled(false);
320 321
    backAction->setEnabled(false);
    forwardAction->setEnabled(false);
322

323 324
    sliderAction->getTrackPositionSlider()->setValue(0);
    sliderAction->getTrackPositionSlider()->setEnabled(false);
325 326

    splitter->stop();
327

328
    statusLabel->clear();
329 330
}

331 332
void JuK::backFile()
{
333
    playFile(splitter->playPreviousFile(randomPlayAction->isChecked()));
334 335 336 337
}

void JuK::forwardFile()
{
338
    playFile(splitter->playNextFile(randomPlayAction->isChecked()));
339 340
}

341 342 343 344 345 346 347 348 349
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

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

351 352 353 354
////////////////////////////////////////////////////////////////////////////////
// additional player slots
////////////////////////////////////////////////////////////////////////////////

355 356
void JuK::trackPositionSliderClick()
{
357
    trackPositionDragging = true;
358 359 360 361
}

void JuK::trackPositionSliderRelease()
{
362
    trackPositionDragging = false;
363
    player.seekPosition(sliderAction->getTrackPositionSlider()->value());
364 365 366 367
}

void JuK::trackPositionSliderUpdate(int position)
{
368
    if(player.playing() && !trackPositionDragging && !noSeek)
369
        player.seekPosition(position);
370 371
}

372 373
// This method is called when the play timer has expired.

374 375
void JuK::pollPlay()
{
376 377
    // Our locking mechanism.  Since this method adjusts the play slider, we 
    // want to make sure that our adjustments
378
    noSeek = true;
379 380 381

    

382
    if(!player.playing()) {
383

384
        playTimer->stop();
385

386 387 388 389 390
	if(!player.paused()) {
	    
	    QString nextFile = splitter->playNextFile();
	    
	    if(!nextFile.isEmpty()) {
391

392 393
		backAction->setEnabled(true);
		
394
		sliderAction->getTrackPositionSlider()->setValue(0);
395 396 397 398 399
		player.play(nextFile, player.getVolume());		
		
		// Check to make sure that the file actually starts playing.
		
		if(player.playing())
400
		    playTimer->start(pollInterval);
401 402
		else
		    stopFile();
403
	    }
404 405
	    else
		stopFile();
406
	}
407
    }
408
    else if(!trackPositionDragging) {
409
        sliderAction->getTrackPositionSlider()->setValue(player.position());
410 411 412 413 414 415 416 417
	statusLabel->setItemTotalTime(player.totalTime());
	statusLabel->setItemCurrentTime(player.currentTime());
    }

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

419
    if(player.playing() && player.totalTime() > 0 && float(player.totalTime() - player.currentTime()) < pollInterval * 2)
420
        playTimer->changeInterval(50);
421

422
    noSeek = false;
423 424 425 426
}

void JuK::setVolume(int volume)
{
427 428 429
    if(sliderAction && sliderAction->getVolumeSlider() &&
       sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && sliderAction->getVolumeSlider()->maxValue() >= volume)
430
    {
431
        player.setVolume(float(volume) / float(sliderAction->getVolumeSlider()->maxValue()));
432 433
    }
}
434

435
void JuK::playFile(const QString &file)
436
{
437 438 439
    float volume = float(sliderAction->getVolumeSlider()->value()) / float(sliderAction->getVolumeSlider()->maxValue());
    
    player.play(file, volume);
440

441
    // Make sure that the player actually starts before doing anything.
442

443 444 445 446 447 448 449 450 451
    if(player.playing()) {
	pauseAction->setEnabled(true);
	stopAction->setEnabled(true);
	
	backAction->setEnabled(true);
	forwardAction->setEnabled(true);
	
	sliderAction->getTrackPositionSlider()->setEnabled(true);
	playTimer->start(pollInterval);
452

453
	statusLabel->setPlayingItemInfo(splitter->playingTrack(), splitter->playingArtist(), splitter->playingList());
454 455
    }
}
456

457
#include "juk.moc"