playlistcollection.cpp 28.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2009 Michael Pyne <mpyne@kde.org>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
17

18 19
#include "playlistcollection.h"

20
#include <kurl.h>
21 22 23 24 25 26 27 28 29 30 31
#include <kicon.h>
#include <kiconloader.h>
#include <kapplication.h>
#include <kinputdialog.h>
#include <kmessagebox.h>
#include <kfiledialog.h>
#include <kaction.h>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <kconfiggroup.h>
32
#include <kfileitem.h>
33
#include <kglobal.h>
34

35
#include <config-juk.h>
36

37
#include <QObject>
Laurent Montel's avatar
Laurent Montel committed
38
#include <QPixmap>
39
#include <QStackedWidget>
40
#include <QMutableListIterator>
41

42 43 44
#include <sys/types.h>
#include <dirent.h>

45 46 47
#include "collectionlist.h"
#include "actioncollection.h"
#include "advancedsearchdialog.h"
Michael Pyne's avatar
Michael Pyne committed
48
#include "coverinfo.h"
49 50
#include "searchplaylist.h"
#include "folderplaylist.h"
51
#include "historyplaylist.h"
52
#include "upcomingplaylist.h"
53 54 55
#include "directorylist.h"
#include "mediafiles.h"
#include "playermanager.h"
56
#include "tracksequencemanager.h"
57
#include "juk.h"
58

Laurent Montel's avatar
Laurent Montel committed
59 60
//Laurent: readd it
//#include "collectionadaptor.h"
61

62
using namespace ActionCollection;
63

64 65 66 67 68 69
////////////////////////////////////////////////////////////////////////////////
// static methods
////////////////////////////////////////////////////////////////////////////////

PlaylistCollection *PlaylistCollection::m_instance = 0;

70 71 72 73 74 75 76 77 78 79 80 81 82 83
// Returns all folders in input list with their canonical path, if available, or
// unchanged if not.
static QStringList canonicalizeFolderPaths(const QStringList &folders)
{
    QStringList result;

    foreach(const QString &folder, folders) {
        QString canonicalFolder = QDir(folder).canonicalPath();
        result << (!canonicalFolder.isEmpty() ? canonicalFolder : folder);
    }

    return result;
}

84 85 86 87
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////

88
PlaylistCollection::PlaylistCollection(PlayerManager *player, QStackedWidget *playlistStack) :
89
    m_playlistStack(playlistStack),
90
    m_historyPlaylist(0),
91
    m_upcomingPlaylist(0),
92
    m_playerManager(player),
93
    m_importPlaylists(true),
94
    m_searchEnabled(true),
95 96
    m_playing(false),
    m_showMorePlaylist(0),
97
    m_belowShowMorePlaylist(0),
98 99 100
    m_dynamicPlaylist(0),
    m_belowDistraction(0),
    m_distraction(0)
101
{
Laurent Montel's avatar
Laurent Montel committed
102 103
    //new CollectionAdaptor( this );
    //QDBus::sessionBus().registerObject("/Collection",this );
104 105
    m_instance = this;

106
    m_actionHandler = new ActionHandler(this);
107 108 109 110 111

    // KDirLister's auto error handling seems to crash JuK during startup in
    // readConfig().

    m_dirLister.setAutoErrorHandlingEnabled(false, playlistStack);
112
    readConfig();
113 114 115 116
}

PlaylistCollection::~PlaylistCollection()
{
117
    saveConfig();
118
    CollectionList::instance()->saveItemsToCache();
119
    delete m_actionHandler;
120
    Playlist::setShuttingDown();
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
}

QString PlaylistCollection::name() const
{
    return currentPlaylist()->name();
}

FileHandle PlaylistCollection::currentFile() const
{
    return currentPlaylist()->currentFile();
}

int PlaylistCollection::count() const
{
    return currentPlaylist()->count();
}

int PlaylistCollection::time() const
{
    return currentPlaylist()->time();
}

143 144
void PlaylistCollection::playFirst()
{
145
    m_playing = true;
146
    currentPlaylist()->playFirst();
147
    currentChanged();
148 149
}

150 151 152 153 154 155 156
void PlaylistCollection::playNextAlbum()
{
    m_playing = true;
    currentPlaylist()->playNextAlbum();
    currentChanged();
}

157 158
void PlaylistCollection::playPrevious()
{
159
    m_playing = true;
160
    currentPlaylist()->playPrevious();
161
    currentChanged();
162 163 164 165
}

void PlaylistCollection::playNext()
{
166
    m_playing = true;
167
    currentPlaylist()->playNext();
168
    currentChanged();
169 170 171 172
}

void PlaylistCollection::stop()
{
173
    m_playing = false;
174
    currentPlaylist()->stop();
175
    dataChanged();
176 177 178 179 180
}

bool PlaylistCollection::playing() const
{
    return m_playing;
181 182
}

183 184 185 186
QStringList PlaylistCollection::playlists() const
{
    QStringList l;

187
    //(or qFindChildren() if you need MSVC 6 compatibility)
188
    const QList<Playlist *> childList = m_playlistStack->findChildren<Playlist *>("Playlist");
189
    foreach(Playlist *p, childList) {
190
        l.append(p->name());
191 192 193 194 195 196 197 198 199 200
    }

    return l;
}

void PlaylistCollection::createPlaylist(const QString &name)
{
    raise(new Playlist(this, name));
}

201 202 203 204
void PlaylistCollection::createDynamicPlaylist(const PlaylistList &playlists)
{
    if(m_dynamicPlaylist)
        m_dynamicPlaylist->setPlaylists(playlists);
205
    else {
206
        m_dynamicPlaylist =
Pino Toscano's avatar
Pino Toscano committed
207
            new DynamicPlaylist(playlists, this, i18n("Dynamic List"), "audio-midi", false, true);
208 209
        PlaylistCollection::setupPlaylist(m_dynamicPlaylist, QString());
    }
210

211
    PlaylistCollection::raise(m_dynamicPlaylist);
212 213
}

214 215 216 217 218 219 220
void PlaylistCollection::showMore(const QString &artist, const QString &album)
{

    PlaylistList playlists;
    PlaylistSearch::ComponentList components;

    if(currentPlaylist() != CollectionList::instance() &&
221 222
       currentPlaylist() != m_showMorePlaylist)
    {
223
        playlists.append(currentPlaylist());
224 225 226
    }

    playlists.append(CollectionList::instance());
227

228
    if(!artist.isNull())
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    { // Just setting off the artist stuff in its own block.
        ColumnList columns;
        columns.append(PlaylistItem::ArtistColumn);
        PlaylistSearch::Component c(artist, false, columns,
                                    PlaylistSearch::Component::Exact);
        components.append(c);
    }

    if(!album.isNull()) {
        ColumnList columns;
        columns.append(PlaylistItem::AlbumColumn);
        PlaylistSearch::Component c(album, false, columns,
                                    PlaylistSearch::Component::Exact);
        components.append(c);
    }

    PlaylistSearch search(playlists, components, PlaylistSearch::MatchAll);

247 248 249 250
    if(m_showMorePlaylist)
        m_showMorePlaylist->setPlaylistSearch(search);
    else
        m_showMorePlaylist = new SearchPlaylist(this, search, i18n("Now Playing"), false, true);
251

252 253 254
    // The call to raise() below will end up clearing m_belowShowMorePlaylist,
    // so cache the value we want it to have now.
    Playlist *belowShowMore = visiblePlaylist();
255

256
    PlaylistCollection::setupPlaylist(m_showMorePlaylist, QString());
257
    PlaylistCollection::raise(m_showMorePlaylist);
258 259

    m_belowShowMorePlaylist = belowShowMore;
260 261
}

262 263 264 265 266
void PlaylistCollection::removeTrack(const QString &playlist, const QStringList &files)
{
    Playlist *p = playlistByName(playlist);
    PlaylistItemList itemList;
    if(!p)
267
        return;
268

269 270
    QStringList::ConstIterator it;
    for(it = files.begin(); it != files.end(); ++it) {
271
        CollectionListItem *item = CollectionList::instance()->lookup(*it);
272

273 274 275 276 277
        if(item) {
            PlaylistItem *playlistItem = item->itemForPlaylist(p);
            if(playlistItem)
                itemList.append(playlistItem);
        }
278 279 280 281 282 283 284
    }

    p->clearItems(itemList);
}

QString PlaylistCollection::playlist() const
{
285
    return visiblePlaylist() ? visiblePlaylist()->name() : QString();
286 287 288 289
}

QString PlaylistCollection::playingPlaylist() const
{
290
    return currentPlaylist() && m_playing ? currentPlaylist()->name() : QString();
291 292 293 294 295 296
}

void PlaylistCollection::setPlaylist(const QString &playlist)
{
    Playlist *p = playlistByName(playlist);
    if(p)
297
        raise(p);
298 299 300 301 302 303 304
}

QStringList PlaylistCollection::playlistTracks(const QString &playlist) const
{
    Playlist *p = playlistByName(playlist);

    if(p)
305
        return p->files();
306 307 308 309 310 311 312 313
    return QStringList();
}

QString PlaylistCollection::trackProperty(const QString &file, const QString &property) const
{
    CollectionList *l = CollectionList::instance();
    CollectionListItem *item = l->lookup(file);

314
    return item ? item->file().property(property) : QString();
315 316
}

Michael Pyne's avatar
Michael Pyne committed
317 318
QPixmap PlaylistCollection::trackCover(const QString &file, const QString &size) const
{
319
    if(size.toLower() != "small" && size.toLower() != "large")
Michael Pyne's avatar
Michael Pyne committed
320 321 322 323 324 325 326 327
        return QPixmap();

    CollectionList *l = CollectionList::instance();
    CollectionListItem *item = l->lookup(file);

    if(!item)
        return QPixmap();

328
    if(size.toLower() == "small")
Michael Pyne's avatar
Michael Pyne committed
329 330 331 332 333
        return item->file().coverInfo()->pixmap(CoverInfo::Thumbnail);
    else
        return item->file().coverInfo()->pixmap(CoverInfo::FullSize);
}

334 335 336 337 338
void PlaylistCollection::open(const QStringList &l)
{
    QStringList files = l;

    if(files.isEmpty())
339
        files = MediaFiles::openDialog(JuK::JuKInstance());
340

341 342
    if(files.isEmpty())
        return;
343

344 345
    bool justPlaylists = true;

346
    for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd() && justPlaylists; ++it)
347 348 349
        justPlaylists = !MediaFiles::isPlaylistFile(*it);

    if(visiblePlaylist() == CollectionList::instance() || justPlaylists ||
350
       KMessageBox::questionYesNo(
351
           JuK::JuKInstance(),
352
           i18n("Do you want to add these items to the current list or to the collection list?"),
353
           QString(),
354
           KGuiItem(i18nc("current playlist", "Current")),
355 356
           KGuiItem(i18n("Collection"))) == KMessageBox::No)
    {
357
        CollectionList::instance()->addFiles(files);
358
    }
359
    else {
360
        visiblePlaylist()->addFiles(files);
361
    }
362 363

    dataChanged();
364 365
}

366 367 368 369 370
void PlaylistCollection::open(const QString &playlist, const QStringList &files)
{
    Playlist *p = playlistByName(playlist);

    if(p)
371
        p->addFiles(files);
372 373
}

374 375
void PlaylistCollection::addFolder()
{
376
    DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance());
377
    // FIXME signal result
378
    //DirectoryList::Result result = l.exec();
379

380
    /*if(result.status == QDialog::Accepted) {
381

382 383 384
        m_dirLister.blockSignals(true);

        const bool reload = m_importPlaylists != result.addPlaylists;
385

386
        m_importPlaylists = result.addPlaylists;
387
        m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs);
388

389 390 391
        foreach(const QString &dir, result.addedDirs) {
            m_dirLister.openUrl(KUrl::fromPath(dir), KDirLister::Keep);
            m_folderList.append(dir);
392 393
        }

394 395 396
        foreach(const QString &dir, result.removedDirs) {
            m_dirLister.stop(KUrl::fromPath(dir));
            m_folderList.removeAll(dir);
397 398
        }

399
        if(reload) {
400
            open(m_folderList);
401 402
        }
        else if(!result.addedDirs.isEmpty()) {
403
            open(result.addedDirs);
404
        }
405 406

        saveConfig();
407 408

        m_dirLister.blockSignals(false);
409
    }*/
410 411 412 413
}

void PlaylistCollection::rename()
{
414
    QString old = visiblePlaylist()->name();
415 416 417
    QString name = playlistNameDialog(i18n("Rename"), old, false);

    m_playlistNames.remove(old);
418

419
    if(name.isEmpty())
420 421
        return;

422
    visiblePlaylist()->setName(name);
423 424 425 426
}

void PlaylistCollection::duplicate()
{
427 428
    QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"),
                                      visiblePlaylist()->name());
429
    if(name.isEmpty())
Scott Wheeler's avatar
Scott Wheeler committed
430
        return;
431

432
    raise(new Playlist(this, visiblePlaylist()->items(), name));
433 434 435 436
}

void PlaylistCollection::save()
{
437
    visiblePlaylist()->save();
438 439 440 441
}

void PlaylistCollection::saveAs()
{
442
    visiblePlaylist()->saveAs();
443 444 445 446
}

void PlaylistCollection::reload()
{
447
    if(visiblePlaylist() == CollectionList::instance())
448
        CollectionList::instance()->addFiles(m_folderList);
449
    else
450
        visiblePlaylist()->slotReload();
451 452 453 454 455

}

void PlaylistCollection::editSearch()
{
456
    SearchPlaylist *p = dynamic_cast<SearchPlaylist *>(visiblePlaylist());
457 458

    if(!p)
459
        return;
460

461
    // FIXME signalResult
462
    /*AdvancedSearchDialog::Result r =
463
        AdvancedSearchDialog(p->name(), p->playlistSearch(), JuK::JuKInstance()).exec();
464 465 466 467

    if(r.result == AdvancedSearchDialog::Accepted) {
        p->setPlaylistSearch(r.search);
        p->setName(r.playlistName);
468
    }*/
469 470 471 472
}

void PlaylistCollection::removeItems()
{
473
    visiblePlaylist()->slotRemoveSelectedItems();
474 475 476 477
}

void PlaylistCollection::refreshItems()
{
478
    visiblePlaylist()->slotRefresh();
479 480 481 482
}

void PlaylistCollection::renameItems()
{
483
    visiblePlaylist()->slotRenameFile();
484 485
}

486 487
void PlaylistCollection::addCovers(bool fromFile)
{
488
    visiblePlaylist()->slotAddCover(fromFile);
489
    dataChanged();
490 491 492 493
}

void PlaylistCollection::removeCovers()
{
494
    visiblePlaylist()->slotRemoveCover();
495
    dataChanged();
496 497 498 499
}

void PlaylistCollection::viewCovers()
{
500
    visiblePlaylist()->slotViewCover();
501 502
}

503 504 505 506 507
void PlaylistCollection::showCoverManager()
{
    visiblePlaylist()->slotShowCoverManager();
}

508 509
PlaylistItemList PlaylistCollection::selectedItems()
{
510
    return visiblePlaylist()->selectedItems();
511 512 513 514
}

void PlaylistCollection::scanFolders()
{
515
    CollectionList::instance()->addFiles(m_folderList);
516 517 518

    if(CollectionList::instance()->count() == 0)
        addFolder();
519 520

    enableDirWatch(true);
521 522 523 524
}

void PlaylistCollection::createPlaylist()
{
525
    QString name = playlistNameDialog();
526
    if(!name.isEmpty())
527 528 529 530 531 532 533
        raise(new Playlist(this, name));
}

void PlaylistCollection::createSearchPlaylist()
{
    QString name = uniquePlaylistName(i18n("Search Playlist"));

534
    // FIXME signal result
535
    /*AdvancedSearchDialog::Result r =
536
        AdvancedSearchDialog(name, PlaylistSearch(), JuK::JuKInstance()).exec();
537 538

    if(r.result == AdvancedSearchDialog::Accepted)
539
        raise(new SearchPlaylist(this, r.search, r.playlistName));*/
540 541 542 543 544 545 546 547 548
}

void PlaylistCollection::createFolderPlaylist()
{
    QString folder = KFileDialog::getExistingDirectory();

    if(folder.isEmpty())
        return;

Stephan Kulow's avatar
Stephan Kulow committed
549
    QString name = uniquePlaylistName(folder.mid(folder.lastIndexOf('/') + 1));
550
    name = playlistNameDialog(i18n("Create Folder Playlist"), name);
551

552
    if(!name.isEmpty())
553 554 555 556 557
        raise(new FolderPlaylist(this, folder, name));
}

void PlaylistCollection::guessTagFromFile()
{
558
    visiblePlaylist()->slotGuessTagInfo(TagGuesser::FileName);
559 560 561 562
}

void PlaylistCollection::guessTagFromInternet()
{
563
    visiblePlaylist()->slotGuessTagInfo(TagGuesser::MusicBrainz);
564 565 566 567 568 569 570 571 572
}

void PlaylistCollection::setSearchEnabled(bool enable)
{
    if(enable == m_searchEnabled)
        return;

    m_searchEnabled = enable;

573
    visiblePlaylist()->setSearchEnabled(enable);
574 575
}

576 577 578 579 580 581 582 583 584 585 586 587 588 589
HistoryPlaylist *PlaylistCollection::historyPlaylist() const
{
    return m_historyPlaylist;
}

void PlaylistCollection::setHistoryPlaylistEnabled(bool enable)
{
    if((enable && m_historyPlaylist) || (!enable && !m_historyPlaylist))
        return;

    if(enable) {
        action<KToggleAction>("showHistory")->setChecked(true);
        m_historyPlaylist = new HistoryPlaylist(this);
        m_historyPlaylist->setName(i18n("History"));
Pino Toscano's avatar
Pino Toscano committed
590
        setupPlaylist(m_historyPlaylist, "view-history");
591 592 593

        QObject::connect(m_playerManager, SIGNAL(signalItemChanged(FileHandle)),
                historyPlaylist(), SLOT(appendProposedItem(FileHandle)));
594 595 596 597 598 599 600
    }
    else {
        delete m_historyPlaylist;
        m_historyPlaylist = 0;
    }
}

601 602 603 604 605 606 607
UpcomingPlaylist *PlaylistCollection::upcomingPlaylist() const
{
    return m_upcomingPlaylist;
}

void PlaylistCollection::setUpcomingPlaylistEnabled(bool enable)
{
608
    if((enable && m_upcomingPlaylist) || (!enable && !m_upcomingPlaylist))
609 610 611 612
        return;

    if(enable) {
        action<KToggleAction>("showUpcoming")->setChecked(true);
613 614
        if(!m_upcomingPlaylist)
            m_upcomingPlaylist = new UpcomingPlaylist(this);
615

Pino Toscano's avatar
Pino Toscano committed
616
        setupPlaylist(m_upcomingPlaylist, "go-jump-today");
617 618
    }
    else {
619
        action<KToggleAction>("showUpcoming")->setChecked(false);
620
        bool raiseCollection = visiblePlaylist() == m_upcomingPlaylist;
621

622 623 624
        if(raiseCollection) {
            raise(CollectionList::instance());
        }
625 626 627

        m_upcomingPlaylist->deleteLater();
        m_upcomingPlaylist = 0;
628 629 630
    }
}

631 632 633 634 635 636
QObject *PlaylistCollection::object() const
{
    return m_actionHandler;
}

Playlist *PlaylistCollection::currentPlaylist() const
637
{
638 639 640
    if(m_belowDistraction)
        return m_belowDistraction;

641
    if(m_upcomingPlaylist && m_upcomingPlaylist->active())
642 643
        return m_upcomingPlaylist;

644 645 646 647 648 649 650
    if(Playlist::playingItem())
        return Playlist::playingItem()->playlist();
    else
        return visiblePlaylist();
}

Playlist *PlaylistCollection::visiblePlaylist() const
651
{
652
    return qobject_cast<Playlist *>(m_playlistStack->currentWidget());
653 654
}

655 656
void PlaylistCollection::raise(Playlist *playlist)
{
657 658 659 660 661 662 663
    if(m_showMorePlaylist && currentPlaylist() == m_showMorePlaylist)
        m_showMorePlaylist->lower(playlist);
    if(m_dynamicPlaylist && currentPlaylist() == m_dynamicPlaylist)
        m_dynamicPlaylist->lower(playlist);

    TrackSequenceManager::instance()->setCurrentPlaylist(playlist);
    playlist->applySharedSettings();
664
    playlist->setSearchEnabled(m_searchEnabled);
665
    m_playlistStack->setCurrentWidget(playlist);
666
    clearShowMore(false);
667 668 669
    dataChanged();
}

670 671 672 673 674 675 676 677
void PlaylistCollection::raiseDistraction()
{
    if(m_belowDistraction)
        return;

    m_belowDistraction = currentPlaylist();

    if(!m_distraction) {
678
        m_distraction = new QWidget(m_playlistStack);
679 680 681
        m_playlistStack->addWidget(m_distraction);
    }

682
    m_playlistStack->setCurrentWidget(m_distraction);
683 684 685 686 687 688 689 690
}

void PlaylistCollection::lowerDistraction()
{
    if(!m_distraction)
        return;

    if(m_belowDistraction)
691
        m_playlistStack->setCurrentWidget(m_belowDistraction);
692 693 694 695

    m_belowDistraction = 0;
}

696 697 698 699
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////

700
QStackedWidget *PlaylistCollection::playlistStack() const
701 702 703 704 705 706
{
    return m_playlistStack;
}

void PlaylistCollection::setupPlaylist(Playlist *playlist, const QString &)
{
707
    if(!playlist->fileName().isEmpty())
708 709
        m_playlistFiles.insert(playlist->fileName());

710
    if(!playlist->name().isEmpty())
711 712
        m_playlistNames.insert(playlist->name());

713
    m_playlistStack->addWidget(playlist);
714
    QObject::connect(playlist, SIGNAL(itemSelectionChanged()),
715 716 717 718 719 720 721 722
                     object(), SIGNAL(signalSelectedItemsChanged()));
}

bool PlaylistCollection::importPlaylists() const
{
    return m_importPlaylists;
}

723 724 725 726 727
bool PlaylistCollection::containsPlaylistFile(const QString &file) const
{
    return m_playlistFiles.contains(file);
}

728 729
bool PlaylistCollection::showMoreActive() const
{
730
    return visiblePlaylist() == m_showMorePlaylist;
731 732
}

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
void PlaylistCollection::clearShowMore(bool raisePlaylist)
{
    if(!m_showMorePlaylist)
        return;

    if(raisePlaylist) {
        if(m_belowShowMorePlaylist)
            raise(m_belowShowMorePlaylist);
        else
            raise(CollectionList::instance());
    }

    m_belowShowMorePlaylist = 0;
}

748 749
void PlaylistCollection::enableDirWatch(bool enable)
{
750 751 752 753
    QObject *collection = CollectionList::instance();

    m_dirLister.disconnect(object());
    if(enable) {
Laurent Montel's avatar
Laurent Montel committed
754 755 756 757 758 759
        QObject::connect(&m_dirLister, SIGNAL(newItems(KFileItemList)),
                object(), SLOT(slotNewItems(KFileItemList)));
        QObject::connect(&m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)),
                collection, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
        QObject::connect(&m_dirLister, SIGNAL(deleteItem(KFileItem)),
                collection, SLOT(slotDeleteItem(KFileItem)));
760
    }
761 762
}

763
QString PlaylistCollection::playlistNameDialog(const QString &caption,
764 765
                                               const QString &suggest,
                                               bool forceUnique) const
766 767 768 769 770
{
    bool ok;

    QString name = KInputDialog::getText(
        caption,
771 772 773
        i18n("Please enter a name for this playlist:"),
        forceUnique ? uniquePlaylistName(suggest) : suggest,
        &ok);
774

775
    return ok ? uniquePlaylistName(name) : QString();
776 777 778 779
}


QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const
780
{
781 782 783
    if(suggest.isEmpty())
        return uniquePlaylistName();

784 785 786 787 788 789
    if(!m_playlistNames.contains(suggest))
        return suggest;

    QString base = suggest;
    base.remove(QRegExp("\\s\\([0-9]+\\)$"));

790
    int count = 1;
791
    QString s = QString("%1 (%2)").arg(base).arg(count);
792

793
    while(m_playlistNames.contains(s)) {
794
        count++;
795 796 797 798 799 800
        s = QString("%1 (%2)").arg(base).arg(count);
    }

    return s;
}

801
void PlaylistCollection::addNameToDict(const QString &name)
802 803 804
{
    m_playlistNames.insert(name);
}
805

806 807 808 809 810 811
void PlaylistCollection::addFileToDict(const QString &file)
{
    m_playlistFiles.insert(file);
}

void PlaylistCollection::removeNameFromDict(const QString &name)
812 813
{
    m_playlistNames.remove(name);
814 815
}

816 817 818 819 820
void PlaylistCollection::removeFileFromDict(const QString &file)
{
    m_playlistFiles.remove(file);
}

821 822
void PlaylistCollection::dirChanged(const QString &path)
{
823 824 825 826 827 828 829 830 831 832
    QString canonicalPath = QDir(path).canonicalPath();
    if(canonicalPath.isEmpty())
        return;

    foreach(const QString &excludedFolder, m_excludedFolderList) {
        if(canonicalPath.startsWith(excludedFolder))
            return;
    }

    CollectionList::instance()->addFiles(QStringList(canonicalPath));
833 834
}

835 836
Playlist *PlaylistCollection::playlistByName(const QString &name) const
{
837 838 839 840
    for(int i = 0; i < m_playlistStack->count(); ++i) {
        Playlist *p = qobject_cast<Playlist *>(m_playlistStack->widget(i));
        if(p && p->name() == name)
            return p;
841 842
    }

843
    return 0;
844 845
}

846 847
void PlaylistCollection::newItems(const KFileItemList &list) const
{
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
    // Make fast-path for the normal case
    if(m_excludedFolderList.isEmpty()) {
        CollectionList::instance()->slotNewItems(list);
        return;
    }

    // Slow case: Directories to exclude from consideration

    KFileItemList filteredList(list);

    foreach(const QString &excludedFolder, m_excludedFolderList) {
        QMutableListIterator<KFileItem> filteredListIterator(filteredList);

        while(filteredListIterator.hasNext()) {
            const KFileItem fileItem = filteredListIterator.next();

            if(fileItem.url().path().startsWith(excludedFolder))
                filteredListIterator.remove();
        }
    }

    CollectionList::instance()->slotNewItems(filteredList);
870
}
871

872 873 874 875 876 877 878 879
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////

void PlaylistCollection::readConfig()
{
    KConfigGroup config(KGlobal::config(), "Playlists");

880 881 882 883
    m_importPlaylists    = config.readEntry("ImportPlaylists", true);
    m_folderList         = config.readEntry("DirectoryList", QStringList());
    m_excludedFolderList = canonicalizeFolderPaths(
            config.readEntry("ExcludeDirectoryList", QStringList()));
884

885
    foreach(const QString &folder, m_folderList) {
886
        m_dirLister.openUrl(folder, KDirLister::Keep);
887
    }
888 889 890 891 892 893
}

void PlaylistCollection::saveConfig()
{
    KConfigGroup config(KGlobal::config(), "Playlists");
    config.writeEntry("ImportPlaylists", m_importPlaylists);
894
    config.writeEntry("showUpcoming", action("showUpcoming")->isChecked());
895
    config.writePathEntry("DirectoryList", m_folderList);
896
    config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList);
897 898

    config.sync();
899 900 901 902 903 904 905
}

////////////////////////////////////////////////////////////////////////////////
// ActionHanlder implementation
////////////////////////////////////////////////////////////////////////////////

PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) :
906
    QObject(0),
907 908
    m_collection(collection)
{
Laurent Montel's avatar
Laurent Montel committed
909
    setObjectName( QLatin1String("ActionHandler" ));
910

911 912 913 914
    KActionMenu *menu;

    // "New" menu

915
    menu = new KActionMenu(KIcon("document-new"), i18nc("new playlist", "&New"), actions());
916
    actions()->addAction("file_new", menu);
917

Stephan Kulow's avatar
Stephan Kulow committed
918
    menu->addAction(createAction(i18n("&Empty Playlist..."), SLOT(slotCreatePlaylist()),
919
                              "newPlaylist", "window-new", KShortcut(Qt::CTRL + Qt::Key_N)));
Stephan Kulow's avatar
Stephan Kulow committed
920
    menu->addAction(createAction(i18n("&Search Playlist..."), SLOT(slotCreateSearchPlaylist()),
921
                              "newSearchPlaylist", "edit-find", KShortcut(Qt::CTRL + Qt::Key_F)));
Stephan Kulow's avatar
Stephan Kulow committed
922
    menu->addAction(createAction(i18n("Playlist From &Folder..."), SLOT(slotCreateFolderPlaylist()),
923
                              "newDirectoryPlaylist", "document-open", KShortcut(Qt::CTRL + Qt::Key_D)));
924 925 926

    // Guess tag info menu

927
#if HAVE_TUNEPIMP
928 929 930
    menu = new KActionMenu(i18n("&Guess Tag Information"), actions());
    actions()->addAction("guessTag", menu);

931
    /* menu->setIcon(SmallIcon("wizard")); */
932

Stephan Kulow's avatar
Stephan Kulow committed
933
    menu->addAction(createAction(i18n("From &File Name"), SLOT(slotGuessTagFromFile()),
Pino Toscano's avatar
Pino Toscano committed
934
                              "guessTagFile", "document-import", KShortcut(Qt::CTRL + Qt::Key_G)));
Stephan Kulow's avatar
Stephan Kulow committed
935
    menu->addAction(createAction(i18n("From &Internet"), SLOT(slotGuessTagFromInternet()),
936
                              "guessTagInternet", "network-server", KShortcut(Qt::CTRL + Qt::Key_I)));
937
#else
938
    createAction(i18n("Guess Tag Information From &File Name"), SLOT(slotGuessTagFromFile()),
Michael Pyne's avatar
Michael Pyne committed
939
                 "guessTag", "document-import", KShortcut(Qt::CTRL + Qt::Key_F));
940 941 942
#endif


943
    createAction(i18n("Play First Track"),SLOT(slotPlayFirst()),     "playFirst");
944
    createAction(i18n("Play Next Album"), SLOT(slotPlayNextAlbum()), "forwardAlbum", "go-down-search");
945

946 947 948 949
    KStandardAction::open(this, SLOT(slotOpen()), actions());
    KStandardAction::save(this, SLOT(slotSave()), actions());
    KStandardAction::saveAs(this, SLOT(slotSaveAs()), actions());

950
    createAction(i18n("Manage &Folders..."),  SLOT(slotManageFolders()),    "openDirectory", "folder-new");
Pino Toscano's avatar
Pino Toscano committed
951
    createAction(i18n("&Rename..."),      SLOT(slotRename()),       "renamePlaylist", "edit-rename");
952 953
    createAction(i18nc("verb, copy the playlist", "D&uplicate..."),
                 SLOT(slotDuplicate()),    "duplicatePlaylist", "edit-copy");
Pino Toscano's avatar
Pino Toscano committed
954
    createAction(i18n("R&emove"),         SLOT(slotRemove()),       "deleteItemPlaylist", "user-trash");
955
    createAction(i18n("Reload"),          SLOT(slotReload()),       "reloadPlaylist", "view-refresh");
Pino Toscano's avatar
Pino Toscano committed
956
    createAction(i18n("Edit Search..."),  SLOT(slotEditSearch()),   "editSearch");
957

958 959 960
    createAction(i18n("&Delete"),         SLOT(slotRemoveItems()),  "removeItem", "edit-delete");
    createAction(i18n("Refresh"),         SLOT(slotRefreshItems()), "refresh", "view-refresh");
    createAction(i18n("&Rename File"),    SLOT(slotRenameItems()),  "renameFile", "document-save-as", KShortcut(Qt::CTRL + Qt::Key_R));
961

962 963
    menu = new KActionMenu(i18n("Cover Manager"), actions());
    actions()->addAction("coverManager", menu);
964
    /* menu->setIcon(SmallIcon("image-x-generic")); */
Stephan Kulow's avatar
Stephan Kulow committed
965
    menu->addAction(createAction(i18n("&View Cover"),
966
        SLOT(slotViewCovers()), "viewCover", "document-preview"));
Stephan Kulow's avatar
Stephan Kulow committed
967
    menu->addAction(createAction(i18n("Get Cover From &File..."),
Pino Toscano's avatar
Pino Toscano committed
968
        SLOT(slotAddLocalCover()), "addCover", "document-import", KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_F)));
Stephan Kulow's avatar
Stephan Kulow committed
969
    menu->addAction(createAction(i18n("Get Cover From &Internet..."),
970
        SLOT(slotAddInternetCover()), "webImageCover", "network-server", KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_G)));
Stephan Kulow's avatar
Stephan Kulow committed
971
    menu->addAction(createAction(i18n("&Delete Cover"),
972
        SLOT(slotRemoveCovers()), "removeCover", "edit-delete"));
Stephan Kulow's avatar
Stephan Kulow committed
973
    menu->addAction(createAction(i18n("Show Cover &Manager"),
974
        SLOT(slotShowCoverManager()), "showCoverManager"));
975

976
    KToggleAction *upcomingAction =
Pino Toscano's avatar
Pino Toscano committed
977
        new KToggleAction(KIcon("go-jump-today"), i18n("Show &Play Queue"), actions());
978
    actions()->addAction("showUpcoming", upcomingAction);
979

980
    connect(upcomingAction, SIGNAL(triggered(bool)),
981
            this, SLOT(slotSetUpcomingPlaylistEnabled(bool)));
982 983 984 985 986 987 988 989
}

KAction *PlaylistCollection::ActionHandler::createAction(const QString &text,
                                                         const char *slot,
                                                         const char *name,
                                                         const QString &icon,
                                                         const KShortcut &shortcut)
{
Stephan Kulow's avatar
Stephan Kulow committed
990
    KAction *action;
991
    if(icon.isNull())
992
        action = new KAction(text, actions());
993
    else
994 995
        action = new KAction(KIcon(icon), text, actions());
    actions()->addAction(name, action);
Stephan Kulow's avatar
Stephan Kulow committed
996 997 998
    connect( action, SIGNAL(triggered(bool)), slot);
    action->setShortcut(shortcut);
    return action;
999 1000
}

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