MythTV  master
playbackbox.cpp
Go to the documentation of this file.
1 
2 #include "playbackbox.h"
3 
4 // QT
5 #include <QCoreApplication>
6 #include <QDateTime>
7 #include <QLocale>
8 #include <QTimer>
9 #include <QMap>
10 
11 // MythTV
12 #include "mythnotificationcenter.h" // for ShowNotificationError, etc
13 #include "mythuimetadataresults.h"
14 #include "previewgeneratorqueue.h"
15 #include "mythprogressdialog.h"
16 #include "mythuiprogressbar.h"
17 #include "mythuibuttonlist.h"
18 #include "mythcorecontext.h"
19 #include "mythmainwindow.h" // for GetMythMainWindow, etc
20 #include "mythscreenstack.h" // for MythScreenStack
21 #include "mythuistatetype.h"
22 #include "mythuicheckbox.h"
23 #include "mythuitextedit.h"
24 #include "recordingtypes.h"
25 #include "mythuiactions.h" // for ACTION_1
26 #include "mythuispinbox.h"
27 #include "mythdialogbox.h"
28 #include "recordinginfo.h"
29 #include "recordingrule.h"
30 #include "programtypes.h" // for AudioProps, SubtitleTypes, etc
31 #include "mythuibutton.h"
32 #include "mythlogging.h"
33 #include "mythuiimage.h"
34 #include "programinfo.h"
35 #include "mythuitext.h"
36 #include "tv_actions.h" // for ACTION_LISTRECORDEDEPISODES, etc
37 #include "mythdbcon.h"
38 #include "mythevent.h" // for MythEvent, etc
39 #include "playgroup.h"
40 #include "mythdb.h"
41 #include "mythdate.h"
42 #include "tv.h"
43 
44 #ifdef _MSC_VER
45 # include "compat.h" // for random
46 #endif
47 
48 // Mythfrontend
49 #include "playbackboxlistitem.h"
50 
51 #define LOC QString("PlaybackBox: ")
52 #define LOC_WARN QString("PlaybackBox Warning: ")
53 #define LOC_ERR QString("PlaybackBox Error: ")
54 
55 static const QString _Location = "Playback Box";
56 
57 static int comp_programid(const ProgramInfo *a, const ProgramInfo *b)
58 {
59  if (a->GetProgramID() == b->GetProgramID())
60  return (a->GetRecordingStartTime() <
61  b->GetRecordingStartTime() ? 1 : -1);
62  return (a->GetProgramID() < b->GetProgramID() ? 1 : -1);
63 }
64 
65 static int comp_programid_rev(const ProgramInfo *a, const ProgramInfo *b)
66 {
67  if (a->GetProgramID() == b->GetProgramID())
68  return (a->GetRecordingStartTime() >
69  b->GetRecordingStartTime() ? 1 : -1);
70  return (a->GetProgramID() > b->GetProgramID() ? 1 : -1);
71 }
72 
73 static int comp_originalAirDate(const ProgramInfo *a, const ProgramInfo *b)
74 {
75  QDate dt1 = (a->GetOriginalAirDate().isValid()) ?
76  a->GetOriginalAirDate() : a->GetScheduledStartTime().date();
77  QDate dt2 = (b->GetOriginalAirDate().isValid()) ?
78  b->GetOriginalAirDate() : b->GetScheduledStartTime().date();
79 
80  if (dt1 == dt2)
81  return (a->GetRecordingStartTime() <
82  b->GetRecordingStartTime() ? 1 : -1);
83  return (dt1 < dt2 ? 1 : -1);
84 }
85 
86 static int comp_originalAirDate_rev(const ProgramInfo *a, const ProgramInfo *b)
87 {
88  QDate dt1 = (a->GetOriginalAirDate().isValid()) ?
89  a->GetOriginalAirDate() : a->GetScheduledStartTime().date();
90  QDate dt2 = (b->GetOriginalAirDate().isValid()) ?
91  b->GetOriginalAirDate() : b->GetScheduledStartTime().date();
92 
93  if (dt1 == dt2)
94  return (a->GetRecordingStartTime() >
95  b->GetRecordingStartTime() ? 1 : -1);
96  return (dt1 > dt2 ? 1 : -1);
97 }
98 
99 static int comp_recpriority2(const ProgramInfo *a, const ProgramInfo *b)
100 {
101  if (a->GetRecordingPriority2() == b->GetRecordingPriority2())
102  return (a->GetRecordingStartTime() <
103  b->GetRecordingStartTime() ? 1 : -1);
104  return (a->GetRecordingPriority2() <
105  b->GetRecordingPriority2() ? 1 : -1);
106 }
107 
108 static int comp_recordDate(const ProgramInfo *a, const ProgramInfo *b)
109 {
110  if (a->GetScheduledStartTime().date() == b->GetScheduledStartTime().date())
111  return (a->GetRecordingStartTime() <
112  b->GetRecordingStartTime() ? 1 : -1);
113  return (a->GetScheduledStartTime().date() <
114  b->GetScheduledStartTime().date() ? 1 : -1);
115 }
116 
117 static int comp_recordDate_rev(const ProgramInfo *a, const ProgramInfo *b)
118 {
119  if (a->GetScheduledStartTime().date() == b->GetScheduledStartTime().date())
120  return (a->GetRecordingStartTime() >
121  b->GetRecordingStartTime() ? 1 : -1);
122  return (a->GetScheduledStartTime().date() >
123  b->GetScheduledStartTime().date() ? 1 : -1);
124 }
125 
126 static int comp_season(const ProgramInfo *a, const ProgramInfo *b)
127 {
128  if (a->GetSeason() == 0 || b->GetSeason() == 0)
129  return comp_originalAirDate(a, b);
130  if (a->GetSeason() != b->GetSeason())
131  return (a->GetSeason() < b->GetSeason() ? 1 : -1);
132  if (a->GetEpisode() == 0 && b->GetEpisode() == 0)
133  return comp_originalAirDate(a, b);
134  return (a->GetEpisode() < b->GetEpisode() ? 1 : -1);
135 }
136 
137 static int comp_season_rev(const ProgramInfo *a, const ProgramInfo *b)
138 {
139  if (a->GetSeason() == 0 || b->GetSeason() == 0)
140  return comp_originalAirDate_rev(a, b);
141  if (a->GetSeason() != b->GetSeason())
142  return (a->GetSeason() > b->GetSeason() ? 1 : -1);
143  if (a->GetEpisode() == 0 && b->GetEpisode() == 0)
144  return comp_originalAirDate_rev(a, b);
145  return (a->GetEpisode() > b->GetEpisode() ? 1 : -1);
146 }
147 
149  const ProgramInfo *a, const ProgramInfo *b)
150 {
151  return comp_programid(a, b) < 0;
152 }
153 
155  const ProgramInfo *a, const ProgramInfo *b)
156 {
157  return comp_programid_rev(a, b) < 0;
158 }
159 
161  const ProgramInfo *a, const ProgramInfo *b)
162 {
163  return comp_originalAirDate(a, b) < 0;
164 }
165 
167  const ProgramInfo *a, const ProgramInfo *b)
168 {
169  return comp_originalAirDate_rev(a, b) < 0;
170 }
171 
173  const ProgramInfo *a, const ProgramInfo *b)
174 {
175  return comp_recpriority2(a, b) < 0;
176 }
177 
179  const ProgramInfo *a, const ProgramInfo *b)
180 {
181  return comp_recordDate(a, b) < 0;
182 }
183 
185  const ProgramInfo *a, const ProgramInfo *b)
186 {
187  return comp_recordDate_rev(a, b) < 0;
188 }
189 
191  const ProgramInfo *a, const ProgramInfo *b)
192 {
193  return comp_season(a, b) < 0;
194 }
195 
197  const ProgramInfo *a, const ProgramInfo *b)
198 {
199  return comp_season_rev(a, b) < 0;
200 }
201 
202 static const uint s_artDelay[] =
204 
206  PlaybackBox::ViewMask toggle)
207 {
208  // can only toggle a single bit at a time
209  if ((mask & toggle))
210  return (PlaybackBox::ViewMask)(mask & ~toggle);
211  return (PlaybackBox::ViewMask)(mask | toggle);
212 }
213 
214 static QString construct_sort_title(
215  QString title, PlaybackBox::ViewMask viewmask,
216  PlaybackBox::ViewTitleSort sortType, int recpriority)
217 {
218  if (title.isEmpty())
219  return title;
220 
221  QString sTitle = title;
222 
223  if (viewmask == PlaybackBox::VIEW_TITLES &&
225  {
226  // Also incorporate recpriority (reverse numeric sort). In
227  // case different episodes of a recording schedule somehow
228  // have different recpriority values (e.g., manual fiddling
229  // with database), the title will appear once for each
230  // distinct recpriority value among its episodes.
231  //
232  // Deal with QMap sorting. Positive recpriority values have a
233  // '+' prefix (QMap alphabetically sorts before '-'). Positive
234  // recpriority values are "inverted" by subtracting them from
235  // 1000, so that high recpriorities are sorted first (QMap
236  // alphabetically). For example:
237  //
238  // recpriority => sort key
239  // 95 +905
240  // 90 +910
241  // 89 +911
242  // 1 +999
243  // 0 -000
244  // -5 -005
245  // -10 -010
246  // -99 -099
247 
248  QString sortprefix;
249  if (recpriority > 0)
250  sortprefix = QString("+%1").arg(1000 - recpriority, 3, 10, QChar('0'));
251  else
252  sortprefix = QString("-%1").arg(-recpriority, 3, 10, QChar('0'));
253 
254  sTitle = sortprefix + '-' + sTitle;
255  }
256  return sTitle;
257 }
258 
259 static QString extract_main_state(const ProgramInfo &pginfo, const TV *player)
260 {
261  QString state("normal");
262  if (pginfo.GetFilesize() == 0)
263  state = "error";
264  else if (pginfo.GetRecordingStatus() == RecStatus::Recording ||
267  state = "running";
268 
269  if (((pginfo.GetRecordingStatus() != RecStatus::Recording) &&
270  (pginfo.GetAvailableStatus() != asAvailable) &&
271  (pginfo.GetAvailableStatus() != asNotYetAvailable)) ||
272  (player && player->IsSameProgram(0, &pginfo)))
273  {
274  state = "disabled";
275  }
276 
277  if ((state == "normal" || state == "running") &&
278  pginfo.GetVideoProperties() & VID_DAMAGED)
279  {
280  state = "warning";
281  }
282 
283  return state;
284 }
285 
287 {
288  QString job = "default";
289 
290  if (pginfo.GetRecordingStatus() == RecStatus::Recording ||
293  job = "recording";
295  JOB_TRANSCODE, pginfo.GetChanID(),
296  pginfo.GetRecordingStartTime()))
297  job = "transcoding";
299  JOB_COMMFLAG, pginfo.GetChanID(),
300  pginfo.GetRecordingStartTime()))
301  job = "commflagging";
302 
303  return job;
304 }
305 
307 {
308  // commflagged can be yes, no or processing
310  pginfo.GetRecordingStartTime()))
311  return "running";
313  pginfo.GetRecordingStartTime()))
314  return "queued";
315 
316  return ((pginfo.GetProgramFlags() & FL_COMMFLAG) ? "yes" : "no");
317 }
318 
319 
320 static QString extract_subtitle(
321  const ProgramInfo &pginfo, const QString &groupname)
322 {
323  QString subtitle;
324  if (groupname != pginfo.GetTitle().toLower())
325  {
326  subtitle = pginfo.toString(ProgramInfo::kTitleSubtitle, " - ");
327  }
328  else
329  {
330  subtitle = pginfo.GetSubtitle();
331  if (subtitle.trimmed().isEmpty())
332  subtitle = pginfo.GetTitle();
333  }
334  return subtitle;
335 }
336 
337 static void push_onto_del(QStringList &list, const ProgramInfo &pginfo)
338 {
339  list.clear();
340  list.push_back(QString::number(pginfo.GetRecordingID()));
341  list.push_back(QString() /* force Delete */);
342  list.push_back(QString()); /* forget history */
343 }
344 
345 static bool extract_one_del(QStringList &list, uint &recordingID)
346 {
347  if (list.size() < 3)
348  {
349  list.clear();
350  return false;
351  }
352 
353  recordingID = list[0].toUInt();
354 
355  list.pop_front();
356  list.pop_front();
357  list.pop_front();
358 
359  if (recordingID == 0U) {
360  LOG(VB_GENERAL, LOG_ERR, LOC + "extract_one_del() invalid entry");
361  return false;
362  }
363  return true;
364 }
365 
366 void * PlaybackBox::RunPlaybackBox(void * player, bool showTV)
367 {
369 
370  PlaybackBox *pbb = new PlaybackBox(
371  mainStack,"playbackbox", (TV *)player, showTV);
372 
373  if (pbb->Create())
374  mainStack->AddScreen(pbb);
375  else
376  delete pbb;
377 
378  return nullptr;
379 }
380 
382  TV *player, bool /*showTV*/)
383  : ScheduleCommon(parent, name),
384  m_titleChaff(" \\(.*\\)$"),
385  // Artwork Variables
386  m_artHostOverride(),
387  // Recording Group settings
388  m_groupDisplayName(ProgramInfo::i18n("All Programs")),
389  m_recGroup("All Programs"),
390  m_watchGroupName(tr("Watch List")),
391  m_watchGroupLabel(m_watchGroupName.toLower()),
392 
393  // Other state
394  m_programInfoCache(this),
395  // Other
396  m_helper(this)
397 {
398  for (size_t i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
399  {
400  m_artImage[i] = nullptr;
401  m_artTimer[i] = new QTimer(this);
402  m_artTimer[i]->setSingleShot(true);
403  }
404 
405  m_recGroup = gCoreContext->GetSetting("DisplayRecGroup",
406  "All Programs");
407  int pbOrder = gCoreContext->GetNumSetting("PlayBoxOrdering", 3);
408  // Split out sort order modes, wacky order for backward compatibility
409  m_listOrder = (pbOrder >> 1) ^ (m_allOrder = pbOrder & 1);
410  m_watchListStart = gCoreContext->GetBoolSetting("PlaybackWLStart", false);
411 
412  m_watchListAutoExpire= gCoreContext->GetBoolSetting("PlaybackWLAutoExpire", false);
413  m_watchListMaxAge = gCoreContext->GetNumSetting("PlaybackWLMaxAge", 60);
414  m_watchListBlackOut = gCoreContext->GetNumSetting("PlaybackWLBlackOut", 2);
415 
416  bool displayCat = gCoreContext->GetBoolSetting("DisplayRecGroupIsCategory", false);
417 
419  "DisplayGroupDefaultViewMask",
421 
422  // Translate these external settings into mask values
423  if (gCoreContext->GetBoolSetting("PlaybackWatchList", true) &&
424  ((m_viewMask & VIEW_WATCHLIST) == 0))
425  {
427  gCoreContext->SaveSetting("DisplayGroupDefaultViewMask", (int)m_viewMask);
428  }
429  else if (! gCoreContext->GetBoolSetting("PlaybackWatchList", true) &&
430  ((m_viewMask & VIEW_WATCHLIST) != 0))
431  {
433  gCoreContext->SaveSetting("DisplayGroupDefaultViewMask", (int)m_viewMask);
434  }
435 
436  // This setting is deprecated in favour of viewmask, this just ensures the
437  // that it is converted over when upgrading from earlier versions
438  if (gCoreContext->GetBoolSetting("LiveTVInAllPrograms",false) &&
439  ((m_viewMask & VIEW_LIVETVGRP) == 0))
440  {
442  gCoreContext->SaveSetting("DisplayGroupDefaultViewMask", (int)m_viewMask);
443  }
444 
445  if (gCoreContext->GetBoolSetting("MasterBackendOverride", false))
447 
448  if (player)
449  {
450  m_player = player;
451  QString tmp = m_player->GetRecordingGroup(0);
452  if (!tmp.isEmpty())
453  m_recGroup = tmp;
454  }
455 
456  // recording group stuff
457  m_recGroupIdx = -1;
458  m_recGroupType.clear();
460  (displayCat && m_recGroup != "All Programs") ? "category" : "recgroup";
462 
464 
465  // misc setup
466  gCoreContext->addListener(this);
467 
468  m_popupStack = GetMythMainWindow()->GetStack("popup stack");
469 }
470 
472 {
475 
476  for (size_t i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
477  {
478  m_artTimer[i]->disconnect(this);
479  m_artTimer[i] = nullptr;
480  m_artImage[i] = nullptr;
481  }
482 
483  if (m_player)
484  {
485  QString message = QString("PLAYBACKBOX_EXITING");
486  qApp->postEvent(m_player, new MythEvent(
487  message, m_player_selected_new_show));
488  }
489 }
490 
492 {
493  if (!LoadWindowFromXML("recordings-ui.xml", "watchrecordings", this))
494  return false;
495 
496  m_recgroupList = dynamic_cast<MythUIButtonList *> (GetChild("recgroups"));
497  m_groupList = dynamic_cast<MythUIButtonList *> (GetChild("groups"));
498  m_recordingList = dynamic_cast<MythUIButtonList *> (GetChild("recordings"));
499 
500  m_noRecordingsText = dynamic_cast<MythUIText *> (GetChild("norecordings"));
501 
502  m_previewImage = dynamic_cast<MythUIImage *>(GetChild("preview"));
503  m_artImage[kArtworkFanart] = dynamic_cast<MythUIImage*>(GetChild("fanart"));
504  m_artImage[kArtworkBanner] = dynamic_cast<MythUIImage*>(GetChild("banner"));
505  m_artImage[kArtworkCoverart]= dynamic_cast<MythUIImage*>(GetChild("coverart"));
506 
507  if (!m_recordingList || !m_groupList)
508  {
509  LOG(VB_GENERAL, LOG_ERR, LOC +
510  "Theme is missing critical theme elements.");
511  return false;
512  }
513 
514  if (m_recgroupList)
515  {
516  if (gCoreContext->GetBoolSetting("RecGroupsFocusable", false))
517  connect(m_recgroupList, SIGNAL(itemSelected(MythUIButtonListItem*)),
519  else
521  }
522 
523  connect(m_groupList, SIGNAL(itemSelected(MythUIButtonListItem*)),
525  connect(m_groupList, SIGNAL(itemClicked(MythUIButtonListItem*)),
526  SLOT(SwitchList()));
527  connect(m_recordingList, SIGNAL(itemSelected(MythUIButtonListItem*)),
529  connect(m_recordingList, SIGNAL(itemClicked(MythUIButtonListItem*)),
531  connect(m_recordingList, SIGNAL(itemVisible(MythUIButtonListItem*)),
533  connect(m_recordingList, SIGNAL(itemLoaded(MythUIButtonListItem*)),
535 
536  // connect up timers...
537  connect(m_artTimer[kArtworkFanart], SIGNAL(timeout()), SLOT(fanartLoad()));
538  connect(m_artTimer[kArtworkBanner], SIGNAL(timeout()), SLOT(bannerLoad()));
539  connect(m_artTimer[kArtworkCoverart], SIGNAL(timeout()), SLOT(coverartLoad()));
540 
541  BuildFocusList();
544 
545  return true;
546 }
547 
549 {
552 }
553 
555 {
556  m_groupList->SetLCDTitles(tr("Groups"));
557  m_recordingList->SetLCDTitles(tr("Recordings"),
558  "titlesubtitle|shortdate|starttime");
559 
560  m_recordingList->SetSearchFields("titlesubtitle");
561 
562  if (gCoreContext->GetNumSetting("QueryInitialFilter", 0) == 1)
563  showGroupFilter();
564  else if (!m_player)
566  else
567  {
568  UpdateUILists();
569 
570  if ((m_titleList.size() <= 1) && (m_progsInDB > 0))
571  {
572  m_recGroup.clear();
573  showGroupFilter();
574  }
575  }
576 
577  if (!gCoreContext->GetBoolSetting("PlaybackBoxStartInTitle", false))
579 }
580 
582 {
583  if (GetFocusWidget() == m_groupList)
585  else if (GetFocusWidget() == m_recordingList)
587 }
588 
589 void PlaybackBox::displayRecGroup(const QString &newRecGroup)
590 {
591  m_groupSelected = true;
592 
593  QString password = getRecGroupPassword(newRecGroup);
594 
595  m_newRecGroup = newRecGroup;
596  if (m_curGroupPassword != password && !password.isEmpty())
597  {
598  MythScreenStack *popupStack =
599  GetMythMainWindow()->GetStack("popup stack");
600 
601  QString label = tr("Password for group '%1':").arg(newRecGroup);
602 
603  MythTextInputDialog *pwd = new MythTextInputDialog(popupStack,
604  label, FilterNone, true);
605 
606  connect(pwd, SIGNAL(haveResult(QString)),
607  SLOT(checkPassword(QString)));
608  connect(pwd, SIGNAL(Exiting(void)),
609  SLOT(passwordClosed(void)));
610 
611  m_passwordEntered = false;
612 
613  if (pwd->Create())
614  popupStack->AddScreen(pwd, false);
615 
616  return;
617  }
618 
619  setGroupFilter(newRecGroup);
620 }
621 
622 void PlaybackBox::checkPassword(const QString &password)
623 {
624  if (password == getRecGroupPassword(m_newRecGroup))
625  {
626  m_curGroupPassword = password;
627  m_passwordEntered = true;
629  }
630 }
631 
633 {
634  if (!m_passwordEntered &&
636  showGroupFilter();
637 }
638 
639 void PlaybackBox::updateGroupInfo(const QString &groupname,
640  const QString &grouplabel)
641 {
642  InfoMap infoMap;
643  int countInGroup;
644  QString desc;
645 
646  infoMap["group"] = m_groupDisplayName;
647  infoMap["title"] = grouplabel;
648  infoMap["show"] =
649  groupname.isEmpty() ? ProgramInfo::i18n("All Programs") : grouplabel;
650  countInGroup = m_progLists[groupname].size();
651 
653  {
654  if (!groupname.isEmpty() && !m_progLists[groupname].empty())
655  {
656  ProgramInfo *pginfo = *m_progLists[groupname].begin();
657 
658  QString fn = m_helper.LocateArtwork(
659  pginfo->GetInetRef(), pginfo->GetSeason(), kArtworkFanart, nullptr, groupname);
660 
661  if (fn.isEmpty())
662  {
663  m_artTimer[kArtworkFanart]->stop();
665  }
666  else if (m_artImage[kArtworkFanart]->GetFilename() != fn)
667  {
670  }
671  }
672  else
673  {
675  }
676  }
677 
678 
679  if (countInGroup >= 1)
680  {
681  ProgramList group = m_progLists[groupname];
682  float groupSize = 0.0;
683 
684  for (ProgramList::iterator it = group.begin(); it != group.end(); ++it)
685  {
686  ProgramInfo *info = *it;
687  if (info)
688  {
689  uint64_t filesize = info->GetFilesize();
690 // This query should be unnecessary if the ProgramInfo Updater is working
691 // if (filesize == 0 || info->GetRecordingStatus() == RecStatus::Recording)
692 // {
693 // filesize = info->QueryFilesize();
694 // info->SetFilesize(filesize);
695 // }
696  groupSize += filesize;
697  }
698  }
699 
700  desc = tr("There is/are %n recording(s) in this display "
701  "group, which consume(s) %1 GiB.", "", countInGroup)
702  .arg(groupSize / 1024.0F / 1024.0F / 1024.0F, 0, 'f', 2);
703  }
704  else
705  {
706  desc = tr("There is no recording in this display group.");
707  }
708 
709  infoMap["description"] = desc;
710  infoMap["rec_count"] = QString("%1").arg(countInGroup);
711 
713  SetTextFromMap(infoMap);
714  m_currentMap = infoMap;
715 
716  MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
717  (GetChild("ratingstate"));
718  if (ratingState)
719  ratingState->Reset();
720 
721  MythUIStateType *jobState = dynamic_cast<MythUIStateType*>
722  (GetChild("jobstate"));
723  if (jobState)
724  jobState->Reset();
725 
726  if (m_previewImage)
728 
731 
734 
735  updateIcons();
736 }
737 
739  bool force_preview_reload)
740 {
741  if (!pginfo)
742  return;
743 
744  MythUIButtonListItem *item =
745  m_recordingList->GetItemByData(qVariantFromValue(pginfo));
746 
747  if (item)
748  {
749  MythUIButtonListItem *sel_item =
751  UpdateUIListItem(item, item == sel_item, force_preview_reload);
752  }
753  else
754  {
755  LOG(VB_GENERAL, LOG_DEBUG, LOC +
756  QString("UpdateUIListItem called with a title unknown "
757  "to us in m_recordingList\n\t\t\t%1")
758  .arg(pginfo->toString(ProgramInfo::kTitleSubtitle)));
759  }
760 }
761 
762 static const char *disp_flags[] = { "playlist", "watched", "preserve",
763  "cutlist", "autoexpire", "editing",
764  "bookmark", "inuse", "transcoded" };
765 
767 {
768  bool disp_flag_stat[sizeof(disp_flags)/sizeof(char*)];
769 
770  disp_flag_stat[0] = m_playList.contains(pginfo->GetRecordingID());
771  disp_flag_stat[1] = pginfo->IsWatched();
772  disp_flag_stat[2] = pginfo->IsPreserved();
773  disp_flag_stat[3] = pginfo->HasCutlist();
774  disp_flag_stat[4] = pginfo->IsAutoExpirable();
775  disp_flag_stat[5] = ((pginfo->GetProgramFlags() & FL_EDITING) != 0U);
776  disp_flag_stat[6] = pginfo->IsBookmarkSet();
777  disp_flag_stat[7] = pginfo->IsInUsePlaying();
778  disp_flag_stat[8] = ((pginfo->GetProgramFlags() & FL_TRANSCODED) != 0U);
779 
780  for (size_t i = 0; i < sizeof(disp_flags) / sizeof(char*); ++i)
781  item->DisplayState(disp_flag_stat[i]?"yes":"no", disp_flags[i]);
782 }
783 
785  bool is_sel, bool force_preview_reload)
786 {
787  if (!item)
788  return;
789 
790  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
791 
792  if (!pginfo)
793  return;
794 
795  QString state = extract_main_state(*pginfo, m_player);
796 
797  // Update the text, e.g. Title or subtitle may have been changed on another
798  // frontend
800  {
801  InfoMap infoMap;
802  pginfo->ToMap(infoMap);
803  item->SetTextFromMap(infoMap);
804 
805  QString groupname =
806  m_groupList->GetItemCurrent()->GetData().toString();
807 
808  QString tempSubTitle = extract_subtitle(*pginfo, groupname);
809 
810  if (groupname == pginfo->GetTitle().toLower())
811  {
812  item->SetText(tempSubTitle, "titlesubtitle");
813  // titlesubtitle will just have the subtitle, so put the full
814  // string in titlesubtitlefull, when a theme can then "depend" on.
815  item->SetText(pginfo->toString(ProgramInfo::kTitleSubtitle, " - "),
816  "titlesubtitlefull");
817  }
818  }
819 
820  // Recording and availability status
821  item->SetFontState(state);
822  item->DisplayState(state, "status");
823 
824  // Job status (recording, transcoding, flagging)
825  QString job = extract_job_state(*pginfo);
826  item->DisplayState(job, "jobstate");
827 
828  // Flagging status (queued, running, no, yes)
829  item->DisplayState(extract_commflag_state(*pginfo), "commflagged");
830 
831  SetItemIcons(item, pginfo);
832 
833  QString rating = QString::number(pginfo->GetStars(10));
834 
835  item->DisplayState(rating, "ratingstate");
836 
837  QString oldimgfile = item->GetImageFilename("preview");
838  if (oldimgfile.isEmpty() || force_preview_reload)
839  m_preview_tokens.insert(m_helper.GetPreviewImage(*pginfo));
840 
841  if ((GetFocusWidget() == m_recordingList) && is_sel)
842  {
843  InfoMap infoMap;
844 
845  pginfo->ToMap(infoMap);
846  infoMap["group"] = m_groupDisplayName;
848  SetTextFromMap(infoMap);
849  m_currentMap = infoMap;
850 
851  MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
852  (GetChild("ratingstate"));
853  if (ratingState)
854  ratingState->DisplayState(rating);
855 
856  MythUIStateType *jobState = dynamic_cast<MythUIStateType*>
857  (GetChild("jobstate"));
858  if (jobState)
859  jobState->DisplayState(job);
860 
861  if (m_previewImage)
862  {
863  m_previewImage->SetFilename(oldimgfile);
864  m_previewImage->Load(true, true);
865  }
866 
867  // Handle artwork
868  QString arthost;
869  for (size_t i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
870  {
871  if (!m_artImage[i])
872  continue;
873 
874  if (arthost.isEmpty())
875  {
876  arthost = (!m_artHostOverride.isEmpty()) ?
877  m_artHostOverride : pginfo->GetHostname();
878  }
879 
880  QString fn = m_helper.LocateArtwork(
881  pginfo->GetInetRef(), pginfo->GetSeason(),
882  (VideoArtworkType)i, pginfo);
883 
884  if (fn.isEmpty())
885  {
886  m_artTimer[i]->stop();
887  m_artImage[i]->Reset();
888  }
889  else if (m_artImage[i]->GetFilename() != fn)
890  {
891  m_artImage[i]->SetFilename(fn);
892  m_artTimer[i]->start(s_artDelay[i]);
893  }
894  }
895 
896  updateIcons(pginfo);
897  }
898 }
899 
901 {
902  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
903  if (item->GetText("is_item_initialized").isNull())
904  {
905  QMap<AudioProps, QString> audioFlags;
906  audioFlags[AUD_DOLBY] = "dolby";
907  audioFlags[AUD_SURROUND] = "surround";
908  audioFlags[AUD_STEREO] = "stereo";
909  audioFlags[AUD_MONO] = "mono";
910 
911  QMap<VideoProps, QString> videoFlags;
912  videoFlags[VID_1080] = "hd1080";
913  videoFlags[VID_720] = "hd720";
914  videoFlags[VID_HDTV] = "hdtv";
915  videoFlags[VID_WIDESCREEN] = "widescreen";
916 
917  QMap<SubtitleTypes, QString> subtitleFlags;
918  subtitleFlags[SUB_SIGNED] = "deafsigned";
919  subtitleFlags[SUB_ONSCREEN] = "onscreensub";
920  subtitleFlags[SUB_NORMAL] = "subtitles";
921  subtitleFlags[SUB_HARDHEAR] = "cc";
922 
923  QString groupname =
924  m_groupList->GetItemCurrent()->GetData().toString();
925 
926  QString state = extract_main_state(*pginfo, m_player);
927 
928  item->SetFontState(state);
929 
930  InfoMap infoMap;
931  pginfo->ToMap(infoMap);
932  item->SetTextFromMap(infoMap);
933 
934  QString tempSubTitle = extract_subtitle(*pginfo, groupname);
935 
936  if (groupname == pginfo->GetTitle().toLower())
937  {
938  item->SetText(tempSubTitle, "titlesubtitle");
939  // titlesubtitle will just have the subtitle, so put the full
940  // string in titlesubtitlefull, when a theme can then "depend" on.
941  item->SetText(pginfo->toString(ProgramInfo::kTitleSubtitle, " - "),
942  "titlesubtitlefull");
943  }
944 
945  item->DisplayState(state, "status");
946 
947  item->DisplayState(QString::number(pginfo->GetStars(10)),
948  "ratingstate");
949 
950  SetItemIcons(item, pginfo);
951 
952  QMap<AudioProps, QString>::iterator ait;
953  for (ait = audioFlags.begin(); ait != audioFlags.end(); ++ait)
954  {
955  if (pginfo->GetAudioProperties() & ait.key())
956  item->DisplayState(ait.value(), "audioprops");
957  }
958 
959  QMap<VideoProps, QString>::iterator vit;
960  for (vit = videoFlags.begin(); vit != videoFlags.end(); ++vit)
961  {
962  if (pginfo->GetVideoProperties() & vit.key())
963  item->DisplayState(vit.value(), "videoprops");
964  }
965 
966  QMap<SubtitleTypes, QString>::iterator sit;
967  for (sit = subtitleFlags.begin(); sit != subtitleFlags.end(); ++sit)
968  {
969  if (pginfo->GetSubtitleType() & sit.key())
970  item->DisplayState(sit.value(), "subtitletypes");
971  }
972 
973  item->DisplayState(pginfo->GetCategoryTypeString(), "categorytype");
974 
975  // Mark this button list item as initialized.
976  item->SetText("yes", "is_item_initialized");
977  }
978 
979 }
980 
982 {
983  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
984 
985  ItemLoaded(item);
986  // Job status (recording, transcoding, flagging)
987  QString job = extract_job_state(*pginfo);
988  item->DisplayState(job, "jobstate");
989 
990  // Flagging status (queued, running, no, yes)
991  item->DisplayState(extract_commflag_state(*pginfo), "commflagged");
992 
993  MythUIButtonListItem *sel_item = item->parent()->GetItemCurrent();
994  if ((item != sel_item) && item->GetImageFilename("preview").isEmpty() &&
995  (asAvailable == pginfo->GetAvailableStatus()))
996  {
997  QString token = m_helper.GetPreviewImage(*pginfo, true);
998  if (token.isEmpty())
999  return;
1000 
1001  m_preview_tokens.insert(token);
1002  // now make sure selected item is still at the top of the queue
1003  ProgramInfo *sel_pginfo =
1004  sel_item->GetData().value<ProgramInfo*>();
1005  if (sel_pginfo && sel_item->GetImageFilename("preview").isEmpty() &&
1006  (asAvailable == sel_pginfo->GetAvailableStatus()))
1007  {
1008  m_preview_tokens.insert(
1009  m_helper.GetPreviewImage(*sel_pginfo, false));
1010  }
1011  }
1012 }
1013 
1014 
1021 void PlaybackBox::HandlePreviewEvent(const QStringList &list)
1022 {
1023  if (list.size() < 5)
1024  {
1025  LOG(VB_GENERAL, LOG_ERR, "HandlePreviewEvent() -- too few args");
1026  for (uint i = 0; i < (uint) list.size(); i++)
1027  {
1028  LOG(VB_GENERAL, LOG_INFO, QString("%1: %2")
1029  .arg(i).arg(list[i]));
1030  }
1031  return;
1032  }
1033 
1034  uint recordingID = list[0].toUInt();
1035  const QString previewFile = list[1];
1036  const QString message = list[2];
1037 
1038  bool found = false;
1039  for (uint i = 4; i < (uint) list.size(); i++)
1040  {
1041  QString token = list[i];
1042  QSet<QString>::iterator it = m_preview_tokens.find(token);
1043  if (it != m_preview_tokens.end())
1044  {
1045  found = true;
1046  m_preview_tokens.erase(it);
1047  }
1048  }
1049 
1050  if (!found)
1051  {
1052  QString tokens("\n\t\t\ttokens: ");
1053  for (uint i = 4; i < (uint) list.size(); i++)
1054  tokens += list[i] + ", ";
1055  LOG(VB_GENERAL, LOG_DEBUG, LOC +
1056  "Ignoring PREVIEW_SUCCESS, no matcing token" + tokens);
1057  return;
1058  }
1059 
1060  if (previewFile.isEmpty())
1061  {
1062  LOG(VB_GENERAL, LOG_ERR, LOC +
1063  "Ignoring PREVIEW_SUCCESS, no preview file.");
1064  return;
1065  }
1066 
1067  ProgramInfo *info = m_programInfoCache.GetRecordingInfo(recordingID);
1068  MythUIButtonListItem *item = nullptr;
1069 
1070  if (info)
1071  item = m_recordingList->GetItemByData(qVariantFromValue(info));
1072 
1073  if (!item)
1074  {
1075  LOG(VB_GENERAL, LOG_DEBUG, LOC +
1076  "Ignoring PREVIEW_SUCCESS, item no longer on screen.");
1077  }
1078 
1079  if (item)
1080  {
1081  LOG(VB_GUI, LOG_INFO, LOC + QString("Loading preview %1,\n\t\t\tmsg %2")
1082  .arg(previewFile).arg(message));
1083 
1084  item->SetImage(previewFile, "preview", true);
1085 
1086  if ((GetFocusWidget() == m_recordingList) &&
1087  (m_recordingList->GetItemCurrent() == item) &&
1089  {
1090  m_previewImage->SetFilename(previewFile);
1091  m_previewImage->Load(true, true);
1092  }
1093  }
1094 }
1095 
1097 {
1098  uint32_t flags = FL_NONE;
1099 
1100  if (pginfo)
1101  flags = pginfo->GetProgramFlags();
1102 
1103  QMap <QString, int>::iterator it;
1104  QMap <QString, int> iconMap;
1105 
1106  iconMap["commflagged"] = FL_COMMFLAG;
1107  iconMap["cutlist"] = FL_CUTLIST;
1108  iconMap["autoexpire"] = FL_AUTOEXP;
1109  iconMap["processing"] = FL_COMMPROCESSING;
1110  iconMap["editing"] = FL_EDITING;
1111  iconMap["bookmark"] = FL_BOOKMARK;
1112  iconMap["inuse"] = (FL_INUSERECORDING |
1113  FL_INUSEPLAYING |
1114  FL_INUSEOTHER);
1115  iconMap["transcoded"] = FL_TRANSCODED;
1116  iconMap["watched"] = FL_WATCHED;
1117  iconMap["preserved"] = FL_PRESERVED;
1118 
1119  MythUIImage *iconImage = nullptr;
1120  MythUIStateType *iconState = nullptr;
1121  for (it = iconMap.begin(); it != iconMap.end(); ++it)
1122  {
1123  iconImage = dynamic_cast<MythUIImage *>(GetChild(it.key()));
1124  if (iconImage)
1125  iconImage->SetVisible((flags & (*it)) != 0U);
1126 
1127  iconState = dynamic_cast<MythUIStateType *>(GetChild(it.key()));
1128  if (iconState)
1129  {
1130  if (flags & (*it))
1131  iconState->DisplayState("yes");
1132  else
1133  iconState->DisplayState("no");
1134  }
1135  }
1136 
1137  iconMap.clear();
1138  // Add prefix to ensure iteration order in case 2 or more properties set
1139  iconMap["1dolby"] = AUD_DOLBY;
1140  iconMap["2surround"] = AUD_SURROUND;
1141  iconMap["3stereo"] = AUD_STEREO;
1142  iconMap["4mono"] = AUD_MONO;
1143 
1144  iconState = dynamic_cast<MythUIStateType *>(GetChild("audioprops"));
1145  bool haveIcon = false;
1146  if (pginfo && iconState)
1147  {
1148  for (it = iconMap.begin(); it != iconMap.end(); ++it)
1149  {
1150  if (pginfo->GetAudioProperties() & (*it))
1151  {
1152  if (iconState->DisplayState(it.key().mid(1)))
1153  {
1154  haveIcon = true;
1155  break;
1156  }
1157  }
1158  }
1159  }
1160 
1161  if (iconState && !haveIcon)
1162  iconState->Reset();
1163 
1164  iconMap.clear();
1165  iconMap["avchd"] = VID_AVC;
1166  iconMap["hd1080"] = VID_1080;
1167  iconMap["hd720"] = VID_720;
1168  iconMap["hdtv"] = VID_HDTV;
1169  iconMap["widescreen"] = VID_WIDESCREEN;
1170 
1171  iconState = dynamic_cast<MythUIStateType *>(GetChild("videoprops"));
1172  haveIcon = false;
1173  if (pginfo && iconState)
1174  {
1175  for (it = iconMap.begin(); it != iconMap.end(); ++it)
1176  {
1177  if (pginfo->GetVideoProperties() & (*it))
1178  {
1179  if (iconState->DisplayState(it.key()))
1180  {
1181  haveIcon = true;
1182  break;
1183  }
1184  }
1185  }
1186  }
1187 
1188  if (iconState && !haveIcon)
1189  iconState->Reset();
1190 
1191  iconMap.clear();
1192  iconMap["damaged"] = VID_DAMAGED;
1193 
1194  iconState = dynamic_cast<MythUIStateType *>(GetChild("videoquality"));
1195  haveIcon = false;
1196  if (pginfo && iconState)
1197  {
1198  for (it = iconMap.begin(); it != iconMap.end(); ++it)
1199  {
1200  if (pginfo->GetVideoProperties() & (*it))
1201  {
1202  if (iconState->DisplayState(it.key()))
1203  {
1204  haveIcon = true;
1205  break;
1206  }
1207  }
1208  }
1209  }
1210 
1211  if (iconState && !haveIcon)
1212  iconState->Reset();
1213  iconMap.clear();
1214  iconMap["deafsigned"] = SUB_SIGNED;
1215  iconMap["onscreensub"] = SUB_ONSCREEN;
1216  iconMap["subtitles"] = SUB_NORMAL;
1217  iconMap["cc"] = SUB_HARDHEAR;
1218 
1219  iconState = dynamic_cast<MythUIStateType *>(GetChild("subtitletypes"));
1220  haveIcon = false;
1221  if (pginfo && iconState)
1222  {
1223  for (it = iconMap.begin(); it != iconMap.end(); ++it)
1224  {
1225  if (pginfo->GetSubtitleType() & (*it))
1226  {
1227  if (iconState->DisplayState(it.key()))
1228  {
1229  haveIcon = true;
1230  break;
1231  }
1232  }
1233  }
1234  }
1235 
1236  if (iconState && !haveIcon)
1237  iconState->Reset();
1238 
1239  iconState = dynamic_cast<MythUIStateType *>(GetChild("categorytype"));
1240  if (iconState)
1241  {
1242  if (!(pginfo && iconState->DisplayState(pginfo->GetCategoryTypeString())))
1243  iconState->Reset();
1244  }
1245 }
1246 
1248 {
1249  return GetChild("freereport") || GetChild("usedbar");
1250 }
1251 
1253 {
1254  MythUIText *freereportText =
1255  dynamic_cast<MythUIText*>(GetChild("freereport"));
1256  MythUIProgressBar *usedProgress =
1257  dynamic_cast<MythUIProgressBar *>(GetChild("usedbar"));
1258 
1259  // If the theme doesn't have these widgets,
1260  // don't waste time querying the backend...
1261  if (!freereportText && !usedProgress && !GetChild("diskspacetotal") &&
1262  !GetChild("diskspaceused") && !GetChild("diskspacefree") &&
1263  !GetChild("diskspacepercentused") && !GetChild("diskspacepercentfree"))
1264  return;
1265 
1266  double freeSpaceTotal = (double) m_helper.GetFreeSpaceTotalMB();
1267  double freeSpaceUsed = (double) m_helper.GetFreeSpaceUsedMB();
1268 
1269  QLocale locale = gCoreContext->GetQLocale();
1270  InfoMap usageMap;
1271  usageMap["diskspacetotal"] = locale.toString((freeSpaceTotal / 1024.0),
1272  'f', 2);
1273  usageMap["diskspaceused"] = locale.toString((freeSpaceUsed / 1024.0),
1274  'f', 2);
1275  usageMap["diskspacefree"] = locale.toString(
1276  ((freeSpaceTotal - freeSpaceUsed) / 1024.0),
1277  'f', 2);
1278 
1279  double perc = 0.0;
1280  if (freeSpaceTotal > 0.0)
1281  perc = (100.0 * freeSpaceUsed) / freeSpaceTotal;
1282 
1283  usageMap["diskspacepercentused"] = QString::number((int)perc);
1284  usageMap["diskspacepercentfree"] = QString::number(100 - (int)perc);
1285 
1286  QString size = locale.toString(((freeSpaceTotal - freeSpaceUsed) / 1024.0),
1287  'f', 2);
1288 
1289  QString usestr = tr("%1% used, %2 GB free", "Diskspace")
1290  .arg(QString::number((int)perc))
1291  .arg(size);
1292 
1293  if (freereportText)
1294  freereportText->SetText(usestr);
1295 
1296  if (usedProgress)
1297  {
1298  usedProgress->SetTotal((int)freeSpaceTotal);
1299  usedProgress->SetUsed((int)freeSpaceUsed);
1300  }
1301 
1302  SetTextFromMap(usageMap);
1303 }
1304 
1305 /*
1306  * \fn PlaybackBox::updateUIRecGroupList(void)
1307  * \brief called when the list of recording groups may have changed
1308  */
1310 {
1311  if (m_recGroupIdx < 0 || !m_recgroupList || m_recGroups.size() < 2)
1312  return;
1313 
1314  QSignalBlocker blocker(m_recgroupList);
1315 
1316  m_recgroupList->Reset();
1317 
1318  int idx = 0;
1319  QStringList::iterator it = m_recGroups.begin();
1320  for (; it != m_recGroups.end(); (++it), (++idx))
1321  {
1322  QString key = (*it);
1323  QString tmp = (key == "All Programs") ? "All" : key;
1324  QString name = ProgramInfo::i18n(tmp);
1325 
1326  if (m_recGroups.size() == 2 && key == "Default")
1327  continue; // All and Default will be the same, so only show All
1328 
1330  m_recgroupList, name, qVariantFromValue(key));
1331 
1332  if (idx == m_recGroupIdx)
1334  item->SetText(name);
1335  }
1336 }
1337 
1338 void PlaybackBox::UpdateUIGroupList(const QStringList &groupPreferences)
1339 {
1340  m_groupList->Reset();
1341 
1342  if (!m_titleList.isEmpty())
1343  {
1344  int best_pref = INT_MAX, sel_idx = 0;
1345  QStringList::iterator it;
1346  for (it = m_titleList.begin(); it != m_titleList.end(); ++it)
1347  {
1348  QString groupname = (*it);
1349 
1350  MythUIButtonListItem *item =
1352  m_groupList, "", qVariantFromValue(groupname.toLower()));
1353 
1354  int pref = groupPreferences.indexOf(groupname.toLower());
1355  if ((pref >= 0) && (pref < best_pref))
1356  {
1357  best_pref = pref;
1358  sel_idx = m_groupList->GetItemPos(item);
1359  m_currentGroup = groupname.toLower();
1360  }
1361 
1362  QString displayName = groupname;
1363  if (displayName.isEmpty())
1364  {
1365  if (m_recGroup == "All Programs")
1366  displayName = ProgramInfo::i18n("All Programs");
1367  else
1368  displayName = ProgramInfo::i18n("All Programs - %1")
1369  .arg(m_groupDisplayName);
1370  }
1371 
1372  item->SetText(groupname, "groupname");
1373  item->SetText(displayName, "name");
1374  item->SetText(displayName);
1375 
1376  int count = m_progLists[groupname.toLower()].size();
1377  item->SetText(QString::number(count), "reccount");
1378  }
1379 
1380  m_needUpdate = true;
1381  m_groupList->SetItemCurrent(sel_idx);
1382  // We need to explicitly call updateRecList in this case,
1383  // since 0 is selected by default, and we need updateRecList
1384  // to be called with m_needUpdate set.
1385  if (!sel_idx)
1387  }
1388 }
1389 
1391 {
1392  QString newRecGroup = sel_item->GetData().toString();
1393  displayRecGroup(newRecGroup);
1394 }
1395 
1397 {
1398  if (!sel_item)
1399  return;
1400 
1401  QString groupname = sel_item->GetData().toString();
1402  QString grouplabel = sel_item->GetText();
1403 
1404  updateGroupInfo(groupname, grouplabel);
1405 
1406  if (((m_currentGroup == groupname) && !m_needUpdate) ||
1408  return;
1409 
1410  m_needUpdate = false;
1411 
1412  if (!m_isFilling)
1413  m_currentGroup = groupname;
1414 
1416 
1417  ProgramMap::iterator pmit = m_progLists.find(groupname);
1418  if (pmit == m_progLists.end())
1419  return;
1420 
1421  ProgramList &progList = *pmit;
1422 
1423  ProgramList::iterator it = progList.begin();
1424  for (; it != progList.end(); ++it)
1425  {
1426  if ((*it)->GetAvailableStatus() == asPendingDelete ||
1427  (*it)->GetAvailableStatus() == asDeleted)
1428  continue;
1429 
1430  new PlaybackBoxListItem(this, m_recordingList, *it);
1431  }
1433 
1434  if (m_noRecordingsText)
1435  {
1436  if (!progList.empty())
1438  else
1439  {
1440  QString txt = m_programInfoCache.empty() ?
1441  tr("There are no recordings available") :
1442  tr("There are no recordings in your current view");
1445  }
1446  }
1447 }
1448 
1449 static bool save_position(
1450  const MythUIButtonList *groupList, const MythUIButtonList *recordingList,
1451  QStringList &groupSelPref, QStringList &itemSelPref,
1452  QStringList &itemTopPref)
1453 {
1454  MythUIButtonListItem *prefSelGroup = groupList->GetItemCurrent();
1455  if (!prefSelGroup)
1456  return false;
1457 
1458  groupSelPref.push_back(prefSelGroup->GetData().toString());
1459  for (int i = groupList->GetCurrentPos();
1460  i < groupList->GetCount(); i++)
1461  {
1462  prefSelGroup = groupList->GetItemAt(i);
1463  if (prefSelGroup)
1464  groupSelPref.push_back(prefSelGroup->GetData().toString());
1465  }
1466 
1467  int curPos = recordingList->GetCurrentPos();
1468  for (int i = curPos; (i >= 0) && (i < recordingList->GetCount()); i++)
1469  {
1470  MythUIButtonListItem *item = recordingList->GetItemAt(i);
1471  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
1472  itemSelPref.push_back(groupSelPref.front());
1473  itemSelPref.push_back(QString::number(pginfo->GetRecordingID()));
1474  }
1475  for (int i = curPos; (i >= 0) && (i < recordingList->GetCount()); i--)
1476  {
1477  MythUIButtonListItem *item = recordingList->GetItemAt(i);
1478  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
1479  itemSelPref.push_back(groupSelPref.front());
1480  itemSelPref.push_back(QString::number(pginfo->GetRecordingID()));
1481  }
1482 
1483  int topPos = recordingList->GetTopItemPos();
1484  for (int i = topPos + 1; i >= topPos - 1; i--)
1485  {
1486  if (i >= 0 && i < recordingList->GetCount())
1487  {
1488  MythUIButtonListItem *item = recordingList->GetItemAt(i);
1489  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
1490  if (i == topPos)
1491  {
1492  itemTopPref.push_front(QString::number(pginfo->GetRecordingID()));
1493  itemTopPref.push_front(groupSelPref.front());
1494  }
1495  else
1496  {
1497  itemTopPref.push_back(groupSelPref.front());
1498  itemTopPref.push_back(QString::number(pginfo->GetRecordingID()));
1499  }
1500  }
1501  }
1502 
1503  return true;
1504 }
1505 
1506 static void restore_position(
1507  MythUIButtonList *groupList, MythUIButtonList *recordingList,
1508  const QStringList &groupSelPref, const QStringList &itemSelPref,
1509  const QStringList &itemTopPref)
1510 {
1511  // If possible reselect the item selected before,
1512  // otherwise select the nearest available item.
1513  MythUIButtonListItem *prefSelGroup = groupList->GetItemCurrent();
1514  if (!prefSelGroup ||
1515  !groupSelPref.contains(prefSelGroup->GetData().toString()) ||
1516  !itemSelPref.contains(prefSelGroup->GetData().toString()))
1517  {
1518  return;
1519  }
1520 
1521  // the group is selected in UpdateUIGroupList()
1522  QString groupname = prefSelGroup->GetData().toString();
1523 
1524  // find best selection
1525  int sel = -1;
1526  for (uint i = 0; i+1 < (uint)itemSelPref.size(); i+=2)
1527  {
1528  if (itemSelPref[i] != groupname)
1529  continue;
1530 
1531  uint recordingID = itemSelPref[i+1].toUInt();
1532  for (uint j = 0; j < (uint)recordingList->GetCount(); j++)
1533  {
1534  MythUIButtonListItem *item = recordingList->GetItemAt(j);
1535  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
1536  if (pginfo && (pginfo->GetRecordingID() == recordingID))
1537  {
1538  sel = j;
1539  i = itemSelPref.size();
1540  break;
1541  }
1542  }
1543  }
1544 
1545  // find best top item
1546  int top = -1;
1547  for (uint i = 0; i+1 < (uint)itemTopPref.size(); i+=2)
1548  {
1549  if (itemTopPref[i] != groupname)
1550  continue;
1551 
1552  uint recordingID = itemTopPref[i+1].toUInt();
1553  for (uint j = 0; j < (uint)recordingList->GetCount(); j++)
1554  {
1555  MythUIButtonListItem *item = recordingList->GetItemAt(j);
1556  ProgramInfo *pginfo = item->GetData().value<ProgramInfo*>();
1557  if (pginfo && (pginfo->GetRecordingID() == recordingID))
1558  {
1559  top = j;
1560  i = itemTopPref.size();
1561  break;
1562  }
1563  }
1564  }
1565 
1566  if (sel >= 0)
1567  {
1568 #if 0
1569  LOG(VB_GENERAL, LOG_DEBUG, QString("Reselect success (%1,%2)")
1570  .arg(sel).arg(top));
1571 #endif
1572  recordingList->SetItemCurrent(sel, top);
1573  }
1574  else
1575  {
1576 #if 0
1577  LOG(VB_GENERAL, LOG_DEBUG, QString("Reselect failure (%1,%2)")
1578  .arg(sel).arg(top));
1579 #endif
1580  }
1581 }
1582 
1584 {
1585  m_isFilling = true;
1586 
1587  // Save selection, including next few items & groups
1588  QStringList groupSelPref, itemSelPref, itemTopPref;
1590  groupSelPref, itemSelPref, itemTopPref))
1591  {
1592  // If user wants to start in watchlist and watchlist is displayed, then
1593  // make it the current group
1595  groupSelPref.push_back(m_watchGroupLabel);
1596  }
1597 
1598  // Cache available status for later restoration
1599  QMap<uint, AvailableStatusType> asCache;
1600  uint asRecordingID;
1601 
1602  if (!m_progLists.isEmpty())
1603  {
1604  ProgramList::iterator it = m_progLists[""].begin();
1605  ProgramList::iterator end = m_progLists[""].end();
1606  for (; it != end; ++it)
1607  {
1608  asRecordingID = (*it)->GetRecordingID();
1609  asCache[asRecordingID] = (*it)->GetAvailableStatus();
1610  }
1611  }
1612 
1613  m_progsInDB = 0;
1614  m_titleList.clear();
1615  m_progLists.clear();
1617  m_groupList->Reset();
1618  if (m_recgroupList)
1619  m_recgroupList->Reset();
1620  // Clear autoDelete for the "all" list since it will share the
1621  // objects with the title lists.
1622  m_progLists[""] = ProgramList(false);
1623  m_progLists[""].setAutoDelete(false);
1624 
1626  "DisplayGroupTitleSort", TitleSortAlphabetical);
1627 
1628  QMap<QString, QString> sortedList;
1629  QMap<int, QString> searchRule;
1630  QMap<int, int> recidEpisodes;
1631 
1633 
1634  if (!m_programInfoCache.empty())
1635  {
1636  QString sTitle;
1637 
1638  if ((m_viewMask & VIEW_SEARCHES))
1639  {
1640  MSqlQuery query(MSqlQuery::InitCon());
1641  query.prepare("SELECT recordid,title FROM record "
1642  "WHERE search > 0 AND search != :MANUAL;");
1643  query.bindValue(":MANUAL", kManualSearch);
1644 
1645  if (query.exec())
1646  {
1647  while (query.next())
1648  {
1649  QString tmpTitle = query.value(1).toString();
1650  tmpTitle.remove(m_titleChaff);
1651  searchRule[query.value(0).toInt()] = tmpTitle;
1652  }
1653  }
1654  }
1655 
1656  bool isCategoryFilter = (m_recGroupType[m_recGroup] == "category");
1657  bool isUnknownCategory = (m_recGroup == tr("Unknown"));
1658  bool isAllProgsGroup = (m_recGroup == "All Programs");
1659  bool isDeletedGroup = (m_recGroup == "Deleted");
1660  bool isLiveTvGroup = (m_recGroup == "LiveTV");
1661 
1662  vector<ProgramInfo*> list;
1663  bool newest_first = (0==m_allOrder);
1664  m_programInfoCache.GetOrdered(list, newest_first);
1665  vector<ProgramInfo*>::const_iterator it = list.begin();
1666  for ( ; it != list.end(); ++it)
1667  {
1668  ProgramInfo *p = *it;
1669  if (p->IsDeletePending())
1670  continue;
1671 
1672  m_progsInDB++;
1673 
1674  const QString& pRecgroup(p->GetRecordingGroup());
1675  const bool isLiveTVProg(pRecgroup == "LiveTV");
1676 
1677  // Never show anything from unauthorised passworded groups
1678  QString password = getRecGroupPassword(pRecgroup);
1679  if (m_curGroupPassword != password && !password.isEmpty())
1680  continue;
1681 
1682  if (pRecgroup == "Deleted")
1683  {
1684  // Filter nothing from Deleted group
1685  // Never show Deleted recs anywhere else
1686  if (!isDeletedGroup)
1687  continue;
1688  }
1689  // Optionally ignore LiveTV programs if not viewing LiveTV group
1690  else if (!(m_viewMask & VIEW_LIVETVGRP) &&
1691  !isLiveTvGroup && isLiveTVProg)
1692  {
1693  continue;
1694  }
1695  // Optionally ignore watched
1696  else if (!(m_viewMask & VIEW_WATCHED) && p->IsWatched())
1697  {
1698  continue;
1699  }
1700  else if (isCategoryFilter)
1701  {
1702  // Filter by category
1703  if (isUnknownCategory ? !p->GetCategory().isEmpty()
1704  : p->GetCategory() != m_recGroup)
1705  continue;
1706  }
1707  // Filter by recgroup
1708  else if (!isAllProgsGroup && pRecgroup != m_recGroup)
1709  continue;
1710 
1711  if (p->GetTitle().isEmpty())
1712  p->SetTitle(tr("_NO_TITLE_"));
1713 
1714  if (m_viewMask != VIEW_NONE && (!isLiveTVProg || isLiveTvGroup))
1715  {
1716  m_progLists[""].push_front(p);
1717  }
1718 
1719  asRecordingID = p->GetRecordingID();
1720  if (asCache.contains(asRecordingID))
1721  p->SetAvailableStatus(asCache[asRecordingID], "UpdateUILists");
1722  else
1723  p->SetAvailableStatus(asAvailable, "UpdateUILists");
1724 
1725  if (!isLiveTvGroup && isLiveTVProg && (m_viewMask & VIEW_LIVETVGRP))
1726  {
1727  QString tmpTitle = tr("Live TV");
1728  sortedList[tmpTitle.toLower()] = tmpTitle;
1729  m_progLists[tmpTitle.toLower()].push_front(p);
1730  m_progLists[tmpTitle.toLower()].setAutoDelete(false);
1731  continue;
1732  }
1733 
1734  // Show titles
1735  if ((m_viewMask & VIEW_TITLES) && (!isLiveTVProg || isLiveTvGroup))
1736  {
1737  sTitle = construct_sort_title(
1738  p->GetSortTitle(), m_viewMask, titleSort,
1739  p->GetRecordingPriority());
1740  sTitle = sTitle.toLower();
1741 
1742  if (!sortedList.contains(sTitle))
1743  sortedList[sTitle] = p->GetTitle();
1744  m_progLists[sortedList[sTitle].toLower()].push_front(p);
1745  m_progLists[sortedList[sTitle].toLower()].setAutoDelete(false);
1746  }
1747 
1748  // Show recording groups
1749  if ((m_viewMask & VIEW_RECGROUPS) &&
1750  !pRecgroup.isEmpty() && !isLiveTVProg)
1751  {
1752  sortedList[pRecgroup.toLower()] = pRecgroup;
1753  m_progLists[pRecgroup.toLower()].push_front(p);
1754  m_progLists[pRecgroup.toLower()].setAutoDelete(false);
1755  }
1756 
1757  // Show categories
1758  if (((m_viewMask & VIEW_CATEGORIES) != 0) && !p->GetCategory().isEmpty())
1759  {
1760  QString catl = p->GetCategory().toLower();
1761  sortedList[catl] = p->GetCategory();
1762  m_progLists[catl].push_front(p);
1763  m_progLists[catl].setAutoDelete(false);
1764  }
1765 
1766  if (((m_viewMask & VIEW_SEARCHES) != 0) &&
1767  !searchRule[p->GetRecordingRuleID()].isEmpty() &&
1768  p->GetTitle() != searchRule[p->GetRecordingRuleID()])
1769  { // Show search rules
1770  QString tmpTitle = QString("(%1)")
1771  .arg(searchRule[p->GetRecordingRuleID()]);
1772  sortedList[tmpTitle.toLower()] = tmpTitle;
1773  m_progLists[tmpTitle.toLower()].push_front(p);
1774  m_progLists[tmpTitle.toLower()].setAutoDelete(false);
1775  }
1776 
1777  if ((m_viewMask & VIEW_WATCHLIST) &&
1778  !isLiveTVProg && pRecgroup != "Deleted")
1779  {
1781  {
1783  LOG(VB_FILE, LOG_INFO, QString("Auto-expire off: %1")
1784  .arg(p->GetTitle()));
1785  }
1786  else if (p->IsWatched())
1787  {
1789  LOG(VB_FILE, LOG_INFO,
1790  QString("Marked as 'watched': %1")
1791  .arg(p->GetTitle()));
1792  }
1793  else
1794  {
1795  if (p->GetRecordingRuleID())
1796  recidEpisodes[p->GetRecordingRuleID()] += 1;
1797  if (recidEpisodes[p->GetRecordingRuleID()] == 1 ||
1798  (p->GetRecordingRuleID() == 0U))
1799  {
1800  m_progLists[m_watchGroupLabel].push_front(p);
1801  m_progLists[m_watchGroupLabel].setAutoDelete(false);
1802  }
1803  else
1804  {
1806  LOG(VB_FILE, LOG_INFO,
1807  QString("Not the earliest: %1")
1808  .arg(p->GetTitle()));
1809  }
1810  }
1811  }
1812  }
1813  }
1814 
1815  if (sortedList.empty())
1816  {
1817  LOG(VB_GENERAL, LOG_WARNING, LOC + "SortedList is Empty");
1818  m_progLists[""];
1819  m_titleList << "";
1820  m_playList.clear();
1821 
1822  m_isFilling = false;
1823  return false;
1824  }
1825 
1826  QString episodeSort = gCoreContext->GetSetting("PlayBoxEpisodeSort", "Date");
1827 
1828  if (episodeSort == "OrigAirDate")
1829  {
1830  QMap<QString, ProgramList>::Iterator Iprog;
1831  for (Iprog = m_progLists.begin(); Iprog != m_progLists.end(); ++Iprog)
1832  {
1833  if (!Iprog.key().isEmpty())
1834  {
1835  std::stable_sort((*Iprog).begin(), (*Iprog).end(),
1836  (m_listOrder == 0) ?
1839  }
1840  }
1841  }
1842  else if (episodeSort == "Id")
1843  {
1844  QMap<QString, ProgramList>::Iterator Iprog;
1845  for (Iprog = m_progLists.begin(); Iprog != m_progLists.end(); ++Iprog)
1846  {
1847  if (!Iprog.key().isEmpty())
1848  {
1849  std::stable_sort((*Iprog).begin(), (*Iprog).end(),
1850  (m_listOrder == 0) ?
1853  }
1854  }
1855  }
1856  else if (episodeSort == "Date")
1857  {
1858  QMap<QString, ProgramList>::iterator it;
1859  for (it = m_progLists.begin(); it != m_progLists.end(); ++it)
1860  {
1861  if (!it.key().isEmpty())
1862  {
1863  std::stable_sort((*it).begin(), (*it).end(),
1864  (!m_listOrder) ?
1867  }
1868  }
1869  }
1870  else if (episodeSort == "Season")
1871  {
1872  QMap<QString, ProgramList>::iterator it;
1873  for (it = m_progLists.begin(); it != m_progLists.end(); ++it)
1874  {
1875  if (!it.key().isEmpty())
1876  {
1877  std::stable_sort((*it).begin(), (*it).end(),
1878  (!m_listOrder) ?
1881  }
1882  }
1883  }
1884 
1885  if (!m_progLists[m_watchGroupLabel].empty())
1886  {
1887  QDateTime now = MythDate::current();
1888  int baseValue = m_watchListMaxAge * 2 / 3;
1889 
1890  QMap<int, int> recType;
1891  QMap<int, int> maxEpisodes;
1892  QMap<int, int> avgDelay;
1893  QMap<int, int> spanHours;
1894  QMap<int, int> delHours;
1895  QMap<int, int> nextHours;
1896 
1897  MSqlQuery query(MSqlQuery::InitCon());
1898  query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
1899  "next_record, last_record, last_delete FROM record;");
1900 
1901  if (query.exec())
1902  {
1903  while (query.next())
1904  {
1905  int recid = query.value(0).toInt();
1906  recType[recid] = query.value(1).toInt();
1907  maxEpisodes[recid] = query.value(2).toInt();
1908  avgDelay[recid] = query.value(3).toInt();
1909 
1910  QDateTime next_record =
1911  MythDate::as_utc(query.value(4).toDateTime());
1912  QDateTime last_record =
1913  MythDate::as_utc(query.value(5).toDateTime());
1914  QDateTime last_delete =
1915  MythDate::as_utc(query.value(6).toDateTime());
1916 
1917  // Time between the last and next recordings
1918  spanHours[recid] = 1000;
1919  if (last_record.isValid() && next_record.isValid())
1920  spanHours[recid] =
1921  last_record.secsTo(next_record) / 3600 + 1;
1922 
1923  // Time since the last episode was deleted
1924  delHours[recid] = 1000;
1925  if (last_delete.isValid())
1926  delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
1927 
1928  // Time until the next recording if any
1929  if (next_record.isValid())
1930  nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
1931  }
1932  }
1933 
1935  while (pit != m_progLists[m_watchGroupLabel].end())
1936  {
1937  int recid = (*pit)->GetRecordingRuleID();
1938  int avgd = avgDelay[recid];
1939 
1940  if (avgd == 0)
1941  avgd = 100;
1942 
1943  // Set the intervals beyond range if there is no record entry
1944  if (spanHours[recid] == 0)
1945  {
1946  spanHours[recid] = 1000;
1947  delHours[recid] = 1000;
1948  }
1949 
1950  // add point equal to baseValue for each additional episode
1951  if (!(*pit)->GetRecordingRuleID() || maxEpisodes[recid] > 0)
1952  (*pit)->SetRecordingPriority2(0);
1953  else
1954  {
1955  (*pit)->SetRecordingPriority2(
1956  (recidEpisodes[(*pit)->GetRecordingRuleID()] - 1) *
1957  baseValue);
1958  }
1959 
1960  // add points every 3hr leading up to the next recording
1961  if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
1962  {
1963  (*pit)->SetRecordingPriority2(
1964  (*pit)->GetRecordingPriority2() +
1965  (baseValue * 3 - nextHours[recid]) / 3);
1966  }
1967 
1968  int hrs = (*pit)->GetScheduledEndTime().secsTo(now) / 3600;
1969  if (hrs < 1)
1970  hrs = 1;
1971 
1972  // add points for a new recording that decrease each hour
1973  if (hrs < 42)
1974  {
1975  (*pit)->SetRecordingPriority2(
1976  (*pit)->GetRecordingPriority2() + 42 - hrs);
1977  }
1978 
1979  // add points for how close the recorded time of day is to 'now'
1980  (*pit)->SetRecordingPriority2(
1981  (*pit)->GetRecordingPriority2() + abs((hrs % 24) - 12) * 2);
1982 
1983  // Daily
1984  if (spanHours[recid] < 50 ||
1985  recType[recid] == kDailyRecord)
1986  {
1987  if (delHours[recid] < m_watchListBlackOut * 4)
1988  {
1989  (*pit)->SetRecordingPriority2(wlDeleted);
1990  LOG(VB_FILE, LOG_INFO,
1991  QString("Recently deleted daily: %1")
1992  .arg((*pit)->GetTitle()));
1993  pit = m_progLists[m_watchGroupLabel].erase(pit);
1994  continue;
1995  }
1996 
1997  LOG(VB_FILE, LOG_INFO, QString("Daily interval: %1")
1998  .arg((*pit)->GetTitle()));
1999 
2000  if (maxEpisodes[recid] > 0)
2001  {
2002  (*pit)->SetRecordingPriority2(
2003  (*pit)->GetRecordingPriority2() +
2004  (baseValue / 2) + (hrs / 24));
2005  }
2006  else
2007  {
2008  (*pit)->SetRecordingPriority2(
2009  (*pit)->GetRecordingPriority2() +
2010  (baseValue / 5) + hrs);
2011  }
2012  }
2013  // Weekly
2014  else if (nextHours[recid] ||
2015  recType[recid] == kWeeklyRecord)
2016 
2017  {
2018  if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
2019  {
2020  (*pit)->SetRecordingPriority2(wlDeleted);
2021  LOG(VB_FILE, LOG_INFO,
2022  QString("Recently deleted weekly: %1")
2023  .arg((*pit)->GetTitle()));
2024  pit = m_progLists[m_watchGroupLabel].erase(pit);
2025  continue;
2026  }
2027 
2028  LOG(VB_FILE, LOG_INFO, QString("Weekly interval: %1")
2029  .arg((*pit)->GetTitle()));
2030 
2031  if (maxEpisodes[recid] > 0)
2032  {
2033  (*pit)->SetRecordingPriority2(
2034  (*pit)->GetRecordingPriority2() +
2035  (baseValue / 2) + (hrs / 24));
2036  }
2037  else
2038  {
2039  (*pit)->SetRecordingPriority2(
2040  (*pit)->GetRecordingPriority2() +
2041  (baseValue / 3) + (baseValue * hrs / 24 / 4));
2042  }
2043  }
2044  // Not recurring
2045  else
2046  {
2047  if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
2048  {
2049  (*pit)->SetRecordingPriority2(wlDeleted);
2050  pit = m_progLists[m_watchGroupLabel].erase(pit);
2051  continue;
2052  }
2053 
2054  // add points for a new Single or final episode
2055  if (hrs < 36)
2056  {
2057  (*pit)->SetRecordingPriority2(
2058  (*pit)->GetRecordingPriority2() +
2059  baseValue * (36 - hrs) / 36);
2060  }
2061 
2062  if (avgd != 100)
2063  {
2064  if (maxEpisodes[recid] > 0)
2065  {
2066  (*pit)->SetRecordingPriority2(
2067  (*pit)->GetRecordingPriority2() +
2068  (baseValue / 2) + (hrs / 24));
2069  }
2070  else
2071  {
2072  (*pit)->SetRecordingPriority2(
2073  (*pit)->GetRecordingPriority2() +
2074  (baseValue / 3) + (baseValue * hrs / 24 / 4));
2075  }
2076  }
2077  else if ((hrs / 24) < m_watchListMaxAge)
2078  {
2079  (*pit)->SetRecordingPriority2(
2080  (*pit)->GetRecordingPriority2() +
2081  hrs / 24);
2082  }
2083  else
2084  {
2085  (*pit)->SetRecordingPriority2(
2086  (*pit)->GetRecordingPriority2() +
2088  }
2089  }
2090 
2091  // Factor based on the average time shift delay.
2092  // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
2093  int delaypct = avgd / 3 + 67;
2094 
2095  if (avgd < 100)
2096  {
2097  (*pit)->SetRecordingPriority2(
2098  (*pit)->GetRecordingPriority2() * (200 - delaypct) / 100);
2099  }
2100  else if (avgd > 100)
2101  {
2102  (*pit)->SetRecordingPriority2(
2103  (*pit)->GetRecordingPriority2() * 100 / delaypct);
2104  }
2105 
2106  LOG(VB_FILE, LOG_INFO, QString(" %1 %2 %3")
2107  .arg(MythDate::toString((*pit)->GetScheduledStartTime(),
2109  .arg((*pit)->GetRecordingPriority2())
2110  .arg((*pit)->GetTitle()));
2111 
2112  ++pit;
2113  }
2114  std::stable_sort(m_progLists[m_watchGroupLabel].begin(),
2117  }
2118 
2119  m_titleList = QStringList("");
2120  if (!m_progLists[m_watchGroupLabel].empty())
2122  if ((!m_progLists["livetv"].empty()) &&
2123  (!sortedList.values().contains(tr("Live TV"))))
2124  m_titleList << tr("Live TV");
2125  m_titleList << sortedList.values();
2126 
2127  // Populate list of recording groups
2128  if (!m_programInfoCache.empty())
2129  {
2130  QMutexLocker locker(&m_recGroupsLock);
2131 
2132  m_recGroups.clear();
2133  m_recGroupIdx = -1;
2134 
2135  m_recGroups.append("All Programs");
2136 
2137  MSqlQuery query(MSqlQuery::InitCon());
2138 
2139  query.prepare("SELECT distinct recgroup from recorded WHERE "
2140  "deletepending = 0 ORDER BY recgroup");
2141  if (query.exec())
2142  {
2143  QString name;
2144  while (query.next())
2145  {
2146  name = query.value(0).toString();
2147  if (name != "Deleted" && name != "LiveTV" && !name.startsWith('.'))
2148  {
2149  m_recGroups.append(name);
2150  m_recGroupType[name] = "recgroup";
2151  }
2152  }
2153 
2155  if (m_recGroupIdx < 0)
2156  m_recGroupIdx = 0;
2157  }
2158  }
2159 
2161  UpdateUIGroupList(groupSelPref);
2162  UpdateUsageUI();
2163 
2164  QList<uint>::const_iterator it = m_playList.begin();
2165  for (; it != m_playList.end(); ++it)
2166  {
2167  ProgramInfo *pginfo = FindProgramInUILists(*it);
2168  if (!pginfo)
2169  continue;
2170  MythUIButtonListItem *item =
2171  m_recordingList->GetItemByData(qVariantFromValue(pginfo));
2172  if (item)
2173  item->DisplayState("yes", "playlist");
2174  }
2175 
2177  groupSelPref, itemSelPref, itemTopPref);
2178 
2179  m_isFilling = false;
2180 
2181  return true;
2182 }
2183 
2185 {
2186  if (_random)
2187  {
2188  m_playListPlay.clear();
2189  QList<uint> tmp = m_playList;
2190  while (!tmp.isEmpty())
2191  {
2192  uint i = random() % tmp.size();
2193  m_playListPlay.append(tmp[i]);
2194  tmp.removeAll(tmp[i]);
2195  }
2196  }
2197  else
2198  {
2200  }
2201 
2202  QCoreApplication::postEvent(
2203  this, new MythEvent("PLAY_PLAYLIST"));
2204 }
2205 
2207 {
2208  if (!item)
2209  item = m_recordingList->GetItemCurrent();
2210 
2211  if (!item)
2212  return;
2213 
2214  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
2215 
2216  const bool ignoreBookmark = false;
2217  const bool ignoreProgStart = false;
2218  const bool ignoreLastPlayPos = true;
2219  const bool underNetworkControl = false;
2220  if (pginfo)
2221  PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2222  underNetworkControl);
2223 }
2224 
2226 {
2227  if (!item)
2228  item = m_recordingList->GetItemCurrent();
2229 
2230  if (!item)
2231  return;
2232 
2233  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
2234 
2235  const bool ignoreBookmark = false;
2236  const bool ignoreProgStart = true;
2237  const bool ignoreLastPlayPos = true;
2238  const bool underNetworkControl = false;
2239  if (pginfo)
2240  PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2241  underNetworkControl);
2242 }
2243 
2245 {
2246  if (!item)
2247  item = m_recordingList->GetItemCurrent();
2248 
2249  if (!item)
2250  return;
2251 
2252  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
2253 
2254  const bool ignoreBookmark = true;
2255  const bool ignoreProgStart = true;
2256  const bool ignoreLastPlayPos = true;
2257  const bool underNetworkControl = false;
2258  if (pginfo)
2259  PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2260  underNetworkControl);
2261 }
2262 
2264 {
2265  if (!item)
2266  item = m_recordingList->GetItemCurrent();
2267 
2268  if (!item)
2269  return;
2270 
2271  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
2272 
2273  const bool ignoreBookmark = true;
2274  const bool ignoreProgStart = true;
2275  const bool ignoreLastPlayPos = false;
2276  const bool underNetworkControl = false;
2277  if (pginfo)
2278  PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2279  underNetworkControl);
2280 }
2281 
2282 void PlaybackBox::PlayX(const ProgramInfo &pginfo,
2283  bool ignoreBookmark,
2284  bool ignoreProgStart,
2285  bool ignoreLastPlayPos,
2286  bool underNetworkControl)
2287 {
2288  if (!m_player)
2289  {
2290  Play(pginfo, false, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2291  underNetworkControl);
2292  return;
2293  }
2294 
2295  if (!m_player->IsSameProgram(0, &pginfo))
2296  {
2298  m_player_selected_new_show.push_back(
2299  ignoreBookmark ? "1" : "0");
2300  m_player_selected_new_show.push_back(
2301  underNetworkControl ? "1" : "0");
2302  // XXX add anything for ignoreProgStart and ignoreLastPlayPos?
2303  }
2304  Close();
2305 }
2306 
2308 {
2309  ProgramInfo *pginfo = GetCurrentProgram();
2310  if (pginfo)
2311  pginfo->SaveBookmark(0);
2312 }
2313 
2315 {
2316  ProgramInfo *pginfo = GetCurrentProgram();
2317  if (pginfo)
2318  m_helper.StopRecording(*pginfo);
2319 }
2320 
2322 {
2323  if (!item)
2324  return;
2325 
2326  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
2327 
2328  if (!pginfo)
2329  return;
2330 
2331  if (pginfo->GetAvailableStatus() == asPendingDelete)
2332  {
2333  LOG(VB_GENERAL, LOG_ERR, QString("deleteSelected(%1) -- failed ")
2334  .arg(pginfo->toString(ProgramInfo::kTitleSubtitle)) +
2335  QString("availability status: %1 ")
2336  .arg(pginfo->GetAvailableStatus()));
2337 
2338  ShowOkPopup(tr("Cannot delete\n") +
2339  tr("This recording is already being deleted"));
2340  }
2341  else if (!pginfo->QueryIsDeleteCandidate())
2342  {
2343  QString byWho;
2344  pginfo->QueryIsInUse(byWho);
2345 
2346  LOG(VB_GENERAL, LOG_ERR, QString("deleteSelected(%1) -- failed ")
2347  .arg(pginfo->toString(ProgramInfo::kTitleSubtitle)) +
2348  QString("delete candidate: %1 in use by %2")
2349  .arg(pginfo->QueryIsDeleteCandidate()).arg(byWho));
2350 
2351  if (byWho.isEmpty())
2352  {
2353  ShowOkPopup(tr("Cannot delete\n") +
2354  tr("This recording is already being deleted"));
2355  }
2356  else
2357  {
2358  ShowOkPopup(tr("Cannot delete\n") +
2359  tr("This recording is currently in use by:") + "\n" +
2360  byWho);
2361  }
2362  }
2363  else
2364  {
2365  push_onto_del(m_delList, *pginfo);
2367  }
2368 }
2369 
2371 {
2372  ProgramInfo *pginfo = nullptr;
2373 
2375 
2376  if (!item)
2377  return nullptr;
2378 
2379  pginfo = item->GetData().value<ProgramInfo *>();
2380 
2381  if (!pginfo)
2382  return nullptr;
2383 
2384  return pginfo;
2385 }
2386 
2388 {
2389  if (!item)
2390  return;
2391 
2393 }
2394 
2395 void PlaybackBox::popupClosed(const QString& which, int result)
2396 {
2397  m_menuDialog = nullptr;
2398 
2399  if (result == -2)
2400  {
2401  if (!m_doToggleMenu)
2402  {
2403  m_doToggleMenu = true;
2404  return;
2405  }
2406 
2407  if (which == "groupmenu")
2408  {
2409  ProgramInfo *pginfo = GetCurrentProgram();
2410  if (pginfo)
2411  {
2413 
2414  if ((asPendingDelete == pginfo->GetAvailableStatus()) ||
2415  (asDeleted == pginfo->GetAvailableStatus()) ||
2416  (asNotYetAvailable == pginfo->GetAvailableStatus()))
2417  {
2418  ShowAvailabilityPopup(*pginfo);
2419  }
2420  else
2421  {
2422  ShowActionPopup(*pginfo);
2423  m_doToggleMenu = false;
2424  }
2425  }
2426  }
2427  else if (which == "actionmenu")
2428  {
2429  ShowGroupPopup();
2430  m_doToggleMenu = false;
2431  }
2432  }
2433  else
2434  m_doToggleMenu = true;
2435 }
2436 
2438 {
2439  QString label = tr("Group List Menu");
2440 
2441  ProgramInfo *pginfo = GetCurrentProgram();
2442 
2443  m_popupMenu = new MythMenu(label, this, "groupmenu");
2444 
2445  m_popupMenu->AddItem(tr("Change Group Filter"),
2446  SLOT(showGroupFilter()));
2447 
2448  m_popupMenu->AddItem(tr("Change Group View"),
2449  SLOT(showViewChanger()));
2450 
2451  if (m_recGroupType[m_recGroup] == "recgroup")
2452  m_popupMenu->AddItem(tr("Change Group Password"),
2453  SLOT(showRecGroupPasswordChanger()));
2454 
2455  if (!m_playList.isEmpty())
2456  {
2457  m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
2458  }
2459  else if (!m_player)
2460  {
2461  if (GetFocusWidget() == m_groupList)
2462  {
2463  m_popupMenu->AddItem(tr("Add this Group to Playlist"),
2464  SLOT(togglePlayListTitle()));
2465  }
2466  else if (pginfo)
2467  {
2468  m_popupMenu->AddItem(tr("Add this recording to Playlist"),
2469  SLOT(togglePlayListItem()));
2470  }
2471  }
2472 
2473  m_popupMenu->AddItem(tr("Help (Status Icons)"), SLOT(showIconHelp()));
2474 
2475  DisplayPopupMenu();
2476 }
2477 
2479  const ProgramInfo &rec,
2480  bool inPlaylist, bool ignoreBookmark, bool ignoreProgStart,
2481  bool ignoreLastPlayPos, bool underNetworkControl)
2482 {
2483  bool playCompleted = false;
2484 
2485  if (m_player)
2486  return true;
2487 
2488  if ((asAvailable != rec.GetAvailableStatus()) || !rec.GetFilesize() ||
2489  !rec.IsPathSet())
2490  {
2492  rec, (inPlaylist) ? kCheckForPlaylistAction : kCheckForPlayAction);
2493  return false;
2494  }
2495 
2496  for (size_t i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
2497  {
2498  if (!m_artImage[i])
2499  continue;
2500 
2501  m_artTimer[i]->stop();
2502  m_artImage[i]->Reset();
2503  }
2504 
2505  ProgramInfo tvrec(rec);
2506 
2507  m_playingSomething = true;
2508  int initIndex = m_recordingList->StopLoad();
2509 
2510  if (!gCoreContext->GetBoolSetting("UseProgStartMark", false))
2511  ignoreProgStart = true;
2512 
2513  uint flags =
2514  (inPlaylist ? kStartTVInPlayList : kStartTVNoFlags) |
2515  (underNetworkControl ? kStartTVByNetworkCommand : kStartTVNoFlags) |
2516  (!ignoreLastPlayPos ? kStartTVAllowLastPlayPos : kStartTVNoFlags) |
2517  (ignoreProgStart ? kStartTVIgnoreProgStart : kStartTVNoFlags) |
2518  (ignoreBookmark ? kStartTVIgnoreBookmark : kStartTVNoFlags);
2519 
2520  playCompleted = TV::StartTV(&tvrec, flags);
2521 
2522  m_playingSomething = false;
2523  m_recordingList->LoadInBackground(initIndex);
2524 
2525  if (inPlaylist && !m_playListPlay.empty())
2526  {
2527  QCoreApplication::postEvent(
2528  this, new MythEvent("PLAY_PLAYLIST"));
2529  }
2530  else
2531  {
2532  // User may have saved or deleted a bookmark
2533  // requiring update of bookmark icon..
2535  if (pginfo)
2536  UpdateUIListItem(pginfo, true);
2537  }
2538 
2539  if (m_needUpdate)
2541 
2542  return playCompleted;
2543 }
2544 
2545 void PlaybackBox::RemoveProgram( uint recordingID, bool forgetHistory,
2546  bool forceMetadataDelete)
2547 {
2548  ProgramInfo *delItem = FindProgramInUILists(recordingID);
2549 
2550  if (!delItem)
2551  return;
2552 
2553  if (!forceMetadataDelete &&
2554  ((delItem->GetAvailableStatus() == asPendingDelete) ||
2555  !delItem->QueryIsDeleteCandidate()))
2556  {
2557  return;
2558  }
2559 
2560  if (m_playList.contains(delItem->GetRecordingID()))
2561  togglePlayListItem(delItem);
2562 
2563  if (!forceMetadataDelete)
2564  delItem->UpdateLastDelete(true);
2565 
2566  delItem->SetAvailableStatus(asPendingDelete, "RemoveProgram");
2568  forceMetadataDelete, forgetHistory);
2569 
2570  // if the item is in the current recording list UI then delete it.
2571  MythUIButtonListItem *uiItem =
2572  m_recordingList->GetItemByData(qVariantFromValue(delItem));
2573  if (uiItem)
2574  m_recordingList->RemoveItem(uiItem);
2575 }
2576 
2578 {
2580 }
2581 
2583 {
2585 }
2586 
2588 {
2590 }
2591 
2593 {
2594  QString label;
2595  switch (type)
2596  {
2597  case kDeleteRecording:
2598  label = tr("Are you sure you want to delete:"); break;
2599  case kForceDeleteRecording:
2600  label = tr("Recording file does not exist.\n"
2601  "Are you sure you want to delete:");
2602  break;
2603  case kStopRecording:
2604  label = tr("Are you sure you want to stop:"); break;
2605  }
2606 
2607  ProgramInfo *delItem = nullptr;
2608  if (m_delList.empty() && (delItem = GetCurrentProgram()))
2609  {
2610  push_onto_del(m_delList, *delItem);
2611  }
2612  else if (m_delList.size() >= 3)
2613  {
2614  delItem = FindProgramInUILists(m_delList[0].toUInt());
2615  }
2616 
2617  if (!delItem)
2618  return;
2619 
2620  uint other_delete_cnt = (m_delList.size() / 3) - 1;
2621 
2622  label += CreateProgramInfoString(*delItem);
2623 
2624  m_popupMenu = new MythMenu(label, this, "deletemenu");
2625 
2626  QString tmpmessage;
2627  const char *tmpslot = nullptr;
2628 
2629  if ((kDeleteRecording == type) &&
2630  delItem->GetRecordingGroup() != "Deleted" &&
2631  delItem->GetRecordingGroup() != "LiveTV")
2632  {
2633  tmpmessage = tr("Yes, and allow re-record");
2634  tmpslot = SLOT(DeleteForgetHistory());
2635  m_popupMenu->AddItem(tmpmessage, tmpslot);
2636  }
2637 
2638  switch (type)
2639  {
2640  case kDeleteRecording:
2641  tmpmessage = tr("Yes, delete it");
2642  tmpslot = SLOT(Delete());
2643  break;
2644  case kForceDeleteRecording:
2645  tmpmessage = tr("Yes, delete it");
2646  tmpslot = SLOT(DeleteForce());
2647  break;
2648  case kStopRecording:
2649  tmpmessage = tr("Yes, stop recording");
2650  tmpslot = SLOT(StopSelected());
2651  break;
2652  }
2653 
2654  bool defaultIsYes =
2655  ((kDeleteRecording != type) &&
2656  (kForceDeleteRecording != type) &&
2657  (delItem->QueryAutoExpire() != kDisableAutoExpire));
2658 
2659  m_popupMenu->AddItem(tmpmessage, tmpslot, nullptr, defaultIsYes);
2660 
2661  if ((kForceDeleteRecording == type) && other_delete_cnt)
2662  {
2663  tmpmessage = tr("Yes, delete it and the remaining %1 list items")
2664  .arg(other_delete_cnt);
2665  tmpslot = SLOT(DeleteForceAllRemaining());
2666  m_popupMenu->AddItem(tmpmessage, tmpslot);
2667  }
2668 
2669  switch (type)
2670  {
2671  case kDeleteRecording:
2672  case kForceDeleteRecording:
2673  tmpmessage = tr("No, keep it");
2674  tmpslot = SLOT(DeleteIgnore());
2675  break;
2676  case kStopRecording:
2677  tmpmessage = tr("No, continue recording");
2678  tmpslot = SLOT(DeleteIgnore());
2679  break;
2680  }
2681  m_popupMenu->AddItem(tmpmessage, tmpslot, nullptr, !defaultIsYes);
2682 
2683  if ((type == kForceDeleteRecording) && other_delete_cnt)
2684  {
2685  tmpmessage = tr("No, and keep the remaining %1 list items")
2686  .arg(other_delete_cnt);
2687  tmpslot = SLOT(DeleteIgnoreAllRemaining());
2688  m_popupMenu->AddItem(tmpmessage, tmpslot);
2689  }
2690 
2691  DisplayPopupMenu();
2692 }
2693 
2695 {
2696  QString msg = pginfo.toString(ProgramInfo::kTitleSubtitle, " ");
2697  msg += "\n";
2698 
2699  QString byWho;
2700  switch (pginfo.GetAvailableStatus())
2701  {
2702  case asAvailable:
2703  if (pginfo.QueryIsInUse(byWho))
2704  {
2705  ShowNotification(tr("Recording Available\n"),
2706  _Location, msg +
2707  tr("This recording is currently in "
2708  "use by:") + "\n" + byWho);
2709  }
2710  else
2711  {
2712  ShowNotification(tr("Recording Available\n"),
2713  _Location, msg +
2714  tr("This recording is currently "
2715  "Available"));
2716  }
2717  break;
2718  case asPendingDelete:
2719  ShowNotificationError(tr("Recording Unavailable\n"),
2720  _Location, msg +
2721  tr("This recording is currently being "
2722  "deleted and is unavailable"));
2723  break;
2724  case asDeleted:
2725  ShowNotificationError(tr("Recording Unavailable\n"),
2726  _Location, msg +
2727  tr("This recording has been "
2728  "deleted and is unavailable"));
2729  break;
2730  case asFileNotFound:
2731  ShowNotificationError(tr("Recording Unavailable\n"),
2732  _Location, msg +
2733  tr("The file for this recording can "
2734  "not be found"));
2735  break;
2736  case asZeroByte:
2737  ShowNotificationError(tr("Recording Unavailable\n"),
2738  _Location, msg +
2739  tr("The file for this recording is "
2740  "empty."));
2741  break;
2742  case asNotYetAvailable:
2743  ShowNotificationError(tr("Recording Unavailable\n"),
2744  _Location, msg +
2745  tr("This recording is not yet "
2746  "available."));
2747  }
2748 }
2749 
2751 {
2752  QString label = tr("There is %n item(s) in the playlist. Actions affect "
2753  "all items in the playlist", "", m_playList.size());
2754 
2755  MythMenu *menu = new MythMenu(label, this, "slotmenu");
2756 
2757  menu->AddItem(tr("Play"), SLOT(doPlayList()));
2758  menu->AddItem(tr("Shuffle Play"), SLOT(doPlayListRandom()));
2759  menu->AddItem(tr("Clear Playlist"), SLOT(doClearPlaylist()));
2760 
2761  if (GetFocusWidget() == m_groupList)
2762  {
2763  if ((m_viewMask & VIEW_TITLES))
2764  menu->AddItem(tr("Toggle playlist for this Category/Title"),
2765  SLOT(togglePlayListTitle()));
2766  else
2767  menu->AddItem(tr("Toggle playlist for this Group"),
2768  SLOT(togglePlayListTitle()));
2769  }
2770  else
2771  menu->AddItem(tr("Toggle playlist for this recording"),
2772  SLOT(togglePlayListItem()));
2773 
2774  menu->AddItem(tr("Storage Options"), nullptr, createPlaylistStorageMenu());
2775  menu->AddItem(tr("Job Options"), nullptr, createPlaylistJobMenu());
2776  menu->AddItem(tr("Delete"), SLOT(PlaylistDelete()));
2777  menu->AddItem(tr("Delete, and allow re-record"),
2778  SLOT(PlaylistDeleteForgetHistory()));
2779 
2780  return menu;
2781 }
2782 
2784 {
2785  QString label = tr("There is %n item(s) in the playlist. Actions affect "
2786  "all items in the playlist", "", m_playList.size());
2787 
2788  MythMenu *menu = new MythMenu(label, this, "slotmenu");
2789 
2790  menu->AddItem(tr("Change Recording Group"), SLOT(ShowRecGroupChangerUsePlaylist()));
2791  menu->AddItem(tr("Change Playback Group"), SLOT(ShowPlayGroupChangerUsePlaylist()));
2792  menu->AddItem(tr("Disable Auto Expire"), SLOT(doPlaylistExpireSetOff()));
2793  menu->AddItem(tr("Enable Auto Expire"), SLOT(doPlaylistExpireSetOn()));
2794  menu->AddItem(tr("Mark as Watched"), SLOT(doPlaylistWatchedSetOn()));
2795  menu->AddItem(tr("Mark as Unwatched"), SLOT(doPlaylistWatchedSetOff()));
2796  menu->AddItem(tr("Allow Re-record"), SLOT(doPlaylistAllowRerecord()));
2797 
2798  return menu;
2799 }
2800 
2802 {
2803  QString label = tr("There is %n item(s) in the playlist. Actions affect "
2804  "all items in the playlist", "", m_playList.size());
2805 
2806  MythMenu *menu = new MythMenu(label, this, "slotmenu");
2807 
2808  QString jobTitle;
2809  QString command;
2810  QList<uint>::Iterator it;
2811  bool isTranscoding = true;
2812  bool isFlagging = true;
2813  bool isMetadataLookup = true;
2814  bool isRunningUserJob1 = true;
2815  bool isRunningUserJob2 = true;
2816  bool isRunningUserJob3 = true;
2817  bool isRunningUserJob4 = true;
2818 
2819  for(it = m_playList.begin(); it != m_playList.end(); ++it)
2820  {
2821  ProgramInfo *tmpItem = FindProgramInUILists(*it);
2822  if (tmpItem)
2823  {
2825  JOB_TRANSCODE,
2826  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2827  isTranscoding = false;
2829  JOB_COMMFLAG,
2830  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2831  isFlagging = false;
2833  JOB_METADATA,
2834  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2835  isMetadataLookup = false;
2837  JOB_USERJOB1,
2838  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2839  isRunningUserJob1 = false;
2841  JOB_USERJOB2,
2842  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2843  isRunningUserJob2 = false;
2845  JOB_USERJOB3,
2846  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2847  isRunningUserJob3 = false;
2849  JOB_USERJOB4,
2850  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2851  isRunningUserJob4 = false;
2852  if (!isTranscoding && !isFlagging && !isRunningUserJob1 &&
2853  !isRunningUserJob2 && !isRunningUserJob3 && !isRunningUserJob4)
2854  break;
2855  }
2856  }
2857 
2858  if (!isTranscoding)
2859  menu->AddItem(tr("Begin Transcoding"), SLOT(doPlaylistBeginTranscoding()));
2860  else
2861  menu->AddItem(tr("Stop Transcoding"), SLOT(stopPlaylistTranscoding()));
2862 
2863  if (!isFlagging)
2864  menu->AddItem(tr("Begin Commercial Detection"), SLOT(doPlaylistBeginFlagging()));
2865  else
2866  menu->AddItem(tr("Stop Commercial Detection"), SLOT(stopPlaylistFlagging()));
2867 
2868  if (!isMetadataLookup)
2869  menu->AddItem(tr("Begin Metadata Lookup"), SLOT(doPlaylistBeginLookup()));
2870  else
2871  menu->AddItem(tr("Stop Metadata Lookup"), SLOT(stopPlaylistLookup()));
2872 
2873  command = gCoreContext->GetSetting("UserJob1", "");
2874  if (!command.isEmpty())
2875  {
2876  jobTitle = gCoreContext->GetSetting("UserJobDesc1");
2877 
2878  if (!isRunningUserJob1)
2879  menu->AddItem(tr("Begin") + ' ' + jobTitle,
2880  SLOT(doPlaylistBeginUserJob1()));
2881  else
2882  menu->AddItem(tr("Stop") + ' ' + jobTitle,
2883  SLOT(stopPlaylistUserJob1()));
2884  }
2885 
2886  command = gCoreContext->GetSetting("UserJob2", "");
2887  if (!command.isEmpty())
2888  {
2889  jobTitle = gCoreContext->GetSetting("UserJobDesc2");
2890 
2891  if (!isRunningUserJob2)
2892  menu->AddItem(tr("Begin") + ' ' + jobTitle,
2893  SLOT(doPlaylistBeginUserJob2()));
2894  else
2895  menu->AddItem(tr("Stop") + ' ' + jobTitle,
2896  SLOT(stopPlaylistUserJob2()));
2897  }
2898 
2899  command = gCoreContext->GetSetting("UserJob3", "");
2900  if (!command.isEmpty())
2901  {
2902  jobTitle = gCoreContext->GetSetting("UserJobDesc3");
2903 
2904  if (!isRunningUserJob3)
2905  menu->AddItem(tr("Begin") + ' ' + jobTitle,
2906  SLOT(doPlaylistBeginUserJob3()));
2907  else
2908  menu->AddItem(tr("Stop") + ' ' + jobTitle,
2909  SLOT(stopPlaylistUserJob3()));
2910  }
2911 
2912  command = gCoreContext->GetSetting("UserJob4", "");
2913  if (!command.isEmpty())
2914  {
2915  jobTitle = gCoreContext->GetSetting("UserJobDesc4");
2916 
2917  if (!isRunningUserJob4)
2918  menu->AddItem(QString("%1 %2").arg(tr("Begin")).arg(jobTitle),
2919  SLOT(doPlaylistBeginUserJob4()));
2920  else
2921  menu->AddItem(QString("%1 %2").arg(tr("Stop")).arg(jobTitle),
2922  SLOT(stopPlaylistUserJob4()));
2923  }
2924 
2925  return menu;
2926 }
2927 
2929 {
2930  if (m_menuDialog || !m_popupMenu)
2931  return;
2932 
2933  m_menuDialog = new MythDialogBox(m_popupMenu, m_popupStack, "pbbmainmenupopup");
2934 
2935  if (m_menuDialog->Create())
2936  {
2938  connect(m_menuDialog, SIGNAL(Closed(QString,int)), SLOT(popupClosed(QString,int)));
2939  }
2940  else
2941  delete m_menuDialog;
2942 }
2943 
2945 {
2946  if (m_menuDialog)
2947  return;
2948 
2949  if (GetFocusWidget() == m_groupList)
2950  ShowGroupPopup();
2951  else
2952  {
2953  ProgramInfo *pginfo = GetCurrentProgram();
2954  if (pginfo)
2955  {
2957  *pginfo, kCheckForMenuAction);
2958 
2959  if ((asPendingDelete == pginfo->GetAvailableStatus()) ||
2960  (asDeleted == pginfo->GetAvailableStatus()) ||
2961  (asNotYetAvailable == pginfo->GetAvailableStatus()))
2962  {
2963  ShowAvailabilityPopup(*pginfo);
2964  }
2965  else
2966  {
2967  ShowActionPopup(*pginfo);
2968  }
2969  }
2970  else
2971  ShowGroupPopup();
2972  }
2973 }
2974 
2976 {
2977  ProgramInfo *pginfo = GetCurrentProgram();
2978  if (!pginfo)
2979  return nullptr;
2980 
2981  QString title = tr("Play Options") + CreateProgramInfoString(*pginfo);
2982 
2983  MythMenu *menu = new MythMenu(title, this, "slotmenu");
2984 
2985  if (pginfo->IsBookmarkSet())
2986  menu->AddItem(tr("Play from bookmark"), SLOT(PlayFromBookmark()));
2987 
2988  if (pginfo->QueryLastPlayPos())
2989  menu->AddItem(tr("Play from last played position"),
2990  SLOT(PlayFromLastPlayPos()));
2991 
2992  menu->AddItem(tr("Play from beginning"), SLOT(PlayFromBeginning()));
2993 
2994  if (pginfo->IsBookmarkSet())
2995  menu->AddItem(tr("Clear bookmark"), SLOT(ClearBookmark()));
2996 
2997  return menu;
2998 }
2999 
3001 {
3002  ProgramInfo *pginfo = GetCurrentProgram();
3003  if (!pginfo)
3004  return nullptr;
3005 
3006  QString title = tr("Storage Options") + CreateProgramInfoString(*pginfo);
3007  QString autoExpireText = (pginfo->IsAutoExpirable()) ?
3008  tr("Disable Auto Expire") : tr("Enable Auto Expire");
3009  QString preserveText = (pginfo->IsPreserved()) ?
3010  tr("Do not preserve this episode") : tr("Preserve this episode");
3011 
3012  MythMenu *menu = new MythMenu(title, this, "slotmenu");
3013  menu->AddItem(tr("Change Recording Group"), SLOT(ShowRecGroupChanger()));
3014  menu->AddItem(tr("Change Playback Group"), SLOT(ShowPlayGroupChanger()));
3015  menu->AddItem(autoExpireText, SLOT(toggleAutoExpire()));
3016  menu->AddItem(preserveText, SLOT(togglePreserveEpisode()));
3017 
3018  return menu;
3019 }
3020 
3022 {
3023  ProgramInfo *pginfo = GetCurrentProgram();
3024  if (!pginfo)
3025  return nullptr;
3026 
3027  QString title = tr("Scheduling Options") + CreateProgramInfoString(*pginfo);
3028 
3029  MythMenu *menu = new MythMenu(title, this, "slotmenu");
3030 
3031  menu->AddItem(tr("Edit Recording Schedule"), SLOT(EditScheduled()));
3032 
3033  menu->AddItem(tr("Allow this episode to re-record"), SLOT(doAllowRerecord()));
3034 
3035  menu->AddItem(tr("Show Recording Details"), SLOT(ShowDetails()));
3036 
3037  menu->AddItem(tr("Change Recording Metadata"), SLOT(showMetadataEditor()));
3038 
3039  menu->AddItem(tr("Custom Edit"), SLOT(EditCustom()));
3040 
3041  return menu;
3042 }
3043 
3045 {
3046  ProgramInfo *pginfo = GetCurrentProgram();
3047  if (!pginfo)
3048  return nullptr;
3049 
3050  QString title = tr("Job Options") + CreateProgramInfoString(*pginfo);
3051 
3052  MythMenu *menu = new MythMenu(title, this, "slotmenu");
3053 
3054  QString command;
3055 
3056  bool add[7] =
3057  {
3058  true,
3059  true,
3060  true,
3061  !gCoreContext->GetSetting("UserJob1", "").isEmpty(),
3062  !gCoreContext->GetSetting("UserJob2", "").isEmpty(),
3063  !gCoreContext->GetSetting("UserJob3", "").isEmpty(),
3064  !gCoreContext->GetSetting("UserJob4", "").isEmpty(),
3065  };
3066  int jobs[7] =
3067  {
3068  JOB_TRANSCODE,
3069  JOB_COMMFLAG,
3070  JOB_METADATA,
3071  JOB_USERJOB1,
3072  JOB_USERJOB2,
3073  JOB_USERJOB3,
3074  JOB_USERJOB4,
3075  };
3076  QString desc[14] =
3077  {
3078  // stop start
3079  tr("Stop Transcoding"), tr("Begin Transcoding"),
3080  tr("Stop Commercial Detection"), tr("Begin Commercial Detection"),
3081  tr("Stop Metadata Lookup"), tr("Begin Metadata Lookup"),
3082  "1", "1",
3083  "2", "2",
3084  "3", "3",
3085  "4", "4",
3086  };
3087  const char *myslots[14] =
3088  { // stop start
3090  SLOT(doBeginFlagging()), SLOT(doBeginFlagging()),
3091  SLOT(doBeginLookup()), SLOT(doBeginLookup()),
3092  SLOT(doBeginUserJob1()), SLOT(doBeginUserJob1()),
3093  SLOT(doBeginUserJob2()), SLOT(doBeginUserJob2()),
3094  SLOT(doBeginUserJob3()), SLOT(doBeginUserJob3()),
3095  SLOT(doBeginUserJob4()), SLOT(doBeginUserJob4()),
3096  };
3097 
3098  for (size_t i = 0; i < sizeof(add) / sizeof(bool); i++)
3099  {
3100  if (!add[i])
3101  continue;
3102 
3103  QString stop_desc = desc[i*2+0];
3104  QString start_desc = desc[i*2+1];
3105 
3106  if (start_desc.toUInt())
3107  {
3108  QString jobTitle = gCoreContext->GetSetting(
3109  "UserJobDesc"+start_desc, tr("User Job") + " #" + start_desc);
3110  stop_desc = tr("Stop") + ' ' + jobTitle;
3111  start_desc = tr("Begin") + ' ' + jobTitle;
3112  }
3113 
3114  bool running = JobQueue::IsJobQueuedOrRunning(
3115  jobs[i], pginfo->GetChanID(), pginfo->GetRecordingStartTime());
3116 
3117  const char *slot = myslots[i * 2 + (running ? 0 : 1)];
3118  MythMenu *submenu = (slot == myslots[1] ? createTranscodingProfilesMenu() : nullptr);
3119 
3120  menu->AddItem((running) ? stop_desc : start_desc, slot, submenu);
3121  }
3122 
3123  return menu;
3124 }
3125 
3127 {
3128  QString label = tr("Transcoding profiles");
3129 
3130  MythMenu *menu = new MythMenu(label, this, "transcode");
3131 
3132  menu->AddItem(tr("Default"), qVariantFromValue(-1));
3133  menu->AddItem(tr("Autodetect"), qVariantFromValue(0));
3134 
3135  MSqlQuery query(MSqlQuery::InitCon());
3136  query.prepare("SELECT r.name, r.id "
3137  "FROM recordingprofiles r, profilegroups p "
3138  "WHERE p.name = 'Transcoders' "
3139  "AND r.profilegroup = p.id "
3140  "AND r.name != 'RTjpeg/MPEG4' "
3141  "AND r.name != 'MPEG2' ");
3142 
3143  if (!query.exec())
3144  {
3145  MythDB::DBError(LOC + "unable to query transcoders", query);
3146  return nullptr;
3147  }
3148 
3149  while (query.next())
3150  {
3151  QString transcoder_name = query.value(0).toString();
3152  int transcoder_id = query.value(1).toInt();
3153 
3154  // Translatable strings for known profiles
3155  if (transcoder_name == "High Quality")
3156  transcoder_name = tr("High Quality");
3157  else if (transcoder_name == "Medium Quality")
3158  transcoder_name = tr("Medium Quality");
3159  else if (transcoder_name == "Low Quality")
3160  transcoder_name = tr("Low Quality");
3161 
3162  menu->AddItem(transcoder_name, qVariantFromValue(transcoder_id));
3163  }
3164 
3165  return menu;
3166 }
3167 
3169 {
3170  ProgramInfo *pginfo = GetCurrentProgram();
3171 
3172  if (!pginfo)
3173  return;
3174 
3175  if (id >= 0)
3176  {
3177  RecordingInfo ri(*pginfo);
3179  }
3181 }
3182 
3184 {
3185  QString label =
3186  (asFileNotFound == pginfo.GetAvailableStatus()) ?
3187  tr("Recording file cannot be found") :
3188  (asZeroByte == pginfo.GetAvailableStatus()) ?
3189  tr("Recording file contains no data") :
3190  tr("Recording Options");
3191 
3192  m_popupMenu = new MythMenu(label + CreateProgramInfoString(pginfo), this, "actionmenu");
3193 
3194  if ((asFileNotFound == pginfo.GetAvailableStatus()) ||
3195  (asZeroByte == pginfo.GetAvailableStatus()))
3196  {
3197  if (m_playList.contains(pginfo.GetRecordingID()))
3198  m_popupMenu->AddItem(tr("Remove from Playlist"), SLOT(togglePlayListItem()));
3199  else
3200  m_popupMenu->AddItem(tr("Add to Playlist"), SLOT(togglePlayListItem()));
3201 
3202  if (!m_playList.isEmpty())
3203  m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
3204 
3205  m_popupMenu->AddItem(tr("Recording Options"), nullptr, createRecordingMenu());
3206 
3208  m_popupMenu->AddItem(tr("List Recorded Episodes"),
3209  SLOT(ShowRecordedEpisodes()));
3210  else
3211  m_popupMenu->AddItem(tr("List All Recordings"),
3212  SLOT(ShowAllRecordings()));
3213 
3214  m_popupMenu->AddItem(tr("Delete"), SLOT(askDelete()));
3215 
3216  DisplayPopupMenu();
3217 
3218  return;
3219  }
3220 
3221  bool sameProgram = false;
3222 
3223  if (m_player)
3224  sameProgram = m_player->IsSameProgram(0, &pginfo);
3225 
3226  TVState tvstate = kState_None;
3227 
3228  if (!sameProgram)
3229  {
3230  if (pginfo.IsBookmarkSet() || pginfo.QueryLastPlayPos())
3231  m_popupMenu->AddItem(tr("Play from..."), nullptr, createPlayFromMenu());
3232  else
3233  m_popupMenu->AddItem(tr("Play"),
3234  SLOT(PlayFromBookmarkOrProgStart()));
3235  }
3236 
3237  if (!m_player)
3238  {
3239  if (m_playList.contains(pginfo.GetRecordingID()))
3240  m_popupMenu->AddItem(tr("Remove from Playlist"),
3241  SLOT(togglePlayListItem()));
3242  else
3243  m_popupMenu->AddItem(tr("Add to Playlist"),
3244  SLOT(togglePlayListItem()));
3245  if (!m_playList.isEmpty())
3246  {
3247  m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
3248  }
3249  }
3250 
3251  if ((pginfo.GetRecordingStatus() == RecStatus::Recording ||
3252  pginfo.GetRecordingStatus() == RecStatus::Tuning ||
3253  pginfo.GetRecordingStatus() == RecStatus::Failing) &&
3254  (!(sameProgram &&
3255  (tvstate == kState_WatchingLiveTV ||
3256  tvstate == kState_WatchingRecording))))
3257  {
3258  m_popupMenu->AddItem(tr("Stop Recording"), SLOT(askStop()));
3259  }
3260 
3261  if (pginfo.IsWatched())
3262  m_popupMenu->AddItem(tr("Mark as Unwatched"), SLOT(toggleWatched()));
3263  else
3264  m_popupMenu->AddItem(tr("Mark as Watched"), SLOT(toggleWatched()));
3265 
3266  m_popupMenu->AddItem(tr("Storage Options"), nullptr, createStorageMenu());
3267  m_popupMenu->AddItem(tr("Recording Options"), nullptr, createRecordingMenu());
3268  m_popupMenu->AddItem(tr("Job Options"), nullptr, createJobMenu());
3269 
3271  m_popupMenu->AddItem(tr("List Recorded Episodes"),
3272  SLOT(ShowRecordedEpisodes()));
3273  else
3274  m_popupMenu->AddItem(tr("List All Recordings"),
3275  SLOT(ShowAllRecordings()));
3276 
3277  if (!sameProgram)
3278  {
3279  if (pginfo.GetRecordingGroup() == "Deleted")
3280  {
3281  push_onto_del(m_delList, pginfo);
3282  m_popupMenu->AddItem(tr("Undelete"), SLOT(Undelete()));
3283  m_popupMenu->AddItem(tr("Delete Forever"), SLOT(Delete()));
3284  }
3285  else
3286  {
3287  m_popupMenu->AddItem(tr("Delete"), SLOT(askDelete()));
3288  }
3289  }
3290 
3291  DisplayPopupMenu();
3292 }
3293 
3295 {
3296  QDateTime recstartts = pginfo.GetRecordingStartTime();
3297  QDateTime recendts = pginfo.GetRecordingEndTime();
3298 
3299  QString timedate = QString("%1 - %2")
3300  .arg(MythDate::toString(
3302  .arg(MythDate::toString(recendts, MythDate::kTime));
3303 
3304  QString title = pginfo.GetTitle();
3305 
3306  QString extra;
3307 
3308  if (!pginfo.GetSubtitle().isEmpty())
3309  {
3310  extra = QString('\n') + pginfo.GetSubtitle();
3311  }
3312 
3313  return QString("\n%1%2\n%3").arg(title).arg(extra).arg(timedate);
3314 }
3315 
3317 {
3318  QList<uint>::Iterator it;
3319  for (it = m_playList.begin(); it != m_playList.end(); ++it)
3320  {
3321  ProgramInfo *tmpItem = FindProgramInUILists(*it);
3322 
3323  if (!tmpItem)
3324  continue;
3325 
3326  MythUIButtonListItem *item =
3327  m_recordingList->GetItemByData(qVariantFromValue(tmpItem));
3328 
3329  if (item)
3330  item->DisplayState("no", "playlist");
3331  }
3332  m_playList.clear();
3333 }
3334 
3336 {
3337  playSelectedPlaylist(false);
3338 }
3339 
3340 
3342 {
3343  playSelectedPlaylist(true);
3344 }
3345 
3347 {
3348  ProgramInfo *pginfo = GetCurrentProgram();
3349  if (pginfo)
3350  {
3351  push_onto_del(m_delList, *pginfo);
3353  }
3354 }
3355 
3363 {
3364  ProgramInfo *pginfo = GetCurrentProgram();
3365 
3366  if (!pginfo)
3367  return;
3368 
3369  RecordingInfo ri(*pginfo);
3370  ri.ForgetHistory();
3371  *pginfo = ri;
3372 }
3373 
3375 {
3376  QList<uint>::Iterator it;
3377 
3378  for (it = m_playList.begin(); it != m_playList.end(); ++it)
3379  {
3380  ProgramInfo *pginfo;
3381  if ((pginfo = FindProgramInUILists(*it)))
3382  {
3383  RecordingInfo ri(*pginfo);
3384  ri.ForgetHistory();
3385  *pginfo = ri;
3386  }
3387  }
3388 
3389  doClearPlaylist();
3390  UpdateUILists();
3391 }
3392 
3393 void PlaybackBox::doJobQueueJob(int jobType, int jobFlags)
3394 {
3395  ProgramInfo *pginfo = GetCurrentProgram();
3396 
3397  if (!pginfo)
3398  return;
3399 
3400  ProgramInfo *tmpItem = FindProgramInUILists(*pginfo);
3401 
3403  jobType, pginfo->GetChanID(), pginfo->GetRecordingStartTime()))
3404  {
3406  jobType, pginfo->GetChanID(), pginfo->GetRecordingStartTime(),
3407  JOB_STOP);
3408  if ((jobType & JOB_COMMFLAG) && (tmpItem))
3409  {
3410  tmpItem->SetEditing(false);
3411  tmpItem->SetFlagging(false);
3412  }
3413  }
3414  else
3415  {
3416  QString jobHost;
3417  if (gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
3418  jobHost = pginfo->GetHostname();
3419 
3420  JobQueue::QueueJob(jobType, pginfo->GetChanID(),
3421  pginfo->GetRecordingStartTime(), "", "", jobHost,
3422  jobFlags);
3423  }
3424 }
3425 
3427 {
3429 }
3430 
3432 {
3434 }
3435 
3436 void PlaybackBox::doPlaylistJobQueueJob(int jobType, int jobFlags)
3437 {
3438  ProgramInfo *tmpItem;
3439  QList<uint>::Iterator it;
3440 
3441  for (it = m_playList.begin(); it != m_playList.end(); ++it)
3442  {
3443  tmpItem = FindProgramInUILists(*it);
3444  if (tmpItem &&
3446  jobType,
3447  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime())))
3448  {
3449  QString jobHost;
3450  if (gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
3451  jobHost = tmpItem->GetHostname();
3452 
3453  JobQueue::QueueJob(jobType, tmpItem->GetChanID(),
3454  tmpItem->GetRecordingStartTime(),
3455  "", "", jobHost, jobFlags);
3456  }
3457  }
3458 }
3459 
3461 {
3462  QList<uint>::Iterator it;
3463 
3464  for (it = m_playList.begin(); it != m_playList.end(); ++it)
3465  {
3466  ProgramInfo *tmpItem = FindProgramInUILists(*it);
3467  if (tmpItem &&
3469  jobType,
3470  tmpItem->GetChanID(), tmpItem->GetRecordingStartTime())))
3471  {
3473  jobType, tmpItem->GetChanID(),
3474  tmpItem->GetRecordingStartTime(), JOB_STOP);
3475 
3476  if ((jobType & JOB_COMMFLAG) && (tmpItem))
3477  {
3478  tmpItem->SetEditing(false);
3479  tmpItem->SetFlagging(false);
3480  }
3481  }
3482  }
3483 }
3484 
3486 {
3487  ProgramInfo *pginfo = GetCurrentProgram();
3488  if (pginfo)
3489  {
3490  push_onto_del(m_delList, *pginfo);
3492  }
3493 }
3494 
3495 void PlaybackBox::PlaylistDelete(bool forgetHistory)
3496 {
3497  QString forceDeleteStr("0");
3498 
3499  QList<uint>::const_iterator it;
3500  QStringList list;
3501  for (it = m_playList.begin(); it != m_playList.end(); ++it)
3502  {
3503  ProgramInfo *tmpItem = FindProgramInUILists(*it);
3504  if (tmpItem && tmpItem->QueryIsDeleteCandidate())
3505  {
3506  tmpItem->SetAvailableStatus(asPendingDelete, "PlaylistDelete");
3507  list.push_back(QString::number(tmpItem->GetRecordingID()));
3508  list.push_back(forceDeleteStr);
3509  list.push_back(forgetHistory ? "1" : "0");
3510 
3511  // if the item is in the current recording list UI then delete it.
3512  MythUIButtonListItem *uiItem =
3513  m_recordingList->GetItemByData(qVariantFromValue(tmpItem));
3514  if (uiItem)
3515  m_recordingList->RemoveItem(uiItem);
3516  }
3517  }
3518  m_playList.clear();
3519 
3520  if (!list.empty())
3521  m_helper.DeleteRecordings(list);
3522 
3523  doClearPlaylist();
3524 }
3525 
3526 // FIXME: Huh? This doesn't specify which recording to undelete, it just
3527 // undeletes the first one on the list
3529 {
3530  uint recordingID;
3531  if (extract_one_del(m_delList, recordingID))
3532  m_helper.UndeleteRecording(recordingID);
3533 }
3534 
3536 {
3537  uint recordingID;
3538  while (extract_one_del(m_delList, recordingID))
3539  {
3540  if (flags & kIgnore)
3541  continue;
3542 
3543  RemoveProgram(recordingID, (flags & kForgetHistory) != 0, (flags & kForce) != 0);
3544 
3545  if (!(flags & kAllRemaining))
3546  break;
3547  }
3548 
3549  if (!m_delList.empty())
3550  {
3551  MythEvent *e = new MythEvent("DELETE_FAILURES", m_delList);
3552  m_delList.clear();
3553  QCoreApplication::postEvent(this, e);
3554  }
3555 }
3556 
3558 {
3559  ProgramInfo *pginfo = GetCurrentProgram();
3560  if (pginfo) {
3561  QString title = pginfo->GetTitle().toLower();
3562  MythUIButtonListItem* group = m_groupList->GetItemByData(qVariantFromValue(title));
3563  if (group)
3564  {
3565  m_groupList->SetItemCurrent(group);
3566  // set focus back to previous item
3567  MythUIButtonListItem *previousItem = m_recordingList->GetItemByData(qVariantFromValue(pginfo));
3568  m_recordingList->SetItemCurrent(previousItem);
3569  }
3570  }
3571 }
3572 
3574 {
3575  ProgramInfo *pginfo = GetCurrentProgram();
3577  if (pginfo)
3578  {
3579  // set focus back to previous item
3580  MythUIButtonListItem *previousitem =
3581  m_recordingList->GetItemByData(qVariantFromValue(pginfo));
3582  m_recordingList->SetItemCurrent(previousitem);
3583  }
3584 }
3585 
3587 {
3588  return FindProgramInUILists( pginfo.GetRecordingID(),
3589  pginfo.GetRecordingGroup());
3590 }
3591 
3593  const QString& recgroup)
3594 {
3595  // LiveTV ProgramInfo's are not in the aggregated list
3596  ProgramList::iterator _it[2] = {
3597  m_progLists[tr("Live TV").toLower()].begin(), m_progLists[""].begin() };
3598  ProgramList::iterator _end[2] = {
3599  m_progLists[tr("Live TV").toLower()].end(), m_progLists[""].end() };
3600 
3601  if (recgroup != "LiveTV")
3602  {
3603  swap( _it[0], _it[1]);
3604  swap(_end[0], _end[1]);
3605  }
3606 
3607  for (uint i = 0; i < 2; i++)
3608  {
3609  ProgramList::iterator it = _it[i], end = _end[i];
3610  for (; it != end; ++it)
3611  {
3612  if ((*it)->GetRecordingID() == recordingID)
3613  {
3614  return *it;
3615  }
3616  }
3617  }
3618 
3619  return nullptr;
3620 }
3621 
3623 {
3625 
3626  if (!item)
3627  return;
3628 
3629  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
3630 
3631  if (!pginfo)
3632  return;
3633 
3634  if (pginfo)
3635  {
3636  bool on = !pginfo->IsWatched();
3637  pginfo->SaveWatched(on);
3638  item->DisplayState((on)?"yes":"on", "watched");
3639  updateIcons(pginfo);
3640 
3641  // A refill affects the responsiveness of the UI and we only
3642  // need to rebuild the list if the watch list is displayed
3643  if (m_viewMask & VIEW_WATCHLIST)
3644  UpdateUILists();
3645  }
3646 }
3647 
3649 {
3651 
3652  if (!item)
3653  return;
3654 
3655  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
3656 
3657  if (!pginfo)
3658  return;
3659 
3660  if (pginfo)
3661  {
3662  bool on = !pginfo->IsAutoExpirable();
3663  pginfo->SaveAutoExpire(
3664  (on) ? kNormalAutoExpire : kDisableAutoExpire, true);
3665  item->DisplayState((on)?"yes":"no", "autoexpire");
3666  updateIcons(pginfo);
3667  }
3668 }
3669 
3671 {
3673 
3674  if (!item)
3675  return;
3676 
3677  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
3678 
3679  if (!pginfo)
3680  return;
3681 
3682  if (pginfo)
3683  {
3684  bool on = !pginfo->IsPreserved();
3685  pginfo->SavePreserve(on);
3686  item->DisplayState(on?"yes":"no", "preserve");
3687  updateIcons(pginfo);
3688  }
3689 }
3690 
3691 void PlaybackBox::toggleView(ViewMask itemMask, bool setOn)
3692 {
3693  if (setOn)
3694  m_viewMask = (ViewMask)(m_viewMask | itemMask);
3695  else
3696  m_viewMask = (ViewMask)(m_viewMask & ~itemMask);
3697 
3698  UpdateUILists();
3699 }
3700 
3702 {
3703  QString groupname = m_groupList->GetItemCurrent()->GetData().toString();
3704 
3705  ProgramList::iterator it = m_progLists[groupname].begin();
3706  ProgramList::iterator end = m_progLists[groupname].end();
3707  for (; it != end; ++it)
3708  {
3709  if (*it && ((*it)->GetAvailableStatus() == asAvailable))
3710  togglePlayListItem(*it);
3711  }
3712 }
3713 
3715 {
3717 
3718  if (!item)
3719  return;
3720 
3721  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
3722 
3723  if (!pginfo)
3724  return;
3725 
3726  togglePlayListItem(pginfo);
3727 
3730 }
3731 
3733 {
3734  if (!pginfo)
3735  return;
3736 
3737  uint recordingID = pginfo->GetRecordingID();
3738 
3739  MythUIButtonListItem *item =
3740  m_recordingList->GetItemByData(qVariantFromValue(pginfo));
3741 
3742  if (m_playList.contains(recordingID))
3743  {
3744  if (item)
3745  item->DisplayState("no", "playlist");
3746 
3747  m_playList.removeAll(recordingID);
3748  }
3749  else
3750  {
3751  if (item)
3752  item->DisplayState("yes", "playlist");
3753  m_playList.append(recordingID);
3754  }
3755 }
3756 
3758 {
3759  int commands = 0;
3760  QString command;
3761 
3762  m_ncLock.lock();
3763  commands = m_networkControlCommands.size();
3764  m_ncLock.unlock();
3765 
3766  while (commands)
3767  {
3768  m_ncLock.lock();
3769  command = m_networkControlCommands.front();
3770  m_networkControlCommands.pop_front();
3771  m_ncLock.unlock();
3772 
3774 
3775  m_ncLock.lock();
3776  commands = m_networkControlCommands.size();
3777  m_ncLock.unlock();
3778  }
3779 }
3780 
3781 void PlaybackBox::processNetworkControlCommand(const QString &command)
3782 {
3783  QStringList tokens = command.simplified().split(" ");
3784 
3785  if (tokens.size() >= 4 && (tokens[1] == "PLAY" || tokens[1] == "RESUME"))
3786  {
3787  if (tokens.size() == 6 && tokens[2] == "PROGRAM")
3788  {
3789  int clientID = tokens[5].toInt();
3790 
3791  LOG(VB_GENERAL, LOG_INFO, LOC +
3792  QString("NetworkControl: Trying to %1 program '%2' @ '%3'")
3793  .arg(tokens[1]).arg(tokens[3]).arg(tokens[4]));
3794 
3795  if (m_playingSomething)
3796  {
3797  LOG(VB_GENERAL, LOG_ERR, LOC +
3798  "NetworkControl: Already playing");
3799 
3800  QString msg = QString(
3801  "NETWORK_CONTROL RESPONSE %1 ERROR: Unable to play, "
3802  "player is already playing another recording.")
3803  .arg(clientID);
3804 
3805  MythEvent me(msg);
3806  gCoreContext->dispatch(me);
3807  return;
3808  }
3809 
3810  uint chanid = tokens[3].toUInt();
3811  QDateTime recstartts = MythDate::fromString(tokens[4]);
3812  ProgramInfo pginfo(chanid, recstartts);
3813 
3814  if (pginfo.GetChanID())
3815  {
3816  QString msg = QString("NETWORK_CONTROL RESPONSE %1 OK")
3817  .arg(clientID);
3818  MythEvent me(msg);
3819  gCoreContext->dispatch(me);
3820 
3821  pginfo.SetPathname(pginfo.GetPlaybackURL());
3822 
3823  const bool ignoreBookmark = (tokens[1] == "PLAY");
3824  const bool ignoreProgStart = true;
3825  const bool ignoreLastPlayPos = true;
3826  const bool underNetworkControl = true;
3827  PlayX(pginfo, ignoreBookmark, ignoreProgStart,
3828  ignoreLastPlayPos, underNetworkControl);
3829  }
3830  else
3831  {
3832  QString message = QString("NETWORK_CONTROL RESPONSE %1 "
3833  "ERROR: Could not find recording for "
3834  "chanid %2 @ %3").arg(clientID)
3835  .arg(tokens[3]).arg(tokens[4]);
3836  MythEvent me(message);
3837  gCoreContext->dispatch(me);
3838  }
3839  }
3840  }
3841 }
3842 
3843 bool PlaybackBox::keyPressEvent(QKeyEvent *event)
3844 {
3845  // This should be an impossible keypress we've simulated
3846  if ((event->key() == Qt::Key_LaunchMedia) &&
3847  (event->modifiers() ==
3848  (Qt::ShiftModifier |
3849  Qt::ControlModifier |
3850  Qt::AltModifier |
3851  Qt::MetaModifier |
3852  Qt::KeypadModifier)))
3853  {
3854  event->accept();
3855  m_ncLock.lock();
3856  int commands = m_networkControlCommands.size();
3857  m_ncLock.unlock();
3858  if (commands)
3860  return true;
3861  }
3862 
3863  if (GetFocusWidget()->keyPressEvent(event))
3864  return true;
3865 
3866  QStringList actions;
3867  bool handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend",
3868  event, actions);
3869 
3870  for (int i = 0; i < actions.size() && !handled; ++i)
3871  {
3872  QString action = actions[i];
3873  handled = true;
3874 
3875  if (action == ACTION_1 || action == "HELP")
3876  showIconHelp();
3877  else if (action == "MENU")
3878  {
3879  ShowMenu();
3880  }
3881  else if (action == "NEXTFAV")
3882  {
3883  if (GetFocusWidget() == m_groupList)
3885  else
3887  }
3888  else if (action == "TOGGLEFAV")
3889  {
3890  m_playList.clear();
3891  UpdateUILists();
3892  }
3893  else if (action == ACTION_TOGGLERECORD)
3894  {
3896  UpdateUILists();
3897  }
3898  else if (action == ACTION_PAGERIGHT)
3899  {
3900  QString nextGroup;
3901  m_recGroupsLock.lock();
3902  if (m_recGroupIdx >= 0 && !m_recGroups.empty())
3903  {
3904  if (++m_recGroupIdx >= m_recGroups.size())
3905  m_recGroupIdx = 0;
3906  nextGroup = m_recGroups[m_recGroupIdx];
3907  }
3908  m_recGroupsLock.unlock();
3909 
3910  if (!nextGroup.isEmpty())
3911  displayRecGroup(nextGroup);
3912  }
3913  else if (action == ACTION_PAGELEFT)
3914  {
3915  QString nextGroup;
3916  m_recGroupsLock.lock();
3917  if (m_recGroupIdx >= 0 && !m_recGroups.empty())
3918  {
3919  if (--m_recGroupIdx < 0)
3920  m_recGroupIdx = m_recGroups.size() - 1;
3921  nextGroup = m_recGroups[m_recGroupIdx];
3922  }
3923  m_recGroupsLock.unlock();
3924 
3925  if (!nextGroup.isEmpty())
3926  displayRecGroup(nextGroup);
3927  }
3928  else if (action == "NEXTVIEW")
3929  {
3931  if (++curpos >= m_groupList->GetCount())
3932  curpos = 0;
3933  m_groupList->SetItemCurrent(curpos);
3934  }
3935  else if (action == "PREVVIEW")
3936  {
3938  if (--curpos < 0)
3939  curpos = m_groupList->GetCount() - 1;
3940  m_groupList->SetItemCurrent(curpos);
3941  }
3942  else if (action == ACTION_LISTRECORDEDEPISODES)
3943  {
3946  else
3948  }
3949  else if (action == "CHANGERECGROUP")
3950  showGroupFilter();
3951  else if (action == "CHANGEGROUPVIEW")
3952  showViewChanger();
3953  else if (action == "EDIT")
3954  EditScheduled();
3955  else if (m_titleList.size() > 1)
3956  {
3957  if (action == "DELETE")
3959  else if (action == ACTION_PLAYBACK)
3961  else if (action == "DETAILS" || action == "INFO")
3962  ShowDetails();
3963  else if (action == "CUSTOMEDIT")
3964  EditCustom();
3965  else if (action == "GUIDE")
3966  ShowGuide();
3967  else if (action == "UPCOMING")
3968  ShowUpcoming();
3969  else if (action == ACTION_VIEWSCHEDULED)
3971  else if (action == ACTION_PREVRECORDED)
3972  ShowPrevious();
3973  else
3974  handled = false;
3975  }
3976  else
3977  handled = false;
3978  }
3979 
3980  if (!handled && MythScreenType::keyPressEvent(event))
3981  handled = true;
3982 
3983  return handled;
3984 }
3985 
3986 void PlaybackBox::customEvent(QEvent *event)
3987 {
3988  if (event->type() == DialogCompletionEvent::kEventType)
3989  {
3990  DialogCompletionEvent *dce = dynamic_cast<DialogCompletionEvent*>(event);
3991 
3992  if (!dce)
3993  return;
3994 
3995  QString resultid = dce->GetId();
3996 
3997  if (resultid == "transcode" && dce->GetResult() >= 0)
3998  changeProfileAndTranscode(dce->GetData().toInt());
3999  }
4000  else if (event->type() == MythEvent::MythEventMessage)
4001  {
4002  MythEvent *me = static_cast<MythEvent *>(event);
4003  const QString& message = me->Message();
4004 
4005  if (message.startsWith("RECORDING_LIST_CHANGE"))
4006  {
4007  QStringList tokens = message.simplified().split(" ");
4008  uint recordingID = 0;
4009  if (tokens.size() >= 3)
4010  recordingID = tokens[2].toUInt();
4011 
4012  if ((tokens.size() >= 2) && tokens[1] == "UPDATE")
4013  {
4014  ProgramInfo evinfo(me->ExtraDataList());
4015  if (evinfo.HasPathname() || evinfo.GetChanID())
4017  }
4018  else if (recordingID && (tokens[1] == "ADD"))
4019  {
4020  ProgramInfo evinfo(recordingID);
4021  if (evinfo.GetChanID())
4022  {
4024  HandleRecordingAddEvent(evinfo);
4025  }
4026  }
4027  else if (recordingID && (tokens[1] == "DELETE"))
4028  {
4029  HandleRecordingRemoveEvent(recordingID);
4030  }
4031  else
4032  {
4034  }
4035  }
4036  else if (message.startsWith("NETWORK_CONTROL"))
4037  {
4038  QStringList tokens = message.simplified().split(" ");
4039  if ((tokens[1] != "ANSWER") && (tokens[1] != "RESPONSE"))
4040  {
4041  m_ncLock.lock();
4042  m_networkControlCommands.push_back(message);
4043  m_ncLock.unlock();
4044 
4045  // This should be an impossible keypress we're simulating
4046  QKeyEvent *keyevent;
4047  Qt::KeyboardModifiers modifiers =
4048  Qt::ShiftModifier |
4049  Qt::ControlModifier |
4050  Qt::AltModifier |
4051  Qt::MetaModifier |
4052  Qt::KeypadModifier;
4053  keyevent = new QKeyEvent(QEvent::KeyPress,
4054  Qt::Key_LaunchMedia, modifiers);
4055  QCoreApplication::postEvent((QObject*)(GetMythMainWindow()),
4056  keyevent);
4057 
4058  keyevent = new QKeyEvent(QEvent::KeyRelease,
4059  Qt::Key_LaunchMedia, modifiers);
4060  QCoreApplication::postEvent((QObject*)(GetMythMainWindow()),
4061  keyevent);
4062  }
4063  }
4064  else if (message.startsWith("UPDATE_FILE_SIZE"))
4065  {
4066  QStringList tokens = message.simplified().split(" ");
4067  bool ok = false;
4068  uint recordingID = 0;
4069  uint64_t filesize = 0ULL;
4070  if (tokens.size() >= 3)
4071  {
4072  recordingID = tokens[1].toUInt();
4073  filesize = tokens[2].toLongLong(&ok);
4074  }
4075  if (recordingID && ok)
4076  {
4077 
4078  HandleUpdateProgramInfoFileSizeEvent(recordingID, filesize);
4079  }
4080  }
4081  else if (message == "UPDATE_UI_LIST")
4082  {
4083  if (m_playingSomething)
4084  m_needUpdate = true;
4085  else
4086  {
4087  UpdateUILists();
4089  }
4090  }
4091  else if (message == "UPDATE_USAGE_UI")
4092  {
4093  UpdateUsageUI();
4094  }
4095  else if (message == "RECONNECT_SUCCESS")
4096  {
4098  }
4099  else if (message == "LOCAL_PBB_DELETE_RECORDINGS")
4100  {
4101  QStringList list;
4102  for (uint i = 0; i+2 < (uint)me->ExtraDataList().size(); i+=3)
4103  {
4104  uint recordingID = me->ExtraDataList()[i+0].toUInt();
4105  ProgramInfo *pginfo =
4106  m_programInfoCache.GetRecordingInfo(recordingID);
4107 
4108  if (!pginfo)
4109  {
4110  LOG(VB_GENERAL, LOG_WARNING, LOC +
4111  QString("LOCAL_PBB_DELETE_RECORDINGS - "
4112  "No matching recording %1")
4113  .arg(recordingID));
4114  continue;
4115  }
4116 
4117  QString forceDeleteStr = me->ExtraDataList()[i+1];
4118  QString forgetHistoryStr = me->ExtraDataList()[i+2];
4119 
4120  list.push_back(QString::number(pginfo->GetRecordingID()));
4121  list.push_back(forceDeleteStr);
4122  list.push_back(forgetHistoryStr);
4124  "LOCAL_PBB_DELETE_RECORDINGS");
4125 
4126  // if the item is in the current recording list UI
4127  // then delete it.
4128  MythUIButtonListItem *uiItem =
4129  m_recordingList->GetItemByData(qVariantFromValue(pginfo));
4130  if (uiItem)
4131  m_recordingList->RemoveItem(uiItem);
4132  }
4133  if (!list.empty())
4134  m_helper.DeleteRecordings(list);
4135  }
4136  else if (message == "DELETE_SUCCESSES")
4137  {
4139  }
4140  else if (message == "DELETE_FAILURES")
4141  {
4142  if (me->ExtraDataList().size() < 3)
4143  return;
4144 
4145  for (uint i = 0; i+2 < (uint)me->ExtraDataList().size(); i += 3)
4146  {
4148  me->ExtraDataList()[i+0].toUInt());
4149  if (pginfo)
4150  {
4151  pginfo->SetAvailableStatus(asAvailable, "DELETE_FAILURES");
4153  }
4154  }
4155 
4156  bool forceDelete = me->ExtraDataList()[1].toUInt() != 0U;
4157  if (!forceDelete)
4158  {
4159  m_delList = me->ExtraDataList();
4160  if (!m_menuDialog)
4161  {
4163  return;
4164  }
4165  LOG(VB_GENERAL, LOG_WARNING, LOC +
4166  "Delete failures not handled due to "
4167  "pre-existing popup.");
4168  }
4169 
4170  // Since we deleted items from the UI after we set
4171  // asPendingDelete, we need to put them back now..
4173  }
4174  else if (message == "PREVIEW_SUCCESS")
4175  {
4177  }
4178  else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
4179  {
4180  for (uint i = 4; i < (uint) me->ExtraDataCount(); i++)
4181  {
4182  const QString& token = me->ExtraData(i);
4183  QSet<QString>::iterator it = m_preview_tokens.find(token);
4184  if (it != m_preview_tokens.end())
4185  m_preview_tokens.erase(it);
4186  }
4187  }
4188  else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
4189  {
4190  const uint kMaxUIWaitTime = 10000; // ms
4191  QStringList list = me->ExtraDataList();
4192  uint recordingID = list[0].toUInt();
4194  (CheckAvailabilityType) list[1].toInt();
4195  AvailableStatusType availableStatus =
4196  (AvailableStatusType) list[2].toInt();
4197  uint64_t fs = list[3].toULongLong();
4198  QTime tm;
4199  tm.setHMS(list[4].toUInt(), list[5].toUInt(),
4200  list[6].toUInt(), list[7].toUInt());
4201  QTime now = QTime::currentTime();
4202  int time_elapsed = tm.msecsTo(now);
4203  if (time_elapsed < 0)
4204  time_elapsed += 24 * 60 * 60 * 1000;
4205 
4206  AvailableStatusType old_avail = availableStatus;
4207  ProgramInfo *pginfo = FindProgramInUILists(recordingID);
4208  if (pginfo)
4209  {
4210  pginfo->SetFilesize(max(pginfo->GetFilesize(), fs));
4211  old_avail = pginfo->GetAvailableStatus();
4212  pginfo->SetAvailableStatus(availableStatus, "AVAILABILITY");
4213  }
4214 
4215  if ((uint)time_elapsed >= kMaxUIWaitTime)
4216  m_playListPlay.clear();
4217 
4218  bool playnext = ((kCheckForPlaylistAction == cat) &&
4219  !m_playListPlay.empty());
4220 
4221 
4222  if (((kCheckForPlayAction == cat) ||
4223  (kCheckForPlaylistAction == cat)) &&
4224  ((uint)time_elapsed < kMaxUIWaitTime))
4225  {
4226  if (asAvailable != availableStatus)
4227  {
4228  if (kCheckForPlayAction == cat && pginfo)
4229  ShowAvailabilityPopup(*pginfo);
4230  }
4231  else if (pginfo)
4232  {
4233  playnext = false;
4234  const bool ignoreBookmark = false;
4235  const bool ignoreProgStart = false;
4236  const bool ignoreLastPlayPos = true;
4237  const bool underNetworkControl = false;
4238  Play(*pginfo, kCheckForPlaylistAction == cat,
4239  ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
4240  underNetworkControl);
4241  }
4242  }
4243 
4244  if (playnext)
4245  {
4246  // failed to play this item, instead
4247  // play the next item on the list..
4248  QCoreApplication::postEvent(
4249  this, new MythEvent("PLAY_PLAYLIST"));
4250  }
4251 
4252  if (old_avail != availableStatus)
4253  UpdateUIListItem(pginfo, true);
4254  }
4255  else if ((message == "PLAY_PLAYLIST") && !m_playListPlay.empty())
4256  {
4257  uint recordingID = m_playListPlay.front();
4258  m_playListPlay.pop_front();
4259 
4260  if (!m_playListPlay.empty())
4261  {
4262  const ProgramInfo *pginfo =
4264  if (pginfo)
4266  }
4267 
4268  ProgramInfo *pginfo = FindProgramInUILists(recordingID);
4269  const bool ignoreBookmark = false;
4270  const bool ignoreProgStart = true;
4271  const bool ignoreLastPlayPos = true;
4272  const bool underNetworkControl = false;
4273  if (pginfo)
4274  Play(*pginfo, true, ignoreBookmark, ignoreProgStart,
4275  ignoreLastPlayPos, underNetworkControl);
4276  }
4277  else if ((message == "SET_PLAYBACK_URL") && (me->ExtraDataCount() == 2))
4278  {
4279  uint recordingID = me->ExtraData(0).toUInt();
4280  ProgramInfo *info = m_programInfoCache.GetRecordingInfo(recordingID);
4281  if (info)
4282  info->SetPathname(me->ExtraData(1));
4283  }
4284  else if ((message == "FOUND_ARTWORK") && (me->ExtraDataCount() >= 5))
4285  {
4286  VideoArtworkType type = (VideoArtworkType) me->ExtraData(2).toInt();
4287  uint recordingID = me->ExtraData(3).toUInt();
4288  const QString& group = me->ExtraData(4);
4289  const QString& fn = me->ExtraData(5);
4290 
4291  if (recordingID)
4292  {
4293  ProgramInfo *pginfo = m_programInfoCache.GetRecordingInfo(recordingID);
4294  if (pginfo &&
4295  m_recordingList->GetItemByData(qVariantFromValue(pginfo)) ==
4297  m_artImage[(uint)type]->GetFilename() != fn)
4298  {
4299  m_artImage[(uint)type]->SetFilename(fn);
4300  m_artTimer[(uint)type]->start(s_artDelay[(uint)type]);
4301  }
4302  }
4303  else if (!group.isEmpty() &&
4304  (m_currentGroup == group) &&
4305  m_artImage[type] &&
4307  m_artImage[(uint)type]->GetFilename() != fn)
4308  {
4309  m_artImage[(uint)type]->SetFilename(fn);
4310  m_artTimer[(uint)type]->start(s_artDelay[(uint)type]);
4311  }
4312  }
4313  else if (message == "EXIT_TO_MENU" ||
4314  message == "CANCEL_PLAYLIST")
4315  {
4316  m_playListPlay.clear();
4317  }
4318  }
4319  else
4321 }
4322 
4324 {
4325  if (!m_programInfoCache.Remove(recordingID))
4326  {
4327  LOG(VB_GENERAL, LOG_WARNING, LOC +
4328  QString("Failed to remove %1, reloading list")
4329  .arg(recordingID));
4331  return;
4332  }
4333 
4335  QString groupname;
4336  if (sel_item)
4337  groupname = sel_item->GetData().toString();
4338 
4339  ProgramMap::iterator git = m_progLists.begin();
4340  while (git != m_progLists.end())
4341  {
4342  ProgramList::iterator pit = (*git).begin();
4343  while (pit != (*git).end())
4344  {
4345  if ((*pit)->GetRecordingID() == recordingID)
4346  {
4347  if (!git.key().isEmpty() && git.key() == groupname)
4348  {
4349  MythUIButtonListItem *item_by_data =
4351  qVariantFromValue(*pit));
4352  MythUIButtonListItem *item_cur =
4354 
4355  if (item_cur && (item_by_data == item_cur))
4356  {
4357  MythUIButtonListItem *item_next =
4358  m_recordingList->GetItemNext(item_cur);
4359  if (item_next)
4360  m_recordingList->SetItemCurrent(item_next);
4361  }
4362 
4363  m_recordingList->RemoveItem(item_by_data);
4364  }
4365  pit = (*git).erase(pit);
4366  }
4367  else
4368  {
4369  ++pit;
4370  }
4371  }
4372 
4373  if ((*git).empty())
4374  {
4375  if (!groupname.isEmpty() && (git.key() == groupname))
4376  {
4377  MythUIButtonListItem *next_item =
4378  m_groupList->GetItemNext(sel_item);
4379  if (next_item)
4380  m_groupList->SetItemCurrent(next_item);
4381 
4382  m_groupList->RemoveItem(sel_item);
4383 
4384  sel_item = next_item;
4385  groupname = "";
4386  if (sel_item)
4387  groupname = sel_item->GetData().toString();
4388  }
4389  git = m_progLists.erase(git);
4390  }
4391  else
4392  {
4393  ++git;
4394  }
4395  }
4396 
4398 }
4399 
4401 {
4402  m_programInfoCache.Add(evinfo);
4404 }
4405 
4407 {
4408  QString old_recgroup = m_programInfoCache.GetRecGroup(
4409  evinfo.GetRecordingID());
4410 
4411  if (!m_programInfoCache.Update(evinfo))
4412  return;
4413 
4414  // If the recording group has changed, reload lists from the recently
4415  // updated cache; if not, only update UI for the updated item
4416  if (evinfo.GetRecordingGroup() == old_recgroup)
4417  {
4418  ProgramInfo *dst = FindProgramInUILists(evinfo);
4419  if (dst)
4420  UpdateUIListItem(dst, true);
4421  return;
4422  }
4423 
4425 }
4426 
4428  uint64_t filesize)
4429 {
4430  m_programInfoCache.UpdateFileSize(recordingID, filesize);
4431 
4432  ProgramInfo *dst = FindProgramInUILists(recordingID);
4433  if (dst)
4434  UpdateUIListItem(dst, false);
4435 }
4436 
4438 {
4440  QCoreApplication::postEvent(this, new MythEvent("UPDATE_UI_LIST"));
4441 }
4442 
4444 {
4445  HelpPopup *helpPopup = new HelpPopup(m_popupStack);
4446 
4447  if (helpPopup->Create())
4448  m_popupStack->AddScreen(helpPopup);
4449  else
4450  delete helpPopup;
4451 }
4452 
4454 {
4455  ChangeView *viewPopup = new ChangeView(m_popupStack, this, m_viewMask);
4456 
4457  if (viewPopup->Create())
4458  {
4459  connect(viewPopup, SIGNAL(save()), SLOT(saveViewChanges()));
4460  m_popupStack->AddScreen(viewPopup);
4461  }
4462  else
4463  delete viewPopup;
4464 }
4465 
4467 {
4468  if (m_viewMask == VIEW_NONE)
4470  gCoreContext->SaveSetting("DisplayGroupDefaultViewMask", (int)m_viewMask);
4471  gCoreContext->SaveBoolSetting("PlaybackWatchList",
4472  (m_viewMask & VIEW_WATCHLIST) != 0);
4473 }
4474 
4476 {
4477  QStringList groupNames;
4478  QStringList displayNames;
4479  QStringList groups;
4480  QStringList displayGroups;
4481 
4482  MSqlQuery query(MSqlQuery::InitCon());
4483 
4484  m_recGroupType.clear();
4485 
4486  uint totalItems = 0;
4487 
4488  // Add the group entries
4489  displayNames.append(QString("------- %1 -------").arg(tr("Groups")));
4490  groupNames.append("");
4491 
4492  // Find each recording group, and the number of recordings in each
4493  query.prepare("SELECT recgroup, COUNT(title) FROM recorded "
4494  "WHERE deletepending = 0 AND watched <= :WATCHED "
4495  "GROUP BY recgroup");
4496  query.bindValue(":WATCHED", (m_viewMask & VIEW_WATCHED));
4497  if (query.exec())
4498  {
4499  while (query.next())
4500  {
4501  QString dispGroup = query.value(0).toString();
4502  uint items = query.value(1).toInt();
4503 
4504  if ((dispGroup != "LiveTV" || (m_viewMask & VIEW_LIVETVGRP)) &&
4505  (dispGroup != "Deleted"))
4506  totalItems += items;
4507 
4508  groupNames.append(dispGroup);
4509 
4510  dispGroup = (dispGroup == "Default") ? tr("Default") : dispGroup;
4511  dispGroup = (dispGroup == "Deleted") ? tr("Deleted") : dispGroup;
4512  dispGroup = (dispGroup == "LiveTV") ? tr("Live TV") : dispGroup;
4513 
4514  displayNames.append(tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup));
4515 
4516  m_recGroupType[query.value(0).toString()] = "recgroup";
4517  }
4518  }
4519 
4520  // Create and add the "All Programs" entry
4521  displayNames.push_front(tr("%1 [%n item(s)]", nullptr, totalItems)
4522  .arg(ProgramInfo::i18n("All Programs")));
4523  groupNames.push_front("All Programs");
4524  m_recGroupType["All Programs"] = "recgroup";
4525 
4526  // Find each category, and the number of recordings in each
4527  query.prepare("SELECT DISTINCT category, COUNT(title) FROM recorded "
4528  "WHERE deletepending = 0 AND watched <= :WATCHED "
4529  "GROUP BY category");
4530  query.bindValue(":WATCHED", (m_viewMask & VIEW_WATCHED));
4531  if (query.exec())
4532  {
4533  int unknownCount = 0;
4534  while (query.next())
4535  {
4536  uint items = query.value(1).toInt();
4537  QString dispGroup = query.value(0).toString();
4538  if (dispGroup.isEmpty())
4539  {
4540  unknownCount += items;
4541  dispGroup = tr("Unknown");
4542  }
4543  else if (dispGroup == tr("Unknown"))
4544  unknownCount += items;
4545 
4546  if ((!m_recGroupType.contains(dispGroup)) &&
4547  (dispGroup != tr("Unknown")))
4548  {
4549  displayGroups += tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup);
4550  groups += dispGroup;
4551 
4552  m_recGroupType[dispGroup] = "category";
4553  }
4554  }
4555 
4556  if (unknownCount > 0)
4557  {
4558  QString dispGroup = tr("Unknown");
4559  uint items = unknownCount;
4560  displayGroups += tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup);
4561  groups += dispGroup;
4562 
4563  m_recGroupType[dispGroup] = "category";
4564  }
4565  }
4566 
4567  // Add the category entries
4568  displayNames.append(QString("------- %1 -------").arg(tr("Categories")));
4569  groupNames.append("");
4570  groups.sort();
4571  displayGroups.sort();
4572  QStringList::iterator it;
4573  for (it = displayGroups.begin(); it != displayGroups.end(); ++it)
4574  displayNames.append(*it);
4575  for (it = groups.begin(); it != groups.end(); ++it)
4576  groupNames.append(*it);
4577 
4578  QString label = tr("Change Filter");
4579 
4580  GroupSelector *recGroupPopup = new GroupSelector(m_popupStack, label,
4581  displayNames, groupNames,
4582  m_recGroup);
4583 
4584  if (recGroupPopup->Create())
4585  {
4586  m_usingGroupSelector = true;
4587  m_groupSelected = false;
4588  connect(recGroupPopup, SIGNAL(result(QString)),
4589  SLOT(displayRecGroup(QString)));
4590  connect(recGroupPopup, SIGNAL(Exiting()),
4591  SLOT(groupSelectorClosed()));
4592  m_popupStack->AddScreen(recGroupPopup);
4593  }
4594  else
4595  delete recGroupPopup;
4596 }
4597 
4599 {
4600  if (m_groupSelected)
4601  return;
4602 
4603  if (m_firstGroup)
4604  Close();
4605 
4606  m_usingGroupSelector = false;
4607 }
4608 
4609 void PlaybackBox::setGroupFilter(const QString &recGroup)
4610 {
4611  QString newRecGroup = recGroup;
4612 
4613  if (newRecGroup.isEmpty())
4614  return;
4615 
4616  m_firstGroup = false;
4617  m_usingGroupSelector = false;
4618 
4619  if (newRecGroup == ProgramInfo::i18n("Default"))
4620  newRecGroup = "Default";
4621  else if (newRecGroup == ProgramInfo::i18n("All Programs"))
4622  newRecGroup = "All Programs";
4623  else if (newRecGroup == ProgramInfo::i18n("LiveTV"))
4624  newRecGroup = "LiveTV";
4625  else if (newRecGroup == ProgramInfo::i18n("Deleted"))
4626  newRecGroup = "Deleted";
4627 
4628  m_recGroup = newRecGroup;
4629 
4631 
4632  // Since the group filter is changing, the current position in the lists
4633  // is meaningless -- so reset the lists so the position won't be saved.
4635  m_groupList->Reset();
4636 
4637  UpdateUILists();
4638 
4639  if (gCoreContext->GetBoolSetting("RememberRecGroup",true))
4640  gCoreContext->SaveSetting("DisplayRecGroup", m_recGroup);
4641 
4642  if (m_recGroupType[m_recGroup] == "recgroup")
4643  gCoreContext->SaveSetting("DisplayRecGroupIsCategory", 0);
4644  else
4645  gCoreContext->SaveSetting("DisplayRecGroupIsCategory", 1);
4646 }
4647 
4648 QString PlaybackBox::getRecGroupPassword(const QString &group)
4649 {
4650  return m_recGroupPwCache.value(group);
4651 }
4652 
4654 {
4655  m_recGroupPwCache.clear();
4656 
4657  MSqlQuery query(MSqlQuery::InitCon());
4658  query.prepare("SELECT recgroup, password FROM recgroups "
4659  "WHERE password IS NOT NULL AND password <> '';");
4660 
4661  if (query.exec())
4662  {
4663  while (query.next())
4664  {
4665  QString recgroup = query.value(0).toString();
4666 
4667  if (recgroup == ProgramInfo::i18n("Default"))
4668  recgroup = "Default";
4669  else if (recgroup == ProgramInfo::i18n("All Programs"))
4670  recgroup = "All Programs";
4671  else if (recgroup == ProgramInfo::i18n("LiveTV"))
4672  recgroup = "LiveTV";
4673  else if (recgroup == ProgramInfo::i18n("Deleted"))
4674  recgroup = "Deleted";
4675 
4676  m_recGroupPwCache.insert(recgroup, query.value(1).toString());
4677  }
4678  }
4679 }
4680 
4682 void PlaybackBox::ShowRecGroupChanger(bool use_playlist)
4683 {
4684  m_op_on_playlist = use_playlist;
4685 
4686  ProgramInfo *pginfo = nullptr;
4687  if (use_playlist)
4688  {
4689  if (!m_playList.empty())
4690  pginfo = FindProgramInUILists(m_playList[0]);
4691  }
4692  else
4693  pginfo = GetCurrentProgram();
4694 
4695  if (!pginfo)
4696  return;
4697 
4698  MSqlQuery query(MSqlQuery::InitCon());
4699  query.prepare(
4700  "SELECT g.recgroup, COUNT(r.title) FROM recgroups g "
4701  "LEFT JOIN recorded r ON g.recgroupid=r.recgroupid AND r.deletepending = 0 "
4702  "WHERE g.recgroupid != 2 AND g.recgroupid != 3 "
4703  "GROUP BY g.recgroupid ORDER BY g.recgroup");
4704 
4705  QStringList displayNames(tr("Add New"));
4706  QStringList groupNames("addnewgroup");
4707 
4708  if (!query.exec())
4709  return;
4710 
4711  while (query.next())
4712  {
4713  QString dispGroup = query.value(0).toString();
4714  groupNames.push_back(dispGroup);
4715 
4716  if (dispGroup == "Default")
4717  dispGroup = tr("Default");
4718  else if (dispGroup == "LiveTV")
4719  dispGroup = tr("Live TV");
4720  else if (dispGroup == "Deleted")
4721  dispGroup = tr("Deleted");
4722 
4723  displayNames.push_back(tr("%1 [%n item(s)]", "", query.value(1).toInt())
4724  .arg(dispGroup));
4725  }
4726 
4727  QString label = tr("Select Recording Group") +
4728  CreateProgramInfoString(*pginfo);
4729 
4730  GroupSelector *rgChanger = new GroupSelector(
4731  m_popupStack, label, displayNames, groupNames,
4732  pginfo->GetRecordingGroup());
4733 
4734  if (rgChanger->Create())
4735  {
4736  connect(rgChanger, SIGNAL(result(QString)), SLOT(setRecGroup(QString)));
4737  m_popupStack->AddScreen(rgChanger);
4738  }
4739  else
4740  delete rgChanger;
4741 }
4742 
4744 void PlaybackBox::ShowPlayGroupChanger(bool use_playlist)
4745 {
4746  m_op_on_playlist = use_playlist;
4747 
4748  ProgramInfo *pginfo = nullptr;
4749  if (use_playlist)
4750  {
4751  if (!m_playList.empty())
4752  pginfo = FindProgramInUILists(m_playList[0]);
4753  }
4754  else
4755  pginfo = GetCurrentProgram();
4756 
4757  if (!pginfo)
4758  return;
4759 
4760  QStringList groupNames(tr("Default"));
4761  QStringList displayNames("Default");
4762 
4763  QStringList list = PlayGroup::GetNames();
4764  QStringList::const_iterator it = list.begin();
4765  for (; it != list.end(); ++it)
4766  {
4767  displayNames.push_back(*it);
4768  groupNames.push_back(*it);
4769  }
4770 
4771  QString label = tr("Select Playback Group") +
4772  CreateProgramInfoString(*pginfo);
4773 
4774  GroupSelector *pgChanger = new GroupSelector(
4775  m_popupStack, label,displayNames, groupNames,
4776  pginfo->GetPlaybackGroup());
4777 
4778  if (pgChanger->Create())
4779  {
4780  connect(pgChanger, SIGNAL(result(QString)),
4781  SLOT(setPlayGroup(QString)));
4782  m_popupStack->AddScreen(pgChanger);
4783  }
4784  else
4785  delete pgChanger;
4786 }
4787 
4789 {
4790  QList<uint>::Iterator it;
4791 
4792  for (it = m_playList.begin(); it != m_playList.end(); ++it)
4793  {
4794  ProgramInfo *tmpItem;
4795  if ((tmpItem = FindProgramInUILists(*it)))
4796  {
4797  if (!tmpItem->IsAutoExpirable() && turnOn)
4798  tmpItem->SaveAutoExpire(kNormalAutoExpire, true);
4799  else if (tmpItem->IsAutoExpirable() && !turnOn)
4800  tmpItem->SaveAutoExpire(kDisableAutoExpire, true);
4801  }
4802  }
4803 }
4804 
4806 {
4807  QList<uint>::Iterator it;
4808 
4809  for (it = m_playList.begin(); it != m_playList.end(); ++it)
4810  {
4811  ProgramInfo *tmpItem;
4812  if ((tmpItem = FindProgramInUILists(*it)))
4813  {
4814  tmpItem->SaveWatched(turnOn);
4815  }
4816  }
4817 
4818  doClearPlaylist();
4819  UpdateUILists();
4820 }
4821 
4823 {
4824  ProgramInfo *pgInfo = GetCurrentProgram();
4825 
4827 
4828  RecMetadataEdit *editMetadata = new RecMetadataEdit(mainStack, pgInfo);
4829 
4830  if (editMetadata->Create())
4831  {
4832  connect(editMetadata, SIGNAL(result(const QString &, const QString &,
4833  const QString &, const QString &, uint, uint)), SLOT(
4834  saveRecMetadata(const QString &, const QString &,
4835  const QString &, const QString &, uint, uint)));
4836  mainStack->AddScreen(editMetadata);
4837  }
4838  else
4839  delete editMetadata;
4840 }
4841 
4842 void PlaybackBox::saveRecMetadata(const QString &newTitle,
4843  const QString &newSubtitle,
4844  const QString &newDescription,
4845  const QString &newInetref,
4846  uint newSeason,
4847  uint newEpisode)
4848 {
4850 
4851  if (!item)
4852  return;
4853 
4854  ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
4855 
4856  if (!pginfo)
4857  return;
4858 
4859  QString groupname = m_groupList->GetItemCurrent()->GetData().toString();
4860 
4861  if (groupname == pginfo->GetTitle().toLower() &&
4862  newTitle != pginfo->GetTitle())
4863  {
4864  m_recordingList->RemoveItem(item);
4865  }
4866  else
4867  {
4868  QString tempSubTitle = newTitle;
4869  if (!newSubtitle.trimmed().isEmpty())
4870  tempSubTitle = QString("%1 - \"%2\"")
4871  .arg(tempSubTitle).arg(newSubtitle);
4872 
4873  QString seasone;
4874  QString seasonx;
4875  QString season;
4876  QString episode;
4877  if (newSeason > 0 || newEpisode > 0)
4878  {
4879  season = format_season_and_episode(newSeason, 1);
4880  episode = format_season_and_episode(newEpisode, 1);
4881  seasone = QString("s%1e%2")
4882  .arg(format_season_and_episode(newSeason, 2))
4883  .arg(format_season_and_episode(newEpisode, 2));
4884  seasonx = QString("%1x%2")
4885  .arg(format_season_and_episode(newSeason, 1))
4886  .arg(format_season_and_episode(newEpisode, 2));
4887  }
4888 
4889  item->SetText(tempSubTitle, "titlesubtitle");
4890  item->SetText(newTitle, "title");
4891  item->SetText(newSubtitle, "subtitle");
4892  item->SetText(newInetref, "inetref");
4893  item->SetText(seasonx, "00x00");
4894  item->SetText(seasone, "s00e00");
4895  item->SetText(season, "season");
4896  item->SetText(episode, "episode");
4897  if (newDescription != nullptr)
4898  item->SetText(newDescription, "description");
4899  }
4900 
4901  pginfo->SaveInetRef(newInetref);
4902  pginfo->SaveSeasonEpisode(newSeason, newEpisode);
4903 
4904  RecordingInfo ri(*pginfo);
4905  ri.ApplyRecordRecTitleChange(newTitle, newSubtitle, newDescription);
4906  *pginfo = ri;
4907 }
4908 
4909 void PlaybackBox::setRecGroup(QString newRecGroup)
4910 {
4911  newRecGroup = newRecGroup.simplified();
4912 
4913  if (newRecGroup.isEmpty())
4914  return;
4915 
4916  if (newRecGroup == "addnewgroup")
4917  {
4918  MythScreenStack *popupStack =
4919  GetMythMainWindow()->GetStack("popup stack");
4920 
4921  MythTextInputDialog *newgroup = new MythTextInputDialog(
4922  popupStack, tr("New Recording Group"));
4923 
4924  connect(newgroup, SIGNAL(haveResult(QString)),
4925  SLOT(setRecGroup(QString)));
4926 
4927  if (newgroup->Create())
4928  popupStack->AddScreen(newgroup, false);
4929  else
4930  delete newgroup;
4931  return;
4932  }
4933 
4934  RecordingRule record;
4935  record.LoadTemplate("Default");
4936  AutoExpireType defaultAutoExpire =
4938 
4939  if (m_op_on_playlist)
4940  {
4941  QList<uint>::const_iterator it;
4942  for (it = m_playList.begin(); it != m_playList.end(); ++it )
4943  {
4945  if (!p)
4946  continue;
4947 
4948  if ((p->GetRecordingGroup() == "LiveTV") &&
4949  (newRecGroup != "LiveTV"))
4950  {
4951  p->SaveAutoExpire(defaultAutoExpire);
4952  }
4953  else if ((p->GetRecordingGroup() != "LiveTV") &&
4954  (newRecGroup == "LiveTV"))
4955  {
4957  }
4958 
4959  RecordingInfo ri(*p);
4960  ri.ApplyRecordRecGroupChange(newRecGroup);
4961  *p = ri;
4962  }
4963  doClearPlaylist();
4964  UpdateUILists();
4965  return;
4966  }
4967 
4969  if (!p)
4970  return;
4971 
4972  if ((p->GetRecordingGroup() == "LiveTV") && (newRecGroup != "LiveTV"))
4973  p->SaveAutoExpire(defaultAutoExpire);
4974  else if ((p->GetRecordingGroup() != "LiveTV") && (newRecGroup == "LiveTV"))
4976 
4977  RecordingInfo ri(*p);
4978  ri.ApplyRecordRecGroupChange(newRecGroup);
4979  *p = ri;
4980  UpdateUILists();
4981 }
4982 
4983 void PlaybackBox::setPlayGroup(QString newPlayGroup)
4984 {
4985  ProgramInfo *tmpItem = GetCurrentProgram();
4986 
4987  if (newPlayGroup.isEmpty() || !tmpItem)
4988  return;
4989 
4990  if (newPlayGroup == tr("Default"))
4991  newPlayGroup = "Default";
4992 
4993  if (m_op_on_playlist)
4994  {
4995  QList<uint>::Iterator it;
4996 
4997  for (it = m_playList.begin(); it != m_playList.end(); ++it )
4998  {
4999  tmpItem = FindProgramInUILists(*it);
5000  if (tmpItem)
5001  {
5002  RecordingInfo ri(*tmpItem);
5003  ri.ApplyRecordPlayGroupChange(newPlayGroup);
5004  *tmpItem = ri;
5005  }
5006  }
5007  doClearPlaylist();
5008  }
5009  else if (tmpItem)
5010  {
5011  RecordingInfo ri(*tmpItem);
5012  ri.ApplyRecordPlayGroupChange(newPlayGroup);
5013  *tmpItem = ri;
5014  }
5015 }
5016 
5018 {
5020 
5021  if (!item)
5022  return;
5023 
5024  QString currentPassword = getRecGroupPassword(m_recGroup);
5025 
5026  PasswordChange *pwChanger = new PasswordChange(m_popupStack,
5027  currentPassword);
5028 
5029  if (pwChanger->Create())
5030  {
5031  connect(pwChanger, SIGNAL(result(const QString &)),
5032  SLOT(SetRecGroupPassword(const QString &)));
5033  m_popupStack->AddScreen(pwChanger);
5034  }
5035  else
5036  delete pwChanger;
5037 }
5038 
5039 void PlaybackBox::SetRecGroupPassword(const QString &newPassword)
5040 {
5041  MSqlQuery query(MSqlQuery::InitCon());
5042 
5043  query.prepare("UPDATE recgroups SET password = :PASSWD WHERE "
5044  "recgroup = :RECGROUP");
5045  query.bindValue(":RECGROUP", m_recGroup);
5046  query.bindValue(":PASSWD", newPassword);
5047 
5048  if (!query.exec())
5049  MythDB::DBError("PlaybackBox::SetRecGroupPassword",
5050  query);
5051 
5052  if (newPassword.isEmpty())
5053  m_recGroupPwCache.remove(m_recGroup);
5054  else
5055  m_recGroupPwCache.insert(m_recGroup, newPassword);
5056 }
5057 
5059 
5061 {
5062  if (!LoadWindowFromXML("recordings-ui.xml", "groupselector", this))
5063  return false;
5064 
5065  MythUIText *labelText = dynamic_cast<MythUIText*> (GetChild("label"));
5066  MythUIButtonList *groupList = dynamic_cast<MythUIButtonList*>
5067  (GetChild("groups"));
5068 
5069  if (!groupList)
5070  {
5071  LOG(VB_GENERAL, LOG_ERR, LOC +
5072  "Theme is missing 'groups' button list.");
5073  return false;
5074  }
5075 
5076  if (labelText)
5077  labelText->SetText(m_label);
5078 
5079  for (int i = 0; i < m_List.size(); ++i)
5080  {
5081  new MythUIButtonListItem(groupList, m_List.at(i),
5082  qVariantFromValue(m_Data.at(i)));
5083  }
5084 
5085  // Set the current position in the list
5086  groupList->SetValueByData(qVariantFromValue(m_selected));
5087 
5088  BuildFocusList();
5089 
5090  connect(groupList, SIGNAL(itemClicked(MythUIButtonListItem *)),
5091  SLOT(AcceptItem(MythUIButtonListItem *)));
5092 
5093  return true;
5094 }
5095 
5097 {
5098  if (!item)
5099  return;
5100 
5101  // ignore the dividers
5102  if (item->GetData().toString().isEmpty())
5103  return;
5104 
5105  QString group = item->GetData().toString();
5106  emit result(group);
5107  Close();
5108 }
5109 
5111 
5113 {
5114  if (!LoadWindowFromXML("recordings-ui.xml", "changeview", this))
5115  return false;
5116 
5117  MythUICheckBox *checkBox;
5118 
5119  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("titles"));
5120  if (checkBox)
5121  {
5124  connect(checkBox, SIGNAL(toggled(bool)),
5125  m_parentScreen, SLOT(toggleTitleView(bool)));
5126  }
5127 
5128  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("categories"));
5129  if (checkBox)
5130  {
5133  connect(checkBox, SIGNAL(toggled(bool)),
5134  m_parentScreen, SLOT(toggleCategoryView(bool)));
5135  }
5136 
5137  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("recgroups"));
5138  if (checkBox)
5139  {
5142  connect(checkBox, SIGNAL(toggled(bool)),
5143  m_parentScreen, SLOT(toggleRecGroupView(bool)));
5144  }
5145 
5146  // TODO Do we need two separate settings to determine whether the watchlist
5147  // is shown? The filter setting be enough?
5148  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("watchlist"));
5149  if (checkBox)
5150  {
5153  connect(checkBox, SIGNAL(toggled(bool)),
5154  m_parentScreen, SLOT(toggleWatchListView(bool)));
5155  }
5156  //
5157 
5158  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("searches"));
5159  if (checkBox)
5160  {
5163  connect(checkBox, SIGNAL(toggled(bool)),
5164  m_parentScreen, SLOT(toggleSearchView(bool)));
5165  }
5166 
5167  // TODO Do we need two separate settings to determine whether livetv
5168  // recordings are shown? Same issue as the watchlist above
5169  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("livetv"));
5170  if (checkBox)
5171  {
5174  connect(checkBox, SIGNAL(toggled(bool)),
5175  m_parentScreen, SLOT(toggleLiveTVView(bool)));
5176  }
5177  //
5178 
5179  checkBox = dynamic_cast<MythUICheckBox*>(GetChild("watched"));
5180  if (checkBox)
5181  {
5184  connect(checkBox, SIGNAL(toggled(bool)),
5185  m_parentScreen, SLOT(toggleWatchedView(bool)));
5186  }
5187 
5188  MythUIButton *savebutton = dynamic_cast<MythUIButton*>(GetChild("save"));
5189  connect(savebutton, SIGNAL(Clicked()), SLOT(SaveChanges()));
5190 
5191  BuildFocusList();
5192 
5193  return true;
5194 }
5195 
5197 {
5198  emit save();
5199  Close();
5200 }
5201 
5203 
5205 {
5206  if (!LoadWindowFromXML("recordings-ui.xml", "passwordchanger", this))
5207  return false;
5208 
5209  m_oldPasswordEdit = dynamic_cast<MythUITextEdit *>(GetChild("oldpassword"));
5210  m_newPasswordEdit = dynamic_cast<MythUITextEdit *>(GetChild("newpassword"));
5211  m_okButton = dynamic_cast<MythUIButton *>(GetChild("ok"));
5212 
5214  {
5215  LOG(VB_GENERAL, LOG_ERR, LOC +
5216  "Window 'passwordchanger' is missing required elements.");
5217  return false;
5218  }
5219 
5222 // if (m_oldPassword.isEmpty())
5223 // m_oldPasswordEdit->SetDisabled(true);
5226 
5227  BuildFocusList();
5228 
5229  connect(m_oldPasswordEdit, SIGNAL(valueChanged()),
5230  SLOT(OldPasswordChanged()));
5231  connect(m_okButton, SIGNAL(Clicked()), SLOT(SendResult()));
5232 
5233  return true;
5234 }
5235 
5237 {
5238  QString newText = m_oldPasswordEdit->GetText();
5239  bool ok = (newText == m_oldPassword);
5240  m_okButton->SetEnabled(ok);
5241 }
5242 
5243 
5245 {
5246  emit result(m_newPasswordEdit->GetText());
5247  Close();
5248 }
5249 
5251 
5253  : MythScreenType(lparent, "recmetadataedit"),
5254  m_progInfo(pginfo)
5255 {
5256  m_popupStack = GetMythMainWindow()->GetStack("popup stack");
5257  m_metadataFactory = new MetadataFactory(this);
5258 }
5259 
5261 {
5262  if (!LoadWindowFromXML("recordings-ui.xml", "editmetadata", this))
5263  return false;
5264 
5265  m_titleEdit = dynamic_cast<MythUITextEdit*>(GetChild("title"));
5266  m_subtitleEdit = dynamic_cast<MythUITextEdit*>(GetChild("subtitle"));
5267  m_descriptionEdit = dynamic_cast<MythUITextEdit*>(GetChild("description"));
5268  m_inetrefEdit = dynamic_cast<MythUITextEdit*>(GetChild("inetref"));
5269  MythUIButton *inetrefClear = dynamic_cast<MythUIButton*>
5270  (GetChild("inetref_clear"));
5271  m_seasonSpin = dynamic_cast<MythUISpinBox*>(GetChild("season"));
5272  m_episodeSpin = dynamic_cast<MythUISpinBox*>(GetChild("episode"));
5273  MythUIButton *okButton = dynamic_cast<MythUIButton*>(GetChild("ok"));
5274  m_queryButton = dynamic_cast<MythUIButton*>(GetChild("query_button"));
5275 
5277  !m_episodeSpin || !okButton)
5278  {
5279  LOG(VB_GENERAL, LOG_ERR, LOC +
5280  "Window 'editmetadata' is missing required elements.");
5281  return false;
5282  }
5283 
5285  m_titleEdit->SetMaxLength(128);
5288  if (m_descriptionEdit)
5289  {
5292  }
5295  m_seasonSpin->SetRange(0,9999,1,5);
5297  m_episodeSpin->SetRange(0,9999,1,10);
5299 
5300  connect(inetrefClear, SIGNAL(Clicked()), SLOT(ClearInetref()));
5301  connect(okButton, SIGNAL(Clicked()), SLOT(SaveChanges()));
5302  if (m_queryButton)
5303  {
5304  connect(m_queryButton, SIGNAL(Clicked()), SLOT(PerformQuery()));
5305  }
5306 
5307  BuildFocusList();
5308 
5309  return true;
5310 }
5311 
5313 {
5314  m_inetrefEdit->SetText("");
5315 }
5316 
5318 {
5319  QString newRecTitle = m_titleEdit->GetText();
5320  QString newRecSubtitle = m_subtitleEdit->GetText();
5321  QString newRecDescription = nullptr;
5322  QString newRecInetref = nullptr;
5323  uint newRecSeason = 0, newRecEpisode = 0;
5324  if (m_descriptionEdit)
5325  newRecDescription = m_descriptionEdit->GetText();
5326  newRecInetref = m_inetrefEdit->GetText();
5327  newRecSeason = m_seasonSpin->GetIntValue();
5328  newRecEpisode = m_episodeSpin->GetIntValue();
5329 
5330  if (newRecTitle.isEmpty())
5331  return;
5332 
5333  emit result(newRecTitle, newRecSubtitle, newRecDescription,
5334  newRecInetref, newRecSeason, newRecEpisode);
5335  Close();
5336 }
5337 
5339 {
5340  if (m_busyPopup)
5341  return;
5342 
5343  m_busyPopup = new MythUIBusyDialog(tr("Trying to manually find this "
5344  "recording online..."),
5345  m_popupStack,
5346  "metaoptsdialog");
5347 
5348  if (m_busyPopup->Create())
5350 
5351  MetadataLookup *lookup = new MetadataLookup();
5352  lookup->SetStep(kLookupSearch);
5353  lookup->SetType(kMetadataRecording);
5355 
5356  if (type == kUnknownVideo)
5357  {
5358  if (m_seasonSpin->GetIntValue() == 0 &&
5359  m_episodeSpin->GetIntValue() == 0 &&
5360  m_subtitleEdit->GetText().isEmpty())
5361  {
5362  lookup->SetSubtype(kProbableMovie);
5363  }
5364  else
5365  {
5367  }
5368  }
5369  else
5370  {
5371  // we could determine the type from the inetref
5372  lookup->SetSubtype(type);
5373  }
5374  lookup->SetAllowGeneric(true);
5375  lookup->SetHandleImages(false);
5377  lookup->SetTitle(m_titleEdit->GetText());
5378  lookup->SetSubtitle(m_subtitleEdit->GetText());
5379  lookup->SetInetref(m_inetrefEdit->GetText());
5381  lookup->SetSeason(m_seasonSpin->GetIntValue());
5382  lookup->SetEpisode(m_episodeSpin->GetIntValue());
5383  lookup->SetAutomatic(false);
5384 
5385  m_metadataFactory->Lookup(lookup);
5386 }
5387 
5389 {
5390  if (!lookup)
5391  return;
5392 
5393  m_inetrefEdit->SetText(lookup->GetInetref());
5394  m_seasonSpin->SetValue(lookup->GetSeason());
5395  m_episodeSpin->SetValue(lookup->GetEpisode());
5396  if (!lookup->GetSubtitle().isEmpty())
5397  {
5399  }
5400  if (!lookup->GetDescription().isEmpty())
5401  {
5403  }
5404 }
5405 
5407 {
5408  QueryComplete(lookup);
5409 }
5410 
5411 void RecMetadataEdit::customEvent(QEvent *levent)
5412 {
5413  if (levent->type() == MetadataFactoryMultiResult::kEventType)
5414  {
5415  if (m_busyPopup)
5416  {
5417  m_busyPopup->Close();
5418  m_busyPopup = nullptr;
5419  }
5420 
5421  MetadataFactoryMultiResult *mfmr = dynamic_cast<MetadataFactoryMultiResult*>(levent);
5422 
5423  if (!mfmr)
5424  return;
5425 
5426  MetadataLookupList list = mfmr->m_results;
5427 
5428  MetadataResultsDialog *resultsdialog =
5430 
5431  connect(resultsdialog, SIGNAL(haveResult(RefCountHandler<MetadataLookup>)),
5433  Qt::QueuedConnection);
5434 
5435  if (resultsdialog->Create())
5436  m_popupStack->AddScreen(resultsdialog);
5437  }
5438  else if (levent->type() == MetadataFactorySingleResult::kEventType)
5439  {
5440  if (m_busyPopup)
5441  {
5442  m_busyPopup->Close();
5443  m_busyPopup = nullptr;
5444  }
5445 
5446  MetadataFactorySingleResult *mfsr = dynamic_cast<MetadataFactorySingleResult*>(levent);
5447 
5448  if (!mfsr || !mfsr->m_result)
5449  return;
5450 
5451  QueryComplete(mfsr->m_result);
5452  }
5453  else if (levent->type() == MetadataFactoryNoResult::kEventType)
5454  {
5455  if (m_busyPopup)
5456  {
5457  m_busyPopup->Close();
5458  m_busyPopup = nullptr;
5459  }
5460 
5461  MetadataFactoryNoResult *mfnr = dynamic_cast<MetadataFactoryNoResult*>(levent);
5462 
5463  if (!mfnr)
5464  return;
5465 
5466  QString title = tr("No match found for this recording. You can "
5467  "try entering a TVDB/TMDB number, season, and "
5468  "episode manually.");
5469 
5470  MythConfirmationDialog *okPopup =
5471  new MythConfirmationDialog(m_popupStack, title, false);
5472 
5473  if (okPopup->Create())
5474  m_popupStack->AddScreen(okPopup);
5475  }
5476 }
5477 
5479 
5481 {
5482  if (!LoadWindowFromXML("recordings-ui.xml", "iconhelp", this))
5483  return false;
5484 
5485  m_iconList = dynamic_cast<MythUIButtonList*>(GetChild("iconlist"));
5486 
5487  if (!m_iconList)
5488  {
5489  LOG(VB_GENERAL, LOG_ERR, LOC +
5490  "Window 'iconhelp' is missing required elements.");
5491  return false;
5492  }
5493 
5494  BuildFocusList();
5495 
5496  addItem("commflagged", tr("Commercials are flagged"));
5497  addItem("cutlist", tr("An editing cutlist is present"));
5498  addItem("autoexpire", tr("The program is able to auto-expire"));
5499  addItem("processing", tr("Commercials are being flagged"));
5500  addItem("bookmark", tr("A bookmark is set"));
5501 #if 0
5502  addItem("inuse", tr("Recording is in use"));
5503  addItem("transcoded", tr("Recording has been transcoded"));
5504 #endif
5505 
5506  addItem("mono", tr("Recording is in Mono"));
5507  addItem("stereo", tr("Recording is in Stereo"));
5508  addItem("surround", tr("Recording is in Surround Sound"));
5509  addItem("dolby", tr("Recording is in Dolby Surround Sound"));
5510 
5511  addItem("cc", tr("Recording is Closed Captioned"));
5512  addItem("subtitles", tr("Recording has Subtitles Available"));
5513  addItem("onscreensub", tr("Recording is Subtitled"));
5514 
5515  addItem("hd1080", tr("Recording is in 1080i/p High Definition"));
5516  addItem("hd720", tr("Recording is in 720p High Definition"));
5517  addItem("hdtv", tr("Recording is in High Definition"));
5518  addItem("widescreen", tr("Recording is Widescreen"));
5519  addItem("avchd", tr("Recording is in HD using H.264 codec"));
5520 
5521  addItem("watched", tr("Recording has been watched"));
5522 // addItem("preserved", tr("Recording is preserved"));
5523 
5524  return true;
5525 }
5526 
5527 void HelpPopup::addItem(const QString &state, const QString &text)
5528 {
5530  item->DisplayState(state, "icons");
5531 }
5532 
5534 {
5535  QDateTime now = QDateTime::currentDateTime();
5536  if (!m_lastUpdated.isValid() ||
5537  m_lastUpdated.msecsTo(now) >= kInvalidateTimeMs)
5538  {
5539  QMap<int, JobQueueEntry> jobs;
5541  m_jobs.clear();
5542  for (int i = 0; i < jobs.size(); ++i)
5543  {
5544  JobQueueEntry &entry = jobs[i];
5545  m_jobs.insert(qMakePair(entry.chanid, entry.recstartts), entry);
5546  }
5547  m_lastUpdated = now;
5548