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