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