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