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