MythTV master
smartplaylist.cpp
Go to the documentation of this file.
1// c/c++
2#include <iostream>
3#include <unistd.h>
4#include <utility>
5
6// qt
7#include <QKeyEvent>
8#include <QSqlDriver>
9#include <QSqlField>
10
11// MythTV
13#include <libmythbase/mythdb.h>
26
27// mythmusic
28#include "musiccommon.h"
29#include "musicdata.h"
30#include "smartplaylist.h"
31
33{
34 QString m_name;
35 QString m_sqlName;
40};
41
42static const std::array<const SmartPLField,13> SmartPLFields
43{{
44 { "", "", ftString, 0, 0, 0 },
45 { "Artist", "music_artists.artist_name", ftString, 0, 0, 0 },
46 { "Album", "music_albums.album_name", ftString, 0, 0, 0 },
47 { "Title", "music_songs.name", ftString, 0, 0, 0 },
48 { "Genre", "music_genres.genre", ftString, 0, 0, 0 },
49 { "Year", "music_songs.year", ftNumeric, 1900, 2099, 2000 },
50 { "Track No.", "music_songs.track", ftNumeric, 0, 99, 0 },
51 { "Rating", "music_songs.rating", ftNumeric, 0, 10, 0 },
52 { "Play Count", "music_songs.numplays", ftNumeric, 0, 9999, 0 },
53 { "Compilation", "music_albums.compilation", ftBoolean, 0, 0, 0 },
54 { "Comp. Artist", "music_comp_artists.artist_name", ftString, 0, 0, 0 },
55 { "Last Play", "FROM_DAYS(TO_DAYS(music_songs.lastplay))",
56 ftDate, 0, 0, 0 },
57 { "Date Imported", "FROM_DAYS(TO_DAYS(music_songs.date_entered))",
58 ftDate, 0, 0, 0 },
59}};
60
62{
63 QString m_name;
67};
68
69static const std::array<const SmartPLOperator,11> SmartPLOperators
70{{
71 { "is equal to", 1, false, true },
72 { "is not equal to", 1, false, true },
73 { "is greater than", 1, false, false },
74 { "is less than", 1, false, false },
75 { "starts with", 1, true, false },
76 { "ends with", 1, true, false },
77 { "contains", 1, true, false },
78 { "does not contain", 1, true, false },
79 { "is between", 2, false, false },
80 { "is set", 0, false, false },
81 { "is not set", 0, false, false },
82}};
83
84static const SmartPLOperator *lookupOperator(const QString& name)
85{
86 for (const auto & oper : SmartPLOperators)
87 {
88 if (oper.m_name == name)
89 return &oper;
90 }
91 return nullptr;
92}
93
94static const SmartPLField *lookupField(const QString& name)
95{
96 for (const auto & field : SmartPLFields)
97 {
98 if (field.m_name == name)
99 return &field;
100 }
101 return nullptr;
102}
103
104QString formattedFieldValue(const QVariant &value)
105{
106#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
107 QSqlField field("", value.type());
108#else
109 QSqlField field("", value.metaType());
110#endif
111 if (value.isNull())
112 field.clear();
113 else
114 field.setValue(value);
115
117 QString result = QString::fromUtf8(query.driver()->formatValue(field).toLatin1().data());
118 return result;
119}
120
121static QString evaluateDateValue(QString sDate)
122{
123 if (sDate.startsWith("$DATE"))
124 {
125 QDate date = MythDate::current().toLocalTime().date();
126
127 if (sDate.length() > 9)
128 {
129 bool bNegative = false;
130 if (sDate[6] == '-')
131 bNegative = true;
132
133 if (sDate.endsWith(" days"))
134 sDate = sDate.left(sDate.length() - 5);
135
136 int nDays = sDate.mid(8).toInt();
137 if (bNegative)
138 nDays = -nDays;
139
140 date = date.addDays(nDays);
141 }
142
143 return date.toString(Qt::ISODate);
144 }
145
146 return sDate;
147}
148
149QString getCriteriaSQL(const QString& fieldName, const QString &operatorName,
150 QString value1, QString value2)
151{
152 QString result;
153
154 if (fieldName.isEmpty())
155 return result;
156
157 const SmartPLField *Field = lookupField(fieldName);
158 if (!Field)
159 {
160 return "";
161 }
162
163 result = Field->m_sqlName;
164
165 const SmartPLOperator *Operator = lookupOperator(operatorName);
166 if (!Operator)
167 {
168 return {};
169 }
170
171 // convert boolean and date values
172 if (Field->m_type == ftBoolean)
173 {
174 // compilation field uses 0 = false; 1 = true
175 value1 = (value1 == "Yes") ? "1":"0";
176 value2 = (value2 == "Yes") ? "1":"0";
177 }
178 else if (Field->m_type == ftDate)
179 {
180 value1 = evaluateDateValue(value1);
181 value2 = evaluateDateValue(value2);
182 }
183
184 if (Operator->m_name == "is equal to")
185 {
186 result = result + " = " + formattedFieldValue(value1);
187 }
188 else if (Operator->m_name == "is not equal to")
189 {
190 result = result + " != " + formattedFieldValue(value1);
191 }
192 else if (Operator->m_name == "is greater than")
193 {
194 result = result + " > " + formattedFieldValue(value1);
195 }
196 else if (Operator->m_name == "is less than")
197 {
198 result = result + " < " + formattedFieldValue(value1);
199 }
200 else if (Operator->m_name == "starts with")
201 {
202 result = result + " LIKE " + formattedFieldValue(value1 + QString("%"));
203 }
204 else if (Operator->m_name == "ends with")
205 {
206 result = result + " LIKE " + formattedFieldValue(QString("%") + value1);
207 }
208 else if (Operator->m_name == "contains")
209 {
210 result = result + " LIKE " + formattedFieldValue(QString("%") + value1 + "%");
211 }
212 else if (Operator->m_name == "does not contain")
213 {
214 result = result + " NOT LIKE " + formattedFieldValue(QString("%") + value1 + "%");
215 }
216 else if (Operator->m_name == "is between")
217 {
218 result = result + " BETWEEN " + formattedFieldValue(value1) +
219 " AND " + formattedFieldValue(value2);
220 }
221 else if (Operator->m_name == "is set")
222 {
223 result = result + " IS NOT NULL";
224 }
225 else if (Operator->m_name == "is not set")
226 {
227 result = result + " IS NULL";
228 }
229 else
230 {
231 result.clear();
232 LOG(VB_GENERAL, LOG_ERR,
233 QString("getCriteriaSQL(): invalid operator '%1'")
234 .arg(Operator->m_name));
235 }
236
237 return result;
238}
239
240QString getOrderBySQL(const QString& orderByFields)
241{
242 if (orderByFields.isEmpty())
243 return {};
244
245 QStringList list = orderByFields.split(",");
246 QString fieldName;
247 QString result;
248 QString order;
249 bool bFirst = true;
250
251 for (int x = 0; x < list.count(); x++)
252 {
253 fieldName = list[x].trimmed();
254 const SmartPLField *Field = lookupField(fieldName.left(fieldName.length() - 4));
255 if (Field)
256 {
257 if (fieldName.right(3) == "(D)")
258 order = " DESC";
259 else
260 order = " ASC";
261
262 if (bFirst)
263 {
264 bFirst = false;
265 result = " ORDER BY " + Field->m_sqlName + order;
266 }
267 else
268 {
269 result += ", " + Field->m_sqlName + order;
270 }
271 }
272 }
273
274 return result;
275}
276
277QString getSQLFieldName(const QString &fieldName)
278{
279 const SmartPLField *Field = lookupField(fieldName);
280 if (!Field)
281 {
282 return "";
283 }
284
285 return Field->m_sqlName;
286}
287
288/*
290*/
291
292QString SmartPLCriteriaRow::getSQL(void) const
293{
294 if (m_field.isEmpty())
295 return {};
296
297 QString result;
298
300
301 return result;
302}
303
304// return false on error
305bool SmartPLCriteriaRow::saveToDatabase(int smartPlaylistID) const
306{
307 // save playlistitem to database
308
309 if (m_field.isEmpty())
310 return true;
311
313 query.prepare("INSERT INTO music_smartplaylist_items (smartplaylistid, field, operator,"
314 " value1, value2)"
315 "VALUES (:SMARTPLAYLISTID, :FIELD, :OPERATOR, :VALUE1, :VALUE2);");
316 query.bindValue(":SMARTPLAYLISTID", smartPlaylistID);
317 query.bindValueNoNull(":FIELD", m_field);
318 query.bindValueNoNull(":OPERATOR", m_operator);
319 query.bindValueNoNull(":VALUE1", m_value1);
320 query.bindValueNoNull(":VALUE2", m_value2);
321
322 if (!query.exec())
323 {
324 MythDB::DBError("Inserting new smartplaylist item", query);
325 return false;
326 }
327
328 return true;
329}
330
332{
333 const SmartPLOperator *PLOperator = lookupOperator(m_operator);
334 if (PLOperator)
335 {
336 QString result;
337 if (PLOperator->m_noOfArguments == 0)
338 result = m_field + " " + m_operator;
339 else if (PLOperator->m_noOfArguments == 1)
340 result = m_field + " " + m_operator + " " + m_value1;
341 else
342 {
343 result = m_field + " " + m_operator + " " + m_value1;
344 result += " " + tr("and") + " " + m_value2;
345 }
346
347 return result;
348 }
349
350 return {};
351}
352
353/*
354---------------------------------------------------------------------
355*/
356
358{
359 while (!m_criteriaRows.empty())
360 {
361 delete m_criteriaRows.back();
362 m_criteriaRows.pop_back();
363 }
364
365 delete m_tempCriteriaRow;
366}
367
368
370{
371 if (!LoadWindowFromXML("music-ui.xml", "smartplaylisteditor", this))
372 return false;
373
374 bool err = false;
375
376 UIUtilE::Assign(this, m_categorySelector, "categoryselector", &err);
377 UIUtilE::Assign(this, m_categoryButton, "categorybutton", &err);
378 UIUtilE::Assign(this, m_titleEdit, "titleedit", &err);
379 UIUtilE::Assign(this, m_matchSelector, "matchselector", &err);
380 UIUtilE::Assign(this, m_criteriaList, "criterialist", &err);
381 UIUtilE::Assign(this, m_orderBySelector, "orderbyselector", &err);
382 UIUtilE::Assign(this, m_orderByButton, "orderbybutton", &err);
383 UIUtilE::Assign(this, m_matchesText, "matchestext", &err);
384 UIUtilE::Assign(this, m_limitSpin, "limitspin", &err);
385
386 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
387 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
388 UIUtilE::Assign(this, m_showResultsButton, "showresultsbutton", &err);
389
390 if (err)
391 {
392 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplaylisteditor'");
393 return false;
394 }
395
397
401
402 for (const auto & field : SmartPLFields)
403 {
404 if (field.m_name == "")
405 new MythUIButtonListItem(m_orderBySelector, field.m_name);
406 else
407 new MythUIButtonListItem(m_orderBySelector, field.m_name + " (A)");
408 }
409
410 m_limitSpin->SetRange(0, 9999, 10);
411
418
420
421 return true;
422}
423
425{
427 return true;
428
429 QStringList actions;
430 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
431
432 for (int i = 0; i < actions.size() && !handled; i++)
433 {
434 const QString& action = actions[i];
435 handled = true;
436
437 if (action == "MENU")
438 {
440 }
441 else if (action == "DELETE" && GetFocusWidget() == m_criteriaList)
442 {
444 }
445 else if (action == "EDIT" && GetFocusWidget() == m_criteriaList)
446 {
447 editCriteria();
448 }
449 else
450 {
451 handled = false;
452 }
453 }
454
455 if (!handled && MythScreenType::keyPressEvent(event))
456 handled = true;
457
458 return handled;
459}
460
462{
463 if (auto *dce = dynamic_cast<DialogCompletionEvent*>(event))
464 {
465 // make sure the user didn't ESCAPE out of the menu
466 if (dce->GetResult() < 0)
467 return;
468
469 QString resultid = dce->GetId();
470 QString resulttext = dce->GetResultText();
471 if (resultid == "categorymenu")
472 {
473 if (resulttext == tr("New Category"))
474 {
475 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
476 QString label = tr("Enter Name Of New Category");
477
478 auto *input = new MythTextInputDialog(popupStack, label);
479
480 connect(input, &MythTextInputDialog::haveResult,
482
483 if (input->Create())
484 popupStack->AddScreen(input);
485 else
486 delete input;
487 }
488 else if (resulttext == tr("Delete Category"))
489 {
491 }
492 else if (resulttext == tr("Rename Category"))
493 {
494 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
495 QString label = tr("Enter New Name For Category: %1").arg(m_categorySelector->GetValue());
496
497 auto *input = new MythTextInputDialog(popupStack, label);
498
499 connect(input, &MythTextInputDialog::haveResult,
501
502 if (input->Create())
503 popupStack->AddScreen(input);
504 else
505 delete input;
506 }
507 }
508 }
509}
510
512{
514 {
515 delete m_tempCriteriaRow;
516 m_tempCriteriaRow = nullptr;
517 }
518
520
521 if (!item)
522 return;
523
524 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
525
526 if (!row)
527 return;
528
529 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
530
531 auto *editor = new CriteriaRowEditor(popupStack, row);
532
533 if (!editor->Create())
534 {
535 delete editor;
536 return;
537 }
538
540
541 popupStack->AddScreen(editor);
542}
543
545{
546 // make sure we have something to delete
548
549 if (!item)
550 return;
551
552 ShowOkPopup(tr("Delete Criteria?"), this, &SmartPlaylistEditor::doDeleteCriteria, true);
553}
554
556{
557 if (doit)
558 {
560 if (!item)
561 return;
562
563 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
564
565 if (!row)
566 return;
567
568 m_criteriaRows.removeAll(row);
570
572 }
573}
574
576{
577 /*
578 SmartPLCriteriaRow *row = new SmartPLCriteriaRow();
579 m_criteriaRows.append(row);
580
581 MythUIButtonListItem *item = new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
582
583 m_criteriaList->SetItemCurrent(item);
584
585 editCriteria();
586 */
587
588 delete m_tempCriteriaRow;
590
591 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
592
593 auto *editor = new CriteriaRowEditor(popupStack, m_tempCriteriaRow);
594
595 if (!editor->Create())
596 {
597 delete editor;
598 return;
599 }
600
602
603 popupStack->AddScreen(editor);
604}
605
607{
608 MythUIButtonListItem *item = nullptr;
609
611 {
612 // this is a new row so add it to the list
614
616 QVariant::fromValue(m_tempCriteriaRow));
617
619
620 m_tempCriteriaRow = nullptr;
621 }
622 else
623 {
624 // update the existing row
626 if (!item)
627 return;
628
629 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
630
631 if (!row)
632 return;
633
634 item->SetText(row->toString());
635 }
636
638}
639
641{
642 QString label = tr("Category Actions");
643
644 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
645
646 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
647
648 if (!menu->Create())
649 {
650 delete menu;
651 return;
652 }
653
654 menu->SetReturnEvent(this, "categorymenu");
655
656 menu->AddButton(tr("New Category"), nullptr);
657 menu->AddButton(tr("Delete Category"), nullptr);
658 menu->AddButton(tr("Rename Category"), nullptr);
659
660 popupStack->AddScreen(menu);
661}
662
664{
665 QString label = tr("Criteria Actions");
666
667 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
668
669 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
670
671 if (!menu->Create())
672 {
673 delete menu;
674 return;
675 }
676
677 menu->SetReturnEvent(this, "criteriamenu");
678
680
681 if (item)
682 menu->AddButton(tr("Edit Criteria"), &SmartPlaylistEditor::editCriteria);
683
684 menu->AddButton(tr("Add Criteria"), &SmartPlaylistEditor::addCriteria);
685
686 if (item)
687 menu->AddButton(tr("Delete Criteria"), &SmartPlaylistEditor::deleteCriteria);
688
689 popupStack->AddScreen(menu);
690}
691
693{
695}
696
698{
699 QString sql =
700 "SELECT count(*) "
701 "FROM music_songs "
702 "LEFT JOIN music_artists ON "
703 " music_songs.artist_id=music_artists.artist_id "
704 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
705 "LEFT JOIN music_artists AS music_comp_artists ON "
706 " music_albums.artist_id=music_comp_artists.artist_id "
707 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
708
709 sql += getWhereClause();
710
711 m_matchesCount = 0;
712
714 if (!query.exec(sql))
715 MythDB::DBError("SmartPlaylistEditor::updateMatches", query);
716 else if (query.next())
717 m_matchesCount = query.value(0).toInt();
718
719 m_matchesText->SetText(QString::number(m_matchesCount));
720
723 titleChanged();
724}
725
727{
728 // save smartplaylist to database
729
730 QString name = m_titleEdit->GetText();
731 QString category = m_categorySelector->GetValue();
732 QString matchType = (m_matchSelector->GetValue() == tr("All") ? "All" : "Any");
733 QString orderBy = m_orderBySelector->GetValue();
734 QString limit = m_limitSpin->GetValue();
735
736 // lookup categoryid
737 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
738
739 // easier to delete any existing smartplaylist and recreate a new one
740 if (!m_newPlaylist)
742 else
744
746 // insert new smartplaylist
747 query.prepare("INSERT INTO music_smartplaylists (name, categoryid, matchtype, orderby, limitto) "
748 "VALUES (:NAME, :CATEGORYID, :MATCHTYPE, :ORDERBY, :LIMIT);");
749 query.bindValue(":NAME", name);
750 query.bindValue(":CATEGORYID", categoryid);
751 query.bindValue(":MATCHTYPE", matchType);
752 query.bindValue(":ORDERBY", orderBy);
753 query.bindValue(":LIMIT", limit);
754
755 if (!query.exec())
756 {
757 MythDB::DBError("Inserting new playlist", query);
758 return;
759 }
760
761 // get smartplaylistid
762 int ID = -1;
763 query.prepare("SELECT smartplaylistid FROM music_smartplaylists "
764 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
765 query.bindValue(":CATEGORYID", categoryid);
766 query.bindValue(":NAME", name);
767 if (query.exec())
768 {
769 if (query.isActive() && query.size() > 0)
770 {
771 query.first();
772 ID = query.value(0).toInt();
773 }
774 else
775 {
776 LOG(VB_GENERAL, LOG_ERR,
777 QString("Failed to find ID for smartplaylist: %1").arg(name));
778 return;
779 }
780 }
781 else
782 {
783 MythDB::DBError("Getting smartplaylist ID", query);
784 return;
785 }
786
787 // save smartplaylist items
788 for (const auto & row : std::as_const(m_criteriaRows))
789 row->saveToDatabase(ID);
790
791 emit smartPLChanged(category, name);
792
793 Close();
794}
795
796void SmartPlaylistEditor::newSmartPlaylist(const QString& category)
797{
798 m_categorySelector->SetValue(category);
800 m_originalCategory = category;
801 m_originalName.clear();
802
803 m_newPlaylist = true;
804
806}
807
808void SmartPlaylistEditor::editSmartPlaylist(const QString& category, const QString& name)
809{
810 m_originalCategory = category;
811 m_originalName = name;
812 m_newPlaylist = false;
813 loadFromDatabase(category, name);
815}
816
817void SmartPlaylistEditor::loadFromDatabase(const QString& category, const QString& name)
818{
819 // load smartplaylist from database
820 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
821
823 int ID = -1;
824
825 query.prepare("SELECT smartplaylistid, name, categoryid, matchtype, orderby, limitto "
826 "FROM music_smartplaylists WHERE name = :NAME AND categoryid = :CATEGORYID;");
827 query.bindValue(":NAME", name);
828 query.bindValue(":CATEGORYID", categoryid);
829 if (query.exec())
830 {
831 if (query.isActive() && query.size() > 0)
832 {
833 query.first();
834 ID = query.value(0).toInt();
835 m_titleEdit->SetText(name);
836 m_categorySelector->SetValue(category);
837 if (query.value(3).toString() == "All")
838 m_matchSelector->SetValue(tr("All"));
839 else
840 m_matchSelector->SetValue(tr("Any"));
841
842 QString orderBy = query.value(4).toString();
843 if (!m_orderBySelector->Find(orderBy))
844 {
845 // not found so add it to the selector
847 m_orderBySelector->SetValue(orderBy);
848 }
849
850 m_limitSpin->SetValue(query.value(5).toInt());
851 }
852 else
853 {
854 LOG(VB_GENERAL, LOG_ERR,
855 QString("Cannot find smartplaylist: %1").arg(name));
856 return;
857 }
858 }
859 else
860 {
861 MythDB::DBError("Load smartplaylist", query);
862 return;
863 }
864
866
867 query.prepare("SELECT field, operator, value1, value2 "
868 "FROM music_smartplaylist_items WHERE smartplaylistid = :ID "
869 "ORDER BY smartplaylistitemid;");
870 query.bindValue(":ID", ID);
871 if (!query.exec())
872 MythDB::DBError("Load smartplaylist items", query);
873
874 if (query.size() > 0)
875 {
876 while (query.next())
877 {
878 QString Field = query.value(0).toString();
879 QString Operator = query.value(1).toString();
880 QString Value1 = query.value(2).toString();
881 QString Value2 = query.value(3).toString();
882 // load smartplaylist items
883 auto *row = new SmartPLCriteriaRow(Field, Operator, Value1, Value2);
884 m_criteriaRows.append(row);
885
886 new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
887 }
888 }
889 else
890 {
891 LOG(VB_GENERAL, LOG_WARNING,
892 QString("Got no smartplaylistitems for ID: ").arg(ID));
893 }
894}
895
896void SmartPlaylistEditor::newCategory(const QString &category)
897{
898 // insert new smartplaylistcategory
899
901 query.prepare("INSERT INTO music_smartplaylist_categories (name) "
902 "VALUES (:NAME);");
903 query.bindValue(":NAME", category);
904
905 if (!query.exec())
906 {
907 MythDB::DBError("Inserting new smartplaylist category", query);
908 return;
909 }
910
912 m_categorySelector->SetValue(category);
913}
914
915void SmartPlaylistEditor::startDeleteCategory(const QString &category)
916{
917 if (category.isEmpty())
918 return;
919
920//FIXME::
921#if 0
922 if (!MythPopupBox::showOkCancelPopup(GetMythMainWindow(),
923 "Delete Category",
924 tr("Are you sure you want to delete this Category?")
925 + "\n\n\"" + category + "\"\n\n"
926 + tr("It will also delete any Smart Playlists belonging to this category."),
927 false))
928 return;
929
931#endif
934}
935
936void SmartPlaylistEditor::renameCategory(const QString &category)
937{
938 if (m_categorySelector->GetValue() == category)
939 return;
940
941 // change the category
943 query.prepare("UPDATE music_smartplaylist_categories SET name = :NEW_CATEGORY "
944 "WHERE name = :OLD_CATEGORY;");
945 query.bindValue(":OLD_CATEGORY", m_categorySelector->GetValue());
946 query.bindValue(":NEW_CATEGORY", category);
947
948 if (!query.exec())
949 MythDB::DBError("Rename smartplaylist", query);
950
951 if (!m_newPlaylist)
953
955 m_categorySelector->SetValue(category);
956}
957
958QString SmartPlaylistEditor::getSQL(const QString& fields)
959{
960 QString sql;
961 QString whereClause;
962 QString orderByClause;
963 QString limitClause;
964 sql = "SELECT " + fields + " FROM music_songs "
965 "LEFT JOIN music_artists ON music_songs.artist_id=music_artists.artist_id "
966 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
967 "LEFT JOIN music_artists AS music_comp_artists ON music_albums.artist_id=music_comp_artists.artist_id "
968 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
969
970 whereClause = getWhereClause();
971 orderByClause = getOrderByClause();
972 if (m_limitSpin->GetIntValue() > 0)
973 limitClause = " LIMIT " + m_limitSpin->GetValue();
974
975 sql = sql + whereClause + orderByClause + limitClause;
976
977 return sql;
978}
979
981{
983}
984
986{
987 if (m_criteriaRows.empty())
988 return {};
989
990 bool bFirst = true;
991 QString sql = "WHERE ";
992
993 for (const auto & row : std::as_const(m_criteriaRows))
994 {
995 QString criteria = row->getSQL();
996 if (criteria.isEmpty())
997 continue;
998
999 if (bFirst)
1000 {
1001 sql += criteria;
1002 bFirst = false;
1003 }
1004 else
1005 {
1006 if (m_matchSelector->GetValue() == tr("Any"))
1007 sql += " OR " + criteria;
1008 else
1009 sql += " AND " + criteria;
1010 }
1011 }
1012
1013 return sql;
1014}
1015
1017{
1018 QString sql = getSQL("song_id, music_artists.artist_name, album_name, "
1019 "name, genre, music_songs.year, track");
1020
1022
1023 auto *resultViewer = new SmartPLResultViewer(mainStack);
1024
1025 if (!resultViewer->Create())
1026 {
1027 delete resultViewer;
1028 return;
1029 }
1030
1031 resultViewer->setSQL(sql);
1032
1033 mainStack->AddScreen(resultViewer);
1034}
1035
1037{
1038 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1039
1040 auto *orderByDialog = new SmartPLOrderByDialog(popupStack);
1041
1042 if (!orderByDialog->Create())
1043 {
1044 delete orderByDialog;
1045 return;
1046 }
1047
1048 orderByDialog->setFieldList(m_orderBySelector->GetValue());
1049
1050 connect(orderByDialog, qOverload<QString>(&SmartPLOrderByDialog::orderByChanged),
1052
1053 popupStack->AddScreen(orderByDialog);
1054}
1055
1056void SmartPlaylistEditor::orderByChanged(const QString& orderBy)
1057{
1059 return;
1060
1061 // not found so add it to the selector
1063 m_orderBySelector->SetValue(orderBy);
1064}
1065
1067{
1070
1071 if (query.exec("SELECT name FROM music_smartplaylist_categories ORDER BY name;"))
1072 {
1073 if (query.isActive() && query.size() > 0)
1074 {
1075 while (query.next())
1076 new MythUIButtonListItem(m_categorySelector, query.value(0).toString());
1077 }
1078 else
1079 {
1080 LOG(VB_GENERAL, LOG_ERR,
1081 "Could not find any smartplaylist categories");
1082 }
1083 }
1084 else
1085 {
1086 MythDB::DBError("Load smartplaylist categories", query);
1087 }
1088}
1089
1090// static function to delete a smartplaylist and any associated smartplaylist items
1091bool SmartPlaylistEditor::deleteSmartPlaylist(const QString &category, const QString& name)
1092{
1093 // get categoryid
1094 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1095
1097
1098 // get playlist ID
1099 int ID = -1;
1100 query.prepare("SELECT smartplaylistid FROM music_smartplaylists WHERE name = :NAME "
1101 "AND categoryid = :CATEGORYID;");
1102 query.bindValue(":NAME", name);
1103 query.bindValue(":CATEGORYID", categoryid);
1104 if (query.exec())
1105 {
1106 if (query.isActive() && query.size() > 0)
1107 {
1108 query.first();
1109 ID = query.value(0).toInt();
1110 }
1111 else
1112 {
1113 // not always an error maybe we are trying to delete a playlist
1114 // that does not exist
1115 return true;
1116 }
1117 }
1118 else
1119 {
1120 MythDB::DBError("Delete smartplaylist", query);
1121 return false;
1122 }
1123
1124 //delete smartplaylist items
1125 query.prepare("DELETE FROM music_smartplaylist_items WHERE smartplaylistid = :ID;");
1126 query.bindValue(":ID", ID);
1127 if (!query.exec())
1128 MythDB::DBError("Delete smartplaylist items", query);
1129
1130 //delete smartplaylist
1131 query.prepare("DELETE FROM music_smartplaylists WHERE smartplaylistid = :ID;");
1132 query.bindValue(":ID", ID);
1133 if (!query.exec())
1134 MythDB::DBError("Delete smartplaylist", query);
1135
1136 return true;
1137}
1138
1139// static function to delete all smartplaylists belonging to the given category
1140// will also delete any associated smartplaylist items
1141bool SmartPlaylistEditor::deleteCategory(const QString& category)
1142{
1143 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1145
1146 //delete all smartplaylists with the selected category
1147 query.prepare("SELECT name FROM music_smartplaylists "
1148 "WHERE categoryid = :CATEGORYID;");
1149 query.bindValue(":CATEGORYID", categoryid);
1150 if (!query.exec())
1151 {
1152 MythDB::DBError("Delete SmartPlaylist Category", query);
1153 return false;
1154 }
1155
1156 if (query.isActive() && query.size() > 0)
1157 {
1158 while (query.next())
1159 {
1160 SmartPlaylistEditor::deleteSmartPlaylist(category, query.value(0).toString());
1161 }
1162 }
1163
1164 // delete the category
1165 query.prepare("DELETE FROM music_smartplaylist_categories WHERE categoryid = :ID;");
1166 query.bindValue(":ID", categoryid);
1167 if (!query.exec())
1168 MythDB::DBError("Delete smartplaylist category", query);
1169
1170 return true;
1171}
1172
1173// static function to lookup the categoryid given its name
1174int SmartPlaylistEditor::lookupCategoryID(const QString& category)
1175{
1176 int ID = -1;
1178 query.prepare("SELECT categoryid FROM music_smartplaylist_categories "
1179 "WHERE name = :CATEGORY;");
1180 query.bindValue(":CATEGORY", category);
1181
1182 if (query.exec())
1183 {
1184 if (query.isActive() && query.size() > 0)
1185 {
1186 query.first();
1187 ID = query.value(0).toInt();
1188 }
1189 else
1190 {
1191 LOG(VB_GENERAL, LOG_ERR,
1192 QString("Failed to find smart playlist category: %1")
1193 .arg(category));
1194 ID = -1;
1195 }
1196 }
1197 else
1198 {
1199 MythDB::DBError("Getting category ID", query);
1200 ID = -1;
1201 }
1202
1203 return ID;
1204}
1205
1206void SmartPlaylistEditor::getCategoryAndName(QString &category, QString &name)
1207{
1208 category = m_categorySelector->GetValue();
1209 name = m_titleEdit->GetText();
1210}
1211
1212/*
1213---------------------------------------------------------------------
1214*/
1215
1217{
1218 if (!LoadWindowFromXML("music-ui.xml", "criteriaroweditor", this))
1219 return false;
1220
1221 bool err = false;
1222
1223 UIUtilE::Assign(this, m_fieldSelector, "fieldselector", &err);
1224 UIUtilE::Assign(this, m_operatorSelector, "operatorselector", &err);
1225 UIUtilE::Assign(this, m_value1Edit, "value1edit", &err);
1226 UIUtilE::Assign(this, m_value2Edit, "value2edit", &err);
1227 UIUtilE::Assign(this, m_value1Selector, "value1selector", &err);
1228 UIUtilE::Assign(this, m_value2Selector, "value2selector", &err);
1229 UIUtilE::Assign(this, m_value1Spinbox, "value1spinbox", &err);
1230 UIUtilE::Assign(this, m_value2Spinbox, "value2spinbox", &err);
1231 UIUtilE::Assign(this, m_value1Button, "value1button", &err);
1232 UIUtilE::Assign(this, m_value2Button, "value2button", &err);
1233 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1234 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
1235
1236 if (err)
1237 {
1238 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'criteriaroweditor'");
1239 return false;
1240 }
1241
1242 updateFields();
1244 updateValues();
1245
1248
1253
1256
1259
1261
1262 return true;
1263}
1264
1266{
1267 for (const auto & field : SmartPLFields)
1268 new MythUIButtonListItem(m_fieldSelector, field.m_name);
1269
1271}
1272
1274{
1275 for (const auto & oper : SmartPLOperators)
1276 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1277
1279}
1280
1282{
1284}
1285
1287{
1292
1294 {
1295 // not found so add it to the selector
1298 }
1299
1301 {
1302 // not found so add it to the selector
1305 }
1306}
1307
1309{
1311 if (!Field)
1312 return;
1313
1316
1317 if (Field->m_type == ftNumeric)
1318 {
1321 }
1322 else if (Field->m_type == ftBoolean || Field->m_type == ftDate)
1323 {
1326 }
1327 else // ftString
1328 {
1331 }
1332
1333 // NOLINTNEXTLINE(readability-misleading-indentation)
1334 emit criteriaChanged();
1335
1336 Close();
1337}
1338
1340{
1341 bool enabled = false;
1342
1344
1346
1347 if (Field && Operator)
1348 {
1349 if (Field->m_type == ftNumeric || Field->m_type == ftBoolean)
1350 enabled = true;
1351 else if (Field->m_type == ftDate)
1352 {
1353 if ((Operator->m_noOfArguments == 0) ||
1354 (Operator->m_noOfArguments == 1 && !m_value1Selector->GetValue().isEmpty()) ||
1355 (Operator->m_noOfArguments == 2 && !m_value1Selector->GetValue().isEmpty()
1356 && !m_value2Selector->GetValue().isEmpty()))
1357 enabled = true;
1358 }
1359 else // ftString
1360 {
1361 if ((Operator->m_noOfArguments == 0) ||
1362 (Operator->m_noOfArguments == 1 && !m_value1Edit->GetText().isEmpty()) ||
1363 (Operator->m_noOfArguments == 2 && !m_value1Edit->GetText().isEmpty()
1364 && !m_value2Edit->GetText().isEmpty()))
1365 enabled = true;
1366 }
1367 }
1368
1369 m_saveButton->SetEnabled(enabled);
1370}
1371
1373{
1375 if (!Field)
1376 return;
1377
1378 if (Field->m_type == ftBoolean)
1379 {
1380 // add yes / no items to combo
1387 }
1388 else if (Field->m_type == ftDate)
1389 {
1390 // add a couple of date values to the combo
1393 new MythUIButtonListItem(m_value1Selector, "$DATE - 30 days");
1394 new MythUIButtonListItem(m_value1Selector, "$DATE - 60 days");
1395
1397 {
1398 // not found so add it to the selector
1401 }
1402
1403
1406 new MythUIButtonListItem(m_value2Selector, "$DATE - 30 days");
1407 new MythUIButtonListItem(m_value2Selector, "$DATE - 60 days");
1408
1410 {
1411 // not found so add it to the selector
1414 }
1415 }
1416
1417 // get list of operators valid for this field type
1418 getOperatorList(Field->m_type);
1419
1421}
1422
1424{
1426 if (!Field)
1427 return;
1428
1430 if (!Operator)
1431 return;
1432
1433 // hide all widgets
1434 m_value1Edit->Hide();
1435 m_value2Edit->Hide();
1442
1443 // show spin edits
1444 if (Field->m_type == ftNumeric)
1445 {
1446 if (Operator->m_noOfArguments >= 1)
1447 {
1449 int currentValue = m_value1Spinbox->GetIntValue();
1450 m_value1Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1451
1452 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1454 }
1455
1456 if (Operator->m_noOfArguments == 2)
1457 {
1459 int currentValue = m_value2Spinbox->GetIntValue();
1460 m_value2Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1461
1462 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1464 }
1465 }
1466 else if (Field->m_type == ftBoolean)
1467 {
1468 // only show value1combo
1470 }
1471 else if (Field->m_type == ftDate)
1472 {
1473 if (Operator->m_noOfArguments >= 1)
1474 {
1477 }
1478
1479 if (Operator->m_noOfArguments == 2)
1480 {
1483 }
1484 }
1485 else // ftString
1486 {
1487 if (Operator->m_noOfArguments >= 1)
1488 {
1489 m_value1Edit->Show();
1491 }
1492
1493 if (Operator->m_noOfArguments == 2)
1494 {
1495 m_value2Edit->Show();
1497 }
1498 }
1499
1501}
1502
1504{
1505 QString currentOperator = m_operatorSelector->GetValue();
1506
1508
1509 for (const auto & oper : SmartPLOperators)
1510 {
1511 // don't add operators that only work with string fields
1512 if (fieldType != ftString && oper.m_stringOnly)
1513 continue;
1514
1515 // don't add operators that only work with boolean fields
1516 if (fieldType == ftBoolean && !oper.m_validForBoolean)
1517 continue;
1518
1519 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1520 }
1521
1522 // try to set the operatorCombo to the same operator or else the first item
1523 m_operatorSelector->SetValue(currentOperator);
1524}
1525
1527{
1528 QString msg;
1529 QStringList searchList;
1531
1532 if (m_fieldSelector->GetValue() == "Artist")
1533 {
1534 msg = tr("Select an Artist");
1535 searchList = MusicMetadata::fillFieldList("artist");
1536 }
1537 else if (m_fieldSelector->GetValue() == "Comp. Artist")
1538 {
1539 msg = tr("Select a Compilation Artist");
1540 searchList = MusicMetadata::fillFieldList("compilation_artist");
1541 }
1542 else if (m_fieldSelector->GetValue() == "Album")
1543 {
1544 msg = tr("Select an Album");
1545 searchList = MusicMetadata::fillFieldList("album");
1546 }
1547 else if (m_fieldSelector->GetValue() == "Genre")
1548 {
1549 msg = tr("Select a Genre");
1550 searchList = MusicMetadata::fillFieldList("genre");
1551 }
1552 else if (m_fieldSelector->GetValue() == "Title")
1553 {
1554 msg = tr("Select a Title");
1555 searchList = MusicMetadata::fillFieldList("title");
1556 }
1557 else if ((m_fieldSelector->GetValue() == "Last Play") ||
1558 (m_fieldSelector->GetValue() == "Date Imported"))
1559 {
1560 editDate();
1561 return;
1562 }
1563
1564 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1565 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, s);
1566
1567 if (!searchDlg->Create())
1568 {
1569 delete searchDlg;
1570 return;
1571 }
1572
1574
1575 popupStack->AddScreen(searchDlg);
1576}
1577
1578void CriteriaRowEditor::setValue(const QString& value)
1579{
1581 m_value1Edit->SetText(value);
1582 else
1583 m_value2Edit->SetText(value);
1584}
1585
1587{
1588 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1589 auto *dateDlg = new SmartPLDateDialog(popupStack);
1591
1592 if (!dateDlg->Create())
1593 {
1594 delete dateDlg;
1595 return;
1596 }
1597
1598 dateDlg->setDate(date);
1599
1601
1602 popupStack->AddScreen(dateDlg);
1603}
1604
1605void CriteriaRowEditor::setDate(const QString& date)
1606{
1608 {
1610 return;
1611
1612 // not found so add it to the selector
1615 }
1616 else
1617 {
1619 return;
1620
1621 // not found so add it to the selector
1624 }
1625}
1626
1627/*
1628---------------------------------------------------------------------
1629*/
1630
1631
1633{
1634 if (!LoadWindowFromXML("music-ui.xml", "smartplresultviewer", this))
1635 return false;
1636
1637 bool err = false;
1638
1639 UIUtilE::Assign(this, m_trackList, "tracklist", &err);
1640 UIUtilW::Assign(this, m_positionText, "position", &err);
1641
1642 if (err)
1643 {
1644 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplresultviewer'");
1645 return false;
1646 }
1647
1652
1654
1655 return true;
1656}
1657
1659{
1660 if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1661 return true;
1662
1663 QStringList actions;
1664 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
1665
1666 for (int i = 0; i < actions.size() && !handled; i++)
1667 {
1668 const QString& action = actions[i];
1669 handled = true;
1670
1671 if (action == "INFO")
1672 showTrackInfo();
1673 else
1674 handled = false;
1675 }
1676
1677 if (!handled && MythScreenType::keyPressEvent(event))
1678 handled = true;
1679
1680 return handled;
1681}
1682
1684{
1685 if (!item)
1686 return;
1687
1688 if (item->GetImageFilename().isEmpty())
1689 {
1690 auto *mdata = item->GetData().value<MusicMetadata *>();
1691 if (mdata)
1692 {
1693 QString artFile = mdata->getAlbumArtFile();
1694 if (artFile.isEmpty())
1695 item->SetImage("mm_nothumb.png");
1696 else
1697 item->SetImage(mdata->getAlbumArtFile());
1698 }
1699 else
1700 {
1701 item->SetImage("mm_nothumb.png");
1702 }
1703 }
1704}
1705
1707{
1708 if (!item || !m_positionText)
1709 return;
1710
1711 m_positionText->SetText(tr("%1 of %2").arg(m_trackList->IsEmpty() ? 0 : m_trackList->GetCurrentPos() + 1)
1712 .arg(m_trackList->GetCount()));
1713}
1715{
1717 if (!item)
1718 return;
1719
1720 auto *mdata = item->GetData().value<MusicMetadata *>();
1721 if (!mdata)
1722 return;
1723
1724 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1725
1726 auto *dlg = new TrackInfoDialog(popupStack, mdata, "trackinfopopup");
1727
1728 if (!dlg->Create())
1729 {
1730 delete dlg;
1731 return;
1732 }
1733
1734 popupStack->AddScreen(dlg);
1735}
1736
1737void SmartPLResultViewer::setSQL(const QString& sql)
1738{
1739 m_trackList->Reset();;
1740
1742
1743 if (query.exec(sql))
1744 {
1745 while (query.next())
1746 {
1747 MusicMetadata *mdata = gMusicData->m_all_music->getMetadata(query.value(0).toInt());
1748 if (mdata)
1749 {
1750 InfoMap metadataMap;
1751 mdata->toMap(metadataMap);
1752
1753 auto *item = new MythUIButtonListItem(m_trackList, "", QVariant::fromValue(mdata));
1754 item->SetTextFromMap(metadataMap);
1755 }
1756 }
1757 }
1758
1760}
1761
1762
1763/*
1764---------------------------------------------------------------------
1765*/
1766
1768{
1769 if (!LoadWindowFromXML("music-ui.xml", "orderbydialog", this))
1770 return false;
1771
1772 bool err = false;
1773
1774 UIUtilE::Assign(this, m_fieldList, "fieldlist", &err);
1775 UIUtilE::Assign(this, m_orderSelector, "fieldselector", &err);
1776 UIUtilE::Assign(this, m_addButton, "addbutton", &err);
1777 UIUtilE::Assign(this, m_deleteButton, "deletebutton", &err);
1778 UIUtilE::Assign(this, m_moveUpButton, "moveupbutton", &err);
1779 UIUtilE::Assign(this, m_moveDownButton, "movedownbutton", &err);
1780 UIUtilE::Assign(this, m_ascendingButton, "ascendingbutton", &err);
1781 UIUtilE::Assign(this, m_descendingButton, "descendingbutton", &err);
1782 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1783 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
1784
1785 if (err)
1786 {
1787 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'orderbydialog'");
1788 return false;
1789 }
1790
1799
1801 this, qOverload<MythUIButtonListItem *>(&SmartPLOrderByDialog::orderByChanged));
1804
1806
1808
1810
1811 return true;
1812}
1813
1815{
1816 QString result;
1817 bool bFirst = true;
1818
1819 for (int i = 0; i < m_fieldList->GetCount(); i++)
1820 {
1821 if (bFirst)
1822 {
1823 bFirst = false;
1824 result = m_fieldList->GetItemAt(i)->GetText();
1825 }
1826 else
1827 {
1828 result += ", " + m_fieldList->GetItemAt(i)->GetText();
1829 }
1830 }
1831
1832 return result;
1833}
1834
1835void SmartPLOrderByDialog::setFieldList(const QString &fieldList)
1836{
1837 m_fieldList->Reset();
1838 QStringList list = fieldList.split(",");
1839
1840 for (int x = 0; x < list.count(); x++)
1841 {
1842 auto *item = new MythUIButtonListItem(m_fieldList, list[x].trimmed());
1843 QString state = list[x].contains("(A)") ? "ascending" : "descending";
1844 item->DisplayState(state, "sortstate");
1845 }
1846
1848}
1849
1851{
1852 if (!item)
1853 return;
1854
1855 m_orderSelector->SetValue(item->GetText().left(item->GetText().length() - 4));
1856}
1857
1859{
1861 return;
1862
1864 m_fieldList->GetItemCurrent()->DisplayState("ascending", "sortstate");
1865
1868}
1869
1871{
1873 return;
1874
1876 m_fieldList->GetItemCurrent()->DisplayState("descending", "sortstate");
1877
1880}
1881
1883{
1884 auto *item = new MythUIButtonListItem(m_fieldList, m_orderSelector->GetValue() + " (A)");
1885 item->DisplayState("ascending", "sortstate");
1886
1889}
1890
1892{
1895
1896 if (!m_deleteButton->IsEnabled())
1898 else
1900}
1901
1903{
1905
1906 if (item)
1907 item->MoveUpDown(true);
1908
1910
1911 if (!m_moveUpButton->IsEnabled())
1913 else
1915}
1916
1918{
1920
1921 if (item)
1922 item->MoveUpDown(false);
1923
1925
1928 else
1930}
1931
1933{
1935 Close();
1936}
1937
1939{
1940 bool found = false;
1941 for (int i = 0 ; i < m_fieldList->GetCount() ; ++i)
1942 {
1943 if (m_fieldList->GetItemAt(i)->GetText().startsWith(m_orderSelector->GetValue()))
1944 {
1946 found = true;
1947 }
1948 }
1949
1950 if (found)
1951 {
1952 m_addButton->SetEnabled(false);
1956 m_ascendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(D)") );
1957 m_descendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(A)"));
1958 }
1959 else
1960 {
1961 m_addButton->SetEnabled(true);
1962 m_deleteButton->SetEnabled(false);
1963 m_moveUpButton->SetEnabled(false);
1967 }
1968}
1969
1971{
1973}
1974
1976{
1978 for (const auto & field : SmartPLFields)
1979 new MythUIButtonListItem(m_orderSelector, field.m_name);
1980}
1981
1982/*
1983---------------------------------------------------------------------
1984*/
1985
1987{
1988 if (!LoadWindowFromXML("music-ui.xml", "dateeditordialog", this))
1989 return false;
1990
1991 bool err = false;
1992
1993 UIUtilE::Assign(this, m_fixedRadio, "fixeddatecheck", &err);
1994 UIUtilE::Assign(this, m_daySpin, "dayspinbox", &err);
1995 UIUtilE::Assign(this, m_monthSpin, "monthspinbox", &err);
1996 UIUtilE::Assign(this, m_yearSpin, "yearspinbox", &err);
1997 UIUtilE::Assign(this, m_nowRadio, "nowcheck", &err);
1998 UIUtilE::Assign(this, m_addDaysSpin, "adddaysspinbox", &err);
1999 UIUtilE::Assign(this, m_statusText, "statustext", &err);
2000 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
2001 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
2002
2003 if (err)
2004 {
2005 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'dateeditordialog'");
2006 return false;
2007 }
2008
2009 m_daySpin->SetRange(1, 31, 1);
2010 m_monthSpin->SetRange(1, 12, 1);
2011 m_yearSpin->SetRange(1900, 2099, 1);
2012 m_addDaysSpin->SetRange(-9999, 9999, 1);
2013
2014
2025
2028
2029 valueChanged();
2030
2032
2033 return true;
2034}
2035
2037{
2038 QString sResult;
2039
2041 {
2042 QString day = m_daySpin->GetValue();
2043 if (m_daySpin->GetIntValue() < 10)
2044 day = "0" + day;
2045
2046 QString month = m_monthSpin->GetValue();
2047 if (m_monthSpin->GetIntValue() < 10)
2048 month = "0" + month;
2049
2050 sResult = m_yearSpin->GetValue() + "-" + month + "-" + day;
2051 }
2052 else
2053 {
2054 sResult = m_statusText->GetText();
2055 }
2056
2057 return sResult;
2058}
2059
2061{
2062 if (date.startsWith("$DATE"))
2063 {
2066
2067 if (date.length() > 9)
2068 {
2069 bool bNegative = false;
2070 if (date[6] == '-')
2071 bNegative = true;
2072
2073 if (date.endsWith(" days"))
2074 date = date.left(date.length() - 5);
2075
2076 int nDays = date.mid(8).toInt();
2077 if (bNegative)
2078 nDays = -nDays;
2079
2080 m_addDaysSpin->SetValue(nDays);
2081 }
2082 else
2083 {
2085 }
2086
2087 nowCheckToggled(true);
2088 }
2089 else
2090 {
2091 int nYear = date.mid(0, 4).toInt();
2092 int nMonth = date.mid(5, 2).toInt();
2093 int nDay = date.mid(8, 2).toInt();
2094
2095 m_daySpin->SetValue(nDay);
2096 m_monthSpin->SetValue(nMonth);
2097 m_yearSpin->SetValue(nYear);
2098
2099 fixedCheckToggled(true);
2100 }
2101}
2102
2104{
2105 if (m_updating)
2106 return;
2107
2108 m_updating = true;
2109 m_daySpin->SetEnabled(on);
2112
2115
2116 valueChanged();
2117
2118 m_updating = false;
2119}
2120
2122{
2123 if (m_updating)
2124 return;
2125
2126 m_updating = true;
2127
2129 m_daySpin->SetEnabled(!on);
2130 m_monthSpin->SetEnabled(!on);
2131 m_yearSpin->SetEnabled(!on);
2132
2134
2135 valueChanged();
2136
2137 m_updating = false;
2138}
2139
2141{
2142 QString date = getDate();
2143
2144 emit dateChanged(date);
2145
2146 Close();
2147}
2148
2150{
2151 bool bValidDate = true;
2152
2154 {
2155 QString day = m_daySpin->GetValue();
2156 if (m_daySpin->GetIntValue() < 10)
2157 day = "0" + day;
2158
2159 QString month = m_monthSpin->GetValue();
2160 if (m_monthSpin->GetIntValue() < 10)
2161 month = "0" + month;
2162
2163 QString sDate = m_yearSpin->GetValue() + "-" + month + "-" + day;
2164 QDate date = QDate::fromString(sDate, Qt::ISODate);
2165 if (date.isValid())
2166 m_statusText->SetText(date.toString("dddd, d MMMM yyyy"));
2167 else
2168 {
2169 bValidDate = false;
2170 m_statusText->SetText(tr("Invalid Date"));
2171 }
2172 }
2173 else if (m_nowRadio->GetBooleanCheckState())
2174 {
2175 QString days;
2176 if (m_addDaysSpin->GetIntValue() > 0)
2177 days = QString("$DATE + %1 days").arg(m_addDaysSpin->GetIntValue());
2178 else if (m_addDaysSpin->GetIntValue() == 0)
2179 days = QString("$DATE");
2180 else
2181 days = QString("$DATE - %1 days").arg(
2182 m_addDaysSpin->GetValue().right(m_addDaysSpin->GetValue().length() - 1));
2183
2185 }
2186
2187 if (bValidDate)
2188 m_statusText->SetFontState("valid");
2189 else
2190 m_statusText->SetFontState("error");
2191
2192 m_okButton->SetEnabled(bValidDate);
2193}
2194
MusicMetadata * getMetadata(int an_id)
MythUIButton * m_cancelButton
MythUIButtonList * m_fieldSelector
void setDate(const QString &date)
MythUIButtonList * m_value1Selector
MythUIButton * m_value1Button
void valueEditChanged(void)
MythUITextEdit * m_value1Edit
MythUIButton * m_value2Button
void getOperatorList(SmartPLFieldType fieldType)
MythUISpinBox * m_value1Spinbox
MythUIButtonList * m_value2Selector
void enableSaveButton(void)
bool Create(void) override
void setValue(const QString &value)
void operatorChanged(void)
MythUIButton * m_saveButton
SmartPLCriteriaRow * m_criteriaRow
MythUISpinBox * m_value2Spinbox
MythUITextEdit * m_value2Edit
void updateOperators(void)
MythUIButtonList * m_operatorSelector
void valueButtonClicked(void)
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:822
QVariant value(int i) const
Definition: mythdbcon.h:204
int size(void) const
Definition: mythdbcon.h:214
bool isActive(void) const
Definition: mythdbcon.h:215
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:902
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
const QSqlDriver * driver(void) const
Definition: mythdbcon.h:220
AllMusic * m_all_music
Definition: musicdata.h:52
static QStringList fillFieldList(const QString &field)
void toMap(InfoMap &metadataMap, const QString &prefix="")
QString getAlbumArtFile(void)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
Basic menu dialog, message and a list of options.
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
Dialog prompting the user to enter a text string.
void haveResult(QString)
bool Create(void) override
void DisplayState(const QString &state, const QString &name)
bool MoveUpDown(bool flag)
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
QString GetImageFilename(const QString &name="") const
QString GetText(const QString &name="") const
void SetText(const QString &text, const QString &name="", const QString &state="")
virtual QString GetValue() const
MythUIButtonListItem * GetItemCurrent() const
void itemVisible(MythUIButtonListItem *item)
void SetItemCurrent(MythUIButtonListItem *item)
void RemoveItem(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
virtual void SetValue(int value)
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemAt(int pos) const
bool MoveToNamedPosition(const QString &position_name)
void itemSelected(MythUIButtonListItem *item)
bool Find(const QString &searchStr, bool startsWith=false)
void Clicked()
void SetCheckState(MythUIStateType::StateType state)
void toggled(bool)
bool GetBooleanCheckState(void) const
Provide a dialog to quickly find an entry in a list.
void haveResult(QString)
void SetRange(int low, int high, int step, uint pageMultiple=5)
Set the lower and upper bounds of the spinbox, the interval and page amount.
void SetValue(int val) override
Definition: mythuispinbox.h:32
QString GetValue(void) const override
Definition: mythuispinbox.h:37
int GetIntValue(void) const override
Definition: mythuispinbox.h:39
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void valueChanged()
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
QString GetText(void) const
Definition: mythuitext.h:43
void SetFontState(const QString &state)
Definition: mythuitext.cpp:202
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
bool IsEnabled(void) const
Definition: mythuitype.h:119
void SetEnabled(bool enable)
void Hide(void)
void Show(void)
bool saveToDatabase(int smartPlaylistID) const
QString toString(void) const
QString getSQL(void) const
void setDate(QString date)
MythUICheckBox * m_fixedRadio
MythUICheckBox * m_nowRadio
MythUISpinBox * m_daySpin
MythUIText * m_statusText
MythUIButton * m_okButton
MythUISpinBox * m_monthSpin
MythUIButton * m_cancelButton
MythUISpinBox * m_yearSpin
MythUISpinBox * m_addDaysSpin
void fixedCheckToggled(bool on)
void nowCheckToggled(bool on)
QString getDate(void)
bool Create(void) override
void dateChanged(QString date)
MythUIButtonList * m_orderSelector
QString getFieldList(void)
bool Create(void) override
MythUIButton * m_addButton
MythUIButtonList * m_fieldList
MythUIButton * m_descendingButton
void setFieldList(const QString &fieldList)
MythUIButton * m_moveUpButton
void fieldListSelectionChanged(MythUIButtonListItem *item)
MythUIButton * m_cancelButton
MythUIButton * m_ascendingButton
MythUIButton * m_okButton
MythUIButton * m_moveDownButton
MythUIButton * m_deleteButton
void setSQL(const QString &sql)
MythUIText * m_positionText
MythUIButtonList * m_trackList
void trackSelected(MythUIButtonListItem *item)
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
static void trackVisible(MythUIButtonListItem *item)
bool Create(void) override
void startDeleteCategory(const QString &category)
void renameCategory(const QString &category)
MythUIButton * m_orderByButton
void editSmartPlaylist(const QString &category, const QString &name)
MythUIButton * m_cancelButton
QString getWhereClause(void)
void customEvent(QEvent *event) override
MythUIButton * m_saveButton
void loadFromDatabase(const QString &category, const QString &name)
void showCategoryMenu(void)
void doDeleteCriteria(bool doit)
void showResultsClicked(void)
void smartPLChanged(const QString &category, const QString &name)
QString getOrderByClause(void)
MythUIButton * m_showResultsButton
MythUIButtonList * m_categorySelector
void showCriteriaMenu(void)
QString getSQL(const QString &fields)
void newCategory(const QString &category)
static bool deleteCategory(const QString &category)
MythUIButtonList * m_matchSelector
MythUIButtonList * m_orderBySelector
void getSmartPlaylistCategories(void)
MythUIText * m_matchesText
bool Create(void) override
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void orderByChanged(const QString &orderBy)
void getCategoryAndName(QString &category, QString &name)
QList< SmartPLCriteriaRow * > m_criteriaRows
static int lookupCategoryID(const QString &category)
MythUITextEdit * m_titleEdit
MythUIButton * m_categoryButton
MythUIButtonList * m_criteriaList
~SmartPlaylistEditor(void) override
MythUISpinBox * m_limitSpin
void newSmartPlaylist(const QString &category)
static bool deleteSmartPlaylist(const QString &category, const QString &name)
SmartPLCriteriaRow * m_tempCriteriaRow
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MusicData * gMusicData
Definition: musicdata.cpp:23
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
static MythThemedMenu * menu
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
@ ISODate
Default UTC.
Definition: mythdate.h:17
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
duration< CHRONO_TYPE, ratio< 86400 > > days
Definition: mythchrono.h:25
static const SmartPLOperator * lookupOperator(const QString &name)
QString getOrderBySQL(const QString &orderByFields)
static const std::array< const SmartPLOperator, 11 > SmartPLOperators
QString getSQLFieldName(const QString &fieldName)
QString getCriteriaSQL(const QString &fieldName, const QString &operatorName, QString value1, QString value2)
static const std::array< const SmartPLField, 13 > SmartPLFields
QString formattedFieldValue(const QVariant &value)
static QString evaluateDateValue(QString sDate)
static const SmartPLField * lookupField(const QString &name)
SmartPLFieldType
Definition: smartplaylist.h:23
@ ftDate
Definition: smartplaylist.h:26
@ ftBoolean
Definition: smartplaylist.h:27
@ ftNumeric
Definition: smartplaylist.h:25
@ ftString
Definition: smartplaylist.h:24
QString m_sqlName
SmartPLFieldType m_type
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27