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 int nDays = sDate.mid(8).toInt();
144 if (bNegative)
145 nDays = -nDays;
146
147 date = date.addDays(nDays);
148 }
149
150 return date.toString(Qt::ISODate);
151 }
152
153 return sDate;
154}
155
156QString getCriteriaSQL(const QString& fieldName, const QString &operatorName,
157 QString value1, QString value2)
158{
159 QString result;
160
161 if (fieldName.isEmpty())
162 return result;
163
164 const SmartPLField *Field = lookupField(fieldName);
165 if (!Field)
166 {
167 return "";
168 }
169
170 result = Field->m_sqlName;
171
172 const SmartPLOperator *Operator = lookupOperator(operatorName);
173 if (!Operator)
174 {
175 return {};
176 }
177
178 // convert boolean and date values
179 if (Field->m_type == ftBoolean)
180 {
181 // compilation field uses 0 = false; 1 = true
182 value1 = (value1 == "Yes") ? "1":"0";
183 value2 = (value2 == "Yes") ? "1":"0";
184 }
185 else if (Field->m_type == ftDate)
186 {
187 value1 = evaluateDateValue(value1);
188 value2 = evaluateDateValue(value2);
189 }
190
191 if (Operator->m_name == "is equal to")
192 {
193 result = result + " = " + formattedFieldValue(value1);
194 }
195 else if (Operator->m_name == "is not equal to")
196 {
197 result = result + " != " + formattedFieldValue(value1);
198 }
199 else if (Operator->m_name == "is greater than")
200 {
201 result = result + " > " + formattedFieldValue(value1);
202 }
203 else if (Operator->m_name == "is less than")
204 {
205 result = result + " < " + formattedFieldValue(value1);
206 }
207 else if (Operator->m_name == "starts with")
208 {
209 result = result + " LIKE " + formattedFieldValue(value1 + QString("%"));
210 }
211 else if (Operator->m_name == "ends with")
212 {
213 result = result + " LIKE " + formattedFieldValue(QString("%") + value1);
214 }
215 else if (Operator->m_name == "contains")
216 {
217 result = result + " LIKE " + formattedFieldValue(QString("%") + value1 + "%");
218 }
219 else if (Operator->m_name == "does not contain")
220 {
221 result = result + " NOT LIKE " + formattedFieldValue(QString("%") + value1 + "%");
222 }
223 else if (Operator->m_name == "is between")
224 {
225 result = result + " BETWEEN " + formattedFieldValue(value1) +
226 " AND " + formattedFieldValue(value2);
227 }
228 else if (Operator->m_name == "is set")
229 {
230 result = result + " IS NOT NULL";
231 }
232 else if (Operator->m_name == "is not set")
233 {
234 result = result + " IS NULL";
235 }
236 else
237 {
238 result.clear();
239 LOG(VB_GENERAL, LOG_ERR,
240 QString("getCriteriaSQL(): invalid operator '%1'")
241 .arg(Operator->m_name));
242 }
243
244 return result;
245}
246
247QString getOrderBySQL(const QString& orderByFields)
248{
249 if (orderByFields.isEmpty())
250 return {};
251
252 QStringList list = orderByFields.split(",");
253 QString fieldName;
254 QString result;
255 QString order;
256 bool bFirst = true;
257
258 for (int x = 0; x < list.count(); x++)
259 {
260 fieldName = list[x].trimmed();
261 const SmartPLField *Field = lookupField(fieldName.left(fieldName.length() - 4));
262 if (Field)
263 {
264 if (fieldName.right(3) == "(D)")
265 order = " DESC";
266 else
267 order = " ASC";
268
269 if (bFirst)
270 {
271 bFirst = false;
272 result = " ORDER BY " + Field->m_sqlName + order;
273 }
274 else
275 {
276 result += ", " + Field->m_sqlName + order;
277 }
278 }
279 }
280
281 return result;
282}
283
284QString getSQLFieldName(const QString &fieldName)
285{
286 const SmartPLField *Field = lookupField(fieldName);
287 if (!Field)
288 {
289 return "";
290 }
291
292 return Field->m_sqlName;
293}
294
295/*
297*/
298
299QString SmartPLCriteriaRow::getSQL(void) const
300{
301 if (m_field.isEmpty())
302 return {};
303
304 QString result;
305
307
308 return result;
309}
310
311// return false on error
312bool SmartPLCriteriaRow::saveToDatabase(int smartPlaylistID) const
313{
314 // save playlistitem to database
315
316 if (m_field.isEmpty())
317 return true;
318
320 query.prepare("INSERT INTO music_smartplaylist_items (smartplaylistid, field, operator,"
321 " value1, value2)"
322 "VALUES (:SMARTPLAYLISTID, :FIELD, :OPERATOR, :VALUE1, :VALUE2);");
323 query.bindValue(":SMARTPLAYLISTID", smartPlaylistID);
324 query.bindValueNoNull(":FIELD", m_field);
325 query.bindValueNoNull(":OPERATOR", m_operator);
326 query.bindValueNoNull(":VALUE1", m_value1);
327 query.bindValueNoNull(":VALUE2", m_value2);
328
329 if (!query.exec())
330 {
331 MythDB::DBError("Inserting new smartplaylist item", query);
332 return false;
333 }
334
335 return true;
336}
337
339{
340 const SmartPLOperator *PLOperator = lookupOperator(m_operator);
341 if (PLOperator)
342 {
343 QString result;
344 if (PLOperator->m_noOfArguments == 0)
345 {
346 result = m_field + " " + m_operator;
347 }
348 else if (PLOperator->m_noOfArguments == 1)
349 {
350 result = m_field + " " + m_operator + " " + m_value1;
351 }
352 else
353 {
354 result = m_field + " " + m_operator + " " + m_value1;
355 result += " " + tr("and") + " " + m_value2;
356 }
357
358 return result;
359 }
360
361 return {};
362}
363
364/*
365---------------------------------------------------------------------
366*/
367
369{
370 while (!m_criteriaRows.empty())
371 {
372 delete m_criteriaRows.back();
373 m_criteriaRows.pop_back();
374 }
375
376 delete m_tempCriteriaRow;
377}
378
379
381{
382 if (!LoadWindowFromXML("music-ui.xml", "smartplaylisteditor", this))
383 return false;
384
385 bool err = false;
386
387 UIUtilE::Assign(this, m_categorySelector, "categoryselector", &err);
388 UIUtilE::Assign(this, m_categoryButton, "categorybutton", &err);
389 UIUtilE::Assign(this, m_titleEdit, "titleedit", &err);
390 UIUtilE::Assign(this, m_matchSelector, "matchselector", &err);
391 UIUtilE::Assign(this, m_criteriaList, "criterialist", &err);
392 UIUtilE::Assign(this, m_orderBySelector, "orderbyselector", &err);
393 UIUtilE::Assign(this, m_orderByButton, "orderbybutton", &err);
394 UIUtilE::Assign(this, m_matchesText, "matchestext", &err);
395 UIUtilE::Assign(this, m_limitSpin, "limitspin", &err);
396
397 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
398 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
399 UIUtilE::Assign(this, m_showResultsButton, "showresultsbutton", &err);
400
401 if (err)
402 {
403 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplaylisteditor'");
404 return false;
405 }
406
408
412
413 for (const auto & field : SmartPLFields)
414 {
415 if (field.m_name == "")
416 new MythUIButtonListItem(m_orderBySelector, field.m_name);
417 else
418 new MythUIButtonListItem(m_orderBySelector, field.m_name + " (A)");
419 }
420
421 m_limitSpin->SetRange(0, 9999, 10);
422
429
431
432 return true;
433}
434
436{
438 return true;
439
440 QStringList actions;
441 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
442
443 for (int i = 0; i < actions.size() && !handled; i++)
444 {
445 const QString& action = actions[i];
446 handled = true;
447
448 if (action == "MENU")
449 {
451 }
452 else if (action == "DELETE" && GetFocusWidget() == m_criteriaList)
453 {
455 }
456 else if (action == "EDIT" && GetFocusWidget() == m_criteriaList)
457 {
458 editCriteria();
459 }
460 else
461 {
462 handled = false;
463 }
464 }
465
466 if (!handled && MythScreenType::keyPressEvent(event))
467 handled = true;
468
469 return handled;
470}
471
473{
474 if (auto *dce = dynamic_cast<DialogCompletionEvent*>(event))
475 {
476 // make sure the user didn't ESCAPE out of the menu
477 if (dce->GetResult() < 0)
478 return;
479
480 QString resultid = dce->GetId();
481 QString resulttext = dce->GetResultText();
482 if (resultid == "categorymenu")
483 {
484 if (resulttext == tr("New Category"))
485 {
486 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
487 QString label = tr("Enter Name Of New Category");
488
489 auto *input = new MythTextInputDialog(popupStack, label);
490
491 connect(input, &MythTextInputDialog::haveResult,
493
494 if (input->Create())
495 popupStack->AddScreen(input);
496 else
497 delete input;
498 }
499 else if (resulttext == tr("Delete Category"))
500 {
502 }
503 else if (resulttext == tr("Rename Category"))
504 {
505 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
506 QString label = tr("Enter New Name For Category: %1").arg(m_categorySelector->GetValue());
507
508 auto *input = new MythTextInputDialog(popupStack, label);
509
510 connect(input, &MythTextInputDialog::haveResult,
512
513 if (input->Create())
514 popupStack->AddScreen(input);
515 else
516 delete input;
517 }
518 }
519 }
520}
521
523{
525 {
526 delete m_tempCriteriaRow;
527 m_tempCriteriaRow = nullptr;
528 }
529
531
532 if (!item)
533 return;
534
535 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
536
537 if (!row)
538 return;
539
540 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
541
542 auto *editor = new CriteriaRowEditor(popupStack, row);
543
544 if (!editor->Create())
545 {
546 delete editor;
547 return;
548 }
549
551
552 popupStack->AddScreen(editor);
553}
554
556{
557 // make sure we have something to delete
559
560 if (!item)
561 return;
562
563 ShowOkPopup(tr("Delete Criteria?"), this, &SmartPlaylistEditor::doDeleteCriteria, true);
564}
565
567{
568 if (doit)
569 {
571 if (!item)
572 return;
573
574 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
575
576 if (!row)
577 return;
578
579 m_criteriaRows.removeAll(row);
581
583 }
584}
585
587{
588 /*
589 SmartPLCriteriaRow *row = new SmartPLCriteriaRow();
590 m_criteriaRows.append(row);
591
592 MythUIButtonListItem *item = new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
593
594 m_criteriaList->SetItemCurrent(item);
595
596 editCriteria();
597 */
598
599 delete m_tempCriteriaRow;
601
602 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
603
604 auto *editor = new CriteriaRowEditor(popupStack, m_tempCriteriaRow);
605
606 if (!editor->Create())
607 {
608 delete editor;
609 return;
610 }
611
613
614 popupStack->AddScreen(editor);
615}
616
618{
619 MythUIButtonListItem *item = nullptr;
620
622 {
623 // this is a new row so add it to the list
625
627 QVariant::fromValue(m_tempCriteriaRow));
628
630
631 m_tempCriteriaRow = nullptr;
632 }
633 else
634 {
635 // update the existing row
637 if (!item)
638 return;
639
640 auto *row = item->GetData().value<SmartPLCriteriaRow*>();
641
642 if (!row)
643 return;
644
645 item->SetText(row->toString());
646 }
647
649}
650
652{
653 QString label = tr("Category Actions");
654
655 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
656
657 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
658
659 if (!menu->Create())
660 {
661 delete menu;
662 return;
663 }
664
665 menu->SetReturnEvent(this, "categorymenu");
666
667 menu->AddButton(tr("New Category"), nullptr);
668 menu->AddButton(tr("Delete Category"), nullptr);
669 menu->AddButton(tr("Rename Category"), nullptr);
670
671 popupStack->AddScreen(menu);
672}
673
675{
676 QString label = tr("Criteria Actions");
677
678 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
679
680 auto *menu = new MythDialogBox(label, popupStack, "actionmenu");
681
682 if (!menu->Create())
683 {
684 delete menu;
685 return;
686 }
687
688 menu->SetReturnEvent(this, "criteriamenu");
689
691
692 if (item)
693 menu->AddButton(tr("Edit Criteria"), &SmartPlaylistEditor::editCriteria);
694
695 menu->AddButton(tr("Add Criteria"), &SmartPlaylistEditor::addCriteria);
696
697 if (item)
698 menu->AddButton(tr("Delete Criteria"), &SmartPlaylistEditor::deleteCriteria);
699
700 popupStack->AddScreen(menu);
701}
702
704{
706}
707
709{
710 QString sql =
711 "SELECT count(*) "
712 "FROM music_songs "
713 "LEFT JOIN music_artists ON "
714 " music_songs.artist_id=music_artists.artist_id "
715 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
716 "LEFT JOIN music_artists AS music_comp_artists ON "
717 " music_albums.artist_id=music_comp_artists.artist_id "
718 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
719
720 sql += getWhereClause();
721
722 m_matchesCount = 0;
723
725 if (!query.exec(sql))
726 MythDB::DBError("SmartPlaylistEditor::updateMatches", query);
727 else if (query.next())
728 m_matchesCount = query.value(0).toInt();
729
730 m_matchesText->SetText(QString::number(m_matchesCount));
731
734 titleChanged();
735}
736
738{
739 // save smartplaylist to database
740
741 QString name = m_titleEdit->GetText();
742 QString category = m_categorySelector->GetValue();
743 QString matchType = (m_matchSelector->GetValue() == tr("All") ? "All" : "Any");
744 QString orderBy = m_orderBySelector->GetValue();
745 QString limit = m_limitSpin->GetValue();
746
747 // lookup categoryid
748 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
749
750 // easier to delete any existing smartplaylist and recreate a new one
751 if (!m_newPlaylist)
753 else
755
757 // insert new smartplaylist
758 query.prepare("INSERT INTO music_smartplaylists (name, categoryid, matchtype, orderby, limitto) "
759 "VALUES (:NAME, :CATEGORYID, :MATCHTYPE, :ORDERBY, :LIMIT);");
760 query.bindValue(":NAME", name);
761 query.bindValue(":CATEGORYID", categoryid);
762 query.bindValue(":MATCHTYPE", matchType);
763 query.bindValue(":ORDERBY", orderBy);
764 query.bindValue(":LIMIT", limit);
765
766 if (!query.exec())
767 {
768 MythDB::DBError("Inserting new playlist", query);
769 return;
770 }
771
772 // get smartplaylistid
773 int ID = -1;
774 query.prepare("SELECT smartplaylistid FROM music_smartplaylists "
775 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
776 query.bindValue(":CATEGORYID", categoryid);
777 query.bindValue(":NAME", name);
778 if (query.exec())
779 {
780 if (query.isActive() && query.size() > 0)
781 {
782 query.first();
783 ID = query.value(0).toInt();
784 }
785 else
786 {
787 LOG(VB_GENERAL, LOG_ERR,
788 QString("Failed to find ID for smartplaylist: %1").arg(name));
789 return;
790 }
791 }
792 else
793 {
794 MythDB::DBError("Getting smartplaylist ID", query);
795 return;
796 }
797
798 // save smartplaylist items
799 for (const auto & row : std::as_const(m_criteriaRows))
800 row->saveToDatabase(ID);
801
802 emit smartPLChanged(category, name);
803
804 Close();
805}
806
807void SmartPlaylistEditor::newSmartPlaylist(const QString& category)
808{
809 m_categorySelector->SetValue(category);
811 m_originalCategory = category;
812 m_originalName.clear();
813
814 m_newPlaylist = true;
815
817}
818
819void SmartPlaylistEditor::editSmartPlaylist(const QString& category, const QString& name)
820{
821 m_originalCategory = category;
822 m_originalName = name;
823 m_newPlaylist = false;
824 loadFromDatabase(category, name);
826}
827
828void SmartPlaylistEditor::loadFromDatabase(const QString& category, const QString& name)
829{
830 // load smartplaylist from database
831 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
832
834 int ID = -1;
835
836 query.prepare("SELECT smartplaylistid, name, categoryid, matchtype, orderby, limitto "
837 "FROM music_smartplaylists WHERE name = :NAME AND categoryid = :CATEGORYID;");
838 query.bindValue(":NAME", name);
839 query.bindValue(":CATEGORYID", categoryid);
840 if (query.exec())
841 {
842 if (query.isActive() && query.size() > 0)
843 {
844 query.first();
845 ID = query.value(0).toInt();
846 m_titleEdit->SetText(name);
847 m_categorySelector->SetValue(category);
848 if (query.value(3).toString() == "All")
849 m_matchSelector->SetValue(tr("All"));
850 else
851 m_matchSelector->SetValue(tr("Any"));
852
853 QString orderBy = query.value(4).toString();
854 if (!m_orderBySelector->Find(orderBy))
855 {
856 // not found so add it to the selector
858 m_orderBySelector->SetValue(orderBy);
859 }
860
861 m_limitSpin->SetValue(query.value(5).toInt());
862 }
863 else
864 {
865 LOG(VB_GENERAL, LOG_ERR,
866 QString("Cannot find smartplaylist: %1").arg(name));
867 return;
868 }
869 }
870 else
871 {
872 MythDB::DBError("Load smartplaylist", query);
873 return;
874 }
875
877
878 query.prepare("SELECT field, operator, value1, value2 "
879 "FROM music_smartplaylist_items WHERE smartplaylistid = :ID "
880 "ORDER BY smartplaylistitemid;");
881 query.bindValue(":ID", ID);
882 if (!query.exec())
883 MythDB::DBError("Load smartplaylist items", query);
884
885 if (query.size() > 0)
886 {
887 while (query.next())
888 {
889 QString Field = query.value(0).toString();
890 QString Operator = query.value(1).toString();
891 QString Value1 = query.value(2).toString();
892 QString Value2 = query.value(3).toString();
893 // load smartplaylist items
894 auto *row = new SmartPLCriteriaRow(Field, Operator, Value1, Value2);
895 m_criteriaRows.append(row);
896
897 new MythUIButtonListItem(m_criteriaList, row->toString(), QVariant::fromValue(row));
898 }
899 }
900 else
901 {
902 LOG(VB_GENERAL, LOG_WARNING,
903 QString("Got no smartplaylistitems for ID: ").arg(ID));
904 }
905}
906
907void SmartPlaylistEditor::newCategory(const QString &category)
908{
909 // insert new smartplaylistcategory
910
912 query.prepare("INSERT INTO music_smartplaylist_categories (name) "
913 "VALUES (:NAME);");
914 query.bindValue(":NAME", category);
915
916 if (!query.exec())
917 {
918 MythDB::DBError("Inserting new smartplaylist category", query);
919 return;
920 }
921
923 m_categorySelector->SetValue(category);
924}
925
926void SmartPlaylistEditor::startDeleteCategory(const QString &category)
927{
928 if (category.isEmpty())
929 return;
930
931//FIXME::
932#if 0
933 if (!MythPopupBox::showOkCancelPopup(GetMythMainWindow(),
934 "Delete Category",
935 tr("Are you sure you want to delete this Category?")
936 + "\n\n\"" + category + "\"\n\n"
937 + tr("It will also delete any Smart Playlists belonging to this category."),
938 false))
939 return;
940
942#endif
945}
946
947void SmartPlaylistEditor::renameCategory(const QString &category)
948{
949 if (m_categorySelector->GetValue() == category)
950 return;
951
952 // change the category
954 query.prepare("UPDATE music_smartplaylist_categories SET name = :NEW_CATEGORY "
955 "WHERE name = :OLD_CATEGORY;");
956 query.bindValue(":OLD_CATEGORY", m_categorySelector->GetValue());
957 query.bindValue(":NEW_CATEGORY", category);
958
959 if (!query.exec())
960 MythDB::DBError("Rename smartplaylist", query);
961
962 if (!m_newPlaylist)
964
966 m_categorySelector->SetValue(category);
967}
968
969QString SmartPlaylistEditor::getSQL(const QString& fields)
970{
971 QString sql;
972 QString whereClause;
973 QString orderByClause;
974 QString limitClause;
975 sql = "SELECT " + fields + " FROM music_songs "
976 "LEFT JOIN music_artists ON music_songs.artist_id=music_artists.artist_id "
977 "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
978 "LEFT JOIN music_artists AS music_comp_artists ON music_albums.artist_id=music_comp_artists.artist_id "
979 "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id ";
980
981 whereClause = getWhereClause();
982 orderByClause = getOrderByClause();
983 if (m_limitSpin->GetIntValue() > 0)
984 limitClause = " LIMIT " + m_limitSpin->GetValue();
985
986 sql = sql + whereClause + orderByClause + limitClause;
987
988 return sql;
989}
990
992{
994}
995
997{
998 if (m_criteriaRows.empty())
999 return {};
1000
1001 bool bFirst = true;
1002 QString sql = "WHERE ";
1003
1004 for (const auto & row : std::as_const(m_criteriaRows))
1005 {
1006 QString criteria = row->getSQL();
1007 if (criteria.isEmpty())
1008 continue;
1009
1010 if (bFirst)
1011 {
1012 sql += criteria;
1013 bFirst = false;
1014 }
1015 else
1016 {
1017 if (m_matchSelector->GetValue() == tr("Any"))
1018 sql += " OR " + criteria;
1019 else
1020 sql += " AND " + criteria;
1021 }
1022 }
1023
1024 return sql;
1025}
1026
1028{
1029 QString sql = getSQL("song_id, music_artists.artist_name, album_name, "
1030 "name, genre, music_songs.year, track");
1031
1033
1034 auto *resultViewer = new SmartPLResultViewer(mainStack);
1035
1036 if (!resultViewer->Create())
1037 {
1038 delete resultViewer;
1039 return;
1040 }
1041
1042 resultViewer->setSQL(sql);
1043
1044 mainStack->AddScreen(resultViewer);
1045}
1046
1048{
1049 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1050
1051 auto *orderByDialog = new SmartPLOrderByDialog(popupStack);
1052
1053 if (!orderByDialog->Create())
1054 {
1055 delete orderByDialog;
1056 return;
1057 }
1058
1059 orderByDialog->setFieldList(m_orderBySelector->GetValue());
1060
1061 connect(orderByDialog, qOverload<QString>(&SmartPLOrderByDialog::orderByChanged),
1063
1064 popupStack->AddScreen(orderByDialog);
1065}
1066
1067void SmartPlaylistEditor::orderByChanged(const QString& orderBy)
1068{
1070 return;
1071
1072 // not found so add it to the selector
1074 m_orderBySelector->SetValue(orderBy);
1075}
1076
1078{
1081
1082 if (query.exec("SELECT name FROM music_smartplaylist_categories ORDER BY name;"))
1083 {
1084 if (query.isActive() && query.size() > 0)
1085 {
1086 while (query.next())
1087 new MythUIButtonListItem(m_categorySelector, query.value(0).toString());
1088 }
1089 else
1090 {
1091 LOG(VB_GENERAL, LOG_ERR,
1092 "Could not find any smartplaylist categories");
1093 }
1094 }
1095 else
1096 {
1097 MythDB::DBError("Load smartplaylist categories", query);
1098 }
1099}
1100
1101// static function to delete a smartplaylist and any associated smartplaylist items
1102bool SmartPlaylistEditor::deleteSmartPlaylist(const QString &category, const QString& name)
1103{
1104 // get categoryid
1105 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1106
1108
1109 // get playlist ID
1110 int ID = -1;
1111 query.prepare("SELECT smartplaylistid FROM music_smartplaylists WHERE name = :NAME "
1112 "AND categoryid = :CATEGORYID;");
1113 query.bindValue(":NAME", name);
1114 query.bindValue(":CATEGORYID", categoryid);
1115 if (query.exec())
1116 {
1117 if (query.isActive() && query.size() > 0)
1118 {
1119 query.first();
1120 ID = query.value(0).toInt();
1121 }
1122 else
1123 {
1124 // not always an error maybe we are trying to delete a playlist
1125 // that does not exist
1126 return true;
1127 }
1128 }
1129 else
1130 {
1131 MythDB::DBError("Delete smartplaylist", query);
1132 return false;
1133 }
1134
1135 //delete smartplaylist items
1136 query.prepare("DELETE FROM music_smartplaylist_items WHERE smartplaylistid = :ID;");
1137 query.bindValue(":ID", ID);
1138 if (!query.exec())
1139 MythDB::DBError("Delete smartplaylist items", query);
1140
1141 //delete smartplaylist
1142 query.prepare("DELETE FROM music_smartplaylists WHERE smartplaylistid = :ID;");
1143 query.bindValue(":ID", ID);
1144 if (!query.exec())
1145 MythDB::DBError("Delete smartplaylist", query);
1146
1147 return true;
1148}
1149
1150// static function to delete all smartplaylists belonging to the given category
1151// will also delete any associated smartplaylist items
1152bool SmartPlaylistEditor::deleteCategory(const QString& category)
1153{
1154 int categoryid = SmartPlaylistEditor::lookupCategoryID(category);
1156
1157 //delete all smartplaylists with the selected category
1158 query.prepare("SELECT name FROM music_smartplaylists "
1159 "WHERE categoryid = :CATEGORYID;");
1160 query.bindValue(":CATEGORYID", categoryid);
1161 if (!query.exec())
1162 {
1163 MythDB::DBError("Delete SmartPlaylist Category", query);
1164 return false;
1165 }
1166
1167 if (query.isActive() && query.size() > 0)
1168 {
1169 while (query.next())
1170 {
1171 SmartPlaylistEditor::deleteSmartPlaylist(category, query.value(0).toString());
1172 }
1173 }
1174
1175 // delete the category
1176 query.prepare("DELETE FROM music_smartplaylist_categories WHERE categoryid = :ID;");
1177 query.bindValue(":ID", categoryid);
1178 if (!query.exec())
1179 MythDB::DBError("Delete smartplaylist category", query);
1180
1181 return true;
1182}
1183
1184// static function to lookup the categoryid given its name
1185int SmartPlaylistEditor::lookupCategoryID(const QString& category)
1186{
1187 int ID = -1;
1189 query.prepare("SELECT categoryid FROM music_smartplaylist_categories "
1190 "WHERE name = :CATEGORY;");
1191 query.bindValue(":CATEGORY", category);
1192
1193 if (query.exec())
1194 {
1195 if (query.isActive() && query.size() > 0)
1196 {
1197 query.first();
1198 ID = query.value(0).toInt();
1199 }
1200 else
1201 {
1202 LOG(VB_GENERAL, LOG_ERR,
1203 QString("Failed to find smart playlist category: %1")
1204 .arg(category));
1205 ID = -1;
1206 }
1207 }
1208 else
1209 {
1210 MythDB::DBError("Getting category ID", query);
1211 ID = -1;
1212 }
1213
1214 return ID;
1215}
1216
1217void SmartPlaylistEditor::getCategoryAndName(QString &category, QString &name)
1218{
1219 category = m_categorySelector->GetValue();
1220 name = m_titleEdit->GetText();
1221}
1222
1223/*
1224---------------------------------------------------------------------
1225*/
1226
1228{
1229 if (!LoadWindowFromXML("music-ui.xml", "criteriaroweditor", this))
1230 return false;
1231
1232 bool err = false;
1233
1234 UIUtilE::Assign(this, m_fieldSelector, "fieldselector", &err);
1235 UIUtilE::Assign(this, m_operatorSelector, "operatorselector", &err);
1236 UIUtilE::Assign(this, m_value1Edit, "value1edit", &err);
1237 UIUtilE::Assign(this, m_value2Edit, "value2edit", &err);
1238 UIUtilE::Assign(this, m_value1Selector, "value1selector", &err);
1239 UIUtilE::Assign(this, m_value2Selector, "value2selector", &err);
1240 UIUtilE::Assign(this, m_value1Spinbox, "value1spinbox", &err);
1241 UIUtilE::Assign(this, m_value2Spinbox, "value2spinbox", &err);
1242 UIUtilE::Assign(this, m_value1Button, "value1button", &err);
1243 UIUtilE::Assign(this, m_value2Button, "value2button", &err);
1244 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1245 UIUtilE::Assign(this, m_saveButton, "savebutton", &err);
1246
1247 if (err)
1248 {
1249 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'criteriaroweditor'");
1250 return false;
1251 }
1252
1253 updateFields();
1255 updateValues();
1256
1259
1264
1267
1270
1272
1273 return true;
1274}
1275
1277{
1278 for (const auto & field : SmartPLFields)
1279 new MythUIButtonListItem(m_fieldSelector, field.m_name);
1280
1282}
1283
1285{
1286 for (const auto & oper : SmartPLOperators)
1287 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1288
1290}
1291
1293{
1295}
1296
1298{
1303
1305 {
1306 // not found so add it to the selector
1309 }
1310
1312 {
1313 // not found so add it to the selector
1316 }
1317}
1318
1320{
1322 if (!Field)
1323 return;
1324
1327
1328 if (Field->m_type == ftNumeric)
1329 {
1332 }
1333 else if (Field->m_type == ftBoolean || Field->m_type == ftDate)
1334 {
1337 }
1338 else // ftString
1339 {
1342 }
1343
1344 // NOLINTNEXTLINE(readability-misleading-indentation)
1345 emit criteriaChanged();
1346
1347 Close();
1348}
1349
1351{
1352 bool enabled = false;
1353
1355
1357
1358 if (Field && Operator)
1359 {
1360 if (Field->m_type == ftNumeric || Field->m_type == ftBoolean)
1361 {
1362 enabled = true;
1363 }
1364 else if (Field->m_type == ftDate)
1365 {
1366 if ((Operator->m_noOfArguments == 0) ||
1367 (Operator->m_noOfArguments == 1 && !m_value1Selector->GetValue().isEmpty()) ||
1368 (Operator->m_noOfArguments == 2 && !m_value1Selector->GetValue().isEmpty()
1369 && !m_value2Selector->GetValue().isEmpty()))
1370 enabled = true;
1371 }
1372 else // ftString
1373 {
1374 if ((Operator->m_noOfArguments == 0) ||
1375 (Operator->m_noOfArguments == 1 && !m_value1Edit->GetText().isEmpty()) ||
1376 (Operator->m_noOfArguments == 2 && !m_value1Edit->GetText().isEmpty()
1377 && !m_value2Edit->GetText().isEmpty()))
1378 enabled = true;
1379 }
1380 }
1381
1382 m_saveButton->SetEnabled(enabled);
1383}
1384
1386{
1388 if (!Field)
1389 return;
1390
1391 if (Field->m_type == ftBoolean)
1392 {
1393 // add yes / no items to combo
1400 }
1401 else if (Field->m_type == ftDate)
1402 {
1403 // add a couple of date values to the combo
1406 new MythUIButtonListItem(m_value1Selector, "$DATE - 30 days");
1407 new MythUIButtonListItem(m_value1Selector, "$DATE - 60 days");
1408
1410 {
1411 // not found so add it to the selector
1414 }
1415
1416
1419 new MythUIButtonListItem(m_value2Selector, "$DATE - 30 days");
1420 new MythUIButtonListItem(m_value2Selector, "$DATE - 60 days");
1421
1423 {
1424 // not found so add it to the selector
1427 }
1428 }
1429
1430 // get list of operators valid for this field type
1431 getOperatorList(Field->m_type);
1432
1434}
1435
1437{
1439 if (!Field)
1440 return;
1441
1443 if (!Operator)
1444 return;
1445
1446 // hide all widgets
1447 m_value1Edit->Hide();
1448 m_value2Edit->Hide();
1455
1456 // show spin edits
1457 if (Field->m_type == ftNumeric)
1458 {
1459 if (Operator->m_noOfArguments >= 1)
1460 {
1462 int currentValue = m_value1Spinbox->GetIntValue();
1463 m_value1Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1464
1465 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1467 }
1468
1469 if (Operator->m_noOfArguments == 2)
1470 {
1472 int currentValue = m_value2Spinbox->GetIntValue();
1473 m_value2Spinbox->SetRange(Field->m_minValue, Field->m_maxValue, 1);
1474
1475 if (currentValue < Field->m_minValue || currentValue > Field->m_maxValue)
1477 }
1478 }
1479 else if (Field->m_type == ftBoolean)
1480 {
1481 // only show value1combo
1483 }
1484 else if (Field->m_type == ftDate)
1485 {
1486 if (Operator->m_noOfArguments >= 1)
1487 {
1490 }
1491
1492 if (Operator->m_noOfArguments == 2)
1493 {
1496 }
1497 }
1498 else // ftString
1499 {
1500 if (Operator->m_noOfArguments >= 1)
1501 {
1502 m_value1Edit->Show();
1504 }
1505
1506 if (Operator->m_noOfArguments == 2)
1507 {
1508 m_value2Edit->Show();
1510 }
1511 }
1512
1514}
1515
1517{
1518 QString currentOperator = m_operatorSelector->GetValue();
1519
1521
1522 for (const auto & oper : SmartPLOperators)
1523 {
1524 // don't add operators that only work with string fields
1525 if (fieldType != ftString && oper.m_stringOnly)
1526 continue;
1527
1528 // don't add operators that only work with boolean fields
1529 if (fieldType == ftBoolean && !oper.m_validForBoolean)
1530 continue;
1531
1532 new MythUIButtonListItem(m_operatorSelector, oper.m_name);
1533 }
1534
1535 // try to set the operatorCombo to the same operator or else the first item
1536 m_operatorSelector->SetValue(currentOperator);
1537}
1538
1540{
1541 QString msg;
1542 QStringList searchList;
1544
1545 if (m_fieldSelector->GetValue() == "Artist")
1546 {
1547 msg = tr("Select an Artist");
1548 searchList = MusicMetadata::fillFieldList("artist");
1549 }
1550 else if (m_fieldSelector->GetValue() == "Comp. Artist")
1551 {
1552 msg = tr("Select a Compilation Artist");
1553 searchList = MusicMetadata::fillFieldList("compilation_artist");
1554 }
1555 else if (m_fieldSelector->GetValue() == "Album")
1556 {
1557 msg = tr("Select an Album");
1558 searchList = MusicMetadata::fillFieldList("album");
1559 }
1560 else if (m_fieldSelector->GetValue() == "Genre")
1561 {
1562 msg = tr("Select a Genre");
1563 searchList = MusicMetadata::fillFieldList("genre");
1564 }
1565 else if (m_fieldSelector->GetValue() == "Title")
1566 {
1567 msg = tr("Select a Title");
1568 searchList = MusicMetadata::fillFieldList("title");
1569 }
1570 else if ((m_fieldSelector->GetValue() == "Last Play") ||
1571 (m_fieldSelector->GetValue() == "Date Imported"))
1572 {
1573 editDate();
1574 return;
1575 }
1576
1577 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1578 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, s);
1579
1580 if (!searchDlg->Create())
1581 {
1582 delete searchDlg;
1583 return;
1584 }
1585
1587
1588 popupStack->AddScreen(searchDlg);
1589}
1590
1591void CriteriaRowEditor::setValue(const QString& value)
1592{
1594 m_value1Edit->SetText(value);
1595 else
1596 m_value2Edit->SetText(value);
1597}
1598
1600{
1601 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1602 auto *dateDlg = new SmartPLDateDialog(popupStack);
1604
1605 if (!dateDlg->Create())
1606 {
1607 delete dateDlg;
1608 return;
1609 }
1610
1611 dateDlg->setDate(date);
1612
1614
1615 popupStack->AddScreen(dateDlg);
1616}
1617
1618void CriteriaRowEditor::setDate(const QString& date)
1619{
1621 {
1623 return;
1624
1625 // not found so add it to the selector
1628 }
1629 else
1630 {
1632 return;
1633
1634 // not found so add it to the selector
1637 }
1638}
1639
1640/*
1641---------------------------------------------------------------------
1642*/
1643
1644
1646{
1647 if (!LoadWindowFromXML("music-ui.xml", "smartplresultviewer", this))
1648 return false;
1649
1650 bool err = false;
1651
1652 UIUtilE::Assign(this, m_trackList, "tracklist", &err);
1653 UIUtilW::Assign(this, m_positionText, "position", &err);
1654
1655 if (err)
1656 {
1657 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'smartplresultviewer'");
1658 return false;
1659 }
1660
1665
1667
1668 return true;
1669}
1670
1672{
1673 if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1674 return true;
1675
1676 QStringList actions;
1677 bool handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
1678
1679 for (int i = 0; i < actions.size() && !handled; i++)
1680 {
1681 const QString& action = actions[i];
1682 handled = true;
1683
1684 if (action == "INFO")
1685 showTrackInfo();
1686 else
1687 handled = false;
1688 }
1689
1690 if (!handled && MythScreenType::keyPressEvent(event))
1691 handled = true;
1692
1693 return handled;
1694}
1695
1697{
1698 if (!item)
1699 return;
1700
1701 if (item->GetImageFilename().isEmpty())
1702 {
1703 auto *mdata = item->GetData().value<MusicMetadata *>();
1704 if (mdata)
1705 {
1706 QString artFile = mdata->getAlbumArtFile();
1707 if (artFile.isEmpty())
1708 item->SetImage("mm_nothumb.png");
1709 else
1710 item->SetImage(mdata->getAlbumArtFile());
1711 }
1712 else
1713 {
1714 item->SetImage("mm_nothumb.png");
1715 }
1716 }
1717}
1718
1720{
1721 if (!item || !m_positionText)
1722 return;
1723
1724 m_positionText->SetText(tr("%1 of %2").arg(m_trackList->IsEmpty() ? 0 : m_trackList->GetCurrentPos() + 1)
1725 .arg(m_trackList->GetCount()));
1726}
1728{
1730 if (!item)
1731 return;
1732
1733 auto *mdata = item->GetData().value<MusicMetadata *>();
1734 if (!mdata)
1735 return;
1736
1737 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1738
1739 auto *dlg = new TrackInfoDialog(popupStack, mdata, "trackinfopopup");
1740
1741 if (!dlg->Create())
1742 {
1743 delete dlg;
1744 return;
1745 }
1746
1747 popupStack->AddScreen(dlg);
1748}
1749
1750void SmartPLResultViewer::setSQL(const QString& sql)
1751{
1752 m_trackList->Reset();;
1753
1755
1756 if (query.exec(sql))
1757 {
1758 while (query.next())
1759 {
1760 MusicMetadata *mdata = gMusicData->m_all_music->getMetadata(query.value(0).toInt());
1761 if (mdata)
1762 {
1763 InfoMap metadataMap;
1764 mdata->toMap(metadataMap);
1765
1766 auto *item = new MythUIButtonListItem(m_trackList, "", QVariant::fromValue(mdata));
1767 item->SetTextFromMap(metadataMap);
1768 }
1769 }
1770 }
1771
1773}
1774
1775
1776/*
1777---------------------------------------------------------------------
1778*/
1779
1781{
1782 if (!LoadWindowFromXML("music-ui.xml", "orderbydialog", this))
1783 return false;
1784
1785 bool err = false;
1786
1787 UIUtilE::Assign(this, m_fieldList, "fieldlist", &err);
1788 UIUtilE::Assign(this, m_orderSelector, "fieldselector", &err);
1789 UIUtilE::Assign(this, m_addButton, "addbutton", &err);
1790 UIUtilE::Assign(this, m_deleteButton, "deletebutton", &err);
1791 UIUtilE::Assign(this, m_moveUpButton, "moveupbutton", &err);
1792 UIUtilE::Assign(this, m_moveDownButton, "movedownbutton", &err);
1793 UIUtilE::Assign(this, m_ascendingButton, "ascendingbutton", &err);
1794 UIUtilE::Assign(this, m_descendingButton, "descendingbutton", &err);
1795 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
1796 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
1797
1798 if (err)
1799 {
1800 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'orderbydialog'");
1801 return false;
1802 }
1803
1812
1814 this, qOverload<MythUIButtonListItem *>(&SmartPLOrderByDialog::orderByChanged));
1817
1819
1821
1823
1824 return true;
1825}
1826
1828{
1829 QString result;
1830 bool bFirst = true;
1831
1832 for (int i = 0; i < m_fieldList->GetCount(); i++)
1833 {
1834 if (bFirst)
1835 {
1836 bFirst = false;
1837 result = m_fieldList->GetItemAt(i)->GetText();
1838 }
1839 else
1840 {
1841 result += ", " + m_fieldList->GetItemAt(i)->GetText();
1842 }
1843 }
1844
1845 return result;
1846}
1847
1848void SmartPLOrderByDialog::setFieldList(const QString &fieldList)
1849{
1850 m_fieldList->Reset();
1851 QStringList list = fieldList.split(",");
1852
1853 for (int x = 0; x < list.count(); x++)
1854 {
1855 auto *item = new MythUIButtonListItem(m_fieldList, list[x].trimmed());
1856 QString state = list[x].contains("(A)") ? "ascending" : "descending";
1857 item->DisplayState(state, "sortstate");
1858 }
1859
1861}
1862
1864{
1865 if (!item)
1866 return;
1867
1868 m_orderSelector->SetValue(item->GetText().left(item->GetText().length() - 4));
1869}
1870
1872{
1874 return;
1875
1877 m_fieldList->GetItemCurrent()->DisplayState("ascending", "sortstate");
1878
1881}
1882
1884{
1886 return;
1887
1889 m_fieldList->GetItemCurrent()->DisplayState("descending", "sortstate");
1890
1893}
1894
1896{
1897 auto *item = new MythUIButtonListItem(m_fieldList, m_orderSelector->GetValue() + " (A)");
1898 item->DisplayState("ascending", "sortstate");
1899
1902}
1903
1905{
1908
1909 if (!m_deleteButton->IsEnabled())
1911 else
1913}
1914
1916{
1918
1919 if (item)
1920 item->MoveUpDown(true);
1921
1923
1924 if (!m_moveUpButton->IsEnabled())
1926 else
1928}
1929
1931{
1933
1934 if (item)
1935 item->MoveUpDown(false);
1936
1938
1941 else
1943}
1944
1946{
1948 Close();
1949}
1950
1952{
1953 bool found = false;
1954 for (int i = 0 ; i < m_fieldList->GetCount() ; ++i)
1955 {
1956 if (m_fieldList->GetItemAt(i)->GetText().startsWith(m_orderSelector->GetValue()))
1957 {
1959 found = true;
1960 }
1961 }
1962
1963 if (found)
1964 {
1965 m_addButton->SetEnabled(false);
1969 m_ascendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(D)") );
1970 m_descendingButton->SetEnabled((m_fieldList->GetValue().right(3) == "(A)"));
1971 }
1972 else
1973 {
1974 m_addButton->SetEnabled(true);
1975 m_deleteButton->SetEnabled(false);
1976 m_moveUpButton->SetEnabled(false);
1980 }
1981}
1982
1984{
1986}
1987
1989{
1991 for (const auto & field : SmartPLFields)
1992 new MythUIButtonListItem(m_orderSelector, field.m_name);
1993}
1994
1995/*
1996---------------------------------------------------------------------
1997*/
1998
2000{
2001 if (!LoadWindowFromXML("music-ui.xml", "dateeditordialog", this))
2002 return false;
2003
2004 bool err = false;
2005
2006 UIUtilE::Assign(this, m_fixedRadio, "fixeddatecheck", &err);
2007 UIUtilE::Assign(this, m_daySpin, "dayspinbox", &err);
2008 UIUtilE::Assign(this, m_monthSpin, "monthspinbox", &err);
2009 UIUtilE::Assign(this, m_yearSpin, "yearspinbox", &err);
2010 UIUtilE::Assign(this, m_nowRadio, "nowcheck", &err);
2011 UIUtilE::Assign(this, m_addDaysSpin, "adddaysspinbox", &err);
2012 UIUtilE::Assign(this, m_statusText, "statustext", &err);
2013 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
2014 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
2015
2016 if (err)
2017 {
2018 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'dateeditordialog'");
2019 return false;
2020 }
2021
2022 m_daySpin->SetRange(1, 31, 1);
2023 m_monthSpin->SetRange(1, 12, 1);
2024 m_yearSpin->SetRange(1900, 2099, 1);
2025 m_addDaysSpin->SetRange(-9999, 9999, 1);
2026
2027
2038
2041
2042 valueChanged();
2043
2045
2046 return true;
2047}
2048
2050{
2051 QString sResult;
2052
2054 {
2055 QString day = m_daySpin->GetValue();
2056 if (m_daySpin->GetIntValue() < 10)
2057 day = "0" + day;
2058
2059 QString month = m_monthSpin->GetValue();
2060 if (m_monthSpin->GetIntValue() < 10)
2061 month = "0" + month;
2062
2063 sResult = m_yearSpin->GetValue() + "-" + month + "-" + day;
2064 }
2065 else
2066 {
2067 sResult = m_statusText->GetText();
2068 }
2069
2070 return sResult;
2071}
2072
2074{
2075 if (date.startsWith("$DATE"))
2076 {
2079
2080 if (date.length() > 9)
2081 {
2082 bool bNegative = false;
2083 if (date[6] == '-')
2084 bNegative = true;
2085
2086 if (date.endsWith(" days"))
2087 date = date.left(date.length() - 5);
2088
2089 int nDays = date.mid(8).toInt();
2090 if (bNegative)
2091 nDays = -nDays;
2092
2093 m_addDaysSpin->SetValue(nDays);
2094 }
2095 else
2096 {
2098 }
2099
2100 nowCheckToggled(true);
2101 }
2102 else
2103 {
2104 int nYear = date.mid(0, 4).toInt();
2105 int nMonth = date.mid(5, 2).toInt();
2106 int nDay = date.mid(8, 2).toInt();
2107
2108 m_daySpin->SetValue(nDay);
2109 m_monthSpin->SetValue(nMonth);
2110 m_yearSpin->SetValue(nYear);
2111
2112 fixedCheckToggled(true);
2113 }
2114}
2115
2117{
2118 if (m_updating)
2119 return;
2120
2121 m_updating = true;
2122 m_daySpin->SetEnabled(on);
2125
2128
2129 valueChanged();
2130
2131 m_updating = false;
2132}
2133
2135{
2136 if (m_updating)
2137 return;
2138
2139 m_updating = true;
2140
2142 m_daySpin->SetEnabled(!on);
2143 m_monthSpin->SetEnabled(!on);
2144 m_yearSpin->SetEnabled(!on);
2145
2147
2148 valueChanged();
2149
2150 m_updating = false;
2151}
2152
2154{
2155 QString date = getDate();
2156
2157 emit dateChanged(date);
2158
2159 Close();
2160}
2161
2163{
2164 bool bValidDate = true;
2165
2167 {
2168 QString day = m_daySpin->GetValue();
2169 if (m_daySpin->GetIntValue() < 10)
2170 day = "0" + day;
2171
2172 QString month = m_monthSpin->GetValue();
2173 if (m_monthSpin->GetIntValue() < 10)
2174 month = "0" + month;
2175
2176 QString sDate = m_yearSpin->GetValue() + "-" + month + "-" + day;
2177 QDate date = QDate::fromString(sDate, Qt::ISODate);
2178 if (date.isValid())
2179 {
2180 m_statusText->SetText(date.toString("dddd, d MMMM yyyy"));
2181 }
2182 else
2183 {
2184 bValidDate = false;
2185 m_statusText->SetText(tr("Invalid Date"));
2186 }
2187 }
2188 else if (m_nowRadio->GetBooleanCheckState())
2189 {
2190 QString days;
2191 if (m_addDaysSpin->GetIntValue() > 0)
2192 days = QString("$DATE + %1 days").arg(m_addDaysSpin->GetIntValue());
2193 else if (m_addDaysSpin->GetIntValue() == 0)
2194 days = QString("$DATE");
2195 else
2196 days = QString("$DATE - %1 days").arg(
2197 m_addDaysSpin->GetValue().right(m_addDaysSpin->GetValue().length() - 1));
2198
2199 m_statusText->SetText(days);
2200 }
2201
2202 if (bValidDate)
2203 m_statusText->SetFontState("valid");
2204 else
2205 m_statusText->SetFontState("error");
2206
2207 m_okButton->SetEnabled(bValidDate);
2208}
2209
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:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:823
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:903
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
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: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: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
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