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