File indexing completed on 2021-12-21 13:27:58

0001 /**
0002  * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org>
0003  * Copyright (C) 2021      Michael Pyne  <mpyne@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify it under
0006  * the terms of the GNU General Public License as published by the Free Software
0007  * Foundation; either version 2 of the License, or (at your option) any later
0008  * version.
0009  *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU General Public License along with
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #include "playlistbox.h"
0019 
0020 #include <kmessagebox.h>
0021 #include <kactioncollection.h>
0022 #include <ktoggleaction.h>
0023 #include <kselectaction.h>
0024 #include <kconfiggroup.h>
0025 #include <KSharedConfig>
0026 
0027 #include <QAction>
0028 #include <QIcon>
0029 #include <QMenu>
0030 #include <QPainter>
0031 #include <QTimer>
0032 #include <QDragLeaveEvent>
0033 #include <QList>
0034 #include <QDragMoveEvent>
0035 #include <QKeyEvent>
0036 #include <QDropEvent>
0037 #include <QMouseEvent>
0038 #include <QFileInfo>
0039 #include <QTime>
0040 #include <QApplication>
0041 #include <QClipboard>
0042 #include <QHeaderView>
0043 #include <QElapsedTimer>
0044 
0045 #include "actioncollection.h"
0046 #include "cache.h"
0047 #include "collectionlist.h"
0048 #include "dbuscollectionproxy.h"
0049 #include "dynamicplaylist.h"
0050 #include "historyplaylist.h"
0051 #include "iconsupport.h"
0052 #include "juk_debug.h"
0053 #include "playermanager.h"
0054 #include "playlist.h"
0055 #include "searchplaylist.h"
0056 #include "tagtransactionmanager.h"
0057 #include "treeviewitemplaylist.h"
0058 #include "upcomingplaylist.h"
0059 #include "viewmode.h"
0060 
0061 using namespace ActionCollection; // ""_act and others
0062 using namespace IconSupport;      // ""_icon
0063 
0064 ////////////////////////////////////////////////////////////////////////////////
0065 // PlaylistBox public methods
0066 ////////////////////////////////////////////////////////////////////////////////
0067 
0068 PlaylistBox::PlaylistBox(PlayerManager *player, QWidget *parent, QStackedWidget *playlistStack)
0069   : QTreeWidget(parent)
0070   , PlaylistCollection(player, playlistStack)
0071 {
0072     readConfig();
0073     setHeaderLabel("Playlists");
0074     setRootIsDecorated(false);
0075     setContextMenuPolicy(Qt::CustomContextMenu);
0076     viewport()->setAcceptDrops(true);
0077     setDragDropMode(QAbstractItemView::DropOnly);
0078     setDropIndicatorShown(true);
0079 
0080     setColumnCount(2); // Use fake column for sorting
0081     setColumnHidden(1, true);
0082     setSortingEnabled(true);
0083     sortByColumn(1, Qt::AscendingOrder);
0084 
0085     header()->blockSignals(true);
0086     header()->hide();
0087     header()->blockSignals(false);
0088 
0089     setSelectionMode(QAbstractItemView::ExtendedSelection);
0090 
0091     m_contextMenu = new QMenu(this);
0092 
0093     m_contextMenu->addAction(action("file_new"));
0094     m_contextMenu->addAction(action("renamePlaylist"));
0095     m_contextMenu->addAction(action("editSearch"));
0096     m_contextMenu->addAction(action("duplicatePlaylist"));
0097     m_contextMenu->addAction(action("reloadPlaylist"));
0098     m_contextMenu->addAction(action("deleteItemPlaylist"));
0099     m_contextMenu->addAction(action("file_save"));
0100     m_contextMenu->addAction(action("file_save_as"));
0101 
0102     m_contextMenu->addSeparator();
0103 
0104     // add the view modes stuff
0105 
0106     KSelectAction *viewModeAction =
0107         new KSelectAction("view-choose"_icon, i18n("View Modes"), ActionCollection::actions());
0108     ActionCollection::actions()->addAction("viewModeMenu", viewModeAction);
0109 
0110     ViewMode* viewmode = new ViewMode(this);
0111     m_viewModes.append(viewmode);
0112     viewModeAction->addAction("view-list-details"_icon, viewmode->name());
0113 
0114     CompactViewMode* compactviewmode = new CompactViewMode(this);
0115     m_viewModes.append(compactviewmode);
0116     viewModeAction->addAction("view-list-text"_icon, compactviewmode->name());
0117 
0118     // TODO: Fix the broken tree view mode
0119 #if 0
0120     TreeViewMode* treeviewmode = new TreeViewMode(this);
0121     m_viewModes.append(treeviewmode);
0122     viewModeAction->addAction("view-list-tree"_icon, treeviewmode->name());
0123 #endif
0124 
0125     CollectionList::initialize(this);
0126 
0127     viewModeAction->setCurrentItem(m_viewModeIndex);
0128     m_viewModes[m_viewModeIndex]->setShown(true);
0129 
0130     raise(CollectionList::instance());
0131 
0132     m_contextMenu->addAction(viewModeAction);
0133     connect(viewModeAction, &QAction::triggered,
0134             this, &PlaylistBox::slotSetViewMode);
0135 
0136     connect(this, &PlaylistBox::itemSelectionChanged,
0137             this, &PlaylistBox::slotPlaylistChanged);
0138 
0139     connect(this, &PlaylistBox::itemDoubleClicked,
0140             this, &PlaylistBox::slotDoubleClicked);
0141 
0142     connect(this, &PlaylistBox::customContextMenuRequested,
0143             this, &PlaylistBox::slotShowContextMenu);
0144 
0145     connect(this, &PlaylistBox::signalPlayFile,
0146             player, qOverload<const FileHandle &>(&PlayerManager::play));
0147 
0148     const auto *tagManager = TagTransactionManager::instance();
0149     connect(tagManager, &TagTransactionManager::signalAboutToModifyTags,
0150             this,       &PlaylistBox::slotFreezePlaylists);
0151     connect(tagManager, &TagTransactionManager::signalDoneModifyingTags,
0152             this,       &PlaylistBox::slotUnfreezePlaylists);
0153 
0154     setupUpcomingPlaylist();
0155 
0156     const auto *collectionList = CollectionList::instance();
0157     connect(collectionList, &CollectionList::signalNewTag,
0158             this,           &PlaylistBox::slotAddItem);
0159     connect(collectionList, &CollectionList::signalRemovedTag,
0160             this,           &PlaylistBox::slotRemoveItem);
0161     connect(collectionList, &CollectionList::cachedItemsLoaded,
0162             this,           &PlaylistBox::slotLoadCachedPlaylists);
0163 
0164     KToggleAction *historyAction =
0165         new KToggleAction("view-history"_icon, i18n("Show &History"), ActionCollection::actions());
0166     ActionCollection::actions()->addAction("showHistory", historyAction);
0167     connect(historyAction, &KToggleAction::triggered,
0168             this,          &PlaylistBox::slotSetHistoryPlaylistEnabled);
0169 
0170     m_showTimer = new QTimer(this);
0171     m_showTimer->setSingleShot(true);
0172     m_showTimer->setInterval(500);
0173     connect(m_showTimer, &QTimer::timeout,
0174             this,        &PlaylistBox::slotShowDropTarget);
0175 
0176     // hook up to the D-Bus
0177     (void) new DBusCollectionProxy(this, this);
0178 }
0179 
0180 PlaylistBox::~PlaylistBox()
0181 {
0182     PlaylistList l;
0183     CollectionList *collection = CollectionList::instance();
0184     for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) {
0185         Item *item = static_cast<Item *>(*it);
0186         if(item->playlist() && item->playlist() != collection)
0187             l.append(item->playlist());
0188     }
0189 
0190     Cache::savePlaylists(l);
0191     saveConfig();
0192 
0193     // Some view modes use event filters onto sibling widgets which may be
0194     // destroyed before the view mode.
0195     // Manually delete the view modes instead.
0196     qDeleteAll(m_viewModes);
0197     m_viewModes.clear();
0198 }
0199 
0200 void PlaylistBox::raise(Playlist *playlist)
0201 {
0202     if(!playlist)
0203         return;
0204 
0205     Item *i = m_playlistDict.value(playlist, 0);
0206 
0207     if(i) {
0208         clearSelection();
0209         setCurrentItem(i);
0210 
0211         setSingleItem(i);
0212         scrollToItem(currentItem());
0213     }
0214     else
0215         PlaylistCollection::raise(playlist);
0216 
0217     slotPlaylistChanged();
0218 }
0219 
0220 void PlaylistBox::duplicate()
0221 {
0222     Item *item = static_cast<Item *>(currentItem());
0223     if(!item || !item->playlist())
0224         return;
0225 
0226     QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"), item->text(0));
0227 
0228     if(name.isNull())
0229         return;
0230 
0231     Playlist *p = new Playlist(this, name);
0232     p->createItems(item->playlist()->items());
0233 }
0234 
0235 void PlaylistBox::scanFolders()
0236 {
0237     PlaylistCollection::scanFolders();
0238     emit startupComplete();
0239 }
0240 
0241 bool PlaylistBox::requestPlaybackFor(const FileHandle &file)
0242 {
0243     emit signalPlayFile(file);
0244     return true;
0245 }
0246 
0247 ////////////////////////////////////////////////////////////////////////////////
0248 // PlaylistBox public slots
0249 ////////////////////////////////////////////////////////////////////////////////
0250 
0251 void PlaylistBox::paste()
0252 {
0253     // TODO: Reimplement
0254 }
0255 
0256 ////////////////////////////////////////////////////////////////////////////////
0257 // PlaylistBox protected methods
0258 ////////////////////////////////////////////////////////////////////////////////
0259 
0260 void PlaylistBox::slotFreezePlaylists()
0261 {
0262     setDynamicListsFrozen(true);
0263 }
0264 
0265 void PlaylistBox::slotUnfreezePlaylists()
0266 {
0267     setDynamicListsFrozen(false);
0268 }
0269 
0270 void PlaylistBox::slotPlaylistDataChanged()
0271 {
0272     if(m_savePlaylistTimer)
0273         m_savePlaylistTimer->start(); // Restarts the timer if it's already running.
0274 }
0275 
0276 void PlaylistBox::slotSetHistoryPlaylistEnabled(bool enable)
0277 {
0278     setHistoryPlaylistEnabled(enable);
0279 }
0280 
0281 void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName)
0282 {
0283     setupPlaylist(playlist, iconName, nullptr);
0284 }
0285 
0286 void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem)
0287 {
0288     connect(playlist, &Playlist::signalPlaylistItemsDropped,
0289             this,     &PlaylistBox::slotPlaylistItemsDropped);
0290     connect(playlist, &Playlist::signalMoveFocusAway,
0291             this,     &PlaylistBox::signalMoveFocusAway);
0292 
0293     PlaylistCollection::setupPlaylist(playlist, iconName);
0294 
0295     if(parentItem)
0296         new Item(parentItem, iconName, playlist->name(), playlist);
0297     else
0298         new Item(this, iconName, playlist->name(), playlist);
0299 }
0300 
0301 void PlaylistBox::removePlaylist(Playlist *playlist)
0302 {
0303     // Could be false if setup() wasn't run yet.
0304     if(m_playlistDict.contains(playlist)) {
0305         removeNameFromDict(m_playlistDict[playlist]->text(0));
0306         delete m_playlistDict[playlist]; // Delete the Item*
0307     }
0308 
0309     removeFileFromDict(playlist->fileName());
0310     m_playlistDict.remove(playlist);
0311 }
0312 
0313 Qt::DropActions PlaylistBox::supportedDropActions() const
0314 {
0315     return Qt::CopyAction;
0316 }
0317 
0318 bool PlaylistBox::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
0319 {
0320     Q_UNUSED(index);
0321 
0322     // The *parent* item won't be null, but index should be zero except in the
0323     // still-broken "tree view" mode.
0324 
0325     if(!parent || action != Qt::CopyAction || !data->hasUrls()) {
0326         return false;
0327     }
0328 
0329     auto *playlistItem = static_cast<Item *>(parent);
0330     if(!playlistItem) {
0331         return false;
0332     }
0333 
0334     auto *playlist = playlistItem->playlist();
0335     const auto droppedUrls = data->urls();
0336     PlaylistItem *lastItem = nullptr;
0337 
0338     for(const auto &url : droppedUrls) {
0339         lastItem = playlist->createItem(FileHandle(url.toLocalFile()), lastItem);
0340     }
0341 
0342     return true;
0343 }
0344 
0345 QStringList PlaylistBox::mimeTypes() const
0346 {
0347     auto result = QTreeWidget::mimeTypes();
0348 
0349     // Need to add Playlists's mime type to convince QTreeWidget to allow it as
0350     // a drop option.
0351     result.append(QLatin1String("text/uri-list"));
0352 
0353     return result;
0354 }
0355 
0356 ////////////////////////////////////////////////////////////////////////////////
0357 // PlaylistBox private methods
0358 ////////////////////////////////////////////////////////////////////////////////
0359 
0360 void PlaylistBox::readConfig()
0361 {
0362     KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox");
0363     m_viewModeIndex = config.readEntry("ViewMode", 0);
0364 
0365     // TODO Restore ability to use Tree View once fixed.
0366     if(m_viewModeIndex == 2) {
0367         m_viewModeIndex = 0;
0368     }
0369 }
0370 
0371 void PlaylistBox::saveConfig()
0372 {
0373     KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox");
0374     config.writeEntry("ViewMode", action<KSelectAction>("viewModeMenu")->currentItem());
0375     KSharedConfig::openConfig()->sync();
0376 }
0377 
0378 void PlaylistBox::remove()
0379 {
0380     const ItemList items = selectedBoxItems();
0381 
0382     if(items.isEmpty())
0383         return;
0384 
0385     QStringList files;
0386     QStringList names;
0387 
0388     for(const auto &item : items) {
0389         if(!item || !item->playlist()) {
0390             qFatal("Ran into an empty item or item playlist when removing playlists!");
0391         }
0392 
0393         if (!item->playlist()->fileName().isEmpty() &&
0394             QFileInfo::exists(item->playlist()->fileName()))
0395         {
0396             files.append(item->playlist()->fileName());
0397         }
0398 
0399         names.append(item->playlist()->name());
0400     }
0401 
0402     if(!files.isEmpty()) {
0403         int remove = KMessageBox::warningYesNoCancelList(
0404             this, i18n("Do you want to delete these files from the disk as well?"), files, QString(), KStandardGuiItem::del(), KGuiItem(i18n("Keep")));
0405 
0406         if(remove == KMessageBox::Yes) {
0407             QStringList couldNotDelete;
0408             for(const auto &playlistFile : qAsConst(files)) {
0409                 if(!QFile::remove(playlistFile))
0410                     couldNotDelete.append(playlistFile);
0411             }
0412 
0413             if(!couldNotDelete.isEmpty())
0414                 KMessageBox::errorList(this, i18n("Could not delete these files."), couldNotDelete);
0415         }
0416         else if(remove == KMessageBox::Cancel)
0417             return;
0418     }
0419     else if(items.count() > 1 || items.front()->playlist() != upcomingPlaylist()) {
0420         if(KMessageBox::Cancel == KMessageBox::warningContinueCancelList(
0421             this,
0422             i18n("Are you sure you want to remove these "
0423                "playlists from your collection?"),
0424             names,
0425             i18n("Remove Items?"),
0426             KGuiItem(i18n("&Remove"), "user-trash")))
0427         {
0428             return;
0429         }
0430     }
0431 
0432     for(const auto &item : items) {
0433         if(item != Item::collectionItem() &&
0434            !item->playlist()->readOnly())
0435         {
0436             if(item->playlist() != upcomingPlaylist())
0437                 delete item;
0438             else {
0439                 action<KToggleAction>("showUpcoming")->setChecked(false);
0440                 setUpcomingPlaylistEnabled(false);
0441             }
0442         }
0443     }
0444 
0445     setSingleItem(Item::collectionItem());
0446 }
0447 
0448 void PlaylistBox::setDynamicListsFrozen(bool frozen)
0449 {
0450     for(auto &playlistBoxItem : qAsConst(m_viewModes)) {
0451         playlistBoxItem->setDynamicListsFrozen(frozen);
0452     }
0453 }
0454 
0455 void PlaylistBox::slotSavePlaylists()
0456 {
0457     qCDebug(JUK_LOG) << "Auto-saving playlists.";
0458 
0459     PlaylistList l;
0460     CollectionList *collection = CollectionList::instance();
0461     for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) {
0462         Item *item = static_cast<Item *>(*it);
0463         if(item->playlist() && item->playlist() != collection)
0464             l.append(item->playlist());
0465     }
0466 
0467     Cache::savePlaylists(l);
0468 }
0469 
0470 void PlaylistBox::slotShowDropTarget()
0471 {
0472     if(m_dropItem) raise(m_dropItem->playlist());
0473 }
0474 
0475 void PlaylistBox::slotAddItem(const QString &tag, unsigned column)
0476 {
0477     for(auto &viewMode : qAsConst(m_viewModes)) {
0478         viewMode->addItems(QStringList(tag), column);
0479     }
0480 }
0481 
0482 void PlaylistBox::slotRemoveItem(const QString &tag, unsigned column)
0483 {
0484     for(auto &viewMode : qAsConst(m_viewModes)) {
0485         viewMode->removeItem(tag, column);
0486     }
0487 }
0488 
0489 void PlaylistBox::mousePressEvent(QMouseEvent *e)
0490 {
0491     if(e->button() == Qt::LeftButton)
0492         m_doingMultiSelect = true;
0493     QTreeWidget::mousePressEvent(e);
0494 }
0495 
0496 void PlaylistBox::mouseReleaseEvent(QMouseEvent *e)
0497 {
0498     if(e->button() == Qt::LeftButton) {
0499         m_doingMultiSelect = false;
0500         slotPlaylistChanged();
0501     }
0502     QTreeWidget::mouseReleaseEvent(e);
0503 }
0504 
0505 void PlaylistBox::keyPressEvent(QKeyEvent *e)
0506 {
0507     if((e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) && e->modifiers() == Qt::ShiftModifier)
0508         m_doingMultiSelect = true;
0509     QTreeWidget::keyPressEvent(e);
0510 }
0511 
0512 void PlaylistBox::keyReleaseEvent(QKeyEvent *e)
0513 {
0514     if(m_doingMultiSelect && e->key() == Qt::Key_Shift) {
0515         m_doingMultiSelect = false;
0516         slotPlaylistChanged();
0517     }
0518     QTreeWidget::keyReleaseEvent(e);
0519 }
0520 
0521 PlaylistBox::ItemList PlaylistBox::selectedBoxItems()
0522 {
0523     ItemList l;
0524 
0525     for(QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected)
0526             ; *it
0527             ; ++it)
0528     {
0529         l.append(static_cast<Item *>(*it));
0530     }
0531 
0532     return l;
0533 }
0534 
0535 void PlaylistBox::setSingleItem(QTreeWidgetItem *item)
0536 {
0537     setSelectionMode(QAbstractItemView::SingleSelection);
0538     setCurrentItem(item);
0539     setSelectionMode(QAbstractItemView::ExtendedSelection);
0540 }
0541 
0542 void PlaylistBox::dragMoveEvent(QDragMoveEvent* event)
0543 {
0544     QTreeWidget::dragMoveEvent(event);
0545 
0546     Item* hovered_item = static_cast<Item*>(itemAt(event->pos()));
0547     if(hovered_item != m_dropItem){
0548         m_dropItem = hovered_item;
0549         if(m_dropItem) m_showTimer->start();
0550         else m_showTimer->stop();
0551     };
0552 }
0553 
0554 void PlaylistBox::dragLeaveEvent(QDragLeaveEvent* event)
0555 {
0556     QTreeWidget::dragLeaveEvent(event);
0557     m_showTimer->stop();
0558 }
0559 
0560 ////////////////////////////////////////////////////////////////////////////////
0561 // PlaylistBox private slots
0562 ////////////////////////////////////////////////////////////////////////////////
0563 
0564 void PlaylistBox::slotPlaylistChanged()
0565 {
0566     // Don't update while the mouse is pressed down.
0567 
0568     if(m_doingMultiSelect)
0569         return;
0570 
0571     const ItemList items = selectedBoxItems();
0572     m_hasSelection = !items.isEmpty();
0573 
0574     const bool allowReload = std::any_of(items.begin(), items.end(),
0575         [](const auto &item) {
0576             return item->playlist() && item->playlist()->canReload();
0577         });
0578 
0579     PlaylistList playlists;
0580     for(const auto &playlistBoxItem : items) {
0581         auto p = playlistBoxItem->playlist();
0582         if(p) {
0583             playlists.append(p);
0584         }
0585     }
0586 
0587     bool singlePlaylist = playlists.count() == 1;
0588 
0589     if(playlists.isEmpty() ||
0590        (singlePlaylist &&
0591         (playlists.front() == CollectionList::instance() ||
0592          playlists.front()->readOnly())))
0593     {
0594         action("file_save")->setEnabled(false);
0595         action("file_save_as")->setEnabled(false);
0596         action("renamePlaylist")->setEnabled(false);
0597         action("deleteItemPlaylist")->setEnabled(false);
0598     }
0599     else {
0600         action("file_save")->setEnabled(true);
0601         action("file_save_as")->setEnabled(true);
0602         action("renamePlaylist")->setEnabled(playlists.count() == 1);
0603         action("deleteItemPlaylist")->setEnabled(true);
0604     }
0605     action("reloadPlaylist")->setEnabled(allowReload);
0606     action("duplicatePlaylist")->setEnabled(!playlists.isEmpty());
0607 
0608     action("editSearch")->setEnabled(singlePlaylist &&
0609                                      playlists.front()->searchIsEditable());
0610 
0611     if(singlePlaylist) {
0612         PlaylistCollection::raise(playlists.front());
0613 
0614         if(playlists.front() == upcomingPlaylist()) {
0615             action("deleteItemPlaylist")->setText(i18n("Hid&e"));
0616             action("deleteItemPlaylist")->setIcon("list-remove"_icon);
0617         }
0618         else {
0619             action("deleteItemPlaylist")->setText(i18n("R&emove"));
0620             action("deleteItemPlaylist")->setIcon("user-trash"_icon);
0621         }
0622     }
0623     else if(!playlists.isEmpty())
0624         createDynamicPlaylist(playlists);
0625 }
0626 
0627 void PlaylistBox::slotDoubleClicked(QTreeWidgetItem *item)
0628 {
0629     if(!item)
0630         return;
0631     auto *playlist = static_cast<Item *>(item)->playlist();
0632 
0633     playlist->slotBeginPlayback();
0634 }
0635 
0636 void PlaylistBox::slotShowContextMenu(const QPoint &point)
0637 {
0638     m_contextMenu->popup(mapToGlobal(point));
0639 }
0640 
0641 void PlaylistBox::slotPlaylistItemsDropped(Playlist *p)
0642 {
0643     raise(p);
0644 }
0645 
0646 void PlaylistBox::slotSetViewMode(int index)
0647 {
0648     if(index == m_viewModeIndex)
0649         return;
0650 
0651     viewMode()->setShown(false);
0652     m_viewModeIndex = index;
0653     viewMode()->setShown(true);
0654 }
0655 
0656 void PlaylistBox::setupItem(Item *item)
0657 {
0658     m_playlistDict.insert(item->playlist(), item);
0659     viewMode()->queueRefresh();
0660 }
0661 
0662 void PlaylistBox::setupUpcomingPlaylist()
0663 {
0664     KConfigGroup config(KSharedConfig::openConfig(), "Playlists");
0665     bool enable = config.readEntry("showUpcoming", false);
0666 
0667     setUpcomingPlaylistEnabled(enable);
0668     action<KToggleAction>("showUpcoming")->setChecked(enable);
0669 }
0670 
0671 
0672 void PlaylistBox::slotLoadCachedPlaylists()
0673 {
0674     qCDebug(JUK_LOG) << "Loading cached playlists.";
0675     QElapsedTimer stopwatch;
0676     stopwatch.start();
0677 
0678     Cache::loadPlaylists(this);
0679 
0680     qCDebug(JUK_LOG) << "Cached playlists loaded, took" << stopwatch.elapsed() << "ms";
0681 
0682     // Auto-save playlists after they change.
0683     m_savePlaylistTimer = new QTimer(this);
0684     m_savePlaylistTimer->setInterval(3000); // 3 seconds with no change? -> commit
0685     m_savePlaylistTimer->setSingleShot(true);
0686     connect(m_savePlaylistTimer, &QTimer::timeout,
0687             this, &PlaylistBox::slotSavePlaylists);
0688 
0689     clearSelection();
0690     setCurrentItem(m_playlistDict[CollectionList::instance()]);
0691 
0692     QTimer::singleShot(0, this, [this]() {
0693             CollectionList::instance()->slotCheckCache();
0694             this->scanFolders();
0695         });
0696 }
0697 
0698 ////////////////////////////////////////////////////////////////////////////////
0699 // PlaylistBox::Item protected methods
0700 ////////////////////////////////////////////////////////////////////////////////
0701 
0702 PlaylistBox::Item *PlaylistBox::Item::m_collectionItem = nullptr;
0703 
0704 PlaylistBox::Item::Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l)
0705     : QObject(listBox), QTreeWidgetItem(listBox, QStringList(text)),
0706       m_playlist(l), m_iconName(icon), m_sortedFirst(false)
0707 {
0708     init();
0709 }
0710 
0711 PlaylistBox::Item::Item(Item *parent, const QString &icon, const QString &text, Playlist *l)
0712     : QObject(parent->listView()), QTreeWidgetItem(parent, QStringList(text)),
0713     m_playlist(l), m_iconName(icon), m_sortedFirst(false)
0714 {
0715     init();
0716 }
0717 
0718 PlaylistBox::Item::~Item()
0719 {
0720 
0721 }
0722 
0723 void PlaylistBox::Item::setup()
0724 {
0725     listView()->viewMode()->setupItem(this);
0726 }
0727 
0728 ////////////////////////////////////////////////////////////////////////////////
0729 // PlaylistBox::Item protected slots
0730 ////////////////////////////////////////////////////////////////////////////////
0731 
0732 void PlaylistBox::Item::slotSetName(const QString &name)
0733 {
0734     setText(0, name); // Display name
0735     setText(1, sortTextFor(name));
0736     setSelected(true);
0737 
0738     treeWidget()->scrollToItem(this);
0739 }
0740 
0741 void PlaylistBox::Item::playlistItemDataChanged()
0742 {
0743     // This avoids spuriously re-saving all playlists just because play queue
0744     // changes.
0745     if(m_playlist != listView()->upcomingPlaylist())
0746         listView()->slotPlaylistDataChanged();
0747 }
0748 
0749 ////////////////////////////////////////////////////////////////////////////////
0750 // PlaylistBox::Item private methods
0751 ////////////////////////////////////////////////////////////////////////////////
0752 
0753 void PlaylistBox::Item::init()
0754 {
0755     PlaylistBox *list = listView();
0756 
0757     list->setupItem(this);
0758 
0759     const QString itemText(text());
0760     setIcon(0, QIcon::fromTheme(m_iconName));
0761     list->addNameToDict(itemText);
0762 
0763     if(m_playlist) {
0764         connect(m_playlist, &Playlist::signalNameChanged,
0765                 this,       &Item::slotSetName);
0766         connect(m_playlist, &Playlist::signalEnableDirWatch,
0767                 this, [list](bool enable) {
0768                     list->enableDirWatch(enable);
0769                 });
0770     }
0771 
0772     if(m_playlist == CollectionList::instance()) {
0773         m_sortedFirst = true;
0774         m_collectionItem = this;
0775         list->viewMode()->setupDynamicPlaylists();
0776     }
0777 
0778     if(m_playlist == list->historyPlaylist() || m_playlist == list->upcomingPlaylist())
0779         m_sortedFirst = true;
0780 
0781     setText(1, sortTextFor(itemText));
0782 
0783     connect(&(m_playlist->signaller), &PlaylistInterfaceSignaller::playingItemDataChanged, this, &PlaylistBox::Item::playlistItemDataChanged);
0784 }
0785 
0786 QString PlaylistBox::Item::sortTextFor(const QString &name) const
0787 {
0788     // Collection List goes before everything, then
0789     // playlists that 'sort first', then remainder of
0790     // playlists.
0791     const auto prefix
0792         = (playlist() == CollectionList::instance())
0793             ? QStringLiteral("0")
0794             : m_sortedFirst
0795                 ? QStringLiteral("1")
0796                 : QStringLiteral("2");
0797     return prefix + name;
0798 }
0799 
0800 // vim: set et sw=4 tw=0 sta: