File indexing completed on 2023-05-30 11:30:49

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