playlistsplitter.cpp 16.6 KB
Newer Older
1 2 3 4 5
/***************************************************************************
                          playlistsplitter.cpp  -  description
                             -------------------
    begin                : Fri Sep 13 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 <klocale.h>
19
#include <kiconloader.h>
20
#include <kapplication.h>
21
#include <kstandarddirs.h>
22
#include <kmessagebox.h>
23 24
#include <kdebug.h>

25 26
#include <qinputdialog.h>

27
#include "playlistitem.h"
28
#include "playlistsplitter.h"
29
#include "collectionlist.h"
30
#include "directorylist.h"
31 32
#include "playlist.h"

33 34

////////////////////////////////////////////////////////////////////////////////
35
// helper functions
36 37
////////////////////////////////////////////////////////////////////////////////

38
void processEvents()
39
{
40 41 42
    static int processed = 0;
    if(processed == 0)
        kapp->processEvents();
43
    processed = (processed + 1) % 5;
44 45
}

46
////////////////////////////////////////////////////////////////////////////////
47
// public methods
48 49
////////////////////////////////////////////////////////////////////////////////

50
PlaylistSplitter::PlaylistSplitter(QWidget *parent, bool restoreOnLoad, const char *name) : QSplitter(Qt::Horizontal, parent, name), 
51
											    playingItem(0), restore(restoreOnLoad)
52
{
53 54 55
    mediaExtensions.append("mp3");
    mediaExtensions.append("ogg");
    listExtensions.append("m3u");
56 57 58

    setupLayout();
    readConfig();
59
}
60

61
PlaylistSplitter::~PlaylistSplitter()
62
{
63
    saveConfig();
64 65
}

66
QString PlaylistSplitter::uniquePlaylistName(const QString &startingWith, bool useParenthesis)
67 68
{
    if(!playlistBox)
69
	return QString::null;
70 71 72 73 74 75 76 77

    QStringList names = playlistBox->names();

    int playlistNumber = 1;

    // while the list contains more than zero instances of the generated 
    // string...

78 79 80 81
    if(useParenthesis) {
	while(names.contains(startingWith + " (" + QString::number(playlistNumber) + ")") != 0)
	    playlistNumber++;
	
82
	return startingWith + " (" + QString::number(playlistNumber) + ")";	
83 84 85 86 87 88
    }
    else
    {
	while(names.contains(startingWith + ' ' + QString::number(playlistNumber)) != 0)
	    playlistNumber++;
	
89
	return startingWith + " " + QString::number(playlistNumber);
90
    }
91 92
}

93
QString PlaylistSplitter::playNextFile(bool random)
94
{
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    Playlist *p;
    PlaylistItem *i;

    if(playingItem) {
	playingItem->setPixmap(0, 0);

	p = static_cast<Playlist *>(playingItem->listView());
	i = p->nextItem(playingItem, random);
    }
    else {
	PlaylistItemList items = playlistSelection();
	if(!items.isEmpty())
	    i = items.first();
	else {
	    p = visiblePlaylist();
	    i = static_cast<PlaylistItem *>(p->firstChild());
	}
    }

    if(i) {
	i->setPixmap(0, QPixmap(UserIcon("playing")));
	playingItem = i;
117
	return i->absFilePath();
118 119
    }
    else
120
	return QString::null;
121 122
}

123
QString PlaylistSplitter::playPreviousFile(bool random)
124
{
125 126 127 128 129 130 131 132
    if(playingItem) {
	Playlist *p = static_cast<Playlist *>(playingItem->listView());
	PlaylistItem *i = p->previousItem(playingItem, random);

	playingItem->setPixmap(0, 0);
	i->setPixmap(0, QPixmap(UserIcon("playing")));
	
	playingItem = i;
133
	return i->absFilePath();
134 135
    }
    else
136
	return QString::null;
137 138
}

139
QString PlaylistSplitter::playSelectedFile()
140
{
141
    stop();
142

143
    PlaylistItemList items = playlistSelection();
144

145 146 147 148 149
    if(!items.isEmpty()) {
	PlaylistItem *i = items.first();
	i->setPixmap(0, QPixmap(UserIcon("playing")));
	
	playingItem = i;
150
	return i->absFilePath();
151 152
    }
    else
153
	return QString::null;
154
}
155

156 157 158
QString PlaylistSplitter::playFirstFile()
{
    stop();
159

160 161
    Playlist *p = visiblePlaylist();
    PlaylistItem *i = static_cast<PlaylistItem *>(p->firstChild());
162

163 164 165 166 167
    if(i) {
	i->setPixmap(0, QPixmap(UserIcon("playing")));
	i->setPixmap(0, QPixmap(UserIcon("playing")));
	playingItem = i;

168
	return i->absFilePath();
169 170
    }
    else
171
	return QString::null;
172 173
}

174 175 176 177 178 179 180 181 182 183 184
void PlaylistSplitter::stop()
{
    if(playingItem) {
	playingItem->setPixmap(0, 0);
	playingItem = 0;
    }
}

QString PlaylistSplitter::playingArtist() const
{
    if(playingItem)
185
	return playingItem->text(PlaylistItem::ArtistColumn);
186
    else
187
	return QString::null;
188 189 190 191 192
}

QString PlaylistSplitter::playingTrack() const
{
    if(playingItem)
193
	return playingItem->text(PlaylistItem::TrackColumn);
194
    else
195
	return QString::null;
196 197 198 199 200
}

QString PlaylistSplitter::playingList() const
{
    if(playingItem)
201
	return static_cast<Playlist *>(playingItem->listView())->name();
202
    else
203
	return QString::null;
204 205
}

206
void PlaylistSplitter::add(const QString &file, Playlist *list)
207
{
208 209 210 211 212 213
    KApplication::setOverrideCursor(Qt::waitCursor);
    addImpl(file, list);
    KApplication::restoreOverrideCursor();
    
    if(editor)
	editor->updateCollection();
214 215
}

216
void PlaylistSplitter::add(const QStringList &files, Playlist *list)
217
{
218 219 220 221 222 223 224
    KApplication::setOverrideCursor(Qt::waitCursor);
    for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it)
        addImpl(*it, list);
    KApplication::restoreOverrideCursor();

    if(editor)
	editor->updateCollection();
225 226
}

227
QString PlaylistSplitter::extensionsString(const QStringList &extensions, const QString &type) // static
228
{
229 230 231 232 233 234 235 236 237 238 239 240
    QStringList l;

    for(QStringList::ConstIterator it = extensions.begin(); it != extensions.end(); ++it)
	l.append(QString("*." + (*it)));

    // i.e. "*.m3u, *.mp3|Media Files"

    QString s = l.join(" ");

    if(type != QString::null)
	s += "|" + type + " (" + l.join(", ") + ")";

241
    return s;
242 243
}

244 245
void PlaylistSplitter::open(const QString &file) 
{
246 247 248
    if(file.isEmpty())
	return;

249 250 251 252 253 254 255 256 257 258
    if(visiblePlaylist() == collection || 
       KMessageBox::questionYesNo(this, i18n("Do you want to add this item to the current list or to the collection list?"), 
				  QString::null, KGuiItem(i18n("Current")), KGuiItem(i18n("Collection"))) == KMessageBox::No)
	add(file, collection);
    else
	add(file, visiblePlaylist());
}

void PlaylistSplitter::open(const QStringList &files) 
{
259 260 261
    if(files.isEmpty())
	return;
    
262 263 264 265 266 267 268 269
    if(visiblePlaylist() == collection || 
       KMessageBox::questionYesNo(this, i18n("Do you want to add these items to the current list or to the collection list?"), 
				  QString::null, KGuiItem(i18n("Current")), KGuiItem(i18n("Collection"))) == KMessageBox::No)
	add(files, collection);
    else
	add(files, visiblePlaylist());
}

270 271 272 273
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

274 275
void PlaylistSplitter::open()
{
276 277
    QStringList files = KFileDialog::getOpenFileNames(QString::null, 
						      extensionsString((mediaExtensions + listExtensions), i18n("Media Files")));
278 279 280
    open(files);
}

281 282 283 284 285 286 287
void PlaylistSplitter::openDirectory()
{ 
    DirectoryList *l = new DirectoryList(directoryList, this, "directoryList");

    directoryQueue.clear();

    connect(l, SIGNAL(directoryAdded(const QString &)), this, SLOT(queueDirectory(const QString &)));
288
    connect(l, SIGNAL(directoryRemoved(const QString &)), this, SLOT(queueDirectoryRemove(const QString &)));
289 290 291 292 293 294 295 296

    if(l->exec() == QDialog::Accepted) {
	open(directoryQueue);
	directoryList += directoryQueue;
	for(QStringList::Iterator it = directoryQueueRemove.begin(); it !=  directoryQueueRemove.end(); it++)
	    directoryList.remove(*it);
    }
}
297 298 299 300 301 302 303 304
void PlaylistSplitter::setEditorVisible(bool visible)
{
    if(visible)
	editor->show();
    else
	editor->hide();
}

305 306 307 308
Playlist *PlaylistSplitter::createPlaylist()
{
    bool ok;

309
    // If this text is changed, please also change it in PlaylistBox::duplicate().
310 311 312 313

    QString name = QInputDialog::getText(i18n("New Playlist..."), i18n("Please enter a name for the new playlist:"),
					 QLineEdit::Normal, uniquePlaylistName(), &ok);
    if(ok)
314
	return createPlaylist(name);
315
    else
316
	return 0;
317 318 319 320
}

Playlist *PlaylistSplitter::createPlaylist(const QString &name)
{
321
    Playlist *p = new Playlist(this, playlistStack, name.latin1());
322
    setupPlaylist(p, true);
323
    return p;
324 325
}

326

327
void PlaylistSplitter::selectPlaying()
328
{
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    if(!playingItem)
	return;

    Playlist *l = static_cast<Playlist *>(playingItem->listView());
	
    if(l) {

	l->clearSelection();
	l->setSelected(playingItem, true);
	l->ensureItemVisible(playingItem);
	
	// Now move on to the PlaylistBox.  The Playlist knows which
	// PlaylistBoxItem that it is associated with, so we'll just get
	// that and then figure out the PlaylistBox from there.
	// 
	// Once we have that we can set the appropriate Playlist to be
	// visible.
	
	if(l->playlistBoxItem() && l->playlistBoxItem()->listBox()) {
	    QListBox *b = l->playlistBoxItem()->listBox();
	    
	    b->clearSelection();
	    b->setSelected(l->playlistBoxItem(), true);
	    
	    b->setCurrentItem(l->playlistBoxItem());
	    b->ensureCurrentVisible();
	}    
    }
357 358
}

359 360 361 362 363 364 365 366 367 368 369 370
void PlaylistSplitter::removeSelectedItems()
{
    PlaylistItemList items = playlistSelection();

    Playlist *p = visiblePlaylist();
    if(p)
	p->remove(items);
}

void PlaylistSplitter::clearSelectedItems()
{
    PlaylistItemList items = playlistSelection();
371

372 373 374
    Playlist *p = visiblePlaylist();
    if(p)
	p->clearItems(items); 
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
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlaylistSplitter::setupLayout()
{
    playlistBox = new PlaylistBox(this, "playlistBox");

    // Create a splitter to go between the playlists and the editor.

    QSplitter *editorSplitter = new QSplitter(Qt::Vertical, this, "editorSplitter");

    // Create the playlist and the editor.

    playlistStack = new QWidgetStack(editorSplitter, "playlistStack");
    editor = new TagEditor(editorSplitter, "tagEditor");

    // Make the editor as small as possible (or at least as small as recommended)

    editorSplitter->setResizeMode(editor, QSplitter::FollowSizeHint);

    // Make the connection that will update the selected playlist when a 
    // selection is made in the playlist box.

401 402 403
    connect(playlistBox, SIGNAL(currentChanged(PlaylistBoxItem *)), 
	    this, SLOT(changePlaylist(PlaylistBoxItem *)));

404
    connect(playlistBox, SIGNAL(doubleClicked()), this, SIGNAL(listBoxDoubleClicked()));
405 406 407 408 409 410

    // Create the collection list; this should always exist.  This has a 
    // slightly different creation process than normal playlists (since it in
    // fact is a subclass) so it is created here rather than by using 
    // createPlaylist().

411
    CollectionList::initialize(this, playlistStack, restore);
412
    collection = CollectionList::instance();
413
    setupPlaylist(collection, true, "folder_sound");
414 415

    // Show the collection on startup.
416
    playlistBox->setSelected(0, true);
417 418 419 420
}

void PlaylistSplitter::readConfig()
{
421 422 423 424
    KConfig *config = KGlobal::config();
    { // block for Playlists group
	KConfigGroupSaver saver(config, "Playlists");

425
	if(restore) {
426 427 428 429

	    QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";

	    QFile f(playlistsFile);
430
	    
431 432 433 434 435 436 437 438 439 440 441 442 443
	    if(f.open(IO_ReadOnly)) {
		QDataStream s(&f);
		while(!s.atEnd()) {
		    Playlist *p = new Playlist(this, playlistStack);
		    s >> *p;

		    // check to see if we've alredy loaded this item before continuing

		    if(!playlistFiles.insert(p->fileName()))
			setupPlaylist(p);
		    else
			delete p;
		}
444
	    }
445

446
	    directoryList = config->readListEntry("DirectoryList");
447
	    open(directoryList);
448
	}
449 450 451 452 453 454 455 456 457 458 459 460 461 462

	// restore the list of hidden and shown columns

	if(collection) {
	    // the last column is just a filler
	    _visibleColumns.resize(collection->columns() - 1, true);
	    QValueList<int> l = config->readIntListEntry("VisibleColumns");

	    uint i = 0;
	    for(QValueList<int>::Iterator it = l.begin(); it != l.end(); ++it) {
		if(! bool(*it)) {
		    _visibleColumns[i] = bool(*it);
		    collection->hideColumn(i);
		}
463 464 465 466 467

		// while we're looping go ahead and populate _columnNames
		
		_columnNames.append(collection->columnText(i));

468 469 470 471
		i++;
	    }
	    setupColumns(collection);
	}
472 473 474 475 476 477 478
    }
}	


void PlaylistSplitter::saveConfig()
{
    KConfig *config = KGlobal::config();
479

480 481
    // Save the list of open playlists.
    
482
    if(restore && playlistBox) {
483 484 485

	// Start at item 1.  We want to skip the collection list.

486 487 488 489 490 491 492 493 494 495 496 497
	QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";
	QFile f(playlistsFile);
	
	if(f.open(IO_WriteOnly)) {

	    QDataStream s(&f);

	    for(uint i = 1; i < playlistBox->count(); i++) {
		PlaylistBoxItem *item = static_cast<PlaylistBoxItem *>(playlistBox->item(i));
		if(item && item->playlist()) {
		    Playlist *p = item->playlist();
		    s << *p;
498
		}
499 500 501 502
	    }

	    f.close();

503 504 505
	}
	{ // block for Playlists group
	    KConfigGroupSaver saver(config, "Playlists");
506
	    config->writeEntry("DirectoryList", directoryList);
507 508 509 510 511 512

	    QValueList<int> l;
	    for(uint i = 0; i < _visibleColumns.size(); i++)
		l.append(int(_visibleColumns[i]));
	    
	    config->writeEntry("VisibleColumns", l);
513 514
	}
    }
515 516
}

517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
void PlaylistSplitter::addImpl(const QString &file, Playlist *list)
{
    processEvents();
    QFileInfo fileInfo(QDir::cleanDirPath(file));
    if(fileInfo.exists()) {
        if(fileInfo.isDir()) {
            QDir dir(fileInfo.filePath());
            QStringList dirContents=dir.entryList();
            for(QStringList::Iterator it = dirContents.begin(); it != dirContents.end(); ++it)
                if(*it != "." && *it != "..")
                    addImpl(fileInfo.filePath() + QDir::separator() + *it, list);
        }
        else {
            QString extension = fileInfo.extension(false);
            if(mediaExtensions.contains(extension) > 0)
		list->createItem(fileInfo);
	    else if(listExtensions.contains(extension) > 0)
		openPlaylist(fileInfo.absFilePath());
        }
    }    
}

539
void PlaylistSplitter::setupPlaylist(Playlist *p, bool raise, const char *icon)
540 541 542 543 544 545 546 547 548
{
    PlaylistBoxItem *i = new PlaylistBoxItem(playlistBox, SmallIcon(icon, 32), p->name(), p);
    p->setPlaylistBoxItem(i);
    playlistBox->sort();

    connect(p, SIGNAL(selectionChanged(const PlaylistItemList &)), editor, SLOT(setItems(const PlaylistItemList &)));
    connect(p, SIGNAL(doubleClicked()), this, SIGNAL(doubleClicked()));
    connect(p, SIGNAL(collectionChanged()), editor, SLOT(updateCollection()));
    connect(p, SIGNAL(numberOfItemsChanged(Playlist *)), this, SLOT(playlistCountChanged(Playlist *)));
549
    connect(p, SIGNAL(aboutToRemove(PlaylistItem *)), this, SLOT(playlistItemRemoved(PlaylistItem *)));
550

551 552
    connect(p, SIGNAL(signalToggleColumnVisible(int)), this, SLOT(slotToggleColumnVisible(int)));

553 554
    if(raise) {
	playlistStack->raiseWidget(p);
555
	setupColumns(p);
556 557 558
	playlistBox->setCurrentItem(i);
	playlistBox->ensureCurrentVisible();
    }
559 560
}

561 562 563 564
Playlist *PlaylistSplitter::openPlaylist(const QString &file)
{
    QFileInfo fileInfo(file);
    if(!fileInfo.exists() || !fileInfo.isFile() || !fileInfo.isReadable() || playlistFiles.insert(fileInfo.absFilePath()))
565
	return 0;
566 567 568

    Playlist *p = new Playlist(this, file, playlistStack, fileInfo.baseName(true).latin1());
    setupPlaylist(p);
569
    return p;
570 571
}

572 573 574 575 576 577 578 579 580 581 582 583 584
void PlaylistSplitter::setupColumns(Playlist *p)
{
    if(!p)
	return;
    
    for(uint i = 0; i < _visibleColumns.size(); i++) {
	if(_visibleColumns[i] && ! p->isColumnVisible(i))
	    p->showColumn(i);
	else if(! _visibleColumns[i] && p->isColumnVisible(i))
	    p->hideColumn(i);
    }
}

585 586 587 588 589 590 591 592 593
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

void PlaylistSplitter::changePlaylist(PlaylistBoxItem *item)
{
    if(item && item->playlist()) {
	playlistStack->raiseWidget(item->playlist());
	editor->setItems(playlistSelection());
594
	setupColumns(item->playlist());
595
	emit(playlistChanged());
596 597 598
    }
}

599 600 601 602 603 604
void PlaylistSplitter::playlistCountChanged(Playlist *p)
{
    if(p && p == playlistStack->visibleWidget())
	emit(selectedPlaylistCountChanged(p->childCount()));
}

605 606 607 608 609 610
void PlaylistSplitter::playlistItemRemoved(PlaylistItem *item)
{
    if(item == playingItem)
	playingItem = 0;
}

611 612 613
void PlaylistSplitter::slotToggleColumnVisible(int column)
{
    _visibleColumns[column] = ! _visibleColumns[column];
614 615
    if(visiblePlaylist())
	setupColumns(visiblePlaylist());
616 617
}

618
#include "playlistsplitter.moc"