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();
292 else if (action == "TOGGLERECORD")
293 QuickRecord();
294 else if (action == "1")
295 {
296 if (m_titleSort)
297 {
298 m_titleSort = false;
300 }
301 else
302 {
304 }
305 needUpdate = true;
306 }
307 else if (action == "2")
308 {
309 if (!m_titleSort)
310 {
311 m_titleSort = true;
312 m_reverseSort = false;
313 }
314 else
315 {
317 }
318 needUpdate = true;
319 }
320 else
321 {
322 handled = false;
323 }
324 }
325
326 if (!handled && MythScreenType::keyPressEvent(e))
327 handled = true;
328
329 if (needUpdate)
331
332 m_allowEvents = true;
333
334 return handled;
335}
336
338{
339 auto *sortGroupMenu = new MythMenu(tr("Sort/Group Options"), this,
340 "sortgroupmenu");
341 sortGroupMenu->AddItem(tr("Reverse Sort Order"));
342 sortGroupMenu->AddItem(tr("Sort By Title"));
343 sortGroupMenu->AddItem(tr("Sort By Time"));
344
345 auto *menu = new MythMenu(tr("Options"), this, "menu");
346
348 {
349 menu->AddItem(tr("Choose Search Phrase..."), &ProgLister::ShowChooseViewMenu);
350 }
351
352 if (m_type != plChannel
353 && m_type != plTime
355 {
356 AddGroupMenuItems(sortGroupMenu);
357 }
358
359 menu->AddItem(tr("Sort/Group"), nullptr, sortGroupMenu);
360
362 menu->AddItem(tr("Record"), &ProgLister::QuickRecord);
363
364 menu->AddItem(tr("Edit Schedule"), qOverload<>(&ProgLister::EditScheduled));
365 menu->AddItem(tr("Program Details"), &ProgLister::ShowDetails);
366 menu->AddItem(tr("Program Guide"), &ProgLister::ShowGuide);
367 if (m_type != plChannel)
368 menu->AddItem(tr("Channel Search"), &ProgLister::ShowChannelSearch);
369 if (m_type != plTitle)
370 menu->AddItem(tr("Upcoming"), qOverload<>(&ProgLister::ShowUpcoming));
372 menu->AddItem(tr("Previously Recorded"), qOverload<>(&ProgLister::ShowPrevious));
373 menu->AddItem(tr("Custom Edit"), &ProgLister::EditCustom);
374
377 {
378 if (pi && pi->GetRecordingRuleID())
379 menu->AddItem(tr("Delete Rule"), &ProgLister::ShowDeleteRuleMenu);
380 }
381 else
382 {
383 menu->AddItem(
384 tr("Delete Episode"), &ProgLister::ShowDeleteOldEpisodeMenu);
385 }
386
387 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
388 auto *menuPopup = new MythDialogBox(menu, popupStack, "menuPopup");
389
390 if (!menuPopup->Create())
391 {
392 delete menuPopup;
393 return;
394 }
395
396 popupStack->AddScreen(menuPopup);
397}
398
400{
401 if (m_type == plTime && !m_viewList.empty() && !m_viewTextList.empty())
402 {
403 m_searchTime = m_searchTime.addSecs(-3600);
404 m_curView = 0;
409 return;
410 }
411
412 if (m_viewList.size() <= 1)
413 return;
414
415 m_curView--;
416 if (m_curView < 0)
417 m_curView = m_viewList.size() - 1;
418
420}
421
423{
424 if (m_type == plTime && !m_viewList.empty() && !m_viewTextList.empty())
425 {
426 m_searchTime = m_searchTime.addSecs(3600);
427 m_curView = 0;
432
433 return;
434 }
435
436 if (m_viewList.size() <= 1)
437 return;
438
439 m_curView++;
440 if (m_curView >= m_viewList.size())
441 m_curView = 0;
442
444}
445
446void ProgLister::UpdateKeywordInDB(const QString &text, const QString &oldValue)
447{
448 int oldview = m_viewList.indexOf(oldValue);
449 int newview = m_viewList.indexOf(text);
450
451 if (newview >= 0 && newview == oldview)
452 return;
453
454 if (oldview >= 0)
455 {
456 QString qphrase = m_viewList[oldview];
457
459 query.prepare("DELETE FROM keyword "
460 "WHERE phrase = :PHRASE AND searchtype = :TYPE;");
461 query.bindValue(":PHRASE", qphrase);
462 query.bindValue(":TYPE", m_searchType);
463 if (!query.exec())
464 {
466 "ProgLister::updateKeywordInDB -- delete", query);
467 }
468 m_viewList.removeAll(qphrase);
469 m_viewTextList.removeAll(qphrase);
470 }
471
472 if (newview < 0)
473 {
474 const QString& qphrase = text;
475
477 query.prepare("REPLACE INTO keyword (phrase, searchtype)"
478 "VALUES(:PHRASE, :TYPE );");
479 query.bindValue(":PHRASE", qphrase);
480 query.bindValue(":TYPE", m_searchType);
481 if (!query.exec())
482 {
484 "ProgLister::updateKeywordInDB -- replace", query);
485 }
486 m_viewList.push_back(qphrase);
487 m_viewTextList.push_back(qphrase);
488 }
489}
490
492{
493 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
494 MythScreenType *screen = nullptr;
495
496 switch (m_type)
497 {
498 case plChannel:
499 case plCategory:
500 case plMovies:
501 case plNewListings:
502 case plStoredSearch:
503 {
504 if (m_viewList.empty())
505 return;
506
507 QString msg;
508 switch (m_type)
509 {
510 case plMovies: msg = tr("Select Rating"); break;
511 case plChannel: msg = tr("Select Channel"); break;
512 case plCategory: msg = tr("Select Category"); break;
513 case plNewListings: msg = tr("Select List"); break;
514 case plStoredSearch: msg = QString("%1\n%2")
515 .arg(tr("Select a search stored from"),
516 tr("Custom Record")); break;
517 default: // silence warning
518 break;
519 }
520
521 auto *dialog = new MythUISearchDialog(
522 popupStack, msg, m_viewTextList, true, "");
523 if (!dialog)
524 return;
525 connect(dialog, &MythUISearchDialog::haveResult,
527 screen = dialog;
528 break;
529 }
530 case plTitleSearch:
531 case plKeywordSearch:
532 case plPeopleSearch:
533 {
534 auto *dialog = new PhrasePopup(
535 popupStack, this, m_searchType, m_viewTextList,
536 (m_curView >= 0) ? m_viewList[m_curView] : QString());
537 if (!dialog)
538 return;
539 connect(dialog, &PhrasePopup::haveResult,
541 screen = dialog;
542 break;
543 }
544 case plPowerSearch:
545 {
546 auto *dialog = new PowerSearchPopup(
547 popupStack, this, m_searchType, m_viewTextList,
548 (m_curView >= 0) ? m_viewList[m_curView] : QString());
549 if (!dialog)
550 return;
551 connect(dialog, &PowerSearchPopup::haveResult,
553 screen = dialog;
554 break;
555 }
556 case plTime:
557 {
558 QString message = tr("Start search from date and time");
559 int flags = (MythTimeInputDialog::kDay |
562 auto *dialog = new MythTimeInputDialog(popupStack, message, flags);
563 if (!dialog)
564 return;
565 connect(dialog, &MythTimeInputDialog::haveResult,
567 screen = dialog;
568 break;
569 }
570 case plRecordid:
572 case plUnknown:
573 case plTitle:
574 case plSQLSearch:
575 break;
576 }
577
578 if (!screen)
579 {
580 LOG(VB_GENERAL, LOG_WARNING, LOC + "No menu");
581 return;
582 }
583
584 if (!screen->Create())
585 {
586 delete screen;
587 return;
588 }
589
590 popupStack->AddScreen(screen);
591}
592
593void ProgLister::SetViewFromTime(QDateTime searchTime)
594{
595 if (m_viewList.empty() || m_viewTextList.empty())
596 return;
597
598 m_searchTime = std::move(searchTime);
599 m_curView = 0;
603
605}
606
607void ProgLister::SetViewFromList(const QString& item)
608{
609 m_curView = m_viewTextList.indexOf(item);
610 if (m_curView >= 0)
612}
613
615 const QString &qphrase, QString &output, MSqlBindings &bindings)
616{
617 output.clear();
618
619 QStringList field = qphrase.split(':');
620 if (field.size() != 6)
621 {
622 LOG(VB_GENERAL, LOG_ERR, LOC + "Power search should have 6 fields," +
623 QString("\n\t\t\tnot %1 (%2)") .arg(field.size()).arg(qphrase));
624 return false;
625 };
626
627 static const std::array<QString,6> kBindingList
628 {
629 ":POWERTITLE",
630 ":POWERSUB",
631 ":POWERDESC",
632 ":POWERCATTYPE",
633 ":POWERGENRE",
634 ":POWERCALLSIGN",
635 };
636
637 static const std::array<QString,6> kOutputList
638 {
639 "program.title LIKE :POWERTITLE ",
640 "program.subtitle LIKE :POWERSUB ",
641 "program.description LIKE :POWERDESC ",
642 "program.category_type = :POWERCATTYPE ",
643 "programgenres.genre = :POWERGENRE ",
644 "channel.callsign = :POWERCALLSIGN ",
645 };
646
647 for (uint i = 0; i < (uint) field.size(); i++)
648 {
649 if (field[i].isEmpty())
650 continue;
651
652 if (!output.isEmpty())
653 output += "\nAND ";
654
655 output += kOutputList[i];
656 bindings[kBindingList[i]] =
657 (!kOutputList[i].contains("=")) ?
658 QString('%') + field[i] + QString('%') : field[i];
659 }
660
661 return output.contains("programgenres");
662}
663
665{
666 int pos = m_progList->GetCurrentPos();
667 if (pos >= 0 && pos < (int) m_itemList.size())
668 return m_itemList[pos];
669 return nullptr;
670}
671
673{
676 else
678}
679
681{
683
684 if (!pi || !pi->GetRecordingRuleID())
685 return;
686
687 auto *record = new RecordingRule();
688 if (!record->LoadByProgram(pi))
689 {
690 delete record;
691 return;
692 }
693
694 QString message = tr("Delete '%1' %2 rule?")
695 .arg(record->m_title, toString(pi->GetRecordingRuleType()));
696
697 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
698
699 auto *okPopup = new MythConfirmationDialog(
700 popupStack, message, true);
701
702 okPopup->SetReturnEvent(this, "deleterule");
703 okPopup->SetData(QVariant::fromValue(record));
704
705 if (okPopup->Create())
706 popupStack->AddScreen(okPopup);
707 else
708 delete okPopup;
709}
710
712{
714
715 if (!pi)
716 return;
717
718 QString message = tr("Delete this episode of '%1'?").arg(pi->GetTitle());
719
720 ShowOkPopup(message, this, &ProgLister::DeleteOldEpisode, true);
721}
722
724{
726 if (!ok || !pi)
727 return;
728
730 query.prepare(
731 "DELETE FROM oldrecorded "
732 "WHERE chanid = :CHANID AND "
733 " starttime = :STARTTIME");
734 query.bindValue(":CHANID", pi->GetChanID());
735 query.bindValue(":STARTTIME", pi->GetScheduledStartTime());
736
737 if (!query.exec())
738 MythDB::DBError("ProgLister::DeleteOldEpisode", query);
739
740 ScheduledRecording::RescheduleCheck(*pi, "DeleteOldEpisode");
741 FillItemList(true);
742}
743
745{
747
748 if (!pi)
749 return;
750
751 QString message = tr("Delete all episodes of '%1'?").arg(pi->GetTitle());
752
753 ShowOkPopup(message, this, &ProgLister::DeleteOldSeries, true);
754}
755
757{
759 if (!ok || !pi)
760 return;
761
763 query.prepare("DELETE FROM oldrecorded "
764 "WHERE title = :TITLE AND future = 0");
765 query.bindValue(":TITLE", pi->GetTitle());
766 if (!query.exec())
767 MythDB::DBError("ProgLister::DeleteOldSeries -- delete", query);
768
769 // Set the programid to the special value of "**any**" which the
770 // scheduler recognizes to mean the entire series was deleted.
771 RecordingInfo tempri(*pi);
772 tempri.SetProgramID("**any**");
773 ScheduledRecording::RescheduleCheck(tempri, "DeleteOldSeries");
774 FillItemList(true);
775}
776
778{
780
781 if (!pi)
782 return;
783
784 QString message = pi->toString(ProgramInfo::kTitleSubtitle, " - ");
785
786 if (!pi->GetDescription().isEmpty())
787 message += "\n\n" + pi->GetDescription();
788
789 message += "\n\n\n" + tr("NOTE: removing items from this list will not "
790 "delete any recordings.");
791
792 QString title = tr("Previously Recorded");
793
794 auto *menu = new MythMenu(title, message, this, "deletemenu");
795 if (pi->IsDuplicate())
796 menu->AddItem(tr("Allow this episode to re-record"));
797 else
798 menu->AddItem(tr("Never record this episode"));
799 menu->AddItem(tr("Remove this episode from the list"));
800 menu->AddItem(tr("Remove all episodes for this title"));
801 menu->AddItem(tr("Cancel"));
802
804 auto *menuPopup = new MythDialogBox(menu, mainStack, "deletepopup", true);
805
806 if (menuPopup->Create())
807 mainStack->AddScreen(menuPopup);
808 else
809 delete menuPopup;
810}
811
812void ProgLister::FillViewList(const QString &view)
813{
814 m_viewList.clear();
815 m_viewTextList.clear();
816
817 if (m_type == plChannel) // list by channel
818 {
820 0, true, "channum, chanid");
822
823 for (auto & channel : channels)
824 {
825 QString chantext = channel.GetFormatted(ChannelInfo::kChannelShort);
826
827 m_viewList.push_back(QString::number(channel.m_chanId));
828 m_viewTextList.push_back(chantext);
829 }
830
831 if (!view.isEmpty())
832 m_curView = m_viewList.indexOf(view);
833 }
834 else if (m_type == plCategory) // list by category
835 {
836 QDateTime query_starttime = m_startTime.addSecs(50 -
837 m_startTime.time().second());
839 query.prepare("SELECT g1.genre, g2.genre "
840 "FROM program "
841 "JOIN programgenres g1 ON "
842 " program.chanid = g1.chanid AND "
843 " program.starttime = g1.starttime "
844 "LEFT JOIN programgenres g2 ON "
845 " g1.chanid = g2.chanid AND "
846 " g1.starttime = g2.starttime "
847 "WHERE program.endtime > :PGILSTART "
848 "GROUP BY g1.genre, g2.genre;");
849 query.bindValue(":PGILSTART", query_starttime);
850
851 m_useGenres = false;
852
853 if (query.exec())
854 {
855 QString lastGenre1;
856
857 while (query.next())
858 {
859 m_useGenres = true;
860
861 QString genre1 = query.value(0).toString();
862 if (genre1.isEmpty())
863 continue;
864
865 if (genre1 != lastGenre1)
866 {
867 m_viewList.push_back(genre1);
868 m_viewTextList.push_back(genre1);
869 lastGenre1 = genre1;
870 }
871
872 QString genre2 = query.value(1).toString();
873 if (genre2.isEmpty() || genre2 == genre1)
874 continue;
875
876 m_viewList.push_back(genre1 + ":/:" + genre2);
877 m_viewTextList.push_back(" " + genre1 + " / " + genre2);
878 }
879 }
880
881 if (!m_useGenres)
882 {
883 query.prepare("SELECT category "
884 "FROM program "
885 "WHERE program.endtime > :PGILSTART "
886 "GROUP BY category");
887 query.bindValue(":PGILSTART", query_starttime);
888
889 if (query.exec())
890 {
891 while (query.next())
892 {
893 QString category = query.value(0).toString();
894 if (category.isEmpty())
895 continue;
896 category = query.value(0).toString();
897 m_viewList.push_back(category);
898 m_viewTextList.push_back(category);
899 }
900 }
901 }
902
903 if (!view.isEmpty())
904 m_curView = m_viewList.indexOf(view);
905 }
906 else if (m_type == plTitleSearch || m_type == plKeywordSearch ||
908 {
910 query.prepare("SELECT phrase FROM keyword "
911 "WHERE searchtype = :SEARCHTYPE;");
912 query.bindValue(":SEARCHTYPE", m_searchType);
913
914 if (query.exec())
915 {
916 while (query.next())
917 {
918 /* The keyword.phrase column uses utf8_bin collation, so
919 * Qt uses QString::fromAscii() for toString(). Explicitly
920 * convert the value using QString::fromUtf8() to prevent
921 * corruption. */
922 QString phrase = QString::fromUtf8(query.value(0)
923 .toByteArray().constData());
924 if (phrase.isEmpty())
925 continue;
926 m_viewList.push_back(phrase);
927 m_viewTextList.push_back(phrase);
928 }
929 }
930
931 if (!view.isEmpty())
932 {
933 m_curView = m_viewList.indexOf(view);
934
935 if (m_curView < 0)
936 {
937 const QString& qphrase = view;
938
940 query2.prepare("REPLACE INTO keyword (phrase, searchtype)"
941 "VALUES(:VIEW, :SEARCHTYPE );");
942 query2.bindValue(":VIEW", qphrase);
943 query2.bindValue(":SEARCHTYPE", m_searchType);
944 if (!query2.exec())
945 MythDB::DBError("ProgLister::FillViewList -- "
946 "replace keyword", query2);
947
948 m_viewList.push_back(qphrase);
949 m_viewTextList.push_back(qphrase);
950
951 m_curView = m_viewList.size() - 1;
952 }
953 }
954 else
955 {
956 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Failed to load viewList"));
957 m_curView = -1;
958 }
959 }
960 else if (m_type == plTitle)
961 {
962 if (!view.isEmpty())
963 {
964 m_viewList.push_back(view);
965 m_viewTextList.push_back(view);
966 m_curView = 0;
967 }
968 else
969 {
970 m_curView = -1;
971 }
972 }
973 else if (m_type == plNewListings)
974 {
975 m_viewList.push_back("all");
976 m_viewTextList.push_back(tr("All"));
977
978 m_viewList.push_back("premieres");
979 m_viewTextList.push_back(tr("Premieres"));
980
981 m_viewList.push_back("movies");
982 m_viewTextList.push_back(tr("Movies"));
983
984 m_viewList.push_back("series");
985 m_viewTextList.push_back(tr("Series"));
986
987 m_viewList.push_back("specials");
988 m_viewTextList.push_back(tr("Specials"));
989
990 if (!view.isEmpty())
991 m_curView = m_viewList.indexOf(view);
992 }
993 else if (m_type == plMovies)
994 {
995 m_viewList.push_back(">= 0.0");
996 m_viewTextList.push_back(tr("All"));
997 m_viewList.push_back("= 0.0");
998 m_viewTextList.push_back(tr("Unrated"));
999 m_viewList.push_back(QString(">= %1").arg(((10 - 0.5) / 10.0) - 0.001));
1000 m_viewTextList.push_back(tr("%n star(s)", "", 10));
1001 for (int i = 9; i > 0; i--)
1002 {
1003 float stars = ((i - 0.5F ) / 10.0F) - 0.001F;
1004 m_viewList.push_back(QString(">= %1").arg(stars));
1005 m_viewTextList.push_back(tr("%n star(s) and above", "", i));
1006 }
1007
1008 if (!view.isEmpty())
1009 m_curView = m_viewList.indexOf(view);
1010 }
1011 else if (m_type == plTime)
1012 {
1013 m_curView = 0;
1017 }
1018 else if (m_type == plSQLSearch)
1019 {
1020 m_curView = 0;
1021 m_viewList.push_back(view);
1022 m_viewTextList.push_back(tr("Power Recording Rule"));
1023 }
1024 else if (m_type == plRecordid)
1025 {
1026 m_curView = 0;
1027
1029 query.prepare("SELECT title FROM record "
1030 "WHERE recordid = :RECORDID");
1031 query.bindValue(":RECORDID", view);
1032
1033 if (query.exec() && query.next())
1034 {
1035 QString title = query.value(0).toString();
1036 m_viewList.push_back(view);
1037 m_viewTextList.push_back(title);
1038 }
1039 }
1040 else if (m_type == plStoredSearch) // stored searches
1041 {
1043 query.prepare("SELECT rulename FROM customexample "
1044 "WHERE search > 0 ORDER BY rulename;");
1045
1046 if (query.exec())
1047 {
1048 while (query.next())
1049 {
1050 QString rulename = query.value(0).toString();
1051 if (rulename.isEmpty() || rulename.trimmed().isEmpty())
1052 continue;
1053 rulename = query.value(0).toString();
1054 m_viewList.push_back(rulename);
1055 m_viewTextList.push_back(rulename);
1056 }
1057 }
1058 if (!view.isEmpty())
1059 m_curView = m_viewList.indexOf(view);
1060 }
1061 else if (m_type == plPreviouslyRecorded) // previously recorded
1062 {
1063 m_viewList.push_back("sort by time");
1064 m_viewTextList.push_back(tr("Time"));
1065
1066 m_viewList.push_back("reverse time");
1067 m_viewTextList.push_back(tr("Reverse Time"));
1068
1069 m_viewList.push_back("sort by title");
1070 m_viewTextList.push_back(tr("Title"));
1071
1072 m_viewList.push_back("reverse title");
1073 m_viewTextList.push_back(tr("Reverse Title"));
1074
1075 if (!view.isEmpty())
1076 m_curView = m_viewList.indexOf(view);
1077 }
1078
1079 if (m_curView >= m_viewList.size())
1080 m_curView = m_viewList.size() - 1;
1081}
1082
1083static bool plTitleSort(const ProgramInfo *a, const ProgramInfo *b)
1084{
1085 if (a->GetSortTitle() != b->GetSortTitle())
1087 if (a->GetSortSubtitle() != b->GetSortSubtitle())
1089
1090 if (a->GetRecordingStatus() == b->GetRecordingStatus())
1092
1096 return true;
1100 return false;
1101
1104 return true;
1107 return false;
1108
1110};
1111
1112static bool plPrevTitleSort(const ProgramInfo *a, const ProgramInfo *b)
1113{
1114 if (a->GetSortTitle() != b->GetSortTitle())
1116 if (a->GetSortSubtitle() != b->GetSortSubtitle())
1118
1119 if (a->GetProgramID() != b->GetProgramID())
1120 return a->GetProgramID() < b->GetProgramID();
1121
1123};
1124
1125static bool plTimeSort(const ProgramInfo *a, const ProgramInfo *b)
1126{
1128 return (a->GetChanID() < b->GetChanID());
1129
1130 return (a->GetScheduledStartTime() < b->GetScheduledStartTime());
1131};
1132
1133void ProgLister::FillItemList(bool restorePosition, bool updateDisp)
1134{
1136 if (m_groupByText)
1138
1140 {
1141 if (!m_titleSort)
1142 {
1143 if (!m_reverseSort)
1144 m_curView = 0;
1145 else
1146 m_curView = 1;
1147 }
1148 else
1149 {
1150 if (!m_reverseSort)
1151 m_curView = 2;
1152 else
1153 m_curView = 3;
1154 }
1155 }
1156
1157 if (m_curView < 0)
1158 return;
1159
1160 QString where;
1161 QString qphrase = m_viewList[m_curView];
1162
1163 MSqlBindings bindings;
1164
1166 bindings[":PGILSTART"] =
1167 m_startTime.addSecs(50 - m_startTime.time().second());
1168
1169 if (m_type == plTitle) // per title listings
1170 {
1171 where = "WHERE channel.deleted IS NULL "
1172 " AND channel.visible > 0 "
1173 " AND program.endtime > :PGILSTART "
1174 " AND (program.title = :PGILPHRASE0 OR "
1175 " (program.seriesid <> '' AND "
1176 " program.seriesid = :PGILPHRASE1)) ";
1177 bindings[":PGILPHRASE0"] = qphrase;
1178 bindings[":PGILPHRASE1"] = m_extraArg;
1179 }
1180 else if (m_type == plNewListings) // what's new list
1181 {
1182 where = "LEFT JOIN oldprogram ON "
1183 " oldprogram.oldtitle = program.title "
1184 "WHERE channel.deleted IS NULL "
1185 " AND channel.visible > 0 "
1186 " AND program.endtime > :PGILSTART "
1187 " AND oldprogram.oldtitle IS NULL "
1188 " AND program.manualid = 0 ";
1189
1190 if (qphrase == "premieres")
1191 {
1192 where += " AND ( ";
1193 where += " ( program.originalairdate = DATE(";
1194 where += " CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM'))";
1195 where += " AND (program.category = 'Special' ";
1196 where += " OR program.programid LIKE 'EP%0001')) ";
1197 where += " OR (program.category_type='movie' ";
1198 where += " AND program.stars > 0.5 ";
1199 where += " AND program.airdate >= YEAR(NOW()) - 2) ";
1200 where += " ) ";
1201 }
1202 else if (qphrase == "movies")
1203 {
1204 where += " AND program.category_type = 'movie' ";
1205 }
1206 else if (qphrase == "series")
1207 {
1208 where += " AND program.category_type = 'series' ";
1209 }
1210 else if (qphrase == "specials")
1211 {
1212 where += " AND program.category_type = 'tvshow' ";
1213 }
1214 else
1215 {
1216 where += " AND (program.category_type <> 'movie' ";
1217 where += " OR program.airdate >= YEAR(NOW()) - 3) ";
1218 }
1219 }
1220 else if (m_type == plTitleSearch) // keyword search
1221 {
1222 where = "WHERE channel.deleted IS NULL "
1223 " AND channel.visible > 0 "
1224 " AND program.endtime > :PGILSTART "
1225 " AND program.title LIKE :PGILLIKEPHRASE0 ";
1226 bindings[":PGILLIKEPHRASE0"] = QString("%") + qphrase + '%';
1227 }
1228 else if (m_type == plKeywordSearch) // keyword search
1229 {
1230 where = "WHERE channel.deleted IS NULL "
1231 " AND channel.visible > 0 "
1232 " AND program.endtime > :PGILSTART "
1233 " AND (program.title LIKE :PGILLIKEPHRASE1 "
1234 " OR program.subtitle LIKE :PGILLIKEPHRASE2 "
1235 " OR program.description LIKE :PGILLIKEPHRASE3 ) ";
1236 bindings[":PGILLIKEPHRASE1"] = QString("%") + qphrase + '%';
1237 bindings[":PGILLIKEPHRASE2"] = QString("%") + qphrase + '%';
1238 bindings[":PGILLIKEPHRASE3"] = QString("%") + qphrase + '%';
1239 }
1240 else if (m_type == plPeopleSearch) // people search
1241 {
1242 where = ", people, credits "
1243 "WHERE channel.deleted IS NULL "
1244 " AND channel.visible > 0 "
1245 " AND program.endtime > :PGILSTART "
1246 " AND people.name LIKE :PGILPHRASE1 "
1247 " AND credits.person = people.person "
1248 " AND program.chanid = credits.chanid "
1249 " AND program.starttime = credits.starttime";
1250 bindings[":PGILPHRASE1"] = qphrase;
1251 }
1252 else if (m_type == plPowerSearch) // complex search
1253 {
1254 QString powerWhere;
1255 MSqlBindings powerBindings;
1256
1257 bool genreflag = PowerStringToSQL(qphrase, powerWhere, powerBindings);
1258
1259 if (!powerWhere.isEmpty())
1260 {
1261 if (genreflag)
1262 {
1263 where = QString("LEFT JOIN programgenres ON "
1264 "program.chanid = programgenres.chanid AND "
1265 "program.starttime = programgenres.starttime ");
1266 }
1267
1268 where += QString("WHERE channel.deleted IS NULL "
1269 " AND channel.visible > 0 "
1270 " AND program.endtime > :PGILSTART "
1271 " AND ( ") + powerWhere + " ) ";
1272 MSqlAddMoreBindings(bindings, powerBindings);
1273 }
1274 }
1275 else if (m_type == plSQLSearch) // complex search
1276 {
1277 qphrase.remove(RecordingInfo::kReLeadingAnd);
1278 where = QString("WHERE channel.deleted iS NULL "
1279 " AND channel.visible > 0 "
1280 " AND program.endtime > :PGILSTART "
1281 " AND ( %1 ) ").arg(qphrase);
1282 if (!m_extraArg.isEmpty())
1283 where = m_extraArg + ' ' + where;
1284 }
1285 else if (m_type == plChannel) // list by channel
1286 {
1287 groupBy = ProgGroupBy::None;
1288 where = "WHERE channel.deleted IS NULL "
1289 " AND channel.visible > 0 "
1290 " AND program.endtime > :PGILSTART "
1291 " AND channel.chanid = :PGILPHRASE2 ";
1292 bindings[":PGILPHRASE2"] = qphrase;
1293 }
1294 else if (m_type == plCategory) // list by category
1295 {
1296 if (!m_useGenres)
1297 {
1298 where = "WHERE channel.deleted IS NULL "
1299 " AND channel.visible > 0 "
1300 " AND program.endtime > :PGILSTART "
1301 " AND program.category = :PGILPHRASE3 ";
1302 bindings[":PGILPHRASE3"] = qphrase;
1303 }
1304 else if (m_viewList[m_curView].indexOf(":/:") < 0)
1305 {
1306 where = "JOIN programgenres g ON "
1307 " program.chanid = g.chanid AND "
1308 " program.starttime = g.starttime AND "
1309 " genre = :PGILPHRASE4 "
1310 "WHERE channel.deleted IS NULL "
1311 " AND channel.visible > 0 "
1312 " AND program.endtime > :PGILSTART ";
1313 bindings[":PGILPHRASE4"] = qphrase;
1314 }
1315 else
1316 {
1317 where = "JOIN programgenres g1 ON "
1318 " program.chanid = g1.chanid AND "
1319 " program.starttime = g1.starttime AND "
1320 " g1.genre = :GENRE1 "
1321 "JOIN programgenres g2 ON "
1322 " program.chanid = g2.chanid AND "
1323 " program.starttime = g2.starttime AND "
1324 " g2.genre = :GENRE2 "
1325 "WHERE channel.deleted IS NULL "
1326 " AND channel.visible > 0 "
1327 " AND program.endtime > :PGILSTART ";
1328 bindings[":GENRE1"] = m_viewList[m_curView].section(":/:", 0, 0);
1329 bindings[":GENRE2"] = m_viewList[m_curView].section(":/:", 1, 1);
1330 }
1331 }
1332 else if (m_type == plMovies) // list movies
1333 {
1334 where = "WHERE channel.deleted IS NULL "
1335 " AND channel.visible > 0 "
1336 " AND program.endtime > :PGILSTART "
1337 " AND program.category_type = 'movie' "
1338 " AND program.stars " + qphrase + ' ';
1339 }
1340 else if (m_type == plTime) // list by time
1341 {
1342 groupBy = ProgGroupBy::ChanNum;
1343 QDateTime searchTime(m_searchTime);
1344 searchTime.setTime(QTime(searchTime.time().hour(), 0, 0));
1345 bindings[":PGILSEARCHTIME1"] = searchTime;
1346 where = "WHERE channel.deleted IS NULL "
1347 " AND channel.visible > 0 "
1348 " AND program.starttime >= :PGILSEARCHTIME1 ";
1349 if (m_titleSort)
1350 {
1351 where += " AND program.starttime < DATE_ADD(:PGILSEARCHTIME2, "
1352 "INTERVAL '1' HOUR) ";
1353 bindings[":PGILSEARCHTIME2"] = bindings[":PGILSEARCHTIME1"];
1354 }
1355 }
1356 else if (m_type == plRecordid) // list by recordid
1357 {
1358 where = "JOIN recordmatch ON "
1359 " (program.starttime = recordmatch.starttime "
1360 " AND program.chanid = recordmatch.chanid) "
1361 "WHERE channel.deleted IS NULL "
1362 " AND channel.visible > 0 "
1363 " AND program.endtime > :PGILSTART "
1364 " AND recordmatch.recordid = :PGILPHRASE5 ";
1365 bindings[":PGILPHRASE5"] = qphrase;
1366 }
1367 else if (m_type == plStoredSearch) // stored search
1368 {
1370 query.prepare("SELECT fromclause, whereclause FROM customexample "
1371 "WHERE rulename = :RULENAME;");
1372 query.bindValue(":RULENAME", qphrase);
1373
1374 if (query.exec() && query.next())
1375 {
1376 QString fromc = query.value(0).toString();
1377 QString wherec = query.value(1).toString();
1378
1379 where = QString("WHERE channel.deleted IS NULL "
1380 " AND channel.visible > 0 "
1381 " AND program.endtime > :PGILSTART "
1382 " AND ( %1 ) ").arg(wherec);
1383 if (!fromc.isEmpty())
1384 where = fromc + ' ' + where;
1385 }
1386 }
1387 else if (m_type == plPreviouslyRecorded)
1388 {
1389 if (m_recid && !m_title.isEmpty())
1390 {
1391 where = QString("AND ( recordid = %1 OR title = :MTITLE )")
1392 .arg(m_recid);
1393 bindings[":MTITLE"] = m_title;
1394 }
1395 else if (!m_title.isEmpty())
1396 {
1397 where = QString("AND title = :MTITLE ");
1398 bindings[":MTITLE"] = m_title;
1399 }
1400 else if (m_recid)
1401 {
1402 where = QString("AND recordid = %1 ").arg(m_recid);
1403 }
1404 }
1405
1406 ProgramInfo selected;
1407 const ProgramInfo *selectedP = (restorePosition) ? GetCurrentProgram() : nullptr;
1408 if (selectedP)
1409 {
1410 selected = *selectedP;
1411 selectedP = &selected;
1412 }
1413
1414 // Save a copy of m_itemList so that deletion of the ProgramInfo
1415 // objects can be deferred until background processing of old
1416 // ProgramInfo objects has completed.
1420 m_itemList.clear();
1422
1424 {
1425 LoadFromOldRecorded(m_itemList, where, bindings);
1426 }
1427 else
1428 {
1430 LoadFromProgram(m_itemList, where, bindings, m_schedList, groupBy);
1431 }
1432
1434 {
1437 {
1438 // Prune to one per title
1439 QString curtitle;
1440 auto it = m_itemList.begin();
1441 while (it != m_itemList.end())
1442 {
1443 if ((*it)->GetSortTitle() != curtitle)
1444 {
1445 curtitle = (*it)->GetSortTitle();
1446 ++it;
1447 }
1448 else
1449 {
1450 it = m_itemList.erase(it);
1451 }
1452 }
1453 }
1454 }
1455
1456 if (!m_titleSort)
1458
1459 if (updateDisp)
1460 UpdateDisplay(selectedP);
1461}
1462
1464{
1465 if (!m_titleSort)
1466 return kTimeSort;
1468 return kPrevTitleSort;
1469 return kTitleSort;
1470}
1471
1472void ProgLister::SortList(SortBy sortby, bool reverseSort)
1473{
1474 if (reverseSort)
1475 {
1476 if (kTimeSort == sortby)
1477 {
1478 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList), plTimeSort);
1479 }
1480 else if (kPrevTitleSort == sortby)
1481 {
1482 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList),
1484 }
1485 else
1486 {
1487 std::ranges::stable_sort(std::ranges::reverse_view(m_itemList), plTitleSort);
1488 }
1489 }
1490 else
1491 {
1492 if (kTimeSort == sortby)
1493 {
1494 std::ranges::stable_sort(m_itemList, plTimeSort);
1495 }
1496 else if (kPrevTitleSort == sortby)
1497 {
1498 std::ranges::stable_sort(m_itemList,plPrevTitleSort);
1499 }
1500 else
1501 {
1502 std::ranges::stable_sort(m_itemList, plTitleSort);
1503 }
1504 }
1505}
1506
1508{
1509 InfoMap infoMap;
1510 ProgramInfo pginfo;
1511 pginfo.ToMap(infoMap);
1512 ResetMap(infoMap);
1513
1514 if (m_positionText)
1516}
1517
1519{
1520 int offset = 0;
1521
1522 if (selected)
1524
1525 m_progList->Reset();
1526
1527 if (m_messageText)
1529
1531
1532 if (m_curviewText && m_curView >= 0)
1534
1536
1537 if (selected)
1538 RestoreSelection(selected, offset);
1539 else if (m_selectedTime.isValid())
1540 {
1541 size_t i = 0;
1542 for (i = 0; i < m_itemList.size(); ++i)
1543 {
1544 if (m_selectedTime <= m_itemList[i]->GetScheduledStartTime())
1545 break;
1546 }
1548 m_selectedTime = QDateTime();
1549 }
1550}
1551
1553 int selectedOffset)
1554{
1555 using ProgramInfoSortFn = bool (*)(const ProgramInfo *a, const ProgramInfo *b);
1556 ProgramInfoSortFn comp { nullptr };
1557 if (!m_titleSort)
1558 comp = plTimeSort;
1559 else if (m_type == plPreviouslyRecorded)
1560 comp = plPrevTitleSort;
1561 else
1562 comp = plTitleSort;
1563
1564 int i = 0;
1565 for (i = m_itemList.size() - 2; i >= 0; i--)
1566 {
1567 bool dobreak = false;
1568 if (m_reverseSort)
1569 dobreak = comp(selected, m_itemList[i]);
1570 else
1571 dobreak = comp(m_itemList[i], selected);
1572 if (dobreak)
1573 break;
1574 }
1575
1576 m_progList->SetItemCurrent(i + 1, i + 1 - selectedOffset);
1577}
1578
1580{
1581 auto *pginfo = item->GetData().value<ProgramInfo*>();
1582
1583 if (item->GetText("is_item_initialized").isNull())
1584 {
1585 InfoMap infoMap;
1586 pginfo->ToMap(infoMap);
1587
1588 QString state = RecStatus::toUIState(pginfo->GetRecordingStatus());
1589 if ((state == "warning") && (plPreviouslyRecorded == m_type))
1590 state = "disabled";
1591
1592 item->SetTextFromMap(infoMap, state);
1593
1594 if (m_type == plTitle)
1595 {
1596 QString tempSubTitle = pginfo->GetSubtitle();
1597 if (tempSubTitle.trimmed().isEmpty())
1598 tempSubTitle = pginfo->GetTitle();
1599 item->SetText(tempSubTitle, "titlesubtitle", state);
1600 }
1601
1602 item->DisplayState(QString::number(pginfo->GetStars(10)),
1603 "ratingstate");
1604
1605 item->DisplayState(state, "status");
1606
1607 // Mark this button list item as initialized.
1608 item->SetText("yes", "is_item_initialized");
1609 }
1610}
1611
1613{
1614 for (auto *it : m_itemList)
1615 new MythUIButtonListItem(m_progList, "", QVariant::fromValue(it));
1617
1618 if (m_positionText)
1619 {
1621 tr("%1 of %2", "Current position in list where %1 is the "
1622 "position, %2 is the total count")
1623 .arg(QLocale::system().toString(m_progList->IsEmpty() ? 0 : m_progList->GetCurrentPos() + 1),
1624 QLocale::system().toString(m_progList->GetCount())));
1625 }
1626}
1627
1629{
1630 if (!item)
1631 {
1633 return;
1634 }
1635
1636 auto *pginfo = item->GetData().value<ProgramInfo*> ();
1637 if (!pginfo)
1638 {
1640 return;
1641 }
1642
1643 InfoMap infoMap;
1644 pginfo->ToMap(infoMap);
1645 SetTextFromMap(infoMap);
1646
1647 if (m_positionText)
1648 {
1650 tr("%1 of %2", "Current position in list where %1 is the "
1651 "position, %2 is the total count")
1652 .arg(QLocale::system().toString(m_progList->IsEmpty() ? 0 : m_progList->GetCurrentPos() + 1),
1653 QLocale::system().toString(m_progList->GetCount())));
1654 }
1655
1656 MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
1657 (GetChild("ratingstate"));
1658
1659 if (ratingState)
1660 {
1661 QString rating = QString::number(pginfo->GetStars(10));
1662 ratingState->DisplayState(rating);
1663 }
1664}
1665
1666void ProgLister::customEvent(QEvent *event)
1667{
1668 bool needUpdate = false;
1669
1670 if (event->type() == DialogCompletionEvent::kEventType)
1671 {
1672 auto *dce = (DialogCompletionEvent*)(event);
1673
1674 QString resultid = dce->GetId();
1675// QString resulttext = dce->GetResultText();
1676 int buttonnum = dce->GetResult();
1677
1678 if (resultid == "sortgroupmenu")
1679 {
1680 switch (buttonnum)
1681 {
1682 case 0:
1684 needUpdate = true;
1685 break;
1686 case 1:
1687 m_titleSort = true;
1688 m_reverseSort = false;
1689 needUpdate = true;
1690 break;
1691 case 2:
1692 m_titleSort = false;
1694 needUpdate = true;
1695 break;
1696 default:
1698 break;
1699 }
1700 }
1701 else if (resultid == "deletemenu")
1702 {
1703 switch (buttonnum)
1704 {
1705 case 0:
1706 {
1708 if (pi)
1709 {
1710 RecordingInfo ri(*pi);
1711 if (ri.IsDuplicate())
1712 ri.ForgetHistory();
1713 else
1714 ri.SetDupHistory();
1715 *pi = ri;
1716 }
1717 break;
1718 }
1719 case 1:
1721 break;
1722 case 2:
1724 break;
1725 }
1726 }
1727 else if (resultid == "deleterule")
1728 {
1729 auto *record = dce->GetData().value<RecordingRule *>();
1730 if (record && buttonnum > 0 && !record->Delete())
1731 {
1732 LOG(VB_GENERAL, LOG_ERR, LOC +
1733 "Failed to delete recording rule");
1734 }
1735 delete record;
1736 }
1737 else
1738 {
1740 }
1741 }
1742 else if (event->type() == ScreenLoadCompletionEvent::kEventType)
1743 {
1744 auto *slce = (ScreenLoadCompletionEvent*)(event);
1745 QString id = slce->GetId();
1746
1747 if (id == objectName())
1748 {
1749 CloseBusyPopup(); // opened by LoadInBackground()
1750 UpdateDisplay();
1751
1754 }
1755 }
1756 else if (event->type() == MythEvent::kMythEventMessage)
1757 {
1758 auto *me = dynamic_cast<MythEvent *>(event);
1759 if (me == nullptr)
1760 return;
1761
1762 const QString& message = me->Message();
1763
1764 if (m_allowViewDialog && message == "CHOOSE_VIEW")
1766 else if (message == "SCHEDULE_CHANGE"
1767 || message == "GROUPBY_CHANGE")
1768 needUpdate = true;
1769 }
1770
1771 if (needUpdate)
1772 FillItemList(true);
1773}
std::vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:130
iterator erase(iterator it)
void clear(void)
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:838
QVariant value(int i) const
Definition: mythdbcon.h:204
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
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:1579
void ShowChooseViewMenu(void)
Definition: proglist.cpp:491
MythUIText * m_groupByText
Definition: proglist.h:141
void SortList(SortBy sortby, bool reverseSort)
Definition: proglist.cpp:1472
QDateTime m_selectedTime
Definition: proglist.h:113
friend class PhrasePopup
Definition: proglist.h:37
ProgramList m_schedList
Definition: proglist.h:125
QStringList m_viewTextList
Definition: proglist.h:121
MythUIButtonList * m_progList
Definition: proglist.h:139
SortBy GetSortBy(void) const
Definition: proglist.cpp:1463
bool m_allowEvents
Definition: proglist.h:131
void SwitchToNextView(void)
Definition: proglist.cpp:422
void customEvent(QEvent *event) override
Definition: proglist.cpp:1666
bool m_reverseSort
Definition: proglist.h:133
TV * m_player
Definition: proglist.h:145
ProgListType m_type
Definition: proglist.h:107
void DeleteOldSeries(bool ok)
Definition: proglist.cpp:756
MythUIText * m_positionText
Definition: proglist.h:138
QString m_title
Definition: proglist.h:109
void SetViewFromList(const QString &item)
Definition: proglist.cpp:607
static void * RunProgramList(void *player, ProgListType pltype, const QString &extraArg)
Definition: proglist.cpp:38
QString m_view
Definition: proglist.h:118
void FillItemList(bool restorePosition, bool updateDisp=true)
Definition: proglist.cpp:1133
void ShowDeleteOldEpisodeMenu(void)
Definition: proglist.cpp:711
void HandleSelected(MythUIButtonListItem *item)
Definition: proglist.cpp:1628
void FillViewList(const QString &view)
Definition: proglist.cpp:812
void UpdateButtonList(void)
Definition: proglist.cpp:1612
void ShowOldRecordedMenu(void)
Definition: proglist.cpp:777
void RestoreSelection(const ProgramInfo *selected, int selectedOffset)
Definition: proglist.cpp:1552
MythUIText * m_curviewText
Definition: proglist.h:137
static bool PowerStringToSQL(const QString &qphrase, QString &output, MSqlBindings &bindings)
Definition: proglist.cpp:614
void SetViewFromTime(QDateTime searchTime)
Definition: proglist.cpp:593
int m_curView
Definition: proglist.h:119
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:140
friend class PowerSearchPopup
Definition: proglist.h:39
void ShowDeleteItemMenu(void)
Definition: proglist.cpp:672
QDateTime m_searchTime
Definition: proglist.h:112
QString m_channelOrdering
Definition: proglist.h:114
void Close(void) override
Definition: proglist.cpp:152
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: proglist.cpp:249
@ kTimeSort
Definition: proglist.h:97
@ kPrevTitleSort
Definition: proglist.h:97
@ kTitleSort
Definition: proglist.h:97
bool Create(void) override
Definition: proglist.cpp:161
bool m_useGenres
Definition: proglist.h:134
RecSearchType m_searchType
Definition: proglist.h:116
QStringList m_viewList
Definition: proglist.h:120
uint m_recid
Definition: proglist.h:108
void UpdateKeywordInDB(const QString &text, const QString &oldValue)
Definition: proglist.cpp:446
ProgramList m_itemListSave
Definition: proglist.h:124
void ShowDeleteOldSeriesMenu(void)
Definition: proglist.cpp:744
void ShowDeleteRuleMenu(void)
Definition: proglist.cpp:680
QString m_extraArg
Definition: proglist.h:110
void UpdateDisplay(const ProgramInfo *selected=nullptr)
Definition: proglist.cpp:1518
ProgramInfo * GetCurrentProgram(void) const override
Definition: proglist.cpp:664
void ShowMenu(void) override
Definition: proglist.cpp:337
MythUIText * m_schedText
Definition: proglist.h:136
void DeleteOldEpisode(bool ok)
Definition: proglist.cpp:723
void ClearCurrentProgramInfo(void)
Definition: proglist.cpp:1507
bool m_titleSort
Definition: proglist.h:132
ProgramList m_itemList
Definition: proglist.h:123
void SwitchToPreviousView(void)
Definition: proglist.cpp:399
QDateTime m_startTime
Definition: proglist.h:111
bool m_allowViewDialog
Definition: proglist.h:143
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:156
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:984
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:119
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:1112
static bool plTitleSort(const ProgramInfo *a, const ProgramInfo *b)
Definition: proglist.cpp:1083
static bool plTimeSort(const ProgramInfo *a, const ProgramInfo *b)
Definition: proglist.cpp:1125
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