MythTV master
proglist.cpp
Go to the documentation of this file.
1// C/C++
2#include <algorithm>
3#include <deque> // for _Deque_iterator, operator-, etc
4#include <functional>
5#include <iterator> // for reverse_iterator
6#include <ranges>
7#include <utility>
8
9// Qt
10#include <QCoreApplication>
11#include <QLocale>
12
13// MythTV
16#include "libmythbase/mythdb.h"
24#include "libmythtv/tv_actions.h" // for ACTION_CHANNELSEARCH
25#include "libmythtv/tv_play.h"
30
31// MythFrontend
32#include "proglist.h"
33
34#define LOC QString("ProgLister: ")
35#define LOC_WARN QString("ProgLister, Warning: ")
36#define LOC_ERR QString("ProgLister, Error: ")
37
38void *ProgLister::RunProgramList(void *player, ProgListType pltype,
39 const QString & extraArg)
40{
42 auto *vsb = new ProgLister(mainStack, static_cast<TV*>(player),
43 pltype, extraArg);
44
45 if (vsb->Create())
46 mainStack->AddScreen(vsb, (player == nullptr));
47 else
48 delete vsb;
49
50 return nullptr;
51}
52
54 QString view, QString extraArg,
55 QDateTime selectedTime) :
56 ScheduleCommon(parent, "ProgLister"),
57 m_type(pltype),
58 m_extraArg(std::move(extraArg)),
59 m_startTime(MythDate::current()),
60 m_searchTime(m_startTime),
61 m_selectedTime(std::move(selectedTime)),
62 m_channelOrdering(gCoreContext->GetSetting("ChannelOrdering", "channum")),
63 m_view(std::move(view))
64{
65 if (pltype == plMovies)
66 {
68 query.prepare("SELECT COUNT(*) FROM program WHERE stars > 0");
69
70 if (query.exec() && query.next())
71 {
72 if (query.value(0).toInt() == 0) // No ratings in database
73 {
74 m_curView = 0; // Show All
75 m_allowViewDialog = false;
76 }
77 }
78 }
79
80 switch (pltype)
81 {
85 case plPowerSearch:
86 case plSQLSearch:
88 default: m_searchType = kNoSearch; break;
89 }
90}
91
92// From tv_play
94 ProgListType pltype, const QString & extraArg) :
95 ScheduleCommon(parent, "ProgLister"),
96 m_type(pltype),
97 m_extraArg(extraArg),
98 m_startTime(MythDate::current()),
99 m_searchTime(m_startTime),
100 m_channelOrdering(gCoreContext->GetSetting("ChannelOrdering", "channum")),
101 m_player(player)
102{
103 if (m_player)
104 m_player->IncrRef();
105
106 switch (pltype)
107 {
111 case plPowerSearch:
112 case plSQLSearch:
114 default: m_searchType = kNoSearch; break;
115 }
116
117 m_view = extraArg;
118 m_viewList.push_back(extraArg);
119 m_viewTextList.push_back(extraArg);
120 m_curView = m_viewList.size() - 1;
121}
122
123// previously recorded ctor
125 MythScreenStack *parent, uint recid, QString title) :
126 ScheduleCommon(parent, "PreviousList"),
127 m_type(plPreviouslyRecorded),
128 m_recid(recid),
129 m_title(std::move(title)),
130 m_startTime(MythDate::current()),
131 m_searchTime(m_startTime),
132 m_channelOrdering(gCoreContext->GetSetting("ChannelOrdering", "channum")),
133 m_view("reverse time"),
134 m_reverseSort(true)
135{
136}
137
139{
143
144 // if we have a player, we need to tell we are done
145 if (m_player)
146 {
147 emit m_player->RequestEmbedding(false);
148 m_player->DecrRef();
149 }
150}
151
153{
154 // don't fade the screen if we are returning to the player
155 if (m_player)
156 GetScreenStack()->PopScreen(this, false);
157 else
158 GetScreenStack()->PopScreen(this, true);
159}
160
162{
163 if (!LoadWindowFromXML("schedule-ui.xml", "programlist", this))
164 return false;
165
166 bool err = false;
167 UIUtilW::Assign(this, m_curviewText, "curview", &err);
168 UIUtilE::Assign(this, m_progList, "proglist", &err);
169 UIUtilW::Assign(this, m_schedText, "sched", &err);
170 UIUtilW::Assign(this, m_messageText, "msg", &err);
171 UIUtilW::Assign(this, m_positionText, "position", &err);
172 UIUtilW::Assign(this, m_groupByText, "groupby");
173
174 if (err)
175 {
176 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'programlist'");
177 return false;
178 }
179
182
185
188
190 {
193 }
194 else
195 {
197 this, qOverload<MythUIButtonListItem*>(&ProgLister::EditRecording));
198 }
199
200 m_progList->SetLCDTitles(tr("Program List"), "title|channel|shortstarttimedate");
201 m_progList->SetSearchFields("titlesubtitle");
202
204
205 QString value;
206 switch (m_type)
207 {
208 case plTitle: value = tr("Program Listings"); break;
209 case plNewListings: value = tr("New Title Search"); break;
210 case plTitleSearch: value = tr("Title Search"); break;
211 case plKeywordSearch: value = tr("Keyword Search"); break;
212 case plPeopleSearch: value = tr("People Search"); break;
213 case plStoredSearch: value = tr("Stored Search"); break;
214 case plPowerSearch:
215 case plSQLSearch: value = tr("Power Search"); break;
216 case plRecordid: value = tr("Rule Search"); break;
217 case plCategory: value = tr("Category Search"); break;
218 case plChannel: value = tr("Channel Search"); break;
219 case plMovies: value = tr("Movie Search"); break;
220 case plTime: value = tr("Time Search"); break;
221 case plPreviouslyRecorded: value = tr("Previously Recorded"); break;
222 default: value = tr("Unknown Search"); break;
223 }
224
225 if (m_schedText)
226 m_schedText->SetText(value);
227
229
231
232 if (m_player)
233 emit m_player->RequestEmbedding(true);
234
235 return true;
236}
237
239{
240 if (m_viewList.isEmpty() || m_curView < 0)
242
243 FillItemList(false, false);
244
245 auto *slce = new ScreenLoadCompletionEvent(objectName());
246 QCoreApplication::postEvent(this, slce);
247}
248
250{
251 if (!m_allowEvents)
252 return true;
253
255 {
256 m_allowEvents = true;
257 return true;
258 }
259
260 m_allowEvents = false;
261
262 QStringList actions;
263 bool handled = GetMythMainWindow()->TranslateKeyPress(
264 "TV Frontend", e, actions);
265
266 bool needUpdate = false;
267 for (uint i = 0; i < uint(actions.size()) && !handled; ++i)
268 {
269 const QString& action = actions[i];
270 handled = true;
271
272 if (action == "PREVVIEW") {
274 } else if (action == "NEXTVIEW") {
276 } else if (action == "CUSTOMEDIT") {
277 EditCustom();
278 } else if (action == "EDIT") {
280 } else if (action == "DELETE") {
282 } else if (action == "UPCOMING" && m_type != plTitle) {
283 ShowUpcoming();
284 } else if (action == "PREVRECORDED" && m_type != plPreviouslyRecorded) {
285 ShowPrevious();
286 } else if (action == "DETAILS" || action == "INFO") {
287 ShowDetails();
288 } else if (action == "GUIDE") {
289 ShowGuide();
290 } else if (action == ACTION_CHANNELSEARCH && m_type != plChannel) {
292 } else if (action == "TOGGLERECORD") {
293 QuickRecord();
294 }
295 else if (action == "1")
296 {
297 if (m_titleSort)
298 {
299 m_titleSort = false;
301 }
302 else
303 {
305 }
306 needUpdate = true;
307 }
308 else if (action == "2")
309 {
310 if (!m_titleSort)
311 {
312 m_titleSort = true;
313 m_reverseSort = false;
314 }
315 else
316 {
318 }
319 needUpdate = true;
320 }
321 else
322 {
323 handled = false;
324 }
325 }
326
327 if (!handled && MythScreenType::keyPressEvent(e))
328 handled = true;
329
330 if (needUpdate)
332
333 m_allowEvents = true;
334
335 return handled;
336}
337
339{
340 auto *sortGroupMenu = new MythMenu(tr("Sort/Group Options"), this,
341 "sortgroupmenu");
342 sortGroupMenu->AddItem(tr("Reverse Sort Order"));
343 sortGroupMenu->AddItem(tr("Sort By Title"));
344 sortGroupMenu->AddItem(tr("Sort By Time"));
345
346 auto *menu = new MythMenu(tr("Options"), this, "menu");
347
349 {
350 menu->AddItem(tr("Choose Search Phrase..."), &ProgLister::ShowChooseViewMenu);
351 }
352
353 if (m_type != plChannel
354 && m_type != plTime
356 {
357 AddGroupMenuItems(sortGroupMenu);
358 }
359
360 menu->AddItem(tr("Sort/Group"), nullptr, sortGroupMenu);
361
363 menu->AddItem(tr("Record"), &ProgLister::QuickRecord);
364
365 menu->AddItem(tr("Edit Schedule"), qOverload<>(&ProgLister::EditScheduled));
366 menu->AddItem(tr("Program Details"), &ProgLister::ShowDetails);
367 menu->AddItem(tr("Program Guide"), &ProgLister::ShowGuide);
368 if (m_type != plChannel)
369 menu->AddItem(tr("Channel Search"), &ProgLister::ShowChannelSearch);
370 if (m_type != plTitle)
371 menu->AddItem(tr("Upcoming"), qOverload<>(&ProgLister::ShowUpcoming));
373 menu->AddItem(tr("Previously Recorded"), qOverload<>(&ProgLister::ShowPrevious));
374 menu->AddItem(tr("Custom Edit"), &ProgLister::EditCustom);
375
378 {
379 if (pi && pi->GetRecordingRuleID())
380 menu->AddItem(tr("Delete Rule"), &ProgLister::ShowDeleteRuleMenu);
381 }
382 else
383 {
384 menu->AddItem(
385 tr("Delete Episode"), &ProgLister::ShowDeleteOldEpisodeMenu);
386 }
387
388 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
389 auto *menuPopup = new MythDialogBox(menu, popupStack, "menuPopup");
390
391 if (!menuPopup->Create())
392 {
393 delete menuPopup;
394 return;
395 }
396
397 popupStack->AddScreen(menuPopup);
398}
399
401{
402 if (m_type == plTime && !m_viewList.empty() && !m_viewTextList.empty())
403 {
404 m_searchTime = m_searchTime.addSecs(-3600);
405 m_curView = 0;
410 return;
411 }
412
413 if (m_viewList.size() <= 1)
414 return;
415
416 m_curView--;
417 if (m_curView < 0)
418 m_curView = m_viewList.size() - 1;
419
421}
422
424{
425 if (m_type == plTime && !m_viewList.empty() && !m_viewTextList.empty())
426 {
427 m_searchTime = m_searchTime.addSecs(3600);
428 m_curView = 0;
433
434 return;
435 }
436
437 if (m_viewList.size() <= 1)
438 return;
439
440 m_curView++;
441 if (m_curView >= m_viewList.size())
442 m_curView = 0;
443
445}
446
447void ProgLister::UpdateKeywordInDB(const QString &text, const QString &oldValue)
448{
449 int oldview = m_viewList.indexOf(oldValue);
450 int newview = m_viewList.indexOf(text);
451
452 if (newview >= 0 && newview == oldview)
453 return;
454
455 if (oldview >= 0)
456 {
457 QString qphrase = m_viewList[oldview];
458
460 query.prepare("DELETE FROM keyword "
461 "WHERE phrase = :PHRASE AND searchtype = :TYPE;");
462 query.bindValue(":PHRASE", qphrase);
463 query.bindValue(":TYPE", m_searchType);
464 if (!query.exec())
465 {
467 "ProgLister::updateKeywordInDB -- delete", query);
468 }
469 m_viewList.removeAll(qphrase);
470 m_viewTextList.removeAll(qphrase);
471 }
472
473 if (newview < 0)
474 {
475 const QString& qphrase = text;
476
478 query.prepare("REPLACE INTO keyword (phrase, searchtype)"
479 "VALUES(:PHRASE, :TYPE );");
480 query.bindValue(":PHRASE", qphrase);
481 query.bindValue(":TYPE", m_searchType);
482 if (!query.exec())
483 {
485 "ProgLister::updateKeywordInDB -- replace", query);
486 }
487 m_viewList.push_back(qphrase);
488 m_viewTextList.push_back(qphrase);
489 }
490}
491
493{
494 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
495 MythScreenType *screen = nullptr;
496
497 switch (m_type)
498 {
499 case plChannel:
500 case plCategory:
501 case plMovies:
502 case plNewListings:
503 case plStoredSearch:
504 {
505 if (m_viewList.empty())
506 return;
507
508 QString msg;
509 switch (m_type)
510 {
511 case plMovies: msg = tr("Select Rating"); break;
512 case plChannel: msg = tr("Select Channel"); break;
513 case plCategory: msg = tr("Select Category"); break;
514 case plNewListings: msg = tr("Select List"); break;
515 case plStoredSearch: msg = QString("%1\n%2")
516 .arg(tr("Select a search stored from"),
517 tr("Custom Record")); break;
518 default: // silence warning
519 break;
520 }
521
522 auto *dialog = new MythUISearchDialog(
523 popupStack, msg, m_viewTextList, true, "");
524 if (!dialog)
525 return;
526 connect(dialog, &MythUISearchDialog::haveResult,
528 screen = dialog;
529 break;
530 }
531 case plTitleSearch:
532 case plKeywordSearch:
533 case plPeopleSearch:
534 {
535 auto *dialog = new PhrasePopup(
536 popupStack, this, m_searchType, m_viewTextList,
537 (m_curView >= 0) ? m_viewList[m_curView] : QString());
538 if (!dialog)
539 return;
540 connect(dialog, &PhrasePopup::haveResult,
542 screen = dialog;
543 break;
544 }
545 case plPowerSearch:
546 {
547 auto *dialog = new PowerSearchPopup(
548 popupStack, this, m_searchType, m_viewTextList,
549 (m_curView >= 0) ? m_viewList[m_curView] : QString());
550 if (!dialog)
551 return;
552 connect(dialog, &PowerSearchPopup::haveResult,
554 screen = dialog;
555 break;
556 }
557 case plTime:
558 {
559 QString message = tr("Start search from date and time");
560 int flags = (MythTimeInputDialog::kDay |
563 auto *dialog = new MythTimeInputDialog(popupStack, message, flags);
564 if (!dialog)
565 return;
566 connect(dialog, &MythTimeInputDialog::haveResult,
568 screen = dialog;
569 break;
570 }
571 case plRecordid:
573 case plUnknown:
574 case plTitle:
575 case plSQLSearch:
576 break;
577 }
578
579 if (!screen)
580 {
581 LOG(VB_GENERAL, LOG_WARNING, LOC + "No menu");
582 return;
583 }
584
585 if (!screen->Create())
586 {
587 delete screen;
588 return;
589 }
590
591 popupStack->AddScreen(screen);
592}
593
594void ProgLister::SetViewFromTime(QDateTime searchTime)
595{
596 if (m_viewList.empty() || m_viewTextList.empty())
597 return;
598
599 m_searchTime = std::move(searchTime);
600 m_curView = 0;
604
606}
607
608void ProgLister::SetViewFromList(const QString& item)
609{
610 m_curView = m_viewTextList.indexOf(item);
611 if (m_curView >= 0)
613}
614
616 const QString &qphrase, QString &output, MSqlBindings &bindings)
617{
618 output.clear();
619
620 QStringList field = qphrase.split(':');
621 if (field.size() != 6)
622 {
623 LOG(VB_GENERAL, LOG_ERR, LOC + "Power search should have 6 fields," +
624 QString("\n\t\t\tnot %1 (%2)") .arg(field.size()).arg(qphrase));
625 return false;
626 };
627
628 static const std::array<QString,6> kBindingList
629 {
630 ":POWERTITLE",
631 ":POWERSUB",
632 ":POWERDESC",
633 ":POWERCATTYPE",
634 ":POWERGENRE",
635 ":POWERCALLSIGN",
636 };
637
638 static const std::array<QString,6> kOutputList
639 {
640 "program.title LIKE :POWERTITLE ",
641 "program.subtitle LIKE :POWERSUB ",
642 "program.description LIKE :POWERDESC ",
643 "program.category_type = :POWERCATTYPE ",
644 "programgenres.genre = :POWERGENRE ",
645 "channel.callsign = :POWERCALLSIGN ",
646 };
647
648 for (uint i = 0; i < (uint) field.size(); i++)
649 {
650 if (field[i].isEmpty())
651 continue;
652
653 if (!output.isEmpty())
654 output += "\nAND ";
655
656 output += kOutputList[i];
657 bindings[kBindingList[i]] =
658 (!kOutputList[i].contains("=")) ?
659 QString('%') + field[i] + QString('%') : field[i];
660 }
661
662 return output.contains("programgenres");
663}
664
666{
667 int pos = m_progList->GetCurrentPos();
668 if (pos >= 0 && pos < (int) m_itemList.size())
669 return m_itemList[pos];
670 return nullptr;
671}
672
674{
677 else
679}
680
682{
684
685 if (!pi || !pi->GetRecordingRuleID())
686 return;
687
688 auto *record = new RecordingRule();
689 if (!record->LoadByProgram(pi))
690 {
691 delete record;
692 return;
693 }
694
695 QString message = tr("Delete '%1' %2 rule?")
696 .arg(record->m_title, toString(pi->GetRecordingRuleType()));
697
698 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
699
700 auto *okPopup = new MythConfirmationDialog(
701 popupStack, message, true);
702
703 okPopup->SetReturnEvent(this, "deleterule");
704 okPopup->SetData(QVariant::fromValue(record));
705
706 if (okPopup->Create())
707 popupStack->AddScreen(okPopup);
708 else
709 delete okPopup;
710}
711
713{
715
716 if (!pi)
717 return;
718
719 QString message = tr("Delete this episode of '%1'?").arg(pi->GetTitle());
720
721 ShowOkPopup(message, this, &ProgLister::DeleteOldEpisode, true);
722}
723
725{
727 if (!ok || !pi)
728 return;
729
731 query.prepare(
732 "DELETE FROM oldrecorded "
733 "WHERE chanid = :CHANID AND "
734 " starttime = :STARTTIME");
735 query.bindValue(":CHANID", pi->GetChanID());
736 query.bindValue(":STARTTIME", pi->GetScheduledStartTime());
737
738 if (!query.exec())
739 MythDB::DBError("ProgLister::DeleteOldEpisode", query);
740
741 ScheduledRecording::RescheduleCheck(*pi, "DeleteOldEpisode");
742 FillItemList(true);
743}
744
746{
748
749 if (!pi)
750 return;
751
752 QString message = tr("Delete all episodes of '%1'?").arg(pi->GetTitle());
753
754 ShowOkPopup(message, this, &ProgLister::DeleteOldSeries, true);
755}
756
758{
760 if (!ok || !pi)
761 return;
762
764 query.prepare("DELETE FROM oldrecorded "
765 "WHERE title = :TITLE AND future = 0");
766 query.bindValue(":TITLE", pi->GetTitle());
767 if (!query.exec())
768 MythDB::DBError("ProgLister::DeleteOldSeries -- delete", query);
769
770 // Set the programid to the special value of "**any**" which the
771 // scheduler recognizes to mean the entire series was deleted.
772 RecordingInfo tempri(*pi);
773 tempri.SetProgramID("**any**");
774 ScheduledRecording::RescheduleCheck(tempri, "DeleteOldSeries");
775 FillItemList(true);
776}
777
779{
781
782 if (!pi)
783 return;
784
785 QString message = pi->toString(ProgramInfo::kTitleSubtitle, " - ");
786
787 if (!pi->GetDescription().isEmpty())
788 message += "\n\n" + pi->GetDescription();
789
790 message += "\n\n\n" + tr("NOTE: removing items from this list will not "
791 "delete any recordings.");
792
793 QString title = tr("Previously Recorded");
794
795 auto *menu = new MythMenu(title, message, this, "deletemenu");
796 if (pi->IsDuplicate())
797 menu->AddItem(tr("Allow this episode to re-record"));
798 else
799 menu->AddItem(tr("Never record this episode"));
800 menu->AddItem(tr("Remove this episode from the list"));
801 menu->AddItem(tr("Remove all episodes for this title"));
802 menu->AddItem(tr("Cancel"));
803
805 auto *menuPopup = new MythDialogBox(menu, mainStack, "deletepopup", true);
806
807 if (menuPopup->Create())
808 mainStack->AddScreen(menuPopup);
809 else
810 delete menuPopup;
811}
812
813void ProgLister::FillViewList(const QString &view)
814{
815 m_viewList.clear();
816 m_viewTextList.clear();
817
818 if (m_type == plChannel) // list by channel
819 {
821 0, true, "channum, chanid");
823
824 for (auto & channel : channels)
825 {
826 QString chantext = channel.GetFormatted(ChannelInfo::kChannelShort);
827
828 m_viewList.push_back(QString::number(channel.m_chanId));
829 m_viewTextList.push_back(chantext);
830 }
831
832 if (!view.isEmpty())
833 m_curView = m_viewList.indexOf(view);
834 }
835 else if (m_type == plCategory) // list by category
836 {
837 QDateTime query_starttime = m_startTime.addSecs(50 -
838 m_startTime.time().second());
840 query.prepare("SELECT g1.genre, g2.genre "
841 "FROM program "
842 "JOIN programgenres g1 ON "
843 " program.chanid = g1.chanid AND "
844 " program.starttime = g1.starttime "
845 "LEFT JOIN programgenres g2 ON "
846 " g1.chanid = g2.chanid AND "
847 " g1.starttime = g2.starttime "
848 "WHERE program.endtime > :PGILSTART "
849 "GROUP BY g1.genre, g2.genre;");
850 query.bindValue(":PGILSTART", query_starttime);
851
852 m_useGenres = false;
853
854 if (query.exec())
855 {
856 QString lastGenre1;
857
858 while (query.next())
859 {
860 m_useGenres = true;
861
862 QString genre1 = query.value(0).toString();
863 if (genre1.isEmpty())
864 continue;
865
866 if (genre1 != lastGenre1)
867 {
868 m_viewList.push_back(genre1);
869 m_viewTextList.push_back(genre1);
870 lastGenre1 = genre1;
871 }
872
873 QString genre2 = query.value(1).toString();
874 if (genre2.isEmpty() || genre2 == genre1)
875 continue;
876
877 m_viewList.push_back(genre1 + ":/:" + genre2);
878 m_viewTextList.push_back(" " + genre1 + " / " + genre2);
879 }
880 }
881
882 if (!m_useGenres)
883 {
884 query.prepare("SELECT category "
885 "FROM program "
886 "WHERE program.endtime > :PGILSTART "
887 "GROUP BY category");
888 query.bindValue(":PGILSTART", query_starttime);
889
890 if (query.exec())
891 {
892 while (query.next())
893 {
894 QString category = query.value(0).toString();
895 if (category.isEmpty())
896 continue;
897 category = query.value(0).toString();
898 m_viewList.push_back(category);
899 m_viewTextList.push_back(category);
900 }
901 }
902 }
903
904 if (!view.isEmpty())
905 m_curView = m_viewList.indexOf(view);
906 }
907 else if (m_type == plTitleSearch || m_type == plKeywordSearch ||
909 {
911 query.prepare("SELECT phrase FROM keyword "
912 "WHERE searchtype = :SEARCHTYPE;");
913 query.bindValue(":SEARCHTYPE", m_searchType);
914
915 if (query.exec())
916 {
917 while (query.next())
918 {
919 /* The keyword.phrase column uses utf8_bin collation, so
920 * Qt uses QString::fromAscii() for toString(). Explicitly
921 * convert the value using QString::fromUtf8() to prevent
922 * corruption. */
923 QString phrase = QString::fromUtf8(query.value(0)
924 .toByteArray().constData());
925 if (phrase.isEmpty())
926 continue;
927 m_viewList.push_back(phrase);
928 m_viewTextList.push_back(phrase);
929 }
930 }
931
932 if (!view.isEmpty())
933 {
934 m_curView = m_viewList.indexOf(view);
935
936 if (m_curView < 0)
937 {
938 const QString& qphrase = view;
939
941 query2.prepare("REPLACE INTO keyword (phrase, searchtype)"
942 "VALUES(:VIEW, :SEARCHTYPE );");
943 query2.bindValue(":VIEW", qphrase);
944 query2.bindValue(":SEARCHTYPE", m_searchType);
945 if (!query2.exec())
946 MythDB::DBError("ProgLister::FillViewList -- "
947 "replace keyword", query2);
948
949 m_viewList.push_back(qphrase);
950 m_viewTextList.push_back(qphrase);
951
952 m_curView = m_viewList.size() - 1;
953 }
954 }
955 else
956 {
957 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Failed to load viewList"));
958 m_curView = -1;
959 }
960 }
961 else if (m_type == plTitle)
962 {
963 if (!view.isEmpty())
964 {
965 m_viewList.push_back(view);
966 m_viewTextList.push_back(view);
967 m_curView = 0;
968 }
969 else
970 {
971 m_curView = -1;
972 }
973 }
974 else if (m_type == plNewListings)
975 {
976 m_viewList.push_back("all");
977 m_viewTextList.push_back(tr("All"));
978
979 m_viewList.push_back("premieres");
980 m_viewTextList.push_back(tr("Premieres"));
981
982 m_viewList.push_back("movies");
983 m_viewTextList.push_back(tr("Movies"));
984
985 m_viewList.push_back("series");
986 m_viewTextList.push_back(tr("Series"));
987
988 m_viewList.push_back("specials");
989 m_viewTextList.push_back(tr("Specials"));
990
991 if (!view.isEmpty())
992 m_curView = m_viewList.indexOf(view);
993 }
994 else if (m_type == plMovies)
995 {
996 m_viewList.push_back(">= 0.0");
997 m_viewTextList.push_back(tr("All"));
998 m_viewList.push_back("= 0.0");
999 m_viewTextList.push_back(tr("Unrated"));
1000 m_viewList.push_back(QString(">= %1").arg(((10 - 0.5) / 10.0) - 0.001));
1001 m_viewTextList.push_back(tr("%n star(s)", "", 10));
1002 for (int i = 9; i > 0; i--)
1003 {
1004 float stars = ((i - 0.5F ) / 10.0F) - 0.001F;
1005 m_viewList.push_back(QString(">= %1").arg(stars));
1006 m_viewTextList.push_back(tr("%n star(s) and above", "", i));
1007 }
1008
1009 if (!view.isEmpty())
1010 m_curView = m_viewList.indexOf(view);
1011 }
1012 else if (m_type == plTime)
1013 {
1014 m_curView = 0;
1018 }
1019 else if (m_type == plSQLSearch)
1020 {
1021 m_curView = 0;
1022 m_viewList.push_back(view);
1023 m_viewTextList.push_back(tr("Power Recording Rule"));
1024 }
1025 else if (m_type == plRecordid)
1026 {
1027 m_curView = 0;
1028
1030 query.prepare("SELECT title FROM record "
1031 "WHERE recordid = :RECORDID");
1032 query.bindValue(":RECORDID", view);
1033
1034 if (query.exec() && query.next())
1035 {
1036 QString title = query.value(0).toString();
1037 m_viewList.push_back(view);
1038 m_viewTextList.push_back(title);
1039 }
1040 }
1041 else if (m_type == plStoredSearch) // stored searches
1042 {
1044 query.prepare("SELECT rulename FROM customexample "
1045 "WHERE search > 0 ORDER BY rulename;");
1046
1047 if (query.exec())
1048 {
1049 while (query.next())
1050 {
1051 QString rulename = query.value(0).toString();
1052 if (rulename.isEmpty() || rulename.trimmed().isEmpty())
1053 continue;
1054 rulename = query.value(0).toString();
1055 m_viewList.push_back(rulename);
1056 m_viewTextList.push_back(rulename);
1057 }
1058 }
1059 if (!view.isEmpty())
1060 m_curView = m_viewList.indexOf(view);
1061 }
1062 else if (m_type == plPreviouslyRecorded) // previously recorded
1063 {
1064 m_viewList.push_back("sort by time");
1065 m_viewTextList.push_back(tr("Time"));
1066
1067 m_viewList.push_back("reverse time");
1068 m_viewTextList.push_back(tr("Reverse Time"));
1069
1070 m_viewList.push_back("sort by title");
1071 m_viewTextList.push_back(tr("Title"));
1072
1073 m_viewList.push_back("reverse title");
1074 m_viewTextList.push_back(tr("Reverse Title"));
1075
1076 if (!view.isEmpty())
1077 m_curView = m_viewList.indexOf(view);
1078 }
1079
1080 if (m_curView >= m_viewList.size())
1081 m_curView = m_viewList.size() - 1;
1082}
1083
1084// For the spaceship operator, the c++ standard library explicitly
1085// requires '0' and not nullptr. NOLINTBEGIN(modernize-use-nullptr)
1086static bool plTitleSort(const ProgramInfo *a, const ProgramInfo *b)
1087{
1088 if (a->GetSortTitle() != b->GetSortTitle())
1090 if (a->GetSortSubtitle() != b->GetSortSubtitle())
1092
1093 if (a->GetRecordingStatus() == b->GetRecordingStatus())
1095
1099 return true;
1103 return false;
1104
1107 return true;
1110 return false;
1111
1113};
1114
1115static bool plPrevTitleSort(const ProgramInfo *a, const ProgramInfo *b)
1116{
1117 if (a->GetSortTitle() != b->GetSortTitle())
1119 if (a->GetSortSubtitle() != b->GetSortSubtitle())
1121
1122 if (a->GetProgramID() != b->GetProgramID())
1123 return a->GetProgramID() < b->GetProgramID();
1124
1126};
1127// NOLINTEND(modernize-use-nullptr)
1128
1129static bool plTimeSort(const ProgramInfo *a, const ProgramInfo *b)
1130{
1132 return (a->GetChanID() < b->GetChanID());
1133
1134 return (a->GetScheduledStartTime() < b->GetScheduledStartTime());
1135};
1136
1137void ProgLister::FillItemList(bool restorePosition, bool updateDisp)
1138{
1140 if (m_groupByText)
1142
1144 {
1145 if (!m_titleSort)
1146 {
1147 if (!m_reverseSort)
1148 m_curView = 0;
1149 else
1150 m_curView = 1;
1151 }
1152 else
1153 {
1154 if (!m_reverseSort)
1155 m_curView = 2;
1156 else
1157 m_curView = 3;
1158 }
1159 }
1160
1161 if (m_curView < 0)
1162 return;
1163
1164 QString where;
1165 QString qphrase = m_viewList[m_curView];
1166
1167 MSqlBindings bindings;
1168
1170 bindings[":PGILSTART"] =
1171 m_startTime.addSecs(50 - m_startTime.time().second());
1172
1173 if (m_type == plTitle) // per title listings
1174 {
1175 where = "WHERE channel.deleted IS NULL "
1176 " AND channel.visible > 0 "
1177 " AND program.endtime > :PGILSTART "
1178 " AND (program.title = :PGILPHRASE0 OR "
1179 " (program.seriesid <> '' AND "
1180 " program.seriesid = :PGILPHRASE1)) ";
1181 bindings[":PGILPHRASE0"] = qphrase;
1182 bindings[":PGILPHRASE1"] = m_extraArg;
1183 }
1184 else if (m_type == plNewListings) // what's new list
1185 {
1186 where = "LEFT JOIN oldprogram ON "
1187 " oldprogram.oldtitle = program.title "
1188 "WHERE channel.deleted IS NULL "
1189 " AND channel.visible > 0 "
1190 " AND program.endtime > :PGILSTART "
1191 " AND oldprogram.oldtitle IS NULL "
1192 " AND program.manualid = 0 ";
1193
1194 if (qphrase == "premieres")
1195 {
1196 where += " AND ( ";
1197 where += " ( program.originalairdate = DATE(";
1198 where += " CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM'))";
1199 where += " AND (program.category = 'Special' ";
1200 where += " OR program.programid LIKE 'EP%0001')) ";
1201 where += " OR (program.category_type='movie' ";
1202 where += " AND program.stars > 0.5 ";
1203 where += " AND program.airdate >= YEAR(NOW()) - 2) ";
1204 where += " ) ";
1205 }
1206 else if (qphrase == "movies")
1207 {
1208 where += " AND program.category_type = 'movie' ";
1209 }
1210 else if (qphrase == "series")
1211 {
1212 where += " AND program.category_type = 'series' ";
1213 }
1214 else if (qphrase == "specials")
1215 {
1216 where += " AND program.category_type = 'tvshow' ";
1217 }
1218 else
1219 {
1220 where += " AND (program.category_type <> 'movie' ";
1221 where += " OR program.airdate >= YEAR(NOW()) - 3) ";
1222 }
1223 }
1224 else if (m_type == plTitleSearch) // keyword search
1225 {
1226 where = "WHERE channel.deleted IS NULL "
1227 " AND channel.visible > 0 "
1228 " AND program.endtime > :PGILSTART "
1229 " AND program.title LIKE :PGILLIKEPHRASE0 ";
1230 bindings[":PGILLIKEPHRASE0"] = QString("%") + qphrase + '%';
1231 }
1232 else if (m_type == plKeywordSearch) // keyword search
1233 {
1234 where = "WHERE channel.deleted IS NULL "
1235 " AND channel.visible > 0 "
1236 " AND program.endtime > :PGILSTART "
1237 " AND (program.title LIKE :PGILLIKEPHRASE1 "
1238 " OR program.subtitle LIKE :PGILLIKEPHRASE2 "
1239 " OR program.description LIKE :PGILLIKEPHRASE3 ) ";
1240 bindings[":PGILLIKEPHRASE1"] = QString("%") + qphrase + '%';
1241 bindings[":PGILLIKEPHRASE2"] = QString("%") + qphrase + '%';
1242 bindings[":PGILLIKEPHRASE3"] = QString("%") + qphrase + '%';
1243 }
1244 else if (m_type == plPeopleSearch) // people search
1245 {
1246 where = ", people, credits "
1247 "WHERE channel.deleted IS NULL "
1248 " AND channel.visible > 0 "
1249 " AND program.endtime > :PGILSTART "
1250 " AND people.name LIKE :PGILPHRASE1 "
1251 " AND credits.person = people.person "
1252 " AND program.chanid = credits.chanid "
1253 " AND program.starttime = credits.starttime";
1254 bindings[":PGILPHRASE1"] = qphrase;
1255 }
1256 else if (m_type == plPowerSearch) // complex search
1257 {
1258 QString powerWhere;
1259 MSqlBindings powerBindings;
1260
1261 bool genreflag = PowerStringToSQL(qphrase, powerWhere, powerBindings);
1262
1263 if (!powerWhere.isEmpty())
1264 {
1265 if (genreflag)
1266 {
1267 where = QString("LEFT JOIN programgenres ON "
1268 "program.chanid = programgenres.chanid AND "
1269 "program.starttime = programgenres.starttime ");
1270 }
1271
1272 where += QString("WHERE channel.deleted IS NULL "
1273 " AND channel.visible > 0 "
1274 " AND program.endtime > :PGILSTART "
1275 " AND ( ") + powerWhere + " ) ";
1276 MSqlAddMoreBindings(bindings, powerBindings);
1277 }
1278 }
1279 else if (m_type == plSQLSearch) // complex search
1280 {
1281 qphrase.remove(RecordingInfo::kReLeadingAnd);
1282 where = QString("WHERE channel.deleted iS NULL "
1283 " AND channel.visible > 0 "
1284 " AND program.endtime > :PGILSTART "
1285 " AND ( %1 ) ").arg(qphrase);
1286 if (!m_extraArg.isEmpty())
1287 where = m_extraArg + ' ' + where;
1288 }
1289 else if (m_type == plChannel) // list by channel
1290 {
1291 groupBy = ProgGroupBy::None;
1292 where = "WHERE channel.deleted IS NULL "
1293 " AND channel.visible > 0 "
1294 " AND program.endtime > :PGILSTART "
1295 " AND channel.chanid = :PGILPHRASE2 ";
1296 bindings[":PGILPHRASE2"] = qphrase;
1297 }
1298 else if (m_type == plCategory) // list by category
1299 {
1300 if (!m_useGenres)
1301 {
1302 where = "WHERE channel.deleted IS NULL "
1303 " AND channel.visible > 0 "
1304 " AND program.endtime > :PGILSTART "
1305 " AND program.category = :PGILPHRASE3 ";
1306 bindings[":PGILPHRASE3"] = qphrase;
1307 }
1308 else if (m_viewList[m_curView].indexOf(":/:") < 0)
1309 {
1310 where = "JOIN programgenres g ON "
1311 " program.chanid = g.chanid AND "
1312 " program.starttime = g.starttime AND "
1313 " genre = :PGILPHRASE4 "
1314 "WHERE channel.deleted IS NULL "
1315 " AND channel.visible > 0 "
1316 " AND program.endtime > :PGILSTART ";
1317 bindings[":PGILPHRASE4"] = qphrase;
1318 }
1319 else
1320 {
1321 where = "JOIN programgenres g1 ON "
1322 " program.chanid = g1.chanid AND "
1323 " program.starttime = g1.starttime AND "
1324 " g1.genre = :GENRE1 "
1325 "JOIN programgenres g2 ON "
1326 " program.chanid = g2.chanid AND "
1327 " program.starttime = g2.starttime AND "
1328 " g2.genre = :GENRE2 "
1329 "WHERE channel.deleted IS NULL "
1330 " AND channel.visible > 0 "
1331 " AND program.endtime > :PGILSTART ";
1332 bindings[":GENRE1"] = m_viewList[m_curView].section(":/:", 0, 0);
1333 bindings[":GENRE2"] = m_viewList[m_curView].section(":/:", 1, 1);
1334 }
1335 }
1336 else if (m_type == plMovies) // list movies
1337 {
1338 where = "WHERE channel.deleted IS NULL "
1339 " AND channel.visible > 0 "
1340 " AND program.endtime > :PGILSTART "
1341 " AND program.category_type = 'movie' "
1342 " AND program.stars " + qphrase + ' ';
1343 }
1344 else if (m_type == plTime) // list by time
1345 {
1346 groupBy = ProgGroupBy::ChanNum;
1347 QDateTime searchTime(m_searchTime);
1348 searchTime.setTime(QTime(searchTime.time().hour(), 0, 0));
1349 bindings[":PGILSEARCHTIME1"] = searchTime;
1350 where = "WHERE channel.deleted IS NULL "
1351 " AND channel.visible > 0 "
1352 " AND program.starttime >= :PGILSEARCHTIME1 ";
1353 if (m_titleSort)
1354 {
1355 where += " AND program.starttime < DATE_ADD(:PGILSEARCHTIME2, "
1356 "INTERVAL '1' HOUR) ";
1357 bindings[":PGILSEARCHTIME2"] = bindings[":PGILSEARCHTIME1"];
1358 }
1359 }
1360 else if (m_type == plRecordid) // list by recordid
1361 {
1362 where = "JOIN recordmatch ON "
1363 " (program.starttime = recordmatch.starttime "
1364 " AND program.chanid = recordmatch.chanid) "
1365 "WHERE channel.deleted IS NULL "
1366 " AND channel.visible > 0 "
1367 " AND program.endtime > :PGILSTART "
1368 " AND recordmatch.recordid = :PGILPHRASE5 ";
1369 bindings[":PGILPHRASE5"] = qphrase;
1370 }
1371 else if (m_type == plStoredSearch) // stored search
1372 {
1374 query.prepare("SELECT fromclause, whereclause FROM customexample "
1375 "WHERE rulename = :RULENAME;");
1376 query.bindValue(":RULENAME", qphrase);
1377
1378 if (query.exec() && query.next())
1379 {
1380 QString fromc = query.value(0).toString();
1381 QString wherec = query.value(1).toString();
1382
1383 where = QString("WHERE channel.deleted IS NULL "
1384 " AND channel.visible > 0 "
1385 " AND program.endtime > :PGILSTART "
1386 " AND ( %1 ) ").arg(wherec);
1387 if (!fromc.isEmpty())
1388 where = fromc + ' ' + where;
1389 }
1390 }
1391 else if (m_type == plPreviouslyRecorded)
1392 {
1393 if (m_recid && !m_title.isEmpty())
1394 {
1395 where = QString("AND ( recordid = %1 OR title = :MTITLE )")
1396 .arg(m_recid);
1397 bindings[":MTITLE"] = m_title;
1398 }
1399 else if (!m_title.isEmpty())
1400 {
1401 where = QString("AND title = :MTITLE ");
1402 bindings[":MTITLE"] = m_title;
1403 }
1404 else if (m_recid)
1405 {
1406 where = QString("AND recordid = %1 ").arg(m_recid);
1407 }
1408 }
1409
1410 ProgramInfo selected;
1411 const ProgramInfo *selectedP = restorePosition ? GetCurrentProgram() : nullptr;
1412 if (selectedP)
1413 {
1414 selected = *selectedP;
1415 selectedP = &selected;
1416 }
1417
1418 // Save a copy of m_itemList so that deletion of the ProgramInfo
1419 // objects can be deferred until background processing of old
1420 // ProgramInfo objects has completed.
1424 m_itemList.clear();
1426
1428 {
1429 LoadFromOldRecorded(m_itemList, where, bindings);
1430 }
1431 else
1432 {
1434 LoadFromProgram(m_itemList, where, bindings, m_schedList, groupBy);
1435 }
1436
1438 {
1441 {
1442 // Prune to one per title
1443 QString curtitle;
1444 auto it = m_itemList.begin();
1445 while (it != m_itemList.end())
1446 {
1447 if ((*it)->GetSortTitle() != curtitle)
1448 {
1449 curtitle = (*it)->GetSortTitle();
1450 ++it;
1451 }
1452 else
1453 {
1454 it = m_itemList.erase(it);
1455 }
1456 }
1457 }
1458 }
1459
1460 if (!m_titleSort)
1462
1463 if (updateDisp)
1464 UpdateDisplay(selectedP);
1465}
1466
1468{
1469 if (!m_titleSort)
1470 return kTimeSort;
1472 return kPrevTitleSort;
1473 return kTitleSort;
1474}
1475
1476void ProgLister::SortList(SortBy sortby, bool reverseSort)
1477{
1478 if (reverseSort)
1479 {
1480 if (kTimeSort == sortby)
1481 {
1482 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList), plTimeSort);
1483 }
1484 else if (kPrevTitleSort == sortby)
1485 {
1486 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList),
1488 }
1489 else
1490 {
1491 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList), plTitleSort);
1492 }
1493 }
1494 else
1495 {
1496 if (kTimeSort == sortby)
1497 {
1498 std::ranges::stable_sort(m_itemList, plTimeSort);
1499 }
1500 else if (kPrevTitleSort == sortby)
1501 {
1502 std::ranges::stable_sort(m_itemList,plPrevTitleSort);
1503 }
1504 else
1505 {
1506 std::ranges::stable_sort(m_itemList, plTitleSort);
1507 }
1508 }
1509}
1510
1512{
1513 InfoMap infoMap;
1514 ProgramInfo pginfo;
1515 pginfo.ToMap(infoMap);
1516 ResetMap(infoMap);
1517
1518 if (m_positionText)
1520}
1521
1523{
1524 int offset = 0;
1525
1526 if (selected)
1528
1529 m_progList->Reset();
1530
1531 if (m_messageText)
1533
1535
1536 if (m_curviewText && m_curView >= 0)
1538
1540
1541 if (selected)
1542 {
1543 RestoreSelection(selected, offset);
1544 }
1545 else if (m_selectedTime.isValid())
1546 {
1547 size_t i = 0;
1548 for (i = 0; i < m_itemList.size(); ++i)
1549 {
1550 if (m_selectedTime <= m_itemList[i]->GetScheduledStartTime())
1551 break;
1552 }
1554 m_selectedTime = QDateTime();
1555 }
1556}
1557
1559 int selectedOffset)
1560{
1561 using ProgramInfoSortFn = bool (*)(const ProgramInfo *a, const ProgramInfo *b);
1562 ProgramInfoSortFn comp { nullptr };
1563 if (!m_titleSort)
1564 comp = plTimeSort;
1565 else if (m_type == plPreviouslyRecorded)
1566 comp = plPrevTitleSort;
1567 else
1568 comp = plTitleSort;
1569
1570 int i = 0;
1571 for (i = m_itemList.size() - 2; i >= 0; i--)
1572 {
1573 bool dobreak = false;
1574 if (m_reverseSort)
1575 dobreak = comp(selected, m_itemList[i]);
1576 else
1577 dobreak = comp(m_itemList[i], selected);
1578 if (dobreak)
1579 break;
1580 }
1581
1582 m_progList->SetItemCurrent(i + 1, i + 1 - selectedOffset);
1583}
1584
1586{
1587 auto *pginfo = item->GetData().value<ProgramInfo*>();
1588
1589 if (item->GetText("is_item_initialized").isNull())
1590 {
1591 InfoMap infoMap;
1592 pginfo->ToMap(infoMap);
1593
1594 QString state = RecStatus::toUIState(pginfo->GetRecordingStatus());
1595 if ((state == "warning") && (plPreviouslyRecorded == m_type))
1596 state = "disabled";
1597
1598 item->SetTextFromMap(infoMap, state);
1599
1600 if (m_type == plTitle)
1601 {
1602 QString tempSubTitle = pginfo->GetSubtitle();
1603 if (tempSubTitle.trimmed().isEmpty())
1604 tempSubTitle = pginfo->GetTitle();
1605 item->SetText(tempSubTitle, "titlesubtitle", state);
1606 }
1607
1608 item->DisplayState(QString::number(pginfo->GetStars(10)),
1609 "ratingstate");
1610
1611 item->DisplayState(state, "status");
1612
1613 // Mark this button list item as initialized.
1614 item->SetText("yes", "is_item_initialized");
1615 }
1616}
1617
1619{
1620 for (auto *it : m_itemList)
1621 new MythUIButtonListItem(m_progList, "", QVariant::fromValue(it));
1623
1624 if (m_positionText)
1625 {
1627 tr("%1 of %2", "Current position in list where %1 is the "
1628 "position, %2 is the total count")
1629 .arg(QLocale::system().toString(m_progList->IsEmpty() ? 0 : m_progList->GetCurrentPos() + 1),
1630 QLocale::system().toString(m_progList->GetCount())));
1631 }
1632}
1633
1635{
1636 if (!item)
1637 {
1639 return;
1640 }
1641
1642 auto *pginfo = item->GetData().value<ProgramInfo*> ();
1643 if (!pginfo)
1644 {
1646 return;
1647 }
1648
1649 InfoMap infoMap;
1650 pginfo->ToMap(infoMap);
1651 SetTextFromMap(infoMap);
1652
1653 if (m_positionText)
1654 {
1656 tr("%1 of %2", "Current position in list where %1 is the "
1657 "position, %2 is the total count")
1658 .arg(QLocale::system().toString(m_progList->IsEmpty() ? 0 : m_progList->GetCurrentPos() + 1),
1659 QLocale::system().toString(m_progList->GetCount())));
1660 }
1661
1662 MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
1663 (GetChild("ratingstate"));
1664
1665 if (ratingState)
1666 {
1667 QString rating = QString::number(pginfo->GetStars(10));
1668 ratingState->DisplayState(rating);
1669 }
1670}
1671
1672void ProgLister::customEvent(QEvent *event)
1673{
1674 bool needUpdate = false;
1675
1676 if (event->type() == DialogCompletionEvent::kEventType)
1677 {
1678 auto *dce = (DialogCompletionEvent*)event;
1679
1680 QString resultid = dce->GetId();
1681// QString resulttext = dce->GetResultText();
1682 int buttonnum = dce->GetResult();
1683
1684 if (resultid == "sortgroupmenu")
1685 {
1686 switch (buttonnum)
1687 {
1688 case 0:
1690 needUpdate = true;
1691 break;
1692 case 1:
1693 m_titleSort = true;
1694 m_reverseSort = false;
1695 needUpdate = true;
1696 break;
1697 case 2:
1698 m_titleSort = false;
1700 needUpdate = true;
1701 break;
1702 default:
1704 break;
1705 }
1706 }
1707 else if (resultid == "deletemenu")
1708 {
1709 switch (buttonnum)
1710 {
1711 case 0:
1712 {
1714 if (pi)
1715 {
1716 RecordingInfo ri(*pi);
1717 if (ri.IsDuplicate())
1718 ri.ForgetHistory();
1719 else
1720 ri.SetDupHistory();
1721 *pi = ri;
1722 }
1723 break;
1724 }
1725 case 1:
1727 break;
1728 case 2:
1730 break;
1731 }
1732 }
1733 else if (resultid == "deleterule")
1734 {
1735 auto *record = dce->GetData().value<RecordingRule *>();
1736 if (record && buttonnum > 0 && !record->Delete())
1737 {
1738 LOG(VB_GENERAL, LOG_ERR, LOC +
1739 "Failed to delete recording rule");
1740 }
1741 delete record;
1742 }
1743 else
1744 {
1746 }
1747 }
1748 else if (event->type() == ScreenLoadCompletionEvent::kEventType)
1749 {
1750 auto *slce = (ScreenLoadCompletionEvent*)event;
1751 QString id = slce->GetId();
1752
1753 if (id == objectName())
1754 {
1755 CloseBusyPopup(); // opened by LoadInBackground()
1756 UpdateDisplay();
1757
1760 }
1761 }
1762 else if (event->type() == MythEvent::kMythEventMessage)
1763 {
1764 auto *me = dynamic_cast<MythEvent *>(event);
1765 if (me == nullptr)
1766 return;
1767
1768 const QString& message = me->Message();
1769
1770 if (m_allowViewDialog && message == "CHOOSE_VIEW")
1772 else if (message == "SCHEDULE_CHANGE"
1773 || message == "GROUPBY_CHANGE")
1774 needUpdate = true;
1775 }
1776
1777 if (needUpdate)
1778 FillItemList(true);
1779}
1780
1781#include "moc_proglist.cpp"
std::vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:130
void clear(void)
iterator erase(const iterator &it)
iterator begin(void)
void setAutoDelete(bool auto_delete)
iterator end(void)
bool empty(void) const
size_t size(void) const
static void SortChannels(ChannelInfoList &list, const QString &order, bool eliminate_duplicates=false)
static ChannelInfoList GetChannels(uint sourceid, bool visible_only, const QString &group_by=QString(), uint channel_groupid=0)
Definition: channelutil.h:252
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:40
static const Type kEventType
Definition: mythdialogbox.h:55
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:839
QVariant value(int i) const
Definition: mythdbcon.h:204
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
Dialog asking for user confirmation.
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
Basic menu dialog, message and a list of options.
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & Message() const
Definition: mythevent.h:65
static const Type kMythEventMessage
Definition: mythevent.h:79
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)
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void PopScreen(MythScreenType *screen=nullptr, bool allowFade=true, bool deleteScreen=true)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void LoadInBackground(const QString &message="")
virtual bool Create(void)
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
MythScreenStack * GetScreenStack() const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void CloseBusyPopup(void)
void haveResult(QDateTime time)
void DisplayState(const QString &state, const QString &name)
void SetTextFromMap(const InfoMap &infoMap, const QString &state="")
QString GetText(const QString &name="") const
void SetText(const QString &text, const QString &name="", const QString &state="")
void SetLCDTitles(const QString &title, const QString &columnList="")
void itemVisible(MythUIButtonListItem *item)
void SetItemCurrent(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
int GetTopItemPos(void) const
void itemLoaded(MythUIButtonListItem *item)
void SetSearchFields(const QString &fields)
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
void LoadInBackground(int start=0, int pageSize=20)
void itemSelected(MythUIButtonListItem *item)
virtual void SetTextFromMap(const InfoMap &infoMap)
virtual void ResetMap(const InfoMap &infoMap)
Provide a dialog to quickly find an entry in a list.
void haveResult(QString)
This widget is used for grouping other widgets for display when a particular named state is called.
bool DisplayState(const QString &name)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:65
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
void haveResult(QString item)
void haveResult(QString item)
static QString toString(ProgGroupBy::Type groupBy)
void HandleVisible(MythUIButtonListItem *item)
Definition: proglist.cpp:1585
void ShowChooseViewMenu(void)
Definition: proglist.cpp:492
MythUIText * m_groupByText
Definition: proglist.h:142
void SortList(SortBy sortby, bool reverseSort)
Definition: proglist.cpp:1476
QDateTime m_selectedTime
Definition: proglist.h:114
friend class PhrasePopup
Definition: proglist.h:37
ProgramList m_schedList
Definition: proglist.h:126
QStringList m_viewTextList
Definition: proglist.h:122
MythUIButtonList * m_progList
Definition: proglist.h:140
SortBy GetSortBy(void) const
Definition: proglist.cpp:1467
bool m_allowEvents
Definition: proglist.h:132
void SwitchToNextView(void)
Definition: proglist.cpp:423
void customEvent(QEvent *event) override
Definition: proglist.cpp:1672
bool m_reverseSort
Definition: proglist.h:134
TV * m_player
Definition: proglist.h:146
ProgListType m_type
Definition: proglist.h:108
void DeleteOldSeries(bool ok)
Definition: proglist.cpp:757
MythUIText * m_positionText
Definition: proglist.h:139
QString m_title
Definition: proglist.h:110
void SetViewFromList(const QString &item)
Definition: proglist.cpp:608
static void * RunProgramList(void *player, ProgListType pltype, const QString &extraArg)
Definition: proglist.cpp:38
QString m_view
Definition: proglist.h:119
void FillItemList(bool restorePosition, bool updateDisp=true)
Definition: proglist.cpp:1137
void ShowDeleteOldEpisodeMenu(void)
Definition: proglist.cpp:712
void HandleSelected(MythUIButtonListItem *item)
Definition: proglist.cpp:1634
void FillViewList(const QString &view)
Definition: proglist.cpp:813
void UpdateButtonList(void)
Definition: proglist.cpp:1618
void ShowOldRecordedMenu(void)
Definition: proglist.cpp:778
void RestoreSelection(const ProgramInfo *selected, int selectedOffset)
Definition: proglist.cpp:1558
MythUIText * m_curviewText
Definition: proglist.h:138
static bool PowerStringToSQL(const QString &qphrase, QString &output, MSqlBindings &bindings)
Definition: proglist.cpp:615
void SetViewFromTime(QDateTime searchTime)
Definition: proglist.cpp:594
int m_curView
Definition: proglist.h:120
void Load(void) override
Load data which will ultimately be displayed on-screen or used to determine what appears on-screen (S...
Definition: proglist.cpp:238
ProgLister(MythScreenStack *parent, ProgListType pltype, QString view, QString extraArg, QDateTime selectedTime=QDateTime())
Definition: proglist.cpp:53
~ProgLister(void) override
Definition: proglist.cpp:138
MythUIText * m_messageText
Definition: proglist.h:141
friend class PowerSearchPopup
Definition: proglist.h:39
void ShowDeleteItemMenu(void)
Definition: proglist.cpp:673
QDateTime m_searchTime
Definition: proglist.h:113
QString m_channelOrdering
Definition: proglist.h:115
void Close(void) override
Definition: proglist.cpp:152
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: proglist.cpp:249
@ kPrevTitleSort
Definition: proglist.h:100
@ kTitleSort
Definition: proglist.h:100
bool Create(void) override
Definition: proglist.cpp:161
bool m_useGenres
Definition: proglist.h:135
RecSearchType m_searchType
Definition: proglist.h:117
QStringList m_viewList
Definition: proglist.h:121
uint m_recid
Definition: proglist.h:109
void UpdateKeywordInDB(const QString &text, const QString &oldValue)
Definition: proglist.cpp:447
ProgramList m_itemListSave
Definition: proglist.h:125
void ShowDeleteOldSeriesMenu(void)
Definition: proglist.cpp:745
void ShowDeleteRuleMenu(void)
Definition: proglist.cpp:681
QString m_extraArg
Definition: proglist.h:111
void UpdateDisplay(const ProgramInfo *selected=nullptr)
Definition: proglist.cpp:1522
ProgramInfo * GetCurrentProgram(void) const override
Definition: proglist.cpp:665
void ShowMenu(void) override
Definition: proglist.cpp:338
MythUIText * m_schedText
Definition: proglist.h:137
void DeleteOldEpisode(bool ok)
Definition: proglist.cpp:724
void ClearCurrentProgramInfo(void)
Definition: proglist.cpp:1511
bool m_titleSort
Definition: proglist.h:133
ProgramList m_itemList
Definition: proglist.h:124
void SwitchToPreviousView(void)
Definition: proglist.cpp:400
QDateTime m_startTime
Definition: proglist.h:112
bool m_allowViewDialog
Definition: proglist.h:144
Holds information on recordings and videos.
Definition: programinfo.h:74
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:380
uint GetRecordingRuleID(void) const
Definition: programinfo.h:460
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
QString GetProgramID(void) const
Definition: programinfo.h:447
QString GetSortSubtitle(void) const
Definition: programinfo.h:371
QString GetDescription(void) const
Definition: programinfo.h:372
QString GetTitle(void) const
Definition: programinfo.h:368
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:398
bool IsDuplicate(void) const
Definition: programinfo.h:500
QString GetSortTitle(void) const
Definition: programinfo.h:369
virtual void ToMap(InfoMap &progMap, bool showrerecord=false, uint star_range=10, uint date_format=0) const
Converts ProgramInfo into QString QHash containing each field in ProgramInfo converted into localized...
RecStatus::Type GetRecordingStatus(void) const
Definition: programinfo.h:458
void SetProgramID(const QString &id)
Definition: programinfo.h:545
RecordingType GetRecordingRuleType(void) const
Definition: programinfo.h:462
static QString toUIState(RecStatus::Type recstatus)
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
void SetDupHistory(void)
Set the duplicate flag in oldrecorded.
void ForgetHistory(void)
Forget the recording of a program so it will be recorded again.
static const QRegularExpression kReLeadingAnd
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:30
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
virtual void EditScheduled(void)
Creates a dialog for editing the recording schedule.
virtual void EditRecording(bool may_watch_now=false)
Creates a dialog for editing the recording status, blocking until user leaves dialog.
static ProgGroupBy::Type GetProgramListGroupBy(void)
virtual void ShowDetails(void) const
Show the Program Details screen.
virtual void AddGroupMenuItems(MythMenu *sortGroupMenu)
void customEvent(QEvent *event) override
virtual void EditCustom(void)
Creates a dialog for creating a custom recording rule.
virtual void ShowGuide(void) const
Show the program guide.
virtual void QuickRecord(void)
Create a kSingleRecord or bring up recording dialog.
virtual void ShowUpcoming(void) const
Show the upcoming recordings for this title.
virtual void ShowChannelSearch(void) const
Show the channel search.
virtual void ShowPrevious(void) const
Show the previous recordings for this recording rule.
static void RescheduleCheck(const RecordingInfo &recinfo, const QString &why)
Event that can be dispatched from a MythScreenType when it has completed loading.
static const Type kEventType
void RequestEmbedding(bool Embed, const QRect &Rect={}, const QStringList &Data={})
Control TV playback.
Definition: tv_play.h:157
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
unsigned int uint
Definition: compat.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
Add the entries in addfrom to the map in output.
Definition: mythdbcon.cpp:985
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:100
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
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h:23
@ kSimplify
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:26
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
MBASE_PUBLIC std::strong_ordering naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity=Qt::CaseSensitive)
This method chops the input a and b into pieces of digits and non-digits (a1.05 becomes a | 1 | .
Definition: stringutil.cpp:122
def rating(profile, smoonURL, gate)
Definition: scan.py:36
#define LOC
Definition: proglist.cpp:34
static bool plPrevTitleSort(const ProgramInfo *a, const ProgramInfo *b)
Definition: proglist.cpp:1115
static bool plTitleSort(const ProgramInfo *a, const ProgramInfo *b)
Definition: proglist.cpp:1086
static bool plTimeSort(const ProgramInfo *a, const ProgramInfo *b)
Definition: proglist.cpp:1129
ProgListType
Definition: proglist.h:17
@ plKeywordSearch
Definition: proglist.h:21
@ plTime
Definition: proglist.h:29
@ plChannel
Definition: proglist.h:28
@ plTitleSearch
Definition: proglist.h:20
@ plPreviouslyRecorded
Definition: proglist.h:32
@ plUnknown
Definition: proglist.h:18
@ plPowerSearch
Definition: proglist.h:23
@ plMovies
Definition: proglist.h:26
@ plSQLSearch
Definition: proglist.h:24
@ plCategory
Definition: proglist.h:27
@ plRecordid
Definition: proglist.h:30
@ plTitle
Definition: proglist.h:19
@ plPeopleSearch
Definition: proglist.h:22
@ plNewListings
Definition: proglist.h:25
@ plStoredSearch
Definition: proglist.h:31
bool LoadFromOldRecorded(ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
bool LoadFromProgram(ProgramList &destination, const QString &where, const QString &groupBy, const QString &orderBy, const MSqlBindings &bindings, const ProgramList &schedList)
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
Definition: programinfo.h:945
@ kTitleSearch
@ kPowerSearch
@ kKeywordSearch
@ kNoSearch
@ kPeopleSearch
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
#define output
#define ACTION_CHANNELSEARCH
Definition: tv_actions.h:28
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:86