playlistsplitter.cpp 17.7 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
#include <kdirwatch.h>
24 25
#include <kdebug.h>

26
#include <qinputdialog.h>
27
#include <qtimer.h>
28

29
#include "playlistitem.h"
30
#include "playlistsplitter.h"
31
#include "collectionlist.h"
32
#include "directorylist.h"
33 34
#include "playlist.h"

35 36
QStringList *PlaylistSplitter::m_mediaExtensions = 0;
QStringList *PlaylistSplitter::m_listExtensions = 0;
37 38

////////////////////////////////////////////////////////////////////////////////
39
// helper functions
40 41
////////////////////////////////////////////////////////////////////////////////

42
void processEvents()
43
{
44 45 46
    static int processed = 0;
    if(processed == 0)
        kapp->processEvents();
47
    processed = (processed + 1) % 5;
48 49
}

50
////////////////////////////////////////////////////////////////////////////////
51
// public methods
52 53
////////////////////////////////////////////////////////////////////////////////

54
PlaylistSplitter::PlaylistSplitter(QWidget *parent, bool restore, const char *name) : QSplitter(Qt::Horizontal, parent, name), 
55
										      m_playingItem(0), m_restore(restore), m_nextPlaylistItem(0)
56
{
57 58 59 60 61 62 63 64
    if(!m_mediaExtensions && !m_listExtensions) {
	m_mediaExtensions = new QStringList();
	m_listExtensions = new QStringList();

	m_mediaExtensions->append("mp3");
	m_mediaExtensions->append("ogg");
	m_listExtensions->append("m3u");
    }
65 66 67

    setupLayout();
    readConfig();
68 69

    m_editor->slotUpdateCollection();
70
}
71

72
PlaylistSplitter::~PlaylistSplitter()
73
{
Scott Wheeler's avatar
Scott Wheeler committed
74
    delete m_dirWatch;
75
    saveConfig();
76 77
}

78
QString PlaylistSplitter::uniquePlaylistName(const QString &startingWith, bool useParenthesis)
79
{
80
    if(!m_playlistBox)
81
	return QString::null;
82

83
    QStringList names = m_playlistBox->names();
84 85 86 87 88 89

    int playlistNumber = 1;

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

90 91 92 93
    if(useParenthesis) {
	while(names.contains(startingWith + " (" + QString::number(playlistNumber) + ")") != 0)
	    playlistNumber++;
	
94
	return startingWith + " (" + QString::number(playlistNumber) + ")";	
95 96 97 98 99 100
    }
    else
    {
	while(names.contains(startingWith + ' ' + QString::number(playlistNumber)) != 0)
	    playlistNumber++;
	
101
	return startingWith + " " + QString::number(playlistNumber);
102
    }
103 104
}

105
QString PlaylistSplitter::playNextFile(bool random, bool loopPlaylist)
106
{
107 108
    PlaylistItem *i;

109 110 111
    // Four basic cases here:  (1) We've asked for a specific next item, (2) play
    // the item that's after the currently playing item, (3) play the selected 
    // item or (4) play the first item in the list.
112

113
    if(m_nextPlaylistItem && m_nextPlaylistItem != m_playingItem) {
114 115 116
        i = m_nextPlaylistItem;
        m_nextPlaylistItem = 0;
    }
117
    else if(m_playingItem) {
118 119
        Playlist *p = static_cast<Playlist *>(m_playingItem->listView());
        i = p->nextItem(m_playingItem, random);
120
        if(!i && loopPlaylist)
121
            i = static_cast<PlaylistItem *>(p->firstChild());
122 123
    }
    else {
124 125 126
        i = playlistSelection().getFirst();
        if(!i)
            i = static_cast<PlaylistItem *>(visiblePlaylist()->firstChild());
127 128
    }

129
    return play(i);
130 131
}

132
QString PlaylistSplitter::playPreviousFile(bool random)
133
{
134
    if(!m_playingItem)
135
	return QString::null;
136

137 138
    Playlist *p = static_cast<Playlist *>(m_playingItem->listView());
    PlaylistItem *i = p->previousItem(m_playingItem, random);
139

140
    return play(i);
141 142
}

143 144 145 146
QString PlaylistSplitter::playFirstFile()
{
    Playlist *p = visiblePlaylist();
    PlaylistItem *i = static_cast<PlaylistItem *>(p->firstChild());
147

148
    return play(i);
149 150
}

151 152
void PlaylistSplitter::stop()
{
153 154
    m_nextPlaylistItem = 0;

155 156 157 158 159 160 161
    if(!m_playingItem)
	return;

    Playlist *p = static_cast<Playlist *>(m_playingItem->listView());

    p->setPlaying(m_playingItem, false);
    m_playingItem = 0;
162 163 164 165
}

QString PlaylistSplitter::playingArtist() const
{
166 167
    if(m_playingItem)
	return m_playingItem->text(PlaylistItem::ArtistColumn);
168
    else
169
	return QString::null;
170 171 172 173
}

QString PlaylistSplitter::playingTrack() const
{
174 175
    if(m_playingItem)
	return m_playingItem->text(PlaylistItem::TrackColumn);
176
    else
177
	return QString::null;
178 179 180 181
}

QString PlaylistSplitter::playingList() const
{
182 183
    if(m_playingItem)
	return static_cast<Playlist *>(m_playingItem->listView())->name();
184
    else
185
	return QString::null;
186 187
}

188
QString PlaylistSplitter::extensionsString(const QStringList &extensions, const QString &type) // static
189
{
190 191 192 193 194 195 196 197 198 199 200 201
    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(", ") + ")";

202
    return s;
203 204
}

205 206
void PlaylistSplitter::open(const QString &file) 
{
207 208 209
    if(file.isEmpty())
	return;

210
    if(visiblePlaylist() == m_collection || 
211 212
       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)
213
	slotAddToPlaylist(file, m_collection);
214
    else
215
	slotAddToPlaylist(file, visiblePlaylist());
216 217 218 219
}

void PlaylistSplitter::open(const QStringList &files) 
{
220 221 222
    if(files.isEmpty())
	return;
    
223
    if(visiblePlaylist() == m_collection || 
224 225
       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)
226
	slotAddToPlaylist(files, m_collection);
227
    else
228
	slotAddToPlaylist(files, visiblePlaylist());
229 230 231 232
}

Playlist *PlaylistSplitter::createPlaylist(const QString &name)
{
233
    Playlist *p = new Playlist(m_playlistStack, name.latin1());
234 235
    setupPlaylist(p, true);
    return p;
236 237
}

238 239 240 241
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

242
void PlaylistSplitter::slotOpen()
243
{
244
    QStringList files = KFileDialog::getOpenFileNames(QString::null, 
245
						      extensionsString((*m_mediaExtensions + *m_listExtensions), i18n("Media Files")));
246 247 248
    open(files);
}

249
void PlaylistSplitter::slotOpenDirectory()
250
{ 
251
    DirectoryList *l = new DirectoryList(m_directoryList, this, "directoryList");
252

253
    m_directoryQueue.clear();
254
    m_directoryQueueRemove.clear();
255

256 257
    connect(l, SIGNAL(signalDirectoryAdded(const QString &)), this, SLOT(slotQueueDirectory(const QString &)));
    connect(l, SIGNAL(signalDirectoryRemoved(const QString &)), this, SLOT(slotQueueDirectoryRemove(const QString &)));
258 259

    if(l->exec() == QDialog::Accepted) {
260
	open(m_directoryQueue);
261 262 263
	for(QStringList::Iterator it = m_directoryQueue.begin(); it !=  m_directoryQueue.end(); it++)
	    m_dirWatch->addDir(*it, false, true);
	    
264
	m_directoryList += m_directoryQueue;
265 266
	for(QStringList::Iterator it = m_directoryQueueRemove.begin(); it !=  m_directoryQueueRemove.end(); it++) {
	    m_dirWatch->removeDir(*it);
267
	    m_directoryList.remove(*it);
268
	}
269 270
    }
}
271
void PlaylistSplitter::slotSetEditorVisible(bool visible)
272 273
{
    if(visible)
274
	m_editor->show();
275
    else
276
	m_editor->hide();
277 278
}

279
Playlist *PlaylistSplitter::slotCreatePlaylist()
280 281 282
{
    bool ok;

283
    // If this text is changed, please also change it in PlaylistBox::duplicate().
284 285 286 287

    QString name = QInputDialog::getText(i18n("New Playlist..."), i18n("Please enter a name for the new playlist:"),
					 QLineEdit::Normal, uniquePlaylistName(), &ok);
    if(ok)
288
	return createPlaylist(name);
289
    else
290
	return 0;
291 292
}

293
void PlaylistSplitter::slotSelectPlaying()
294
{
295
    if(!m_playingItem)
296 297
	return;

298
    Playlist *l = static_cast<Playlist *>(m_playingItem->listView());
299
	
300 301
    if(!l)
	return;
302

303
    l->clearSelection();
304 305
    l->setSelected(m_playingItem, true);
    l->ensureItemVisible(m_playingItem);
306
    
307
    m_playlistBox->raise(l);
308 309
}

310
void PlaylistSplitter::slotDeleteSelectedItems()
311 312 313
{
    Playlist *p = visiblePlaylist();
    if(p)
314
	p->slotDeleteSelectedItems();
315 316
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
void PlaylistSplitter::slotAddToPlaylist(const QString &file, Playlist *list)
{
    KApplication::setOverrideCursor(Qt::waitCursor);
    addImpl(file, list);
    KApplication::restoreOverrideCursor();
    
    if(m_editor)
	m_editor->slotUpdateCollection();
}

void PlaylistSplitter::slotAddToPlaylist(const QStringList &files, Playlist *list)
{
    KApplication::setOverrideCursor(Qt::waitCursor);
    for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it)
        addImpl(*it, list);
    KApplication::restoreOverrideCursor();

    if(m_editor)
	m_editor->slotUpdateCollection();
}

338
void PlaylistSplitter::slotToggleColumnVisible(int column)
339
{
340 341 342
    m_visibleColumns[column] = ! m_visibleColumns[column];
    if(visiblePlaylist())
	setupColumns(visiblePlaylist());
343 344
}

345 346 347 348 349 350
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlaylistSplitter::setupLayout()
{
351
    m_playlistBox = new PlaylistBox(this, "playlistBox");
352 353 354 355 356 357 358

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

359 360
    m_playlistStack = new QWidgetStack(editorSplitter, "playlistStack");
    m_editor = new TagEditor(editorSplitter, "tagEditor");
361 362 363

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

364
    editorSplitter->setResizeMode(m_editor, QSplitter::FollowSizeHint);
365 366 367 368

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

369 370
    connect(m_playlistBox, SIGNAL(signalCurrentChanged(Playlist *)), 
	    this, SLOT(slotChangePlaylist(Playlist *)));
371

372
    connect(m_playlistBox, SIGNAL(signalDoubleClicked()), this, SIGNAL(signalListBoxDoubleClicked()));
373 374 375 376

    // 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 
377
    // slotCreatePlaylist().
378

379
    CollectionList::initialize(m_playlistStack, m_restore);
380 381
    m_collection = CollectionList::instance();
    setupPlaylist(m_collection, true, "folder_sound");
382
    connect(m_collection, SIGNAL(signalCollectionChanged()), m_editor, SLOT(slotUpdateCollection()));
383 384

    // Show the collection on startup.
385
    m_playlistBox->setSelected(0, true);
386 387 388 389
}

void PlaylistSplitter::readConfig()
{
390 391 392 393
    KConfig *config = KGlobal::config();
    { // block for Playlists group
	KConfigGroupSaver saver(config, "Playlists");

394 395 396 397 398 399 400
	QValueList<int> splitterSizes = config->readIntListEntry("PlaylistSplitterSizes");
	if(splitterSizes.isEmpty()) {
	    splitterSizes.append(100);
	    splitterSizes.append(640);
	}
	setSizes(splitterSizes);
	
401
	if(m_restore) {
402 403 404 405

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

	    QFile f(playlistsFile);
406
	    
407 408 409
	    if(f.open(IO_ReadOnly)) {
		QDataStream s(&f);
		while(!s.atEnd()) {
410
		    Playlist *p = new Playlist(m_playlistStack);
411 412 413 414
		    s >> *p;

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

415
		    if(p->fileName().isEmpty() || !m_playlistFiles.insert(p->fileName()))
416 417 418 419
			setupPlaylist(p);
		    else
			delete p;
		}
420
	    }
421

422
	    m_directoryList = config->readListEntry("DirectoryList");
423
	    QTimer::singleShot(0, this, SLOT(slotScanDirectories()));
424 425 426 427 428 429 430 431

	    m_dirWatch = new KDirWatch();
	    connect(m_dirWatch, SIGNAL(dirty(const QString &)), this, SLOT(slotDirChanged(const QString &)));

            for(QStringList::Iterator it = m_directoryList.begin(); it != m_directoryList.end(); ++it)
		m_dirWatch->addDir(*it, false, true);

	    m_dirWatch->startScan();
432
	}
433 434 435

	// restore the list of hidden and shown columns

436
	if(m_collection) {
437
	    // the last column is just a filler
438
	    m_visibleColumns.resize(m_collection->columns() - 1, true);
439
	    QValueList<int> l = config->readIntListEntry("VisibleColumns");
Scott Wheeler's avatar
Scott Wheeler committed
440
	    m_collection->setSorting(config->readNumEntry("SortColumn", 1));
441 442 443 444

	    uint i = 0;
	    for(QValueList<int>::Iterator it = l.begin(); it != l.end(); ++it) {
		if(! bool(*it)) {
445
		    m_visibleColumns[i] = bool(*it);
446
		    m_collection->hideColumn(i);
447
		}
448

449
		// while we're looping go ahead and populate m_columnNames
450
		
451
		m_columnNames.append(m_collection->columnText(i));
452

453 454
		i++;
	    }
455
	    setupColumns(m_collection);
456
	}
457
    }
458
}
459 460 461 462

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

464 465
    // Save the list of open playlists.
    
466
    if(m_restore && m_playlistBox) {
467 468 469

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

470 471 472 473 474 475 476
	QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";
	QFile f(playlistsFile);
	
	if(f.open(IO_WriteOnly)) {

	    QDataStream s(&f);

477
	    QPtrList<Playlist> l = m_playlistBox->playlists();
478

479 480
	    for(Playlist *p = l.first(); p; p = l.next())
		s << *p;
481

482
	    f.close();
483 484 485
	}
	{ // block for Playlists group
	    KConfigGroupSaver saver(config, "Playlists");
486
	    config->writeEntry("DirectoryList", m_directoryList);
487 488

	    QValueList<int> l;
489 490
	    for(uint i = 0; i < m_visibleColumns.size(); i++)
		l.append(int(m_visibleColumns[i]));
491 492
	    
	    config->writeEntry("VisibleColumns", l);
493
	    config->writeEntry("SortColumn", m_collection->sortColumn());
494 495

	    config->writeEntry("PlaylistSplitterSizes", sizes());
496 497
	}
    }
498 499
}

500 501 502 503 504 505 506 507 508 509 510 511 512 513
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);
514
            if(m_mediaExtensions->contains(extension) > 0)
515
		list->createItem(fileInfo);
516
	    else if(m_listExtensions->contains(extension) > 0)
517 518 519 520 521
		openPlaylist(fileInfo.absFilePath());
        }
    }    
}

522
void PlaylistSplitter::setupPlaylist(Playlist *p, bool raise, const char *icon)
523
{
524 525 526 527
    connect(p, SIGNAL(signalSelectionChanged(const PlaylistItemList &)), m_editor, SLOT(slotSetItems(const PlaylistItemList &)));
    connect(p, SIGNAL(signalDoubleClicked()), this, SIGNAL(signalDoubleClicked()));
    connect(p, SIGNAL(signalNumberOfItemsChanged(Playlist *)), this, SLOT(slotPlaylistCountChanged(Playlist *)));
    connect(p, SIGNAL(signalAboutToRemove(PlaylistItem *)), this, SLOT(slotPlaylistItemRemoved(PlaylistItem *)));
528 529
    connect(p, SIGNAL(signalFilesDropped(const QStringList &, Playlist *)), this, SLOT(slotAddToPlaylist(const QStringList &, Playlist *)));
    connect(p, SIGNAL(signalSetNext(PlaylistItem *)), this, SLOT(slotSetNextItem(PlaylistItem *)));
530

531 532
    connect(p, SIGNAL(signalToggleColumnVisible(int)), this, SLOT(slotToggleColumnVisible(int)));

533
    m_playlistBox->createItem(p, icon, raise);
534

535
    if(raise) {
536
	m_playlistStack->raiseWidget(p);
537
	setupColumns(p);
538
    }
539 540
}

541 542 543
Playlist *PlaylistSplitter::openPlaylist(const QString &file)
{
    QFileInfo fileInfo(file);
544
    if(!fileInfo.exists() || !fileInfo.isFile() || !fileInfo.isReadable() || m_playlistFiles.insert(fileInfo.absFilePath()))
545
	return 0;
546

547
    Playlist *p = new Playlist(file, m_playlistStack, fileInfo.baseName(true).latin1());
548
    setupPlaylist(p);
549
    return p;
550 551
}

552 553 554 555 556
void PlaylistSplitter::setupColumns(Playlist *p)
{
    if(!p)
	return;
    
557 558
    for(uint i = 0; i < m_visibleColumns.size(); i++) {
	if(m_visibleColumns[i] && ! p->isColumnVisible(i))
559
	    p->showColumn(i);
560
	else if(! m_visibleColumns[i] && p->isColumnVisible(i))
561 562 563 564
	    p->hideColumn(i);
    }
}

565 566 567 568 569 570 571
QString PlaylistSplitter::play(PlaylistItem *item)
{
    stop();

    if(!item)
	return QString::null;

572 573 574 575
    Playlist *p = static_cast<Playlist *>(item->listView());

    p->setPlaying(item, true);

576 577 578 579 580
    m_playingItem = item;

    return item->absFilePath();
}

581 582 583 584
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

585
void PlaylistSplitter::slotChangePlaylist(Playlist *p)
586
{
587 588
    if(!p)
	return;
589 590

    m_nextPlaylistItem = 0; 
591
    m_playlistStack->raiseWidget(p);
592
    m_editor->slotSetItems(playlistSelection());
593
    setupColumns(p);
594
    emit signalPlaylistChanged();
595 596
}

597
void PlaylistSplitter::slotPlaylistCountChanged(Playlist *p)
598
{
599 600
    if(p && p == m_playlistStack->visibleWidget())
	emit signalSelectedPlaylistCountChanged(p->childCount());
601 602
}

603
void PlaylistSplitter::slotPlaylistItemRemoved(PlaylistItem *item)
604
{
605 606
    if(item == m_playingItem)
	m_playingItem = 0;
607 608 609

    if(item == m_nextPlaylistItem)
	m_nextPlaylistItem = 0;
610 611
}

612
#include "playlistsplitter.moc"