diff --git a/advancedsearchdialog.cpp b/advancedsearchdialog.cpp index 94a6abc3..916466ba 100644 --- a/advancedsearchdialog.cpp +++ b/advancedsearchdialog.cpp @@ -1,186 +1,187 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * Copyright (C) 2017 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "advancedsearchdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include "collectionlist.h" #include "searchwidget.h" //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// AdvancedSearchDialog::AdvancedSearchDialog(const QString &defaultName, - const PlaylistSearch &defaultSearch, + PlaylistSearch &defaultSearch, QWidget *parent) : - QDialog(parent) + QDialog(parent), + m_search(&defaultSearch) { setWindowTitle(i18n("Create Search Playlist")); setObjectName(QStringLiteral("juk_advSrchDlg")); auto mw = new QVBoxLayout(this); setLayout(mw); auto box = new QHBoxLayout; mw->addLayout(box); box->addWidget(new QLabel(i18n("Playlist name:"))); m_playlistNameLineEdit = new QLineEdit(defaultName); box->addWidget(m_playlistNameLineEdit); auto criteriaGroupBox = new QGroupBox(i18n("Search Criteria")); mw->addWidget(criteriaGroupBox, 1); m_criteriaLayout = new QVBoxLayout(criteriaGroupBox); auto group = new QGroupBox; m_matchAnyButton = new QRadioButton(i18n("Match any of the following")); m_matchAllButton = new QRadioButton(i18n("Match all of the following")); QHBoxLayout *hgroupbox = new QHBoxLayout(group); hgroupbox->addWidget(m_matchAnyButton); hgroupbox->addWidget(m_matchAllButton); m_criteriaLayout->addWidget(group); m_criteriaLayout->addStretch(1); // more()/fewer() assume this is here QWidget *buttons = new QWidget; mw->addWidget(buttons); QHBoxLayout *l = new QHBoxLayout(buttons); l->setSpacing(5); l->setContentsMargins(0, 0, 0, 0); const auto &clearGuiItem = KStandardGuiItem::clear(); QPushButton *clearButton = new QPushButton(clearGuiItem.icon(), clearGuiItem.text()); connect(clearButton, &QPushButton::clicked, this, &AdvancedSearchDialog::clearSearches); l->addWidget(clearButton); l->addStretch(1); m_moreButton = new QPushButton(i18nc("additional search options", "More")); connect(m_moreButton, &QPushButton::clicked, this, &AdvancedSearchDialog::more); l->addWidget(m_moreButton); m_fewerButton = new QPushButton(i18n("Fewer")); connect(m_fewerButton, &QPushButton::clicked, this, &AdvancedSearchDialog::fewer); l->addWidget(m_fewerButton); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); mw->addWidget(buttonBox); if(defaultSearch.isNull()) { this->more(); this->more(); // Create first 2 searches m_matchAnyButton->setChecked(true); } else { PlaylistSearch::ComponentList components = defaultSearch.components(); for(PlaylistSearch::ComponentList::ConstIterator it = components.constBegin(); it != components.constEnd(); ++it) { SearchLine *s = new SearchLine(this); s->setSearchComponent(*it); m_searchLines.append(s); m_criteriaLayout->insertWidget(m_criteriaLayout->count() - 1, s); } if(defaultSearch.searchMode() == PlaylistSearch::MatchAny) m_matchAnyButton->setChecked(true); else m_matchAllButton->setChecked(true); } m_playlistNameLineEdit->setFocus(); } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void AdvancedSearchDialog::accept() { m_search->clearPlaylists(); m_search->clearComponents(); m_search->addPlaylist(CollectionList::instance()); for(const auto &searchLine : m_searchLines) m_search->addComponent(searchLine->searchComponent()); PlaylistSearch::SearchMode m = PlaylistSearch::SearchMode(!m_matchAnyButton->isChecked()); m_search->setSearchMode(m); m_playlistName = m_playlistNameLineEdit->text(); QDialog::accept(); } void AdvancedSearchDialog::clearSearches() { for(auto &searchLine : m_searchLines) searchLine->clear(); } void AdvancedSearchDialog::more() { SearchLine *searchLine = new SearchLine(this); // inserting it to keep the trailing stretch item at end m_criteriaLayout->insertWidget(m_criteriaLayout->count() - 1, searchLine); m_searchLines.append(searchLine); searchLine->show(); updateButtons(); } void AdvancedSearchDialog::fewer() { SearchLine *searchLine = m_searchLines.last(); m_searchLines.removeAll(searchLine); delete searchLine; updateButtons(); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// void AdvancedSearchDialog::updateButtons() { m_moreButton->setEnabled(m_searchLines.count() < 16); m_fewerButton->setEnabled(m_searchLines.count() > 1); } // vim: set et sw=4 tw=0 sta: diff --git a/advancedsearchdialog.h b/advancedsearchdialog.h index 0bf17af7..383dcf43 100644 --- a/advancedsearchdialog.h +++ b/advancedsearchdialog.h @@ -1,73 +1,73 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef ADVANCEDSEARCHDIALOG_H #define ADVANCEDSEARCHDIALOG_H #include #include #include "playlistsearch.h" class QLineEdit; class QPushButton; class QRadioButton; class SearchLine; class QBoxLayout; class AdvancedSearchDialog : public QDialog { Q_OBJECT public: explicit AdvancedSearchDialog( const QString& defaultName, - const PlaylistSearch& defaultSearch = PlaylistSearch(), + PlaylistSearch& defaultSearch, QWidget* parent = nullptr); PlaylistSearch* resultSearch() const { return m_search; } QString resultPlaylistName() const { return m_playlistName; } protected slots: void accept() Q_DECL_OVERRIDE; void clearSearches(); void more(); void fewer(); private: void updateButtons(); QBoxLayout *m_criteriaLayout; PlaylistSearch* m_search; QString m_playlistName; QList m_searchLines; QLineEdit *m_playlistNameLineEdit; QRadioButton *m_matchAnyButton; QRadioButton *m_matchAllButton; QPushButton *m_moreButton; QPushButton *m_fewerButton; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/cache.cpp b/cache.cpp index 245f34d9..9bb66eb3 100644 --- a/cache.cpp +++ b/cache.cpp @@ -1,351 +1,351 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2008, 2013 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "cache.h" #include "juk-exception.h" #include #include #include #include #include #include #include #include #include "juktag.h" #include "searchplaylist.h" #include "historyplaylist.h" #include "upcomingplaylist.h" #include "folderplaylist.h" #include "playlistcollection.h" #include "actioncollection.h" #include "juk.h" #include "juk_debug.h" using namespace ActionCollection; const int Cache::playlistListCacheVersion = 3; const int Cache::playlistItemsCacheVersion = 2; enum PlaylistType { Normal = 0, Search = 1, History = 2, Upcoming = 3, Folder = 4 }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// Cache *Cache::instance() { static Cache cache; return &cache; } static void parsePlaylistStream(QDataStream &s, PlaylistCollection *collection) { while(!s.atEnd()) { qint32 playlistType; s >> playlistType; Playlist *playlist = nullptr; switch(playlistType) { case Search: { - SearchPlaylist *p = new SearchPlaylist(collection); + SearchPlaylist *p = new SearchPlaylist(collection, *(new PlaylistSearch(JuK::JuKInstance()))); s >> *p; playlist = p; break; } case History: { action("showHistory")->setChecked(true); collection->setHistoryPlaylistEnabled(true); s >> *collection->historyPlaylist(); playlist = collection->historyPlaylist(); break; } case Upcoming: { /* collection->setUpcomingPlaylistEnabled(true); Playlist *p = collection->upcomingPlaylist(); action("saveUpcomingTracks")->setChecked(true); s >> *p; playlist = p; */ break; } case Folder: { FolderPlaylist *p = new FolderPlaylist(collection); s >> *p; playlist = p; break; } default: Playlist *p = new Playlist(collection, true); s >> *p; // We may have already read this playlist from the folder // scanner, if an .m3u playlist if(collection->containsPlaylistFile(p->fileName())) { delete p; p = nullptr; } playlist = p; break; } // switch qint32 sortColumn; s >> sortColumn; if(playlist) playlist->sortByColumn(sortColumn); } } void Cache::loadPlaylists(PlaylistCollection *collection) // static { const QString playlistsFile = playlistsCacheFileName(); QFile f(playlistsFile); if(!f.open(QIODevice::ReadOnly)) return; QDataStream fs(&f); qint32 version; fs >> version; if(version != 3 || fs.status() != QDataStream::Ok) { // Either the file is corrupt or is from a truly ancient version // of JuK. qCWarning(JUK_LOG) << "Found the playlist cache but it was clearly corrupt."; return; } // Our checksum is only for the values after the version and checksum so // we want to get a byte array with just the checksummed data. QByteArray data; quint16 checksum; fs >> checksum >> data; if(fs.status() != QDataStream::Ok || checksum != qChecksum(data.data(), data.size())) return; QDataStream s(&data, QIODevice::ReadOnly); s.setVersion(QDataStream::Qt_4_3); try { // Loading failures are indicated by an exception parsePlaylistStream(s, collection); } catch(BICStreamException &) { qCCritical(JUK_LOG) << "Exception loading playlists - binary incompatible stream."; // TODO Restructure the Playlist data model and PlaylistCollection data model // to be separate from the view/controllers. return; } } void Cache::savePlaylists(const PlaylistList &playlists) { QString playlistsFile = playlistsCacheFileName(); QSaveFile f(playlistsFile); if(!f.open(QIODevice::WriteOnly)) { qCCritical(JUK_LOG) << "Error saving collection:" << f.errorString(); return; } QByteArray data; QDataStream s(&data, QIODevice::WriteOnly); s.setVersion(QDataStream::Qt_4_3); for(const auto &it : playlists) { if(!(it)) { continue; } // TODO back serialization type into Playlist itself if(dynamic_cast(it)) { s << qint32(History) << *static_cast(it); } else if(dynamic_cast(it)) { s << qint32(Search) << *static_cast(it); } else if(dynamic_cast(it)) { if(!action("saveUpcomingTracks")->isChecked()) continue; s << qint32(Upcoming) << *static_cast(it); } else if(dynamic_cast(it)) { s << qint32(Folder) << *static_cast(it); } else { s << qint32(Normal) << *(it); } s << qint32(it->sortColumn()); } QDataStream fs(&f); fs << qint32(playlistListCacheVersion); fs << qChecksum(data.data(), data.size()); fs << data; if(!f.commit()) qCCritical(JUK_LOG) << "Error saving collection:" << f.errorString(); } void Cache::ensureAppDataStorageExists() // static { QString dirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); QDir appDataDir(dirPath); if(!appDataDir.exists() && !appDataDir.mkpath(dirPath)) qCCritical(JUK_LOG) << "Unable to create appdata storage in" << dirPath; } bool Cache::cacheFileExists() // static { return QFile::exists(fileHandleCacheFileName()); } // Despite the 'Cache' class name, these data files are not regenerable and so // should not be stored in cache directory. QString Cache::fileHandleCacheFileName() // static { return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/cache"; } QString Cache::playlistsCacheFileName() // static { return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/playlists"; } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// Cache::Cache() { } bool Cache::prepareToLoadCachedItems() { m_loadFile.setFileName(fileHandleCacheFileName()); if(!m_loadFile.open(QIODevice::ReadOnly)) return false; m_loadDataStream.setDevice(&m_loadFile); int dataStreamVersion = CacheDataStream::Qt_3_3; qint32 version; m_loadDataStream >> version; switch(version) { case 2: dataStreamVersion = CacheDataStream::Qt_4_3; #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) Q_FALLTHROUGH(); #endif // Other than that we're compatible with cache v1, so fallthrough // to setCacheVersion case 1: { m_loadDataStream.setCacheVersion(1); m_loadDataStream.setVersion(dataStreamVersion); qint32 checksum; m_loadDataStream >> checksum >> m_loadFileBuffer.buffer(); m_loadFileBuffer.open(QIODevice::ReadOnly); m_loadDataStream.setDevice(&m_loadFileBuffer); qint32 checksumExpected = qChecksum( m_loadFileBuffer.data(), m_loadFileBuffer.size()); if(m_loadDataStream.status() != CacheDataStream::Ok || checksum != checksumExpected) { qCCritical(JUK_LOG) << "Music cache checksum expected to get" << checksumExpected << "actually was" << checksum; KMessageBox::sorry(0, i18n("The music data cache has been corrupted. JuK " "needs to rescan it now. This may take some time.")); return false; } break; } default: { m_loadDataStream.device()->reset(); m_loadDataStream.setCacheVersion(0); // This cache is so old that this is just a wild guess here that 3.3 // is compatible. m_loadDataStream.setVersion(CacheDataStream::Qt_3_3); break; } } return true; } FileHandle Cache::loadNextCachedItem() { if(!m_loadFile.isOpen() || !m_loadDataStream.device()) { qCWarning(JUK_LOG) << "Already completed reading cache file."; return FileHandle(); } if(m_loadDataStream.status() == QDataStream::ReadCorruptData) { qCCritical(JUK_LOG) << "Attempted to read file handle from corrupt cache file."; return FileHandle(); } if(!m_loadDataStream.atEnd()) { QString fileName; m_loadDataStream >> fileName; fileName.squeeze(); return FileHandle(fileName, m_loadDataStream); } else { m_loadDataStream.setDevice(0); m_loadFile.close(); return FileHandle(); } } // vim: set et sw=4 tw=0 sta: diff --git a/playlistcollection.cpp b/playlistcollection.cpp index e0838418..d9915e47 100644 --- a/playlistcollection.cpp +++ b/playlistcollection.cpp @@ -1,994 +1,994 @@ /** * Copyright (C) 2004 Scott Wheeler * Copyright (C) 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlistcollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionlist.h" #include "actioncollection.h" #include "advancedsearchdialog.h" #include "coverinfo.h" #include "searchplaylist.h" #include "folderplaylist.h" #include "historyplaylist.h" #include "upcomingplaylist.h" #include "directorylist.h" #include "mediafiles.h" #include "playermanager.h" #include "tracksequencemanager.h" #include "juk.h" //Laurent: readd it //#include "collectionadaptor.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////// PlaylistCollection *PlaylistCollection::m_instance = 0; // Returns all folders in input list with their canonical path, if available, or // unchanged if not. static QStringList canonicalizeFolderPaths(const QStringList &folders) { QStringList result; foreach(const QString &folder, folders) { QString canonicalFolder = QDir(folder).canonicalPath(); result << (!canonicalFolder.isEmpty() ? canonicalFolder : folder); } return result; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// PlaylistCollection::PlaylistCollection(PlayerManager *player, QStackedWidget *playlistStack) : m_playlistStack(playlistStack), m_historyPlaylist(0), m_upcomingPlaylist(0), m_playerManager(player), m_importPlaylists(true), m_searchEnabled(true), m_playing(false), m_showMorePlaylist(0), m_belowShowMorePlaylist(0), m_dynamicPlaylist(0), m_belowDistraction(0), m_distraction(0) { //new CollectionAdaptor( this ); //QDBus::sessionBus().registerObject("/Collection",this ); m_instance = this; m_actionHandler = new ActionHandler(this); // KDirLister's auto error handling seems to crash JuK during startup in // readConfig(). m_dirLister.setAutoErrorHandlingEnabled(false, playlistStack); readConfig(); } PlaylistCollection::~PlaylistCollection() { saveConfig(); CollectionList::instance()->saveItemsToCache(); delete m_actionHandler; Playlist::setShuttingDown(); } QString PlaylistCollection::name() const { return currentPlaylist()->name(); } FileHandle PlaylistCollection::currentFile() const { return currentPlaylist()->currentFile(); } int PlaylistCollection::count() const { return currentPlaylist()->count(); } int PlaylistCollection::time() const { return currentPlaylist()->time(); } void PlaylistCollection::playFirst() { m_playing = true; currentPlaylist()->playFirst(); currentPlayingItemChanged(); } void PlaylistCollection::playNextAlbum() { m_playing = true; currentPlaylist()->playNextAlbum(); currentPlayingItemChanged(); } void PlaylistCollection::playPrevious() { m_playing = true; currentPlaylist()->playPrevious(); currentPlayingItemChanged(); } void PlaylistCollection::playNext() { m_playing = true; currentPlaylist()->playNext(); currentPlayingItemChanged(); } void PlaylistCollection::stop() { m_playing = false; currentPlaylist()->stop(); playlistItemsChanged(); } bool PlaylistCollection::playing() const { return m_playing; } QStringList PlaylistCollection::playlists() const { QStringList l; //(or qFindChildren() if you need MSVC 6 compatibility) const auto childList = m_playlistStack->findChildren("Playlist"); for(Playlist *p : childList) { l.append(p->name()); } return l; } void PlaylistCollection::createPlaylist(const QString &name) { raise(new Playlist(this, name)); } void PlaylistCollection::createDynamicPlaylist(const PlaylistList &playlists) { if(m_dynamicPlaylist) m_dynamicPlaylist->setPlaylists(playlists); else { m_dynamicPlaylist = new DynamicPlaylist(playlists, this, i18n("Dynamic List"), "audio-midi", false, true); PlaylistCollection::setupPlaylist(m_dynamicPlaylist, QString()); } PlaylistCollection::raise(m_dynamicPlaylist); } void PlaylistCollection::showMore(const QString &artist, const QString &album) { PlaylistList playlists; PlaylistSearch::ComponentList components; if(currentPlaylist() != CollectionList::instance() && currentPlaylist() != m_showMorePlaylist) { playlists.append(currentPlaylist()); } playlists.append(CollectionList::instance()); if(!artist.isNull()) { // Just setting off the artist stuff in its own block. ColumnList columns; columns.append(PlaylistItem::ArtistColumn); PlaylistSearch::Component c(artist, false, columns, PlaylistSearch::Component::Exact); components.append(c); } if(!album.isNull()) { ColumnList columns; columns.append(PlaylistItem::AlbumColumn); PlaylistSearch::Component c(album, false, columns, PlaylistSearch::Component::Exact); components.append(c); } PlaylistSearch search(playlists, components, PlaylistSearch::MatchAll); if(m_showMorePlaylist) m_showMorePlaylist->setPlaylistSearch(&search); else m_showMorePlaylist = new SearchPlaylist(this, search, i18n("Now Playing"), false, true); // The call to raise() below will end up clearing m_belowShowMorePlaylist, // so cache the value we want it to have now. Playlist *belowShowMore = visiblePlaylist(); PlaylistCollection::setupPlaylist(m_showMorePlaylist, QString()); PlaylistCollection::raise(m_showMorePlaylist); m_belowShowMorePlaylist = belowShowMore; } void PlaylistCollection::removeTrack(const QString &playlist, const QStringList &files) { Playlist *p = playlistByName(playlist); PlaylistItemList itemList; if(!p) return; QStringList::ConstIterator it; for(it = files.begin(); it != files.end(); ++it) { CollectionListItem *item = CollectionList::instance()->lookup(*it); if(item) { PlaylistItem *playlistItem = item->itemForPlaylist(p); if(playlistItem) itemList.append(playlistItem); } } p->clearItems(itemList); } QString PlaylistCollection::playlist() const { return visiblePlaylist() ? visiblePlaylist()->name() : QString(); } QString PlaylistCollection::playingPlaylist() const { return currentPlaylist() && m_playing ? currentPlaylist()->name() : QString(); } void PlaylistCollection::setPlaylist(const QString &playlist) { Playlist *p = playlistByName(playlist); if(p) raise(p); } QStringList PlaylistCollection::playlistTracks(const QString &playlist) const { Playlist *p = playlistByName(playlist); if(p) return p->files(); return QStringList(); } QString PlaylistCollection::trackProperty(const QString &file, const QString &property) const { CollectionList *l = CollectionList::instance(); CollectionListItem *item = l->lookup(file); return item ? item->file().property(property) : QString(); } QPixmap PlaylistCollection::trackCover(const QString &file, const QString &size) const { if(size.toLower() != "small" && size.toLower() != "large") return QPixmap(); CollectionList *l = CollectionList::instance(); CollectionListItem *item = l->lookup(file); if(!item) return QPixmap(); if(size.toLower() == "small") return item->file().coverInfo()->pixmap(CoverInfo::Thumbnail); else return item->file().coverInfo()->pixmap(CoverInfo::FullSize); } void PlaylistCollection::open(const QStringList &l) { QStringList files = l; if(files.isEmpty()) files = MediaFiles::openDialog(JuK::JuKInstance()); if(files.isEmpty()) return; bool justPlaylists = true; for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd() && justPlaylists; ++it) justPlaylists = !MediaFiles::isPlaylistFile(*it); if(visiblePlaylist() == CollectionList::instance() || justPlaylists || KMessageBox::questionYesNo( JuK::JuKInstance(), i18n("Do you want to add these items to the current list or to the collection list?"), QString(), KGuiItem(i18nc("current playlist", "Current")), KGuiItem(i18n("Collection"))) == KMessageBox::No) { CollectionList::instance()->addFiles(files); } else { visiblePlaylist()->addFiles(files); } playlistItemsChanged(); } void PlaylistCollection::open(const QString &playlist, const QStringList &files) { Playlist *p = playlistByName(playlist); if(p) p->addFiles(files); } void PlaylistCollection::addFolder() { DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance()); if(l.exec() == QDialog::Accepted) { m_dirLister.blockSignals(true); DirectoryList::Result result = l.dialogResult(); const bool reload = m_importPlaylists != result.addPlaylists; m_importPlaylists = result.addPlaylists; m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs); foreach(const QString &dir, result.addedDirs) { m_dirLister.openUrl(QUrl::fromLocalFile(dir), KDirLister::Keep); m_folderList.append(dir); } foreach(const QString &dir, result.removedDirs) { m_dirLister.stop(QUrl::fromLocalFile(dir)); m_folderList.removeAll(dir); } if(reload) { open(m_folderList); } else if(!result.addedDirs.isEmpty()) { open(result.addedDirs); } saveConfig(); m_dirLister.blockSignals(false); } } void PlaylistCollection::rename() { QString old = visiblePlaylist()->name(); QString name = playlistNameDialog(i18n("Rename"), old, false); m_playlistNames.remove(old); if(name.isEmpty()) return; visiblePlaylist()->setName(name); } void PlaylistCollection::duplicate() { QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"), visiblePlaylist()->name()); if(name.isEmpty()) return; raise(new Playlist(this, visiblePlaylist()->items(), name)); } void PlaylistCollection::save() { visiblePlaylist()->save(); } void PlaylistCollection::saveAs() { visiblePlaylist()->saveAs(); } void PlaylistCollection::reload() { if(visiblePlaylist() == CollectionList::instance()) CollectionList::instance()->addFiles(m_folderList); else visiblePlaylist()->slotReload(); } void PlaylistCollection::editSearch() { SearchPlaylist *p = dynamic_cast(visiblePlaylist()); if(!p) return; auto searchDialog = new AdvancedSearchDialog( p->name(), *(p->playlistSearch()), JuK::JuKInstance()); QObject::connect(searchDialog, &QDialog::finished, [searchDialog, p](int result) { if (result) { p->setPlaylistSearch(searchDialog->resultSearch()); p->setName(searchDialog->resultPlaylistName()); } searchDialog->deleteLater(); }); searchDialog->exec(); } void PlaylistCollection::removeItems() { visiblePlaylist()->slotRemoveSelectedItems(); } void PlaylistCollection::refreshItems() { visiblePlaylist()->slotRefresh(); } void PlaylistCollection::renameItems() { visiblePlaylist()->slotRenameFile(); } void PlaylistCollection::addCovers(bool fromFile) { visiblePlaylist()->slotAddCover(fromFile); playlistItemsChanged(); } void PlaylistCollection::removeCovers() { visiblePlaylist()->slotRemoveCover(); playlistItemsChanged(); } void PlaylistCollection::viewCovers() { visiblePlaylist()->slotViewCover(); } void PlaylistCollection::showCoverManager() { visiblePlaylist()->slotShowCoverManager(); } PlaylistItemList PlaylistCollection::selectedItems() { return visiblePlaylist()->selectedItems(); } void PlaylistCollection::scanFolders() { // If no music folder was configured, open music folder dialog if(m_folderList.count() == 0) addFolder(); CollectionList::instance()->addFiles(m_folderList); enableDirWatch(true); } void PlaylistCollection::createPlaylist() { QString name = playlistNameDialog(); if(!name.isEmpty()) raise(new Playlist(this, name)); } void PlaylistCollection::createSearchPlaylist() { QString name = uniquePlaylistName(i18n("Search Playlist")); auto searchDialog = new AdvancedSearchDialog( - name, PlaylistSearch(), JuK::JuKInstance()); + name, *(new PlaylistSearch(JuK::JuKInstance())), JuK::JuKInstance()); QObject::connect(searchDialog, &QDialog::finished, [searchDialog, this](int result) { if (result) { raise(new SearchPlaylist( this, *searchDialog->resultSearch(), searchDialog->resultPlaylistName())); } searchDialog->deleteLater(); }); searchDialog->exec(); } void PlaylistCollection::createFolderPlaylist() { QString folder = QFileDialog::getExistingDirectory(); if(folder.isEmpty()) return; QString name = uniquePlaylistName(folder.mid(folder.lastIndexOf('/') + 1)); name = playlistNameDialog(i18n("Create Folder Playlist"), name); if(!name.isEmpty()) raise(new FolderPlaylist(this, folder, name)); } void PlaylistCollection::guessTagFromFile() { visiblePlaylist()->slotGuessTagInfo(TagGuesser::FileName); } void PlaylistCollection::guessTagFromInternet() { visiblePlaylist()->slotGuessTagInfo(TagGuesser::MusicBrainz); } void PlaylistCollection::setSearchEnabled(bool enable) { if(enable == m_searchEnabled) return; m_searchEnabled = enable; visiblePlaylist()->setSearchEnabled(enable); } HistoryPlaylist *PlaylistCollection::historyPlaylist() const { return m_historyPlaylist; } void PlaylistCollection::setHistoryPlaylistEnabled(bool enable) { if((enable && m_historyPlaylist) || (!enable && !m_historyPlaylist)) return; if(enable) { action("showHistory")->setChecked(true); m_historyPlaylist = new HistoryPlaylist(this); m_historyPlaylist->setName(i18n("History")); setupPlaylist(m_historyPlaylist, "view-history"); QObject::connect(m_playerManager, SIGNAL(signalItemChanged(FileHandle)), historyPlaylist(), SLOT(appendProposedItem(FileHandle))); } else { delete m_historyPlaylist; m_historyPlaylist = 0; } } UpcomingPlaylist *PlaylistCollection::upcomingPlaylist() const { return m_upcomingPlaylist; } void PlaylistCollection::setUpcomingPlaylistEnabled(bool enable) { if((enable && m_upcomingPlaylist) || (!enable && !m_upcomingPlaylist)) return; if(enable) { action("showUpcoming")->setChecked(true); if(!m_upcomingPlaylist) m_upcomingPlaylist = new UpcomingPlaylist(this); setupPlaylist(m_upcomingPlaylist, "go-jump-today"); } else { action("showUpcoming")->setChecked(false); bool raiseCollection = visiblePlaylist() == m_upcomingPlaylist; if(raiseCollection) { raise(CollectionList::instance()); } m_upcomingPlaylist->deleteLater(); m_upcomingPlaylist = 0; } } QObject *PlaylistCollection::object() const { return m_actionHandler; } Playlist *PlaylistCollection::currentPlaylist() const { if(m_belowDistraction) return m_belowDistraction; if(m_upcomingPlaylist && m_upcomingPlaylist->active()) return m_upcomingPlaylist; if(Playlist::playingItem()) return Playlist::playingItem()->playlist(); else return visiblePlaylist(); } Playlist *PlaylistCollection::visiblePlaylist() const { return qobject_cast(m_playlistStack->currentWidget()); } void PlaylistCollection::raise(Playlist *playlist) { if(m_showMorePlaylist && currentPlaylist() == m_showMorePlaylist) m_showMorePlaylist->lower(playlist); if(m_dynamicPlaylist && currentPlaylist() == m_dynamicPlaylist) m_dynamicPlaylist->lower(playlist); TrackSequenceManager::instance()->setCurrentPlaylist(playlist); playlist->applySharedSettings(); playlist->setSearchEnabled(m_searchEnabled); m_playlistStack->setCurrentWidget(playlist); clearShowMore(false); playlistItemsChanged(); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// QStackedWidget *PlaylistCollection::playlistStack() const { return m_playlistStack; } void PlaylistCollection::setupPlaylist(Playlist *playlist, const QString &) { if(!playlist->fileName().isEmpty()) m_playlistFiles.insert(playlist->fileName()); if(!playlist->name().isEmpty()) m_playlistNames.insert(playlist->name()); m_playlistStack->addWidget(playlist); QObject::connect(playlist, SIGNAL(itemSelectionChanged()), object(), SIGNAL(signalSelectedItemsChanged())); } bool PlaylistCollection::importPlaylists() const { return m_importPlaylists; } bool PlaylistCollection::containsPlaylistFile(const QString &file) const { return m_playlistFiles.contains(file); } bool PlaylistCollection::showMoreActive() const { return visiblePlaylist() == m_showMorePlaylist; } void PlaylistCollection::clearShowMore(bool raisePlaylist) { if(!m_showMorePlaylist) return; if(raisePlaylist) { if(m_belowShowMorePlaylist) raise(m_belowShowMorePlaylist); else raise(CollectionList::instance()); } m_belowShowMorePlaylist = 0; } void PlaylistCollection::enableDirWatch(bool enable) { auto collection = CollectionList::instance(); m_dirLister.disconnect(object()); if(enable) { QObject::connect(&m_dirLister, &KDirLister::newItems, object(), [this](const KFileItemList &items) { this->newItems(items); }); QObject::connect(&m_dirLister, &KDirLister::refreshItems, collection, &CollectionList::slotRefreshItems); QObject::connect(&m_dirLister, &KDirLister::itemsDeleted, collection, &CollectionList::slotDeleteItems); } } QString PlaylistCollection::playlistNameDialog(const QString &caption, const QString &suggest, bool forceUnique) const { bool ok; QString name = QInputDialog::getText( m_playlistStack, caption, i18n("Please enter a name for this playlist:"), QLineEdit::Normal, forceUnique ? uniquePlaylistName(suggest) : suggest, &ok); return ok ? uniquePlaylistName(name) : QString(); } QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const { if(suggest.isEmpty()) return uniquePlaylistName(); if(!m_playlistNames.contains(suggest)) return suggest; QString base = suggest; base.remove(QRegExp("\\s\\([0-9]+\\)$")); int count = 1; QString s = QString("%1 (%2)").arg(base).arg(count); while(m_playlistNames.contains(s)) { count++; s = QString("%1 (%2)").arg(base).arg(count); } return s; } void PlaylistCollection::addNameToDict(const QString &name) { m_playlistNames.insert(name); } void PlaylistCollection::addFileToDict(const QString &file) { m_playlistFiles.insert(file); } void PlaylistCollection::removeNameFromDict(const QString &name) { m_playlistNames.remove(name); } void PlaylistCollection::removeFileFromDict(const QString &file) { m_playlistFiles.remove(file); } void PlaylistCollection::dirChanged(const QString &path) { QString canonicalPath = QDir(path).canonicalPath(); if(canonicalPath.isEmpty()) return; foreach(const QString &excludedFolder, m_excludedFolderList) { if(canonicalPath.startsWith(excludedFolder)) return; } CollectionList::instance()->addFiles(QStringList(canonicalPath)); } Playlist *PlaylistCollection::playlistByName(const QString &name) const { for(int i = 0; i < m_playlistStack->count(); ++i) { Playlist *p = qobject_cast(m_playlistStack->widget(i)); if(p && p->name() == name) return p; } return 0; } void PlaylistCollection::newItems(const KFileItemList &list) const { // Make fast-path for the normal case if(m_excludedFolderList.isEmpty()) { CollectionList::instance()->slotNewItems(list); return; } // Slow case: Directories to exclude from consideration KFileItemList filteredList(list); foreach(const QString &excludedFolder, m_excludedFolderList) { QMutableListIterator filteredListIterator(filteredList); while(filteredListIterator.hasNext()) { const KFileItem fileItem = filteredListIterator.next(); if(fileItem.url().path().startsWith(excludedFolder)) filteredListIterator.remove(); } } CollectionList::instance()->slotNewItems(filteredList); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistCollection::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); m_importPlaylists = config.readEntry("ImportPlaylists", true); m_folderList = config.readEntry("DirectoryList", QStringList()); m_excludedFolderList = canonicalizeFolderPaths( config.readEntry("ExcludeDirectoryList", QStringList())); for(const auto &folder : m_folderList) { m_dirLister.openUrl(QUrl::fromUserInput(folder), KDirLister::Keep); } } void PlaylistCollection::saveConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); config.writeEntry("ImportPlaylists", m_importPlaylists); config.writeEntry("showUpcoming", action("showUpcoming")->isChecked()); config.writePathEntry("DirectoryList", m_folderList); config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList); config.sync(); } //////////////////////////////////////////////////////////////////////////////// // ActionHandler implementation //////////////////////////////////////////////////////////////////////////////// PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) : QObject(nullptr), m_collection(collection) { setObjectName( QLatin1String("ActionHandler" )); KActionMenu *menu; // "New" menu menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("new playlist", "&New"), actions()); actions()->addAction("file_new", menu); menu->addAction(createAction(i18n("&Empty Playlist..."), SLOT(slotCreatePlaylist()), "newPlaylist", "window-new", QKeySequence(Qt::CTRL + Qt::Key_N))); menu->addAction(createAction(i18n("&Search Playlist..."), SLOT(slotCreateSearchPlaylist()), "newSearchPlaylist", "edit-find", QKeySequence(Qt::CTRL + Qt::Key_F))); menu->addAction(createAction(i18n("Playlist From &Folder..."), SLOT(slotCreateFolderPlaylist()), "newDirectoryPlaylist", "document-open", QKeySequence(Qt::CTRL + Qt::Key_D))); // Guess tag info menu #if HAVE_TUNEPIMP menu = new KActionMenu(i18n("&Guess Tag Information"), actions()); actions()->addAction("guessTag", menu); menu->setIcon(QIcon::fromTheme("wizard")); menu->addAction(createAction(i18n("From &File Name"), SLOT(slotGuessTagFromFile()), "guessTagFile", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G))); menu->addAction(createAction(i18n("From &Internet"), SLOT(slotGuessTagFromInternet()), "guessTagInternet", "network-server", QKeySequence(Qt::CTRL + Qt::Key_I))); #else createAction(i18n("Guess Tag Information From &File Name"), SLOT(slotGuessTagFromFile()), "guessTag", "document-import", QKeySequence(Qt::CTRL + Qt::Key_G)); #endif createAction(i18n("Play First Track"),SLOT(slotPlayFirst()), "playFirst"); createAction(i18n("Play Next Album"), SLOT(slotPlayNextAlbum()), "forwardAlbum", "go-down-search"); KStandardAction::open(this, SLOT(slotOpen()), actions()); KStandardAction::save(this, SLOT(slotSave()), actions()); KStandardAction::saveAs(this, SLOT(slotSaveAs()), actions()); createAction(i18n("Manage &Folders..."), SLOT(slotManageFolders()), "openDirectory", "folder-new"); createAction(i18n("&Rename..."), SLOT(slotRename()), "renamePlaylist", "edit-rename"); createAction(i18nc("verb, copy the playlist", "D&uplicate..."), SLOT(slotDuplicate()), "duplicatePlaylist", "edit-copy"); createAction(i18n("R&emove"), SLOT(slotRemove()), "deleteItemPlaylist", "user-trash"); createAction(i18n("Reload"), SLOT(slotReload()), "reloadPlaylist", "view-refresh"); createAction(i18n("Edit Search..."), SLOT(slotEditSearch()), "editSearch"); createAction(i18n("&Delete"), SLOT(slotRemoveItems()), "removeItem", "edit-delete"); createAction(i18n("Refresh"), SLOT(slotRefreshItems()), "refresh", "view-refresh"); createAction(i18n("&Rename File"), SLOT(slotRenameItems()), "renameFile", "document-save-as", QKeySequence(Qt::CTRL + Qt::Key_R)); menu = new KActionMenu(i18n("Cover Manager"), actions()); actions()->addAction("coverManager", menu); menu->setIcon(QIcon::fromTheme("image-x-generic")); menu->addAction(createAction(i18n("&View Cover"), SLOT(slotViewCovers()), "viewCover", "document-preview")); menu->addAction(createAction(i18n("Get Cover From &File..."), SLOT(slotAddLocalCover()), "addCover", "document-import", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F))); menu->addAction(createAction(i18n("Get Cover From &Internet..."), SLOT(slotAddInternetCover()), "webImageCover", "network-server", QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G))); menu->addAction(createAction(i18n("&Delete Cover"), SLOT(slotRemoveCovers()), "removeCover", "edit-delete")); menu->addAction(createAction(i18n("Show Cover &Manager"), SLOT(slotShowCoverManager()), "showCoverManager")); KToggleAction *upcomingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18n("Show &Play Queue"), actions()); actions()->addAction("showUpcoming", upcomingAction); connect(upcomingAction, SIGNAL(triggered(bool)), this, SLOT(slotSetUpcomingPlaylistEnabled(bool))); connect(m_collection->m_playerManager, &PlayerManager::signalStop, this, [this]() { m_collection->stop(); }); } QAction *PlaylistCollection::ActionHandler::createAction(const QString &text, const char *slot, const char *name, const QString &icon, const QKeySequence &shortcut) { auto actionCollection = actions(); QAction *action = new QAction(text, actions()); if(!icon.isEmpty()) { action->setIcon(QIcon::fromTheme(icon)); } connect(action, SIGNAL(triggered(bool)), slot); actionCollection->addAction(name, action); if (!shortcut.isEmpty()) { actionCollection->setDefaultShortcut(action, shortcut); } return action; } // vim: set et sw=4 tw=0 sta: diff --git a/playlistsearch.cpp b/playlistsearch.cpp index d353c745..64d68b05 100644 --- a/playlistsearch.cpp +++ b/playlistsearch.cpp @@ -1,299 +1,297 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) #include #else #include typedef KConcatenateRowsProxyModel QConcatenateTablesProxyModel; #endif #include "playlistsearch.h" #include "playlist.h" #include "playlistitem.h" #include "collectionlist.h" #include "juk-exception.h" #include "juk_debug.h" //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// PlaylistSearch::PlaylistSearch(QObject* parent) : QSortFilterProxyModel(parent), m_mode(MatchAny) { } PlaylistSearch::PlaylistSearch(const PlaylistList &playlists, const ComponentList &components, SearchMode mode, QObject* parent) : QSortFilterProxyModel(parent), m_playlists(playlists), m_components(components), m_mode(mode) { QConcatenateTablesProxyModel* const model = new QConcatenateTablesProxyModel(this); for(Playlist* playlist : playlists) model->addSourceModel(playlist->model()); setSourceModel(model); } bool PlaylistSearch::checkItem(QModelIndex *item) { return mapFromSource(static_cast(sourceModel())->mapFromSource(*item)).isValid(); } QModelIndexList PlaylistSearch::matchedItems() const{ QModelIndexList res; for(int row = 0; row < rowCount(); ++row) res.append(mapToSource(index(row, 0))); return res; } void PlaylistSearch::addPlaylist(Playlist* p) { static_cast(sourceModel())->addSourceModel(p->model()); m_playlists.append(p); } void PlaylistSearch::clearPlaylists() { - beginResetModel(); setSourceModel(new QConcatenateTablesProxyModel(this)); - endResetModel(); m_playlists.clear(); } void PlaylistSearch::addComponent(const Component &c) { m_components.append(c); invalidateFilter(); } void PlaylistSearch::clearComponents() { m_components.clear(); invalidateFilter(); } PlaylistSearch::ComponentList PlaylistSearch::components() const { return m_components; } bool PlaylistSearch::isNull() const { return m_components.isEmpty(); } bool PlaylistSearch::isEmpty() const { if(isNull()) return true; ComponentList::ConstIterator it = m_components.begin(); for(; it != m_components.end(); ++it) { if(!(*it).query().isEmpty() || !(*it).pattern().isEmpty()) return false; } return true; } bool PlaylistSearch::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const{ QAbstractItemModel* const model = sourceModel(); auto matcher = [&](Component c){ return c.matches(source_row, source_parent, model); }; return m_mode == MatchAny? std::any_of(m_components.begin(), m_components.end(), matcher) : std::all_of(m_components.begin(), m_components.end(), matcher); } //////////////////////////////////////////////////////////////////////////////// // Component public methods //////////////////////////////////////////////////////////////////////////////// PlaylistSearch::Component::Component() : m_mode(Contains), m_searchAllVisible(true), m_caseSensitive(false) { } PlaylistSearch::Component::Component(const QString &query, bool caseSensitive, const ColumnList &columns, MatchMode mode) : m_query(query), m_columns(columns), m_mode(mode), m_searchAllVisible(columns.isEmpty()), m_caseSensitive(caseSensitive), m_re(false) { } PlaylistSearch::Component::Component(const QRegExp &query, const ColumnList& columns) : m_queryRe(query), m_columns(columns), m_mode(Exact), m_searchAllVisible(columns.isEmpty()), m_caseSensitive(false), m_re(true) { } bool PlaylistSearch::Component::matches(int row, QModelIndex parent, QAbstractItemModel* model) const { for(int column : m_columns){ const QString str = model->index(row, column, parent).data().toString(); if(m_re){ return str.contains(m_queryRe); } switch(m_mode) { case Contains: if(str.contains(m_query, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)) return true; break; case Exact: if(str.length() == m_query.length()) { if(m_caseSensitive) { if(str == m_query) return true; } else if(str.toLower() == m_query.toLower()) return true; } break; case ContainsWord: { int i = str.indexOf(m_query, 0, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive ); if(i >= 0) { // If we found the pattern and the lengths are the same, then // this is a match. if(str.length() == m_query.length()) return true; // First: If the match starts at the beginning of the text or the // character before the match is not a word character // AND // Second: Either the pattern was found at the end of the text, // or the text following the match is a non-word character // ...then we have a match if((i == 0 || !str.at(i - 1).isLetterOrNumber()) && (i + m_query.length() == str.length() || !str.at(i + m_query.length()).isLetterOrNumber())) return true; } } } }; return false; } bool PlaylistSearch::Component::operator==(const Component &v) const { return m_query == v.m_query && m_queryRe == v.m_queryRe && m_columns == v.m_columns && m_mode == v.m_mode && m_searchAllVisible == v.m_searchAllVisible && m_caseSensitive == v.m_caseSensitive && m_re == v.m_re; } //////////////////////////////////////////////////////////////////////////////// // helper functions //////////////////////////////////////////////////////////////////////////////// QDataStream &operator<<(QDataStream &s, const PlaylistSearch &search) { s << search.components() << qint32(search.searchMode()); return s; } QDataStream &operator>>(QDataStream &s, PlaylistSearch &search) { search.clearPlaylists(); search.addPlaylist(CollectionList::instance()); search.clearComponents(); PlaylistSearch::ComponentList components; s >> components; PlaylistSearch::ComponentList::ConstIterator it = components.constBegin(); for(; it != components.constEnd(); ++it) search.addComponent(*it); qint32 mode; s >> mode; search.setSearchMode(PlaylistSearch::SearchMode(mode)); return s; } QDataStream &operator<<(QDataStream &s, const PlaylistSearch::Component &c) { s << c.isPatternSearch() << (c.isPatternSearch() ? c.pattern().pattern() : c.query()) << c.isCaseSensitive() << c.columns() << qint32(c.matchMode()); return s; } QDataStream &operator>>(QDataStream &s, PlaylistSearch::Component &c) { bool patternSearch; QString pattern; bool caseSensitive; ColumnList columns; qint32 mode; s >> patternSearch >> pattern >> caseSensitive >> columns >> mode; if(patternSearch) c = PlaylistSearch::Component(QRegExp(pattern), columns); else c = PlaylistSearch::Component(pattern, caseSensitive, columns, PlaylistSearch::Component::MatchMode(mode)); return s; } // vim: set et sw=4 tw=0 sta: diff --git a/searchplaylist.cpp b/searchplaylist.cpp index 9197aecd..3ff4be3f 100644 --- a/searchplaylist.cpp +++ b/searchplaylist.cpp @@ -1,99 +1,99 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "searchplaylist.h" #include "juk-exception.h" #include #include "playlistitem.h" #include "collectionlist.h" #include "juk_debug.h" //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// SearchPlaylist::SearchPlaylist(PlaylistCollection *collection, - const PlaylistSearch& search, + PlaylistSearch& search, const QString &name, bool setupPlaylist, bool synchronizePlaying) : DynamicPlaylist(search.playlists(), collection, name, "edit-find", setupPlaylist, synchronizePlaying), m_search(&search) { } -void SearchPlaylist::setPlaylistSearch(const PlaylistSearch* s, bool update) +void SearchPlaylist::setPlaylistSearch(PlaylistSearch* s, bool update) { m_search = s; if(update) setPlaylists(s->playlists()); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// void SearchPlaylist::updateItems() { // Here we don't simply use "clear" since that would involve a call to // items() which would in turn call this method... PlaylistItemList items; for(const QModelIndex index: m_search->matchedItems()) items.push_back(static_cast(itemFromIndex(index))); synchronizeItemsTo(items); if(synchronizePlaying()) { qCDebug(JUK_LOG) << "synchronizing playing"; synchronizePlayingItems(m_search->playlists(), true); } } //////////////////////////////////////////////////////////////////////////////// // helper functions //////////////////////////////////////////////////////////////////////////////// QDataStream &operator<<(QDataStream &s, const SearchPlaylist &p) { s << p.name() << p.playlistSearch(); return s; } QDataStream &operator>>(QDataStream &s, SearchPlaylist &p) { QString name; PlaylistSearch search; s >> name >> search; if(name.isEmpty()) throw BICStreamException(); p.setName(name); p.setPlaylistSearch(&search, false); return s; } // vim: set et sw=4 tw=0 sta: diff --git a/searchplaylist.h b/searchplaylist.h index 56b3e25c..c55521ca 100644 --- a/searchplaylist.h +++ b/searchplaylist.h @@ -1,51 +1,51 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef SEARCHPLAYLIST_H #define SEARCHPLAYLIST_H #include "dynamicplaylist.h" class SearchPlaylist : public DynamicPlaylist { Q_OBJECT public: explicit SearchPlaylist(PlaylistCollection *collection, - const PlaylistSearch& search = PlaylistSearch(), + PlaylistSearch& search, const QString &name = QString(), bool setupPlaylist = true, bool synchronizePlaying = false); - const PlaylistSearch* playlistSearch() const { return m_search; } - void setPlaylistSearch ( const PlaylistSearch* s, bool update = true ); + PlaylistSearch* playlistSearch() const { return m_search; } + void setPlaylistSearch ( PlaylistSearch* s, bool update = true ); virtual bool searchIsEditable() const override { return true; } protected: /** * Runs the search to update the current items. */ virtual void updateItems() override; private: - const PlaylistSearch* m_search; + PlaylistSearch* m_search; }; QDataStream &operator<<(QDataStream &s, const SearchPlaylist &p); QDataStream &operator>>(QDataStream &s, SearchPlaylist &p); #endif // vim: set et sw=4 tw=0 sta: diff --git a/treeviewitemplaylist.cpp b/treeviewitemplaylist.cpp index 45c25e02..a0bac41e 100644 --- a/treeviewitemplaylist.cpp +++ b/treeviewitemplaylist.cpp @@ -1,94 +1,94 @@ /** * Copyright (C) 2004 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "treeviewitemplaylist.h" #include #include #include #include "collectionlist.h" #include "juktag.h" #include "playlistitem.h" #include "playlistsearch.h" #include "tagtransactionmanager.h" #include "juk_debug.h" TreeViewItemPlaylist::TreeViewItemPlaylist(PlaylistCollection *collection, - const PlaylistSearch &search, + PlaylistSearch &search, const QString &name) : SearchPlaylist(collection, search, name, false) { PlaylistSearch::Component component = *(search.components().begin()); m_columnType = static_cast(*(component.columns().begin())); } void TreeViewItemPlaylist::retag(const QStringList &files, Playlist *) { CollectionList *collection = CollectionList::instance(); if(files.isEmpty()) return; QString changedTag = i18n("artist"); if(m_columnType == PlaylistItem::GenreColumn) changedTag = i18n("genre"); else if(m_columnType == PlaylistItem::AlbumColumn) changedTag = i18n("album"); if(KMessageBox::warningContinueCancelList( this, i18n("You are about to change the %1 on these files.", changedTag), files, i18n("Changing Track Tags"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "dragDropRetagWarn" ) == KMessageBox::Cancel) { return; } QStringList::ConstIterator it; for(it = files.begin(); it != files.end(); ++it) { CollectionListItem *item = collection->lookup(*it); if(!item) continue; Tag *tag = TagTransactionManager::duplicateTag(item->file().tag()); switch(m_columnType) { case PlaylistItem::ArtistColumn: tag->setArtist(name()); break; case PlaylistItem::AlbumColumn: tag->setAlbum(name()); break; case PlaylistItem::GenreColumn: tag->setGenre(name()); break; default: qCDebug(JUK_LOG) << "Unhandled column type editing " << *it; } TagTransactionManager::instance()->changeTagOnItem(item, tag); } } // vim: set et sw=4 tw=0 sta: diff --git a/treeviewitemplaylist.h b/treeviewitemplaylist.h index 947c727f..5bf52e21 100644 --- a/treeviewitemplaylist.h +++ b/treeviewitemplaylist.h @@ -1,46 +1,46 @@ /** * Copyright (C) 2004 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef TREEVIEWITEMPLAYLIST_H #define TREEVIEWITEMPLAYLIST_H #include "searchplaylist.h" #include "playlistitem.h" class QStringList; class TreeViewItemPlaylist : public SearchPlaylist { Q_OBJECT public: explicit TreeViewItemPlaylist(PlaylistCollection *collection, - const PlaylistSearch &search = PlaylistSearch(), + PlaylistSearch &search, const QString &name = QString()); virtual bool searchIsEditable() const override { return false; } void retag(const QStringList &files, Playlist *donorPlaylist); signals: void signalTagsChanged(); private: PlaylistItem::ColumnType m_columnType; }; #endif // TREEVIEWITEMPLAYLIST_H // vim: set et sw=4 tw=0 sta: