diff --git a/collectionlist.cpp b/collectionlist.cpp index 6a56dd8e..be66f078 100644 --- a/collectionlist.cpp +++ b/collectionlist.cpp @@ -1,597 +1,599 @@ /** * Copyright (C) 2002-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 "collectionlist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlistcollection.h" #include "stringshare.h" #include "cache.h" #include "actioncollection.h" #include "tag.h" #include "viewmode.h" #include "juk_debug.h" using ActionCollection::action; //////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////// CollectionList *CollectionList::m_list = 0; CollectionList *CollectionList::instance() { return m_list; } static QTime stopwatch; void CollectionList::startLoadingCachedItems() { if(!m_list) return; qCDebug(JUK_LOG) << "Starting to load cached items"; stopwatch.start(); if(!Cache::instance()->prepareToLoadCachedItems()) { qCCritical(JUK_LOG) << "Unable to setup to load cache... perhaps it doesn't exist?"; completedLoadingCachedItems(); return; } qCDebug(JUK_LOG) << "Kicked off first batch"; QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } void CollectionList::loadNextBatchCachedItems() { Cache *cache = Cache::instance(); bool done = false; for(int i = 0; i < 20; ++i) { FileHandle cachedItem(cache->loadNextCachedItem()); if(cachedItem.isNull()) { done = true; break; } // This may have already been created via a loaded playlist. if(!m_itemsDict.contains(cachedItem.absFilePath())) { CollectionListItem *newItem = new CollectionListItem(this, cachedItem); setupItem(newItem); } } if(!done) { QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } else { completedLoadingCachedItems(); } } void CollectionList::completedLoadingCachedItems() { // The CollectionList is created with sorting disabled for speed. Re-enable // it here, and perform the sort. KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); Qt::SortOrder order = Qt::DescendingOrder; if(config.readEntry("CollectionListSortAscending", true)) order = Qt::AscendingOrder; m_list->sortByColumn(config.readEntry("CollectionListSortColumn", 1), order); qCDebug(JUK_LOG) << "Finished loading cached items, took" << stopwatch.elapsed() << "ms"; qCDebug(JUK_LOG) << m_itemsDict.size() << "items are in the CollectionList"; emit cachedItemsLoaded(); } void CollectionList::initialize(PlaylistCollection *collection) { if(m_list) return; // We have to delay initialization here because dynamic_cast or comparing to // the collection instance won't work in the PlaylistBox::Item initialization // won't work until the CollectionList is fully constructed. m_list = new CollectionList(collection); m_list->setName(i18n("Collection List")); collection->setupPlaylist(m_list, "folder-sound"); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem *CollectionList::createItem(const FileHandle &file, QTreeWidgetItem *, bool) { // It's probably possible to optimize the line below away, but, well, right // now it's more important to not load duplicate items. if(m_itemsDict.contains(file.absFilePath())) return 0; CollectionListItem *item = new CollectionListItem(this, file); if(!item->isValid()) { qCCritical(JUK_LOG) << "CollectionList::createItem() -- A valid tag was not created for \"" << file.absFilePath() << "\""; delete item; return 0; } setupItem(item); return item; } void CollectionList::clearItems(const PlaylistItemList &items) { foreach(PlaylistItem *item, items) { delete item; } dataChanged(); } void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const { TreeViewMode *treeViewMode = dynamic_cast(viewMode); if(!treeViewMode) { qCWarning(JUK_LOG) << "Can't setup entries on a non-tree-view mode!\n"; return; } QList columnList; columnList << PlaylistItem::ArtistColumn; columnList << PlaylistItem::GenreColumn; columnList << PlaylistItem::AlbumColumn; foreach(int column, columnList) treeViewMode->addItems(m_columnTags[column]->keys(), column); } void CollectionList::slotNewItems(const KFileItemList &items) { QStringList files; for(KFileItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) files.append((*it).url().path()); addFiles(files); update(); } void CollectionList::slotRefreshItems(const QList > &items) { for(int i = 0; i < items.count(); ++i) { const KFileItem fileItem = items[i].second; CollectionListItem *item = lookup(fileItem.url().path()); if(item) { item->refreshFromDisk(); // If the item is no longer on disk, remove it from the collection. if(item->file().fileInfo().exists()) item->repaint(); else delete item; } } update(); } -void CollectionList::slotDeleteItem(const KFileItem &item) +void CollectionList::slotDeleteItems(const KFileItemList &items) { - delete lookup(item.url().path()); + for(const auto &item : items) { + delete lookup(item.url().path()); + } } void CollectionList::saveItemsToCache() const { qCDebug(JUK_LOG) << "Saving collection list to cache"; QSaveFile f(Cache::fileHandleCacheFileName()); if(!f.open(QIODevice::WriteOnly)) { qCCritical(JUK_LOG) << "Error saving cache:" << f.errorString(); return; } QByteArray data; QDataStream s(&data, QIODevice::WriteOnly); s.setVersion(QDataStream::Qt_4_3); QHash::const_iterator it; for(it = m_itemsDict.begin(); it != m_itemsDict.end(); ++it) { s << it.key(); s << (*it)->file(); } QDataStream fs(&f); qint32 checksum = qChecksum(data.data(), data.size()); fs << qint32(Cache::playlistItemsCacheVersion) << checksum << data; if(!f.commit()) qCCritical(JUK_LOG) << "Error saving cache:" << f.errorString(); } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void CollectionList::paste() { decode(QApplication::clipboard()->mimeData()); } void CollectionList::clear() { int result = KMessageBox::warningContinueCancel(this, i18n("Removing an item from the collection will also remove it from " "all of your playlists. Are you sure you want to continue?\n\n" "Note, however, that if the directory that these files are in is in " "your \"scan on startup\" list, they will be readded on startup.")); if(result == KMessageBox::Continue) { Playlist::clear(); emit signalCollectionChanged(); } } void CollectionList::slotCheckCache() { PlaylistItemList invalidItems; qCDebug(JUK_LOG) << "Starting to check cached items for consistency"; stopwatch.start(); int i = 0; foreach(CollectionListItem *item, m_itemsDict) { if(!item->checkCurrent()) invalidItems.append(item); if(++i == (m_itemsDict.size() / 2)) qCDebug(JUK_LOG) << "Checkpoint"; } clearItems(invalidItems); qCDebug(JUK_LOG) << "Finished consistency check, took" << stopwatch.elapsed() << "ms"; } void CollectionList::slotRemoveItem(const QString &file) { delete m_itemsDict[file]; } void CollectionList::slotRefreshItem(const QString &file) { if(m_itemsDict[file]) m_itemsDict[file]->refresh(); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// CollectionList::CollectionList(PlaylistCollection *collection) : Playlist(collection, true), m_columnTags(15, 0) { QAction *spaction = ActionCollection::actions()->addAction("showPlaying"); spaction->setText(i18n("Show Playing")); connect(spaction, SIGNAL(triggered(bool)), SLOT(slotShowPlaying())); connect(action("back")->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPopulateBackMenu())); connect(action("back")->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotPlayFromBackMenu(QAction*))); setSortingEnabled(false); // Temporarily disable sorting to add items faster. m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict; m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict; m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict; // Even set to true it wouldn't work with this class due to other checks setAllowDuplicates(false); } CollectionList::~CollectionList() { KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); config.writeEntry("CollectionListSortColumn", header()->sortIndicatorSection()); config.writeEntry("CollectionListSortAscending", header()->sortIndicatorOrder() == Qt::AscendingOrder); // In some situations the dataChanged signal from clearItems will cause observers to // subsequently try to access a deleted item. Since we're going away just remove all // observers. clearObservers(); // The CollectionListItems will try to remove themselves from the // m_columnTags member, so we must make sure they're gone before we // are. clearItems(items()); qDeleteAll(m_columnTags); m_columnTags.clear(); } void CollectionList::dropEvent(QDropEvent *e) { if(e->source() == this) return; // Don't rearrange in the CollectionList. else Playlist::dropEvent(e); } void CollectionList::dragMoveEvent(QDragMoveEvent *e) { if(e->source() != this) Playlist::dragMoveEvent(e); else e->setAccepted(false); } QString CollectionList::addStringToDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return QString(); if(m_columnTags[column]->contains(value)) ++((*m_columnTags[column])[value]); else { m_columnTags[column]->insert(value, 1); emit signalNewTag(value, column); } return value; } QStringList CollectionList::uniqueSet(UniqueSetType t) const { int column; switch(t) { case Artists: column = PlaylistItem::ArtistColumn; break; case Albums: column = PlaylistItem::AlbumColumn; break; case Genres: column = PlaylistItem::GenreColumn; break; default: return QStringList(); } return m_columnTags[column]->keys(); } CollectionListItem *CollectionList::lookup(const QString &file) const { - return m_itemsDict.value(file, 0); + return m_itemsDict.value(file, nullptr); } void CollectionList::removeStringFromDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return; if(m_columnTags[column]->contains(value) && --((*m_columnTags[column])[value])) // If the decrement goes to 0... { emit signalRemovedTag(value, column); m_columnTags[column]->remove(value); } } void CollectionList::addWatched(const QString &file) { m_dirWatch->addFile(file); } void CollectionList::removeWatched(const QString &file) { m_dirWatch->removeFile(file); } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem public methods //////////////////////////////////////////////////////////////////////////////// void CollectionListItem::refresh() { int offset = CollectionList::instance()->columnOffset(); int columns = lastColumn() + offset + 1; sharedData()->metadata.resize(columns); sharedData()->cachedWidths.resize(columns); for(int i = offset; i < columns; i++) { setText(i, text(i)); int id = i - offset; if(id != TrackNumberColumn && id != LengthColumn) { // All columns other than track num and length need local-encoded data for sorting QString toLower = text(i).toLower(); // For some columns, we may be able to share some strings if((id == ArtistColumn) || (id == AlbumColumn) || (id == GenreColumn) || (id == YearColumn) || (id == CommentColumn)) { toLower = StringShare::tryShare(toLower); if(id != YearColumn && id != CommentColumn && sharedData()->metadata[id] != toLower) { CollectionList::instance()->removeStringFromDict(sharedData()->metadata[id], id); CollectionList::instance()->addStringToDict(text(i), id); } } sharedData()->metadata[id] = toLower; } int newWidth = treeWidget()->fontMetrics().width(text(i)); if(newWidth != sharedData()->cachedWidths[i]) playlist()->slotWeightDirty(i); sharedData()->cachedWidths[i] = newWidth; } for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->playlist()->update(); (*it)->playlist()->dataChanged(); } if(treeWidget()->isVisible()) treeWidget()->viewport()->update(); CollectionList::instance()->dataChanged(); emit CollectionList::instance()->signalCollectionChanged(); } PlaylistItem *CollectionListItem::itemForPlaylist(const Playlist *playlist) { if(playlist == CollectionList::instance()) return this; PlaylistItemList::ConstIterator it; for(it = m_children.constBegin(); it != m_children.constEnd(); ++it) if((*it)->playlist() == playlist) return *it; return 0; } void CollectionListItem::updateCollectionDict(const QString &oldPath, const QString &newPath) { CollectionList *collection = CollectionList::instance(); if(!collection) return; collection->removeFromDict(oldPath); collection->addToDict(newPath, this); } void CollectionListItem::repaint() const { // FIXME repaint /*QItemDelegate::repaint(); for(PlaylistItemList::ConstIterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) (*it)->repaint();*/ } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem protected methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem::CollectionListItem(CollectionList *parent, const FileHandle &file) : PlaylistItem(parent), m_shuttingDown(false) { parent->addToDict(file.absFilePath(), this); sharedData()->fileHandle = file; if(file.tag()) { refresh(); parent->dataChanged(); } else { qCCritical(JUK_LOG) << "CollectionListItem::CollectionListItem() -- Tag() could not be created."; } } CollectionListItem::~CollectionListItem() { m_shuttingDown = true; foreach(PlaylistItem *item, m_children) delete item; CollectionList *l = CollectionList::instance(); if(l) { l->removeFromDict(file().absFilePath()); l->removeStringFromDict(file().tag()->album(), AlbumColumn); l->removeStringFromDict(file().tag()->artist(), ArtistColumn); l->removeStringFromDict(file().tag()->genre(), GenreColumn); } } void CollectionListItem::addChildItem(PlaylistItem *child) { m_children.append(child); } void CollectionListItem::removeChildItem(PlaylistItem *child) { if(!m_shuttingDown) m_children.removeAll(child); } bool CollectionListItem::checkCurrent() { if(!file().fileInfo().exists() || !file().fileInfo().isFile()) return false; if(!file().current()) { file().refresh(); refresh(); } return true; } // vim: set et sw=4 tw=0 sta: diff --git a/collectionlist.h b/collectionlist.h index a1622e5a..9e9b5193 100644 --- a/collectionlist.h +++ b/collectionlist.h @@ -1,216 +1,216 @@ /** * Copyright (C) 2002-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 JUK_COLLECTIONLIST_H #define JUK_COLLECTIONLIST_H #include #include #include "playlist.h" #include "playlistitem.h" class ViewMode; class KFileItem; class KFileItemList; class KDirWatch; /** * This type is for mapping QString track attributes like the album, artist * and track to an integer count representing the number of outstanding items * that hold the string. */ typedef QHash TagCountDict; typedef QHashIterator TagCountDictIterator; /** * We then have an array of dicts, one for each column in the list view. * The array is sparse (not every vector will have a TagCountDict so we use * pointers. */ typedef QVector TagCountDicts; /** * This is the "collection", or all of the music files that have been opened * in any playlist and not explicitly removed from the collection. * * It is being implemented as a "semi-singleton" because I need universal access * to just one instance. However, because the collection needs initialization * parameters (that will not always be available when an instance is needed). * Hence there will be the familiar singleton "instance()" method allong with an * "initialize()" method. */ class CollectionListItem : public PlaylistItem { friend class Playlist; friend class CollectionList; friend class PlaylistItem; public: virtual void refresh(); PlaylistItem *itemForPlaylist(const Playlist *playlist); void updateCollectionDict(const QString &oldPath, const QString &newPath); void repaint() const; PlaylistItemList children() const { return m_children; } protected: CollectionListItem(CollectionList *parent, const FileHandle &file); virtual ~CollectionListItem(); void addChildItem(PlaylistItem *child); void removeChildItem(PlaylistItem *child); /** * Returns true if the item is now up to date (even if this required a refresh) or * false if the item is invalid. */ bool checkCurrent(); virtual CollectionListItem *collectionItem() { return this; } private: bool m_shuttingDown; PlaylistItemList m_children; }; class CollectionList : public Playlist { friend class CollectionListItem; Q_OBJECT public: /** * A variety of unique value lists will be kept in the collection. This * enum can be used as an index into those structures. */ enum UniqueSetType { Artists = 0, Albums = 1, Genres = 2 }; static CollectionList *instance(); static void initialize(PlaylistCollection *collection); /** * Returns a unique set of values associated with the type specified. */ QStringList uniqueSet(UniqueSetType t) const; CollectionListItem *lookup(const QString &file) const; virtual CollectionListItem *createItem(const FileHandle &file, QTreeWidgetItem * = 0, bool = false); void emitVisibleColumnsChanged() { emit signalVisibleColumnsChanged(); } virtual void clearItems(const PlaylistItemList &items); void setupTreeViewEntries(ViewMode *viewMode) const; virtual bool canReload() const { return true; } void saveItemsToCache() const; public slots: virtual void paste(); virtual void clear(); void slotCheckCache(); void slotRemoveItem(const QString &file); void slotRefreshItem(const QString &file); void slotNewItems(const KFileItemList &items); void slotRefreshItems(const QList > &items); - void slotDeleteItem(const KFileItem &item); + void slotDeleteItems(const KFileItemList &items); protected: CollectionList(PlaylistCollection *collection); virtual ~CollectionList(); virtual void dropEvent(QDropEvent *e); virtual void dragMoveEvent(QDragMoveEvent *e); // These methods are used by CollectionListItem, which is a friend class. void addToDict(const QString &file, CollectionListItem *item) { m_itemsDict.insert(file, item); } void removeFromDict(const QString &file) { m_itemsDict.remove(file); } // These methods are also used by CollectionListItem, to manage the // strings used in generating the unique sets and tree view mode playlists. QString addStringToDict(const QString &value, int column); void removeStringFromDict(const QString &value, int column); void addWatched(const QString &file); void removeWatched(const QString &file); virtual bool hasItem(const QString &file) const { return m_itemsDict.contains(file); } signals: void signalCollectionChanged(); /** * This is emitted when the set of columns that is visible is changed. * * \see Playlist::hideColumn() * \see Playlist::showColumn() * \see Playlsit::isColumnVisible() */ void signalVisibleColumnsChanged(); void signalNewTag(const QString &, unsigned); void signalRemovedTag(const QString &, unsigned); // Emitted once cached items are loaded, which allows for folder scanning // and invalid track detection to proceed. void cachedItemsLoaded(); public slots: /** * Loads the CollectionListItems from the Cache. Should be called after program * initialization. */ void startLoadingCachedItems(); /** * Loads a few items at a time. Intended to be single-shotted into the event * loop so that loading the music doesn't freeze the GUI. */ void loadNextBatchCachedItems(); /** * Teardown from cache loading (e.g. a sort operation). Should * always be called if startLoadingCachedItems is called. */ void completedLoadingCachedItems(); private: /** * Just the size of the above enum to keep from hard coding it in several * locations. */ static const int m_uniqueSetCount = 3; static CollectionList *m_list; QHash m_itemsDict; KDirWatch *m_dirWatch; TagCountDicts m_columnTags; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/playlistcollection.cpp b/playlistcollection.cpp index 16fc8e02..d6c1938b 100644 --- a/playlistcollection.cpp +++ b/playlistcollection.cpp @@ -1,1014 +1,1016 @@ /** * 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(); currentChanged(); } void PlaylistCollection::playNextAlbum() { m_playing = true; currentPlaylist()->playNextAlbum(); currentChanged(); } void PlaylistCollection::playPrevious() { m_playing = true; currentPlaylist()->playPrevious(); currentChanged(); } void PlaylistCollection::playNext() { m_playing = true; currentPlaylist()->playNext(); currentChanged(); } void PlaylistCollection::stop() { m_playing = false; currentPlaylist()->stop(); dataChanged(); } bool PlaylistCollection::playing() const { return m_playing; } QStringList PlaylistCollection::playlists() const { QStringList l; //(or qFindChildren() if you need MSVC 6 compatibility) const QList childList = m_playlistStack->findChildren("Playlist"); foreach(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); } dataChanged(); } 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()); // FIXME signal result //DirectoryList::Result result = l.exec(); /*if(result.status == QDialog::Accepted) { m_dirLister.blockSignals(true); 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(KUrl::fromPath(dir), KDirLister::Keep); m_folderList.append(dir); } foreach(const QString &dir, result.removedDirs) { m_dirLister.stop(KUrl::fromPath(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); dataChanged(); } void PlaylistCollection::removeCovers() { visiblePlaylist()->slotRemoveCover(); dataChanged(); } void PlaylistCollection::viewCovers() { visiblePlaylist()->slotViewCover(); } void PlaylistCollection::showCoverManager() { visiblePlaylist()->slotShowCoverManager(); } PlaylistItemList PlaylistCollection::selectedItems() { return visiblePlaylist()->selectedItems(); } void PlaylistCollection::scanFolders() { CollectionList::instance()->addFiles(m_folderList); if(CollectionList::instance()->count() == 0) addFolder(); 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()); 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 = KFileDialog::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); dataChanged(); } void PlaylistCollection::raiseDistraction() { if(m_belowDistraction) return; m_belowDistraction = currentPlaylist(); if(!m_distraction) { m_distraction = new QWidget(m_playlistStack); m_playlistStack->addWidget(m_distraction); } m_playlistStack->setCurrentWidget(m_distraction); } void PlaylistCollection::lowerDistraction() { if(!m_distraction) return; if(m_belowDistraction) m_playlistStack->setCurrentWidget(m_belowDistraction); m_belowDistraction = 0; } //////////////////////////////////////////////////////////////////////////////// // 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) { - QObject *collection = CollectionList::instance(); + auto collection = CollectionList::instance(); m_dirLister.disconnect(object()); if(enable) { - QObject::connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), - object(), SLOT(slotNewItems(KFileItemList))); - QObject::connect(&m_dirLister, SIGNAL(refreshItems(QList >)), - collection, SLOT(slotRefreshItems(QList >))); - QObject::connect(&m_dirLister, SIGNAL(deleteItem(KFileItem)), - collection, SLOT(slotDeleteItem(KFileItem))); + 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 = KInputDialog::getText( caption, i18n("Please enter a name for this playlist:"), 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())); foreach(const QString &folder, m_folderList) { m_dirLister.openUrl(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(); } //////////////////////////////////////////////////////////////////////////////// // ActionHanlder implementation //////////////////////////////////////////////////////////////////////////////// PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) : QObject(0), 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(SmallIcon("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(SmallIcon("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))); } 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/tageditor.cpp b/tageditor.cpp index 3e922c75..c06df2e1 100644 --- a/tageditor.cpp +++ b/tageditor.cpp @@ -1,674 +1,667 @@ /** * Copyright (C) 2002-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 "tageditor.h" #include "collectionlist.h" #include "playlistitem.h" #include "tag.h" #include "actioncollection.h" #include "tagtransactionmanager.h" #include "juk_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef KeyRelease class FileNameValidator : public QValidator { public: FileNameValidator(QObject *parent, const char *name = 0) : QValidator(parent) { setObjectName( QLatin1String( name ) ); } virtual void fixup(QString &s) const { s.remove('/'); } virtual State validate(QString &s, int &) const { if(s.contains('/')) return Invalid; return Acceptable; } }; class FixedHLayout : public QHBoxLayout { public: FixedHLayout(QWidget *parent, int margin = 0, int spacing = -1, const char *name = 0) : QHBoxLayout(parent), m_width(-1) { setMargin(margin); setSpacing(spacing); setObjectName(QLatin1String(name)); } FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) : QHBoxLayout(), m_width(-1) { parentLayout->addItem(this); setSpacing(spacing); setObjectName(QLatin1String(name)); } void setWidth(int w = -1) { m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w; } virtual QSize minimumSize() const { QSize s = QHBoxLayout::minimumSize(); s.setWidth(m_width); return s; } private: int m_width; }; class CollectionObserver : public PlaylistObserver { public: CollectionObserver(TagEditor *parent) : PlaylistObserver(CollectionList::instance()), m_parent(parent) { } virtual void updateData() { if(m_parent && m_parent->m_currentPlaylist && m_parent->isVisible()) m_parent->slotSetItems(m_parent->m_currentPlaylist->selectedItems()); } virtual void updateCurrent() {} private: TagEditor *m_parent; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// TagEditor::TagEditor(QWidget *parent) : QWidget(parent), m_currentPlaylist(0), m_observer(0), m_performingSave(false) { setupActions(); setupLayout(); readConfig(); m_dataChanged = false; m_collectionChanged = false; setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } TagEditor::~TagEditor() { delete m_observer; saveConfig(); } void TagEditor::setupObservers() { m_observer = new CollectionObserver(this); } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void TagEditor::slotSetItems(const PlaylistItemList &list) { if(m_performingSave) return; // Store the playlist that we're setting because saveChangesPrompt // can delete the PlaylistItems in list. Playlist *itemPlaylist = 0; if(!list.isEmpty()) itemPlaylist = list.first()->playlist(); bool hadPlaylist = m_currentPlaylist != 0; saveChangesPrompt(); if(m_currentPlaylist) { disconnect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem*)), this, SLOT(slotItemRemoved(PlaylistItem*))); } if((hadPlaylist && !m_currentPlaylist) || !itemPlaylist) { m_currentPlaylist = 0; m_items.clear(); } else { m_currentPlaylist = itemPlaylist; // We can't use list here, it may not be valid m_items = itemPlaylist->selectedItems(); } if(m_currentPlaylist) { connect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem*)), this, SLOT(slotItemRemoved(PlaylistItem*))); connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved())); } if(isVisible()) slotRefresh(); else m_collectionChanged = true; } void TagEditor::slotRefresh() { // This method takes the list of currently selected m_items and tries to // figure out how to show that in the tag editor. The current strategy -- // the most common case -- is to just process the first item. Then we // check after that to see if there are other m_items and adjust accordingly. if(m_items.isEmpty() || !m_items.first()->file().tag()) { slotClear(); setEnabled(false); return; } setEnabled(true); PlaylistItem *item = m_items.first(); Q_ASSERT(item); Tag *tag = item->file().tag(); QFileInfo fi(item->file().absFilePath()); if(!fi.isWritable() && m_items.count() == 1) setEnabled(false); artistNameBox->setEditText(tag->artist()); trackNameBox->setText(tag->title()); albumNameBox->setEditText(tag->album()); fileNameBox->setText(item->file().fileInfo().fileName()); fileNameBox->setToolTip(item->file().absFilePath()); bitrateBox->setText(QString::number(tag->bitrate())); lengthBox->setText(tag->lengthString()); if(m_genreList.indexOf(tag->genre()) >= 0) genreBox->setCurrentIndex(m_genreList.indexOf(tag->genre()) + 1); else { genreBox->setCurrentIndex(0); genreBox->setEditText(tag->genre()); } trackSpin->setValue(tag->track()); yearSpin->setValue(tag->year()); commentBox->setPlainText(tag->comment()); // Start at the second item, since we've already processed the first. PlaylistItemList::Iterator it = m_items.begin(); ++it; // If there is more than one item in the m_items that we're dealing with... QList disabledForMulti; disabledForMulti << fileNameLabel << fileNameBox << lengthLabel << lengthBox << bitrateLabel << bitrateBox; foreach(QWidget *w, disabledForMulti) { w->setDisabled(m_items.size() > 1); if(m_items.size() > 1 && !w->inherits("QLabel")) QMetaObject::invokeMethod(w, "clear"); } if(it != m_items.end()) { foreach(QCheckBox *box, m_enableBoxes) { box->setChecked(true); box->show(); } // Yep, this is ugly. Loop through all of the files checking to see // if their fields are the same. If so, by default, enable their // checkbox. // Also, if there are more than 50 m_items, don't scan all of them. if(m_items.count() > 50) { m_enableBoxes[artistNameBox]->setChecked(false); m_enableBoxes[trackNameBox]->setChecked(false); m_enableBoxes[albumNameBox]->setChecked(false); m_enableBoxes[genreBox]->setChecked(false); m_enableBoxes[trackSpin]->setChecked(false); m_enableBoxes[yearSpin]->setChecked(false); m_enableBoxes[commentBox]->setChecked(false); } else { for(; it != m_items.end(); ++it) { tag = (*it)->file().tag(); if(tag) { if(artistNameBox->currentText() != tag->artist() && m_enableBoxes.contains(artistNameBox)) { artistNameBox->lineEdit()->clear(); m_enableBoxes[artistNameBox]->setChecked(false); } if(trackNameBox->text() != tag->title() && m_enableBoxes.contains(trackNameBox)) { trackNameBox->clear(); m_enableBoxes[trackNameBox]->setChecked(false); } if(albumNameBox->currentText() != tag->album() && m_enableBoxes.contains(albumNameBox)) { albumNameBox->lineEdit()->clear(); m_enableBoxes[albumNameBox]->setChecked(false); } if(genreBox->currentText() != tag->genre() && m_enableBoxes.contains(genreBox)) { genreBox->lineEdit()->clear(); m_enableBoxes[genreBox]->setChecked(false); } if(trackSpin->value() != tag->track() && m_enableBoxes.contains(trackSpin)) { trackSpin->setValue(0); m_enableBoxes[trackSpin]->setChecked(false); } if(yearSpin->value() != tag->year() && m_enableBoxes.contains(yearSpin)) { yearSpin->setValue(0); m_enableBoxes[yearSpin]->setChecked(false); } if(commentBox->toPlainText() != tag->comment() && m_enableBoxes.contains(commentBox)) { commentBox->clear(); m_enableBoxes[commentBox]->setChecked(false); } } } } } else { foreach(QCheckBox *box, m_enableBoxes) { box->setChecked(true); box->hide(); } } m_dataChanged = false; } void TagEditor::slotClear() { artistNameBox->lineEdit()->clear(); trackNameBox->clear(); albumNameBox->lineEdit()->clear(); genreBox->setCurrentIndex(0); fileNameBox->clear(); fileNameBox->setToolTip(QString()); trackSpin->setValue(0); yearSpin->setValue(0); lengthBox->clear(); bitrateBox->clear(); commentBox->clear(); } void TagEditor::slotUpdateCollection() { if(isVisible()) updateCollection(); else m_collectionChanged = true; } void TagEditor::updateCollection() { m_collectionChanged = false; CollectionList *list = CollectionList::instance(); if(!list) return; QStringList artistList = list->uniqueSet(CollectionList::Artists); artistList.sort(); artistNameBox->clear(); artistNameBox->addItems(artistList); artistNameBox->completionObject()->setItems(artistList); QStringList albumList = list->uniqueSet(CollectionList::Albums); albumList.sort(); albumNameBox->clear(); albumNameBox->addItems(albumList); albumNameBox->completionObject()->setItems(albumList); // Merge the list of genres found in tags with the standard ID3v1 set. StringHash genreHash; m_genreList = list->uniqueSet(CollectionList::Genres); foreach(const QString &genre, m_genreList) genreHash.insert(genre); TagLib::StringList genres = TagLib::ID3v1::genreList(); for(TagLib::StringList::Iterator it = genres.begin(); it != genres.end(); ++it) genreHash.insert(TStringToQString((*it))); m_genreList = genreHash.values(); m_genreList.sort(); genreBox->clear(); genreBox->addItem(QString()); genreBox->addItems(m_genreList); genreBox->completionObject()->setItems(m_genreList); // We've cleared out the original entries of these list boxes, re-read // the current item if one is selected. slotRefresh(); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void TagEditor::readConfig() { // combo box completion modes KConfigGroup config(KSharedConfig::openConfig(), "TagEditor"); if(artistNameBox && albumNameBox) { readCompletionMode(config, artistNameBox, "ArtistNameBoxMode"); readCompletionMode(config, albumNameBox, "AlbumNameBoxMode"); readCompletionMode(config, genreBox, "GenreBoxMode"); } bool show = config.readEntry("Show", false); ActionCollection::action("showEditor")->setChecked(show); setVisible(show); TagLib::StringList genres = TagLib::ID3v1::genreList(); for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it) m_genreList.append(TStringToQString((*it))); m_genreList.sort(); genreBox->clear(); genreBox->addItem(QString()); genreBox->addItems(m_genreList); genreBox->completionObject()->setItems(m_genreList); } void TagEditor::readCompletionMode(const KConfigGroup &config, KComboBox *box, const QString &key) { KGlobalSettings::Completion mode = KGlobalSettings::Completion(config.readEntry(key, (int)KGlobalSettings::CompletionAuto)); // FIXME tag completion //box->setCompletionMode(mode); } void TagEditor::saveConfig() { // combo box completion modes KConfigGroup config(KSharedConfig::openConfig(), "TagEditor"); if(artistNameBox && albumNameBox) { config.writeEntry("ArtistNameBoxMode", (int)artistNameBox->completionMode()); config.writeEntry("AlbumNameBoxMode", (int)albumNameBox->completionMode()); config.writeEntry("GenreBoxMode", (int)genreBox->completionMode()); } config.writeEntry("Show", ActionCollection::action("showEditor")->isChecked()); } void TagEditor::setupActions() { KToggleAction *show = new KToggleAction(QIcon::fromTheme(QLatin1String("document-properties")), i18n("Show &Tag Editor"), this); ActionCollection::actions()->addAction("showEditor", show); connect(show, &QAction::toggled, this, &TagEditor::setVisible); QAction *act = new QAction(QIcon::fromTheme(QLatin1String( "document-save")), i18n("&Save"), this); ActionCollection::actions()->addAction("saveItem", act); ActionCollection::actions()->setDefaultShortcut(act, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(act, &QAction::triggered, this, &TagEditor::slotSave); } void TagEditor::setupLayout() { setupUi(this); foreach(QWidget *input, findChildren()) { - if(input->inherits("QLineEdit") || input->inherits("QComboBox")) + if(input->inherits("QLineEdit")) connect(input, SIGNAL(textChanged(QString)), this, SLOT(slotDataChanged())); - if(input->inherits("QComboxBox")) + if(input->inherits("QComboxBox")) { connect(input, SIGNAL(activated(int)), this, SLOT(slotDataChanged())); + connect(input, SIGNAL(currentTextChanged(QString)), this, SLOT(slotDataChanged())); + } if(input->inherits("QSpinBox")) connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotDataChanged())); if(input->inherits("QTextEdit")) connect(input, SIGNAL(textChanged()), this, SLOT(slotDataChanged())); } // Do some meta-programming to find the matching enable boxes foreach(QCheckBox *enable, findChildren(QRegExp("Enable"))) { enable->hide(); QRegExp re('^' + enable->objectName().replace("Enable", "") + "(Box|Spin)$"); QList targets = findChildren(re); Q_ASSERT(!targets.isEmpty()); m_enableBoxes[targets.front()] = enable; } // Make sure that the labels are as tall as the enable buttons so that the // layout doesn't jump around. foreach(QLabel *label, findChildren()) { if(m_enableBoxes.contains(label->buddy())) label->setMinimumHeight(m_enableBoxes[label->buddy()]->height()); } tagEditorLayout->setColumnMinimumWidth(1, 200); } void TagEditor::save(const PlaylistItemList &list) { if(!list.isEmpty() && m_dataChanged) { QApplication::setOverrideCursor(Qt::WaitCursor); m_dataChanged = false; m_performingSave = true; // The list variable can become corrupted if the playlist holding its // items dies, which is possible as we edit tags. So we need to copy // the end marker. PlaylistItemList::ConstIterator end = list.end(); for(PlaylistItemList::ConstIterator it = list.begin(); it != end; /* Deliberately missing */ ) { // Process items before we being modifying tags, as the dynamic // playlists will try to modify the file we edit if the tag changes // due to our alterations here. qApp->processEvents(QEventLoop::ExcludeUserInputEvents); PlaylistItem *item = *it; // The playlist can be deleted from under us if this is the last // item and we edit it so that it doesn't match the search, which // means we can't increment the iterator, so let's do it now. ++it; QString fileName = item->file().fileInfo().path() + QDir::separator() + fileNameBox->text(); if(list.count() > 1) fileName = item->file().fileInfo().absoluteFilePath(); Tag *tag = TagTransactionManager::duplicateTag(item->file().tag(), fileName); // A bit more ugliness. If there are multiple files that are // being modified, they each have a "enabled" checkbox that // says if that field is to be respected for the multiple // files. We have to check to see if that is enabled before // each field that we write. if(m_enableBoxes[artistNameBox]->isChecked()) tag->setArtist(artistNameBox->currentText()); if(m_enableBoxes[trackNameBox]->isChecked()) tag->setTitle(trackNameBox->text()); if(m_enableBoxes[albumNameBox]->isChecked()) tag->setAlbum(albumNameBox->currentText()); if(m_enableBoxes[trackSpin]->isChecked()) { if(trackSpin->text().isEmpty()) trackSpin->setValue(0); tag->setTrack(trackSpin->value()); } if(m_enableBoxes[yearSpin]->isChecked()) { if(yearSpin->text().isEmpty()) yearSpin->setValue(0); tag->setYear(yearSpin->value()); } if(m_enableBoxes[commentBox]->isChecked()) tag->setComment(commentBox->toPlainText()); if(m_enableBoxes[genreBox]->isChecked()) tag->setGenre(genreBox->currentText()); TagTransactionManager::instance()->changeTagOnItem(item, tag); } TagTransactionManager::instance()->commit(); CollectionList::instance()->dataChanged(); m_performingSave = false; QApplication::restoreOverrideCursor(); } } void TagEditor::saveChangesPrompt() { if(!isVisible() || !m_dataChanged || m_items.isEmpty()) return; QStringList files; foreach(const PlaylistItem *item, m_items) files.append(item->file().absFilePath()); if(KMessageBox::questionYesNoList(this, i18n("Do you want to save your changes to:\n"), files, i18n("Save Changes"), KStandardGuiItem::save(), KStandardGuiItem::discard(), "tagEditor_showSaveChangesBox") == KMessageBox::Yes) { save(m_items); } } void TagEditor::showEvent(QShowEvent *e) { if(m_collectionChanged) { updateCollection(); } QWidget::showEvent(e); } -bool TagEditor::eventFilter(QObject *watched, QEvent *e) -{ - QKeyEvent *ke = static_cast(e); - if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->modifiers() == 0) - slotDataChanged(); - - return false; -} - //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void TagEditor::slotDataChanged(bool c) { m_dataChanged = c; } void TagEditor::slotItemRemoved(PlaylistItem *item) { m_items.removeAll(item); if(m_items.isEmpty()) slotRefresh(); } void TagEditor::slotPlaylistDestroyed(Playlist *p) { if(m_currentPlaylist == p) { m_currentPlaylist = 0; slotSetItems(PlaylistItemList()); } } // vim: set et sw=4 tw=0 sta: diff --git a/tageditor.h b/tageditor.h index 2a6c7cd8..281c979a 100644 --- a/tageditor.h +++ b/tageditor.h @@ -1,100 +1,99 @@ /** * Copyright (C) 2002-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 TAGEDITOR_H -#define TAGEDITOR_H +#ifndef JUK_TAGEDITOR_H +#define JUK_TAGEDITOR_H #include #include #include #include "ui_tageditor.h" class KComboBox; class KLineEdit; class KIntSpinBox; class KTextEdit; class KConfigGroup; class QCheckBox; class QBoxLayout; class CollectionObserver; class Playlist; class PlaylistItem; typedef QList PlaylistItemList; class TagEditor : public QWidget, public Ui::TagEditor { Q_OBJECT public: TagEditor(QWidget *parent = 0); virtual ~TagEditor(); PlaylistItemList items() const { return m_items; } void setupObservers(); public slots: void slotSave() { save(m_items); } void slotSetItems(const PlaylistItemList &list); void slotRefresh(); void slotClear(); void slotPlaylistDestroyed(Playlist *p); /** * Update collection if we're visible, or defer otherwise */ void slotUpdateCollection(); private: void updateCollection(); void setupActions(); void setupLayout(); void readConfig(); void readCompletionMode(const KConfigGroup &config, KComboBox *box, const QString &key); void saveConfig(); void save(const PlaylistItemList &list); void saveChangesPrompt(); virtual void showEvent(QShowEvent *e); - virtual bool eventFilter(QObject *watched, QEvent *e); private slots: void slotDataChanged(bool c = true); void slotItemRemoved(PlaylistItem *item); void slotPlaylistRemoved() { m_currentPlaylist = 0; } private: typedef QMap BoxMap; BoxMap m_enableBoxes; QStringList m_genreList; PlaylistItemList m_items; Playlist *m_currentPlaylist; CollectionObserver *m_observer; bool m_dataChanged; bool m_collectionChanged; bool m_performingSave; friend class CollectionObserver; }; #endif // vim: set et sw=4 tw=0 sta: