diff --git a/playlistbox.cpp b/playlistbox.cpp index ab186ef8..7fcd596c 100644 --- a/playlistbox.cpp +++ b/playlistbox.cpp @@ -1,813 +1,804 @@ /** * 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 "playlistbox.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 "playlist.h" #include "collectionlist.h" #include "dynamicplaylist.h" #include "upcomingplaylist.h" #include "historyplaylist.h" #include "viewmode.h" #include "searchplaylist.h" #include "treeviewitemplaylist.h" #include "actioncollection.h" #include "cache.h" #include "tracksequencemanager.h" #include "tagtransactionmanager.h" #include "playermanager.h" #include "dbuscollectionproxy.h" #include "juk_debug.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::PlaylistBox(PlayerManager *player, QWidget *parent, QStackedWidget *playlistStack) : QTreeWidget(parent), PlaylistCollection(player, playlistStack), m_viewModeIndex(0), m_hasSelection(false), m_doingMultiSelect(false), m_dropItem(0), m_showTimer(0) { readConfig(); setHeaderLabel("Playlists"); setRootIsDecorated(false); setContextMenuPolicy(Qt::CustomContextMenu); viewport()->setAcceptDrops(true); setDragDropMode(QAbstractItemView::DropOnly); setDropIndicatorShown(true); setColumnCount(2); // Use fake column for sorting setColumnHidden(1, true); setSortingEnabled(true); sortByColumn(1, Qt::AscendingOrder); header()->blockSignals(true); header()->hide(); header()->blockSignals(false); setSelectionMode(QAbstractItemView::ExtendedSelection); m_contextMenu = new QMenu(this); m_contextMenu->addAction( action("file_new") ); m_contextMenu->addAction( action("renamePlaylist") ); m_contextMenu->addAction( action("editSearch") ); m_contextMenu->addAction( action("duplicatePlaylist") ); m_contextMenu->addAction( action("reloadPlaylist") ); m_contextMenu->addAction( action("deleteItemPlaylist") ); m_contextMenu->addAction( action("file_save") ); m_contextMenu->addAction( action("file_save_as") ); m_contextMenu->addSeparator(); // add the view modes stuff KSelectAction *viewModeAction = new KSelectAction( QIcon::fromTheme(QStringLiteral("view-choose")), i18n("View Modes"), ActionCollection::actions()); ActionCollection::actions()->addAction("viewModeMenu", viewModeAction); ViewMode* viewmode = new ViewMode(this); m_viewModes.append(viewmode); viewModeAction->addAction(QIcon::fromTheme(QStringLiteral("view-list-details")), viewmode->name()); CompactViewMode* compactviewmode = new CompactViewMode(this); m_viewModes.append(compactviewmode); viewModeAction->addAction(QIcon::fromTheme(QStringLiteral("view-list-text")), compactviewmode->name()); // TODO: Fix the broken tree view mode #if 0 TreeViewMode* treeviewmode = new TreeViewMode(this); m_viewModes.append(treeviewmode); viewModeAction->addAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), treeviewmode->name()); #endif CollectionList::initialize(this); viewModeAction->setCurrentItem(m_viewModeIndex); m_viewModes[m_viewModeIndex]->setShown(true); TrackSequenceManager::instance()->setCurrentPlaylist(CollectionList::instance()); raise(CollectionList::instance()); m_contextMenu->addAction( viewModeAction ); connect(viewModeAction, SIGNAL(triggered(int)), this, SLOT(slotSetViewMode(int))); connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(slotPlaylistChanged())); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotDoubleClicked(QTreeWidgetItem*))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotShowContextMenu(QPoint))); TagTransactionManager *tagManager = TagTransactionManager::instance(); connect(tagManager, SIGNAL(signalAboutToModifyTags()), SLOT(slotFreezePlaylists())); connect(tagManager, SIGNAL(signalDoneModifyingTags()), SLOT(slotUnfreezePlaylists())); setupUpcomingPlaylist(); connect(CollectionList::instance(), SIGNAL(signalNewTag(QString,uint)), this, SLOT(slotAddItem(QString,uint))); connect(CollectionList::instance(), SIGNAL(signalRemovedTag(QString,uint)), this, SLOT(slotRemoveItem(QString,uint))); connect(CollectionList::instance(), SIGNAL(cachedItemsLoaded()), this, SLOT(slotLoadCachedPlaylists())); m_savePlaylistTimer = 0; KToggleAction *historyAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-history")), i18n("Show &History"), ActionCollection::actions()); ActionCollection::actions()->addAction("showHistory", historyAction); connect(historyAction, SIGNAL(triggered(bool)), this, SLOT(slotSetHistoryPlaylistEnabled(bool))); m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); m_showTimer->setInterval(500); connect(m_showTimer, SIGNAL(timeout()), SLOT(slotShowDropTarget())); // hook up to the D-Bus (void) new DBusCollectionProxy(this, this); } PlaylistBox::~PlaylistBox() { PlaylistList l; CollectionList *collection = CollectionList::instance(); for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) { Item *item = static_cast(*it); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); saveConfig(); } void PlaylistBox::raise(Playlist *playlist) { if(!playlist) return; Item *i = m_playlistDict.value(playlist, 0); if(i) { clearSelection(); setCurrentItem(i); setSingleItem(i); scrollToItem(currentItem()); } else PlaylistCollection::raise(playlist); slotPlaylistChanged(); } void PlaylistBox::duplicate() { Item *item = static_cast(currentItem()); if(!item || !item->playlist()) return; QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"), item->text(0)); if(name.isNull()) return; Playlist *p = new Playlist(this, name); p->createItems(item->playlist()->items()); } void PlaylistBox::scanFolders() { PlaylistCollection::scanFolders(); emit startupComplete(); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::paste() { // TODO: Reimplement } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox protected methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotFreezePlaylists() { setDynamicListsFrozen(true); } void PlaylistBox::slotUnfreezePlaylists() { setDynamicListsFrozen(false); } void PlaylistBox::slotPlaylistDataChanged() { if(m_savePlaylistTimer) m_savePlaylistTimer->start(); // Restarts the timer if it's already running. } void PlaylistBox::slotSetHistoryPlaylistEnabled(bool enable) { setHistoryPlaylistEnabled(enable); } void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName) { setupPlaylist(playlist, iconName, nullptr); } void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem) { connect(playlist, &Playlist::signalPlaylistItemsDropped, this, &PlaylistBox::slotPlaylistItemsDropped); connect(playlist, &Playlist::signalMoveFocusAway, this, &PlaylistBox::signalMoveFocusAway); PlaylistCollection::setupPlaylist(playlist, iconName); if(parentItem) new Item(parentItem, iconName, playlist->name(), playlist); else new Item(this, iconName, playlist->name(), playlist); } void PlaylistBox::removePlaylist(Playlist *playlist) { // Could be false if setup() wasn't run yet. if(m_playlistDict.contains(playlist)) { removeNameFromDict(m_playlistDict[playlist]->text(0)); delete m_playlistDict[playlist]; // Delete the Item* } removeFileFromDict(playlist->fileName()); m_playlistDict.remove(playlist); } Qt::DropActions PlaylistBox::supportedDropActions() const { return Qt::CopyAction; } bool PlaylistBox::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action) { Q_UNUSED(index); // The *parent* item won't be null, but index should be zero except in the // still-broken "tree view" mode. if(!parent || action != Qt::CopyAction || !data->hasUrls()) { return false; } auto *playlistItem = static_cast(parent); if(!playlistItem) { return false; } auto *playlist = playlistItem->playlist(); const auto droppedUrls = data->urls(); PlaylistItem *lastItem = nullptr; for(const auto &url : droppedUrls) { lastItem = playlist->createItem(FileHandle(url.toLocalFile()), lastItem); } return true; } QStringList PlaylistBox::mimeTypes() const { auto result = QTreeWidget::mimeTypes(); // Need to add Playlists's mime type to convince QTreeWidget to allow it as // a drop option. result.append(QLatin1String("text/uri-list")); return result; } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox"); m_viewModeIndex = config.readEntry("ViewMode", 0); // TODO Restore ability to use Tree View once fixed. if(m_viewModeIndex == 2) { m_viewModeIndex = 0; } } void PlaylistBox::saveConfig() { KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox"); config.writeEntry("ViewMode", action("viewModeMenu")->currentItem()); KSharedConfig::openConfig()->sync(); } void PlaylistBox::remove() { ItemList items = selectedBoxItems(); if(items.isEmpty()) return; QStringList files; QStringList names; foreach(Item *item, items) { if(item && item->playlist()) { if (!item->playlist()->fileName().isEmpty() && QFileInfo(item->playlist()->fileName()).exists()) { files.append(item->playlist()->fileName()); } names.append(item->playlist()->name()); } } if(!files.isEmpty()) { int remove = KMessageBox::warningYesNoCancelList( this, i18n("Do you want to delete these files from the disk as well?"), files, QString(), KStandardGuiItem::del(), KGuiItem(i18n("Keep"))); if(remove == KMessageBox::Yes) { QStringList couldNotDelete; for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) { if(!QFile::remove(*it)) couldNotDelete.append(*it); } if(!couldNotDelete.isEmpty()) KMessageBox::errorList(this, i18n("Could not delete these files."), couldNotDelete); } else if(remove == KMessageBox::Cancel) return; } else if(items.count() > 1 || items.front()->playlist() != upcomingPlaylist()) { if(KMessageBox::warningContinueCancelList(this, i18n("Are you sure you want to remove these " "playlists from your collection?"), names, i18n("Remove Items?"), KGuiItem(i18n("&Remove"), "user-trash")) == KMessageBox::Cancel) { return; } } PlaylistList removeQueue; for(ItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { if(*it != Item::collectionItem() && (*it)->playlist() && (!(*it)->playlist()->readOnly())) { removeQueue.append((*it)->playlist()); } } // FIXME removing items /*if(items.back()->nextSibling() && static_cast(items.back()->nextSibling())->playlist()) setSingleItem(items.back()->nextSibling()); else { Item *i = static_cast(items.front()->itemAbove()); while(i && !i->playlist()) i = static_cast(i->itemAbove()); if(!i) i = Item::collectionItem(); setSingleItem(i); }*/ for(PlaylistList::ConstIterator it = removeQueue.constBegin(); it != removeQueue.constEnd(); ++it) { if(*it != upcomingPlaylist()) delete *it; else { action("showUpcoming")->setChecked(false); setUpcomingPlaylistEnabled(false); } } } void PlaylistBox::setDynamicListsFrozen(bool frozen) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) { (*it)->setDynamicListsFrozen(frozen); } } void PlaylistBox::slotSavePlaylists() { qCDebug(JUK_LOG) << "Auto-saving playlists.\n"; PlaylistList l; CollectionList *collection = CollectionList::instance(); for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) { Item *item = static_cast(*it); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); } void PlaylistBox::slotShowDropTarget() { if(m_dropItem) raise(m_dropItem->playlist()); } void PlaylistBox::slotAddItem(const QString &tag, unsigned column) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->addItems(QStringList(tag), column); } void PlaylistBox::slotRemoveItem(const QString &tag, unsigned column) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->removeItem(tag, column); } void PlaylistBox::mousePressEvent(QMouseEvent *e) { if(e->button() == Qt::LeftButton) m_doingMultiSelect = true; QTreeWidget::mousePressEvent(e); } void PlaylistBox::mouseReleaseEvent(QMouseEvent *e) { if(e->button() == Qt::LeftButton) { m_doingMultiSelect = false; slotPlaylistChanged(); } QTreeWidget::mouseReleaseEvent(e); } void PlaylistBox::keyPressEvent(QKeyEvent *e) { if((e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) && e->modifiers() == Qt::ShiftModifier) m_doingMultiSelect = true; QTreeWidget::keyPressEvent(e); } void PlaylistBox::keyReleaseEvent(QKeyEvent *e) { if(m_doingMultiSelect && e->key() == Qt::Key_Shift) { m_doingMultiSelect = false; slotPlaylistChanged(); } QTreeWidget::keyReleaseEvent(e); } PlaylistBox::ItemList PlaylistBox::selectedBoxItems() const { ItemList l; for(QTreeWidgetItemIterator it(const_cast(this), QTreeWidgetItemIterator::Selected); *it; ++it) l.append(static_cast(*it)); return l; } void PlaylistBox::setSingleItem(QTreeWidgetItem *item) { setSelectionMode(QAbstractItemView::SingleSelection); setCurrentItem(item); setSelectionMode(QAbstractItemView::ExtendedSelection); } void PlaylistBox::dragMoveEvent(QDragMoveEvent* event) { QTreeWidget::dragMoveEvent(event); Item* hovered_item = static_cast(itemAt(event->pos())); if(hovered_item != m_dropItem){ m_dropItem = hovered_item; if(m_dropItem) m_showTimer->start(); else m_showTimer->stop(); }; } void PlaylistBox::dragLeaveEvent(QDragLeaveEvent* event) { QTreeWidget::dragLeaveEvent(event); m_showTimer->stop(); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotPlaylistChanged() { // Don't update while the mouse is pressed down. if(m_doingMultiSelect) return; ItemList items = selectedBoxItems(); m_hasSelection = !items.isEmpty(); bool allowReload = false; PlaylistList playlists; for(ItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { Playlist *p = (*it)->playlist(); if(p) { if(p->canReload()) allowReload = true; playlists.append(p); } } bool singlePlaylist = playlists.count() == 1; if(playlists.isEmpty() || (singlePlaylist && (playlists.front() == CollectionList::instance() || playlists.front()->readOnly()))) { action("file_save")->setEnabled(false); action("file_save_as")->setEnabled(false); action("renamePlaylist")->setEnabled(false); action("deleteItemPlaylist")->setEnabled(false); } else { action("file_save")->setEnabled(true); action("file_save_as")->setEnabled(true); action("renamePlaylist")->setEnabled(playlists.count() == 1); action("deleteItemPlaylist")->setEnabled(true); } action("reloadPlaylist")->setEnabled(allowReload); action("duplicatePlaylist")->setEnabled(!playlists.isEmpty()); action("editSearch")->setEnabled(singlePlaylist && playlists.front()->searchIsEditable()); if(singlePlaylist) { PlaylistCollection::raise(playlists.front()); if(playlists.front() == upcomingPlaylist()) action("deleteItemPlaylist")->setText(i18n("Hid&e")); else action("deleteItemPlaylist")->setText(i18n("R&emove")); } else if(!playlists.isEmpty()) createDynamicPlaylist(playlists); } void PlaylistBox::slotDoubleClicked(QTreeWidgetItem *item) { if(!item) return; TrackSequenceManager *manager = TrackSequenceManager::instance(); Item *playlistItem = static_cast(item); manager->setCurrentPlaylist(playlistItem->playlist()); manager->setCurrent(0); // Reset playback PlaylistItem *next = manager->nextItem(); // Allow manager to choose if(next) { emit startFilePlayback(next->file()); playlistItem->playlist()->setPlaying(next); } else action("stop")->trigger(); } void PlaylistBox::slotShowContextMenu(const QPoint &point) { m_contextMenu->popup(mapToGlobal(point)); } void PlaylistBox::slotPlaylistItemsDropped(Playlist *p) { raise(p); } void PlaylistBox::slotSetViewMode(int index) { if(index == m_viewModeIndex) return; viewMode()->setShown(false); m_viewModeIndex = index; viewMode()->setShown(true); } void PlaylistBox::setupItem(Item *item) { m_playlistDict.insert(item->playlist(), item); viewMode()->queueRefresh(); } void PlaylistBox::setupUpcomingPlaylist() { KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); bool enable = config.readEntry("showUpcoming", false); setUpcomingPlaylistEnabled(enable); action("showUpcoming")->setChecked(enable); } void PlaylistBox::slotLoadCachedPlaylists() { qCDebug(JUK_LOG) << "Loading cached playlists."; QElapsedTimer stopwatch; stopwatch.start(); Cache::loadPlaylists(this); qCDebug(JUK_LOG) << "Cached playlists loaded, took" << stopwatch.elapsed() << "ms"; // Auto-save playlists after they change. m_savePlaylistTimer = new QTimer(this); m_savePlaylistTimer->setInterval(3000); // 3 seconds with no change? -> commit m_savePlaylistTimer->setSingleShot(true); connect(m_savePlaylistTimer, SIGNAL(timeout()), SLOT(slotSavePlaylists())); clearSelection(); setCurrentItem(m_playlistDict[CollectionList::instance()]); QTimer::singleShot(0, CollectionList::instance(), SLOT(slotCheckCache())); QTimer::singleShot(0, object(), SLOT(slotScanFolders())); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::Item *PlaylistBox::Item::m_collectionItem = 0; PlaylistBox::Item::Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l) : QObject(listBox), QTreeWidgetItem(listBox, QStringList(text)), m_playlist(l), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::Item(Item *parent, const QString &icon, const QString &text, Playlist *l) : QObject(parent->listView()), QTreeWidgetItem(parent, QStringList(text)), m_playlist(l), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::~Item() { } -// FIXME paintcell -/*void PlaylistBox::Item::paintCell(QPainter *painter, const QColorGroup &colorGroup, int column, int width, int align) -{ - PlaylistBox *playlistBox = static_cast(listView()); - playlistBox->viewMode()->paintCell(this, painter, colorGroup, column, width, align); -}*/ - void PlaylistBox::Item::setup() { listView()->viewMode()->setupItem(this); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::slotSetName(const QString &name) { setText(0, name); // Display name setText(1, sortTextFor(name)); setSelected(true); treeWidget()->scrollToItem(this); - //FIXME viewmode - //listView()->viewMode()->queueRefresh(); } void PlaylistBox::Item::playlistItemDataChanged() { // This avoids spuriously re-saving all playlists just because play queue // changes. if(m_playlist != listView()->upcomingPlaylist()) listView()->slotPlaylistDataChanged(); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::init() { PlaylistBox *list = listView(); list->setupItem(this); const QString itemText(text()); setIcon(0, QIcon::fromTheme(m_iconName)); list->addNameToDict(itemText); if(m_playlist) { connect(m_playlist, SIGNAL(signalNameChanged(QString)), this, SLOT(slotSetName(QString))); connect(m_playlist, SIGNAL(signalEnableDirWatch(bool)), list->object(), SLOT(slotEnableDirWatch(bool))); } if(m_playlist == CollectionList::instance()) { m_sortedFirst = true; m_collectionItem = this; list->viewMode()->setupDynamicPlaylists(); } if(m_playlist == list->historyPlaylist() || m_playlist == list->upcomingPlaylist()) m_sortedFirst = true; setText(1, sortTextFor(itemText)); connect(&(m_playlist->signaller), &PlaylistInterfaceSignaller::playingItemDataChanged, this, &PlaylistBox::Item::playlistItemDataChanged); } QString PlaylistBox::Item::sortTextFor(const QString &name) const { // Collection List goes before everything, then // playlists that 'sort first', then remainder of // playlists. const auto prefix = (playlist() == CollectionList::instance()) ? QStringLiteral("0") : m_sortedFirst ? QStringLiteral("1") : QStringLiteral("2"); return prefix + name; } // vim: set et sw=4 tw=0 sta: diff --git a/playlistbox.h b/playlistbox.h index 52a1feb4..224827ff 100644 --- a/playlistbox.h +++ b/playlistbox.h @@ -1,204 +1,200 @@ /** * 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 PLAYLISTBOX_H #define PLAYLISTBOX_H #include "playlistcollection.h" #include #include class Playlist; class PlaylistItem; class ViewMode; class QMenu; template class QVector; typedef QVector PlaylistList; /** * This is the play list selection box that is by default on the left side of * JuK's main widget (PlaylistSplitter). */ class PlaylistBox final : public QTreeWidget, public PlaylistCollection { Q_OBJECT public: class Item; typedef QList ItemList; friend class Item; PlaylistBox(PlayerManager *player, QWidget *parent, QStackedWidget *playlistStack); virtual ~PlaylistBox(); virtual void raise(Playlist *playlist) override; virtual void duplicate() override; virtual void remove() override; // Called after files loaded to pickup any new files that might be present // in managed directories. virtual void scanFolders() override; /** * For view modes that have dynamic playlists, this freezes them from * removing playlists. */ virtual void setDynamicListsFrozen(bool frozen) override; Item *dropItem() const { return m_dropItem; } void setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem = nullptr); public slots: void paste(); void clear() {} void slotFreezePlaylists(); void slotUnfreezePlaylists(); void slotPlaylistDataChanged(); void slotSetHistoryPlaylistEnabled(bool enable); protected: virtual void setupPlaylist(Playlist *playlist, const QString &iconName) override; virtual void removePlaylist(Playlist *playlist) override; virtual Qt::DropActions supportedDropActions() const override; virtual bool dropMimeData(QTreeWidgetItem *, int, const QMimeData *, Qt::DropAction) override; virtual QStringList mimeTypes() const override; signals: void signalPlaylistDestroyed(Playlist *); void signalMoveFocusAway(); // Handles keyboard scrolling up out of playlist void startupComplete(); ///< Emitted after playlists are loaded. void startFilePlayback(const FileHandle &file); private: void readConfig(); void saveConfig(); virtual void mousePressEvent(QMouseEvent *e) override; virtual void mouseReleaseEvent(QMouseEvent *e) override; virtual void keyPressEvent(QKeyEvent *e) override; virtual void keyReleaseEvent(QKeyEvent *e) override; // selectedItems already used for something different ItemList selectedBoxItems() const; void setSingleItem(QTreeWidgetItem *item); void setupItem(Item *item); void setupUpcomingPlaylist(); int viewModeIndex() const { return m_viewModeIndex; } ViewMode *viewMode() const { return m_viewModes[m_viewModeIndex]; } void dragMoveEvent(QDragMoveEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; private slots: /** * Catches QListBox::currentChanged(QListBoxItem *), does a cast and then re-emits * the signal as currentChanged(Item *). */ void slotPlaylistChanged(); void slotDoubleClicked(QTreeWidgetItem *); void slotShowContextMenu(const QPoint &point); void slotSetViewMode(int index); void slotSavePlaylists(); void slotShowDropTarget(); void slotPlaylistItemsDropped(Playlist *p); void slotAddItem(const QString &tag, unsigned column); void slotRemoveItem(const QString &tag, unsigned column); // Used to load the playlists after GUI setup. void slotLoadCachedPlaylists(); private: QMenu *m_contextMenu; QHash m_playlistDict; int m_viewModeIndex; QList m_viewModes; bool m_hasSelection; bool m_doingMultiSelect; Item *m_dropItem; QTimer *m_showTimer; QTimer *m_savePlaylistTimer; }; class PlaylistBox::Item final : public QObject, public QTreeWidgetItem { friend class PlaylistBox; friend class PlaylistSplitter; friend class ViewMode; friend class CompactViewMode; friend class TreeViewMode; Q_OBJECT // moc won't let me create private QObject subclasses and Qt won't let me // make the destructor protected, so here's the closest hack that will // compile. public: virtual ~Item(); protected: using QTreeWidgetItem::text; Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l = 0); Item(Item *parent, const QString &icon, const QString &text, Playlist *l = 0); Playlist *playlist() const { return m_playlist; } PlaylistBox *listView() const { return static_cast(QTreeWidgetItem::treeWidget()); } QString iconName() const { return m_iconName; } QString text() const { return QTreeWidgetItem::text(0); } void setSortedFirst(bool first = true) { m_sortedFirst = first; } - /*virtual void paintCell(QPainter *p, const QColorGroup &colorGroup, int column, int width, int align); - virtual void paintFocus(QPainter *, const QColorGroup &, const QRect &) {}*/ - virtual void setup(); static Item *collectionItem() { return m_collectionItem; } - static void setCollectionItem(Item *item) { m_collectionItem = item; } // Used to post a timer in PlaylistBox to save playlists. void playlistItemDataChanged(); protected slots: void slotSetName(const QString &name); private: // setup() was already taken. void init(); QString sortTextFor(const QString &name) const; Playlist *m_playlist; QString m_iconName; bool m_sortedFirst; static Item *m_collectionItem; }; #endif // vim: set et sw=4 tw=0 sta: diff --git a/viewmode.cpp b/viewmode.cpp index db4cbf8a..bbdcfc76 100644 --- a/viewmode.cpp +++ b/viewmode.cpp @@ -1,472 +1,291 @@ /** * 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 "viewmode.h" #include #include #include #include #include "playlistbox.h" #include "searchplaylist.h" #include "treeviewitemplaylist.h" #include "collectionlist.h" #include "juk_debug.h" //////////////////////////////////////////////////////////////////////////////// // ViewMode //////////////////////////////////////////////////////////////////////////////// ViewMode::ViewMode(PlaylistBox *b) : QObject(b), m_playlistBox(b), m_visible(false), m_needsRefresh(false) { m_playlistBox->viewport()->installEventFilter(this); } ViewMode::~ViewMode() { } - // FIXME -/*void ViewMode::paintCell(PlaylistBox::Item *item, - QPainter *painter, - const QColorGroup &colorGroup, - int column, int width, int) -{ - if(width < item->pixmap(column)->width()) - return; - - if(m_needsRefresh) - updateHeights(); - - QFontMetrics fm = painter->fontMetrics(); - - int y = item->listView()->itemMargin() + border; - const QPixmap *pm = item->pixmap(column); - - if(item->isSelected()) { - painter->eraseRect(0, 0, width, item->height()); - painter->setRenderHint(QPainter::Antialiasing); - - QPen oldPen = painter->pen(); - QPen newPen = oldPen; - - painter->setPen(newPen); - newPen.setJoinStyle(Qt::RoundJoin); - - newPen.setWidth(1); - - QColor background = m_playlistBox->palette().color(QPalette::Highlight); - newPen.setColor(m_playlistBox->palette().color(QPalette::Text)); - painter->setPen(newPen); - painter->drawRoundedRect(border, border, width - border * 2, - item->height() - border * 2, 2, 2); - - QRect inner(border + 1, border + 1, width - border * 2 - 2, - item->height() - border * 2 - 2); - - painter->fillRect(inner, background); - - QPainterPath path(inner.bottomLeft()); - - path.lineTo(QPoint(inner.topLeft().x(), inner.topLeft().y() - 3)); - const QPointF topLeft(inner.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - path.arcTo(arc, 180, -90); - path.lineTo(inner.topRight()); - path.lineTo(inner.bottomRight()); - path.lineTo(inner.bottomLeft()); - - QColor window(item->listView()->palette().window().color()); - const QColor base = background; - - window.setAlphaF(0.5); - - QLinearGradient decoGradient1; - decoGradient1.setStart(inner.topLeft()); - decoGradient1.setFinalStop(inner.bottomLeft()); - decoGradient1.setColorAt(0, window); - decoGradient1.setColorAt(1, Qt::transparent); - - QLinearGradient decoGradient2; - decoGradient2.setStart(inner.topLeft()); - decoGradient2.setFinalStop(inner.topRight()); - decoGradient2.setColorAt(0, Qt::transparent); - decoGradient2.setColorAt(1, base); - - painter->fillPath(path, decoGradient1); - painter->fillPath(path, decoGradient2); - - painter->setPen(colorGroup.color(QPalette::HighlightedText)); - } - else - painter->eraseRect(0, 0, width, item->height()); - - if(!pm->isNull()) { - int x = (width - pm->width()) / 2; - x = qMax(x, item->listView()->itemMargin()); - painter->drawPixmap(x, y, *pm); - } - - y += pm->height() + fm.height() - fm.descent(); - - foreach(const QString &line, m_lines[item]) { - int x = (width - fm.width(line)) / 2; - x = qMax(x, item->listView()->itemMargin()); - painter->drawText(x, y, line); - y += fm.height() - fm.descent(); - } - - if(item == item->listView()->dropItem()) - paintDropIndicator(painter, width, item->height()); -}*/ - bool ViewMode::eventFilter(QObject *watched, QEvent *e) { if(m_visible && watched == m_playlistBox->viewport() && e->type() == QEvent::Resize) { QResizeEvent *re = static_cast(e); if(re->size().width() != re->oldSize().width()) m_needsRefresh = true; } if(e->type() == QEvent::Hide) m_needsRefresh = true; return QObject::eventFilter(watched, e); } QString ViewMode::name() const { return i18nc("the normal viewing mode", "Default"); } void ViewMode::setShown(bool shown) { m_visible = shown; if(shown) { updateIcons(); m_needsRefresh = true; } } void ViewMode::updateIcons() { for(QTreeWidgetItemIterator it(m_playlistBox); *it; ++it) { PlaylistBox::Item *i = static_cast(*it); i->setIcon(0, QIcon::fromTheme(i->iconName())); } } void ViewMode::setupItem(PlaylistBox::Item *item) const { Q_UNUSED(item); - // FIXME - /*const PlaylistBox *box = item->listView(); - const int width = box->width() - box->verticalScrollBar()->width() - border * 2; - const int baseHeight = 2 * box->itemMargin() + 32 + border * 2; - const QFontMetrics fm = box->fontMetrics(); - item->setHeight(baseHeight + (fm.height() - fm.descent()) * lines(item, fm, width).count());*/ -} - -void ViewMode::updateHeights() -{ - // FIXME - /*const int width = m_playlistBox->width() - m_playlistBox->verticalScrollBar()->width() - border * 2; - - const int baseHeight = 2 * m_playlistBox->itemMargin() + 32 + - border * 2 + 4; - const QFontMetrics fm = m_playlistBox->fontMetrics(); - - for(Q3ListViewItemIterator it(m_playlistBox); it.current(); ++it) { - PlaylistBox::Item *i = static_cast(it.current()); - m_lines[i] = lines(i, fm, width); - const int height = baseHeight + (fm.height() - fm.descent()) * m_lines[i].count(); - i->setHeight(height); - } - - m_needsRefresh = false;*/ } void ViewMode::paintDropIndicator(QPainter *painter, int width, int height) // static { static const int border = 1; static const int lineWidth = 2; QPen oldPen = painter->pen(); QPen newPen = oldPen; newPen.setWidth(lineWidth); newPen.setStyle(Qt::DotLine); painter->setPen(newPen); painter->drawRect(border, border, width - border * 2, height - border * 2); painter->setPen(oldPen); } -QStringList ViewMode::lines(const PlaylistBox::Item *item, - const QFontMetrics &fm, - int width) -{ - // Here 32 is a bit arbitrary, but that's the width of the icons in this - // mode and seems to a reasonable toLower bound. - - if(width < 32) - return QStringList(); - - QString line = item->text(); - - QStringList l; - - Q_UNUSED(fm); - - while(!line.isEmpty()) { - int textLength = line.length(); - // FIXME - /*while(textLength > 0 && - fm.width(line.mid(0, textLength).trimmed()) + - item->listView()->itemMargin() * 2 > width) - { - int i = line.lastIndexOf(QRegExp( "\\W"), textLength - 1); - if(i > 0) - textLength = i; - else - textLength--; - }*/ - - l.append(line.mid(0, textLength).trimmed()); - line = line.mid(textLength); - } - return l; -} - /////////////////////////////////////////////////////////////////////////////// // CompactViewMode //////////////////////////////////////////////////////////////////////////////// CompactViewMode::CompactViewMode(PlaylistBox *b) : ViewMode(b) { } CompactViewMode::~CompactViewMode() { } - // FIXME -/*void CompactViewMode::paintCell(PlaylistBox::Item *item, - QPainter *painter, - const QColorGroup &colorGroup, - int column, int width, int align) -{ - item->K3ListViewItem::paintCell(painter, colorGroup, column, width, align); - if(item == item->listView()->dropItem()) - paintDropIndicator(painter, width, item->height()); -}*/ - QString CompactViewMode::name() const { return i18nc("compact viewing mode", "Compact"); } void CompactViewMode::setShown(bool shown) { setVisible(shown); if(shown) { updateIcons(); - updateHeights(); } } -void CompactViewMode::updateHeights() -{ - // FIXME - /*for(Q3ListViewItemIterator it(playlistBox()); it.current(); ++it) - it.current()->setup();*/ -} - //////////////////////////////////////////////////////////////////////////////// // TreeViewMode //////////////////////////////////////////////////////////////////////////////// TreeViewMode::TreeViewMode(PlaylistBox *b) : CompactViewMode(b), m_dynamicListsFrozen(false), m_setup(false) { } TreeViewMode::~TreeViewMode() { } QString TreeViewMode::name() const { return i18n("Tree"); } void TreeViewMode::setShown(bool show) { CompactViewMode::setShown(show); playlistBox()->setRootIsDecorated(show); - // FIXME - /*if(show) { - PlaylistBox::Item *collectionItem = PlaylistBox::Item::collectionItem(); - - if(!collectionItem) - return; - - if(collectionItem && m_searchCategories.isEmpty()) + if(show) { + if(m_searchCategories.isEmpty()) setupDynamicPlaylists(); else { - foreach(PlaylistBox::Item *item, m_searchCategories) - item->setVisible(true); + for(auto &item : m_searchCategories) + item->setHidden(false); } if(!m_setup) { m_setup = true; - playlistBox()->setSorting(-1); + playlistBox()->setSortingEnabled(false); CollectionList::instance()->setupTreeViewEntries(this); - playlistBox()->setSorting(0); - playlistBox()->sort(); + playlistBox()->setSortingEnabled(true); } } else { - foreach(PlaylistBox::Item *item, m_searchCategories) - item->setVisible(false); - }*/ + for(auto &item : m_searchCategories) + item->setHidden(true); + } } void TreeViewMode::removeItem(const QString &item, unsigned column) { if(!m_setup) return; QString itemKey; if(column == PlaylistItem::ArtistColumn) itemKey = "artists" + item; else if(column == PlaylistItem::GenreColumn) itemKey = "genres" + item; else if(column == PlaylistItem::AlbumColumn) itemKey = "albums" + item; else { qCWarning(JUK_LOG) << "Unhandled column type " << column; return; } if(!m_treeViewItems.contains(itemKey)) return; TreeViewItemPlaylist *itemPlaylist = m_treeViewItems.value(itemKey, 0); if(m_dynamicListsFrozen) { m_pendingItemsToRemove << itemKey; return; } m_treeViewItems.remove(itemKey); itemPlaylist->deleteLater(); emit signalPlaylistDestroyed(itemPlaylist); } void TreeViewMode::addItems(const QStringList &items, unsigned column) { if(!m_setup) return; QString searchCategory; if(column == PlaylistItem::ArtistColumn) searchCategory = "artists"; else if(column == PlaylistItem::GenreColumn) searchCategory = "genres"; else if(column == PlaylistItem::AlbumColumn) searchCategory = "albums"; else { qCWarning(JUK_LOG) << "Unhandled column type " << column; return; } ColumnList columns; columns.append(column); PlaylistSearch::Component::MatchMode mode = PlaylistSearch::Component::ContainsWord; if(column != PlaylistItem::ArtistColumn) mode = PlaylistSearch::Component::Exact; PlaylistSearch::ComponentList components; PlaylistList playlists; playlists.append(CollectionList::instance()); QString itemKey; PlaylistBox::Item *itemParent = m_searchCategories.value(searchCategory, 0); foreach(const QString &item, items) { itemKey = searchCategory + item; if(m_treeViewItems.contains(itemKey)) continue; components.clear(); components.append(PlaylistSearch::Component(item, false, columns, mode)); PlaylistSearch s(playlists, components, PlaylistSearch::MatchAny); TreeViewItemPlaylist *p = new TreeViewItemPlaylist(playlistBox(), s, item); playlistBox()->setupPlaylist(p, "audio-midi", itemParent); m_treeViewItems.insert(itemKey, p); } } void TreeViewMode::setDynamicListsFrozen(bool frozen) { m_dynamicListsFrozen = frozen; if(frozen) return; foreach(const QString &pendingItem, m_pendingItemsToRemove) { m_treeViewItems[pendingItem]->deleteLater(); m_treeViewItems.remove(pendingItem); } m_pendingItemsToRemove.clear(); } void TreeViewMode::setupDynamicPlaylists() { PlaylistBox::Item *i; PlaylistBox::Item *collectionItem = PlaylistBox::Item::collectionItem(); i = new PlaylistBox::Item(collectionItem, "media-optical-audio", i18n("Artists")); m_searchCategories.insert("artists", i); i = new PlaylistBox::Item(collectionItem, "media-optical-audio", i18n("Albums")); m_searchCategories.insert("albums", i); i = new PlaylistBox::Item(collectionItem, "media-optical-audio", i18n("Genres")); m_searchCategories.insert("genres", i); } // vim: set et sw=4 tw=0 sta: diff --git a/viewmode.h b/viewmode.h index 22d36922..f7d0feef 100644 --- a/viewmode.h +++ b/viewmode.h @@ -1,157 +1,147 @@ /** * Copyright (C) 2003-2004 Scott Wheeler * Copyright (C) 2007 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 JUK_VIEWMODE_H #define JUK_VIEWMODE_H #include #include #include #include "playlistbox.h" class QPainter; class QColorGroup; class ViewMode : public QObject { Q_OBJECT public: explicit ViewMode(PlaylistBox *b); virtual ~ViewMode(); virtual QString name() const; virtual void setShown(bool shown); /*virtual void paintCell(PlaylistBox::Item *item, QPainter *painter, const QColorGroup &colorGroup, int column, int width, int align);*/ virtual bool eventFilter(QObject *watched, QEvent *e) override; void queueRefresh() { m_needsRefresh = true; } virtual void setupItem(PlaylistBox::Item *item) const; virtual void setupDynamicPlaylists() {} /** * If the view mode has dynamic lists, this function is used to temporarily * freeze them to prevent them from deleting dynamic elements. */ virtual void setDynamicListsFrozen(bool /* frozen */) {} /** * Used for dynamic view modes. This function will be called when \p items * are added to \p column (even if the view mode hasn't been shown yet). */ virtual void addItems(const QStringList &items, unsigned column) { Q_UNUSED(items); Q_UNUSED(column); } /** * Used for dynamic view modes. This function will be called when \p item * is removed from \p column (even if the view mode hasn't been shown yet). */ virtual void removeItem(const QString &item, unsigned column) { Q_UNUSED(item); Q_UNUSED(column); } protected: PlaylistBox *playlistBox() const { return m_playlistBox; } bool visible() const { return m_visible; } void setVisible(bool v) { m_visible = v; } void updateIcons(); - virtual void updateHeights(); static void paintDropIndicator(QPainter *painter, int width, int height); private: - static QStringList lines(const PlaylistBox::Item *item, const QFontMetrics &fm, int width); - PlaylistBox *m_playlistBox; bool m_visible; bool m_needsRefresh; QMap m_lines; static const int border = 4; }; //////////////////////////////////////////////////////////////////////////////// class CompactViewMode : public ViewMode { Q_OBJECT public: explicit CompactViewMode(PlaylistBox *b); virtual ~CompactViewMode(); virtual QString name() const override; virtual void setShown(bool shown) override; - /*virtual void paintCell(PlaylistBox::Item *item, - QPainter *painter, - const QColorGroup &colorGroup, - int column, int width, int align);*/ - virtual void setupItem(PlaylistBox::Item *item) const override { item->setup(); } -protected: - virtual void updateHeights() override; }; //////////////////////////////////////////////////////////////////////////////// class TreeViewItemPlaylist; class TreeViewMode final : public CompactViewMode { Q_OBJECT public: explicit TreeViewMode(PlaylistBox *l); virtual ~TreeViewMode(); virtual QString name() const override; virtual void setShown(bool shown) override; virtual void setupDynamicPlaylists() override; virtual void setDynamicListsFrozen(bool frozen) override; virtual void removeItem(const QString &item, unsigned column) override; virtual void addItems(const QStringList &items, unsigned column) override; signals: void signalPlaylistDestroyed(Playlist*); private: QMap m_searchCategories; QMap m_treeViewItems; QStringList m_pendingItemsToRemove; bool m_dynamicListsFrozen; bool m_setup; }; #endif // vim: set et sw=4 tw=0 sta: