playlistcollection.cpp 28.7 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 188
    const auto childList = m_playlistStack->findChildren<Playlist *>("Playlist");
    for(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
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////

680
QStackedWidget *PlaylistCollection::playlistStack() const
681 682 683 684 685 686
{
    return m_playlistStack;
}

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

690
    if(!playlist->name().isEmpty())
691 692
        m_playlistNames.insert(playlist->name());

693
    m_playlistStack->addWidget(playlist);
694
    QObject::connect(playlist, SIGNAL(itemSelectionChanged()),
695 696 697 698 699 700 701 702
                     object(), SIGNAL(signalSelectedItemsChanged()));
}

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

703 704 705 706 707
bool PlaylistCollection::containsPlaylistFile(const QString &file) const
{
    return m_playlistFiles.contains(file);
}

708 709
bool PlaylistCollection::showMoreActive() const
{
710
    return visiblePlaylist() == m_showMorePlaylist;
711 712
}

713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
void PlaylistCollection::clearShowMore(bool raisePlaylist)
{
    if(!m_showMorePlaylist)
        return;

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

    m_belowShowMorePlaylist = 0;
}

728 729
void PlaylistCollection::enableDirWatch(bool enable)
{
730
    auto collection = CollectionList::instance();
731 732 733

    m_dirLister.disconnect(object());
    if(enable) {
734 735 736 737 738 739 740 741
        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);
742
    }
743 744
}

745
QString PlaylistCollection::playlistNameDialog(const QString &caption,
746 747
                                               const QString &suggest,
                                               bool forceUnique) const
748 749 750
{
    bool ok;

751 752
    QString name = QInputDialog::getText(
        m_playlistStack,
753
        caption,
754
        i18n("Please enter a name for this playlist:"),
755
        QLineEdit::Normal,
756 757
        forceUnique ? uniquePlaylistName(suggest) : suggest,
        &ok);
758

759
    return ok ? uniquePlaylistName(name) : QString();
760 761 762 763
}


QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const
764
{
765 766 767
    if(suggest.isEmpty())
        return uniquePlaylistName();

768 769 770 771 772 773
    if(!m_playlistNames.contains(suggest))
        return suggest;

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

774
    int count = 1;
775
    QString s = QString("%1 (%2)").arg(base).arg(count);
776

777
    while(m_playlistNames.contains(s)) {
778
        count++;
779 780 781 782 783 784
        s = QString("%1 (%2)").arg(base).arg(count);
    }

    return s;
}

785
void PlaylistCollection::addNameToDict(const QString &name)
786 787 788
{
    m_playlistNames.insert(name);
}
789

790 791 792 793 794 795
void PlaylistCollection::addFileToDict(const QString &file)
{
    m_playlistFiles.insert(file);
}

void PlaylistCollection::removeNameFromDict(const QString &name)
796 797
{
    m_playlistNames.remove(name);
798 799
}

800 801 802 803 804
void PlaylistCollection::removeFileFromDict(const QString &file)
{
    m_playlistFiles.remove(file);
}

805 806
void PlaylistCollection::dirChanged(const QString &path)
{
807 808 809 810 811 812 813 814 815 816
    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));
817 818
}

819 820
Playlist *PlaylistCollection::playlistByName(const QString &name) const
{
821 822 823 824
    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;
825 826
    }

827
    return 0;
828 829
}

830 831
void PlaylistCollection::newItems(const KFileItemList &list) const
{
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
    // 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);
854
}
855

856 857 858 859 860 861
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////

void PlaylistCollection::readConfig()
{
862
    KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
863

864 865 866 867
    m_importPlaylists    = config.readEntry("ImportPlaylists", true);
    m_folderList         = config.readEntry("DirectoryList", QStringList());
    m_excludedFolderList = canonicalizeFolderPaths(
            config.readEntry("ExcludeDirectoryList", QStringList()));
868

Michael Pyne's avatar
Michael Pyne committed
869 870
    for(const auto &folder : m_folderList) {
        m_dirLister.openUrl(QUrl::fromUserInput(folder), KDirLister::Keep);
871
    }
872 873 874 875
}

void PlaylistCollection::saveConfig()
{
876
    KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
877
    config.writeEntry("ImportPlaylists", m_importPlaylists);
878
    config.writeEntry("showUpcoming", action("showUpcoming")->isChecked());
879
    config.writePathEntry("DirectoryList", m_folderList);
880
    config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList);
881 882

    config.sync();
883 884 885
}

////////////////////////////////////////////////////////////////////////////////
886
// ActionHandler implementation
887 888 889
////////////////////////////////////////////////////////////////////////////////

PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) :
890
    QObject(nullptr),
891 892
    m_collection(collection)
{
Laurent Montel's avatar
Laurent Montel committed
893
    setObjectName( QLatin1String("ActionHandler" ));
894

895 896 897 898
    KActionMenu *menu;

    // "New" menu

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

902
    menu->addAction(createAction(i18n("&Empty Playlist..."), SLOT(slotCreatePlaylist()),
903
                              "newPlaylist", "window-new", QKeySequence(Qt::CTRL + Qt::Key_N)));
904
    menu->addAction(createAction(i18n("&Search Playlist..."), SLOT(slotCreateSearchPlaylist()),
905
                              "newSearchPlaylist", "edit-find", QKeySequence(Qt::CTRL + Qt::Key_F)));
906
    menu->addAction(createAction(i18n("Playlist From &Folder..."), SLOT(slotCreateFolderPlaylist()),
907
                              "newDirectoryPlaylist", "document-open", QKeySequence(Qt::CTRL + Qt::Key_D)));
908 909 910

    // Guess tag info menu

911
#if HAVE_TUNEPIMP
912 913 914
    menu = new KActionMenu(i18n("&Guess Tag Information"), actions());
    actions()->addAction("guessTag", menu);

915
    menu->setIcon(QIcon::fromTheme("wizard"));
916

917
    menu->addAction(createAction(i18n("From &File Name"), SLOT(slotGuessTagFromFile()),
918
                              "guessTagFile", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G)));
919
    menu->addAction(createAction(i18n("From &Internet"), SLOT(slotGuessTagFromInternet()),
920
                              "guessTagInternet", "network-server", QKeySequence(Qt::CTRL + Qt::Key_I)));
921
#else
922
    createAction(i18n("Guess Tag Information From &File Name"), SLOT(slotGuessTagFromFile()),
923
                 "guessTag", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G));
924 925 926
#endif


927
    createAction(i18n("Play First Track"),SLOT(slotPlayFirst()),     "playFirst");
928
    createAction(i18n("Play Next Album"), SLOT(slotPlayNextAlbum()), "forwardAlbum", "go-down-search");
929

930 931 932 933
    KStandardAction::open(this, SLOT(slotOpen()), actions());
    KStandardAction::save(this, SLOT(slotSave()), actions());
    KStandardAction::saveAs(this, SLOT(slotSaveAs()), actions());

934
    createAction(i18n("Manage &Folders..."),  SLOT(slotManageFolders()),    "openDirectory", "folder-new");
Pino Toscano's avatar
Pino Toscano committed
935
    createAction(i18n("&Rename..."),      SLOT(slotRename()),       "renamePlaylist", "edit-rename");
936 937
    createAction(i18nc("verb, copy the playlist", "D&uplicate..."),
                 SLOT(slotDuplicate()),    "duplicatePlaylist", "edit-copy");
Pino Toscano's avatar
Pino Toscano committed
938
    createAction(i18n("R&emove"),         SLOT(slotRemove()),       "deleteItemPlaylist", "user-trash");
939
    createAction(i18n("Reload"),          SLOT(slotReload()),       "reloadPlaylist", "view-refresh");
Pino Toscano's avatar
Pino Toscano committed
940
    createAction(i18n("Edit Search..."),  SLOT(slotEditSearch()),   "editSearch");
941

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

946 947
    menu = new KActionMenu(i18n("Cover Manager"), actions());
    actions()->addAction("coverManager", menu);
948
    menu->setIcon(QIcon::fromTheme("image-x-generic"));
949
    menu->addAction(createAction(i18n("&View Cover"),
950
        SLOT(slotViewCovers()), "viewCover", "document-preview"));
951
    menu->addAction(createAction(i18n("Get Cover From &File..."),
952
        SLOT(slotAddLocalCover()), "addCover", "document-import", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)));
953
    menu->addAction(createAction(i18n("Get Cover From &Internet..."),
954
        SLOT(slotAddInternetCover()), "webImageCover", "network-server", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G)));
955
    menu->addAction(createAction(i18n("&Delete Cover"),
956
        SLOT(slotRemoveCovers()), "removeCover", "edit-delete"));
957
    menu->addAction(createAction(i18n("Show Cover &Manager"),
958
        SLOT(slotShowCoverManager()), "showCoverManager"));
959

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

964
    connect(upcomingAction, SIGNAL(triggered(bool)),
965
            this, SLOT(slotSetUpcomingPlaylistEnabled(bool)));
966 967 968

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

Michael Pyne's avatar
Michael Pyne committed
971
QAction *PlaylistCollection::ActionHandler::createAction(const QString &text,
972 973 974
                                                         const char *slot,
                                                         const char *name,
                                                         const QString &icon,
975
                                                         const QKeySequence &shortcut)
976
{
977 978 979 980 981 982 983 984 985 986 987 988
    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);
989
    }
Stephan Kulow's avatar
Stephan Kulow committed
990
    return action;
991 992
}

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