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: