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