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;
37 int m_minValue { 0 };
38 int m_maxValue { 0 };
39 int m_defaultValue { 0 };
40};
41
42static const std::array<const SmartPLField,13> SmartPLFields
43{{
44 { .m_name="", .m_sqlName="" },
45 { .m_name="Artist", .m_sqlName="music_artists.artist_name" },
46 { .m_name="Album", .m_sqlName="music_albums.album_name" },
47 { .m_name="Title", .m_sqlName="music_songs.name" },
48 { .m_name="Genre", .m_sqlName="music_genres.genre" },
49 { .m_name="Year", .m_sqlName="music_songs.year",
50 .m_type=ftNumeric,
51 .m_minValue=1900, .m_maxValue=2099, .m_defaultValue=2000 },
52 { .m_name="Track No.", .m_sqlName="music_songs.track",
53 .m_type=ftNumeric, .m_maxValue=99 },
54 { .m_name="Rating", .m_sqlName="music_songs.rating",
55 .m_type=ftNumeric, .m_maxValue=10 },
56 { .m_name="Play Count", .m_sqlName="music_songs.numplays",
57 .m_type=ftNumeric, .m_maxValue=9999 },
58 { .m_name="Compilation", .m_sqlName="music_albums.compilation",
59 .m_type=ftBoolean, .m_maxValue=0 },
60 { .m_name="Comp. Artist", .m_sqlName="music_comp_artists.artist_name",
61 .m_type=ftString, },
62 { .m_name="Last Play", .m_sqlName="FROM_DAYS(TO_DAYS(music_songs.lastplay))",
63 .m_type=ftDate },
64 { .m_name="Date Imported", .m_sqlName="FROM_DAYS(TO_DAYS(music_songs.date_entered))",
65 .m_type=ftDate },
66}};
67
69{
70 QString m_name;
71 int m_noOfArguments { 1 };
72 bool m_stringOnly { false };
73 bool m_validForBoolean { false };
74};
75
76static const std::array<const SmartPLOperator,11> SmartPLOperators
77{{
78 { .m_name="is equal to", .m_validForBoolean=true },
79 { .m_name="is not equal to", .m_validForBoolean=true },
80 { .m_name="is greater than" },
81 { .m_name="is less than" },
82 { .m_name="starts with", .m_stringOnly=true },
83 { .m_name="ends with", .m_stringOnly=true },
84 { .m_name="contains", .m_stringOnly=true },
85 { .m_name="does not contain", .m_stringOnly=true },
86 { .m_name="is between", .m_noOfArguments=2 },
87 { .m_name="is set", .m_noOfArguments=0 },
88 { .m_name="is not set", .m_noOfArguments=0 },
89}};
90
91static const SmartPLOperator *lookupOperator(const QString& name)
92{
93 for (const auto & oper : SmartPLOperators)
94 {
95 if (oper.m_name == name)
96 return &oper;
97 }
98 return nullptr;
99}
100
101static const SmartPLField *lookupField(const QString& name)
102{
103 for (const auto & field : SmartPLFields)
104 {
105 if (field.m_name == name)
106 return &field;
107 }
108 return nullptr;
109}
110
111QString formattedFieldValue(const QVariant &value)
112{
113#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
114 QSqlField field("", value.type());
115#else
116 QSqlField field("", value.metaType());
117#endif
118 if (value.isNull())
119 field.clear();
120 else
121 field.setValue(value);
122
124 QString result = QString::fromUtf8(query.driver()->formatValue(field).toLatin1().data());
125 return result;
126}
127
128static QString evaluateDateValue(QString sDate)
129{
130 if (sDate.startsWith("$DATE"))
131 {
132 QDate date = MythDate::current().toLocalTime().date();
133
134 if (sDate.length() > 9)
135 {
136 bool bNegative = false;
137 if (sDate[6] == '-')
138 bNegative = true;
139
140 if (sDate.endsWith(" days"))
141 sDate = sDate.left(sDate.length() - 5);
142
143#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
144 int nDays = sDate.midRef(8).toInt();
145#else
146 int nDays = QStringView(sDate).mid(8).toInt();
147#endif
148 if (bNegative)
149 nDays = -nDays;
150
151 date = date.addDays(nDays);
152 }
153
154 return date.toString(Qt::ISODate);
155 }
156
157 return sDate;
158}
159
160QString getCriteriaSQL(const QString& fieldName, const QString &operatorName,
161 QString value1, QString value2)
162{
163 QString result;
164
165 if (fieldName.isEmpty())
166 return result;
167
168 const SmartPLField *Field = lookupField(fieldName);
169 if (!Field)
170 {
171 return "";
172 }
173
174 result = Field->m_sqlName;
175
176 const SmartPLOperator *Operator = lookupOperator(operatorName);
177 if (!Operator)
178 {
179 return {};
180 }
181
182 // convert boolean and date values
183 if (Field->m_type == ftBoolean)
184 {
185 // compilation field uses 0 = false; 1 = true
186 value1 = (value1 == "Yes") ? "1":"0";
187 value2 = (value2 == "Yes") ? "1":"0";
188 }
189 else if (Field->m_type == ftDate)
190 {
191 value1 = evaluateDateValue(value1);
192 value2 = evaluateDateValue(value2);
193 }
194
195 if (Operator->m_name == "is equal to")
196 {
197 result = result + " = " + formattedFieldValue(value1);
198 }
199 else if (Operator->m_name == "is not equal to")
200 {
201 result = result + " != " + formattedFieldValue(value1);
202 }
203 else if (Operator->m_name == "is greater than")
204 {
205 result = result + " > " + formattedFieldValue(value1);
206 }
207 else if (Operator->m_name == "is less than")
208 {
209 result = result + " < " + formattedFieldValue(value1);
210 }
211 else if (Operator->m_name == "starts with")
212 {
213 result = result + " LIKE " + formattedFieldValue(value1 + QString("%"));
214 }
215 else if (Operator->m_name == "ends with")
216 {
217 result = result + " LIKE " + formattedFieldValue(QString("%") + value1);
218 }
219 else if (Operator->m_name == "contains")
220 {
221 result = result + " LIKE " + formattedFieldValue(QString("%") + value1 + "%");
222 }
223 else if (Operator->m_name == "does not contain")
224 {
225 result = result + " NOT LIKE " + formattedFieldValue(QString("%") + value1 + "%");
226 }
227 else if (Operator->m_name == "is between")
228 {
229 result = result + " BETWEEN " + formattedFieldValue(value1) +
230 " AND " + formattedFieldValue(value2);
231 }
232 else if (Operator->m_name == "is set")
233 {
234 result = result + " IS NOT NULL";
235 }
236 else if (Operator->m_name == "is not set")
237 {
238 result = result + " IS NULL";
239 }
240 else
241 {
242 result.clear();
243 LOG(VB_GENERAL, LOG_ERR,
244 QString("getCriteriaSQL(): invalid operator '%1'")
245 .arg(Operator->m_name));
246 }
247
248 return result;
249}
250
251QString getOrderBySQL(const QString& orderByFields)
252{
253 if (orderByFields.isEmpty())
254 return {};
255
256 QStringList list = orderByFields.split(",");
257 QString fieldName;
258 QString result;
259 QString order;
260 bool bFirst = true;
261
262 for (int x = 0; x < list.count(); x++)
263 {
264 fieldName = list[x].trimmed();
265 const SmartPLField *Field = lookupField(fieldName.left(fieldName.length() - 4));
266 if (Field)
267 {
268 if (fieldName.right(3) == "(D)")
269 order = " DESC";
270 else
271 order = " ASC";
272
273 if (bFirst)
274 {
275 bFirst = false;
276 result = " ORDER BY " + Field->m_sqlName + order;
277 }
278 else
279 {
280 result += ", " + Field->m_sqlName + order;
281 }
282 }
283 }
284
285 return result;
286}
287
288QString getSQLFieldName(const QString &fieldName)
289{
290 const SmartPLField *Field = lookupField(fieldName);
291 if (!Field)
292 {
293 return "";
294 }
295
296 return Field->m_sqlName;
297}
298
299/*
301*/
302
303QString SmartPLCriteriaRow::getSQL(void) const
304{
305 if (m_field.isEmpty())
306 return {};
307
308 QString result;
309
311
312 return result;
313}
314
315// return false on error
316bool SmartPLCriteriaRow::saveToDatabase(int smartPlaylistID) const
317{
318 // save playlistitem to database
319
320 if (m_field.isEmpty())
321 return true;
322
324 query.prepare("INSERT INTO music_smartplaylist_items (smartplaylistid, field, operator,"
325 " value1, value2)"
326 "VALUES (:SMARTPLAYLISTID, :FIELD, :OPERATOR, :VALUE1, :VALUE2);");
327 query.bindValue(":SMARTPLAYLISTID", smartPlaylistID);
328 query.bindValueNoNull(":FIELD", m_field);
329 query.bindValueNoNull(":OPERATOR", m_operator);
330 query.bindValueNoNull(":VALUE1", m_value1);
331 query.bindValueNoNull(":VALUE2", m_value2);
332
333 if (!query.exec())
334 {
335 MythDB::DBError("Inserting new smartplaylist item", query);
336 return false;
337 }
338
339 return true;
340}
341
343{
344 const SmartPLOperator *PLOperator = lookupOperator(m_operator);
345 if (PLOperator)
346 {
347 QString result;
348 if (PLOperator->m_noOfArguments == 0)
349 {
350 result = m_field + " " + m_operator;
351 }
352 else if (PLOperator->m_noOfArguments == 1)
353 {
354 result = m_field + " " + m_operator + " " + m_value1;
355 }
356 else
357 {
358 result = m_field + " " + m_operator + " " + m_value1;
359 result += " " + tr("and") + " " + m_value2;
360 }
361
362 return result;
363 }
364
365 return {};
366}
367
368/*
369---------------------------------------------------------------------
370*/
371
373{
374 while (!m_criteriaRows.empty())
375 {
376 delete m_criteriaRows.back();
377 m_criteriaRows.pop_back();
378 }
379
380 delete m_tempCriteriaRow;
381}
382
383
385{
386 if (!LoadWindowFromXML("music-ui.xml", "smartplaylisteditor", this))
387 return false;
388
389 bool err = false;
390
391 UIUtilE::Assign(this, m_categorySelector, "categoryselector", &err);
392 UIUtilE::Assign(this, m_categoryButton, "categorybutton", &err);
393 UIUtilE::Assign(this, m_titleEdit, "titleedit", &err);
394 UIUtilE::Assign(this, m_matchSelector, "matchselector", &err);
395 UIUtilE::Assign(this, m_criteriaList, "criterialist", &err);
396 UIUtilE::Assign(this, m_orderBySelector, "orderbyselector", &err);
397 UIUtilE::Assign(this, m_orderByButton, "orderbybutton", &err);
398 UIUtilE::Assign(this, m_matchesText, "matchestext", &err);
399 UIUtilE::Assign(this, m_limitSpin, "limitspin", &err);
400
401 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
402 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
403 UIUtilE::Assign(this, m_showResultsButton, "showresultsbutton", &err);
404
405 if (err)
406 {
407 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplaylisteditor'");
408 return false;
409 }
410
412
416
417 for (const auto & field : SmartPLFields)
418 {
419 if (field.m_name == "")
420 new MythUIButtonListItem(m_orderBySelector, field.m_name);
421 else
422 new MythUIButtonListItem(m_orderBySelector, field.m_name + " (A)");
423 }
424
425 m_limitSpin->SetRange(0, 9999, 10);
426
433
435
436 return true;
437}
438
440{
442 return true;
443
444 QStringList actions;
445 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
446
447 for (int i = 0; i < actions.size() && !handled; i++)
448 {
449 const QString& action = actions[i];
450 handled = true;
451
452 if (action == "MENU")
453 {
455 }
456 else if (action == "DELETE" && GetFocusWidget() == m_criteriaList)
457 {
459 }
460 else if (action == "EDIT" && GetFocusWidget() == m_criteriaList)
461 {
462 editCriteria();
463 }
464 else
465 {
466 handled = false;
467 }
468 }
469
470 if (!handled && MythScreenType::keyPressEvent(event))
471 handled = true;
472
473 return handled;
474}
475
477{
478 if (auto *dce = dynamic_cast<DialogCompletionEvent*>(event))
479 {
480 // make sure the user didn't ESCAPE out of the menu
481 if (dce->GetResult() < 0)
482 return;
483
484 QString resultid = dce->GetId();
485 QString resulttext = dce->GetResultText();
486 if (resultid == "categorymenu")
487 {
488 if (resulttext == tr("New Category"))
489 {
490 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
491 QString label = tr("Enter Name Of New Category");
492
493 auto *input = new MythTextInputDialog(popupStack, label);
494
495 connect(input, &MythTextInputDialog::haveResult,
497
498 if (input->Create())
499 popupStack->AddScreen(input);
500 else
501 delete input;
502 }
503 else if (resulttext == tr("Delete Category"))
504 {
506 }
507 else if (resulttext == tr("Rename Category"))
508 {
509 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
510 QString label = tr("Enter New Name For Category: %1").arg(m_categorySelector->GetValue());
511
512 auto *input = new MythTextInputDialog(popupStack, label);
513
514 connect(input, &MythTextInputDialog::haveResult,
516
517 if (input->Create())
518 popupStack->AddScreen(input);
519 else
520 delete input;
521 }
522 }
523 }
524}
525
527{
529 {
530 delete m_tempCriteriaRow;
531 m_tempCriteriaRow = nullptr;
532 }
533
535
536 if (!item)
537 return;
538
539 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
540
541 if (!row)
542 return;
543
544 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
545
546 auto *editor = new CriteriaRowEditor(popupStack, row);
547
548 if (!editor->Create())
549 {
550 delete editor;
551 return;
552 }
553
555
556 popupStack->AddScreen(editor);
557}
558
560{
561 // make sure we have something to delete
563
564 if (!item)
565 return;
566
567 ShowOkPopup(tr("Delete Criteria?"), this, &SmartPlaylistEditor::doDeleteCriteria, true);
568}
569
571{
572 if (doit)
573 {
575 if (!item)
576 return;
577
578 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
579
580 if (!row)
581 return;
582
583 m_criteriaRows.removeAll(row);
585
587 }
588}
589
591{
592 /*
593 SmartPLCriteriaRow *row = new SmartPLCriteriaRow();
594 m_criteriaRows.append(row);
595
596 MythUIButtonListItem *item = new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
597
598 m_criteriaList->SetItemCurrent(item);
599
600 editCriteria();
601 */
602
603 delete m_tempCriteriaRow;
605
606 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
607
608 auto *editor = new CriteriaRowEditor(popupStack, m_tempCriteriaRow);
609
610 if (!editor->Create())
611 {
612 delete editor;
613 return;
614 }
615
617
618 popupStack->AddScreen(editor);
619}
620
622{
623 MythUIButtonListItem *item = nullptr;
624
626 {
627 // this is a new row so add it to the list
629
631 QVariant::fromValue(m_tempCriteriaRow));
632
634
635 m_tempCriteriaRow = nullptr;
636 }
637 else
638 {
639 // update the existing row
641 if (!item)
642 return;
643
644 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
645
646 if (!row)
647 return;
648
649 item->SetText(row->toString());
650 }
651
653}
654
656{
657 QString label = tr("Category Actions");
658
659 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
660
661 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
662
663 if (!menu->Create())
664 {
665 delete menu;
666 return;
667 }
668
669 menu->SetReturnEvent(this, "categorymenu");
670
671 menu->AddButton(tr("New Category"), nullptr);
672 menu->AddButton(tr("Delete Category"), nullptr);
673 menu->AddButton(tr("Rename Category"), nullptr);
674
675 popupStack->AddScreen(menu);
676}
677
679{
680 QString label = tr("Criteria Actions");
681
682 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
683
684 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
685
686 if (!menu->Create())
687 {
688 delete menu;
689 return;
690 }
691
692 menu->SetReturnEvent(this, "criteriamenu");
693
695
696 if (item)
697 menu->AddButton(tr("Edit Criteria"), &SmartPlaylistEditor::editCriteria);
698
699 menu->AddButton(tr("Add Criteria"), &SmartPlaylistEditor::addCriteria);
700
701 if (item)
702 menu->AddButton(tr("Delete Criteria"), &SmartPlaylistEditor::deleteCriteria);
703
704 popupStack->AddScreen(menu);
705}
706
708{
710}
711
713{
714 QString sql =
715 "SELECT count(*) "
716 "FROM music_songs "
717 "LEFT JOIN music_artists ON "
718 " music_songs.artist_id=music_artists.artist_id "
719 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
720 "LEFT JOIN music_artists AS music_comp_artists ON "
721 " music_albums.artist_id=music_comp_artists.artist_id "
722 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
723
724 sql += getWhereClause();
725
726 m_matchesCount = 0;
727
729 if (!query.exec(sql))
730 MythDB::DBError("SmartPlaylistEditor::updateMatches", query);
731 else if (query.next())
732 m_matchesCount = query.value(0).toInt();
733
734 m_matchesText->SetText(QString::number(m_matchesCount));
735
738 titleChanged();
739}
740
742{
743 // save smartplaylist to database
744
745 QString name = m_titleEdit->GetText();
746 QString category = m_categorySelector->GetValue();
747 QString matchType = (m_matchSelector->GetValue() == tr("All") ? "All" : "Any");
748 QString orderBy = m_orderBySelector->GetValue();
749 QString limit = m_limitSpin->GetValue();
750
751 // lookup categoryid
752 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
753
754 // easier to delete any existing smartplaylist and recreate a new one
755 if (!m_newPlaylist)
757 else
759
761 // insert new smartplaylist
762 query.prepare("INSERT INTO music_smartplaylists (name, categoryid, matchtype, orderby, limitto) "
763 "VALUES (:NAME, :CATEGORYID, :MATCHTYPE, :ORDERBY, :LIMIT);");
764 query.bindValue(":NAME", name);
765 query.bindValue(":CATEGORYID", categoryid);
766 query.bindValue(":MATCHTYPE", matchType);
767 query.bindValue(":ORDERBY", orderBy);
768 query.bindValue(":LIMIT", limit);
769
770 if (!query.exec())
771 {
772 MythDB::DBError("Inserting new playlist", query);
773 return;
774 }
775
776 // get smartplaylistid
777 int ID = -1;
778 query.prepare("SELECT smartplaylistid FROM music_smartplaylists "
779 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
780 query.bindValue(":CATEGORYID", categoryid);
781 query.bindValue(":NAME", name);
782 if (query.exec())
783 {
784 if (query.isActive() && query.size() > 0)
785 {
786 query.first();
787 ID = query.value(0).toInt();
788 }
789 else
790 {
791 LOG(VB_GENERAL, LOG_ERR,
792 QString("Failed to find ID for smartplaylist: %1").arg(name));
793 return;
794 }
795 }
796 else
797 {
798 MythDB::DBError("Getting smartplaylist ID", query);
799 return;
800 }
801
802 // save smartplaylist items
803 for (const auto & row : std::as_const(m_criteriaRows))
804 row->saveToDatabase(ID);
805
806 emit smartPLChanged(category, name);
807
808 Close();
809}
810
811void SmartPlaylistEditor::newSmartPlaylist(const QString& category)
812{
813 m_categorySelector->SetValue(category);
815 m_originalCategory = category;
816 m_originalName.clear();
817
818 m_newPlaylist = true;
819
821}
822
823void SmartPlaylistEditor::editSmartPlaylist(const QString& category, const QString& name)
824{
825 m_originalCategory = category;
826 m_originalName = name;
827 m_newPlaylist = false;
828 loadFromDatabase(category, name);
830}
831
832void SmartPlaylistEditor::loadFromDatabase(const QString& category, const QString& name)
833{
834 // load smartplaylist from database
835 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
836
838 int ID = -1;
839
840 query.prepare("SELECT smartplaylistid, name, categoryid, matchtype, orderby, limitto "
841 "FROM music_smartplaylists WHERE name = :NAME AND categoryid = :CATEGORYID;");
842 query.bindValue(":NAME", name);
843 query.bindValue(":CATEGORYID", categoryid);
844 if (query.exec())
845 {
846 if (query.isActive() && query.size() > 0)
847 {
848 query.first();
849 ID = query.value(0).toInt();
850 m_titleEdit->SetText(name);
851 m_categorySelector->SetValue(category);
852 if (query.value(3).toString() == "All")
853 m_matchSelector->SetValue(tr("All"));
854 else
855 m_matchSelector->SetValue(tr("Any"));
856
857 QString orderBy = query.value(4).toString();
858 if (!m_orderBySelector->Find(orderBy))
859 {
860 // not found so add it to the selector
862 m_orderBySelector->SetValue(orderBy);
863 }
864
865 m_limitSpin->SetValue(query.value(5).toInt());
866 }
867 else
868 {
869 LOG(VB_GENERAL, LOG_ERR,
870 QString("Cannot find smartplaylist: %1").arg(name));
871 return;
872 }
873 }
874 else
875 {
876 MythDB::DBError("Load smartplaylist", query);
877 return;
878 }
879
881
882 query.prepare("SELECT field, operator, value1, value2 "
883 "FROM music_smartplaylist_items WHERE smartplaylistid = :ID "
884 "ORDER BY smartplaylistitemid;");
885 query.bindValue(":ID", ID);
886 if (!query.exec())
887 MythDB::DBError("Load smartplaylist items", query);
888
889 if (query.size() > 0)
890 {
891 while (query.next())
892 {
893 QString Field = query.value(0).toString();
894 QString Operator = query.value(1).toString();
895 QString Value1 = query.value(2).toString();
896 QString Value2 = query.value(3).toString();
897 // load smartplaylist items
898 auto *row = new SmartPLCriteriaRow(Field, Operator, Value1, Value2);
899 m_criteriaRows.append(row);
900
901 new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
902 }
903 }
904 else
905 {
906 LOG(VB_GENERAL, LOG_WARNING,
907 QString("Got no smartplaylistitems for ID: ").arg(ID));
908 }
909}
910
911void SmartPlaylistEditor::newCategory(const QString &category)
912{
913 // insert new smartplaylistcategory
914
916 query.prepare("INSERT INTO music_smartplaylist_categories (name) "
917 "VALUES (:NAME);");
918 query.bindValue(":NAME", category);
919
920 if (!query.exec())
921 {
922 MythDB::DBError("Inserting new smartplaylist category", query);
923 return;
924 }
925
927 m_categorySelector->SetValue(category);
928}
929
930void SmartPlaylistEditor::startDeleteCategory(const QString &category)
931{
932 if (category.isEmpty())
933 return;
934
935//FIXME::
936#if 0
937 if (!MythPopupBox::showOkCancelPopup(GetMythMainWindow(),
938 "Delete Category",
939 tr("Are you sure you want to delete this Category?")
940 + "\n\n\"" + category + "\"\n\n"
941 + tr("It will also delete any Smart Playlists belonging to this category."),
942 false))
943 return;
944
946#endif
949}
950
951void SmartPlaylistEditor::renameCategory(const QString &category)
952{
953 if (m_categorySelector->GetValue() == category)
954 return;
955
956 // change the category
958 query.prepare("UPDATE music_smartplaylist_categories SET name = :NEW_CATEGORY "
959 "WHERE name = :OLD_CATEGORY;");
960 query.bindValue(":OLD_CATEGORY", m_categorySelector->GetValue());
961 query.bindValue(":NEW_CATEGORY", category);
962
963 if (!query.exec())
964 MythDB::DBError("Rename smartplaylist", query);
965
966 if (!m_newPlaylist)
968
970 m_categorySelector->SetValue(category);
971}
972
973QString SmartPlaylistEditor::getSQL(const QString& fields)
974{
975 QString sql;
976 QString whereClause;
977 QString orderByClause;
978 QString limitClause;
979 sql = "SELECT " + fields + " FROM music_songs "
980 "LEFT JOIN music_artists ON music_songs.artist_id=music_artists.artist_id "
981 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
982 "LEFT JOIN music_artists AS music_comp_artists ON music_albums.artist_id=music_comp_artists.artist_id "
983 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
984
985 whereClause = getWhereClause();
986 orderByClause = getOrderByClause();
987 if (m_limitSpin->GetIntValue() > 0)
988 limitClause = " LIMIT " + m_limitSpin->GetValue();
989
990 sql = sql + whereClause + orderByClause + limitClause;
991
992 return sql;
993}
994
996{
998}
999
1001{
1002 if (m_criteriaRows.empty())
1003 return {};
1004
1005 bool bFirst = true;
1006 QString sql = "WHERE ";
1007
1008 for (const auto & row : std::as_const(m_criteriaRows))
1009 {
1010 QString criteria = row->getSQL();
1011 if (criteria.isEmpty())
1012 continue;
1013
1014 if (bFirst)
1015 {
1016 sql += criteria;
1017 bFirst = false;
1018 }
1019 else
1020 {
1021 if (m_matchSelector->GetValue() == tr("Any"))
1022 sql += " OR " + criteria;
1023 else
1024 sql += " AND " + criteria;
1025 }
1026 }
1027
1028 return sql;
1029}
1030
1032{
1033 QString sql = getSQL("song_id, music_artists.artist_name, album_name, "
1034 "name, genre, music_songs.year, track");
1035
1037
1038 auto *resultViewer = new SmartPLResultViewer(mainStack);
1039
1040 if (!resultViewer->Create())
1041 {
1042 delete resultViewer;
1043 return;
1044 }
1045
1046 resultViewer->setSQL(sql);
1047
1048 mainStack->AddScreen(resultViewer);
1049}
1050
1052{
1053 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1054
1055 auto *orderByDialog = new SmartPLOrderByDialog(popupStack);
1056
1057 if (!orderByDialog->Create())
1058 {
1059 delete orderByDialog;
1060 return;
1061 }
1062
1063 orderByDialog->setFieldList(m_orderBySelector->GetValue());
1064
1065 connect(orderByDialog, qOverload<QString>(&SmartPLOrderByDialog::orderByChanged),
1067
1068 popupStack->AddScreen(orderByDialog);
1069}
1070
1071void SmartPlaylistEditor::orderByChanged(const QString& orderBy)
1072{
1074 return;
1075
1076 // not found so add it to the selector
1078 m_orderBySelector->SetValue(orderBy);
1079}
1080
1082{
1085
1086 if (query.exec("SELECT name FROM music_smartplaylist_categories ORDER BY name;"))
1087 {
1088 if (query.isActive() && query.size() > 0)
1089 {
1090 while (query.next())
1091 new MythUIButtonListItem(m_categorySelector, query.value(0).toString());
1092 }
1093 else
1094 {
1095 LOG(VB_GENERAL, LOG_ERR,
1096 "Could not find any smartplaylist categories");
1097 }
1098 }
1099 else
1100 {
1101 MythDB::DBError("Load smartplaylist categories", query);
1102 }
1103}
1104
1105// static function to delete a smartplaylist and any associated smartplaylist items
1106bool SmartPlaylistEditor::deleteSmartPlaylist(const QString &category, const QString& name)
1107{
1108 // get categoryid
1109 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1110
1112
1113 // get playlist ID
1114 int ID = -1;
1115 query.prepare("SELECT smartplaylistid FROM music_smartplaylists WHERE name = :NAME "
1116 "AND categoryid = :CATEGORYID;");
1117 query.bindValue(":NAME", name);
1118 query.bindValue(":CATEGORYID", categoryid);
1119 if (query.exec())
1120 {
1121 if (query.isActive() && query.size() > 0)
1122 {
1123 query.first();
1124 ID = query.value(0).toInt();
1125 }
1126 else
1127 {
1128 // not always an error maybe we are trying to delete a playlist
1129 // that does not exist
1130 return true;
1131 }
1132 }
1133 else
1134 {
1135 MythDB::DBError("Delete smartplaylist", query);
1136 return false;
1137 }
1138
1139 //delete smartplaylist items
1140 query.prepare("DELETE FROM music_smartplaylist_items WHERE smartplaylistid = :ID;");
1141 query.bindValue(":ID", ID);
1142 if (!query.exec())
1143 MythDB::DBError("Delete smartplaylist items", query);
1144
1145 //delete smartplaylist
1146 query.prepare("DELETE FROM music_smartplaylists WHERE smartplaylistid = :ID;");
1147 query.bindValue(":ID", ID);
1148 if (!query.exec())
1149 MythDB::DBError("Delete smartplaylist", query);
1150
1151 return true;
1152}
1153
1154// static function to delete all smartplaylists belonging to the given category
1155// will also delete any associated smartplaylist items
1156bool SmartPlaylistEditor::deleteCategory(const QString& category)
1157{
1158 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1160
1161 //delete all smartplaylists with the selected category
1162 query.prepare("SELECT name FROM music_smartplaylists "
1163 "WHERE categoryid = :CATEGORYID;");
1164 query.bindValue(":CATEGORYID", categoryid);
1165 if (!query.exec())
1166 {
1167 MythDB::DBError("Delete SmartPlaylist Category", query);
1168 return false;
1169 }
1170
1171 if (query.isActive() && query.size() > 0)
1172 {
1173 while (query.next())
1174 {
1175 SmartPlaylistEditor::deleteSmartPlaylist(category, query.value(0).toString());
1176 }
1177 }
1178
1179 // delete the category
1180 query.prepare("DELETE FROM music_smartplaylist_categories WHERE categoryid = :ID;");
1181 query.bindValue(":ID", categoryid);
1182 if (!query.exec())
1183 MythDB::DBError("Delete smartplaylist category", query);
1184
1185 return true;
1186}
1187
1188// static function to lookup the categoryid given its name
1189int SmartPlaylistEditor::lookupCategoryID(const QString& category)
1190{
1191 int ID = -1;
1193 query.prepare("SELECT categoryid FROM music_smartplaylist_categories "
1194 "WHERE name = :CATEGORY;");
1195 query.bindValue(":CATEGORY", category);
1196
1197 if (query.exec())
1198 {
1199 if (query.isActive() && query.size() > 0)
1200 {
1201 query.first();
1202 ID = query.value(0).toInt();
1203 }
1204 else
1205 {
1206 LOG(VB_GENERAL, LOG_ERR,
1207 QString("Failed to find smart playlist category: %1")
1208 .arg(category));
1209 ID = -1;
1210 }
1211 }
1212 else
1213 {
1214 MythDB::DBError("Getting category ID", query);
1215 ID = -1;
1216 }
1217
1218 return ID;
1219}
1220
1221void SmartPlaylistEditor::getCategoryAndName(QString &category, QString &name)
1222{
1223 category = m_categorySelector->GetValue();
1224 name = m_titleEdit->GetText();
1225}
1226
1227/*
1228---------------------------------------------------------------------
1229*/
1230
1232{
1233 if (!LoadWindowFromXML("music-ui.xml", "criteriaroweditor", this))
1234 return false;
1235
1236 bool err = false;
1237
1238 UIUtilE::Assign(this, m_fieldSelector, "fieldselector", &err);
1239 UIUtilE::Assign(this, m_operatorSelector, "operatorselector", &err);
1240 UIUtilE::Assign(this, m_value1Edit, "value1edit", &err);
1241 UIUtilE::Assign(this, m_value2Edit, "value2edit", &err);
1242 UIUtilE::Assign(this, m_value1Selector, "value1selector", &err);
1243 UIUtilE::Assign(this, m_value2Selector, "value2selector", &err);
1244 UIUtilE::Assign(this, m_value1Spinbox, "value1spinbox", &err);
1245 UIUtilE::Assign(this, m_value2Spinbox, "value2spinbox", &err);
1246 UIUtilE::Assign(this, m_value1Button, "value1button", &err);
1247 UIUtilE::Assign(this, m_value2Button, "value2button", &err);
1248 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1249 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
1250
1251 if (err)
1252 {
1253 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'criteriaroweditor'");
1254 return false;
1255 }
1256
1257 updateFields();
1259 updateValues();
1260
1263
1268
1271
1274
1276
1277 return true;
1278}
1279
1281{
1282 for (const auto & field : SmartPLFields)
1283 new MythUIButtonListItem(m_fieldSelector, field.m_name);
1284
1286}
1287
1289{
1290 for (const auto & oper : SmartPLOperators)
1291 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1292
1294}
1295
1297{
1299}
1300
1302{
1307
1309 {
1310 // not found so add it to the selector
1313 }
1314
1316 {
1317 // not found so add it to the selector
1320 }
1321}
1322
1324{
1326 if (!Field)
1327 return;
1328
1331
1332 if (Field->m_type == ftNumeric)
1333 {
1336 }
1337 else if (Field->m_type == ftBoolean || Field->m_type == ftDate)
1338 {
1341 }
1342 else // ftString
1343 {
1346 }
1347
1348 // NOLINTNEXTLINE(readability-misleading-indentation)
1349 emit criteriaChanged();
1350
1351 Close();
1352}
1353
1355{
1356 bool enabled = false;
1357
1359
1361
1362 if (Field && Operator)
1363 {
1364 if (Field->m_type == ftNumeric || Field->m_type == ftBoolean)
1365 {
1366 enabled = true;
1367 }
1368 else if (Field->m_type == ftDate)
1369 {
1370 if ((Operator->m_noOfArguments == 0) ||
1371 (Operator->m_noOfArguments == 1 && !m_value1Selector->GetValue().isEmpty()) ||
1372 (Operator->m_noOfArguments == 2 && !m_value1Selector->GetValue().isEmpty()
1373 && !m_value2Selector->GetValue().isEmpty()))
1374 enabled = true;
1375 }
1376 else // ftString
1377 {
1378 if ((Operator->m_noOfArguments == 0) ||
1379 (Operator->m_noOfArguments == 1 && !m_value1Edit->GetText().isEmpty()) ||
1380 (Operator->m_noOfArguments == 2 && !m_value1Edit->GetText().isEmpty()
1381 && !m_value2Edit->GetText().isEmpty()))
1382 enabled = true;
1383 }
1384 }
1385
1386 m_saveButton->SetEnabled(enabled);
1387}
1388
1390{
1392 if (!Field)
1393 return;
1394
1395 if (Field->m_type == ftBoolean)
1396 {
1397 // add yes / no items to combo
1404 }
1405 else if (Field->m_type == ftDate)
1406 {
1407 // add a couple of date values to the combo
1410 new MythUIButtonListItem(m_value1Selector, "$DATE - 30 days");
1411 new MythUIButtonListItem(m_value1Selector, "$DATE - 60 days");
1412
1414 {
1415 // not found so add it to the selector
1418 }
1419
1420
1423 new MythUIButtonListItem(m_value2Selector, "$DATE - 30 days");
1424 new MythUIButtonListItem(m_value2Selector, "$DATE - 60 days");
1425
1427 {
1428 // not found so add it to the selector
1431 }
1432 }
1433
1434 // get list of operators valid for this field type
1435 getOperatorList(Field->m_type);
1436
1438}
1439
1441{
1443 if (!Field)
1444 return;
1445
1447 if (!Operator)
1448 return;
1449
1450 // hide all widgets
1451 m_value1Edit->Hide();
1452 m_value2Edit->Hide();
1459
1460 // show spin edits
1461 if (Field->m_type == ftNumeric)
1462 {
1463 if (Operator->m_noOfArguments >= 1)
1464 {
1466 int currentValue = m_value1Spinbox->GetIntValue();
1467 m_value1Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1468
1469 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1471 }
1472
1473 if (Operator->m_noOfArguments == 2)
1474 {
1476 int currentValue = m_value2Spinbox->GetIntValue();
1477 m_value2Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1478
1479 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1481 }
1482 }
1483 else if (Field->m_type == ftBoolean)
1484 {
1485 // only show value1combo
1487 }
1488 else if (Field->m_type == ftDate)
1489 {
1490 if (Operator->m_noOfArguments >= 1)
1491 {
1494 }
1495
1496 if (Operator->m_noOfArguments == 2)
1497 {
1500 }
1501 }
1502 else // ftString
1503 {
1504 if (Operator->m_noOfArguments >= 1)
1505 {
1506 m_value1Edit->Show();
1508 }
1509
1510 if (Operator->m_noOfArguments == 2)
1511 {
1512 m_value2Edit->Show();
1514 }
1515 }
1516
1518}
1519
1521{
1522 QString currentOperator = m_operatorSelector->GetValue();
1523
1525
1526 for (const auto & oper : SmartPLOperators)
1527 {
1528 // don't add operators that only work with string fields
1529 if (fieldType != ftString && oper.m_stringOnly)
1530 continue;
1531
1532 // don't add operators that only work with boolean fields
1533 if (fieldType == ftBoolean && !oper.m_validForBoolean)
1534 continue;
1535
1536 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1537 }
1538
1539 // try to set the operatorCombo to the same operator or else the first item
1540 m_operatorSelector->SetValue(currentOperator);
1541}
1542
1544{
1545 QString msg;
1546 QStringList searchList;
1548
1549 if (m_fieldSelector->GetValue() == "Artist")
1550 {
1551 msg = tr("Select an Artist");
1552 searchList = MusicMetadata::fillFieldList("artist");
1553 }
1554 else if (m_fieldSelector->GetValue() == "Comp. Artist")
1555 {
1556 msg = tr("Select a Compilation Artist");
1557 searchList = MusicMetadata::fillFieldList("compilation_artist");
1558 }
1559 else if (m_fieldSelector->GetValue() == "Album")
1560 {
1561 msg = tr("Select an Album");
1562 searchList = MusicMetadata::fillFieldList("album");
1563 }
1564 else if (m_fieldSelector->GetValue() == "Genre")
1565 {
1566 msg = tr("Select a Genre");
1567 searchList = MusicMetadata::fillFieldList("genre");
1568 }
1569 else if (m_fieldSelector->GetValue() == "Title")
1570 {
1571 msg = tr("Select a Title");
1572 searchList = MusicMetadata::fillFieldList("title");
1573 }
1574 else if ((m_fieldSelector->GetValue() == "Last Play") ||
1575 (m_fieldSelector->GetValue() == "Date Imported"))
1576 {
1577 editDate();
1578 return;
1579 }
1580
1581 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1582 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, s);
1583
1584 if (!searchDlg->Create())
1585 {
1586 delete searchDlg;
1587 return;
1588 }
1589
1591
1592 popupStack->AddScreen(searchDlg);
1593}
1594
1595void CriteriaRowEditor::setValue(const QString& value)
1596{
1598 m_value1Edit->SetText(value);
1599 else
1600 m_value2Edit->SetText(value);
1601}
1602
1604{
1605 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1606 auto *dateDlg = new SmartPLDateDialog(popupStack);
1608
1609 if (!dateDlg->Create())
1610 {
1611 delete dateDlg;
1612 return;
1613 }
1614
1615 dateDlg->setDate(date);
1616
1618
1619 popupStack->AddScreen(dateDlg);
1620}
1621
1622void CriteriaRowEditor::setDate(const QString& date)
1623{
1625 {
1627 return;
1628
1629 // not found so add it to the selector
1632 }
1633 else
1634 {
1636 return;
1637
1638 // not found so add it to the selector
1641 }
1642}
1643
1644/*
1645---------------------------------------------------------------------
1646*/
1647
1648
1650{
1651 if (!LoadWindowFromXML("music-ui.xml", "smartplresultviewer", this))
1652 return false;
1653
1654 bool err = false;
1655
1656 UIUtilE::Assign(this, m_trackList, "tracklist", &err);
1657 UIUtilW::Assign(this, m_positionText, "position", &err);
1658
1659 if (err)
1660 {
1661 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplresultviewer'");
1662 return false;
1663 }
1664
1669
1671
1672 return true;
1673}
1674
1676{
1677 if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1678 return true;
1679
1680 QStringList actions;
1681 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
1682
1683 for (int i = 0; i < actions.size() && !handled; i++)
1684 {
1685 const QString& action = actions[i];
1686 handled = true;
1687
1688 if (action == "INFO")
1689 showTrackInfo();
1690 else
1691 handled = false;
1692 }
1693
1694 if (!handled && MythScreenType::keyPressEvent(event))
1695 handled = true;
1696
1697 return handled;
1698}
1699
1701{
1702 if (!item)
1703 return;
1704
1705 if (item->GetImageFilename().isEmpty())
1706 {
1707 auto *mdata = item->GetData().value<MusicMetadata *>();
1708 if (mdata)
1709 {
1710 QString artFile = mdata->getAlbumArtFile();
1711 if (artFile.isEmpty())
1712 item->SetImage("mm_nothumb.png");
1713 else
1714 item->SetImage(mdata->getAlbumArtFile());
1715 }
1716 else
1717 {
1718 item->SetImage("mm_nothumb.png");
1719 }
1720 }
1721}
1722
1724{
1725 if (!item || !m_positionText)
1726 return;
1727
1728 m_positionText->SetText(tr("%1 of %2").arg(m_trackList->IsEmpty() ? 0 : m_trackList->GetCurrentPos() + 1)
1729 .arg(m_trackList->GetCount()));
1730}
1732{
1734 if (!item)
1735 return;
1736
1737 auto *mdata = item->GetData().value<MusicMetadata *>();
1738 if (!mdata)
1739 return;
1740
1741 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1742
1743 auto *dlg = new TrackInfoDialog(popupStack, mdata, "trackinfopopup");
1744
1745 if (!dlg->Create())
1746 {
1747 delete dlg;
1748 return;
1749 }
1750
1751 popupStack->AddScreen(dlg);
1752}
1753
1754void SmartPLResultViewer::setSQL(const QString& sql)
1755{
1756 m_trackList->Reset();;
1757
1759
1760 if (query.exec(sql))
1761 {
1762 while (query.next())
1763 {
1764 MusicMetadata *mdata = gMusicData->m_all_music->getMetadata(query.value(0).toInt());
1765 if (mdata)
1766 {
1767 InfoMap metadataMap;
1768 mdata->toMap(metadataMap);
1769
1770 auto *item = new MythUIButtonListItem(m_trackList, "", QVariant::fromValue(mdata));
1771 item->SetTextFromMap(metadataMap);
1772 }
1773 }
1774 }
1775
1777}
1778
1779
1780/*
1781---------------------------------------------------------------------
1782*/
1783
1785{
1786 if (!LoadWindowFromXML("music-ui.xml", "orderbydialog", this))
1787 return false;
1788
1789 bool err = false;
1790
1791 UIUtilE::Assign(this, m_fieldList, "fieldlist", &err);
1792 UIUtilE::Assign(this, m_orderSelector, "fieldselector", &err);
1793 UIUtilE::Assign(this, m_addButton, "addbutton", &err);
1794 UIUtilE::Assign(this, m_deleteButton, "deletebutton", &err);
1795 UIUtilE::Assign(this, m_moveUpButton, "moveupbutton", &err);
1796 UIUtilE::Assign(this, m_moveDownButton, "movedownbutton", &err);
1797 UIUtilE::Assign(this, m_ascendingButton, "ascendingbutton", &err);
1798 UIUtilE::Assign(this, m_descendingButton, "descendingbutton", &err);
1799 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1800 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
1801
1802 if (err)
1803 {
1804 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'orderbydialog'");
1805 return false;
1806 }
1807
1816
1818 this, qOverload<MythUIButtonListItem *>(&SmartPLOrderByDialog::orderByChanged));
1821
1823
1825
1827
1828 return true;
1829}
1830
1832{
1833 QString result;
1834 bool bFirst = true;
1835
1836 for (int i = 0; i < m_fieldList->GetCount(); i++)
1837 {
1838 if (bFirst)
1839 {
1840 bFirst = false;
1841 result = m_fieldList->GetItemAt(i)->GetText();
1842 }
1843 else
1844 {
1845 result += ", " + m_fieldList->GetItemAt(i)->GetText();
1846 }
1847 }
1848
1849 return result;
1850}
1851
1852void SmartPLOrderByDialog::setFieldList(const QString &fieldList)
1853{
1854 m_fieldList->Reset();
1855 QStringList list = fieldList.split(",");
1856
1857 for (int x = 0; x < list.count(); x++)
1858 {
1859 auto *item = new MythUIButtonListItem(m_fieldList, list[x].trimmed());
1860 QString state = list[x].contains("(A)") ? "ascending" : "descending";
1861 item->DisplayState(state, "sortstate");
1862 }
1863
1865}
1866
1868{
1869 if (!item)
1870 return;
1871
1872 m_orderSelector->SetValue(item->GetText().left(item->GetText().length() - 4));
1873}
1874
1876{
1878 return;
1879
1881 m_fieldList->GetItemCurrent()->DisplayState("ascending", "sortstate");
1882
1885}
1886
1888{
1890 return;
1891
1893 m_fieldList->GetItemCurrent()->DisplayState("descending", "sortstate");
1894
1897}
1898
1900{
1901 auto *item = new MythUIButtonListItem(m_fieldList, m_orderSelector->GetValue() + " (A)");
1902 item->DisplayState("ascending", "sortstate");
1903
1906}
1907
1909{
1912
1913 if (!m_deleteButton->IsEnabled())
1915 else
1917}
1918
1920{
1922
1923 if (item)
1924 item->MoveUpDown(true);
1925
1927
1928 if (!m_moveUpButton->IsEnabled())
1930 else
1932}
1933
1935{
1937
1938 if (item)
1939 item->MoveUpDown(false);
1940
1942
1945 else
1947}
1948
1950{
1952 Close();
1953}
1954
1956{
1957 bool found = false;
1958 for (int i = 0 ; i < m_fieldList->GetCount() ; ++i)
1959 {
1960 if (m_fieldList->GetItemAt(i)->GetText().startsWith(m_orderSelector->GetValue()))
1961 {
1963 found = true;
1964 }
1965 }
1966
1967 if (found)
1968 {
1969 m_addButton->SetEnabled(false);
1973 m_ascendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(D)") );
1974 m_descendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(A)"));
1975 }
1976 else
1977 {
1978 m_addButton->SetEnabled(true);
1979 m_deleteButton->SetEnabled(false);
1980 m_moveUpButton->SetEnabled(false);
1984 }
1985}
1986
1988{
1990}
1991
1993{
1995 for (const auto & field : SmartPLFields)
1996 new MythUIButtonListItem(m_orderSelector, field.m_name);
1997}
1998
1999/*
2000---------------------------------------------------------------------
2001*/
2002
2004{
2005 if (!LoadWindowFromXML("music-ui.xml", "dateeditordialog", this))
2006 return false;
2007
2008 bool err = false;
2009
2010 UIUtilE::Assign(this, m_fixedRadio, "fixeddatecheck", &err);
2011 UIUtilE::Assign(this, m_daySpin, "dayspinbox", &err);
2012 UIUtilE::Assign(this, m_monthSpin, "monthspinbox", &err);
2013 UIUtilE::Assign(this, m_yearSpin, "yearspinbox", &err);
2014 UIUtilE::Assign(this, m_nowRadio, "nowcheck", &err);
2015 UIUtilE::Assign(this, m_addDaysSpin, "adddaysspinbox", &err);
2016 UIUtilE::Assign(this, m_statusText, "statustext", &err);
2017 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
2018 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
2019
2020 if (err)
2021 {
2022 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'dateeditordialog'");
2023 return false;
2024 }
2025
2026 m_daySpin->SetRange(1, 31, 1);
2027 m_monthSpin->SetRange(1, 12, 1);
2028 m_yearSpin->SetRange(1900, 2099, 1);
2029 m_addDaysSpin->SetRange(-9999, 9999, 1);
2030
2031
2042
2045
2046 valueChanged();
2047
2049
2050 return true;
2051}
2052
2054{
2055 QString sResult;
2056
2058 {
2059 QString day = m_daySpin->GetValue();
2060 if (m_daySpin->GetIntValue() < 10)
2061 day = "0" + day;
2062
2063 QString month = m_monthSpin->GetValue();
2064 if (m_monthSpin->GetIntValue() < 10)
2065 month = "0" + month;
2066
2067 sResult = m_yearSpin->GetValue() + "-" + month + "-" + day;
2068 }
2069 else
2070 {
2071 sResult = m_statusText->GetText();
2072 }
2073
2074 return sResult;
2075}
2076
2078{
2079 if (date.startsWith("$DATE"))
2080 {
2083
2084 if (date.length() > 9)
2085 {
2086 bool bNegative = false;
2087 if (date[6] == '-')
2088 bNegative = true;
2089
2090 if (date.endsWith(" days"))
2091 date = date.left(date.length() - 5);
2092
2093#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2094 int nDays = date.midRef(8).toInt();
2095#else
2096 int nDays = QStringView(date).mid(8).toInt();
2097#endif
2098 if (bNegative)
2099 nDays = -nDays;
2100
2101 m_addDaysSpin->SetValue(nDays);
2102 }
2103 else
2104 {
2106 }
2107
2108 nowCheckToggled(true);
2109 }
2110 else
2111 {
2112#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2113 int nYear = date.midRef(0, 4).toInt();
2114 int nMonth = date.midRef(5, 2).toInt();
2115 int nDay = date.midRef(8, 2).toInt();
2116#else
2117 int nYear = QStringView(date).mid(0, 4).toInt();
2118 int nMonth = QStringView(date).mid(5, 2).toInt();
2119 int nDay = QStringView(date).mid(8, 2).toInt();
2120#endif
2121
2122 m_daySpin->SetValue(nDay);
2123 m_monthSpin->SetValue(nMonth);
2124 m_yearSpin->SetValue(nYear);
2125
2126 fixedCheckToggled(true);
2127 }
2128}
2129
2131{
2132 if (m_updating)
2133 return;
2134
2135 m_updating = true;
2136 m_daySpin->SetEnabled(on);
2139
2142
2143 valueChanged();
2144
2145 m_updating = false;
2146}
2147
2149{
2150 if (m_updating)
2151 return;
2152
2153 m_updating = true;
2154
2156 m_daySpin->SetEnabled(!on);
2157 m_monthSpin->SetEnabled(!on);
2158 m_yearSpin->SetEnabled(!on);
2159
2161
2162 valueChanged();
2163
2164 m_updating = false;
2165}
2166
2168{
2169 QString date = getDate();
2170
2171 emit dateChanged(date);
2172
2173 Close();
2174}
2175
2177{
2178 bool bValidDate = true;
2179
2181 {
2182 QString day = m_daySpin->GetValue();
2183 if (m_daySpin->GetIntValue() < 10)
2184 day = "0" + day;
2185
2186 QString month = m_monthSpin->GetValue();
2187 if (m_monthSpin->GetIntValue() < 10)
2188 month = "0" + month;
2189
2190 QString sDate = m_yearSpin->GetValue() + "-" + month + "-" + day;
2191 QDate date = QDate::fromString(sDate, Qt::ISODate);
2192 if (date.isValid())
2193 {
2194 m_statusText->SetText(date.toString("dddd, d MMMM yyyy"));
2195 }
2196 else
2197 {
2198 bValidDate = false;
2199 m_statusText->SetText(tr("Invalid Date"));
2200 }
2201 }
2202 else if (m_nowRadio->GetBooleanCheckState())
2203 {
2204 QString days;
2205 if (m_addDaysSpin->GetIntValue() > 0)
2206 days = QString("$DATE + %1 days").arg(m_addDaysSpin->GetIntValue());
2207 else if (m_addDaysSpin->GetIntValue() == 0)
2208 days = QString("$DATE");
2209 else
2210 days = QString("$DATE - %1 days").arg(
2211 m_addDaysSpin->GetValue().right(m_addDaysSpin->GetValue().length() - 1));
2212
2213 m_statusText->SetText(days);
2214 }
2215
2216 if (bValidDate)
2217 m_statusText->SetFontState("valid");
2218 else
2219 m_statusText->SetFontState("error");
2220
2221 m_okButton->SetEnabled(bValidDate);
2222}
2223
2224#include "moc_smartplaylist.cpp"
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:40
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:824
QVariant value(int i) const
Definition: mythdbcon.h:205
int size(void) const
Definition: mythdbcon.h:215
bool isActive(void) const
Definition: mythdbcon.h:216
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:904
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
const QSqlDriver * driver(void) const
Definition: mythdbcon.h:221
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:225
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:28
QString GetValue(void) const override
Definition: mythuispinbox.h:33
int GetIntValue(void) const override
Definition: mythuispinbox.h:35
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:45
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:130
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:18
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
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