1 #include <QAction>
2 #include <QClipboard>
3 #include <QDragEnterEvent>
4 #include <QGuiApplication>
5 #include <QMimeData>
6 #include <QInputDialog>
7 #include <QFileDialog>
8 #include <QMenu>
9 #include <QThread>
10 #include "playlistwindow.h"
11 #include "ui_playlistwindow.h"
12 #include "widgets/drawnplaylist.h"
13 #include "playlist.h"
14 #include "platform/unify.h"
15 
PlaylistWindow(QWidget * parent)16 PlaylistWindow::PlaylistWindow(QWidget *parent) :
17     QDockWidget(parent),
18     ui(new Ui::PlaylistWindow),
19     currentPlaylist(),
20     randomDevice(),
21     randomGenerator(randomDevice())
22 {
23     clipboard = new PlaylistSelection;
24 
25     ui->setupUi(this);
26     setObjectName("playlistWindow");
27     setWindowTitle(tr("Playlist"));
28     addNewTab(QUuid(), tr("Quick Playlist"));
29     addQuickQueue();
30     ui->searchHost->setVisible(false);
31     ui->searchField->installEventFilter(this);
32 
33     setupIconThemer();
34     connectSignalsToSlots();
35     Platform::disableAutomaticAccel(this);
36 }
37 
~PlaylistWindow()38 PlaylistWindow::~PlaylistWindow()
39 {
40     delete ui;
41     delete clipboard;
42 }
43 
setCurrentPlaylist(QUuid what)44 void PlaylistWindow::setCurrentPlaylist(QUuid what)
45 {
46     if (widgets.contains(what)) {
47         ui->tabWidget->setCurrentWidget(widgets[what]);
48         currentPlaylist = what;
49     }
50     updateCurrentPlaylist();
51 }
52 
clearPlaylist(QUuid what)53 void PlaylistWindow::clearPlaylist(QUuid what)
54 {
55     if (widgets.contains(what))
56         widgets[what]->removeAll();
57     updatePlaylistHasItems();
58 }
59 
addToPlaylist(const QUuid & playlist,const QList<QUrl> & what)60 QPair<QUuid, QUuid> PlaylistWindow::addToPlaylist(const QUuid &playlist, const QList<QUrl> &what)
61 {
62     QList<QUrl> filtered = Helpers::filterUrls(what);
63     QPair<QUuid, QUuid> info;
64     auto qdp = widgets.contains(playlist) ? widgets.value(playlist) : widgets[QUuid()];
65     for (QUrl &url : filtered) {
66         QPair<QUuid,QUuid> itemInfo = qdp->importUrl(url);
67         if (info.second.isNull())
68             info = itemInfo;
69     }
70     updatePlaylistHasItems();
71     return info;
72 }
73 
addToCurrentPlaylist(QList<QUrl> what)74 QPair<QUuid, QUuid> PlaylistWindow::addToCurrentPlaylist(QList<QUrl> what)
75 {
76     return addToPlaylist(currentPlaylist, what);
77 }
78 
urlToQuickPlaylist(QUrl what)79 QPair<QUuid, QUuid> PlaylistWindow::urlToQuickPlaylist(QUrl what)
80 {
81     auto pl = PlaylistCollection::getSingleton()->playlistOf(QUuid());
82     pl->clear();
83     widgets[QUuid()]->clear();
84     ui->tabWidget->setCurrentWidget(widgets[QUuid()]);
85     return addToCurrentPlaylist(QList<QUrl>() << what);
86 }
87 
isCurrentPlaylistEmpty()88 bool PlaylistWindow::isCurrentPlaylistEmpty()
89 {
90     auto pl = PlaylistCollection::getSingleton()->playlistOf(currentPlaylist);
91     return pl ? pl->isEmpty() : true;
92 }
93 
isPlaylistSingularFile(QUuid list)94 bool PlaylistWindow::isPlaylistSingularFile(QUuid list)
95 {
96     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
97     if (!pl || pl->count() != 1)
98         return false;
99     auto item = pl->itemFirst();
100     return item->url().isLocalFile();
101 }
102 
isPlaylistShuffle(QUuid list)103 bool PlaylistWindow::isPlaylistShuffle(QUuid list)
104 {
105     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
106     if (!pl)
107         return false;
108     return pl->shuffle();
109 }
110 
getItemAfter(QUuid list,QUuid item)111 QPair<QUuid,QUuid> PlaylistWindow::getItemAfter(QUuid list, QUuid item)
112 {
113     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
114     if (!pl)
115         return { QUuid(), QUuid() };
116     auto qpl = PlaylistCollection::queuePlaylist();
117     QPair<QUuid, QUuid> next = qpl->takeFirst();
118     if (!next.second.isNull())
119         return next;
120     QSharedPointer<Item> after;
121     if (pl->shuffle() && !pl->isEmpty()) {
122         std::uniform_int_distribution<> itemDistribution(0, pl->count()-1);
123         after = pl->itemAt(itemDistribution(randomGenerator));
124     } else {
125         after = pl->itemAfter(item);
126     }
127     if (!after)
128         return { QUuid(), QUuid() };
129     return { pl->uuid(), after->uuid() };
130 }
131 
getItemBefore(QUuid list,QUuid item)132 QUuid PlaylistWindow::getItemBefore(QUuid list, QUuid item)
133 {
134     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
135     if (!pl)
136         return QUuid();
137     QSharedPointer<Item> before = pl->itemBefore(item);
138     if (!before)
139         return QUuid();
140     return before->uuid();
141 }
142 
getUrlOf(QUuid list,QUuid item)143 QUrl PlaylistWindow::getUrlOf(QUuid list, QUuid item)
144 {
145     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
146     if (!pl)
147         return QUrl();
148     auto i = pl->itemOf(item);
149     if (!i)
150         return QUrl();
151     return i->url();
152 }
153 
getUrlOfFirst(QUuid list)154 QUrl PlaylistWindow::getUrlOfFirst(QUuid list)
155 {
156     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
157     auto item = pl->itemFirst();
158     if (item.isNull())
159         return QUrl();
160     return item->url();
161 }
162 
setMetadata(QUuid list,QUuid item,const QVariantMap & map)163 void PlaylistWindow::setMetadata(QUuid list, QUuid item, const QVariantMap &map)
164 {
165     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
166     if (!pl)
167         return;
168     auto i = pl->itemOf(item);
169     if (!i)
170         return;
171     i->setMetadata(map);
172 
173     auto qdp = currentPlaylistWidget();
174     if (qdp->uuid() == list)
175         qdp->viewport()->update();
176 
177 }
178 
replaceItem(QUuid list,QUuid item,const QList<QUrl> & urls)179 void PlaylistWindow::replaceItem(QUuid list, QUuid item, const QList<QUrl> &urls)
180 {
181     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
182     if (!pl)
183         return;
184 
185     QList<QUrl> filtered = Helpers::filterUrls(urls);
186     if (filtered.isEmpty()) {
187         // FIXME: remove the item that cannot played
188         return;
189     }
190 
191     QList<QUuid> addedItems = pl->replaceItem(item, filtered);
192     auto listWidget = widgets[list];
193     if (listWidget)
194         listWidget->addItemsAfter(item, addedItems);
195 
196     auto qdp = currentPlaylistWidget();
197     if (qdp->uuid() == list)
198         qdp->viewport()->update();
199 
200     updatePlaylistHasItems();
201 }
202 
extraPlayTimes(QUuid list,QUuid item)203 int PlaylistWindow::extraPlayTimes(QUuid list, QUuid item)
204 {
205     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
206     if (!pl)
207         return -1;
208     auto i = pl->itemOf(item);
209     return i ? i->extraPlayTimes() : -1;
210 }
211 
setExtraPlayTimes(QUuid list,QUuid item,int amount)212 void PlaylistWindow::setExtraPlayTimes(QUuid list, QUuid item, int amount)
213 {
214     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
215     if (!pl)
216         return;
217     auto i = pl->itemOf(item);
218     if (!i)
219         return;
220     i->setExtraPlayTimes(amount);
221 }
222 
deltaExtraPlayTimes(QUuid list,QUuid item,int delta)223 void PlaylistWindow::deltaExtraPlayTimes(QUuid list, QUuid item, int delta)
224 {
225     auto pl = PlaylistCollection::getSingleton()->playlistOf(list);
226     if (!pl)
227         return;
228     auto i = pl->itemOf(item);
229     if (!i)
230         return;
231     i->deltaExtraPlayTimes(delta);
232     widgets[list]->viewport()->repaint();
233 }
234 
tabsToVList() const235 QVariantList PlaylistWindow::tabsToVList() const
236 {
237     QVariantList qvl;
238     for (int i = 0; i < ui->tabWidget->count(); i++) {
239         auto widget = reinterpret_cast<DrawnPlaylist *>(ui->tabWidget->widget(i));
240         qvl.append(widget->toVMap());
241     }
242     return qvl;
243 }
244 
tabsFromVList(const QVariantList & qvl)245 void PlaylistWindow::tabsFromVList(const QVariantList &qvl)
246 {
247     ui->tabWidget->clear();
248     widgets.clear();
249     for (const QVariant &v : qvl) {
250         auto qdp = new DrawnPlaylist(PlaylistCollection::getSingleton());
251         qdp->setDisplayParser(&displayParser);
252         qdp->fromVMap(v.toMap());
253         connect(qdp, &DrawnPlaylist::itemDesired,
254                 this, &PlaylistWindow::itemDesired);
255         connect(qdp, &DrawnPlaylist::contextMenuRequested,
256                 this, &PlaylistWindow::playlist_contextMenuRequested);
257         auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
258         ui->tabWidget->addTab(qdp, pl->title());
259         widgets.insert(pl->uuid(), qdp);
260     }
261     if (widgets.count() < 1)
262         addNewTab(QUuid(), tr("Quick Playlist"));
263     updatePlaylistHasItems();
264 }
265 
eventFilter(QObject * obj,QEvent * event)266 bool PlaylistWindow::eventFilter(QObject *obj, QEvent *event)
267 {
268     Q_UNUSED(obj)
269     if (obj == ui->searchField && event->type() == QEvent::KeyPress) {
270         auto keyEvent = reinterpret_cast<QKeyEvent*>(event);
271         if (!keyEvent->modifiers() &&
272                 (keyEvent->key() == Qt::Key_Up ||
273                  keyEvent->key() == Qt::Key_Down)) {
274             if (keyEvent->key() == Qt::Key_Up)
275                 selectPrevious();
276             else
277                 selectNext();
278             return true;
279         }
280     }
281     return QDockWidget::eventFilter(obj, event);
282 }
283 
dragEnterEvent(QDragEnterEvent * event)284 void PlaylistWindow::dragEnterEvent(QDragEnterEvent *event)
285 {
286     if (event->mimeData()->hasUrls())
287         event->accept();
288 }
289 
dropEvent(QDropEvent * event)290 void PlaylistWindow::dropEvent(QDropEvent *event)
291 {
292     if (!event->mimeData()->hasUrls())
293         return;
294     addToCurrentPlaylist(event->mimeData()->urls());
295 }
296 
wheelEvent(QWheelEvent * event)297 void PlaylistWindow::wheelEvent(QWheelEvent *event)
298 {
299     // Don't pass scroll events up the chain.  They are used for e.g. tab
300     // switching when over the tab bar and also scrolling the playlists.
301     event->accept();
302 }
303 
setupIconThemer()304 void PlaylistWindow::setupIconThemer()
305 {
306     QVector<IconThemer::IconData> data {
307         { ui->newTab, "tab-new", {} },
308         { ui->closeTab, "tab-close", {} },
309         { ui->duplicateTab, "tab-duplicate", {} },
310         { ui->importList, "document-import", {} },
311         { ui->exportList, "document-export", {} },
312         { ui->visibleToQueue, "media-queue-visible", {} },
313         { ui->showQueue, "view-media-queue", {} }
314     };
315     for (const auto &d : data)
316         themer.addIconData(d);
317 }
318 
connectSignalsToSlots()319 void PlaylistWindow::connectSignalsToSlots()
320 {
321     connect(this, &PlaylistWindow::visibilityChanged,
322             this, &PlaylistWindow::self_visibilityChanged);
323     connect(this, &PlaylistWindow::dockLocationChanged,
324             this, &PlaylistWindow::self_dockLocationChanged);
325     connect(this->toggleViewAction(), &QAction::toggled,
326             this, &PlaylistWindow::viewActionChanged);
327 
328     connect(ui->newTab, &QPushButton::clicked,
329             this, &PlaylistWindow::newTab);
330     connect(ui->closeTab, &QPushButton::clicked,
331             this, &PlaylistWindow::closeTab);
332     connect(ui->duplicateTab, &QPushButton::clicked,
333             this, &PlaylistWindow::duplicateTab);
334     connect(ui->importList, &QPushButton::clicked,
335             this, &PlaylistWindow::importTab);
336     connect(ui->exportList, &QPushButton::clicked,
337             this, &PlaylistWindow::exportTab);
338     connect(ui->visibleToQueue, &QPushButton::clicked,
339             this, &PlaylistWindow::visibleToQueue);
340     connect(ui->showQueue, &QPushButton::clicked,
341             this, &PlaylistWindow::setQueueMode);
342 }
343 
currentPlaylistWidget()344 DrawnPlaylist *PlaylistWindow::currentPlaylistWidget()
345 {
346     return reinterpret_cast<DrawnPlaylist *>(ui->tabWidget->currentWidget());
347 }
348 
updateCurrentPlaylist()349 void PlaylistWindow::updateCurrentPlaylist()
350 {
351     auto qdp = currentPlaylistWidget();
352     if (!qdp)
353         return;
354     currentPlaylist = qdp->uuid();
355     setTabOrder(ui->tabWidget->focusProxy(), qdp);
356     setTabOrder(qdp, ui->searchField);
357     updatePlaylistHasItems();
358 }
359 
updatePlaylistHasItems()360 void PlaylistWindow::updatePlaylistHasItems()
361 {
362     auto qdp = currentPlaylistWidget();
363     if (!qdp)
364         return;
365     emit currentPlaylistHasItems(qdp->count() > 0);
366 }
367 
setPlaylistFilters(QString filterText)368 void PlaylistWindow::setPlaylistFilters(QString filterText)
369 {
370     for (auto &widget : widgets) {
371         widget->setFilter(filterText);
372     }
373     queueWidget->setFilter(filterText);
374 }
375 
addNewTab(QUuid playlist,QString title)376 void PlaylistWindow::addNewTab(QUuid playlist, QString title)
377 {
378     auto qdp = new DrawnPlaylist(PlaylistCollection::getSingleton());
379     qdp->setDisplayParser(&displayParser);
380     qdp->setUuid(playlist);
381     connect(qdp, &DrawnPlaylist::itemDesired, this, &PlaylistWindow::itemDesired);
382     connect(qdp, &DrawnPlaylist::contextMenuRequested,
383             this, &PlaylistWindow::playlist_contextMenuRequested);
384     widgets.insert(playlist, qdp);
385     ui->tabWidget->addTab(qdp, title);
386     ui->tabWidget->setCurrentWidget(qdp);
387 }
388 
addQuickQueue()389 void PlaylistWindow::addQuickQueue()
390 {
391     queueWidget = new DrawnQueue();
392     queueWidget->setDisplayParser(&displayParser);
393     queueWidget->setUuid(QUuid());
394     connect(queueWidget, &DrawnQueue::itemDesired,
395             this, &PlaylistWindow::itemDesired);
396     ui->quickPage->layout()->addWidget(queueWidget);
397 }
398 
setIconTheme(IconThemer::FolderMode mode,const QString & fallback,const QString & custom)399 void PlaylistWindow::setIconTheme(IconThemer::FolderMode mode,
400                                   const QString &fallback,
401                                   const QString &custom)
402 {
403     themer.setIconFolders(mode, fallback, custom);
404 }
405 
setHideFullscreen(bool hidden)406 void PlaylistWindow::setHideFullscreen(bool hidden)
407 {
408     hideFullscreen = hidden;
409 }
410 
activateItem(QUuid playlistUuid,QUuid itemUuid)411 bool PlaylistWindow::activateItem(QUuid playlistUuid, QUuid itemUuid)
412 {
413     if (!widgets.contains(playlistUuid))
414         return false;
415     auto qdp = widgets[playlistUuid];
416     qdp->scrollToItem(itemUuid);
417     qdp->setNowPlayingItem(itemUuid);
418     return true;
419 }
420 
changePlaylistSelection(QUrl itemUrl,QUuid playlistUuid,QUuid itemUuid)421 void PlaylistWindow::changePlaylistSelection( QUrl itemUrl, QUuid playlistUuid, QUuid itemUuid)
422 {
423     (void)itemUrl;
424     if (!activateItem(playlistUuid, itemUuid))
425         return;
426     auto pl = PlaylistCollection::getSingleton()->playlistOf(playlistUuid);
427     auto qpl = PlaylistCollection::queuePlaylist();
428     if (!itemUuid.isNull() && qpl->first().second == itemUuid) {
429         queueWidget->removeItem(itemUuid);
430     }
431 }
432 
addSimplePlaylist(QStringList data)433 void PlaylistWindow::addSimplePlaylist(QStringList data)
434 {
435 
436     auto pl = PlaylistCollection::getSingleton()->newPlaylist(tr("New Playlist"));
437     pl->fromStringList(data);
438     addNewTab(pl->uuid(), pl->title());
439 }
440 
addPlaylistByUuid(QUuid uuid)441 void PlaylistWindow::addPlaylistByUuid(QUuid uuid)
442 {
443     auto pl = PlaylistCollection::getSingleton()->playlistOf(uuid);
444     if (!pl) {
445         throw std::runtime_error("received a nullptr for a playlist");
446     }
447     addNewTab(pl->uuid(), pl->title());
448 }
449 
setDisplayFormatSpecifier(QString fmt)450 void PlaylistWindow::setDisplayFormatSpecifier(QString fmt)
451 {
452     displayParser.takeFormatString(fmt);
453     ui->tabWidget->currentWidget()->update();
454 }
455 
newTab()456 void PlaylistWindow::newTab()
457 {
458     bool ok;
459     QString title = QInputDialog::getText(this, tr("Enter Playlist Name"),
460                                            "Name", QLineEdit::Normal,
461                                           tr("New Playlist"), &ok);
462     if (!ok)
463         return;
464     else if (title.isEmpty())
465         title = tr("New Playlist");
466 
467     auto pl = PlaylistCollection::getSingleton()->newPlaylist(title.replace("&","+"));
468     addNewTab(pl->uuid(), pl->title());
469 }
470 
closeTab()471 void PlaylistWindow::closeTab()
472 {
473     int index = ui->tabWidget->currentIndex();
474     on_tabWidget_tabCloseRequested(index);
475     updateCurrentPlaylist();
476 }
477 
duplicateTab()478 void PlaylistWindow::duplicateTab()
479 {
480     auto origin = currentPlaylistWidget();
481     auto remote = PlaylistCollection::getSingleton()->clonePlaylist(origin->uuid());
482     addNewTab(remote->uuid(), remote->title());
483 }
484 
importTab()485 void PlaylistWindow::importTab()
486 {
487     QString file;
488     file = QFileDialog::getOpenFileName(this, tr("Import File"), QString(),
489                                         tr("Playlist files (*.m3u *.m3u8)"));
490     if (!file.isEmpty())
491         emit importPlaylist(file);
492 }
493 
exportTab()494 void PlaylistWindow::exportTab()
495 {
496     auto uuid = currentPlaylistWidget()->uuid();
497     savePlaylist(uuid);
498 }
499 
copy()500 void PlaylistWindow::copy()
501 {
502     clipboard->fromSelected(currentPlaylistWidget());
503 }
504 
copyQueue()505 void PlaylistWindow::copyQueue()
506 {
507     clipboard->fromQueue(currentPlaylistWidget());
508 }
509 
paste()510 void PlaylistWindow::paste()
511 {
512     clipboard->appendToPlaylist(currentPlaylistWidget());
513 }
514 
pasteQueue()515 void PlaylistWindow::pasteQueue()
516 {
517     clipboard->appendAndQuickQueue(currentPlaylistWidget());
518 }
519 
playCurrentItem()520 void PlaylistWindow::playCurrentItem()
521 {
522     auto qdp = currentPlaylistWidget();
523     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
524     auto itemUuid = qdp->currentItemUuid();
525     if (itemUuid.isNull())
526         return;
527     emit itemDesired(pl->uuid(), itemUuid);
528 }
529 
playActiveItem()530 bool PlaylistWindow::playActiveItem()
531 {
532     auto qdp = currentPlaylistWidget();
533     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
534     auto itemUuid = qdp->nowPlayingItem();
535     if (itemUuid.isNull())
536         return false;
537     emit itemDesired(pl->uuid(), itemUuid);
538     return true;
539 }
540 
selectNext()541 void PlaylistWindow::selectNext()
542 {
543     auto qdp = currentPlaylistWidget();
544     int index = qdp->currentRow();
545     if (index < qdp->count())
546         qdp->setCurrentRow(index + 1);
547 }
548 
selectPrevious()549 void PlaylistWindow::selectPrevious()
550 {
551     auto qdp = currentPlaylistWidget();
552     int index = qdp->currentRow();
553     if (index > 0)
554         qdp->setCurrentRow(index - 1);
555 }
556 
incExtraPlayTimes()557 void PlaylistWindow::incExtraPlayTimes()
558 {
559     auto qdp = currentPlaylistWidget();
560     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
561     auto incrementer = [pl](QUuid uuid) {
562         auto item = pl->itemOf(uuid);
563         if (Q_LIKELY(!item.isNull()))
564             item->incExtraPlayTimes();
565     };
566     qdp->traverseSelected(incrementer);
567     qdp->viewport()->update();
568 }
569 
decExtraPlayTimes()570 void PlaylistWindow::decExtraPlayTimes()
571 {
572     auto qdp = currentPlaylistWidget();
573     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
574     auto decrementer = [pl](QUuid uuid) {
575         auto item = pl->itemOf(uuid);
576         if (Q_LIKELY(!item.isNull()))
577             item->decExtraPlayTimes();
578     };
579     qdp->traverseSelected(decrementer);
580     qdp->viewport()->update();
581 }
582 
zeroExtraPlayTimes()583 void PlaylistWindow::zeroExtraPlayTimes()
584 {
585     auto qdp = currentPlaylistWidget();
586     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
587     auto zeroer = [pl](QUuid uuid) {
588         auto item = pl->itemOf(uuid);
589         if (Q_LIKELY(!item.isNull()))
590             item->setExtraPlayTimes(0);
591     };
592     qdp->traverseSelected(zeroer);
593     qdp->viewport()->update();
594 }
595 
activateNext()596 void PlaylistWindow::activateNext()
597 {
598     auto qdp = currentPlaylistWidget();
599     auto now = qdp->nowPlayingItem();
600     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
601     auto next = pl->itemAfter(now);
602     if (!!next)
603         activateItem(qdp->uuid(), next->uuid());
604 }
605 
activatePrevious()606 void PlaylistWindow::activatePrevious()
607 {
608     auto qdp = currentPlaylistWidget();
609     auto now = qdp->nowPlayingItem();
610     auto pl = PlaylistCollection::getSingleton()->playlistOf(qdp->uuid());
611     auto prev = pl->itemBefore(now);
612     if (!!prev)
613         activateItem(qdp->uuid(), prev->uuid());
614 }
615 
quickQueue()616 void PlaylistWindow::quickQueue()
617 {
618     if (ui->showQueue->isChecked())
619         return;
620 
621     auto qdp = currentPlaylistWidget();
622     auto itemUuids = qdp->currentItemUuids();
623     if (itemUuids.isEmpty())
624         return;
625     auto qpl = PlaylistCollection::queuePlaylist();
626     QList<QUuid> added;
627     QList<int>removed;
628     qpl->toggle(qdp->uuid(), itemUuids, added, removed);
629     queueWidget->removeItems(removed);
630     queueWidget->addItems(added);
631     qdp->viewport()->update();
632     queueWidget->viewport()->update();
633 }
634 
visibleToQueue()635 void PlaylistWindow::visibleToQueue()
636 {
637     if (ui->showQueue->isChecked())
638         return;
639 
640     QList<QUuid> added;
641     QList<int> removed;
642     PlaylistCollection::queuePlaylist()->\
643             toggleFromPlaylist(currentPlaylistWidget()->uuid(), added, removed);
644     queueWidget->removeItems(removed);
645     queueWidget->addItems(added);
646     queueWidget->viewport()->update();
647     currentPlaylistWidget()->viewport()->update();
648 }
649 
setQueueMode(bool yes)650 void PlaylistWindow::setQueueMode(bool yes)
651 {
652     ui->playStack->setCurrentIndex(yes ? 1 : 0);
653     setWindowTitle(yes ? "Queue" : "Playlist");
654     ui->showQueue->setChecked(yes);
655     emit quickQueueMode(yes);
656 }
657 
revealSearch()658 void PlaylistWindow::revealSearch()
659 {
660     showSearch = true;
661     activateWindow();
662     ui->searchHost->setVisible(true);
663     ui->searchField->setFocus();
664 }
665 
finishSearch()666 void PlaylistWindow::finishSearch()
667 {
668     showSearch = false;
669     if (!ui->searchHost->isVisible())
670         return;
671 
672     if (!ui->searchField->text().isEmpty()) {
673         ui->searchField->setText(QString());
674         setPlaylistFilters(QString());
675     }
676 
677     if (ui->searchField->hasFocus())
678         currentPlaylistWidget()->setFocus();
679 
680     ui->searchHost->setVisible(false);
681 }
682 
savePlaylist(const QUuid & playlistUuid)683 void PlaylistWindow::savePlaylist(const QUuid &playlistUuid)
684 {
685     QString file;
686     file = QFileDialog::getSaveFileName(this, tr("Export File"), QString(),
687                                         tr("Playlist files (*.m3u *.m3u8)"));
688     auto pl = PlaylistCollection::getSingleton()->playlistOf(playlistUuid);
689     if (!file.isEmpty() && pl)
690         emit exportPlaylist(file, pl->toStringList());
691 }
692 
sortPlaylistByLabel(const QUuid & playlistUuid)693 void PlaylistWindow::sortPlaylistByLabel(const QUuid &playlistUuid)
694 {
695     auto qdp = widgets.value(playlistUuid, nullptr);
696     if (!qdp)
697         return;
698     auto converter = [this](QSharedPointer<Item> i) {
699         return displayParser.parseMetadata(i->metadata(), i->toDisplayString(), Helpers::VideoFile);
700     };
701     auto lessThan = [](const QString &a, const QString &b) {
702         return a < b;
703     };
704     qdp->sort<QString>(converter, lessThan);
705 }
706 
sortPlaylistByUrl(const QUuid & playlistUuid)707 void PlaylistWindow::sortPlaylistByUrl(const QUuid &playlistUuid)
708 {
709     auto qdp = widgets.value(playlistUuid, nullptr);
710     if (!qdp)
711         return;
712     auto converter = [](QSharedPointer<Item> i) {
713         return i->url().toDisplayString();
714     };
715     auto lessThan = [](const QString &a, const QString &b) {
716         return a < b;
717     };
718     qdp->sort<QString>(converter, lessThan);
719 }
720 
randomizePlaylist(const QUuid & playlistUuid)721 void PlaylistWindow::randomizePlaylist(const QUuid &playlistUuid)
722 {
723     auto qdp = widgets.value(playlistUuid, nullptr);
724     if (!qdp)
725         return;
726     std::uniform_int_distribution<> itemDistribution(0, qdp->count()-1);
727     auto converter = [&](QSharedPointer<Item> i) {
728         Q_UNUSED(i)
729         return itemDistribution(randomGenerator);
730     };
731     auto lessThan = [](const int &a, const int &b) {
732         return a < b;
733     };
734     qdp->sort<int>(converter, lessThan);
735 }
736 
restorePlaylist(const QUuid & playlistUuid)737 void PlaylistWindow::restorePlaylist(const QUuid &playlistUuid)
738 {
739     auto qdp = widgets.value(playlistUuid, nullptr);
740     if (!qdp)
741         return;
742     auto converter = [&](QSharedPointer<Item> i) {
743         return i->originalPosition();
744     };
745     auto lessThan = [](const int &a, const int &b) {
746         return a < b;
747     };
748     qdp->sort<int>(converter, lessThan);
749 }
750 
self_visibilityChanged()751 void PlaylistWindow::self_visibilityChanged()
752 {
753     // When the window was (re)created/destroyed for whatever reason by
754     // the toolkit/wm/etc, reveal the search widget if it was active last.
755     if (showSearch)
756         revealSearch();
757     else
758         finishSearch();
759 }
760 
self_dockLocationChanged(Qt::DockWidgetArea area)761 void PlaylistWindow::self_dockLocationChanged(Qt::DockWidgetArea area)
762 {
763     if (area != Qt::NoDockWidgetArea)
764         emit windowDocked();
765 }
766 
playlist_removeItemRequested()767 void PlaylistWindow::playlist_removeItemRequested()
768 {
769     auto qdp = currentPlaylistWidget();
770     if (!qdp)
771         return;
772 
773     qdp->traverseSelected([qdp](QUuid uuid) { qdp->removeItem(uuid); });
774     updatePlaylistHasItems();
775 }
776 
playlist_removeAllRequested()777 void PlaylistWindow::playlist_removeAllRequested()
778 {
779     auto qdp = currentPlaylistWidget();
780     if (!qdp)
781         return;
782 
783     qdp->removeAll();
784     updatePlaylistHasItems();
785 }
786 
playlist_copySelectionToClipboard(const QUuid & playlistUuid)787 void PlaylistWindow::playlist_copySelectionToClipboard(const QUuid &playlistUuid)
788 {
789     auto qdp = widgets.value(playlistUuid, nullptr);
790     if (!qdp)
791         return;
792     auto pl = qdp->playlist();
793     QList<QUrl> urls;
794     qdp->traverseSelected([&pl,&urls](QUuid itemUuid) {
795         urls.append(pl->itemOf(itemUuid)->url());
796     });
797     QMimeData *mimeData = new QMimeData();
798     mimeData->setUrls(urls);
799     QGuiApplication::clipboard()->setMimeData(mimeData);
800 }
801 
playlist_hideOnFullscreenToggled(bool checked)802 void PlaylistWindow::playlist_hideOnFullscreenToggled(bool checked)
803 {
804     Q_UNUSED(checked)
805     // TODO: is this stub of any use?
806 }
807 
playlist_contextMenuRequested(const QPoint & p,const QUuid & playlistUuid,const QUuid & itemUuid)808 void PlaylistWindow::playlist_contextMenuRequested(const QPoint &p, const QUuid &playlistUuid, const QUuid &itemUuid)
809 {
810     if (!widgets.contains(playlistUuid))
811         return;
812     DrawnPlaylist *listWidget = widgets[playlistUuid];
813 
814     QMenu *m = new QMenu(this);
815     QAction *a;
816 
817     a = new QAction(m);
818     a->setText(tr("Open"));
819     connect(a, &QAction::triggered,
820             this, [this,playlistUuid,itemUuid]() {
821         emit itemDesired(playlistUuid, itemUuid);
822     });
823     m->addAction(a);
824 
825     a = new QAction(m);
826     a->setText(tr("Add"));
827     connect(a, &QAction::triggered,
828             this, [this,playlistUuid]() {
829         emit playlistAddItem(playlistUuid);
830     });
831     m->addAction(a);
832 
833     a = new QAction(m);
834     a->setText(tr("Remove"));
835     connect(a, &QAction::triggered,
836             this, &PlaylistWindow::playlist_removeItemRequested);
837     m->addAction(a);
838 
839     m->addSeparator();
840 
841     a = new QAction(m);
842     a->setText(tr("Clear"));
843     connect(a, &QAction::triggered,
844             this, &PlaylistWindow::playlist_removeAllRequested);
845     m->addAction(a);
846 
847     m->addSeparator();
848 
849     a = new QAction(m);
850     a->setText(tr("Copy To clipboard"));
851     connect(a, &QAction::triggered,
852             this, [this,playlistUuid]() {
853         playlist_copySelectionToClipboard(playlistUuid);
854     });
855     m->addAction(a);
856 
857     a = new QAction(m);
858     a->setText(tr("Save As..."));
859     connect(a, &QAction::triggered,
860             this, [this,playlistUuid]() {
861         savePlaylist(playlistUuid);
862     });
863     m->addAction(a);
864 
865     m->addSeparator();
866 
867     a = new QAction(m);
868     a->setText(tr("Sort By Label"));
869     connect(a, &QAction::triggered,
870             this, [this,playlistUuid]() {
871         sortPlaylistByLabel(playlistUuid);
872     });
873     m->addAction(a);
874 
875     a = new QAction(m);
876     a->setText(tr("Sort By Url"));
877     connect(a, &QAction::triggered,
878             this, [this,playlistUuid]() {
879         sortPlaylistByUrl(playlistUuid);
880     });
881     m->addAction(a);
882 
883     a = new QAction(m);
884     a->setText(tr("Randomize"));
885     connect(a, &QAction::triggered,
886             this, [this,playlistUuid]() {
887         randomizePlaylist(playlistUuid);
888     });
889     m->addAction(a);
890 
891     a = new QAction(m);
892     a->setText(tr("Restore"));
893     connect(a, &QAction::triggered,
894             this, [this,playlistUuid]() {
895         restorePlaylist(playlistUuid);
896     });
897     m->addAction(a);
898 
899     m->addSeparator();
900 
901     a = new QAction(m);
902     a->setText(tr("Shuffle"));
903     a->setCheckable(true);
904     a->setChecked(listWidget->playlist()->shuffle());
905     connect(a, &QAction::triggered,
906             this, [this,playlistUuid](bool checked) {
907         if (widgets.contains(playlistUuid))
908             widgets[playlistUuid]->playlist()->setShuffle(checked);
909         emit playlistShuffleChanged(playlistUuid, checked);
910     });
911     m->addAction(a);
912 
913     m->addSeparator();
914 
915     a = new QAction(m);
916     a->setText(tr("Hide On Fullscreen"));
917     a->setCheckable(true);
918     a->setChecked(hideFullscreen);
919     connect(a, &QAction::triggered,
920             this, [this](bool checked) {
921         hideFullscreen = checked;
922         emit hideFullscreenChanged(checked);
923     });
924     m->addAction(a);
925 
926     connect(m, &QMenu::aboutToHide,
927             m, &QObject::deleteLater);
928     m->exec(listWidget->mapToGlobal(p));
929 }
930 
on_tabWidget_tabCloseRequested(int index)931 void PlaylistWindow::on_tabWidget_tabCloseRequested(int index)
932 {
933     int current = ui->tabWidget->currentIndex();
934     auto qdp = reinterpret_cast<DrawnPlaylist *>(ui->tabWidget->widget(index));
935     if (!qdp)
936         return;
937 
938     auto collection = PlaylistCollection::getSingleton();
939     auto backup = PlaylistCollection::getBackup();
940     auto copy = collection->clonePlaylist(qdp->uuid());
941     copy->setCreated(qdp->playlist()->created());
942     collection->takePlaylist(copy->uuid()); // detach 'copy' from collection
943     backup->addPlaylist(copy);
944     emit playlistMovedToBackup(copy->uuid());
945 
946     if (qdp->uuid().isNull()) {
947         qdp->removeAll();
948     } else {
949         collection->removePlaylist(qdp->uuid());
950         widgets.remove(qdp->uuid());
951         ui->tabWidget->removeTab(index);
952     }
953     if (current == index)
954         updateCurrentPlaylist();
955 }
956 
on_tabWidget_tabBarDoubleClicked(int index)957 void PlaylistWindow::on_tabWidget_tabBarDoubleClicked(int index)
958 {
959     auto widget = reinterpret_cast<DrawnPlaylist *>(ui->tabWidget->widget(index));
960     QUuid tabUuid = widget->uuid();
961     if (tabUuid.isNull())
962         return;
963     QInputDialog *qid = new QInputDialog(this);
964     qid->setAttribute(Qt::WA_DeleteOnClose);
965     qid->setWindowModality(Qt::ApplicationModal);
966     qid->setWindowTitle(tr("Enter playlist name"));
967     qid->setTextValue(ui->tabWidget->tabText(index).replace(QRegExp("&{1,}"), ""));
968     connect(qid, &QInputDialog::accepted, this, [=]() {
969         int tabIndex = ui->tabWidget->indexOf(widget);
970         if (tabIndex < 0)
971             return;
972         auto pl = PlaylistCollection::getSingleton()->playlistOf(tabUuid);
973         if (!pl)
974             return;
975         pl->setTitle(qid->textValue());
976         ui->tabWidget->setTabText(tabIndex, qid->textValue().replace("&", "+"));
977     });
978     qid->show();
979 }
980 
on_tabWidget_customContextMenuRequested(const QPoint & pos)981 void PlaylistWindow::on_tabWidget_customContextMenuRequested(const QPoint &pos)
982 {
983     QMenu *m = new QMenu(this);
984     m->addAction(tr("&New Playlist"), this, SLOT(newTab()));
985     m->addAction(tr("&Remove Playlist"), this, SLOT(closeTab()));
986     m->addAction(tr("&Duplicate Playlist"), this, SLOT(duplicateTab()));
987     m->addAction(tr("&Import Playlist"), this, SLOT(importTab()));
988     m->addAction(tr("&Export Playlist"), this, SLOT(exportTab()));
989     m->exec(ui->tabWidget->mapToGlobal(pos));
990 }
991 
on_searchField_textEdited(const QString & arg1)992 void PlaylistWindow::on_searchField_textEdited(const QString &arg1)
993 {
994     setPlaylistFilters(arg1);
995 }
996 
on_tabWidget_currentChanged(int index)997 void PlaylistWindow::on_tabWidget_currentChanged(int index)
998 {
999     Q_UNUSED(index)
1000     updateCurrentPlaylist();
1001 }
1002 
on_searchField_returnPressed()1003 void PlaylistWindow::on_searchField_returnPressed()
1004 {
1005     playCurrentItem();
1006 }
1007