1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "streamspage.h"
25 #include "streamdialog.h"
26 #include "streamssettings.h"
27 #include "mpd-interface/mpdconnection.h"
28 #include "support/messagebox.h"
29 #include "widgets/icons.h"
30 #include "gui/stdactions.h"
31 #include "support/actioncollection.h"
32 #include "network/networkaccessmanager.h"
33 #include "support/configuration.h"
34 #include "support/monoicon.h"
35 #include "gui/settings.h"
36 #include "widgets/menubutton.h"
37 #include "widgets/itemview.h"
38 #include "widgets/servicestatuslabel.h"
39 #include "models/digitallyimported.h"
40 #include "models/playqueuemodel.h"
41 #include "digitallyimportedsettings.h"
42 #include <QToolButton>
43 #include <QFileDialog>
44 #include <QDir>
45 #include <QMenu>
46 #include <QFileInfo>
47 #include <QUrlQuery>
48 
49 static const int constMsgDisplayTime=1500;
50 static const char *constNameProperty="name";
51 
StreamsPage(QWidget * p)52 StreamsPage::StreamsPage(QWidget *p)
53     : StackedPageWidget(p)
54 {
55     qRegisterMetaType<StreamItem>("StreamItem");
56     qRegisterMetaType<QList<StreamItem> >("QList<StreamItem>");
57 
58     browse=new StreamsBrowsePage(this);
59     addWidget(browse);
60     connect(browse, SIGNAL(close()), this, SIGNAL(close()));
61     connect(browse, SIGNAL(searchForStreams()), this, SLOT(searchForStreams()));
62     search=new StreamSearchPage(this);
63     addWidget(search);
64     connect(search, SIGNAL(close()), this, SLOT(closeSearch()));
65 
66     disconnect(browse, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool)));
67     disconnect(search, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool)));
68     connect(browse, SIGNAL(add(const QStringList &, int, quint8, bool)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, int, quint8, bool)));
69     connect(search, SIGNAL(add(const QStringList &, int, quint8, bool)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, int, quint8, bool)));
70     connect(StreamsModel::self()->addToFavouritesAct(), SIGNAL(triggered()), this, SLOT(addToFavourites()));
71     connect(search, SIGNAL(addToFavourites(QList<StreamItem>)), browse, SLOT(addToFavourites(QList<StreamItem>)));
72 }
73 
~StreamsPage()74 StreamsPage:: ~StreamsPage()
75 {
76 }
77 
searchForStreams()78 void StreamsPage::searchForStreams()
79 {
80     setCurrentWidget(search);
81 }
82 
closeSearch()83 void StreamsPage::closeSearch()
84 {
85     setCurrentWidget(browse);
86 }
87 
addToFavourites()88 void StreamsPage::addToFavourites()
89 {
90     QWidget *w=currentWidget();
91     if (browse==w) {
92         browse->addToFavourites();
93     } else if (search==w) {
94         search->addToFavourites();
95     }
96 }
97 
StreamsBrowsePage(QWidget * p)98 StreamsBrowsePage::StreamsBrowsePage(QWidget *p)
99     : SinglePageWidget(p)
100     , settings(nullptr)
101 {
102     QColor iconCol=Utils::monoIconColor();
103     importAction = new Action(MonoIcon::icon(FontAwesome::arrowright, iconCol), tr("Import Streams Into Favorites"), this);
104     exportAction = new Action(MonoIcon::icon(FontAwesome::arrowleft, iconCol), tr("Export Favorite Streams"), this);
105     addAction = ActionCollection::get()->createAction("addstream", tr("Add New Stream To Favorites"));
106     editAction = new Action(Icons::self()->editIcon, tr("Edit"), this);
107     searchAction = new Action(Icons::self()->searchIcon, tr("Seatch For Streams"), this);
108     connect(searchAction, SIGNAL(triggered()), this, SIGNAL(searchForStreams()));
109 //     connect(view, SIGNAL(itemsSelected(bool)), addToPlaylist, SLOT(setEnabled(bool)));
110     connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &)));
111     connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions()));
112     connect(addAction, SIGNAL(triggered()), this, SLOT(addStream()));
113     connect(StreamsModel::self()->addBookmarkAct(), SIGNAL(triggered()), this, SLOT(addBookmark()));
114     connect(StreamsModel::self()->configureDiAct(), SIGNAL(triggered()), this, SLOT(configureDi()));
115     connect(StreamsModel::self()->reloadAct(), SIGNAL(triggered()), this, SLOT(reload()));
116     connect(editAction, SIGNAL(triggered()), this, SLOT(edit()));
117     connect(importAction, SIGNAL(triggered()), this, SLOT(importXml()));
118     connect(exportAction, SIGNAL(triggered()), this, SLOT(exportXml()));
119     connect(StreamsModel::self(), SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &)));
120     connect(StreamsModel::self(), SIGNAL(loading()), view, SLOT(showSpinner()));
121     connect(StreamsModel::self(), SIGNAL(loaded()), view, SLOT(hideSpinner()));
122     connect(StreamsModel::self(), SIGNAL(categoriesChanged()), view, SLOT(closeSearch()));
123     connect(StreamsModel::self(), SIGNAL(favouritesLoaded()), SLOT(expandFavourites()));
124     connect(StreamsModel::self(), SIGNAL(addedToFavourites(QString)), SLOT(addedToFavourites(QString)));
125     connect(DigitallyImported::self(), SIGNAL(loginStatus(bool,QString)), SLOT(updateDiStatus()));
126     connect(DigitallyImported::self(), SIGNAL(updated()), SLOT(updateDiStatus()));
127     connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int)));
128     StreamsModel::self()->configureDiAct()->setEnabled(false);
129 
130     proxy.setSourceModel(StreamsModel::self());
131     view->setModel(&proxy);
132     view->setDeleteAction(StdActions::self()->removeAction);
133     view->setSearchResetLevel(1);
134     view->alwaysShowHeader();
135 
136     Configuration config(metaObject()->className());
137     view->setMode(ItemView::Mode_DetailedTree);
138     view->load(config);
139 
140     MenuButton *menuButton=new MenuButton(this);
141     Action *configureAction=new Action(Icons::self()->configureIcon, tr("Configure"), this);
142     connect(configureAction, SIGNAL(triggered()), SLOT(configure()));
143     menuButton->addAction(createViewMenu(QList<ItemView::Mode>()  << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree
144                                                                   << ItemView::Mode_DetailedTree << ItemView::Mode_List));
145     menuButton->addAction(configureAction);
146     menuButton->addAction(StreamsModel::self()->configureDiAct());
147     menuButton->addSeparator();
148     menuButton->addAction(addAction);
149     menuButton->addAction(StdActions::self()->removeAction);
150     menuButton->addAction(editAction);
151     menuButton->addAction(StreamsModel::self()->reloadAct());
152     menuButton->addSeparator();
153     menuButton->addAction(importAction);
154     menuButton->addAction(exportAction);
155 
156     diStatusLabel=new ServiceStatusLabel(this);
157     diStatusLabel->setText("DI", tr("Digitally Imported", "Service name"));
158     connect(diStatusLabel, SIGNAL(clicked()), SLOT(diSettings()));
159     updateDiStatus();
160     ToolButton *searchButton=new ToolButton(this);
161     searchButton->setDefaultAction(searchAction);
162     init(ReplacePlayQueue, QList<QWidget *>() << menuButton << diStatusLabel, QList<QWidget *>() << searchButton);
163 
164     view->addAction(editAction);
165     view->addAction(StdActions::self()->removeAction);
166     view->addAction(StreamsModel::self()->addToFavouritesAct());
167     view->addAction(StreamsModel::self()->addBookmarkAct());
168     view->addAction(StreamsModel::self()->reloadAct());
169 }
170 
~StreamsBrowsePage()171 StreamsBrowsePage::~StreamsBrowsePage()
172 {
173     for (NetworkJob *job: resolveJobs) {
174         disconnect(job, SIGNAL(finished()), this, SLOT(tuneInResolved()));
175         job->deleteLater();
176     }
177     resolveJobs.clear();
178     Configuration config(metaObject()->className());
179     view->save(config);
180 }
181 
showEvent(QShowEvent * e)182 void StreamsBrowsePage::showEvent(QShowEvent *e)
183 {
184     view->focusView();
185     QWidget::showEvent(e);
186 }
187 
addSelectionToPlaylist(const QString & name,int action,quint8 priority,bool decreasePriority)188 void StreamsBrowsePage::addSelectionToPlaylist(const QString &name, int action, quint8 priority, bool decreasePriority)
189 {
190     Q_UNUSED(name)
191     addItemsToPlayQueue(view->selectedIndexes(), action, priority, decreasePriority);
192 }
193 
addItemsToPlayQueue(const QModelIndexList & indexes,int action,quint8 priority,bool decreasePriority)194 void StreamsBrowsePage::addItemsToPlayQueue(const QModelIndexList &indexes, int action, quint8 priority, bool decreasePriority)
195 {
196     if (indexes.isEmpty()) {
197         return;
198     }
199     QModelIndexList mapped;
200     for (const QModelIndex &idx: indexes) {
201         mapped.append(proxy.mapToSource(idx));
202     }
203 
204     QStringList files=StreamsModel::self()->filenames(mapped, true);
205 
206     if (!files.isEmpty()) {
207         emit add(files, action, priority, decreasePriority);
208         view->clearSelection();
209     }
210 }
211 
itemDoubleClicked(const QModelIndex & index)212 void StreamsBrowsePage::itemDoubleClicked(const QModelIndex &index)
213 {
214     if (!static_cast<StreamsModel::Item *>(proxy.mapToSource(index).internalPointer())->isCategory()) {
215         QModelIndexList indexes;
216         indexes.append(index);
217         addItemsToPlayQueue(indexes, false);
218     }
219 }
220 
configure()221 void StreamsBrowsePage::configure()
222 {
223     if (!settings) {
224         settings=new StreamsSettings(this);
225     }
226     if (!settings->isVisible()) {
227         settings->load();
228     }
229     settings->show();
230     settings->raiseWindow();
231 }
232 
configureDi()233 void StreamsBrowsePage::configureDi()
234 {
235     QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here...
236     if (1!=selected.count()) {
237         return;
238     }
239 
240     const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(proxy.mapToSource(selected.first()).internalPointer());
241     if (item->isCategory() && static_cast<const StreamsModel::CategoryItem *>(item)->isDi()) {
242         diSettings();
243     }
244 }
245 
diSettings()246 void StreamsBrowsePage::diSettings()
247 {
248     DigitallyImportedSettings(this).show();
249 }
250 
importXml()251 void StreamsBrowsePage::importXml()
252 {
253     QString fileName=QFileDialog::getOpenFileName(this, tr("Import Streams"), QDir::homePath(),
254                                                   tr("XML Streams (*.xml *.xml.gz *.cantata)"));
255 
256     if (fileName.isEmpty()) {
257         return;
258     }
259     StreamsModel::self()->importIntoFavourites(fileName);
260 }
261 
exportXml()262 void StreamsBrowsePage::exportXml()
263 {
264     QLatin1String ext(".xml.gz");
265     QString fileName=QFileDialog::getSaveFileName(this, tr("Export Streams"), QDir::homePath()+QLatin1String("/streams")+ext, tr("XML Streams (*.xml.gz)"));
266 
267     if (fileName.isEmpty()) {
268         return;
269     }
270 
271     if (!fileName.endsWith(ext)) {
272         fileName+=ext;
273     }
274 
275     if (!StreamsModel::self()->exportFavourites(fileName)) {
276         MessageBox::error(this, tr("Failed to create '%1'!").arg(fileName));
277     }
278 }
279 
addStream()280 void StreamsBrowsePage::addStream()
281 {
282     StreamDialog dlg(this);
283 
284     if (QDialog::Accepted==dlg.exec()) {
285         QString name=dlg.name();
286         QString url=dlg.url();
287         QString existingNameForUrl=StreamsModel::self()->favouritesNameForUrl(url);
288 
289         if (!existingNameForUrl.isEmpty()) {
290             MessageBox::error(this, tr("Stream '%1' already exists!").arg(existingNameForUrl));
291         } else if (StreamsModel::self()->nameExistsInFavourites(name)) {
292             MessageBox::error(this, tr("A stream named '%1' already exists!").arg(name));
293         } else {
294             StreamsModel::self()->addToFavourites(url, name);
295         }
296     }
297 }
298 
addBookmark()299 void StreamsBrowsePage::addBookmark()
300 {
301     QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here...
302 
303     if (1!=selected.count()) {
304         return;
305     }
306 
307     const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(proxy.mapToSource(selected.first()).internalPointer());
308 
309     // TODO: In future, if other categories support bookmarking, then we will need to calculate parent here!!!
310     if (StreamsModel::self()->addBookmark(item->url, item->name, nullptr)) {
311         view->showMessage(tr("Bookmark added"), constMsgDisplayTime);
312     } else {
313         view->showMessage(tr("Already bookmarked"), constMsgDisplayTime);
314     }
315 }
316 
addToFavourites()317 void StreamsBrowsePage::addToFavourites()
318 {
319     QModelIndexList selected = view->selectedIndexes();
320 
321     QList<const StreamsModel::Item *> items;
322 
323     for (const QModelIndex &i: selected) {
324         QModelIndex mapped=proxy.mapToSource(i);
325         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(mapped.internalPointer());
326         if (!item->isCategory() && item->parent && !item->parent->isFavourites()) {
327             items.append(item);
328         }
329     }
330     QList<StreamItem> itemsToAdd;
331     for (const StreamsModel::Item *item: items) {
332         itemsToAdd.append(StreamItem(item->url, item->modifiedName()));
333     }
334     addToFavourites(itemsToAdd);
335 }
336 
addToFavourites(const QList<StreamItem> & items)337 void StreamsBrowsePage::addToFavourites(const QList<StreamItem> &items)
338 {
339     int added=0;
340     for (const StreamItem item: items) {
341         QUrl url(item.url);
342         QUrlQuery query(url);
343         query.removeQueryItem(QLatin1String("locale"));
344         if (!query.isEmpty()) {
345             url.setQuery(query);
346         }
347         QString urlStr=url.toString();
348         if (urlStr.endsWith('&')) {
349             urlStr=urlStr.left(urlStr.length()-1);
350         }
351         if (urlStr.startsWith(QLatin1String("http://opml.radiotime.com/Tune.ashx"))) {
352             NetworkJob *job=NetworkAccessManager::self()->get(urlStr, 5000);
353             job->setProperty(constNameProperty, item.modifiedName);
354             connect(job, SIGNAL(finished()), this, SLOT(tuneInResolved()));
355             resolveJobs.insert(job);
356             added++;
357         } else if (StreamsModel::self()->addToFavourites(urlStr, item.modifiedName)) {
358             added++;
359         }
360     }
361 
362     if (!added) {
363         view->showMessage(tr("Already in favorites"), constMsgDisplayTime);
364     }
365 }
366 
tuneInResolved()367 void StreamsBrowsePage::tuneInResolved()
368 {
369     NetworkJob *job=qobject_cast<NetworkJob *>(sender());
370     if (!job) {
371         return;
372     }
373     job->deleteLater();
374     if (!resolveJobs.contains(job)) {
375         return;
376     }
377     resolveJobs.remove(job);
378     QString url=job->readAll().split('\n').first();
379     QString name=job->property(constNameProperty).toString();
380     if (!url.isEmpty() && !name.isEmpty() && !StreamsModel::self()->addToFavourites(url, name)) {
381         view->showMessage(tr("Already in favorites"), constMsgDisplayTime);
382     }
383 }
384 
headerClicked(int level)385 void StreamsBrowsePage::headerClicked(int level)
386 {
387     if (0==level) {
388         emit close();
389     }
390 }
391 
reload()392 void StreamsBrowsePage::reload()
393 {
394     QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here...
395     if (1!=selected.count()) {
396         return;
397     }
398 
399     QModelIndex mapped=proxy.mapToSource(selected.first());
400     const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(mapped.internalPointer());
401     if (!item->isCategory()) {
402         return;
403     }
404     const StreamsModel::CategoryItem *cat=static_cast<const StreamsModel::CategoryItem *>(item);
405     if (!cat->canReload()) {
406         return;
407     }
408 
409     if (cat->children.isEmpty() || cat->cacheName.isEmpty() || MessageBox::Yes==MessageBox::questionYesNo(this, tr("Reload '%1' streams?").arg(cat->name))) {
410         StreamsModel::self()->reload(mapped);
411     }
412 }
413 
removeItems()414 void StreamsBrowsePage::removeItems()
415 {
416     QModelIndexList selected = view->selectedIndexes();
417 
418     if (1==selected.count()) {
419         QModelIndex mapped=proxy.mapToSource(selected.first());
420         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(mapped.internalPointer());
421         if (item->isCategory() && item->parent) {
422             if (item->parent->isBookmarks) {
423                 if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove bookmark to '%1'?").arg(item->name))) {
424                     return;
425                 }
426                 StreamsModel::self()->removeBookmark(mapped);
427                 return;
428             } else if (static_cast<const StreamsModel::CategoryItem *>(item)->isBookmarks) {
429                 if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove all '%1' bookmarks?").arg(item->parent->name))) {
430                     return;
431                 }
432                 StreamsModel::self()->removeAllBookmarks(mapped);
433                 return;
434             }
435         }
436     }
437 
438     QModelIndexList useable;
439 
440     for (const QModelIndex &i: selected) {
441         QModelIndex mapped=proxy.mapToSource(i);
442         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(mapped.internalPointer());
443         if (!item->isCategory() && item->parent && item->parent->isFavourites()) {
444             useable.append(mapped);
445         }
446     }
447 
448     if (useable.isEmpty()) {
449         return;
450     }
451 
452     if (useable.size()>1) {
453         if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the %1 selected streams?").arg(useable.size()))) {
454             return;
455         }
456     } else {
457         if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove '%1'?").arg(
458                                                      StreamsModel::self()->data(useable.first(), Qt::DisplayRole).toString()))) {
459             return;
460         }
461     }
462 
463     StreamsModel::self()->removeFromFavourites(useable);
464 }
465 
edit()466 void StreamsBrowsePage::edit()
467 {
468     QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here...
469 
470     if (1!=selected.size()) {
471         return;
472     }
473 
474     QModelIndex index=proxy.mapToSource(selected.first());
475     StreamsModel::Item *item=static_cast<StreamsModel::Item *>(index.internalPointer());
476     if (item->isCategory() || !item->parent || !item->parent->isFavourites()) {
477         return;
478     }
479 
480     QString name=item->name;
481     QString url=item->url;
482 
483     StreamDialog dlg(this);
484     dlg.setEdit(name, url);
485 
486     if (QDialog::Accepted==dlg.exec()) {
487         QString newName=dlg.name();
488         QString newUrl=dlg.url();
489         QString existingNameForUrl=newUrl!=url ? StreamsModel::self()->favouritesNameForUrl(newUrl) : QString();
490 
491         if (!existingNameForUrl.isEmpty()) {
492             MessageBox::error(this, tr("Stream '%1' already exists!").arg(existingNameForUrl));
493         } else if (newName!=name && StreamsModel::self()->nameExistsInFavourites(newName)) {
494             MessageBox::error(this, tr("A stream named '%1' already exists!").arg(newName));
495         } else {
496             StreamsModel::self()->updateFavouriteStream(newUrl, newName, index);
497         }
498     }
499 }
500 
doSearch()501 void StreamsBrowsePage::doSearch()
502 {
503     QString text=view->searchText().trimmed();
504     if (!view->isSearchActive()) {
505         proxy.setFilterItem(nullptr);
506     }
507     proxy.update(view->isSearchActive() ? text : QString());
508     if (proxy.enabled() && !text.isEmpty()) {
509         view->expandAll(proxy.filterItem()
510                         ? proxy.mapFromSource(StreamsModel::self()->categoryIndex(static_cast<const StreamsModel::CategoryItem *>(proxy.filterItem())))
511                         : QModelIndex());
512     }
513 }
514 
controlActions()515 void StreamsBrowsePage::controlActions()
516 {
517     QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here...
518     bool haveSelection=!selected.isEmpty();
519     bool enableAddToFav=true;
520     bool onlyStreamsSelected=true;
521     StreamsModel::self()->addBookmarkAct()->setEnabled(false);
522 
523     editAction->setEnabled(false);
524     StreamsModel::self()->reloadAct()->setEnabled(false);
525 
526     bool enableRemove=true;
527     for (const QModelIndex &idx: selected) {
528         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(proxy.mapToSource(idx).internalPointer());
529         if (item->isCategory() || (item->parent && !item->parent->isFavourites())) {
530             enableRemove=false;
531         }
532         if (item->isCategory() || (item->parent && item->parent->isFavourites())) {
533             enableAddToFav=false;
534         }
535         if (item->isCategory()) {
536             onlyStreamsSelected=false;
537         }
538         if (!enableRemove && !enableAddToFav && !onlyStreamsSelected) {
539             break;
540         }
541     }
542 
543     StdActions::self()->removeAction->setEnabled(haveSelection && enableRemove);
544     StreamsModel::self()->addToFavouritesAct()->setEnabled(haveSelection && enableAddToFav);
545 
546     if (1==selected.size()) {
547         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(proxy.mapToSource(selected.first()).internalPointer());
548         if (!item->isCategory() && item->parent && item->parent->isFavourites()) {
549             editAction->setEnabled(true);
550         }
551         StreamsModel::self()->reloadAct()->setEnabled(item->isCategory() && static_cast<const StreamsModel::CategoryItem *>(item)->canReload());
552         StreamsModel::self()->addBookmarkAct()->setEnabled(item->isCategory() && static_cast<const StreamsModel::CategoryItem *>(item)->canBookmark);
553         if (!StdActions::self()->removeAction->isEnabled()) {
554             StdActions::self()->removeAction->setEnabled(item->isCategory() && item->parent &&
555                                                          (item->parent->isBookmarks || (static_cast<const StreamsModel::CategoryItem *>(item)->isBookmarks)));
556         }
557         StreamsModel::self()->configureDiAct()->setEnabled(item->isCategory() && static_cast<const StreamsModel::CategoryItem *>(item)->isDi());
558     } else {
559         StreamsModel::self()->configureDiAct()->setEnabled(false);
560     }
561 
562     StdActions::self()->replacePlayQueueAction->setEnabled(haveSelection && onlyStreamsSelected);
563 }
564 
updateDiStatus()565 void StreamsBrowsePage::updateDiStatus()
566 {
567     if (DigitallyImported::self()->user().isEmpty() || DigitallyImported::self()->pass().isEmpty()) {
568         diStatusLabel->setVisible(false);
569     } else {
570         diStatusLabel->setStatus(DigitallyImported::self()->loggedIn());
571     }
572 }
573 
expandFavourites()574 void StreamsBrowsePage::expandFavourites()
575 {
576     view->expand(proxy.mapFromSource(StreamsModel::self()->favouritesIndex()), true);
577 }
578 
addedToFavourites(const QString & name)579 void StreamsBrowsePage::addedToFavourites(const QString &name)
580 {
581     view->showMessage(tr("Added '%1'' to favorites").arg(name), constMsgDisplayTime);
582 }
583 
StreamSearchPage(QWidget * p)584 StreamSearchPage::StreamSearchPage(QWidget *p)
585     : SinglePageWidget(p)
586 {
587     proxy.setSourceModel(&model);
588     view->setModel(&proxy);
589     view->alwaysShowHeader();
590     view->setPermanentSearch();
591     connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int)));
592     view->setMode(ItemView::Mode_DetailedTree);
593     init(ReplacePlayQueue);
594     connect(StreamsModel::self(), SIGNAL(addedToFavourites(QString)), SLOT(addedToFavourites(QString)));
595 }
596 
~StreamSearchPage()597 StreamSearchPage::~StreamSearchPage()
598 {
599 
600 }
601 
showEvent(QShowEvent * e)602 void StreamSearchPage::showEvent(QShowEvent *e)
603 {
604     SinglePageWidget::showEvent(e);
605     view->focusSearch();
606 }
607 
headerClicked(int level)608 void StreamSearchPage::headerClicked(int level)
609 {
610     if (0==level) {
611         emit close();
612     }
613 }
614 
doSearch()615 void StreamSearchPage::doSearch()
616 {
617     model.search(view->searchText().trimmed(), false);
618 }
619 
addSelectionToPlaylist(const QString & name,int action,quint8 priority,bool decreasePriority)620 void StreamSearchPage::addSelectionToPlaylist(const QString &name, int action, quint8 priority, bool decreasePriority)
621 {
622     Q_UNUSED(name)
623     QModelIndexList indexes=view->selectedIndexes();
624     if (indexes.isEmpty()) {
625         return;
626     }
627     QModelIndexList mapped;
628     for (const QModelIndex &idx: indexes) {
629         mapped.append(proxy.mapToSource(idx));
630     }
631 
632     QStringList files=StreamsModel::self()->filenames(mapped, true);
633 
634     if (!files.isEmpty()) {
635         emit add(files, action, priority, decreasePriority);
636         view->clearSelection();
637     }
638 }
639 
addToFavourites()640 void StreamSearchPage::addToFavourites()
641 {
642     QModelIndexList selected = view->selectedIndexes();
643 
644     QList<const StreamsModel::Item *> items;
645 
646     for (const QModelIndex &i: selected) {
647         QModelIndex mapped=proxy.mapToSource(i);
648         const StreamsModel::Item *item=static_cast<const StreamsModel::Item *>(mapped.internalPointer());
649         if (!item->isCategory() && item->parent && !item->parent->isFavourites()) {
650             items.append(item);
651         }
652     }
653     QList<StreamItem> itemsToAdd;
654     for (const StreamsModel::Item *item: items) {
655         itemsToAdd.append(StreamItem(item->url, item->modifiedName()));
656     }
657     emit addToFavourites(itemsToAdd);
658 }
659 
addedToFavourites(const QString & name)660 void StreamSearchPage::addedToFavourites(const QString &name)
661 {
662     view->showMessage(tr("Added '%1'' to favorites").arg(name), constMsgDisplayTime);
663 }
664 
665 #include "moc_streamspage.cpp"
666