playlistcollection.cpp 29.2 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 20 21 22 23 24 25
#include "playlistcollection.h"

#include <kiconloader.h>
#include <kmessagebox.h>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <kconfiggroup.h>
Michael Pyne's avatar
Michael Pyne committed
26
#include <KSharedConfig>
27
#include <kfileitem.h>
28

29
#include <config-juk.h>
30

Michael Pyne's avatar
Michael Pyne committed
31 32 33
#include <QAction>
#include <QIcon>
#include <QMutableListIterator>
34
#include <QObject>
Laurent Montel's avatar
Laurent Montel committed
35
#include <QPixmap>
Michael Pyne's avatar
Michael Pyne committed
36 37
#include <QDir>
#include <QFileDialog>
38
#include <QInputDialog>
39
#include <QStackedWidget>
40

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

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

58 59
//Laurent: readd it
//#include "collectionadaptor.h"
60

61
using namespace ActionCollection;
62

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

PlaylistCollection *PlaylistCollection::m_instance = 0;

69 70 71 72 73 74 75 76 77 78 79 80 81 82
// 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;
}

83 84 85 86
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////

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

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

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

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

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

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();
}

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

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

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

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

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

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

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

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

    return l;
}

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

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

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

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

    PlaylistList playlists;
    PlaylistSearch::ComponentList components;

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

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

227
    if(!artist.isNull())
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    { // 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);

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

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

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

    m_belowShowMorePlaylist = belowShowMore;
259 260
}

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

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

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

    p->clearItems(itemList);
}

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

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

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

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

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

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

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

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

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

    if(!item)
        return QPixmap();

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

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

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

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

343 344
    bool justPlaylists = true;

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

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

362
    playlistItemsChanged();
363 364
}

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

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

373 374
void PlaylistCollection::addFolder()
{
375
    DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance());
376

377
    if(l.exec() == QDialog::Accepted) {
378
        m_dirLister.blockSignals(true);
379
        DirectoryList::Result result = l.dialogResult();
380 381

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

383
        m_importPlaylists = result.addPlaylists;
384
        m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs);
385

386
        foreach(const QString &dir, result.addedDirs) {
387
            m_dirLister.openUrl(QUrl::fromLocalFile(dir), KDirLister::Keep);
388
            m_folderList.append(dir);
389 390
        }

391
        foreach(const QString &dir, result.removedDirs) {
392
            m_dirLister.stop(QUrl::fromLocalFile(dir));
393
            m_folderList.removeAll(dir);
394 395
        }

396
        if(reload) {
397
            open(m_folderList);
398 399
        }
        else if(!result.addedDirs.isEmpty()) {
400
            open(result.addedDirs);
401
        }
402 403

        saveConfig();
404 405

        m_dirLister.blockSignals(false);
406
    }
407 408 409 410
}

void PlaylistCollection::rename()
{
411
    QString old = visiblePlaylist()->name();
412 413 414
    QString name = playlistNameDialog(i18n("Rename"), old, false);

    m_playlistNames.remove(old);
415

416
    if(name.isEmpty())
417 418
        return;

419
    visiblePlaylist()->setName(name);
420 421 422 423
}

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

429
    raise(new Playlist(this, visiblePlaylist()->items(), name));
430 431 432 433
}

void PlaylistCollection::save()
{
434
    visiblePlaylist()->save();
435 436 437 438
}

void PlaylistCollection::saveAs()
{
439
    visiblePlaylist()->saveAs();
440 441 442 443
}

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

}

void PlaylistCollection::editSearch()
{
453
    SearchPlaylist *p = dynamic_cast<SearchPlaylist *>(visiblePlaylist());
454
    if(!p)
455
        return;
456

457 458 459 460 461 462 463 464 465 466 467
    auto searchDialog = new AdvancedSearchDialog(
            p->name(), p->playlistSearch(), JuK::JuKInstance());
    QObject::connect(searchDialog, &QDialog::finished, [searchDialog, p](int result)
            {
                if (result) {
                    p->setPlaylistSearch(searchDialog->resultSearch());
                    p->setName(searchDialog->resultPlaylistName());
                }
                searchDialog->deleteLater();
            });
    searchDialog->exec();
468 469 470 471
}

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

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

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

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

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

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

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

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

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

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

    enableDirWatch(true);
520 521 522 523
}

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

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

533 534 535 536 537 538 539 540 541 542 543 544 545
    auto searchDialog = new AdvancedSearchDialog(
            name, PlaylistSearch(), JuK::JuKInstance());
    QObject::connect(searchDialog, &QDialog::finished, [searchDialog, this](int result)
            {
                if (result) {
                    raise(new SearchPlaylist(
                                this,
                                searchDialog->resultSearch(),
                                searchDialog->resultPlaylistName()));
                }
                searchDialog->deleteLater();
            });
    searchDialog->exec();
546 547 548 549
}

void PlaylistCollection::createFolderPlaylist()
{
Michael Pyne's avatar
Michael Pyne committed
550
    QString folder = QFileDialog::getExistingDirectory();
551 552 553 554

    if(folder.isEmpty())
        return;

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

558
    if(!name.isEmpty())
559 560 561 562 563
        raise(new FolderPlaylist(this, folder, name));
}

void PlaylistCollection::guessTagFromFile()
{
564
    visiblePlaylist()->slotGuessTagInfo(TagGuesser::FileName);
565 566 567 568
}

void PlaylistCollection::guessTagFromInternet()
{
569
    visiblePlaylist()->slotGuessTagInfo(TagGuesser::MusicBrainz);
570 571 572 573 574 575 576 577 578
}

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

    m_searchEnabled = enable;

579
    visiblePlaylist()->setSearchEnabled(enable);
580 581
}

582 583 584 585 586 587 588 589 590 591 592 593 594 595
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
596
        setupPlaylist(m_historyPlaylist, "view-history");
597 598 599

        QObject::connect(m_playerManager, SIGNAL(signalItemChanged(FileHandle)),
                historyPlaylist(), SLOT(appendProposedItem(FileHandle)));
600 601 602 603 604 605 606
    }
    else {
        delete m_historyPlaylist;
        m_historyPlaylist = 0;
    }
}

607 608 609 610 611 612 613
UpcomingPlaylist *PlaylistCollection::upcomingPlaylist() const
{
    return m_upcomingPlaylist;
}

void PlaylistCollection::setUpcomingPlaylistEnabled(bool enable)
{
614
    if((enable && m_upcomingPlaylist) || (!enable && !m_upcomingPlaylist))
615 616 617 618
        return;

    if(enable) {
        action<KToggleAction>("showUpcoming")->setChecked(true);
619 620
        if(!m_upcomingPlaylist)
            m_upcomingPlaylist = new UpcomingPlaylist(this);
621

Pino Toscano's avatar
Pino Toscano committed
622
        setupPlaylist(m_upcomingPlaylist, "go-jump-today");
623 624
    }
    else {
625
        action<KToggleAction>("showUpcoming")->setChecked(false);
626
        bool raiseCollection = visiblePlaylist() == m_upcomingPlaylist;
627

628 629 630
        if(raiseCollection) {
            raise(CollectionList::instance());
        }
631 632 633

        m_upcomingPlaylist->deleteLater();
        m_upcomingPlaylist = 0;
634 635 636
    }
}

637 638 639 640 641 642
QObject *PlaylistCollection::object() const
{
    return m_actionHandler;
}

Playlist *PlaylistCollection::currentPlaylist() const
643
{
644 645 646
    if(m_belowDistraction)
        return m_belowDistraction;

647
    if(m_upcomingPlaylist && m_upcomingPlaylist->active())
648 649
        return m_upcomingPlaylist;

650 651 652 653 654 655 656
    if(Playlist::playingItem())
        return Playlist::playingItem()->playlist();
    else
        return visiblePlaylist();
}

Playlist *PlaylistCollection::visiblePlaylist() const
657
{
658
    return qobject_cast<Playlist *>(m_playlistStack->currentWidget());
659 660
}

661 662
void PlaylistCollection::raise(Playlist *playlist)
{
663 664 665 666 667 668 669
    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();
670
    playlist->setSearchEnabled(m_searchEnabled);
671
    m_playlistStack->setCurrentWidget(playlist);
672
    clearShowMore(false);
673
    playlistItemsChanged();
674 675
}

676 677 678 679 680 681 682 683
void PlaylistCollection::raiseDistraction()
{
    if(m_belowDistraction)
        return;

    m_belowDistraction = currentPlaylist();

    if(!m_distraction) {
684
        m_distraction = new QWidget(m_playlistStack);
685 686 687
        m_playlistStack->addWidget(m_distraction);
    }

688
    m_playlistStack->setCurrentWidget(m_distraction);
689 690 691 692 693 694 695 696
}

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

    if(m_belowDistraction)
697
        m_playlistStack->setCurrentWidget(m_belowDistraction);
698 699 700 701

    m_belowDistraction = 0;
}

702 703 704 705
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////

706
QStackedWidget *PlaylistCollection::playlistStack() const
707 708 709 710 711 712
{
    return m_playlistStack;
}

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

716
    if(!playlist->name().isEmpty())
717 718
        m_playlistNames.insert(playlist->name());

719
    m_playlistStack->addWidget(playlist);
720
    QObject::connect(playlist, SIGNAL(itemSelectionChanged()),
721 722 723 724 725 726 727 728
                     object(), SIGNAL(signalSelectedItemsChanged()));
}

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

729 730 731 732 733
bool PlaylistCollection::containsPlaylistFile(const QString &file) const
{
    return m_playlistFiles.contains(file);
}

734 735
bool PlaylistCollection::showMoreActive() const
{
736
    return visiblePlaylist() == m_showMorePlaylist;
737 738
}

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
void PlaylistCollection::clearShowMore(bool raisePlaylist)
{
    if(!m_showMorePlaylist)
        return;

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

    m_belowShowMorePlaylist = 0;
}

754 755
void PlaylistCollection::enableDirWatch(bool enable)
{
756
    auto collection = CollectionList::instance();
757 758 759

    m_dirLister.disconnect(object());
    if(enable) {
760 761 762 763 764 765 766 767
        QObject::connect(&m_dirLister, &KDirLister::newItems,
                object(), [this](const KFileItemList &items) {
                    this->newItems(items);
                });
        QObject::connect(&m_dirLister, &KDirLister::refreshItems,
                collection, &CollectionList::slotRefreshItems);
        QObject::connect(&m_dirLister, &KDirLister::itemsDeleted,
                collection, &CollectionList::slotDeleteItems);
768
    }
769 770
}

771
QString PlaylistCollection::playlistNameDialog(const QString &caption,
772 773
                                               const QString &suggest,
                                               bool forceUnique) const
774 775 776
{
    bool ok;

777 778
    QString name = QInputDialog::getText(
        m_playlistStack,
779
        caption,
780
        i18n("Please enter a name for this playlist:"),
781
        QLineEdit::Normal,
782 783
        forceUnique ? uniquePlaylistName(suggest) : suggest,
        &ok);
784

785
    return ok ? uniquePlaylistName(name) : QString();
786 787 788 789
}


QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const
790
{
791 792 793
    if(suggest.isEmpty())
        return uniquePlaylistName();

794 795 796 797 798 799
    if(!m_playlistNames.contains(suggest))
        return suggest;

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

800
    int count = 1;
801
    QString s = QString("%1 (%2)").arg(base).arg(count);
802

803
    while(m_playlistNames.contains(s)) {
804
        count++;
805 806 807 808 809 810
        s = QString("%1 (%2)").arg(base).arg(count);
    }

    return s;
}

811
void PlaylistCollection::addNameToDict(const QString &name)
812 813 814
{
    m_playlistNames.insert(name);
}
815

816 817 818 819 820 821
void PlaylistCollection::addFileToDict(const QString &file)
{
    m_playlistFiles.insert(file);
}

void PlaylistCollection::removeNameFromDict(const QString &name)
822 823
{
    m_playlistNames.remove(name);
824 825
}

826 827 828 829 830
void PlaylistCollection::removeFileFromDict(const QString &file)
{
    m_playlistFiles.remove(file);
}

831 832
void PlaylistCollection::dirChanged(const QString &path)
{
833 834 835 836 837 838 839 840 841 842
    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));
843 844
}

845 846
Playlist *PlaylistCollection::playlistByName(const QString &name) const
{
847 848 849 850
    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;
851 852
    }

853
    return 0;
854 855
}

856 857
void PlaylistCollection::newItems(const KFileItemList &list) const
{
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
    // 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);
880
}
881

882 883 884 885 886 887
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////

void PlaylistCollection::readConfig()
{
888
    KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
889

890 891 892 893
    m_importPlaylists    = config.readEntry("ImportPlaylists", true);
    m_folderList         = config.readEntry("DirectoryList", QStringList());
    m_excludedFolderList = canonicalizeFolderPaths(
            config.readEntry("ExcludeDirectoryList", QStringList()));
894

Michael Pyne's avatar
Michael Pyne committed
895 896
    for(const auto &folder : m_folderList) {
        m_dirLister.openUrl(QUrl::fromUserInput(folder), KDirLister::Keep);
897
    }
898 899 900 901
}

void PlaylistCollection::saveConfig()
{
902
    KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
903
    config.writeEntry("ImportPlaylists", m_importPlaylists);
904
    config.writeEntry("showUpcoming", action("showUpcoming")->isChecked());
905
    config.writePathEntry("DirectoryList", m_folderList);
906
    config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList);
907 908

    config.sync();
909 910 911
}

////////////////////////////////////////////////////////////////////////////////
912
// ActionHandler implementation
913 914 915
////////////////////////////////////////////////////////////////////////////////

PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) :
916
    QObject(nullptr),
917 918
    m_collection(collection)
{
Laurent Montel's avatar
Laurent Montel committed
919
    setObjectName( QLatin1String("ActionHandler" ));
920

921 922 923 924
    KActionMenu *menu;

    // "New" menu

Michael Pyne's avatar
Michael Pyne committed
925
    menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("new playlist", "&New"), actions());
926
    actions()->addAction("file_new", menu);
927

928
    menu->addAction(createAction(i18n("&Empty Playlist..."), SLOT(slotCreatePlaylist()),
929
                              "newPlaylist", "window-new", QKeySequence(Qt::CTRL + Qt::Key_N)));
930
    menu->addAction(createAction(i18n("&Search Playlist..."), SLOT(slotCreateSearchPlaylist()),
931
                              "newSearchPlaylist", "edit-find", QKeySequence(Qt::CTRL + Qt::Key_F)));
932
    menu->addAction(createAction(i18n("Playlist From &Folder..."), SLOT(slotCreateFolderPlaylist()),
933
                              "newDirectoryPlaylist", "document-open", QKeySequence(Qt::CTRL + Qt::Key_D)));
934 935 936

    // Guess tag info menu

937
#if HAVE_TUNEPIMP
938 939 940
    menu = new KActionMenu(i18n("&Guess Tag Information"), actions());
    actions()->addAction("guessTag", menu);

941
    /* menu->setIcon(SmallIcon("wizard")); */
942

943
    menu->addAction(createAction(i18n("From &File Name"), SLOT(slotGuessTagFromFile()),
944
                              "guessTagFile", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G)));
945
    menu->addAction(createAction(i18n("From &Internet"), SLOT(slotGuessTagFromInternet()),
946
                              "guessTagInternet", "network-server", QKeySequence(Qt::CTRL + Qt::Key_I)));
947
#else
948
    createAction(i18n("Guess Tag Information From &File Name"), SLOT(slotGuessTagFromFile()),
949
                 "guessTag", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G));
950 951 952
#endif


953
    createAction(i18n("Play First Track"),SLOT(slotPlayFirst()),     "playFirst");
954
    createAction(i18n("Play Next Album"), SLOT(slotPlayNextAlbum()), "forwardAlbum", "go-down-search");
955

956 957 958 959
    KStandardAction::open(this, SLOT(slotOpen()), actions());
    KStandardAction::save(this, SLOT(slotSave()), actions());
    KStandardAction::saveAs(this, SLOT(slotSaveAs()), actions());

960
    createAction(i18n("Manage &Folders..."),  SLOT(slotManageFolders()),    "openDirectory", "folder-new");
Pino Toscano's avatar
Pino Toscano committed
961
    createAction(i18n("&Rename..."),      SLOT(slotRename()),       "renamePlaylist", "edit-rename");
962 963
    createAction(i18nc("verb, copy the playlist", "D&uplicate..."),
                 SLOT(slotDuplicate()),    "duplicatePlaylist", "edit-copy");
Pino Toscano's avatar
Pino Toscano committed
964
    createAction(i18n("R&emove"),         SLOT(slotRemove()),       "deleteItemPlaylist", "user-trash");
965
    createAction(i18n("Reload"),          SLOT(slotReload()),       "reloadPlaylist", "view-refresh");
Pino Toscano's avatar
Pino Toscano committed
966
    createAction(i18n("Edit Search..."),  SLOT(slotEditSearch()),   "editSearch");
967

968 969
    createAction(i18n("&Delete"),         SLOT(slotRemoveItems()),  "removeItem", "edit-delete");
    createAction(i18n("Refresh"),         SLOT(slotRefreshItems()), "refresh", "view-refresh");
970
    createAction(i18n("&Rename File"),    SLOT(slotRenameItems()),  "renameFile", "document-save-as", QKeySequence(Qt::CTRL + Qt::Key_R));
971

972 973
    menu = new KActionMenu(i18n("Cover Manager"), actions());
    actions()->addAction("coverManager", menu);
974
    /* menu->setIcon(SmallIcon("image-x-generic")); */
975
    menu->addAction(createAction(i18n("&View Cover"),
976
        SLOT(slotViewCovers()), "viewCover", "document-preview"));
977
    menu->addAction(createAction(i18n("Get Cover From &File..."),
978
        SLOT(slotAddLocalCover()), "addCover", "document-import", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)));
979
    menu->addAction(createAction(i18n("Get Cover From &Internet..."),
980
        SLOT(slotAddInternetCover()), "webImageCover", "network-server", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G)));
981
    menu->addAction(createAction(i18n("&Delete Cover"),
982
        SLOT(slotRemoveCovers()), "removeCover", "edit-delete"));
983
    menu->addAction(createAction(i18n("Show Cover &Manager"),
984
        SLOT(slotShowCoverManager()), "showCoverManager"));
985

986
    KToggleAction *upcomingAction =
Michael Pyne's avatar
Michael Pyne committed
987
        new KToggleAction(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18n("Show &Play Queue"), actions());
988
    actions()->addAction("showUpcoming", upcomingAction);
989

990
    connect(upcomingAction, SIGNAL(triggered(bool)),
991
            this, SLOT(slotSetUpcomingPlaylistEnabled(bool)));
992 993 994

    connect(m_collection->m_playerManager, &PlayerManager::signalStop,
            this, [this]() { m_collection->stop(); });
995 996
}

Michael Pyne's avatar
Michael Pyne committed
997
QAction *PlaylistCollection::ActionHandler::createAction(const QString &text,
998 999 1000
                                                         const char *slot,
                                                         const char *name,
                                                         const QString &icon,
1001
                                                         const QKeySequence &shortcut)
1002
{
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
    auto actionCollection = actions();
    QAction *action = new QAction(text, actions());

    if(!icon.isEmpty()) {
        action->setIcon(QIcon::fromTheme(icon));
    }
    connect(action, SIGNAL(triggered(bool)), slot);

    actionCollection->addAction(name, action);

    if (!shortcut.isEmpty()) {
        actionCollection->setDefaultShortcut(action, shortcut);
1015
    }
Stephan Kulow's avatar
Stephan Kulow committed
1016
    return action;
1017 1018
}

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