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: