File indexing completed on 2023-05-30 11:30:50

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