playlistsplitter.cpp 28.6 KB
Newer Older
1 2 3 4
/***************************************************************************
                          playlistsplitter.cpp  -  description
                             -------------------
    begin                : Fri Sep 13 2002
5
    copyright            : (C) 2002, 2003 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 <kstandarddirs.h>
19
#include <kmessagebox.h>
20
#include <kinputdialog.h>
21
#include <kcmdlineargs.h>
22
#include <kaction.h>
23 24
#include <kdebug.h>

25
#include <qpopupmenu.h>
26
#include <qlayout.h>
27

28
#include "playlistsplitter.h"
29
#include "searchwidget.h"
30
#include "directorylist.h"
31
#include "playlistsearch.h"
32
#include "dynamicplaylist.h"
33
#include "searchplaylist.h"
34
#include "historyplaylist.h"
35
#include "mediafiles.h"
36
#include "advancedsearchdialog.h"
37
#include "actioncollection.h"
38
#include "viewmode.h"
39
#include "tag.h"
40 41

using namespace ActionCollection;
42 43

////////////////////////////////////////////////////////////////////////////////
44
// helper functions
45 46
////////////////////////////////////////////////////////////////////////////////

47
void processEvents()
48
{
49 50 51
    static int processed = 0;
    if(processed == 0)
        kapp->processEvents();
52
    processed = (processed + 1) % 5;
53 54
}

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
////////////////////////////////////////////////////////////////////////////////
// helper class
////////////////////////////////////////////////////////////////////////////////

// Painting is slow, so we want to be able to ignore the fact that QListView
// likes to do it so much.  Specifically while loading -- when without a bit of
// hackery it takes more time to paint the new items than it does to read them.
// This helper class operates on a Playlist while loading items and throws out
// most of the repaint events that are issued.

class PaintEater : public QObject
{
public:
    PaintEater(Playlist *list) : QObject(list), m_list(list),
				 m_allowOne(false), m_previousHeight(0)
    {
        // We want to catch paint events for both the contents and the frame of
        // our listview.

	list->installEventFilter(this);
	list->viewport()->installEventFilter(this);
    }

private:
    virtual bool eventFilter(QObject *o, QEvent *e)
    {
	if(e->type() == QEvent::Paint) {

            // There are two cases where we want to let our viewport repaint
            // itself -- if the actual contents have changed as indicated by
            // m_allowOne being true, or if the height has changed indicating
            // that we've either scrolled or resized the widget.

	    if(o == m_list->viewport()) {
                if(m_allowOne) {
                    m_allowOne = false;
                    return false;
                }

                int newHeight = static_cast<QPaintEvent *>(e)->rect().top();

                if(m_previousHeight != newHeight) {
                    m_previousHeight = newHeight;
                    return false;
                }
            }
            else
		m_allowOne = true;

	    if(m_list->count() < 20)
                m_list->slotWeightDirty();

	    return true;
	}

	return false;
    }

    Playlist *m_list;
    bool m_allowOne;
    int m_previousHeight;
};

118
////////////////////////////////////////////////////////////////////////////////
119
// public methods
120 121
////////////////////////////////////////////////////////////////////////////////

122
PlaylistSplitter::PlaylistSplitter(QWidget *parent, const char *name) :
123
    QSplitter(Qt::Horizontal, parent, name),
124
    m_playingItem(0), m_searchWidget(0), m_history(0),
125
    m_dynamicList(0), m_importPlaylists(true), m_nextPlaylistItem(0)
126
{
127 128 129 130 131 132
#ifndef NO_DEBUG
    m_restore = KCmdLineArgs::parsedArgs()->isSet("restore");
#else
    m_restore = true;
#endif

133 134
    setupLayout();
    readConfig();
135

136 137
    connect(action("stop"), SIGNAL(activated()), this, SLOT(stop()));

138
    m_editor->slotUpdateCollection();
139 140 141

    if(m_collection->childCount() == 0)
	QTimer::singleShot(0, this, SLOT(slotOpenDirectory()));
142
}
143

144
PlaylistSplitter::~PlaylistSplitter()
145
{
146
    saveConfig();
147 148
}

149
QString PlaylistSplitter::uniquePlaylistName(const QString &startingWith, bool useParenthesis)
150
{
151
    if(!m_playlistBox)
152
	return QString::null;
153

154
    QStringList names = m_playlistBox->names();
155 156 157

    int playlistNumber = 1;

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

161
    if(useParenthesis) {
162
	while(names.find(startingWith + " (" + QString::number(playlistNumber) + ")") != names.end())
163
	    playlistNumber++;
164 165

	return startingWith + " (" + QString::number(playlistNumber) + ")";
166
    }
167
    else {
168
	while(names.find(startingWith + ' ' + QString::number(playlistNumber)) != names.end())
169
	    playlistNumber++;
170

171
	return startingWith + " " + QString::number(playlistNumber);
172
    }
173 174
}

175 176 177 178 179
QString PlaylistSplitter::name() const
{
    return m_playingItem ? m_playingItem->playlist()->name() : QString::null;
}

180
FileHandle PlaylistSplitter::nextFile()
181 182 183 184 185 186 187 188
{
    PlaylistItem *i = 0;

    if(m_nextPlaylistItem && m_nextPlaylistItem != m_playingItem) {
        i = m_nextPlaylistItem;
        m_nextPlaylistItem = 0;
    }
    else if(m_playingItem) {
189

190
	bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
191
	bool loop = action("loopPlaylist") && action<KToggleAction>("loopPlaylist")->isChecked();
192 193 194 195 196

        i = m_playingItem->playlist()->nextItem(m_playingItem, random);

	if(!i && loop)
	    i = m_playingItem->playlist()->visibleItems().front();
197 198 199 200 201
    }

    return play(i);
}

202
FileHandle PlaylistSplitter::currentFile()
203 204 205
{
    PlaylistItem *i = 0;

206
    bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
207
    
208 209
    PlaylistItemList selection = playlistSelection();

210 211 212 213 214 215 216
    if(m_nextPlaylistItem && m_nextPlaylistItem != m_playingItem) {
        i = m_nextPlaylistItem;
        m_nextPlaylistItem = 0;
    }

    // Play the selected item.

217 218
    else if(!selection.isEmpty()) {
        i = selection.first();
219 220 221 222 223 224 225 226 227 228 229 230 231 232
        if(!i)
	    i = m_playingItem->playlist()->nextItem(0, random);
    }

    // Play the first item in the list.

    else {
	kdDebug(65432) << k_funcinfo << "Playing the first item in the current list." << endl;
	i = visiblePlaylist()->nextItem(0, random);
    }

    return play(i);
}

233
FileHandle PlaylistSplitter::previousFile()
234
{
235
    if(!m_playingItem)
236
	return FileHandle::null();
237

238
    bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
239
    Playlist *p = m_playingItem->playlist();
240
    PlaylistItem *i = p->previousItem(m_playingItem, random);
241

242
    return play(i);
243 244
}

245
void PlaylistSplitter::populatePlayHistoryMenu(QPopupMenu *menu, bool random)
246
{
247
    Playlist *p = m_playingItem->playlist();
248 249 250
    PlaylistItemList list = p->historyItems(m_playingItem, random);
    menu->clear();
    int i = 0;
251
    for(PlaylistItemList::Iterator it = list.begin(); it != list.end(); ++it)
252
        menu->insertItem((*it)->file().tag()->title(), ++i);
253 254
}

255
void PlaylistSplitter::open(const QString &file)
256
{
257 258 259
    if(file.isEmpty())
	return;

260 261 262 263 264
    if(visiblePlaylist() == m_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")),
265 266
				  KGuiItem(i18n("Collection"))) == KMessageBox::No)
    {
267
	slotAddToPlaylist(file, m_collection);
268
    }
269
    else
270
	slotAddToPlaylist(file, visiblePlaylist());
271 272
}

273
void PlaylistSplitter::open(const QStringList &files)
274
{
275 276
    if(files.isEmpty())
	return;
277 278 279 280 281 282 283

    if(visiblePlaylist() == m_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)
284
    {
285
	slotAddToPlaylist(files, m_collection);
286
    }
287
    else
288
	slotAddToPlaylist(files, visiblePlaylist());
289 290
}

291
Playlist *PlaylistSplitter::createPlaylist(const QString &name, bool raise)
292
{
293
    Playlist *p = new Playlist(m_playlistStack, name);
294
    setupPlaylist(p, raise);
295
    return p;
296 297
}

298 299 300 301 302 303 304 305
void PlaylistSplitter::setDirWatchEnabled(bool enabled)
{
    if(enabled)
	m_dirWatch.startScan();
    else
	m_dirWatch.stopScan();
}

306 307 308 309
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

310
void PlaylistSplitter::slotOpen()
311
{
312
    open(MediaFiles::openDialog(this));
313 314
}

315
void PlaylistSplitter::slotOpenDirectory()
316
{
317 318
    DirectoryList l(m_directoryList, m_importPlaylists, this, "directoryList");
    DirectoryList::Result result = l.exec();
319

320
    if(result.status == QDialog::Accepted) {
321

322 323 324
	for(QStringList::Iterator it = result.addedDirs.begin();
	    it != result.addedDirs.end(); it++)
	{
Scott Wheeler's avatar
Scott Wheeler committed
325
	    m_dirWatch.addDir(*it, false, true);
326 327
	    m_directoryList.append(*it);
	}
328

329 330 331
	for(QStringList::Iterator it = result.removedDirs.begin();
	    it !=  result.removedDirs.end(); it++)
	{
Scott Wheeler's avatar
Scott Wheeler committed
332
	    m_dirWatch.removeDir(*it);
333
	    m_directoryList.remove(*it);
334
	}
335 336 337 338 339 340 341 342


	if(result.addPlaylists && !m_importPlaylists)
	    open(m_directoryList);
	else
	    open(result.addedDirs);

	m_importPlaylists = result.addPlaylists;
343 344
    }
}
345

346
Playlist *PlaylistSplitter::slotCreatePlaylist(const QString &name, bool raise)
347
{
348 349 350
    if(!name.isNull())
	return createPlaylist(name, raise);

351 352
    bool ok;

353
    // If this text is changed, please also change it in PlaylistBox::duplicate().
354

355 356
    QString s = KInputDialog::getText(
	i18n("Create New Playlist"),
Nadeem Hasan's avatar
Nadeem Hasan committed
357
	i18n("Please enter a name for the new playlist:"),
358 359
	uniquePlaylistName(), &ok);
    
360
    if(ok)
361
	return createPlaylist(s, raise);
362
    else
363
	return 0;
364 365
}

366 367 368 369 370 371
Playlist *PlaylistSplitter::slotCreatePlaylistFromDir()
{
    const QString dirName = KFileDialog::getExistingDirectory();
    if(dirName.isEmpty())
        return 0;

372
    Playlist *playlist = slotCreatePlaylist(dirName.mid(dirName.findRev('/') + 1));
373 374 375
    if(!playlist)
        return 0;

376
    slotAddToPlaylist(dirName, playlist);
377 378 379 380

    return playlist;
}

381
void PlaylistSplitter::slotSelectPlaying()
382
{
383
    if(!m_playingItem)
384 385
	return;

386
    Playlist *l = m_playingItem->playlist();
387

388 389
    if(!l)
	return;
390

391
    l->clearSelection();
392 393
    l->setSelected(m_playingItem, true);
    l->ensureItemVisible(m_playingItem);
Nadeem Hasan's avatar
Nadeem Hasan committed
394

395 396
    if(l != visiblePlaylist())
	m_playlistBox->raise(l);
397 398

    m_playlistBox->ensureCurrentVisible();
399 400
}

401
void PlaylistSplitter::slotDeleteSelectedItems()
402 403 404
{
    Playlist *p = visiblePlaylist();
    if(p)
405
	p->slotRemoveSelectedItems();
406 407
}

408 409
void PlaylistSplitter::slotReloadPlaylist()
{
410
    if(visiblePlaylist() == m_collection)
411 412 413 414 415
	slotScanDirectories();
    else
	visiblePlaylist()->slotReload();
}

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
void PlaylistSplitter::slotEditSearch()
{
    SearchPlaylist *p = dynamic_cast<SearchPlaylist *>(visiblePlaylist());

    if(!p)
	return;

    AdvancedSearchDialog::Result r =
	AdvancedSearchDialog(p->name(), p->playlistSearch(), this).exec();

    if(r.result == AdvancedSearchDialog::Accepted) {
	p->setPlaylistSearch(r.search);
	p->setName(r.playlistName);
	m_playlistBox->viewMode()->queueRefresh();
    }
}

433
void PlaylistSplitter::slotAddToPlaylist(const QString &file, Playlist *list, PlaylistItem *after)
434
{
435 436 437
    if(!after)
	after = static_cast<PlaylistItem *>(list->lastItem());

438
    KApplication::setOverrideCursor(Qt::waitCursor);
439
    PaintEater pe(list);
440
    addImpl(file, list, after);
441
    list->slotWeightDirty();
442
    list->emitCountChanged();
443
    KApplication::restoreOverrideCursor();
Nadeem Hasan's avatar
Nadeem Hasan committed
444

445 446 447 448
    if(m_editor)
	m_editor->slotUpdateCollection();
}

449 450 451 452 453
void PlaylistSplitter::slotDeletePlaylist()
{
    if(m_playingItem && m_playingItem->listView() == visiblePlaylist())
	m_playingItem = 0;

454
    m_playlistBox->deleteItems();
455 456
}

457
void PlaylistSplitter::slotAddToPlaylist(const QStringList &files, Playlist *list, PlaylistItem *after)
458
{
459 460 461
    if(!after)
	after = static_cast<PlaylistItem *>(list->lastItem());

462
    KApplication::setOverrideCursor(Qt::waitCursor);
463

464 465
    PaintEater pe(list);

466
    for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it)
467 468
        after = addImpl(*it, list, after);

469
    list->slotWeightDirty();
470
    list->emitCountChanged();
471

472 473 474 475 476 477
    KApplication::restoreOverrideCursor();

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

478 479 480 481 482 483
void PlaylistSplitter::slotSetSearchVisible(bool visible)
{
    m_searchWidget->setShown(visible);
    redisplaySearch();
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497
void PlaylistSplitter::slotSetHistoryVisible(bool visible)
{
    if(visible && !m_history) {
	m_history = new HistoryPlaylist(m_playlistStack);
	setupPlaylist(m_history, false, "history", true);
	return;
    }

    if(!visible && m_history) {
	m_playlistBox->deleteItem(m_history);
	m_history = 0;
    }
}

498 499
void PlaylistSplitter::slotAdvancedSearch()
{
500
    AdvancedSearchDialog *d =
501 502
	new AdvancedSearchDialog(uniquePlaylistName(i18n("Search Playlist")),
				 PlaylistSearch(), this);
503
    AdvancedSearchDialog::Result r = d->exec();
504
    delete d;
505 506

    if(r.result == AdvancedSearchDialog::Accepted) {
507
	SearchPlaylist *p = new SearchPlaylist(m_playlistStack, r.search, r.playlistName);
508 509
	setupPlaylist(p, true, "find");
    }
510
}
511

512
void PlaylistSplitter::slotGuessTagInfo(TagGuesser::Type type)
513
{
514
    visiblePlaylist()->slotGuessTagInfo(type);
515 516 517 518
    if(m_editor)
        m_editor->slotRefresh();
}

519 520 521 522 523 524 525
void PlaylistSplitter::slotRenameFile()
{
    visiblePlaylist()->slotRenameFile();
    if(m_editor)
        m_editor->slotRefresh();
}

526 527 528 529 530 531
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlaylistSplitter::setupLayout()
{
532
    setOpaqueResize(false);
533

534
    m_playlistBox = new PlaylistBox(this, "playlistBox");
535

536 537 538
    connect(m_playlistBox, SIGNAL(signalCreateSearchList(const PlaylistSearch &, const QString &, const QString &)),
            this, SLOT(slotCreateSearchList(const PlaylistSearch &, const QString &, const QString &)));

539 540 541
    connect(m_playlistBox, SIGNAL(signalCreatePlaylist(const QStringList &)),
	    this, SLOT(slotCreatePlaylist(const QStringList &)));

542 543 544 545 546
    // 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.
547

548 549 550 551
    QWidget *top = new QWidget(editorSplitter);
    QVBoxLayout *topLayout = new QVBoxLayout(top);

    m_playlistStack = new QWidgetStack(top, "playlistStack");
552
    m_editor = new TagEditor(editorSplitter, "tagEditor");
553 554 555

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

556
    editorSplitter->setResizeMode(m_editor, QSplitter::FollowSizeHint);
557

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

561
    connect(m_playlistBox, SIGNAL(signalCurrentChanged(const PlaylistList &)),
562
	    this, SLOT(slotChangePlaylist(const PlaylistList &)));
563

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

569
    CollectionList::initialize(m_playlistStack, m_restore);
570
    m_collection = CollectionList::instance();
571
    setupPlaylist(m_collection, true, "folder_sound", true);
572
    connect(m_collection, SIGNAL(signalCollectionChanged()), m_editor, SLOT(slotUpdateCollection()));
573 574

    // Create the search widget -- this must be done after the CollectionList is created.
575 576

    m_searchWidget = new SearchWidget(top, "searchWidget");
577
    connect(m_searchWidget, SIGNAL(signalQueryChanged()), this, SLOT(slotShowSearchResults()));
578 579
    connect(m_searchWidget, SIGNAL(signalAdvancedSearchClicked()), this, SLOT(slotAdvancedSearch()));

580 581 582
    connect(CollectionList::instance(), SIGNAL(signalVisibleColumnsChanged()),
	    this, SLOT(slotVisibleColumnsChanged()));

583 584 585
    topLayout->addWidget(m_searchWidget);
    topLayout->addWidget(m_playlistStack);

586
    // Show the collection on startup.
587
    m_playlistBox->setSelected(0, true);
588 589 590 591
}

void PlaylistSplitter::readConfig()
{
592 593 594 595
    KConfig *config = KGlobal::config();
    { // block for Playlists group
	KConfigGroupSaver saver(config, "Playlists");

596 597 598 599 600 601
	QValueList<int> splitterSizes = config->readIntListEntry("PlaylistSplitterSizes");
	if(splitterSizes.isEmpty()) {
	    splitterSizes.append(100);
	    splitterSizes.append(640);
	}
	setSizes(splitterSizes);
602

603
	if(m_restore) {
604

605
	    readPlaylists();
606

607 608
	    m_importPlaylists = config->readBoolEntry("ImportPlaylists", true);

Helge Deller's avatar
Helge Deller committed
609
	    m_directoryList = config->readPathListEntry("DirectoryList");
610
	    QTimer::singleShot(0, this, SLOT(slotScanDirectories()));
611

Scott Wheeler's avatar
Scott Wheeler committed
612
	    connect(&m_dirWatch, SIGNAL(dirty(const QString &)),
613
		    this, SLOT(slotDirChanged(const QString &)));
614

615 616
	    QStringList::Iterator it = m_directoryList.begin();
            for(; it != m_directoryList.end(); ++it)
Scott Wheeler's avatar
Scott Wheeler committed
617
		m_dirWatch.addDir(*it, false, true);
618

Scott Wheeler's avatar
Scott Wheeler committed
619
	    m_dirWatch.startScan();
620
	}
621

622
	if(m_collection) {
623 624 625

	    // Restore the list of hidden and shown columns

626
	    for(int i = 0; i < m_collection->columns(); i++)
627
		m_columnNames.append(m_collection->columnText(i));
628 629 630 631 632

	    // Restore the collection list's sort column -- the other playlists
	    // are hanlded in the serialization code.

	    m_collection->setSortColumn(config->readNumEntry("CollectionListSortColumn", 1));
633
	}
634

635
    }
636
}
637 638 639 640

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

642
    // Save the list of open playlists.
Nadeem Hasan's avatar
Nadeem Hasan committed
643

644
    if(m_restore && m_playlistBox) {
645

646
	savePlaylists();
647

648 649
	{ // block for Playlists group
	    KConfigGroupSaver saver(config, "Playlists");
650
	    config->writeEntry("ImportPlaylists", m_importPlaylists);
Helge Deller's avatar
Helge Deller committed
651
	    config->writePathEntry("DirectoryList", m_directoryList);
652
	    config->writeEntry("SortColumn", m_collection->sortColumn());
653
	    config->writeEntry("PlaylistSplitterSizes", sizes());
654
	    config->writeEntry("CollectionListSortColumn", m_collection->sortColumn());
655 656
	}
    }
657 658
}

659
PlaylistItem *PlaylistSplitter::addImpl(const QString &file, Playlist *list, PlaylistItem *after)
660 661
{
    processEvents();
662 663 664 665 666 667

    const QFileInfo fileInfo = QDir::cleanDirPath(file);

    if(!fileInfo.exists())
	return after;

668 669
    if(fileInfo.isFile() && fileInfo.isReadable()) {
	if(MediaFiles::isMediaFile(file))
670
	    return list->createItem(FileHandle(fileInfo, fileInfo.absFilePath()), after, false);
671

672 673 674 675
	if(m_importPlaylists && MediaFiles::isPlaylistFile(file)) {
	    openPlaylist(fileInfo.absFilePath());
	    return after;
	}
676 677 678 679 680 681 682 683 684 685 686
    }

    if(fileInfo.isDir()) {

	QDir dir = fileInfo.filePath();
	QStringList dirContents = dir.entryList();

	for(QStringList::Iterator it = dirContents.begin(); it != dirContents.end(); ++it) {
	    if(*it != "." && *it != "..")
		after = addImpl(fileInfo.filePath() + QDir::separator() + *it, list, after);
	}
Nadeem Hasan's avatar
Nadeem Hasan committed
687
    }
688

689
    return after;
690 691
}

692
void PlaylistSplitter::setupPlaylist(Playlist *p, bool raise, const char *icon, bool sortedFirst)
693
{
694 695
    connect(p, SIGNAL(signalSelectionChanged(const PlaylistItemList &)),
	    m_editor, SLOT(slotSetItems(const PlaylistItemList &)));
696 697

    connect(p, SIGNAL(doubleClicked(QListViewItem *)),
698
	    this, SLOT(slotPlayCurrent()));
699 700

    connect(p, SIGNAL(returnPressed(QListViewItem *)), 
701
	    this, SLOT(slotPlayCurrent()));
702

703
    connect(p, SIGNAL(signalCountChanged(Playlist *)),
704
	    this, SLOT(slotPlaylistCountChanged(Playlist *)));
705

706
    connect(p, SIGNAL(signalAboutToRemove(PlaylistItem *)),
707
	    this, SLOT(slotPlaylistItemRemoved(PlaylistItem *)));
708

709 710
    connect(p, SIGNAL(signalFilesDropped(const QStringList &, Playlist *, PlaylistItem *)),
	    this, SLOT(slotAddToPlaylist(const QStringList &, Playlist *, PlaylistItem *)));
711

712 713
    connect(p, SIGNAL(signalSetNext(PlaylistItem *)),
	    this, SLOT(slotSetNextItem(PlaylistItem *)));
714

715 716 717
    connect(p, SIGNAL(signalCreatePlaylist(const PlaylistItemList &)),
	    this, SLOT(slotCreatePlaylist(const PlaylistItemList &)));

718
    if(icon)
719
	m_playlistBox->createItem(p, icon, raise, sortedFirst);
720

721 722 723 724 725
    if(raise) {
	PlaylistList l;
	l.append(p);
	slotChangePlaylist(l);
    }
726 727
}

728 729 730
Playlist *PlaylistSplitter::openPlaylist(const QString &file)
{
    QFileInfo fileInfo(file);
731 732 733
    if(!fileInfo.exists() ||
       !fileInfo.isFile() ||
       !fileInfo.isReadable() ||
734 735
       m_playlistFiles.insert(fileInfo.absFilePath()))
    {
736
	return 0;
737
    }
738

739
    Playlist *p = new Playlist(file, m_playlistStack, fileInfo.baseName(true));
740
    setupPlaylist(p);
741
    return p;
742 743
}

744
FileHandle PlaylistSplitter::play(PlaylistItem *item)
745 746 747
{
    stop();

748 749
    if(!item) {
	kdDebug(65432) << k_funcinfo << "The current item is null." << endl;
750
	return FileHandle::null();
751
    }
752

753
    Playlist *p = item->playlist();
754

755
    if(!p)
756
	return FileHandle::null();
757

758 759
    p->setPlaying(item, true);

760 761
    m_playingItem = item;

762 763
    if(m_history && p != m_history)
	m_history->createItem(item->file());
764

765
    return item->file();
766 767
}

768 769
void PlaylistSplitter::redisplaySearch()
{
770 771
    // kdDebug(65432) << k_funcinfo << endl;

772 773 774 775 776 777 778 779
    if(!m_searchWidget->isVisible() || visiblePlaylist()->search().isEmpty())
	visiblePlaylist()->setItemsVisible(visiblePlaylist()->items(), true);
    else {
	Playlist::setItemsVisible(visiblePlaylist()->search().matchedItems(), true);
	Playlist::setItemsVisible(visiblePlaylist()->search().unmatchedItems(), false);
    }
}

780 781 782 783 784 785
void PlaylistSplitter::readPlaylists()
{
    QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";

    QFile f(playlistsFile);

786 787 788
    if(!f.open(IO_ReadOnly))
	return;

789 790 791 792 793 794 795
    QDataStream fs(&f);

    Q_INT32 version;
    fs >> version;

    switch(version) {
    case 1:
796
    case 2:
797 798 799 800 801 802 803 804 805 806
    {
	// Our checksum is only for the values after the version and checksum so
	// we want to get a byte array with just the checksummed data.

	QByteArray data;
	Q_UINT16 checksum;
	fs >> checksum >> data;

	if(checksum != qChecksum(data.data(), data.size()))
	    return;
807

808
	// Create a new stream just based on the data.
809

810
	QDataStream s(data, IO_ReadOnly);
811

812
	while(!s.atEnd()) {
813

814 815 816
	    Q_INT32 playlistType;
	    s >> playlistType;

817 818
	    Playlist *playlist;

819 820
	    switch(playlistType) {
	    case Search:
821 822 823 824 825
	    {
		SearchPlaylist *p = new SearchPlaylist(m_playlistStack);
		s >> *p;
		setupPlaylist(p, false, "find");

826 827
		playlist = p;

828
		break;
829
	    }
830
	    case History:
831 832 833
	    {
		slotSetHistoryVisible(true);
		s >> *m_history;
834 835

		playlist = m_history;
836
		break;
837
	    }
838
	    default:
839
		Playlist *p = new Playlist(m_playlistStack);
840 841 842 843 844 845
		s >> *p;

		if(!p->fileName().isEmpty() && m_playlistFiles.insert(p->fileName())) {
		    delete p;
		    p = 0;
		}
846 847
		else
		    setupPlaylist(p);
848 849 850

		playlist = p;

851 852
		break;
	    }
853 854 855
	    if(version == 2) {
		Q_INT32 sortColumn;
		s >> sortColumn;
856 857
		if(playlist)
		    playlist->setSorting(sortColumn);
858
	    }
859 860
	}
	break;
861
    }
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
    default:
    {
	// Because the original version of the playlist cache did not contain a
	// version number, we want to revert to the beginning of the file before
	// reading the data.

	f.reset();

	while(!fs.atEnd()) {
	    Playlist *p = new Playlist(m_playlistStack);
	    fs >> *p;

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

	    if(p->fileName().isEmpty() || !m_playlistFiles.insert(p->fileName()))
		setupPlaylist(p);
	    else
		delete p;
	}
	break;
    }
    }

    f.close();
886 887 888 889
}

void PlaylistSplitter::savePlaylists()
{
890 891
    QString dirName = KGlobal::dirs()->saveLocation("appdata");
    QString playlistsFile = dirName + "playlists.new";
892 893
    QFile f(playlistsFile);

894 895
    if(!f.open(IO_WriteOnly))
	return;
896

897 898
    QByteArray data;
    QDataStream s(data, IO_WriteOnly);
899

900
    PlaylistList l = m_playlistBox->playlists();
901

902
    for(PlaylistList::Iterator it = l.begin(); it != l.end(); it++) {
903 904
	if(*it) {
	    if(*it == m_history) {
905 906
		s << Q_INT32(History)
		  << *m_history;
907 908
	    }
	    else if(dynamic_cast<SearchPlaylist *>(*it)) {
909 910
		s << Q_INT32(Search)
		  << *static_cast<SearchPlaylist *>(*it);
911 912
	    }
	    else {
913 914
		s << Q_INT32(Normal)
		  << *(*it);
915
	    }
916
	    s << Q_INT32((*it)->sortColumn());
917
	}
918
    }
919

920 921 922 923 924
    QDataStream fs(&f);
    fs << Q_INT32(playlistCacheVersion);
    fs << qChecksum(data.data(), data.size());

    fs << data;
925
    f.close();
926 927

    QDir(dirName).rename("playlists.new", "playlists");
928 929
}

930 931 932 933
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

934
void PlaylistSplitter::slotChangePlaylist(const PlaylistList &l)
935
{
936 937
    if(l.isEmpty()) {
	emit signalPlaylistChanged();
938
	return;
939
    }
940 941 942
    if(l.count() == 1 && l.first() == visiblePlaylist())
	return;
    
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
    // Save the current dynamic list so that we can delete it when we're done
    // showing the next list.  The two situations are that we're either showing
    // an existing, non-dynamic list or that we're creating a dynamic list; in 
    // both cases we want to get rid of the current one.
    //
    // If in fact the currently visible list *is not* a dynamic list, then
    // m_dyanmicList will simply be zero, making deleting it at the end of this
    // method just a no-op.
    //
    // And finally, because we will end up doing a recursive call to this method
    // to show the dynamic playlist (second case calls the first case), we want
    // to make sure that in that case we don't delete the very thing we're
    // being asked to show.  (Hence the conditional assignment.)

    Playlist *current = l.first() != m_dynamicList ? m_dynamicList : 0;
958

959
    m_nextPlaylistItem = 0;
960 961 962

    // First case:  We're just showing one, currently existing list.

963
    if(l.count() == 1) {
964 965

	l.first()->applySharedSettings();
966 967
	m_playlistStack->raiseWidget(l.first());
	m_editor->slotSetItems(playlistSelection());
968 969 970

	if(m_dynamicList != l.first())
	   m_dynamicList = 0;
971

972
	if(m_searchWidget)
973
	    m_searchWidget->setSearch(l.first()->search());
974
    }
975 976 977 978

    // Second case: There are multiple playlists in our list, so we need to create
    // a new "dynamic list" that is the union of these playlists.

979
    else {
980
	m_dynamicList = new DynamicPlaylist(l, m_playlistStack, i18n("Dynamic List"));
981