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