File indexing completed on 2021-12-21 13:27:58

0001 /**
0002  * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
0003  * Copyright (C) 2009, 2021 Michael Pyne <mpyne@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify it under
0006  * the terms of the GNU General Public License as published by the Free Software
0007  * Foundation; either version 2 of the License, or (at your option) any later
0008  * version.
0009  *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU General Public License along with
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #include "playlistcollection.h"
0019 
0020 #include <kio_version.h>
0021 #include <kiconloader.h>
0022 #include <kmessagebox.h>
0023 #include <kactioncollection.h>
0024 #include <ktoggleaction.h>
0025 #include <kactionmenu.h>
0026 #include <kconfiggroup.h>
0027 #include <KSharedConfig>
0028 #include <kfileitem.h>
0029 
0030 #include <config-juk.h>
0031 
0032 #include <QAction>
0033 #include <QIcon>
0034 #include <QMutableListIterator>
0035 #include <QObject>
0036 #include <QPixmap>
0037 #include <QDir>
0038 #include <QFileDialog>
0039 #include <QInputDialog>
0040 #include <QStackedWidget>
0041 
0042 #include <sys/types.h>
0043 #include <dirent.h>
0044 
0045 #include "actioncollection.h"
0046 #include "advancedsearchdialog.h"
0047 #include "collectionlist.h"
0048 #include "coverinfo.h"
0049 #include "directorylist.h"
0050 #include "folderplaylist.h"
0051 #include "historyplaylist.h"
0052 #include "iconsupport.h"
0053 #include "juk.h"
0054 #include "mediafiles.h"
0055 #include "playermanager.h"
0056 #include "searchplaylist.h"
0057 #include "upcomingplaylist.h"
0058 
0059 //Laurent: readd it
0060 //#include "collectionadaptor.h"
0061 
0062 ////////////////////////////////////////////////////////////////////////////////
0063 // static methods
0064 ////////////////////////////////////////////////////////////////////////////////
0065 
0066 PlaylistCollection *PlaylistCollection::m_instance = nullptr;
0067 
0068 // Returns all folders in input list with their canonical path, if available, or
0069 // unchanged if not.
0070 static QStringList canonicalizeFolderPaths(const QStringList &folders)
0071 {
0072     QStringList result;
0073 
0074     foreach(const QString &folder, folders) {
0075         QString canonicalFolder = QDir(folder).canonicalPath();
0076         result << (!canonicalFolder.isEmpty() ? canonicalFolder : folder);
0077     }
0078 
0079     return result;
0080 }
0081 
0082 ////////////////////////////////////////////////////////////////////////////////
0083 // public methods
0084 ////////////////////////////////////////////////////////////////////////////////
0085 
0086 PlaylistCollection::PlaylistCollection(PlayerManager *player, QStackedWidget *playlistStack) :
0087     m_playlistStack(playlistStack),
0088     m_historyPlaylist(0),
0089     m_upcomingPlaylist(0),
0090     m_playerManager(player),
0091     m_importPlaylists(true),
0092     m_searchEnabled(true),
0093     m_playing(false),
0094     m_showMorePlaylist(0),
0095     m_belowShowMorePlaylist(0),
0096     m_dynamicPlaylist(0),
0097     m_belowDistraction(0),
0098     m_distraction(0)
0099 {
0100     //new CollectionAdaptor( this );
0101     //QDBus::sessionBus().registerObject("/Collection",this );
0102     m_instance = this;
0103 
0104     m_actionHandler = new ActionHandler(this);
0105 
0106 #if KIO_VERSION < QT_VERSION_CHECK(5, 82, 0)
0107     // KDirLister's auto error handling seems to crash JuK during startup in
0108     // readConfig().  This auto handler was removed in KF5.82 so no longer
0109     // needs disabled afterward.
0110 
0111     m_dirLister.setAutoErrorHandlingEnabled(false, playlistStack);
0112 #endif
0113 
0114     readConfig();
0115 }
0116 
0117 PlaylistCollection::~PlaylistCollection()
0118 {
0119     saveConfig();
0120     CollectionList::instance()->saveItemsToCache();
0121     delete m_actionHandler;
0122     Playlist::setShuttingDown();
0123 }
0124 
0125 QString PlaylistCollection::name() const
0126 {
0127     return currentPlaylist()->name();
0128 }
0129 
0130 FileHandle PlaylistCollection::currentFile() const
0131 {
0132     return currentPlaylist()->currentFile();
0133 }
0134 
0135 int PlaylistCollection::count() const
0136 {
0137     return currentPlaylist()->count();
0138 }
0139 
0140 int PlaylistCollection::time() const
0141 {
0142     return currentPlaylist()->time();
0143 }
0144 
0145 void PlaylistCollection::playFirst()
0146 {
0147     m_playing = true;
0148     currentPlaylist()->playFirst();
0149     currentPlayingItemChanged();
0150 }
0151 
0152 void PlaylistCollection::playNextAlbum()
0153 {
0154     m_playing = true;
0155     currentPlaylist()->playNextAlbum();
0156     currentPlayingItemChanged();
0157 }
0158 
0159 void PlaylistCollection::playPrevious()
0160 {
0161     m_playing = true;
0162     currentPlaylist()->playPrevious();
0163     currentPlayingItemChanged();
0164 }
0165 
0166 void PlaylistCollection::playNext()
0167 {
0168     m_playing = true;
0169     currentPlaylist()->playNext();
0170     currentPlayingItemChanged();
0171 }
0172 
0173 void PlaylistCollection::stop()
0174 {
0175     m_playing = false;
0176     currentPlaylist()->stop();
0177     playlistItemsChanged();
0178 }
0179 
0180 bool PlaylistCollection::playing() const
0181 {
0182     return m_playing;
0183 }
0184 
0185 QStringList PlaylistCollection::playlists() const
0186 {
0187     QStringList l;
0188 
0189     //(or qFindChildren() if you need MSVC 6 compatibility)
0190     const auto childList = m_playlistStack->findChildren<Playlist *>("Playlist");
0191     for(Playlist *p : childList) {
0192         l.append(p->name());
0193     }
0194 
0195     return l;
0196 }
0197 
0198 void PlaylistCollection::createPlaylist(const QString &name)
0199 {
0200     raise(new Playlist(this, name));
0201 }
0202 
0203 void PlaylistCollection::createDynamicPlaylist(const PlaylistList &playlists)
0204 {
0205     if(m_dynamicPlaylist)
0206         m_dynamicPlaylist->setPlaylists(playlists);
0207     else {
0208         m_dynamicPlaylist =
0209             new DynamicPlaylist(playlists, this, i18n("Dynamic List"), "audio-midi", false, true);
0210         PlaylistCollection::setupPlaylist(m_dynamicPlaylist, QString());
0211     }
0212 
0213     PlaylistCollection::raise(m_dynamicPlaylist);
0214 }
0215 
0216 void PlaylistCollection::showMore(const QString &artist, const QString &album)
0217 {
0218     if(showMoreActive()) {
0219         clearShowMore();
0220     }
0221 
0222     PlaylistList playlists;
0223     PlaylistSearch::ComponentList components;
0224 
0225     playlists.append(visiblePlaylist());
0226 
0227     if(!artist.isEmpty()) {
0228         // Just setting off the artist stuff in its own block.
0229         ColumnList columns;
0230         columns.append(PlaylistItem::ArtistColumn);
0231         PlaylistSearch::Component c(artist, false, columns,
0232                                     PlaylistSearch::Component::Exact);
0233         components.append(c);
0234     }
0235 
0236     if(!album.isEmpty()) {
0237         ColumnList columns;
0238         columns.append(PlaylistItem::AlbumColumn);
0239         PlaylistSearch::Component c(album, false, columns,
0240                                     PlaylistSearch::Component::Exact);
0241         components.append(c);
0242     }
0243 
0244     auto search = new PlaylistSearch(playlists, components,
0245             PlaylistSearch::MatchAll, collectionActions());
0246 
0247     if(m_showMorePlaylist)
0248         m_showMorePlaylist->setPlaylistSearch(search);
0249     else
0250         m_showMorePlaylist = new SearchPlaylist(this, *search, i18n("Now Playing"), false, true);
0251 
0252     // The call to raise() below will end up clearing m_belowShowMorePlaylist,
0253     // so cache the value we want it to have now.
0254     Playlist *belowShowMore = visiblePlaylist();
0255 
0256     PlaylistCollection::setupPlaylist(m_showMorePlaylist, QString());
0257     PlaylistCollection::raise(m_showMorePlaylist);
0258 
0259     m_belowShowMorePlaylist = belowShowMore;
0260 }
0261 
0262 void PlaylistCollection::removeTrack(const QString &playlist, const QStringList &files)
0263 {
0264     Playlist *p = playlistByName(playlist);
0265     PlaylistItemList itemList;
0266     if(!p)
0267         return;
0268 
0269     QStringList::ConstIterator it;
0270     for(it = files.begin(); it != files.end(); ++it) {
0271         CollectionListItem *item = CollectionList::instance()->lookup(*it);
0272 
0273         if(item) {
0274             PlaylistItem *playlistItem = item->itemForPlaylist(p);
0275             if(playlistItem)
0276                 itemList.append(playlistItem);
0277         }
0278     }
0279 
0280     p->clearItems(itemList);
0281 }
0282 
0283 QString PlaylistCollection::playlist() const
0284 {
0285     return visiblePlaylist() ? visiblePlaylist()->name() : QString();
0286 }
0287 
0288 QString PlaylistCollection::playingPlaylist() const
0289 {
0290     return currentPlaylist() && m_playing ? currentPlaylist()->name() : QString();
0291 }
0292 
0293 void PlaylistCollection::setPlaylist(const QString &playlist)
0294 {
0295     Playlist *p = playlistByName(playlist);
0296     if(p)
0297         raise(p);
0298 }
0299 
0300 QStringList PlaylistCollection::playlistTracks(const QString &playlist) const
0301 {
0302     Playlist *p = playlistByName(playlist);
0303 
0304     if(p)
0305         return p->files();
0306     return QStringList();
0307 }
0308 
0309 QString PlaylistCollection::trackProperty(const QString &file, const QString &property) const
0310 {
0311     CollectionList *l = CollectionList::instance();
0312     CollectionListItem *item = l->lookup(file);
0313 
0314     return item ? item->file().property(property) : QString();
0315 }
0316 
0317 QPixmap PlaylistCollection::trackCover(const QString &file, const QString &size) const
0318 {
0319     if(size.toLower() != "small" && size.toLower() != "large")
0320         return QPixmap();
0321 
0322     CollectionList *l = CollectionList::instance();
0323     CollectionListItem *item = l->lookup(file);
0324 
0325     if(!item)
0326         return QPixmap();
0327 
0328     if(size.toLower() == "small")
0329         return item->file().coverInfo()->pixmap(CoverInfo::Thumbnail);
0330     else
0331         return item->file().coverInfo()->pixmap(CoverInfo::FullSize);
0332 }
0333 
0334 void PlaylistCollection::open(const QStringList &l)
0335 {
0336     QStringList files = l;
0337 
0338     if(files.isEmpty())
0339         files = MediaFiles::openDialog(JuK::JuKInstance());
0340 
0341     if(files.isEmpty())
0342         return;
0343 
0344     bool justPlaylists = true;
0345 
0346     for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd() && justPlaylists; ++it)
0347         justPlaylists = !MediaFiles::isPlaylistFile(*it);
0348 
0349     if(visiblePlaylist() == CollectionList::instance() || justPlaylists ||
0350        KMessageBox::questionYesNo(
0351            JuK::JuKInstance(),
0352            i18n("Do you want to add these items to the current list or to the collection list?"),
0353            QString(),
0354            KGuiItem(i18nc("current playlist", "Current")),
0355            KGuiItem(i18n("Collection"))) == KMessageBox::No)
0356     {
0357         CollectionList::instance()->addFiles(files);
0358     }
0359     else {
0360         visiblePlaylist()->addFiles(files);
0361     }
0362 
0363     playlistItemsChanged();
0364 }
0365 
0366 void PlaylistCollection::open(const QString &playlist, const QStringList &files)
0367 {
0368     Playlist *p = playlistByName(playlist);
0369 
0370     if(p)
0371         p->addFiles(files);
0372 }
0373 
0374 void PlaylistCollection::addFolder()
0375 {
0376     DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance());
0377 
0378     if(l.exec() == QDialog::Accepted) {
0379         m_dirLister.blockSignals(true);
0380         DirectoryList::Result result = l.dialogResult();
0381 
0382         const bool reload = m_importPlaylists != result.addPlaylists;
0383 
0384         m_importPlaylists = result.addPlaylists;
0385         m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs);
0386 
0387         foreach(const QString &dir, result.addedDirs) {
0388             m_dirLister.openUrl(QUrl::fromLocalFile(dir), KDirLister::Keep);
0389             m_folderList.append(dir);
0390         }
0391 
0392         foreach(const QString &dir, result.removedDirs) {
0393             m_dirLister.stop(QUrl::fromLocalFile(dir));
0394             m_folderList.removeAll(dir);
0395         }
0396 
0397         if(reload) {
0398             open(m_folderList);
0399         }
0400         else if(!result.addedDirs.isEmpty()) {
0401             open(result.addedDirs);
0402         }
0403 
0404         saveConfig();
0405 
0406         m_dirLister.blockSignals(false);
0407     }
0408 }
0409 
0410 void PlaylistCollection::rename()
0411 {
0412     QString old = visiblePlaylist()->name();
0413     QString name = playlistNameDialog(i18n("Rename"), old, false);
0414 
0415     m_playlistNames.remove(old);
0416 
0417     if(name.isEmpty())
0418         return;
0419 
0420     visiblePlaylist()->setName(name);
0421 }
0422 
0423 void PlaylistCollection::duplicate()
0424 {
0425     QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"),
0426                                       visiblePlaylist()->name());
0427     if(name.isEmpty())
0428         return;
0429 
0430     raise(new Playlist(this, visiblePlaylist()->items(), name));
0431 }
0432 
0433 void PlaylistCollection::save()
0434 {
0435     visiblePlaylist()->save();
0436 }
0437 
0438 void PlaylistCollection::saveAs()
0439 {
0440     visiblePlaylist()->saveAs();
0441 }
0442 
0443 void PlaylistCollection::reload()
0444 {
0445     if(visiblePlaylist() == CollectionList::instance())
0446         CollectionList::instance()->addFiles(m_folderList);
0447     else
0448         visiblePlaylist()->slotReload();
0449 
0450 }
0451 
0452 void PlaylistCollection::editSearch()
0453 {
0454     SearchPlaylist *p = dynamic_cast<SearchPlaylist *>(visiblePlaylist());
0455     if(!p)
0456         return;
0457 
0458     auto searchDialog = new AdvancedSearchDialog(
0459             p->name(), *(p->playlistSearch()), JuK::JuKInstance());
0460     QObject::connect(searchDialog, &QDialog::finished, p, [searchDialog, p](int result)
0461             {
0462                 if (result) {
0463                     p->setPlaylistSearch(searchDialog->resultSearch());
0464                     p->setName(searchDialog->resultPlaylistName());
0465                 }
0466                 searchDialog->deleteLater();
0467             });
0468     searchDialog->exec();
0469 }
0470 
0471 void PlaylistCollection::removeItems()
0472 {
0473     visiblePlaylist()->slotRemoveSelectedItems();
0474 }
0475 
0476 void PlaylistCollection::refreshItems()
0477 {
0478     visiblePlaylist()->slotRefresh();
0479 }
0480 
0481 void PlaylistCollection::openItemDir()
0482 {
0483     visiblePlaylist()->slotOpenItemDir();
0484 }
0485 
0486 void PlaylistCollection::renameItems()
0487 {
0488     visiblePlaylist()->slotRenameFile();
0489 }
0490 
0491 void PlaylistCollection::addLocalCover()
0492 {
0493     visiblePlaylist()->slotAddCover(true /* from file */);
0494     playlistItemsChanged();
0495 }
0496 
0497 void PlaylistCollection::addInternetCover()
0498 {
0499     visiblePlaylist()->slotAddCover(false /* not from file */);
0500     playlistItemsChanged();
0501 }
0502 
0503 void PlaylistCollection::removeCovers()
0504 {
0505     visiblePlaylist()->slotRemoveCover();
0506     playlistItemsChanged();
0507 }
0508 
0509 void PlaylistCollection::viewCovers()
0510 {
0511     visiblePlaylist()->slotViewCover();
0512 }
0513 
0514 void PlaylistCollection::showCoverManager()
0515 {
0516     visiblePlaylist()->slotShowCoverManager();
0517 }
0518 
0519 PlaylistItemList PlaylistCollection::selectedItems()
0520 {
0521     return visiblePlaylist()->selectedItems();
0522 }
0523 
0524 void PlaylistCollection::scanFolders()
0525 {
0526     // If no music folder was configured, open music folder dialog
0527     if(m_folderList.count() == 0)
0528         addFolder();
0529 
0530     CollectionList::instance()->addFiles(m_folderList);
0531 
0532     enableDirWatch(true);
0533 }
0534 
0535 void PlaylistCollection::createPlaylist()
0536 {
0537     QString name = playlistNameDialog();
0538     if(!name.isEmpty())
0539         raise(new Playlist(this, name));
0540 }
0541 
0542 void PlaylistCollection::createSearchPlaylist()
0543 {
0544     QString name = uniquePlaylistName(i18n("Search Playlist"));
0545 
0546     auto searchDialog = new AdvancedSearchDialog(
0547             name, *(new PlaylistSearch(JuK::JuKInstance())), JuK::JuKInstance());
0548     QObject::connect(searchDialog, &QDialog::finished, collectionActions(),
0549             [searchDialog, this](int result) {
0550                 if (result) {
0551                     raise(new SearchPlaylist(
0552                                 this,
0553                                 *searchDialog->resultSearch(),
0554                                 searchDialog->resultPlaylistName()));
0555                 }
0556                 searchDialog->deleteLater();
0557             });
0558     searchDialog->exec();
0559 }
0560 
0561 void PlaylistCollection::createFolderPlaylist()
0562 {
0563     QString folder = QFileDialog::getExistingDirectory();
0564 
0565     if(folder.isEmpty())
0566         return;
0567 
0568     QString name = uniquePlaylistName(folder.mid(folder.lastIndexOf('/') + 1));
0569     name = playlistNameDialog(i18n("Create Folder Playlist"), name);
0570 
0571     if(!name.isEmpty())
0572         raise(new FolderPlaylist(this, folder, name));
0573 }
0574 
0575 void PlaylistCollection::guessTagFromFile()
0576 {
0577     visiblePlaylist()->slotGuessTagInfo(TagGuesser::FileName);
0578 }
0579 
0580 void PlaylistCollection::guessTagFromInternet()
0581 {
0582     visiblePlaylist()->slotGuessTagInfo(TagGuesser::MusicBrainz);
0583 }
0584 
0585 void PlaylistCollection::setSearchEnabled(bool enable)
0586 {
0587     if(enable == m_searchEnabled)
0588         return;
0589 
0590     m_searchEnabled = enable;
0591 
0592     visiblePlaylist()->setSearchEnabled(enable);
0593 }
0594 
0595 HistoryPlaylist *PlaylistCollection::historyPlaylist() const
0596 {
0597     return m_historyPlaylist;
0598 }
0599 
0600 void PlaylistCollection::setHistoryPlaylistEnabled(bool enable)
0601 {
0602     if(enable == static_cast<bool>(m_historyPlaylist)) {
0603         return;
0604     }
0605 
0606     if(enable) {
0607         ActionCollection::action<KToggleAction>("showHistory")->setChecked(true);
0608         m_historyPlaylist = new HistoryPlaylist(this);
0609         m_historyPlaylist->setName(i18n("History"));
0610         setupPlaylist(m_historyPlaylist, "view-history");
0611 
0612         QObject::connect(
0613                 m_playerManager,   &PlayerManager::signalItemChanged,
0614                 historyPlaylist(), &HistoryPlaylist::appendProposedItem);
0615     }
0616     else {
0617         delete m_historyPlaylist;
0618         m_historyPlaylist = nullptr;
0619     }
0620 }
0621 
0622 UpcomingPlaylist *PlaylistCollection::upcomingPlaylist() const
0623 {
0624     return m_upcomingPlaylist;
0625 }
0626 
0627 void PlaylistCollection::setUpcomingPlaylistEnabled(bool enable)
0628 {
0629     if(enable == static_cast<bool>(m_upcomingPlaylist))
0630         return;
0631 
0632     ActionCollection::action<KToggleAction>("showUpcoming")->setChecked(enable);
0633     if(enable) {
0634         m_upcomingPlaylist = new UpcomingPlaylist(this);
0635         setupPlaylist(m_upcomingPlaylist, "go-jump-today");
0636     }
0637     else {
0638         if(visiblePlaylist() == m_upcomingPlaylist) {
0639             raise(CollectionList::instance());
0640         }
0641 
0642         m_upcomingPlaylist->deleteLater();
0643         m_upcomingPlaylist = nullptr;
0644     }
0645 }
0646 
0647 PlaylistCollection::ActionHandler *PlaylistCollection::collectionActions() const
0648 {
0649     return m_actionHandler;
0650 }
0651 
0652 Playlist *PlaylistCollection::currentPlaylist() const
0653 {
0654     if(m_belowDistraction)
0655         return m_belowDistraction;
0656 
0657     if(m_upcomingPlaylist && m_upcomingPlaylist->topLevelItemCount() > 0)
0658         return m_upcomingPlaylist;
0659 
0660     if(Playlist::playingItem())
0661         return Playlist::playingItem()->playlist();
0662     else
0663         return visiblePlaylist();
0664 }
0665 
0666 Playlist *PlaylistCollection::visiblePlaylist() const
0667 {
0668     return qobject_cast<Playlist *>(m_playlistStack->currentWidget());
0669 }
0670 
0671 void PlaylistCollection::raise(Playlist *playlist)
0672 {
0673     if(m_showMorePlaylist && currentPlaylist() == m_showMorePlaylist)
0674         m_showMorePlaylist->lower(playlist);
0675     if(m_dynamicPlaylist && currentPlaylist() == m_dynamicPlaylist)
0676         m_dynamicPlaylist->lower(playlist);
0677 
0678     playlist->applySharedSettings();
0679     playlist->setSearchEnabled(m_searchEnabled);
0680     m_playlistStack->setCurrentWidget(playlist);
0681     clearShowMore(false);
0682 
0683     const auto playingItem = Playlist::playingItem();
0684     if(playingItem && visiblePlaylist() != playlist && playingItem->playlist() == playlist) {
0685         playlist->scrollToItem(playingItem, QAbstractItemView::PositionAtCenter);
0686     }
0687 
0688     playlistItemsChanged();
0689 }
0690 
0691 ////////////////////////////////////////////////////////////////////////////////
0692 // protected methods
0693 ////////////////////////////////////////////////////////////////////////////////
0694 
0695 QStackedWidget *PlaylistCollection::playlistStack() const
0696 {
0697     return m_playlistStack;
0698 }
0699 
0700 void PlaylistCollection::setupPlaylist(Playlist *playlist, const QString &)
0701 {
0702     if(!playlist->fileName().isEmpty())
0703         m_playlistFiles.insert(playlist->fileName());
0704 
0705     if(!playlist->name().isEmpty())
0706         m_playlistNames.insert(playlist->name());
0707 
0708     m_playlistStack->addWidget(playlist);
0709     QObject::connect(playlist, SIGNAL(itemSelectionChanged()),
0710                      collectionActions(), SIGNAL(signalSelectedItemsChanged()));
0711 }
0712 
0713 bool PlaylistCollection::importPlaylists() const
0714 {
0715     return m_importPlaylists;
0716 }
0717 
0718 bool PlaylistCollection::containsPlaylistFile(const QString &file) const
0719 {
0720     return m_playlistFiles.contains(file);
0721 }
0722 
0723 bool PlaylistCollection::showMoreActive() const
0724 {
0725     return visiblePlaylist() == m_showMorePlaylist;
0726 }
0727 
0728 void PlaylistCollection::clearShowMore(bool raisePlaylist)
0729 {
0730     if(!m_showMorePlaylist)
0731         return;
0732 
0733     const auto playingItem = currentPlaylist()->playingItem();
0734     if(playingItem && playingItem->playlist() == m_showMorePlaylist) {
0735         // Reset the playing indicator to the corresponding CollectionList item
0736         playingItem->collectionItem()->setPlaying(true, true);
0737     }
0738 
0739     if(raisePlaylist) {
0740         if(m_belowShowMorePlaylist)
0741             raise(m_belowShowMorePlaylist);
0742         else
0743             raise(CollectionList::instance());
0744     }
0745 
0746     m_belowShowMorePlaylist = nullptr;
0747 }
0748 
0749 void PlaylistCollection::enableDirWatch(bool enable)
0750 {
0751     auto collection = CollectionList::instance();
0752 
0753     m_dirLister.disconnect(collectionActions());
0754     if(enable) {
0755         QObject::connect(&m_dirLister, &KDirLister::newItems,
0756                 collectionActions(), [this](const KFileItemList &items) {
0757                     this->newItems(items);
0758                 });
0759         QObject::connect(&m_dirLister, &KDirLister::refreshItems,
0760                 collection, &CollectionList::slotRefreshItems);
0761         QObject::connect(&m_dirLister, &KDirLister::itemsDeleted,
0762                 collection, &CollectionList::slotDeleteItems);
0763     }
0764 }
0765 
0766 QString PlaylistCollection::playlistNameDialog(const QString &caption,
0767                                                const QString &suggest,
0768                                                bool forceUnique) const
0769 {
0770     bool ok;
0771 
0772     QString name = QInputDialog::getText(
0773         m_playlistStack,
0774         caption,
0775         i18n("Please enter a name for this playlist:"),
0776         QLineEdit::Normal,
0777         forceUnique ? uniquePlaylistName(suggest) : suggest,
0778         &ok);
0779 
0780     return ok ? uniquePlaylistName(name) : QString();
0781 }
0782 
0783 
0784 QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const
0785 {
0786     if(suggest.isEmpty())
0787         return uniquePlaylistName();
0788 
0789     if(!m_playlistNames.contains(suggest))
0790         return suggest;
0791 
0792     QString base = suggest;
0793     base.remove(QRegExp("\\s\\([0-9]+\\)$"));
0794 
0795     int count = 1;
0796     QString s = QString("%1 (%2)").arg(base).arg(count);
0797 
0798     while(m_playlistNames.contains(s)) {
0799         count++;
0800         s = QString("%1 (%2)").arg(base).arg(count);
0801     }
0802 
0803     return s;
0804 }
0805 
0806 void PlaylistCollection::addNameToDict(const QString &name)
0807 {
0808     m_playlistNames.insert(name);
0809 }
0810 
0811 void PlaylistCollection::addFileToDict(const QString &file)
0812 {
0813     m_playlistFiles.insert(file);
0814 }
0815 
0816 void PlaylistCollection::removeNameFromDict(const QString &name)
0817 {
0818     m_playlistNames.remove(name);
0819 }
0820 
0821 void PlaylistCollection::removeFileFromDict(const QString &file)
0822 {
0823     m_playlistFiles.remove(file);
0824 }
0825 
0826 void PlaylistCollection::dirChanged(const QString &path)
0827 {
0828     QString canonicalPath = QDir(path).canonicalPath();
0829     if(canonicalPath.isEmpty())
0830         return;
0831 
0832     foreach(const QString &excludedFolder, m_excludedFolderList) {
0833         if(canonicalPath.startsWith(excludedFolder))
0834             return;
0835     }
0836 
0837     CollectionList::instance()->addFiles(QStringList(canonicalPath));
0838 }
0839 
0840 Playlist *PlaylistCollection::playlistByName(const QString &name) const
0841 {
0842     for(int i = 0; i < m_playlistStack->count(); ++i) {
0843         Playlist *p = qobject_cast<Playlist *>(m_playlistStack->widget(i));
0844         if(p && p->name() == name)
0845             return p;
0846     }
0847 
0848     return 0;
0849 }
0850 
0851 void PlaylistCollection::newItems(const KFileItemList &list) const
0852 {
0853     // Make fast-path for the normal case
0854     if(m_excludedFolderList.isEmpty()) {
0855         CollectionList::instance()->slotNewItems(list);
0856         return;
0857     }
0858 
0859     // Slow case: Directories to exclude from consideration
0860 
0861     KFileItemList filteredList(list);
0862 
0863     foreach(const QString &excludedFolder, m_excludedFolderList) {
0864         QMutableListIterator<KFileItem> filteredListIterator(filteredList);
0865 
0866         while(filteredListIterator.hasNext()) {
0867             const KFileItem fileItem = filteredListIterator.next();
0868 
0869             if(fileItem.url().path().startsWith(excludedFolder))
0870                 filteredListIterator.remove();
0871         }
0872     }
0873 
0874     CollectionList::instance()->slotNewItems(filteredList);
0875 }
0876 
0877 ////////////////////////////////////////////////////////////////////////////////
0878 // private methods
0879 ////////////////////////////////////////////////////////////////////////////////
0880 
0881 void PlaylistCollection::readConfig()
0882 {
0883     KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
0884 
0885     m_importPlaylists    = config.readEntry("ImportPlaylists", true);
0886     m_folderList         = config.readEntry("DirectoryList", QStringList());
0887     m_excludedFolderList = canonicalizeFolderPaths(
0888             config.readEntry("ExcludeDirectoryList", QStringList()));
0889 
0890     for(const auto &folder : qAsConst(m_folderList)) {
0891         m_dirLister.openUrl(QUrl::fromUserInput(folder), KDirLister::Keep);
0892     }
0893 }
0894 
0895 void PlaylistCollection::saveConfig()
0896 {
0897     KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
0898     config.writeEntry("ImportPlaylists", m_importPlaylists);
0899     config.writeEntry("showUpcoming", ActionCollection::action("showUpcoming")->isChecked());
0900     config.writePathEntry("DirectoryList", m_folderList);
0901     config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList);
0902 
0903     config.sync();
0904 }
0905 
0906 ////////////////////////////////////////////////////////////////////////////////
0907 // ActionHandler implementation
0908 ////////////////////////////////////////////////////////////////////////////////
0909 
0910 PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection)
0911   : QObject(nullptr)
0912   , m_collection(collection)
0913 {
0914     using namespace IconSupport; // ""_icon
0915 
0916     setObjectName(QLatin1String("ActionHandler"));
0917 
0918     KActionMenu *menu;
0919     KActionCollection *actionCollection = ActionCollection::actions();
0920 
0921     // "New" menu
0922 
0923     menu = new KActionMenu("document-new"_icon, i18nc("new playlist", "&New"), this);
0924     actionCollection->addAction("file_new", menu);
0925 
0926     menu->addAction(createAction(i18n("&Empty Playlist..."),
0927                 qOverload<>(&PlaylistCollection::createPlaylist),
0928                 "newPlaylist", "window-new",
0929                 QKeySequence(Qt::CTRL + Qt::Key_N)));
0930     menu->addAction(createAction(i18n("&Search Playlist..."),
0931                 &PlaylistCollection::createSearchPlaylist,
0932                 "newSearchPlaylist", "edit-find",
0933                 QKeySequence(Qt::CTRL + Qt::Key_F)));
0934     menu->addAction(createAction(i18n("Playlist From &Folder..."),
0935                 &PlaylistCollection::createFolderPlaylist,
0936                 "newDirectoryPlaylist", "document-open",
0937                 QKeySequence(Qt::CTRL + Qt::Key_D)));
0938 
0939     // Guess tag info menu
0940 
0941 #if HAVE_TUNEPIMP
0942     menu = new KActionMenu(i18n("&Guess Tag Information"), actionCollection);
0943     actionCollection->addAction("guessTag", menu);
0944 
0945     menu->setIcon("wizard"_icon);
0946 
0947     menu->addAction(createAction(i18n("From &File Name"),
0948                 &PlaylistCollection::guessTagFromFile,
0949                 "guessTagFile", "document-import",
0950                 QKeySequence(Qt::CTRL + Qt::Key_G)));
0951     menu->addAction(createAction(i18n("From &Internet"),
0952                 &PlaylistCollection::guessTagFromInternet,
0953                 "guessTagInternet", "network-server",
0954                 QKeySequence(Qt::CTRL + Qt::Key_I)));
0955 #else
0956     createAction(i18n("Guess Tag Information From &File Name"),
0957                 &PlaylistCollection::guessTagFromFile,
0958                 "guessTag", "document-import",
0959                 QKeySequence(Qt::CTRL + Qt::Key_G));
0960 #endif
0961 
0962 
0963     createAction(i18n("Play First Track"), &PlaylistCollection::playFirst,
0964             "playFirst");
0965     createAction(i18n("Play Next Album"), &PlaylistCollection::playNextAlbum,
0966             "forwardAlbum", "go-down-search");
0967 
0968     KStandardAction::open(this, SLOT(slotOpen()), actionCollection);
0969     KStandardAction::save(this, SLOT(slotSave()), actionCollection);
0970     KStandardAction::saveAs(this, SLOT(slotSaveAs()), actionCollection);
0971 
0972     createAction(i18n("Manage &Folders..."), &PlaylistCollection::addFolder,
0973             "openDirectory", "folder-new");
0974     createAction(i18n("&Rename..."), &PlaylistCollection::rename,
0975             "renamePlaylist", "edit-rename");
0976     createAction(i18nc("verb, copy the playlist", "D&uplicate..."),
0977                  &PlaylistCollection::duplicate,
0978                  "duplicatePlaylist", "edit-copy");
0979     createAction(i18n("R&emove"), &PlaylistCollection::remove,
0980             "deleteItemPlaylist", "user-trash");
0981     createAction(i18n("Reload"), &PlaylistCollection::reload,
0982             "reloadPlaylist", "view-refresh");
0983     createAction(i18n("Edit Search..."), &PlaylistCollection::editSearch,
0984             "editSearch");
0985 
0986     createAction(i18n("&Delete"), &PlaylistCollection::removeItems,
0987             "removeItem", "edit-delete");
0988     createAction(i18n("Refresh"), &PlaylistCollection::refreshItems,
0989             "refresh", "view-refresh");
0990     createAction(i18n("Open Containing Folder"), &PlaylistCollection::openItemDir,
0991             "openItemDir", "stock_folder");
0992     createAction(i18n("&Rename File"), &PlaylistCollection::renameItems,
0993             "renameFile", "document-save-as",
0994             QKeySequence(Qt::CTRL + Qt::Key_R));
0995 
0996     menu = new KActionMenu(i18n("Cover Manager"), actionCollection);
0997     actionCollection->addAction("coverManager", menu);
0998     menu->setIcon("image-x-generic"_icon);
0999     menu->addAction(createAction(i18n("&View Cover"),
1000         &PlaylistCollection::viewCovers, "viewCover", "document-preview"));
1001     menu->addAction(createAction(i18n("Get Cover From &File..."),
1002         &PlaylistCollection::addLocalCover, "addCover", "document-import",
1003         QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)));
1004     menu->addAction(createAction(i18n("Get Cover From &Internet..."),
1005         &PlaylistCollection::addInternetCover,
1006         "webImageCover", "network-server",
1007         QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G)));
1008     menu->addAction(createAction(i18n("&Delete Cover"),
1009         &PlaylistCollection::removeCovers, "removeCover", "edit-delete"));
1010     menu->addAction(createAction(i18n("Show Cover &Manager"),
1011         &PlaylistCollection::showCoverManager, "showCoverManager"));
1012 
1013     auto upcomingAction = new KToggleAction(
1014             "go-jump-today"_icon, i18n("Show &Play Queue"), actionCollection);
1015     actionCollection->addAction("showUpcoming", upcomingAction);
1016 
1017     connect(upcomingAction, &KToggleAction::triggered,
1018             this, [this](bool enable) {
1019                 m_collection->setUpcomingPlaylistEnabled(enable);
1020             });
1021 
1022     connect(m_collection->m_playerManager, &PlayerManager::signalStop,
1023             this, [this]() { m_collection->stop(); });
1024 }
1025 
1026 //
1027 // Used with ActionHandler::createAction below to dispatch QAction::triggered(bool) to slots
1028 // that will actually use the bool parameter... or not, as the case may be
1029 //
1030 
1031 template<int k = 1>
1032 static void invokeSlot(PlaylistCollection *c, void (PlaylistCollection::*ptr)(bool), bool b)
1033 {
1034     (c->*ptr)(b);
1035 }
1036 
1037 template<int k = 0>
1038 static void invokeSlot(PlaylistCollection *c, void (PlaylistCollection::*ptr)(), bool)
1039 {
1040     (c->*ptr)();
1041 }
1042 
1043 template<typename ... PMFArg>
1044 QAction *PlaylistCollection::ActionHandler::createAction(
1045     const QString &text
1046   , void (PlaylistCollection::*slot)(PMFArg...)
1047   , const char *name
1048   , const QString &icon
1049   , const QKeySequence &shortcut
1050   )
1051 {
1052     KActionCollection *actionCollection = ActionCollection::actions();
1053     QAction *action = new QAction(text, actionCollection);
1054 
1055     if(!icon.isEmpty()) {
1056         action->setIcon(QIcon::fromTheme(icon));
1057     }
1058 
1059     QObject::connect(action, &QAction::triggered, this,
1060             [this, slot](bool b) -> void {
1061                 invokeSlot<sizeof...(PMFArg)>(m_collection, slot, b);
1062             });
1063 
1064     actionCollection->addAction(name, action);
1065 
1066     if (!shortcut.isEmpty()) {
1067         actionCollection->setDefaultShortcut(action, shortcut);
1068     }
1069     return action;
1070 }
1071 
1072 // vim: set et sw=4 tw=0 sta: