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