MythTV  master
playlisteditorview.cpp
Go to the documentation of this file.
1 #include <iostream>
2 #include <cstdlib>
3 
4 // qt
5 #include <QKeyEvent>
6 
7 // myth
8 #include <mythcontext.h>
9 #include <mythdbcon.h>
10 #include <mythmainwindow.h>
11 #include <mythuibuttontree.h>
12 #include <mythuitext.h>
13 #include <mythuiutils.h>
14 #include <mythdialogbox.h>
15 #include <mythdirs.h>
16 #include <musicutils.h>
17 
18 // mythmusic
19 #include "musicdata.h"
20 #include "playlistcontainer.h"
21 #include "musiccommon.h"
22 #include "playlisteditorview.h"
23 #include "smartplaylist.h"
24 #include "mainvisual.h"
25 
27  const QString &name, const QString &action,
29  bool showArrow)
30  : MythGenericTree(name)
31 {
32  m_check = check;
33  m_action = action;
34  m_showArrow = showArrow;
35 
36  if (!action.isEmpty() && !action.isNull())
37  setSelectable(true);
38 
39  if (parent)
40  {
41  parent->addNode(this);
42  parent->setDrawArrow(true);
43  }
44 }
45 
47 {
48  m_showArrow = flag;
49  if (m_buttonItem)
50  m_buttonItem->setDrawArrow(true);
51 }
52 
54 {
55  m_check = state;
56  if (m_buttonItem)
57  {
59  m_buttonItem->setChecked(m_check);
60  }
61 }
62 
64 {
65  auto *item = new MusicButtonItem(list, GetText());
66  item->SetData(qVariantFromValue((MythGenericTree*) this));
67 
68  if (visibleChildCount() > 0)
69  item->setDrawArrow(true);
70 
71  if (m_showArrow)
72  item->setDrawArrow(true);
73 
74  item->setCheckable(m_check != MythUIButtonListItem::CantCheck);
75  item->setChecked(m_check);
76 
77  m_buttonItem = item;
78 
79  return item;
80 }
81 
83 
84 #define LOC QString("PlaylistEditorView: ")
85 #define LOC_WARN QString("PlaylistEditorView, Warning: ")
86 #define LOC_ERR QString("PlaylistEditorView, Error: ")
87 
89  const QString &layout, bool restorePosition)
90  :MusicCommon(parent, parentScreen, "playlisteditorview"),
91  m_layout(layout), m_restorePosition(restorePosition)
92 {
94  gCoreContext->SaveSetting("MusicPlaylistEditorView", layout);
95 }
96 
98 {
100 
102 
103  for (int x = 0; x < m_deleteList.count(); x++)
104  delete m_deleteList.at(x);
105  m_deleteList.clear();
106 
107  delete m_rootNode;
108 }
109 
111 {
112  bool err = false;
113 
114  QString windowName;
115 
116  if (m_layout == "gallery")
117  {
118  windowName = "playlisteditorview_gallery";
120  }
121  else
122  {
123  windowName = "playlisteditorview_tree";
125  }
126 
127  // Load the theme for this screen
128  err = LoadWindowFromXML("music-ui.xml", windowName, this);
129 
130  if (!err)
131  {
132  gPlayer->removeListener(this);
133  return false;
134  }
135 
136  // find common widgets available on any view
137  err = CreateCommon();
138 
139  // find widgets specific to this view
140  UIUtilE::Assign(this, m_playlistTree , "playlist_tree", &err);
141  UIUtilW::Assign(this, m_breadcrumbsText, "breadcrumbs", &err);
142  UIUtilW::Assign(this, m_positionText, "position", &err);
143 
144  if (err)
145  {
146  LOG(VB_GENERAL, LOG_ERR, QString("Cannot load screen '%1'").arg(windowName));
147  gPlayer->removeListener(this);
148  return false;
149  }
150 
151  createRootNode();
152 
154 
156 
157  if (m_restorePosition)
158  {
159  QStringList route = gCoreContext->GetSetting("MusicTreeLastActive", "").split("\n");
160  restoreTreePosition(route);
161  }
162 
163  connect(m_playlistTree, SIGNAL(itemClicked(MythUIButtonListItem*)),
164  this, SLOT(treeItemClicked(MythUIButtonListItem*)));
165  connect(m_playlistTree, SIGNAL(nodeChanged(MythGenericTree*)),
166  this, SLOT(treeNodeChanged(MythGenericTree*)));
167 
169  connect(m_playlistTree, SIGNAL(itemVisible(MythUIButtonListItem*)),
170  this, SLOT(treeItemVisible(MythUIButtonListItem*)));
171 
172  BuildFocusList();
173 
174  return true;
175 }
176 
178 {
179  if ((event->type() == MusicPlayerEvent::MetadataChangedEvent) ||
180  (event->type() == MusicPlayerEvent::AlbumArtChangedEvent))
181  { // NOLINT(bugprone-branch-clone)
182  // TODO: this could be more efficient
183  reloadTree();
184  }
185  else if ((event->type() == MusicPlayerEvent::TrackRemovedEvent) ||
186  (event->type() == MusicPlayerEvent::TrackAddedEvent) ||
187  (event->type() == MusicPlayerEvent::AllTracksRemovedEvent))
188  {
190  }
191  else if (event->type() == MusicPlayerEvent::PlaylistChangedEvent)
192  {
193  //TODO should just update the relevent playlist here
194  reloadTree();
195  }
196  else if (event->type() == MusicPlayerEvent::CDChangedEvent)
197  {
198  //TODO should just update the cd node
199  reloadTree();
200  }
201  else if (event->type() == MythEvent::MythEventMessage)
202  {
203  auto *me = dynamic_cast<MythEvent*>(event);
204  if (!me)
205  return;
206 
207  if (me->Message().startsWith("MUSIC_RESYNC_FINISHED"))
208  {
209  QStringList list = me->Message().simplified().split(' ');
210  if (list.size() == 4)
211  {
212  int added = list[1].toInt();
213  int removed = list[2].toInt();
214  int changed = list[3].toInt();
215 
216  // if something changed reload the tree
217  if (added || removed || changed)
218  reloadTree();
219  }
220  }
221  }
222  else if (event->type() == DialogCompletionEvent::kEventType)
223  {
224  auto dce = static_cast<DialogCompletionEvent*>(event);
225 
226  // make sure the user didn't ESCAPE out of the menu
227  if ((dce == nullptr) || (dce->GetResult() < 0))
228  return;
229 
230  QString resultid = dce->GetId();
231  QString resulttext = dce->GetResultText();
232 
233  if (resultid == "smartplaylistmenu")
234  {
236  return;
237 
239  if (!node)
240  return;
241 
242  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
243  if (!mnode)
244  return;
245 
246  if (resulttext == tr("New Smart Playlist"))
247  {
248  QString category;
249  if (mnode->getAction() == "smartplaylistcategory")
250  category = mnode->GetText();
251 
253  auto* editor = new SmartPlaylistEditor(mainStack);
254 
255  if (!editor->Create())
256  {
257  delete editor;
258  return;
259  }
260 
261  editor->newSmartPlaylist(category);
262 
263  connect(editor, SIGNAL(smartPLChanged(const QString&, const QString&)),
264  this, SLOT(smartPLChanged(QString, QString)));
265 
266  mainStack->AddScreen(editor);
267  }
268  else if (resulttext == tr("Remove Smart Playlist"))
269  {
270  QString category = mnode->getParent()->GetText();
271  QString name = mnode->GetText();
272 
273  ShowOkPopup(tr("Are you sure you want to delete this Smart Playlist?\n"
274  "Category: %1 - Name: %2").arg(category).arg(name),
275  this, SLOT(deleteSmartPlaylist(bool)), true);
276  }
277  else if (resulttext == tr("Edit Smart Playlist"))
278  {
279  QString category = mnode->getParent()->GetText();
280  QString name = mnode->GetText();
281 
283  auto* editor = new SmartPlaylistEditor(mainStack);
284 
285  if (!editor->Create())
286  {
287  delete editor;
288  return;
289  }
290 
291  editor->editSmartPlaylist(category, name);
292 
293  connect(editor, SIGNAL(smartPLChanged(const QString&, const QString&)),
294  this, SLOT(smartPLChanged(QString, QString)));
295 
296  mainStack->AddScreen(editor);
297  }
298  else if (resulttext == tr("Replace Tracks"))
299  {
303  }
304  else if (resulttext == tr("Add Tracks"))
305  {
309  }
310  }
311  else if (resultid == "treeplaylistmenu")
312  {
314  return;
315 
317  if (!node)
318  return;
319 
320  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
321  if (!mnode)
322  return;
323 
324  if (resulttext == tr("Remove Playlist"))
325  {
326  QString name = mnode->GetText();
327 
328  ShowOkPopup(tr("Are you sure you want to delete this Playlist?\n"
329  "Name: %1").arg(name),
330  this, SLOT(deletePlaylist(bool)), true);
331  }
332  else if (resulttext == tr("Replace Tracks"))
333  {
337  }
338  else if (resulttext == tr("Add Tracks"))
339  {
342  }
343  }
344  }
345 
347 }
348 
349 bool PlaylistEditorView::keyPressEvent(QKeyEvent *event)
350 {
351  // if there is a pending jump point pass the key press to the default handler
352  if (GetMythMainWindow()->IsExitingToMain())
353  {
354  // Just call the parent handler. It will properly clean up.
355  return MusicCommon::keyPressEvent(event);
356  }
357 
359  return true;
360 
361  QStringList actions;
362  bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
363 
364  for (int i = 0; i < actions.size() && !handled; i++)
365  {
366  QString action = actions[i];
367  handled = true;
368 
369  if ((action == "EDIT" || action == "INFO") && GetFocusWidget() == m_playlistTree)
370  {
371  handled = false;
372 
374  if (node)
375  {
376  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
377  if (mnode)
378  {
379  if (mnode->getAction() == "smartplaylist" && action == "EDIT")
380  {
381  QString category = mnode->getParent()->GetText();
382  QString name = mnode->GetText();
383 
385  auto* editor = new SmartPlaylistEditor(mainStack);
386 
387  if (!editor->Create())
388  {
389  delete editor;
390  return true;
391  }
392 
393  editor->editSmartPlaylist(category, name);
394  connect(editor, SIGNAL(smartPLChanged(const QString&, const QString&)),
395  this, SLOT(smartPLChanged(QString, QString)));
396 
397  mainStack->AddScreen(editor);
398 
399  handled = true;
400  }
401  else if (mnode->getAction() == "smartplaylistcategory" && action == "EDIT")
402  {
403  QString category = mnode->GetText();
404 
406  auto* editor = new SmartPlaylistEditor(mainStack);
407 
408  if (!editor->Create())
409  {
410  delete editor;
411  return true;
412  }
413 
414  editor->newSmartPlaylist(category);
415 
416  connect(editor, SIGNAL(smartPLChanged(const QString&, const QString&)),
417  this, SLOT(smartPLChanged(QString, QString)));
418 
419  mainStack->AddScreen(editor);
420 
421  handled = true;
422  }
423  else if (mnode->getAction() == "trackid")
424  {
425  MusicMetadata *mdata = gMusicData->m_all_music->getMetadata(mnode->getInt());
426  if (mdata)
427  {
428  if (action == "INFO")
429  showTrackInfo(mdata);
430  else
431  editTrackInfo(mdata);
432  }
433 
434  handled = true;
435  }
436  }
437  }
438  }
439  else if (action == "DELETE" && GetFocusWidget() == m_playlistTree)
440  {
441  handled = false;
442 
444  if (node)
445  {
446  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
447  if (mnode)
448  {
449  if (mnode->getAction() == "smartplaylist")
450  {
451  QString category = mnode->getParent()->GetText();
452  QString name = mnode->GetText();
453 
454  ShowOkPopup(tr("Are you sure you want to delete this Smart Playlist?\n"
455  "Category: %1 - Name: %2").arg(category).arg(name),
456  this, SLOT(deleteSmartPlaylist(bool)), true);
457  handled = true;
458  }
459  else if (mnode->getAction() == "playlist")
460  {
461  QString name = mnode->GetText();
462 
463  ShowOkPopup(tr("Are you sure you want to delete this Playlist?\n"
464  "Name: %1").arg(name),
465  this, SLOT(deletePlaylist(bool)), true);
466  handled = true;
467  }
468  }
469  }
470  }
471  else if (action == "MARK")
472  {
474  if (item)
475  treeItemClicked(item);
476  }
477  else if ((action == "PLAY") && (GetFocusWidget() == m_playlistTree))
478  {
480  if (item)
481  {
482  auto *node = item->GetData().value<MythGenericTree*>();
483  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
484 
485  if (mnode)
486  {
487  updateSonglist(mnode);
488 
489  if (m_songList.count() > 0)
490  {
493 
494  stopVisualizer();
495 
497 
498  if (m_mainvisual)
499  {
500  m_mainvisual->mutex()->lock();
502  m_mainvisual->mutex()->unlock();
503  }
504  }
505  else
506  {
507  handled = false;
508  }
509  }
510  }
511  }
512  else
513  handled = false;
514  }
515 
516  if (!handled && MusicCommon::keyPressEvent(event))
517  handled = true;
518 
519  return handled;
520 }
521 
523 {
524  m_songList.clear();
525 
526  if (node->getAction() == "playlists" ||
527  node->getAction() == "smartplaylists" ||
528  node->getAction() == "smartplaylistcategory")
529  { // NOLINT(bugprone-branch-clone)
530  }
531  else if (node->getAction() == "trackid" || node->getAction() == "cdtrack")
532  {
533  m_songList.append(node->getInt());
534  }
535  else if (node->getAction() == "all tracks" ||
536  node->getAction() == "albums" ||
537  node->getAction() == "compartists" ||
538  node->getAction() == "artists" ||
539  node->getAction() == "genres" ||
540  node->getAction() == "ratings" ||
541  node->getAction() == "years")
542  {
543  // get the list of tracks from the previous 'All Tracks' node
544  auto *allTracksNode = dynamic_cast<MusicGenericTree*>(node->getParent()->getChildAt(0));
545  if (allTracksNode)
546  {
547  for (int x = 0; x < allTracksNode->childCount(); x++)
548  {
549  MythGenericTree *trackNode = allTracksNode->getChildAt(x);
550  if (trackNode)
551  m_songList.append(trackNode->getInt());
552  }
553  }
554  }
555  else if (node->getAction() == "album" ||
556  node->getAction() == "artist" ||
557  node->getAction() == "genre" ||
558  node->getAction() == "rating" ||
559  node->getAction() == "year" ||
560  node->getAction() == "compilations" ||
561  node->getAction() == "compartist")
562  {
563  // get the list of tracks from the 'All Tracks' node
564  auto *allTracksNode = dynamic_cast<MusicGenericTree*>(node->getChildAt(0));
565  if (allTracksNode)
566  {
567  if (allTracksNode->childCount() == 0)
568  filterTracks(allTracksNode);
569 
570  for (int x = 0; x < allTracksNode->childCount(); x++)
571  {
572  MythGenericTree *trackNode = allTracksNode->getChildAt(x);
573  if (trackNode)
574  m_songList.append(trackNode->getInt());
575  }
576  }
577  }
578  else if (node->getAction() == "smartplaylist")
579  {
580  // add the selected smart playlist's tracks to the song list
581  QList<MythGenericTree*> *children = node->getAllChildren();
582  for (int x = 0; x < children->count(); x++)
583  {
584  MythGenericTree *childnode = children->at(x);
585  m_songList.append(childnode->getInt());
586  }
587  }
588  else if (node->getAction() == "playlist")
589  {
590  // get list of tracks to add from the playlist
591  int playlistID = node->getInt();
592  Playlist *playlist = gMusicData->m_all_playlists->getPlaylist(playlistID);
593 
594  if (playlist)
595  {
596  for (int x = 0; x < playlist->getTrackCount(); x++)
597  {
598  MusicMetadata *mdata = playlist->getSongAt(x);
599  if (mdata)
600  m_songList.append((int) mdata->ID());
601  }
602  }
603  }
604  else if (node->getAction() == "error")
605  {
606  // a smart playlist has returned no tracks etc.
607  }
608  else
609  {
610  // fall back to getting the tracks from the MetadataPtrList
611  auto *tracks = node->GetData().value<MetadataPtrList*>();
612  for (int x = 0; x < tracks->count(); x++)
613  {
614  MusicMetadata *mdata = tracks->at(x);
615  if (mdata)
616  m_songList.append((int)mdata->ID());
617  }
618  }
619 }
620 
622 {
624  {
627 
628  MythMenu *menu = nullptr;
629  auto *mnode = dynamic_cast<MusicGenericTree*>(m_playlistTree->GetCurrentNode());
630 
631  if (mnode)
632  {
633  if (mnode->getAction() == "smartplaylists" ||
634  mnode->getAction() == "smartplaylistcategory" ||
635  mnode->getAction() == "smartplaylist")
636  {
638  }
639  else if (mnode->getAction() == "playlists" ||
640  mnode->getAction() == "playlist")
641  {
643  }
644  else if ((mnode->getAction() == "trackid") ||
645  (mnode->getAction() == "error"))
646  {
647  }
648  else
649  {
651  }
652 
653  updateSonglist(mnode);
654  }
655 
656  if (menu)
657  {
658  menu->AddItem(tr("More Options"), nullptr, createMainMenu());
659 
660  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
661 
662  auto *menuPopup = new MythDialogBox(menu, popupStack, "actionmenu");
663 
664  if (menuPopup->Create())
665  popupStack->AddScreen(menuPopup);
666  else
667  delete menu;
668 
669  return;
670  }
671  }
672 
674 }
675 
677 {
678  MythMenu *menu = nullptr;
679 
681  {
682  auto *mnode = dynamic_cast<MusicGenericTree*>(m_playlistTree->GetCurrentNode());
683 
684  if (!mnode)
685  return nullptr;
686 
687  if (mnode->getAction() == "playlist")
688  {
689  menu = new MythMenu(tr("Playlist Actions"), this, "treeplaylistmenu");
690  menu->AddItem(tr("Replace Tracks"));
691  menu->AddItem(tr("Add Tracks"));
692  menu->AddItem(tr("Remove Playlist"));
693  }
694  }
695 
696  return menu;
697 }
698 
700 {
701  MythMenu *menu = nullptr;
702 
704  {
705  auto *mnode = dynamic_cast<MusicGenericTree*>(m_playlistTree->GetCurrentNode());
706 
707  if (!mnode)
708  return nullptr;
709 
710  if (mnode->getAction() == "smartplaylists" || mnode->getAction() == "smartplaylistcategory")
711  {
712  QString label = tr("Smart Playlist Actions");
713 
714  menu = new MythMenu(label, this, "smartplaylistmenu");
715 
716  menu->AddItem(tr("New Smart Playlist"));
717  }
718  else if (mnode->getAction() == "smartplaylist")
719  {
720  menu = new MythMenu(tr("Smart Playlist Actions"), this, "smartplaylistmenu");
721 
722  menu->AddItem(tr("Replace Tracks"));
723  menu->AddItem(tr("Add Tracks"));
724 
725  menu->AddItem(tr("Edit Smart Playlist"));
726  menu->AddItem(tr("New Smart Playlist"));
727  menu->AddItem(tr("Remove Smart Playlist"));
728  }
729  }
730 
731  return menu;
732 }
733 
735 {
736  if (!m_rootNode)
737  m_rootNode = new MusicGenericTree(nullptr, "Root Music Node");
738 
739  auto *node = new MusicGenericTree(m_rootNode, tr("All Tracks"), "all tracks");
740  node->setDrawArrow(true);
741  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
742 
743  node = new MusicGenericTree(m_rootNode, tr("Albums"), "albums");
744  node->setDrawArrow(true);
745  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
746 
747  node = new MusicGenericTree(m_rootNode, tr("Artists"), "artists");
748  node->setDrawArrow(true);
749  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
750 
751  node = new MusicGenericTree(m_rootNode, tr("Genres"), "genres");
752  node->setDrawArrow(true);
753  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
754 #if 0
755  node = new MusicGenericTree(m_rootNode, tr("Tags"), "tags");
756  node->setDrawArrow(true);
757  node->SetData(qVariantFromValue(gMusicData->all_music->getAllMetadata()));
758 #endif
759  node = new MusicGenericTree(m_rootNode, tr("Ratings"), "ratings");
760  node->setDrawArrow(true);
761  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
762 
763  node = new MusicGenericTree(m_rootNode, tr("Years"), "years");
764  node->setDrawArrow(true);
765  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
766 
767  node = new MusicGenericTree(m_rootNode, tr("Compilations"), "compilations");
768  node->setDrawArrow(true);
769 
771  auto *compTracks = new MetadataPtrList;
772  m_deleteList.append(compTracks);
773 
774  for (int x = 0; x < alltracks->count(); x++)
775  {
776  MusicMetadata *mdata = alltracks->at(x);
777  if (mdata)
778  {
779  if (mdata->Compilation())
780  compTracks->append(mdata);
781  }
782  }
783  node->SetData(qVariantFromValue(compTracks));
784 
786  {
787  node = new MusicGenericTree(m_rootNode, tr("CD - %1").arg(gMusicData->m_all_music->getCDTitle()), "cd");
788  node->setDrawArrow(true);
789  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllCDMetadata()));
790  }
791 
792  node = new MusicGenericTree(m_rootNode, tr("Directory"), "directory");
793  node->setDrawArrow(true);
794  node->SetData(qVariantFromValue(gMusicData->m_all_music->getAllMetadata()));
795 
796  node = new MusicGenericTree(m_rootNode, tr("Playlists"), "playlists");
797  node->setDrawArrow(true);
798 
799  node = new MusicGenericTree(m_rootNode, tr("Smart Playlists"), "smartplaylists");
800  node->setDrawArrow(true);
801 }
802 
804 {
805  auto *node = item->GetData().value<MythGenericTree*>();
806  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
807 
808  if (!mnode || !gPlayer->getCurrentPlaylist() || mnode->getAction() == "error")
809  return;
810 
811  if (mnode->getAction() == "trackid")
812  {
813  if (gPlayer->getCurrentPlaylist()->checkTrack(mnode->getInt()))
814  {
815  // remove track from the current playlist
816  gPlayer->removeTrack(mnode->getInt());
817  mnode->setCheck(MythUIButtonListItem::NotChecked);
818  }
819  else
820  {
821  // add track to the current playlist
822  gPlayer->addTrack(mnode->getInt(), true);
823  mnode->setCheck(MythUIButtonListItem::FullChecked);
824  }
825  }
826  else
827  ShowMenu();
828 }
829 
830 
832 {
833  auto *node = item->GetData().value<MythGenericTree*>();;
834  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
835 
836  if (!mnode)
837  return;
838 
839  if (item->GetText("*").isEmpty())
840  {
841  item->SetText(" ", "*");
842 
843  QString artFile;
844 
845  if (mnode->getAction() == "trackid")
846  {
847  MusicMetadata *mdata = gMusicData->m_all_music->getMetadata(mnode->getInt());
848  if (mdata)
849  artFile = mdata->getAlbumArtFile();
850  }
851  else if (mnode->getAction() == "album")
852  {
853  // hunt for a coverart image for the album
854  auto *tracks = node->GetData().value<MetadataPtrList*>();
855  for (int x = 0; x < tracks->count(); x++)
856  {
857  MusicMetadata *mdata = tracks->at(x);
858  if (mdata)
859  {
860  artFile = mdata->getAlbumArtFile();
861  if (!artFile.isEmpty())
862  break;
863  }
864  }
865  }
866  else if (mnode->getAction() == "compartist")
867  {
868  artFile = findIcon("artist", mnode->GetText().toLower());
869  }
870  else if (mnode->getAction() == "all tracks" || mnode->getAction() == "genres" ||
871  mnode->getAction() == "albums" || mnode->getAction() == "artists" ||
872  mnode->getAction() == "compartists"|| mnode->getAction() == "ratings" ||
873  mnode->getAction() == "years" || mnode->getAction() == "compilations" ||
874  mnode->getAction() == "cd" || mnode->getAction() == "directory" ||
875  mnode->getAction() == "playlists" || mnode->getAction() == "smartplaylists")
876  {
877  artFile = "blank.png";
878  }
879  else
880  {
881  artFile = findIcon(mnode->getAction(), mnode->GetText().toLower());
882  }
883 
884  QString state = "default";
885 
886  if (mnode->getAction() == "all tracks")
887  state = "alltracks";
888  else if (mnode->getAction() == "genres")
889  state = "genres";
890  else if (mnode->getAction() == "albums")
891  state = "albums";
892  else if (mnode->getAction() == "artists")
893  state = "artists";
894  else if (mnode->getAction() == "compartists")
895  state = "compartists";
896  else if (mnode->getAction() == "ratings")
897  state = "ratings";
898  else if (mnode->getAction() == "years")
899  state = "years";
900  else if (mnode->getAction() == "compilations")
901  state = "compilations";
902  else if (mnode->getAction() == "cd")
903  state = "cd";
904  else if (mnode->getAction() == "directory")
905  state = "directory";
906  else if (mnode->getAction() == "playlists")
907  state = "playlists";
908  else if (mnode->getAction() == "smartplaylists")
909  state = "smartplaylists";
910 
911  item->DisplayState(state, "nodetype");
912 
913  if (artFile.isEmpty())
914  item->SetImage("");
915  else
916  item->SetImage(artFile);
917  }
918 }
919 
921 {
922  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
923  if (!mnode)
924  return;
925 
926  if (m_breadcrumbsText)
927  {
928  QString route = node->getRouteByString().join(" -> ");
929  route = route.remove("Root Music Node -> ");
930  m_breadcrumbsText->SetText(route);
931  }
932 
933  if (m_positionText)
934  {
935  m_positionText->SetText(tr("%1 of %2").arg(node->getPosition() + 1)
936  .arg(node->siblingCount()));
937  }
938 
939  if (mnode->childCount() > 0 || mnode->getAction() == "trackid")
940  return;
941 
942  if (mnode->getAction() == "smartplaylists")
943  {
945  }
946  else if (mnode->getAction() == "smartplaylistcategory")
947  {
948  getSmartPlaylists(mnode);
949  }
950  else if (mnode->getAction() == "smartplaylist")
951  {
952  getSmartPlaylistTracks(mnode, mnode->getInt());
953  }
954  else if (mnode->getAction() == "playlists")
955  {
956  getPlaylists(mnode);
957  }
958  else if (mnode->getAction() == "playlist")
959  {
960  getPlaylistTracks(mnode, mnode->getInt());
961  }
962  else if (mnode->getAction() == "cd")
963  {
964  getCDTracks(mnode);
965  }
966  else
967  filterTracks(mnode);
968 }
969 
971 {
972  auto *tracks = node->GetData().value<MetadataPtrList*>();
973 
974  if (!tracks)
975  return;
976 
977  if (node->getAction() == "all tracks")
978  {
979  QMap<QString, int> map;
980  QStringList list;
981  bool isAlbum = false;
982  auto *parentNode = dynamic_cast<MusicGenericTree*>(node->getParent());
983 
984  if (parentNode)
985  isAlbum = parentNode->getAction() == "album";
986 
987  for (int x = 0; x < tracks->count(); x++)
988  {
989  MusicMetadata *mdata = tracks->at(x);
990  if (mdata)
991  {
992  QString key = mdata->Title();
993 
994  // Add the track number if an album is selected
995  if (isAlbum && mdata->Track() > 0)
996  {
997  key.prepend(QString::number(mdata->Track()) + " - ");
998  if (mdata->Track() < 10)
999  key.prepend("0");
1000 
1001  // Add the disc number if this is a multi-disc album
1002  if (mdata->DiscNumber() > 0)
1003  {
1004  key.prepend(QString::number(mdata->DiscNumber()) + "/");
1005  if (mdata->DiscNumber() < 10)
1006  key.prepend("0");
1007  }
1008  }
1009  map.insertMulti(key, mdata->ID());
1010  }
1011  }
1012 
1013  QMap<QString, int>::const_iterator i = map.constBegin();
1014  while (i != map.constEnd())
1015  {
1016  auto *newnode = new MusicGenericTree(node, i.key(), "trackid");
1017  newnode->setInt(i.value());
1018  newnode->setDrawArrow(false);
1019  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(newnode->getInt()) : false;
1021  ++i;
1022  }
1023 
1024  node->sortByString(); // Case-insensitive sort
1025  }
1026  else if (node->getAction() == "artists")
1027  {
1028  QMap<QString, MetadataPtrList*> map;
1029 
1030  for (int x = 0; x < tracks->count(); x++)
1031  {
1032  MusicMetadata *mdata = tracks->at(x);
1033  if (mdata)
1034  {
1035  if (map.contains(mdata->Artist()))
1036  {
1037  MetadataPtrList *filteredTracks = map.value(mdata->Artist());
1038  filteredTracks->append(mdata);
1039  }
1040  else
1041  {
1042  auto *filteredTracks = new MetadataPtrList;
1043  m_deleteList.append(filteredTracks);
1044  filteredTracks->append(mdata);
1045  map.insert(mdata->Artist(), filteredTracks);
1046  }
1047  }
1048  }
1049 
1050  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1051  while (i != map.constEnd())
1052  {
1053  auto *newnode = new MusicGenericTree(node, i.key(), "artist");
1054  newnode->SetData(qVariantFromValue(i.value()));
1055  ++i;
1056  }
1057 
1058  node->sortByString(); // Case-insensitive sort
1059  }
1060  else if (node->getAction() == "compartists")
1061  {
1062  QMap<QString, MetadataPtrList*> map;
1063 
1064  for (int x = 0; x < tracks->count(); x++)
1065  {
1066  MusicMetadata *mdata = tracks->at(x);
1067  if (mdata)
1068  {
1069  if (mdata->CompilationArtist() != mdata->Artist())
1070  {
1071  if (map.contains(mdata->CompilationArtist()))
1072  {
1073  MetadataPtrList *filteredTracks = map.value(mdata->CompilationArtist());
1074  filteredTracks->append(mdata);
1075  }
1076  else
1077  {
1078  auto *filteredTracks = new MetadataPtrList;
1079  m_deleteList.append(filteredTracks);
1080  filteredTracks->append(mdata);
1081  map.insert(mdata->CompilationArtist(), filteredTracks);
1082  }
1083  }
1084  }
1085  }
1086 
1087  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1088  while (i != map.constEnd())
1089  {
1090  auto *newnode = new MusicGenericTree(node, i.key(), "compartist");
1091  newnode->SetData(qVariantFromValue(i.value()));
1092  ++i;
1093  }
1094 
1095  node->sortByString(); // Case-insensitive sort
1096  }
1097  else if (node->getAction() == "albums")
1098  {
1099  QMap<QString, MetadataPtrList*> map;
1100 
1101  for (int x = 0; x < tracks->count(); x++)
1102  {
1103  MusicMetadata *mdata = tracks->at(x);
1104  if (mdata)
1105  {
1106  if (map.contains(mdata->Album()))
1107  {
1108  MetadataPtrList *filteredTracks = map.value(mdata->Album());
1109  filteredTracks->append(mdata);
1110  }
1111  else
1112  {
1113  auto *filteredTracks = new MetadataPtrList;
1114  m_deleteList.append(filteredTracks);
1115  filteredTracks->append(mdata);
1116  map.insert(mdata->Album(), filteredTracks);
1117  }
1118  }
1119  }
1120 
1121  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1122  while (i != map.constEnd())
1123  {
1124  auto *newnode = new MusicGenericTree(node, i.key(), "album");
1125  newnode->SetData(qVariantFromValue(i.value()));
1126  ++i;
1127  }
1128 
1129  node->sortByString(); // Case-insensitive sort
1130  }
1131  else if (node->getAction() == "genres")
1132  {
1133  QMap<QString, MetadataPtrList*> map;
1134 
1135  for (int x = 0; x < tracks->count(); x++)
1136  {
1137  MusicMetadata *mdata = tracks->at(x);
1138  if (mdata)
1139  {
1140  if (map.contains(mdata->Genre()))
1141  {
1142  MetadataPtrList *filteredTracks = map.value(mdata->Genre());
1143  filteredTracks->append(mdata);
1144  }
1145  else
1146  {
1147  auto *filteredTracks = new MetadataPtrList;
1148  m_deleteList.append(filteredTracks);
1149  filteredTracks->append(mdata);
1150  map.insert(mdata->Genre(), filteredTracks);
1151  }
1152  }
1153  }
1154 
1155  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1156  while (i != map.constEnd())
1157  {
1158  auto *newnode = new MusicGenericTree(node, i.key(), "genre");
1159  newnode->SetSortText(i.key()); // No manipulation of prefixes on genres
1160  newnode->SetData(qVariantFromValue(i.value()));
1161  ++i;
1162  }
1163 
1164  node->sortByString(); // Case-insensitive sort
1165  }
1166  else if (node->getAction() == "ratings")
1167  {
1168  QMap<QString, MetadataPtrList*> map;
1169 
1170  for (int x = 0; x < tracks->count(); x++)
1171  {
1172  MusicMetadata *mdata = tracks->at(x);
1173  if (mdata)
1174  {
1175  QString ratingStr = tr("%n Star(s)", "", mdata->Rating());
1176  if (map.contains(ratingStr))
1177  {
1178  MetadataPtrList *filteredTracks = map.value(ratingStr);
1179  filteredTracks->append(mdata);
1180  }
1181  else
1182  {
1183  auto *filteredTracks = new MetadataPtrList;
1184  m_deleteList.append(filteredTracks);
1185  filteredTracks->append(mdata);
1186  map.insert(ratingStr, filteredTracks);
1187  }
1188  }
1189  }
1190 
1191  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1192  while (i != map.constEnd())
1193  {
1194  auto *newnode = new MusicGenericTree(node, i.key(), "rating");
1195  newnode->SetData(qVariantFromValue(i.value()));
1196  ++i;
1197  }
1198  }
1199  else if (node->getAction() == "years")
1200  {
1201  QMap<QString, MetadataPtrList*> map;
1202 
1203  for (int x = 0; x < tracks->count(); x++)
1204  {
1205  MusicMetadata *mdata = tracks->at(x);
1206  if (mdata)
1207  {
1208  QString yearStr = QString("%1").arg(mdata->Year());
1209  if (map.contains(yearStr))
1210  {
1211  MetadataPtrList *filteredTracks = map.value(yearStr);
1212  filteredTracks->append(mdata);
1213  }
1214  else
1215  {
1216  auto *filteredTracks = new MetadataPtrList;
1217  m_deleteList.append(filteredTracks);
1218  filteredTracks->append(mdata);
1219  map.insert(yearStr, filteredTracks);
1220  }
1221  }
1222  }
1223 
1224  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1225  while (i != map.constEnd())
1226  {
1227  auto *newnode = new MusicGenericTree(node, i.key(), "year");
1228  newnode->SetData(qVariantFromValue(i.value()));
1229  ++i;
1230  }
1231  }
1232  else if (node->getAction() == "directory")
1233  {
1234  QMap<QString, MetadataPtrList*> map;
1235 
1236  // which directories have we already filtered by
1237  QString dir;
1238  MusicGenericTree *climber = node;
1239  while (climber)
1240  {
1241  dir = climber->GetText() + '/' + dir;
1242  climber = dynamic_cast<MusicGenericTree *>(climber->getParent());
1243  }
1244 
1245  // remove the top two nodes
1246  QString top2 = "Root Music Node/" + tr("Directory") + '/';
1247  if (dir.startsWith(top2))
1248  dir = dir.mid(top2.length());
1249 
1250  for (int x = 0; x < tracks->count(); x++)
1251  {
1252  MusicMetadata *mdata = tracks->at(x);
1253  if (mdata)
1254  {
1255  QString filename = mdata->Filename(false);
1256 
1257  if (filename.startsWith(dir))
1258  filename = filename.mid(dir.length());
1259 
1260  QStringList dirs = filename.split("/");
1261 
1262  QString key = dirs.count() > 1 ? dirs[0] : "[TRACK]" + dirs[0];
1263  if (map.contains(key))
1264  {
1265  MetadataPtrList *filteredTracks = map.value(key);
1266  filteredTracks->append(mdata);
1267  }
1268  else
1269  {
1270  auto *filteredTracks = new MetadataPtrList;
1271  m_deleteList.append(filteredTracks);
1272  filteredTracks->append(mdata);
1273  map.insert(key, filteredTracks);
1274  }
1275  }
1276  }
1277 
1278  // add directories first
1279  QMap<QString, MetadataPtrList*>::const_iterator i = map.constBegin();
1280  while (i != map.constEnd())
1281  {
1282  if (!i.key().startsWith("[TRACK]"))
1283  {
1284  auto *newnode = new MusicGenericTree(node, i.key(), "directory");
1285  newnode->SetData(qVariantFromValue(i.value()));
1286  }
1287  ++i;
1288  }
1289 
1290  // now add tracks
1291  i = map.constBegin();
1292  while (i != map.constEnd())
1293  {
1294  if (i.key().startsWith("[TRACK]"))
1295  {
1296  auto *newnode = new MusicGenericTree(node, i.key().mid(7), "trackid");
1297  newnode->setInt(i.value()->at(0)->ID());
1298  newnode->setDrawArrow(false);
1299  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(newnode->getInt()) : false;
1301  }
1302  ++i;
1303  }
1304  }
1305  else if (node->getAction() == "artist" || node->getAction() == "compartist" ||
1306  node->getAction() == "album" || node->getAction() == "genre" ||
1307  node->getAction() == "rating" || node->getAction() == "year" ||
1308  node->getAction() == "compilations")
1309  {
1310  // which fields have we already filtered by
1311  QStringList fields;
1312  MusicGenericTree *climber = node;
1313  while (climber)
1314  {
1315  fields.append(climber->getAction());
1316  climber = dynamic_cast<MusicGenericTree *>(climber->getParent());
1317  }
1318 
1319  auto *newnode = new MusicGenericTree(node, tr("All Tracks"), "all tracks");
1320  newnode->setDrawArrow(true);
1321  newnode->SetData(node->GetData());
1322 
1323  if (!fields.contains("albums"))
1324  {
1325  newnode = new MusicGenericTree(node, tr("Albums"), "albums");
1326  newnode->setDrawArrow(true);
1327  newnode->SetData(node->GetData());
1328  }
1329 
1330  if (!fields.contains("artists"))
1331  {
1332  newnode = new MusicGenericTree(node, tr("Artists"), "artists");
1333  newnode->setDrawArrow(true);
1334  newnode->SetData(node->GetData());
1335  }
1336 
1337  if (!fields.contains("compartists"))
1338  {
1339  // only show the Compilation Artists node if we are one the Compilations branch
1340  bool showCompArtists = false;
1341  MusicGenericTree *mnode = node;
1342  do
1343  {
1344  if (mnode->getAction() == "compilations")
1345  {
1346  showCompArtists = true;
1347  break;
1348  }
1349 
1350  mnode = dynamic_cast<MusicGenericTree *>(mnode->getParent());
1351 
1352  } while (mnode);
1353 
1354  // only show the Comp. Artist if it differs from the Artist
1355  bool found = false;
1356  auto *tracks2 = node->GetData().value<MetadataPtrList*>();
1357  for (int x = 0; x < tracks2->count(); x++)
1358  {
1359  MusicMetadata *mdata = tracks2->at(x);
1360  if (mdata)
1361  {
1362  if (mdata->Artist() != mdata->CompilationArtist())
1363  {
1364  found = true;
1365  break;
1366  }
1367  }
1368  }
1369 
1370  if (showCompArtists && found)
1371  {
1372  newnode = new MusicGenericTree(node, tr("Compilation Artists"), "compartists");
1373  newnode->setDrawArrow(true);
1374  newnode->SetData(node->GetData());
1375  }
1376  }
1377 
1378  if (!fields.contains("genres"))
1379  {
1380  newnode = new MusicGenericTree(node, tr("Genres"), "genres");
1381  newnode->setDrawArrow(true);
1382  newnode->SetData(node->GetData());
1383  }
1384 #if 0
1385  if (!fields.contains("tags"))
1386  {
1387  newnode = new MusicGenericTree(node, tr("Tags"), "tags");
1388  newnode->setDrawArrow(true);
1389  newnode->SetData(node->GetData());
1390  }
1391 #endif
1392  if (!fields.contains("ratings"))
1393  {
1394  newnode = new MusicGenericTree(node, tr("Ratings"), "ratings");
1395  newnode->setDrawArrow(true);
1396  newnode->SetData(node->GetData());
1397  }
1398 
1399  if (!fields.contains("years"))
1400  {
1401  newnode = new MusicGenericTree(node, tr("Years"), "years");
1402  newnode->setDrawArrow(true);
1403  newnode->SetData(node->GetData());
1404  }
1405  }
1406 }
1407 
1409 {
1410  MSqlQuery query(MSqlQuery::InitCon());
1411 
1412  if (query.exec("SELECT categoryid, name FROM music_smartplaylist_categories ORDER BY name;"))
1413  {
1414  if (query.isActive() && query.size() > 0)
1415  {
1416  while (query.next())
1417  {
1418  // No memory leak. MusicGenericTree adds the new node
1419  // into a list of nodes maintained by its parent.
1420  auto *newnode =
1421  new MusicGenericTree(node, query.value(1).toString(), "smartplaylistcategory");
1422  newnode->setInt(query.value(0).toInt());
1423  }
1424  }
1425  }
1426  else
1427  {
1428  MythDB::DBError("Load smartplaylist categories", query);
1429  }
1430 }
1431 
1433 {
1434  int categoryid = node->getInt();
1435 
1436  MSqlQuery query(MSqlQuery::InitCon());
1437  query.prepare("SELECT smartplaylistid, name FROM music_smartplaylists WHERE categoryid = :CATEGORYID "
1438  "ORDER BY name;");
1439  query.bindValue(":CATEGORYID", categoryid);
1440  if (query.exec())
1441  {
1442  if (query.isActive() && query.size() > 0)
1443  {
1444  while (query.next())
1445  {
1446  // No memory leak. MusicGenericTree adds the new node
1447  // into a list of nodes maintained by its parent.
1448  auto *newnode =
1449  new MusicGenericTree(node, query.value(1).toString(), "smartplaylist");
1450  newnode->setInt(query.value(0).toInt());
1451  }
1452  }
1453  }
1454  else
1455  MythDB::DBError("Load smartplaylist names", query);
1456 }
1457 
1459 {
1460  MSqlQuery query(MSqlQuery::InitCon());
1461 
1462  // find smartplaylist
1463  QString matchType;
1464  QString orderBy;
1465  int limitTo = 0;
1466 
1467  query.prepare("SELECT smartplaylistid, matchtype, orderby, limitto "
1468  "FROM music_smartplaylists "
1469  "WHERE smartplaylistid = :SMARTPLAYLISTID;");
1470  query.bindValue(":SMARTPLAYLISTID", playlistID);
1471 
1472  if (query.exec())
1473  {
1474  if (query.isActive() && query.size() > 0)
1475  {
1476  query.first();
1477  matchType = (query.value(1).toString() == "All") ? " AND " : " OR ";
1478  orderBy = query.value(2).toString();
1479  limitTo = query.value(3).toInt();
1480  }
1481  else
1482  {
1483  LOG(VB_GENERAL, LOG_WARNING,
1484  LOC + QString("Cannot find smartplaylist: %1").arg(playlistID));
1485  return;
1486  }
1487  }
1488  else
1489  {
1490  MythDB::DBError("Find SmartPlaylist", query);
1491  return;
1492  }
1493 
1494  // get smartplaylist items
1495  QString whereClause = "WHERE ";
1496 
1497  query.prepare("SELECT field, operator, value1, value2 "
1498  "FROM music_smartplaylist_items "
1499  "WHERE smartplaylistid = :ID;");
1500  query.bindValue(":ID", playlistID);
1501  if (query.exec())
1502  {
1503  bool bFirst = true;
1504  while (query.next())
1505  {
1506  QString fieldName = query.value(0).toString();
1507  QString operatorName = query.value(1).toString();
1508  QString value1 = query.value(2).toString();
1509  QString value2 = query.value(3).toString();
1510  if (!bFirst)
1511  whereClause += matchType + getCriteriaSQL(fieldName,
1512  operatorName, value1, value2);
1513  else
1514  {
1515  bFirst = false;
1516  whereClause += " " + getCriteriaSQL(fieldName, operatorName,
1517  value1, value2);
1518  }
1519  }
1520  }
1521 
1522  // add order by clause
1523  whereClause += getOrderBySQL(orderBy);
1524 
1525  // add limit
1526  if (limitTo > 0)
1527  whereClause += " LIMIT " + QString::number(limitTo);
1528 
1529 
1530  // find the tracks for this smartplaylist
1531  QString theQuery;
1532 
1533  theQuery = "SELECT song_id, name FROM music_songs "
1534  "LEFT JOIN music_directories ON"
1535  " music_songs.directory_id=music_directories.directory_id "
1536  "LEFT JOIN music_artists ON"
1537  " music_songs.artist_id=music_artists.artist_id "
1538  "LEFT JOIN music_albums ON"
1539  " music_songs.album_id=music_albums.album_id "
1540  "LEFT JOIN music_genres ON"
1541  " music_songs.genre_id=music_genres.genre_id "
1542  "LEFT JOIN music_artists AS music_comp_artists ON "
1543  "music_albums.artist_id=music_comp_artists.artist_id ";
1544  if (whereClause.length() > 0)
1545  theQuery += whereClause;
1546 
1547  if (!query.exec(theQuery))
1548  {
1549  MythDB::DBError("Load songlist from query", query);
1550  return;
1551  }
1552 
1553  while (query.next())
1554  {
1555  auto *newnode =
1556  new MusicGenericTree(node, query.value(1).toString(), "trackid");
1557  newnode->setInt(query.value(0).toInt());
1558  newnode->setDrawArrow(false);
1559  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(newnode->getInt()) : false;
1561  }
1562 
1563  // check we found some tracks if not add something to let the user know
1564  if (node->childCount() == 0)
1565  {
1566  auto *newnode =
1567  new MusicGenericTree(node, tr("** No matching tracks **"), "error");
1568  newnode->setDrawArrow(false);
1569  }
1570 }
1571 
1573 {
1574  QList<Playlist*> *playlists = gMusicData->m_all_playlists->getPlaylists();
1575 
1576  for (int x =0; x < playlists->count(); x++)
1577  {
1578  Playlist *playlist = playlists->at(x);
1579  // No memory leak. MusicGenericTree adds the new node
1580  // into a list of nodes maintained by its parent.
1581  auto *newnode =
1582  new MusicGenericTree(node, playlist->getName(), "playlist");
1583  newnode->setInt(playlist->getID());
1584  }
1585 }
1586 
1588 {
1590 
1591  for (int x = 0; x < tracks->count(); x++)
1592  {
1593  MusicMetadata *mdata = tracks->at(x);
1594  QString title = QString("%1 - %2").arg(mdata->Track()).arg(mdata->FormatTitle());
1595  auto *newnode = new MusicGenericTree(node, title, "trackid");
1596  newnode->setInt(mdata->ID());
1597  newnode->setDrawArrow(false);
1598  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(mdata->ID()) : false;
1600  }
1601 }
1602 
1604 {
1605  Playlist *playlist = gMusicData->m_all_playlists->getPlaylist(playlistID);
1606 
1607  if (playlist)
1608  {
1609  for (int x = 0; x < playlist->getTrackCount(); x++)
1610  {
1611  MusicMetadata *mdata = playlist->getSongAt(x);
1612  if (mdata)
1613  {
1614  auto *newnode = new MusicGenericTree(node, mdata->Title(), "trackid");
1615  newnode->setInt(mdata->ID());
1616  newnode->setDrawArrow(false);
1617  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(mdata->ID()) : false;
1619  }
1620  }
1621  }
1622 
1623  // check we found some tracks if not add something to let the user know
1624  if (node->childCount() == 0)
1625  {
1626  auto *newnode =
1627  new MusicGenericTree(node, tr("** Empty Playlist!! **"), "error");
1628  newnode->setDrawArrow(false);
1629  }
1630 }
1631 
1633 {
1635 }
1636 
1638 {
1639  for (int x = 0; x < node->childCount(); x++)
1640  {
1641  auto *mnode = dynamic_cast<MusicGenericTree*>(node->getChildAt(x));
1642  if (mnode)
1643  {
1644  if (mnode->getAction() == "trackid")
1645  {
1646  bool hasTrack = gPlayer->getCurrentPlaylist() ? gPlayer->getCurrentPlaylist()->checkTrack(mnode->getInt()) : false;
1648  }
1649  else
1650  {
1651  if (mnode->childCount())
1652  updateSelectedTracks(mnode);
1653  }
1654  }
1655  }
1656 }
1657 
1659 {
1660  QStringList route = m_playlistTree->GetCurrentNode()->getRouteByString();
1661 
1662  m_playlistTree->Reset();
1663 
1664  for (int x = 0; x < m_deleteList.count(); x++)
1665  delete m_deleteList.at(x);
1666  m_deleteList.clear();
1667 
1669  createRootNode();
1671 
1672  restoreTreePosition(route);
1673 }
1674 
1675 void PlaylistEditorView::restoreTreePosition(const QStringList &route)
1676 {
1677  if (route.count() < 2)
1678  return;
1679 
1680  // traverse up the tree creating each nodes children as we go
1681  MythGenericTree *node = m_rootNode;
1682  for (int x = 1 ; x < route.count(); x++)
1683  {
1684  node = node->getChildByName(route.at(x));
1685 
1686  if (node)
1687  treeNodeChanged(node);
1688  else
1689  break;
1690  }
1691 
1693 }
1694 
1696 {
1697  if (m_playlistTree)
1698  {
1699  QString route = m_playlistTree->GetCurrentNode()->getRouteByString().join("\n");
1700  gCoreContext->SaveSetting("MusicTreeLastActive", route);
1701  }
1702 }
1703 
1704 void PlaylistEditorView::smartPLChanged(const QString &category, const QString &name)
1705 {
1706  reloadTree();
1707 
1708  // move to the smart playlist in tree
1709  QStringList route;
1710  route << "Root Music Node" << tr("Smart Playlists") << category << name;
1711  restoreTreePosition(route);
1712 }
1713 
1715 {
1716  if (!ok)
1717  return;
1718 
1720  if (node)
1721  {
1722  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
1723  if (mnode)
1724  {
1725  if (mnode->getAction() == "smartplaylist")
1726  {
1727  QString category = mnode->getParent()->GetText();
1728  QString name = mnode->GetText();
1729 
1731  reloadTree();
1732  }
1733  }
1734  }
1735 }
1736 
1738 {
1739  if (!ok)
1740  return;
1741 
1743  if (node)
1744  {
1745  auto *mnode = dynamic_cast<MusicGenericTree*>(node);
1746  if (mnode)
1747  {
1748  if (mnode->getAction() == "playlist")
1749  {
1750  int id = mnode->getInt();
1751 
1754  }
1755  }
1756  }
1757 }
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
static bool deleteSmartPlaylist(const QString &category, const QString &name)
MusicMetadata * getSongAt(int pos) const
Definition: playlist.cpp:1090
MythGenericTree * GetCurrentNode(void) const
int getCDTrackCount(void) const
void treeNodeChanged(MythGenericTree *node)
QString FormatTitle()
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
int DiscNumber() const
MusicPlayer * gPlayer
Definition: musicplayer.cpp:35
MythGenericTree * addNode(const QString &a_string, int an_int=0, bool selectable_flag=false, bool visible=true)
static Type MetadataChangedEvent
Definition: musicplayer.h:46
uint visibleChildCount() const
void doUpdatePlaylist(void)
void deletePlaylist(int kill_me)
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
Playlist * getCurrentPlaylist(void)
QList< MythGenericTree * > * getAllChildren() const
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
MythUIButtonListItem * CreateListButton(MythUIButtonList *list) override
void removeListener(QObject *listener)
static Type TrackAddedEvent
Definition: musicplayer.h:42
QString getOrderBySQL(const QString &orderByFields)
void SaveSetting(const QString &key, int newValue)
static Type MythEventMessage
Definition: mythevent.h:66
static void editTrackInfo(MusicMetadata *mdata)
MusicView m_currentView
Definition: musiccommon.h:139
void removeListener(QObject *listener)
Remove a listener to the observable.
bool SetNodeByString(QStringList route)
Using a path based on the node string, set the currently selected node.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QString Genre() const
QString getAction(void) const
Basic menu dialog, message and a list of options.
PlayPLOption playPLOption
Definition: playlist.h:37
int childCount(void) const
PlaylistOptions m_playlistOptions
Definition: musiccommon.h:164
void customEvent(QEvent *event) override
MythUIButtonListItem::CheckState m_check
static void getSmartPlaylistTracks(MusicGenericTree *node, int playlistID)
int size(void) const
Definition: mythdbcon.h:203
void ShowMenu(void) override
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:135
const QVariant GetData(void) const
MythScreenStack * GetStack(const QString &stackname)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
MythUIText * m_breadcrumbsText
void RemoveCurrentItem(bool deleteNode=false)
Remove the currently selected item from the tree.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythScreenStack * GetMainStack()
void addListener(QObject *listener)
Add a listener to the observable.
void setDrawArrow(bool flag)
void treeItemClicked(MythUIButtonListItem *item)
static MythThemedMenu * menu
MythUIButtonTree * m_playlistTree
QString getName(void)
Definition: playlist.h:103
void BuildFocusList(void)
static void getSmartPlaylistCategories(MusicGenericTree *node)
int Rating() const
Playlist * getPlaylist(int id)
static Type kEventType
Definition: mythdialogbox.h:50
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
static Type CDChangedEvent
Definition: musicplayer.h:49
bool m_moveTrackMode
Definition: musiccommon.h:150
void deleteSmartPlaylist(bool ok)
QList< MetadataPtrList * > m_deleteList
QVariant value(int i) const
Definition: mythdbcon.h:198
QString getCriteriaSQL(const QString &fieldName, const QString &operatorName, QString value1, QString value2)
QString GetText(const QString &name="") const
QString Artist() const
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
PlaylistContainer * m_all_playlists
Definition: musicdata.h:53
bool keyPressEvent(QKeyEvent *) override
Key event handler.
MusicMetadata * getMetadata(int an_id)
bool checkTrack(MusicMetadata::IdType trackID) const
Definition: playlist.cpp:38
MainVisual * m_mainvisual
Definition: musiccommon.h:142
int getTrackCount(void)
Definition: playlist.h:79
QPointer< MusicButtonItem > m_buttonItem
void addTrack(int trackID, bool updateUI)
QString GetSetting(const QString &key, const QString &defaultval="")
void customEvent(QEvent *event) override
IdType ID() const
QString findIcon(const QString &type, const QString &name, bool ignoreCache)
find an image for a artist or genre
Definition: musicutils.cpp:34
MythMenu * createMainMenu(void)
static void getSmartPlaylists(MusicGenericTree *node)
bool isActive(void) const
Definition: mythdbcon.h:204
QList< MusicMetadata * > MetadataPtrList
QString getAlbumArtFile(void)
int Track() const
void filterTracks(MusicGenericTree *node)
static Type TrackRemovedEvent
Definition: musicplayer.h:43
static void getCDTracks(MusicGenericTree *node)
void SetSortText(const QString &text)
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
void SetText(const QString &text, const QString &name="", const QString &state="")
PlaylistEditorView(MythScreenStack *parent, MythScreenType *parentScreen, const QString &layout, bool restorePosition=false)
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
QString Album() const
int getID(void)
Definition: playlist.h:108
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
void restoreTreePosition(const QStringList &route)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythUIType * GetFocusWidget(void) const
MythMainWindow * GetMythMainWindow(void)
QString GetText(const QString &name="") const
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:792
bool Compilation() const
MythMenu * createSmartPlaylistMenu(void)
static void getPlaylistTracks(MusicGenericTree *node, int playlistID)
int getInt() const
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
int Year() const
void updateSonglist(MusicGenericTree *node)
QList< Playlist * > * getPlaylists(void)
static Type AllTracksRemovedEvent
Definition: musicplayer.h:45
int siblingCount(void) const
QMutex * mutex()
Definition: visual.h:25
void prepare(void) override
Definition: mainvisual.cpp:139
MythUIText * m_positionText
MythGenericTree * getChildByName(const QString &a_name) const
QString Title() const
MythUIButtonListItem * GetItemCurrent(void) const
Return the currently selected list item.
static void getPlaylists(MusicGenericTree *node)
bool AssignTree(MythGenericTree *tree)
Assign the root node of the tree to be displayed.
bool Create(void) override
void setCheck(MythUIButtonListItem::CheckState state)
void SetData(QVariant data)
QString Filename(bool find=true)
MusicGenericTree(MusicGenericTree *parent, const QString &name, const QString &action="", MythUIButtonListItem::CheckState check=MythUIButtonListItem::CantCheck, bool showArrow=true)
static Type PlaylistChangedEvent
Definition: musicplayer.h:50
void stopVisualizer(void)
MusicData * gMusicData
Definition: musicdata.cpp:20
MythGenericTree * getParent(void) const
InsertPLOption insertPLOption
Definition: playlist.h:36
QList< int > m_songList
Definition: musiccommon.h:168
void setInt(int an_int)
static void treeItemVisible(MythUIButtonListItem *item)
QString CompilationArtist() const
void removeTrack(int trackID)
MythGenericTree * getChildAt(uint reference) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QString getCDTitle(void) const
MusicGenericTree * m_rootNode
void ShowMenu(void) override
void setSelectable(bool flag)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
Screen in which all other widgets are contained and rendered.
static void showTrackInfo(MusicMetadata *mdata)
MythMenu * createPlaylistOptionsMenu(void)
MetadataPtrList * getAllMetadata(void)
static Type AlbumArtChangedEvent
Definition: musicplayer.h:48
void DisplayState(const QString &state, const QString &name)
MetadataPtrList * getAllCDMetadata(void)
QStringList getRouteByString(void)
void smartPLChanged(const QString &category, const QString &name)
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
AllMusic * m_all_music
Definition: musicdata.h:54
#define LOC
bool CreateCommon(void)
Definition: musiccommon.cpp:81
MythMenu * createPlaylistMenu(void)