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);
456 m_watchListBlackOut = gCoreContext->GetDurSetting<std::chrono::days>("PlaybackWLBlackOut",
457 std::chrono::days(2));
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 !sortedList.contains(tr("Live TV")))
2136 m_titleList << tr("Live TV");
2137 m_titleList << sortedList.values();
2138
2139 // Populate list of recording groups
2141 {
2142 QMutexLocker locker(&m_recGroupsLock);
2143
2144 m_recGroups.clear();
2145 m_recGroupIdx = -1;
2146
2147 m_recGroups.append("All Programs");
2148
2150
2151 query.prepare("SELECT distinct recgroup from recorded WHERE "
2152 "deletepending = 0 ORDER BY recgroup");
2153 if (query.exec())
2154 {
2155 QString name;
2156 while (query.next())
2157 {
2158 name = query.value(0).toString();
2159 if (name != "Deleted" && name != "LiveTV" && !name.startsWith('.'))
2160 {
2161 m_recGroups.append(name);
2162 m_recGroupType[name] = "recgroup";
2163 }
2164 }
2165
2167 m_recGroupIdx = std::max(m_recGroupIdx, 0);
2168 }
2169 }
2170
2171 QChar first;
2172 m_groupAlphabet.clear();
2173 for (auto it = sortedList.keyValueBegin();
2174 it != sortedList.keyValueEnd(); ++it)
2175 {
2176 first = (*it).first.at(0).toUpper();
2177 if (!m_groupAlphabet.contains(first))
2178 m_groupAlphabet[first] = (*it).second;
2179 }
2180
2182 UpdateUIGroupList(groupSelPref);
2183 UpdateUsageUI();
2184
2185 for (uint id : std::as_const(m_playList))
2186 {
2187 ProgramInfo *pginfo = FindProgramInUILists(id);
2188 if (!pginfo)
2189 continue;
2190 MythUIButtonListItem *item =
2191 m_recordingList->GetItemByData(QVariant::fromValue(pginfo));
2192 if (item)
2193 item->DisplayState("yes", "playlist");
2194 }
2195
2197 groupSelPref, itemSelPref, itemTopPref);
2198
2199 m_isFilling = false;
2200
2201 return true;
2202}
2203
2205{
2206 if (Random)
2207 {
2208 m_playListPlay.clear();
2209 QList<uint> tmp = m_playList;
2210 while (!tmp.isEmpty())
2211 {
2212 unsigned int i = MythRandom(0, tmp.size() - 1);
2213 m_playListPlay.append(tmp[i]);
2214 tmp.removeAll(tmp[i]);
2215 }
2216 }
2217 else
2218 {
2220 }
2221
2222 QCoreApplication::postEvent(
2223 this, new MythEvent("PLAY_PLAYLIST"));
2224}
2225
2227{
2228 if (!item)
2230
2231 if (!item)
2232 return;
2233
2234 auto *pginfo = item->GetData().value<ProgramInfo *>();
2235
2236 const bool ignoreBookmark = false;
2237 const bool ignoreProgStart = false;
2238 const bool ignoreLastPlayPos = false;
2239 const bool underNetworkControl = false;
2240 if (pginfo)
2241 PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2242 underNetworkControl);
2243}
2244
2246{
2247 if (!item)
2249
2250 if (!item)
2251 return;
2252
2253 auto *pginfo = item->GetData().value<ProgramInfo *>();
2254
2255 const bool ignoreBookmark = false;
2256 const bool ignoreProgStart = true;
2257 const bool ignoreLastPlayPos = true;
2258 const bool underNetworkControl = false;
2259 if (pginfo)
2260 PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2261 underNetworkControl);
2262}
2263
2265{
2266 if (!item)
2268
2269 if (!item)
2270 return;
2271
2272 auto *pginfo = item->GetData().value<ProgramInfo *>();
2273
2274 const bool ignoreBookmark = true;
2275 const bool ignoreProgStart = true;
2276 const bool ignoreLastPlayPos = true;
2277 const bool underNetworkControl = false;
2278 if (pginfo)
2279 PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2280 underNetworkControl);
2281}
2282
2284{
2285 if (!item)
2287
2288 if (!item)
2289 return;
2290
2291 auto *pginfo = item->GetData().value<ProgramInfo *>();
2292
2293 const bool ignoreBookmark = true;
2294 const bool ignoreProgStart = true;
2295 const bool ignoreLastPlayPos = false;
2296 const bool underNetworkControl = false;
2297 if (pginfo)
2298 PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
2299 underNetworkControl);
2300}
2301
2303 bool ignoreBookmark,
2304 bool ignoreProgStart,
2305 bool ignoreLastPlayPos,
2306 bool underNetworkControl)
2307{
2308 if (!m_player)
2309 {
2310 Play(pginfo, false, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos, underNetworkControl);
2311 return;
2312 }
2313
2314 if (!m_player->IsSameProgram(&pginfo))
2315 {
2317 m_playerSelectedNewShow.push_back(ignoreBookmark ? "1" : "0");
2318 m_playerSelectedNewShow.push_back(underNetworkControl ? "1" : "0");
2319 // XXX add anything for ignoreProgStart and ignoreLastPlayPos?
2320 }
2321 Close();
2322}
2323
2325{
2326 ProgramInfo *pginfo = GetCurrentProgram();
2327 if (pginfo)
2328 pginfo->SaveBookmark(0);
2329}
2330
2332{
2333 ProgramInfo *pginfo = GetCurrentProgram();
2334 if (pginfo)
2335 pginfo->SaveLastPlayPos(0);
2336}
2337
2339{
2340 ProgramInfo *pginfo = GetCurrentProgram();
2341 if (pginfo)
2342 m_helper.StopRecording(*pginfo);
2343}
2344
2346{
2347 if (!item)
2348 return;
2349
2350 auto *pginfo = item->GetData().value<ProgramInfo *>();
2351
2352 if (!pginfo)
2353 return;
2354
2355 if (pginfo->GetAvailableStatus() == asPendingDelete)
2356 {
2357 LOG(VB_GENERAL, LOG_ERR, QString("deleteSelected(%1) -- failed ")
2358 .arg(pginfo->toString(ProgramInfo::kTitleSubtitle)) +
2359 QString("availability status: %1 ")
2360 .arg(pginfo->GetAvailableStatus()));
2361
2362 ShowOkPopup(tr("Cannot delete\n") +
2363 tr("This recording is already being deleted"));
2364 }
2365 else if (!pginfo->QueryIsDeleteCandidate())
2366 {
2367 QString byWho;
2368 pginfo->QueryIsInUse(byWho);
2369
2370 LOG(VB_GENERAL, LOG_ERR, QString("deleteSelected(%1) -- failed ")
2371 .arg(pginfo->toString(ProgramInfo::kTitleSubtitle)) +
2372 QString("delete candidate: %1 in use by %2")
2373 .arg(pginfo->QueryIsDeleteCandidate()).arg(byWho));
2374
2375 if (byWho.isEmpty())
2376 {
2377 ShowOkPopup(tr("Cannot delete\n") +
2378 tr("This recording is already being deleted"));
2379 }
2380 else
2381 {
2382 ShowOkPopup(tr("Cannot delete\n") +
2383 tr("This recording is currently in use by:") + "\n" +
2384 byWho);
2385 }
2386 }
2387 else
2388 {
2389 push_onto_del(m_delList, *pginfo);
2391 }
2392}
2393
2395{
2396 ProgramInfo *pginfo = nullptr;
2397
2399
2400 if (!item)
2401 return nullptr;
2402
2403 pginfo = item->GetData().value<ProgramInfo *>();
2404
2405 if (!pginfo)
2406 return nullptr;
2407
2408 return pginfo;
2409}
2410
2412{
2413 if (!item)
2414 return;
2415
2416 PlayFromAnyMark(item);
2417}
2418
2419void PlaybackBox::popupClosed(const QString& which, int result)
2420{
2421 m_menuDialog = nullptr;
2422
2423 if (result == -2)
2424 {
2425 if (!m_doToggleMenu)
2426 {
2427 m_doToggleMenu = true;
2428 return;
2429 }
2430
2431 if (which == "groupmenu")
2432 {
2433 ProgramInfo *pginfo = GetCurrentProgram();
2434 if (pginfo)
2435 {
2437
2438 if ((asPendingDelete == pginfo->GetAvailableStatus()) ||
2439 (asDeleted == pginfo->GetAvailableStatus()) ||
2441 {
2442 ShowAvailabilityPopup(*pginfo);
2443 }
2444 else
2445 {
2446 ShowActionPopup(*pginfo);
2447 m_doToggleMenu = false;
2448 }
2449 }
2450 }
2451 else if (which == "actionmenu")
2452 {
2454 m_doToggleMenu = false;
2455 }
2456 }
2457 else
2458 {
2459 m_doToggleMenu = true;
2460 }
2461}
2462
2464{
2465 QString label = tr("Group List Menu");
2466
2467 ProgramInfo *pginfo = GetCurrentProgram();
2468
2469 m_popupMenu = new MythMenu(label, this, "groupmenu");
2470
2471 m_popupMenu->AddItem(tr("Change Group Filter"),
2473
2474 m_popupMenu->AddItem(tr("Change Group View"),
2476
2477 if (m_recGroupType[m_recGroup] == "recgroup")
2478 m_popupMenu->AddItem(tr("Change Group Password"),
2480
2481 if (!m_playList.isEmpty())
2482 {
2483 m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
2484 }
2485 else if (!m_player)
2486 {
2487 if (GetFocusWidget() == m_groupList)
2488 {
2489 m_popupMenu->AddItem(tr("Add this Group to Playlist"),
2491 }
2492 else if (pginfo)
2493 {
2494 m_popupMenu->AddItem(tr("Add this recording to Playlist"),
2495 qOverload<>(&PlaybackBox::togglePlayListItem));
2496 }
2497 }
2498
2499 m_popupMenu->AddItem(tr("Help (Status Icons)"), &PlaybackBox::showIconHelp);
2500
2502}
2503
2505 const ProgramInfo &rec,
2506 bool inPlaylist, bool ignoreBookmark, bool ignoreProgStart,
2507 bool ignoreLastPlayPos, bool underNetworkControl)
2508{
2509 bool playCompleted = false;
2510
2511 if (m_player)
2512 return true;
2513
2514 if ((asAvailable != rec.GetAvailableStatus()) || !rec.GetFilesize() ||
2515 !rec.IsPathSet())
2516 {
2518 rec, (inPlaylist) ? kCheckForPlaylistAction : kCheckForPlayAction);
2519 return false;
2520 }
2521
2522 for (size_t i = 0; i < kNumArtImages; i++)
2523 {
2524 if (!m_artImage[i])
2525 continue;
2526
2527 m_artTimer[i]->stop();
2528 m_artImage[i]->Reset();
2529 }
2530
2531 ProgramInfo tvrec(rec);
2532
2533 m_playingSomething = true;
2534 int initIndex = m_recordingList->StopLoad();
2535
2536 if (!gCoreContext->GetBoolSetting("UseProgStartMark", false))
2537 ignoreProgStart = true;
2538
2539 uint flags =
2540 (inPlaylist ? kStartTVInPlayList : kStartTVNoFlags) |
2541 (underNetworkControl ? kStartTVByNetworkCommand : kStartTVNoFlags) |
2542 (ignoreLastPlayPos ? kStartTVIgnoreLastPlayPos : kStartTVNoFlags) |
2543 (ignoreProgStart ? kStartTVIgnoreProgStart : kStartTVNoFlags) |
2544 (ignoreBookmark ? kStartTVIgnoreBookmark : kStartTVNoFlags);
2545
2546 playCompleted = TV::StartTV(&tvrec, flags);
2547
2548 m_playingSomething = false;
2550
2551 if (inPlaylist && !m_playListPlay.empty())
2552 {
2553 QCoreApplication::postEvent(
2554 this, new MythEvent("PLAY_PLAYLIST"));
2555 }
2556
2557 if (m_needUpdate)
2559
2560 return playCompleted;
2561}
2562
2563void PlaybackBox::RemoveProgram( uint recordingID, bool forgetHistory,
2564 bool forceMetadataDelete)
2565{
2566 ProgramInfo *delItem = FindProgramInUILists(recordingID);
2567
2568 if (!delItem)
2569 return;
2570
2571 if (!forceMetadataDelete &&
2572 ((delItem->GetAvailableStatus() == asPendingDelete) ||
2573 !delItem->QueryIsDeleteCandidate()))
2574 {
2575 return;
2576 }
2577
2578 if (m_playList.contains(delItem->GetRecordingID()))
2579 togglePlayListItem(delItem);
2580
2581 if (!forceMetadataDelete)
2582 delItem->UpdateLastDelete(true);
2583
2584 delItem->SetAvailableStatus(asPendingDelete, "RemoveProgram");
2586 forceMetadataDelete, forgetHistory);
2587
2588 // if the item is in the current recording list UI then delete it.
2589 MythUIButtonListItem *uiItem =
2590 m_recordingList->GetItemByData(QVariant::fromValue(delItem));
2591 if (uiItem)
2592 m_recordingList->RemoveItem(uiItem);
2593}
2594
2596{
2597 m_artImage[kArtworkFanart]->Load();
2598}
2599
2601{
2602 m_artImage[kArtworkBanner]->Load();
2603}
2604
2606{
2608}
2609
2611{
2612 QString label;
2613 switch (type)
2614 {
2615 case kDeleteRecording:
2616 label = tr("Are you sure you want to delete:"); break;
2618 label = tr("Recording file does not exist.\n"
2619 "Are you sure you want to delete:");
2620 break;
2621 case kStopRecording:
2622 label = tr("Are you sure you want to stop:"); break;
2623 }
2624
2625 ProgramInfo *delItem = nullptr;
2626 if (m_delList.empty())
2627 {
2628 delItem = GetCurrentProgram();
2629 if (delItem != nullptr)
2630 push_onto_del(m_delList, *delItem);
2631 }
2632 else if (m_delList.size() >= 3)
2633 {
2634 delItem = FindProgramInUILists(m_delList[0].toUInt());
2635 }
2636
2637 if (!delItem)
2638 return;
2639
2640 uint other_delete_cnt = (m_delList.size() / 3) - 1;
2641
2642 label += CreateProgramInfoString(*delItem);
2643
2644 m_popupMenu = new MythMenu(label, this, "deletemenu");
2645
2646 if ((kDeleteRecording == type) &&
2647 delItem->GetRecordingGroup() != "Deleted" &&
2648 delItem->GetRecordingGroup() != "LiveTV")
2649 {
2650 m_popupMenu->AddItem(tr("Yes, and allow re-record"),
2652 }
2653
2654 bool defaultIsYes =
2655 ((kDeleteRecording != type) &&
2657 (delItem->QueryAutoExpire() != kDisableAutoExpire));
2658
2659 switch (type)
2660 {
2661 case kDeleteRecording:
2662 m_popupMenu->AddItem(tr("Yes, delete it"),
2663 qOverload<>(&PlaybackBox::Delete), nullptr, defaultIsYes);
2664 break;
2666 m_popupMenu->AddItem(tr("Yes, delete it"),
2667 &PlaybackBox::DeleteForce, nullptr, defaultIsYes);
2668 break;
2669 case kStopRecording:
2670 m_popupMenu->AddItem(tr("Yes, stop recording"),
2671 &PlaybackBox::StopSelected, nullptr, defaultIsYes);
2672 break;
2673 }
2674
2675
2676 if ((kForceDeleteRecording == type) && other_delete_cnt)
2677 {
2679 tr("Yes, delete it and the remaining %1 list items")
2680 .arg(other_delete_cnt), &PlaybackBox::DeleteForceAllRemaining);
2681 }
2682
2683 switch (type)
2684 {
2685 case kDeleteRecording:
2687 m_popupMenu->AddItem(tr("No, keep it"), &PlaybackBox::DeleteIgnore,
2688 nullptr, !defaultIsYes);
2689 break;
2690 case kStopRecording:
2691 m_popupMenu->AddItem(tr("No, continue recording"), &PlaybackBox::DeleteIgnore,
2692 nullptr, !defaultIsYes);
2693 break;
2694 }
2695
2696 if ((type == kForceDeleteRecording) && other_delete_cnt)
2697 {
2699 tr("No, and keep the remaining %1 list items")
2700 .arg(other_delete_cnt),
2702 }
2703
2705}
2706
2708{
2709 QString msg = pginfo.toString(ProgramInfo::kTitleSubtitle, " ");
2710 msg += "\n";
2711
2712 QString byWho;
2713 switch (pginfo.GetAvailableStatus())
2714 {
2715 case asAvailable:
2716 if (pginfo.QueryIsInUse(byWho))
2717 {
2718 ShowNotification(tr("Recording Available\n"),
2719 sLocation, msg +
2720 tr("This recording is currently in "
2721 "use by:") + "\n" + byWho);
2722 }
2723 else
2724 {
2725 ShowNotification(tr("Recording Available\n"),
2726 sLocation, msg +
2727 tr("This recording is currently "
2728 "Available"));
2729 }
2730 break;
2731 case asPendingDelete:
2732 ShowNotificationError(tr("Recording Unavailable\n"),
2733 sLocation, msg +
2734 tr("This recording is currently being "
2735 "deleted and is unavailable"));
2736 break;
2737 case asDeleted:
2738 ShowNotificationError(tr("Recording Unavailable\n"),
2739 sLocation, msg +
2740 tr("This recording has been "
2741 "deleted and is unavailable"));
2742 break;
2743 case asFileNotFound:
2744 ShowNotificationError(tr("Recording Unavailable\n"),
2745 sLocation, msg +
2746 tr("The file for this recording can "
2747 "not be found"));
2748 break;
2749 case asZeroByte:
2750 ShowNotificationError(tr("Recording Unavailable\n"),
2751 sLocation, msg +
2752 tr("The file for this recording is "
2753 "empty."));
2754 break;
2755 case asNotYetAvailable:
2756 ShowNotificationError(tr("Recording Unavailable\n"),
2757 sLocation, msg +
2758 tr("This recording is not yet "
2759 "available."));
2760 }
2761}
2762
2764{
2765 QString label = tr("There is %n item(s) in the playlist. Actions affect "
2766 "all items in the playlist", "", m_playList.size());
2767
2768 auto *menu = new MythMenu(label, this, "slotmenu");
2769
2770 menu->AddItem(tr("Play"), &PlaybackBox::doPlayList);
2771 menu->AddItem(tr("Shuffle Play"), &PlaybackBox::doPlayListRandom);
2772 menu->AddItem(tr("Clear Playlist"), &PlaybackBox::doClearPlaylist);
2773
2774 if (GetFocusWidget() == m_groupList)
2775 {
2776 if ((m_viewMask & VIEW_TITLES))
2777 {
2778 menu->AddItem(tr("Toggle playlist for this Category/Title"),
2780 }
2781 else
2782 {
2783 menu->AddItem(tr("Toggle playlist for this Group"),
2785 }
2786 }
2787 else
2788 {
2789 menu->AddItem(tr("Toggle playlist for this recording"),
2790 qOverload<>(&PlaybackBox::togglePlayListItem));
2791 }
2792
2793 menu->AddItem(tr("Storage Options"), nullptr, createPlaylistStorageMenu());
2794 menu->AddItem(tr("Job Options"), nullptr, createPlaylistJobMenu());
2795 menu->AddItem(tr("Delete"), &PlaybackBox::PlaylistDeleteKeepHistory);
2796 menu->AddItem(tr("Delete, and allow re-record"),
2798
2799 return menu;
2800}
2801
2803{
2804 QString label = tr("There is %n item(s) in the playlist. Actions affect "
2805 "all items in the playlist", "", m_playList.size());
2806
2807 auto *menu = new MythMenu(label, this, "slotmenu");
2808
2809 menu->AddItem(tr("Change Recording Group"), &PlaybackBox::ShowRecGroupChangerUsePlaylist);
2810 menu->AddItem(tr("Change Playback Group"), &PlaybackBox::ShowPlayGroupChangerUsePlaylist);
2811 menu->AddItem(tr("Disable Auto Expire"), &PlaybackBox::doPlaylistExpireSetOff);
2812 menu->AddItem(tr("Enable Auto Expire"), &PlaybackBox::doPlaylistExpireSetOn);
2813 menu->AddItem(tr("Mark as Watched"), &PlaybackBox::doPlaylistWatchedSetOn);
2814 menu->AddItem(tr("Mark as Unwatched"), &PlaybackBox::doPlaylistWatchedSetOff);
2815 menu->AddItem(tr("Allow Re-record"), &PlaybackBox::doPlaylistAllowRerecord);
2816
2817 return menu;
2818}
2819
2821{
2822 QString label = tr("There is %n item(s) in the playlist. Actions affect "
2823 "all items in the playlist", "", m_playList.size());
2824
2825 auto *menu = new MythMenu(label, this, "slotmenu");
2826
2827 QString jobTitle;
2828 QString command;
2829 QList<uint>::Iterator it;
2830 bool isTranscoding = true;
2831 bool isFlagging = true;
2832 bool isMetadataLookup = true;
2833 bool isRunningUserJob1 = true;
2834 bool isRunningUserJob2 = true;
2835 bool isRunningUserJob3 = true;
2836 bool isRunningUserJob4 = true;
2837
2838 for(it = m_playList.begin(); it != m_playList.end(); ++it)
2839 {
2840 ProgramInfo *tmpItem = FindProgramInUILists(*it);
2841 if (tmpItem)
2842 {
2845 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2846 isTranscoding = false;
2849 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2850 isFlagging = false;
2853 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2854 isMetadataLookup = false;
2857 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2858 isRunningUserJob1 = false;
2861 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2862 isRunningUserJob2 = false;
2865 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2866 isRunningUserJob3 = false;
2869 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime()))
2870 isRunningUserJob4 = false;
2871 if (!isTranscoding && !isFlagging && !isRunningUserJob1 &&
2872 !isRunningUserJob2 && !isRunningUserJob3 && !isRunningUserJob4)
2873 break;
2874 }
2875 }
2876
2877 if (!isTranscoding)
2878 menu->AddItem(tr("Begin Transcoding"), &PlaybackBox::doPlaylistBeginTranscoding);
2879 else
2880 menu->AddItem(tr("Stop Transcoding"), &PlaybackBox::stopPlaylistTranscoding);
2881
2882 if (!isFlagging)
2883 menu->AddItem(tr("Begin Commercial Detection"), &PlaybackBox::doPlaylistBeginFlagging);
2884 else
2885 menu->AddItem(tr("Stop Commercial Detection"), &PlaybackBox::stopPlaylistFlagging);
2886
2887 if (!isMetadataLookup)
2888 menu->AddItem(tr("Begin Metadata Lookup"), &PlaybackBox::doPlaylistBeginLookup);
2889 else
2890 menu->AddItem(tr("Stop Metadata Lookup"), &PlaybackBox::stopPlaylistLookup);
2891
2892 command = gCoreContext->GetSetting("UserJob1", "");
2893 if (!command.isEmpty())
2894 {
2895 jobTitle = gCoreContext->GetSetting("UserJobDesc1");
2896
2897 if (!isRunningUserJob1)
2898 {
2899 menu->AddItem(tr("Begin") + ' ' + jobTitle,
2901 }
2902 else
2903 {
2904 menu->AddItem(tr("Stop") + ' ' + jobTitle,
2906 }
2907 }
2908
2909 command = gCoreContext->GetSetting("UserJob2", "");
2910 if (!command.isEmpty())
2911 {
2912 jobTitle = gCoreContext->GetSetting("UserJobDesc2");
2913
2914 if (!isRunningUserJob2)
2915 {
2916 menu->AddItem(tr("Begin") + ' ' + jobTitle,
2918 }
2919 else
2920 {
2921 menu->AddItem(tr("Stop") + ' ' + jobTitle,
2923 }
2924 }
2925
2926 command = gCoreContext->GetSetting("UserJob3", "");
2927 if (!command.isEmpty())
2928 {
2929 jobTitle = gCoreContext->GetSetting("UserJobDesc3");
2930
2931 if (!isRunningUserJob3)
2932 {
2933 menu->AddItem(tr("Begin") + ' ' + jobTitle,
2935 }
2936 else
2937 {
2938 menu->AddItem(tr("Stop") + ' ' + jobTitle,
2940 }
2941 }
2942
2943 command = gCoreContext->GetSetting("UserJob4", "");
2944 if (!command.isEmpty())
2945 {
2946 jobTitle = gCoreContext->GetSetting("UserJobDesc4");
2947
2948 if (!isRunningUserJob4)
2949 {
2950 menu->AddItem(QString("%1 %2").arg(tr("Begin"), jobTitle),
2952 }
2953 else
2954 {
2955 menu->AddItem(QString("%1 %2").arg(tr("Stop"), jobTitle),
2957 }
2958 }
2959
2960 return menu;
2961}
2962
2964{
2965 if (m_menuDialog || !m_popupMenu)
2966 return;
2967
2968 m_menuDialog = new MythDialogBox(m_popupMenu, m_popupStack, "pbbmainmenupopup");
2969
2970 if (m_menuDialog->Create())
2971 {
2974 }
2975 else
2976 {
2977 delete m_menuDialog;
2978 }
2979}
2980
2982{
2983 if (m_menuDialog)
2984 return;
2985
2986 if (GetFocusWidget() == m_groupList)
2988 else
2989 {
2990 ProgramInfo *pginfo = GetCurrentProgram();
2991 if (pginfo)
2992 {
2994 *pginfo, kCheckForMenuAction);
2995
2996 if ((asPendingDelete == pginfo->GetAvailableStatus()) ||
2997 (asDeleted == pginfo->GetAvailableStatus()) ||
2999 {
3000 ShowAvailabilityPopup(*pginfo);
3001 }
3002 else
3003 {
3004 ShowActionPopup(*pginfo);
3005 }
3006 }
3007 else
3008 {
3010 }
3011 }
3012}
3013
3015{
3016 ProgramInfo *pginfo = GetCurrentProgram();
3017 if (!pginfo)
3018 return nullptr;
3019
3020 QString title = tr("Play Options") + CreateProgramInfoString(*pginfo);
3021
3022 auto *menu = new MythMenu(title, this, "slotmenu");
3023 bool hasLastPlay = pginfo->IsLastPlaySet();
3024 bool hasBookMark = pginfo->IsBookmarkSet();
3025 if (hasLastPlay)
3026 menu->AddItem(tr("Play from last played position"),
3027 qOverload<>(&PlaybackBox::PlayFromLastPlayPos));
3028 if (hasBookMark)
3029 menu->AddItem(tr("Play from bookmark"),
3030 qOverload<>(&PlaybackBox::PlayFromBookmark));
3031 menu->AddItem(tr("Play from beginning"),
3032 qOverload<>(&PlaybackBox::PlayFromBeginning));
3033 if (hasLastPlay)
3034 menu->AddItem(tr("Clear last played position"),
3036 if (hasBookMark)
3037 menu->AddItem(tr("Clear bookmark"), &PlaybackBox::ClearBookmark);
3038
3039 return menu;
3040}
3041
3043{
3044 ProgramInfo *pginfo = GetCurrentProgram();
3045 if (!pginfo)
3046 return nullptr;
3047
3048 QString title = tr("Storage Options") + CreateProgramInfoString(*pginfo);
3049 QString autoExpireText = (pginfo->IsAutoExpirable()) ?
3050 tr("Disable Auto Expire") : tr("Enable Auto Expire");
3051 QString preserveText = (pginfo->IsPreserved()) ?
3052 tr("Do not preserve this episode") : tr("Preserve this episode");
3053
3054 auto *menu = new MythMenu(title, this, "slotmenu");
3055 menu->AddItem(tr("Change Recording Group"), &PlaybackBox::ShowRecGroupChangerNoPlaylist);
3056 menu->AddItem(tr("Change Playback Group"), &PlaybackBox::ShowPlayGroupChangerNoPlaylist);
3057 menu->AddItem(autoExpireText, &PlaybackBox::toggleAutoExpire);
3058 menu->AddItem(preserveText, &PlaybackBox::togglePreserveEpisode);
3059
3060 return menu;
3061}
3062
3064{
3065 ProgramInfo *pginfo = GetCurrentProgram();
3066 if (!pginfo)
3067 return nullptr;
3068
3069 QString title = tr("Scheduling Options") + CreateProgramInfoString(*pginfo);
3070
3071 auto *menu = new MythMenu(title, this, "slotmenu");
3072
3073 menu->AddItem(tr("Edit Recording Schedule"),
3074 qOverload<>(&PlaybackBox::EditScheduled));
3075
3076 menu->AddItem(tr("Allow this episode to re-record"), &PlaybackBox::doAllowRerecord);
3077
3078 menu->AddItem(tr("Show Recording Details"), &PlaybackBox::ShowDetails);
3079
3080 menu->AddItem(tr("Change Recording Metadata"), &PlaybackBox::showMetadataEditor);
3081
3082 menu->AddItem(tr("Custom Edit"), &PlaybackBox::EditCustom);
3083
3084 return menu;
3085}
3086
3087static const std::array<const int,kMaxJobs> kJobs
3088{
3096};
3097std::array<PlaybackBoxCb,kMaxJobs*2> PlaybackBox::kMySlots
3098{ // stop start
3106};
3107
3109{
3110 ProgramInfo *pginfo = GetCurrentProgram();
3111 if (!pginfo)
3112 return nullptr;
3113
3114 QString title = tr("Job Options") + CreateProgramInfoString(*pginfo);
3115
3116 auto *menu = new MythMenu(title, this, "slotmenu");
3117
3118 const std::array<const bool,kMaxJobs> add
3119 {
3120 true,
3121 true,
3122 true,
3123 !gCoreContext->GetSetting("UserJob1", "").isEmpty(),
3124 !gCoreContext->GetSetting("UserJob2", "").isEmpty(),
3125 !gCoreContext->GetSetting("UserJob3", "").isEmpty(),
3126 !gCoreContext->GetSetting("UserJob4", "").isEmpty(),
3127 };
3128 const std::array<const QString,kMaxJobs*2> desc
3129 {
3130 // stop start
3131 tr("Stop Transcoding"), tr("Begin Transcoding"),
3132 tr("Stop Commercial Detection"), tr("Begin Commercial Detection"),
3133 tr("Stop Metadata Lookup"), tr("Begin Metadata Lookup"),
3134 "1", "1",
3135 "2", "2",
3136 "3", "3",
3137 "4", "4",
3138 };
3139
3140 for (size_t i = 0; i < kMaxJobs; i++)
3141 {
3142 if (!add[i])
3143 continue;
3144
3145 QString stop_desc = desc[(i*2)+0];
3146 QString start_desc = desc[(i*2)+1];
3147
3148 if (start_desc.toUInt())
3149 {
3150 QString jobTitle = gCoreContext->GetSetting(
3151 "UserJobDesc"+start_desc, tr("User Job") + " #" + start_desc);
3152 stop_desc = tr("Stop") + ' ' + jobTitle;
3153 start_desc = tr("Begin") + ' ' + jobTitle;
3154 }
3155
3156 bool running = JobQueue::IsJobQueuedOrRunning(
3157 kJobs[i], pginfo->GetChanID(), pginfo->GetRecordingStartTime());
3158
3159 MythMenu *submenu = ((kJobs[i] == JOB_TRANSCODE) && !running)
3160 ? createTranscodingProfilesMenu() : nullptr;
3161 menu->AddItem((running) ? stop_desc : start_desc,
3162 kMySlots[(i * 2) + (running ? 0 : 1)], submenu);
3163 }
3164
3165 return menu;
3166}
3167
3169{
3170 QString label = tr("Transcoding profiles");
3171
3172 auto *menu = new MythMenu(label, this, "transcode");
3173
3174 menu->AddItemV(tr("Default"), QVariant::fromValue(-1));
3175 menu->AddItemV(tr("Autodetect"), QVariant::fromValue(0));
3176
3178 query.prepare("SELECT r.name, r.id "
3179 "FROM recordingprofiles r, profilegroups p "
3180 "WHERE p.name = 'Transcoders' "
3181 "AND r.profilegroup = p.id "
3182 "AND r.name != 'RTjpeg/MPEG4' "
3183 "AND r.name != 'MPEG2' ");
3184
3185 if (!query.exec())
3186 {
3187 MythDB::DBError(LOC + "unable to query transcoders", query);
3188 return nullptr;
3189 }
3190
3191 while (query.next())
3192 {
3193 QString transcoder_name = query.value(0).toString();
3194 int transcoder_id = query.value(1).toInt();
3195
3196 // Translatable strings for known profiles
3197 if (transcoder_name == "High Quality")
3198 transcoder_name = tr("High Quality");
3199 else if (transcoder_name == "Medium Quality")
3200 transcoder_name = tr("Medium Quality");
3201 else if (transcoder_name == "Low Quality")
3202 transcoder_name = tr("Low Quality");
3203
3204 menu->AddItemV(transcoder_name, QVariant::fromValue(transcoder_id));
3205 }
3206
3207 return menu;
3208}
3209
3211{
3212 ProgramInfo *pginfo = GetCurrentProgram();
3213
3214 if (!pginfo)
3215 return;
3216
3217 if (id >= 0)
3218 {
3219 RecordingInfo ri(*pginfo);
3221 }
3223}
3224
3226{
3227 QString label;
3228 if (asFileNotFound == pginfo.GetAvailableStatus())
3229 label = tr("Recording file cannot be found");
3230 else if (asZeroByte == pginfo.GetAvailableStatus())
3231 label = tr("Recording file contains no data");
3232 else
3233 tr("Recording Options");
3234
3235 m_popupMenu = new MythMenu(label + CreateProgramInfoString(pginfo), this, "actionmenu");
3236
3237 if ((asFileNotFound == pginfo.GetAvailableStatus()) ||
3238 (asZeroByte == pginfo.GetAvailableStatus()))
3239 {
3240 if (m_playList.contains(pginfo.GetRecordingID()))
3241 {
3242 m_popupMenu->AddItem(tr("Remove from Playlist"),
3243 qOverload<>(&PlaybackBox::togglePlayListItem));
3244 }
3245 else
3246 {
3247 m_popupMenu->AddItem(tr("Add to Playlist"),
3248 qOverload<>(&PlaybackBox::togglePlayListItem));
3249 }
3250
3251 if (!m_playList.isEmpty())
3252 m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
3253
3254 m_popupMenu->AddItem(tr("Recording Options"), nullptr, createRecordingMenu());
3255
3258 {
3259 m_popupMenu->AddItem(tr("List Recorded Episodes"),
3261 }
3262 else
3263 {
3264 m_popupMenu->AddItem(tr("List All Recordings"),
3266 }
3267
3269
3271
3272 return;
3273 }
3274
3275 bool sameProgram = false;
3276
3277 if (m_player)
3278 sameProgram = m_player->IsSameProgram(&pginfo);
3279
3280 TVState tvstate = kState_None;
3281
3282 if (!sameProgram)
3283 {
3284 if (pginfo.IsBookmarkSet() || pginfo.IsLastPlaySet())
3285 m_popupMenu->AddItem(tr("Play from..."), nullptr, createPlayFromMenu());
3286 else
3287 m_popupMenu->AddItem(tr("Play"),
3288 qOverload<>(&PlaybackBox::PlayFromAnyMark));
3289 }
3290
3291 if (!m_player)
3292 {
3293 if (m_playList.contains(pginfo.GetRecordingID()))
3294 {
3295 m_popupMenu->AddItem(tr("Remove from Playlist"),
3296 qOverload<>(&PlaybackBox::togglePlayListItem));
3297 }
3298 else
3299 {
3300 m_popupMenu->AddItem(tr("Add to Playlist"),
3301 qOverload<>(&PlaybackBox::togglePlayListItem));
3302 }
3303 if (!m_playList.isEmpty())
3304 {
3305 m_popupMenu->AddItem(tr("Playlist Options"), nullptr, createPlaylistMenu());
3306 }
3307 }
3308
3309 if ((pginfo.GetRecordingStatus() == RecStatus::Recording ||
3312 (!sameProgram ||
3313 (tvstate != kState_WatchingLiveTV &&
3314 tvstate != kState_WatchingRecording)))
3315 {
3316 m_popupMenu->AddItem(tr("Stop Recording"), &PlaybackBox::askStop);
3317 }
3318
3319 if (pginfo.IsWatched())
3320 m_popupMenu->AddItem(tr("Mark as Unwatched"), &PlaybackBox::toggleWatched);
3321 else
3322 m_popupMenu->AddItem(tr("Mark as Watched"), &PlaybackBox::toggleWatched);
3323
3324 m_popupMenu->AddItem(tr("Storage Options"), nullptr, createStorageMenu());
3325 m_popupMenu->AddItem(tr("Recording Options"), nullptr, createRecordingMenu());
3326 m_popupMenu->AddItem(tr("Job Options"), nullptr, createJobMenu());
3327
3330 {
3331 m_popupMenu->AddItem(tr("List Recorded Episodes"),
3333 }
3334 else
3335 {
3336 m_popupMenu->AddItem(tr("List All Recordings"),
3338 }
3339
3340 if (!sameProgram)
3341 {
3342 if (pginfo.GetRecordingGroup() == "Deleted")
3343 {
3344 push_onto_del(m_delList, pginfo);
3345 m_popupMenu->AddItem(tr("Undelete"), &PlaybackBox::Undelete);
3346 m_popupMenu->AddItem(tr("Delete Forever"), qOverload<>(&PlaybackBox::Delete));
3347 }
3348 else
3349 {
3351 }
3352 }
3353
3355}
3356
3358{
3359 QDateTime recstartts = pginfo.GetRecordingStartTime();
3360 QDateTime recendts = pginfo.GetRecordingEndTime();
3361
3362 QString timedate = QString("%1 - %2")
3363 .arg(MythDate::toString(
3366
3367 QString title = pginfo.GetTitle();
3368
3369 QString extra;
3370
3371 if (!pginfo.GetSubtitle().isEmpty())
3372 {
3373 extra = QString('\n') + pginfo.GetSubtitle();
3374 }
3375
3376 return QString("\n%1%2\n%3").arg(title, extra, timedate);
3377}
3378
3380{
3381 QList<uint>::Iterator it;
3382 for (it = m_playList.begin(); it != m_playList.end(); ++it)
3383 {
3384 ProgramInfo *tmpItem = FindProgramInUILists(*it);
3385
3386 if (!tmpItem)
3387 continue;
3388
3389 MythUIButtonListItem *item =
3390 m_recordingList->GetItemByData(QVariant::fromValue(tmpItem));
3391
3392 if (item)
3393 item->DisplayState("no", "playlist");
3394 }
3395 m_playList.clear();
3396}
3397
3399{
3400 playSelectedPlaylist(false);
3401}
3402
3403
3405{
3407}
3408
3410{
3411 ProgramInfo *pginfo = GetCurrentProgram();
3412 if (pginfo)
3413 {
3414 push_onto_del(m_delList, *pginfo);
3416 }
3417}
3418
3426{
3427 ProgramInfo *pginfo = GetCurrentProgram();
3428
3429 if (!pginfo)
3430 return;
3431
3432 RecordingInfo ri(*pginfo);
3433 ri.ForgetHistory();
3434 *pginfo = ri;
3435}
3436
3438{
3439 QList<uint>::Iterator it;
3440
3441 for (it = m_playList.begin(); it != m_playList.end(); ++it)
3442 {
3443 ProgramInfo *pginfo = FindProgramInUILists(*it);
3444 if (pginfo != nullptr)
3445 {
3446 RecordingInfo ri(*pginfo);
3447 ri.ForgetHistory();
3448 *pginfo = ri;
3449 }
3450 }
3451
3453 UpdateUILists();
3454}
3455
3456void PlaybackBox::doJobQueueJob(int jobType, int jobFlags)
3457{
3458 ProgramInfo *pginfo = GetCurrentProgram();
3459
3460 if (!pginfo)
3461 return;
3462
3463 ProgramInfo *tmpItem = FindProgramInUILists(*pginfo);
3464
3466 jobType, pginfo->GetChanID(), pginfo->GetRecordingStartTime()))
3467 {
3469 jobType, pginfo->GetChanID(), pginfo->GetRecordingStartTime(),
3470 JOB_STOP);
3471 if ((jobType & JOB_COMMFLAG) && (tmpItem))
3472 {
3473 tmpItem->SetEditing(false);
3474 tmpItem->SetFlagging(false);
3475 }
3476 }
3477 else
3478 {
3479 QString jobHost;
3480 if (gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
3481 jobHost = pginfo->GetHostname();
3482
3483 JobQueue::QueueJob(jobType, pginfo->GetChanID(),
3484 pginfo->GetRecordingStartTime(), "", "", jobHost,
3485 jobFlags);
3486 }
3487}
3488
3490{
3492}
3493
3495{
3497}
3498
3499void PlaybackBox::doPlaylistJobQueueJob(int jobType, int jobFlags)
3500{
3501 for (const uint pbs : std::as_const(m_playList))
3502 {
3504 if (tmpItem &&
3506 jobType,
3507 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime())))
3508 {
3509 QString jobHost;
3510 if (gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
3511 jobHost = tmpItem->GetHostname();
3512
3513 JobQueue::QueueJob(jobType, tmpItem->GetChanID(),
3514 tmpItem->GetRecordingStartTime(),
3515 "", "", jobHost, jobFlags);
3516 }
3517 }
3518}
3519
3521{
3522 QList<uint>::Iterator it;
3523
3524 for (it = m_playList.begin(); it != m_playList.end(); ++it)
3525 {
3526 ProgramInfo *tmpItem = FindProgramInUILists(*it);
3527 if (tmpItem &&
3529 jobType,
3530 tmpItem->GetChanID(), tmpItem->GetRecordingStartTime())))
3531 {
3533 jobType, tmpItem->GetChanID(),
3534 tmpItem->GetRecordingStartTime(), JOB_STOP);
3535
3536 if (jobType & JOB_COMMFLAG)
3537 {
3538 tmpItem->SetEditing(false);
3539 tmpItem->SetFlagging(false);
3540 }
3541 }
3542 }
3543}
3544
3546{
3547 ProgramInfo *pginfo = GetCurrentProgram();
3548 if (pginfo)
3549 {
3550 push_onto_del(m_delList, *pginfo);
3552 }
3553}
3554
3555void PlaybackBox::PlaylistDelete(bool forgetHistory)
3556{
3557 QString forceDeleteStr("0");
3558
3559 QStringList list;
3560 for (int id : std::as_const(m_playList))
3561 {
3562 ProgramInfo *tmpItem = FindProgramInUILists(id);
3563 if (tmpItem && tmpItem->QueryIsDeleteCandidate())
3564 {
3565 tmpItem->SetAvailableStatus(asPendingDelete, "PlaylistDelete");
3566 list.push_back(QString::number(tmpItem->GetRecordingID()));
3567 list.push_back(forceDeleteStr);
3568 list.push_back(forgetHistory ? "1" : "0");
3569
3570 // if the item is in the current recording list UI then delete it.
3571 MythUIButtonListItem *uiItem =
3572 m_recordingList->GetItemByData(QVariant::fromValue(tmpItem));
3573 if (uiItem)
3574 m_recordingList->RemoveItem(uiItem);
3575 }
3576 }
3577 m_playList.clear();
3578
3579 if (!list.empty())
3581
3583}
3584
3585// FIXME: Huh? This doesn't specify which recording to undelete, it just
3586// undeletes the first one on the list
3588{
3589 uint recordingID = 0;
3590 if (extract_one_del(m_delList, recordingID))
3591 m_helper.UndeleteRecording(recordingID);
3592}
3593
3595{
3596 uint recordingID = 0;
3597 while (extract_one_del(m_delList, recordingID))
3598 {
3599 if (flags & kIgnore)
3600 continue;
3601
3602 RemoveProgram(recordingID, (flags & kForgetHistory) != 0, (flags & kForce) != 0);
3603
3604 if (!(flags & kAllRemaining))
3605 break;
3606 }
3607
3608 if (!m_delList.empty())
3609 {
3610 auto *e = new MythEvent("DELETE_FAILURES", m_delList);
3611 m_delList.clear();
3612 QCoreApplication::postEvent(this, e);
3613 }
3614}
3615
3617{
3618 ProgramInfo *pginfo = GetCurrentProgram();
3619 if (pginfo) {
3620 QString title = pginfo->GetTitle().toLower();
3621 MythUIButtonListItem* group = m_groupList->GetItemByData(QVariant::fromValue(title));
3622 if (group)
3623 {
3625 // set focus back to previous item
3626 MythUIButtonListItem *previousItem = m_recordingList->GetItemByData(QVariant::fromValue(pginfo));
3627 m_recordingList->SetItemCurrent(previousItem);
3628 }
3629 }
3630}
3631
3633{
3634 ProgramInfo *pginfo = GetCurrentProgram();
3636 if (pginfo)
3637 {
3638 // set focus back to previous item
3639 MythUIButtonListItem *previousitem =
3640 m_recordingList->GetItemByData(QVariant::fromValue(pginfo));
3641 m_recordingList->SetItemCurrent(previousitem);
3642 }
3643}
3644
3646{
3647 return FindProgramInUILists( pginfo.GetRecordingID(),
3648 pginfo.GetRecordingGroup());
3649}
3650
3652 const QString& recgroup)
3653{
3654 // LiveTV ProgramInfo's are not in the aggregated list
3655 std::array<ProgramList::iterator,2> _it {
3656 m_progLists[tr("Live TV").toLower()].begin(), m_progLists[""].begin() };
3657 std::array<ProgramList::iterator,2> _end {
3658 m_progLists[tr("Live TV").toLower()].end(), m_progLists[""].end() };
3659
3660 if (recgroup != "LiveTV")
3661 {
3662 swap( _it[0], _it[1]);
3663 swap(_end[0], _end[1]);
3664 }
3665
3666 for (uint i = 0; i < 2; i++)
3667 {
3668 auto it = _it[i];
3669 const auto& end = _end[i];
3670 for (; it != end; ++it)
3671 {
3672 if ((*it)->GetRecordingID() == recordingID)
3673 {
3674 return *it;
3675 }
3676 }
3677 }
3678
3679 return nullptr;
3680}
3681
3683{
3685
3686 if (!item)
3687 return;
3688
3689 auto *pginfo = item->GetData().value<ProgramInfo *>();
3690
3691 if (!pginfo)
3692 return;
3693
3694 bool on = !pginfo->IsWatched();
3695 pginfo->SaveWatched(on);
3696 item->DisplayState((on)?"yes":"on", "watched");
3697 updateIcons(pginfo);
3698
3699 // A refill affects the responsiveness of the UI and we only
3700 // need to rebuild the list if the watch list is displayed
3702 UpdateUILists();
3703}
3704
3706{
3708
3709 if (!item)
3710 return;
3711
3712 auto *pginfo = item->GetData().value<ProgramInfo *>();
3713
3714 if (!pginfo)
3715 return;
3716
3717 bool on = !pginfo->IsAutoExpirable();
3718 pginfo->SaveAutoExpire((on) ? kNormalAutoExpire : kDisableAutoExpire, true);
3719 item->DisplayState((on)?"yes":"no", "autoexpire");
3720 updateIcons(pginfo);
3721}
3722
3724{
3726
3727 if (!item)
3728 return;
3729
3730 auto *pginfo = item->GetData().value<ProgramInfo *>();
3731
3732 if (!pginfo)
3733 return;
3734
3735 bool on = !pginfo->IsPreserved();
3736 pginfo->SavePreserve(on);
3737 item->DisplayState(on?"yes":"no", "preserve");
3738 updateIcons(pginfo);
3739}
3740
3741void PlaybackBox::toggleView(ViewMask itemMask, bool setOn)
3742{
3743 if (setOn)
3744 m_viewMask = (ViewMask)(m_viewMask | itemMask);
3745 else
3746 m_viewMask = (ViewMask)(m_viewMask & ~itemMask);
3747
3748 UpdateUILists();
3749}
3750
3752{
3753 QString groupname = m_groupList->GetItemCurrent()->GetData().toString();
3754
3755 for (auto *pl : std::as_const(m_progLists[groupname]))
3756 {
3757 if (pl && (pl->GetAvailableStatus() == asAvailable))
3759 }
3760}
3761
3763{
3765
3766 if (!item)
3767 return;
3768
3769 auto *pginfo = item->GetData().value<ProgramInfo *>();
3770
3771 if (!pginfo)
3772 return;
3773
3774 togglePlayListItem(pginfo);
3775
3778}
3779
3781{
3782 if (!pginfo)
3783 return;
3784
3785 uint recordingID = pginfo->GetRecordingID();
3786
3787 MythUIButtonListItem *item =
3788 m_recordingList->GetItemByData(QVariant::fromValue(pginfo));
3789
3790 if (m_playList.contains(recordingID))
3791 {
3792 if (item)
3793 item->DisplayState("no", "playlist");
3794
3795 m_playList.removeAll(recordingID);
3796 }
3797 else
3798 {
3799 if (item)
3800 item->DisplayState("yes", "playlist");
3801 m_playList.append(recordingID);
3802 }
3803}
3804
3806{
3807 int commands = 0;
3808 QString command;
3809
3810 m_ncLock.lock();
3811 commands = m_networkControlCommands.size();
3812 m_ncLock.unlock();
3813
3814 while (commands)
3815 {
3816 m_ncLock.lock();
3817 command = m_networkControlCommands.front();
3818 m_networkControlCommands.pop_front();
3819 m_ncLock.unlock();
3820
3822
3823 m_ncLock.lock();
3824 commands = m_networkControlCommands.size();
3825 m_ncLock.unlock();
3826 }
3827}
3828
3830{
3831 QStringList tokens = command.simplified().split(" ");
3832
3833 if (tokens.size() >= 4 && (tokens[1] == "PLAY" || tokens[1] == "RESUME"))
3834 {
3835 if (tokens.size() == 6 && tokens[2] == "PROGRAM")
3836 {
3837 int clientID = tokens[5].toInt();
3838
3839 LOG(VB_GENERAL, LOG_INFO, LOC +
3840 QString("NetworkControl: Trying to %1 program '%2' @ '%3'")
3841 .arg(tokens[1], tokens[3], tokens[4]));
3842
3844 {
3845 LOG(VB_GENERAL, LOG_ERR, LOC +
3846 "NetworkControl: Already playing");
3847
3848 QString msg = QString(
3849 "NETWORK_CONTROL RESPONSE %1 ERROR: Unable to play, "
3850 "player is already playing another recording.")
3851 .arg(clientID);
3852
3853 MythEvent me(msg);
3855 return;
3856 }
3857
3858 uint chanid = tokens[3].toUInt();
3859 QDateTime recstartts = MythDate::fromString(tokens[4]);
3860 ProgramInfo pginfo(chanid, recstartts);
3861
3862 if (pginfo.GetChanID())
3863 {
3864 QString msg = QString("NETWORK_CONTROL RESPONSE %1 OK")
3865 .arg(clientID);
3866 MythEvent me(msg);
3868
3869 pginfo.SetPathname(pginfo.GetPlaybackURL());
3870
3871 const bool ignoreBookmark = (tokens[1] == "PLAY");
3872 const bool ignoreProgStart = true;
3873 const bool ignoreLastPlayPos = true;
3874 const bool underNetworkControl = true;
3875 PlayX(pginfo, ignoreBookmark, ignoreProgStart,
3876 ignoreLastPlayPos, underNetworkControl);
3877 }
3878 else
3879 {
3880 QString message = QString("NETWORK_CONTROL RESPONSE %1 "
3881 "ERROR: Could not find recording for "
3882 "chanid %2 @ %3")
3883 .arg(tokens[5], tokens[3], tokens[4]);
3884 MythEvent me(message);
3886 }
3887 }
3888 }
3889}
3890
3891bool PlaybackBox::keyPressEvent(QKeyEvent *event)
3892{
3893 // This should be an impossible keypress we've simulated
3894 if ((event->key() == Qt::Key_LaunchMedia) &&
3895 (event->modifiers() ==
3896 (Qt::ShiftModifier |
3897 Qt::ControlModifier |
3898 Qt::AltModifier |
3899 Qt::MetaModifier |
3900 Qt::KeypadModifier)))
3901 {
3902 event->accept();
3903 m_ncLock.lock();
3904 int commands = m_networkControlCommands.size();
3905 m_ncLock.unlock();
3906 if (commands)
3908 return true;
3909 }
3910
3911 if (GetFocusWidget()->keyPressEvent(event))
3912 return true;
3913
3914 QStringList actions;
3915 bool handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend",
3916 event, actions);
3917
3918 for (int i = 0; i < actions.size() && !handled; ++i)
3919 {
3920 const QString& action = actions[i];
3921 handled = true;
3922
3923 if (action == ACTION_1 || action == "HELP")
3924 showIconHelp();
3925 else if (action == "MENU")
3926 {
3927 ShowMenu();
3928 }
3929 else if (action == "NEXTFAV")
3930 {
3931 if (GetFocusWidget() == m_groupList)
3933 else
3935 }
3936 else if (action == "TOGGLEFAV")
3937 {
3938 m_playList.clear();
3939 UpdateUILists();
3940 }
3941 else if (action == ACTION_TOGGLERECORD)
3942 {
3944 UpdateUILists();
3945 }
3946 else if (action == ACTION_PAGERIGHT)
3947 {
3949 }
3950 else if (action == ACTION_PAGELEFT)
3951 {
3952 QString nextGroup;
3953 m_recGroupsLock.lock();
3954 if (m_recGroupIdx >= 0 && !m_recGroups.empty())
3955 {
3956 if (--m_recGroupIdx < 0)
3957 m_recGroupIdx = m_recGroups.size() - 1;
3958 nextGroup = m_recGroups[m_recGroupIdx];
3959 }
3960 m_recGroupsLock.unlock();
3961
3962 if (!nextGroup.isEmpty())
3963 displayRecGroup(nextGroup);
3964 }
3965 else if (action == "NEXTVIEW")
3966 {
3968 if (++curpos >= m_groupList->GetCount())
3969 curpos = 0;
3970 m_groupList->SetItemCurrent(curpos);
3971 }
3972 else if (action == "PREVVIEW")
3973 {
3975 if (--curpos < 0)
3976 curpos = m_groupList->GetCount() - 1;
3977 m_groupList->SetItemCurrent(curpos);
3978 }
3980 {
3984 else
3986 }
3987 else if (action == "CHANGERECGROUP")
3988 {
3990 }
3991 else if (action == "CHANGEGROUPVIEW")
3992 {
3994 }
3995 else if (action == "EDIT")
3996 {
3997 EditScheduled();
3998 }
3999 else if (m_titleList.size() > 1)
4000 {
4001 if (action == "DELETE")
4003 else if (action == ACTION_PLAYBACK)
4005 else if (action == "DETAILS" || action == "INFO")
4006 ShowDetails();
4007 else if (action == "CUSTOMEDIT")
4008 EditCustom();
4009 else if (action == "GUIDE")
4010 ShowGuide();
4011 else if (action == "UPCOMING")
4012 ShowUpcoming();
4013 else if (action == ACTION_VIEWSCHEDULED)
4015 else if (action == ACTION_PREVRECORDED)
4016 ShowPrevious();
4017 else
4018 handled = false;
4019 }
4020 else
4021 {
4022 handled = false;
4023 }
4024 }
4025
4026 if (!handled && MythScreenType::keyPressEvent(event))
4027 handled = true;
4028
4029 return handled;
4030}
4031
4032void PlaybackBox::customEvent(QEvent *event)
4033{
4034 if (event->type() == DialogCompletionEvent::kEventType)
4035 {
4036 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
4037 if (!dce)
4038 return;
4039
4040 QString resultid = dce->GetId();
4041
4042 if (resultid == "transcode" && dce->GetResult() >= 0)
4043 changeProfileAndTranscode(dce->GetData().toInt());
4044 }
4045 else if (event->type() == MythEvent::kMythEventMessage)
4046 {
4047 auto *me = dynamic_cast<MythEvent *>(event);
4048 if (me == nullptr)
4049 return;
4050
4051 const QString& message = me->Message();
4052
4053 if (message.startsWith("RECORDING_LIST_CHANGE"))
4054 {
4055 QStringList tokens = message.simplified().split(" ");
4056 uint recordingID = 0;
4057 if (tokens.size() >= 3)
4058 recordingID = tokens[2].toUInt();
4059
4060 if ((tokens.size() >= 2) && tokens[1] == "UPDATE")
4061 {
4062 ProgramInfo evinfo(me->ExtraDataList());
4063 if (evinfo.HasPathname() || evinfo.GetChanID())
4064 {
4065 uint32_t flags = m_programInfoCache.Update(evinfo);
4067 HandleUpdateItemEvent(evinfo.GetRecordingID(), flags);
4068 }
4069 }
4070 else if (recordingID && (tokens[1] == "ADD"))
4071 {
4072 ProgramInfo evinfo(recordingID);
4073 if (evinfo.GetChanID())
4074 {
4077 }
4078 }
4079 else if (recordingID && (tokens[1] == "DELETE"))
4080 {
4081 HandleRecordingRemoveEvent(recordingID);
4082 }
4083 else
4084 {
4086 }
4087 }
4088 else if (message.startsWith("NETWORK_CONTROL"))
4089 {
4090 QStringList tokens = message.simplified().split(" ");
4091 if ((tokens[1] != "ANSWER") && (tokens[1] != "RESPONSE"))
4092 {
4093 m_ncLock.lock();
4094 m_networkControlCommands.push_back(message);
4095 m_ncLock.unlock();
4096
4097 // This should be an impossible keypress we're simulating
4098 Qt::KeyboardModifiers modifiers =
4099 Qt::ShiftModifier |
4100 Qt::ControlModifier |
4101 Qt::AltModifier |
4102 Qt::MetaModifier |
4103 Qt::KeypadModifier;
4104 auto *keyevent = new QKeyEvent(QEvent::KeyPress,
4105 Qt::Key_LaunchMedia, modifiers);
4106 QCoreApplication::postEvent(GetMythMainWindow(), keyevent);
4107
4108 keyevent = new QKeyEvent(QEvent::KeyRelease,
4109 Qt::Key_LaunchMedia, modifiers);
4110 QCoreApplication::postEvent(GetMythMainWindow(), keyevent);
4111 }
4112 }
4113 else if (message.startsWith("UPDATE_FILE_SIZE"))
4114 {
4115 QStringList tokens = message.simplified().split(" ");
4116 if (tokens.size() >= 3)
4117 {
4118 bool ok = false;
4119 uint recordingID = tokens[1].toUInt();
4120 uint64_t filesize = tokens[2].toLongLong(&ok);
4121 if (ok)
4122 {
4123 // Delegate to background thread
4124 MConcurrent::run("UpdateFileSize", &m_programInfoCache,
4126 recordingID, filesize,
4128 }
4129 }
4130 }
4131 else if (message == "UPDATE_UI_LIST")
4132 {
4134 m_needUpdate = true;
4135 else
4136 {
4137 UpdateUILists();
4139 }
4140 }
4141 else if (message.startsWith("UPDATE_UI_ITEM"))
4142 {
4143 QStringList tokens = message.simplified().split(" ");
4144 if (tokens.size() < 3)
4145 return;
4146
4147 uint recordingID = tokens[1].toUInt();
4148 auto flags = static_cast<ProgramInfoCache::UpdateState>(tokens[2].toUInt());
4149
4151 HandleUpdateItemEvent(recordingID, flags);
4152 }
4153 else if (message == "UPDATE_USAGE_UI")
4154 {
4155 UpdateUsageUI();
4156 }
4157 else if (message == "RECONNECT_SUCCESS")
4158 {
4160 }
4161 else if (message == "LOCAL_PBB_DELETE_RECORDINGS")
4162 {
4163 QStringList list;
4164 for (uint i = 0; i+2 < (uint)me->ExtraDataList().size(); i+=3)
4165 {
4166 uint recordingID = me->ExtraDataList()[i+0].toUInt();
4167 ProgramInfo *pginfo =
4169
4170 if (!pginfo)
4171 {
4172 LOG(VB_GENERAL, LOG_WARNING, LOC +
4173 QString("LOCAL_PBB_DELETE_RECORDINGS - "
4174 "No matching recording %1")
4175 .arg(recordingID));
4176 continue;
4177 }
4178
4179 QString forceDeleteStr = me->ExtraDataList()[i+1];
4180 QString forgetHistoryStr = me->ExtraDataList()[i+2];
4181
4182 list.push_back(QString::number(pginfo->GetRecordingID()));
4183 list.push_back(forceDeleteStr);
4184 list.push_back(forgetHistoryStr);
4186 "LOCAL_PBB_DELETE_RECORDINGS");
4187
4188 // if the item is in the current recording list UI
4189 // then delete it.
4190 MythUIButtonListItem *uiItem =
4191 m_recordingList->GetItemByData(QVariant::fromValue(pginfo));
4192 if (uiItem)
4193 m_recordingList->RemoveItem(uiItem);
4194 }
4195 if (!list.empty())
4197 }
4198 else if (message == "DELETE_SUCCESSES")
4199 {
4201 }
4202 else if (message == "DELETE_FAILURES")
4203 {
4204 if (me->ExtraDataList().size() < 3)
4205 return;
4206
4207 for (uint i = 0; i+2 < (uint)me->ExtraDataList().size(); i += 3)
4208 {
4210 me->ExtraDataList()[i+0].toUInt());
4211 if (pginfo)
4212 {
4213 pginfo->SetAvailableStatus(asAvailable, "DELETE_FAILURES");
4215 }
4216 }
4217
4218 bool forceDelete = me->ExtraDataList()[1].toUInt() != 0U;
4219 if (!forceDelete)
4220 {
4221 m_delList = me->ExtraDataList();
4222 if (!m_menuDialog)
4223 {
4225 return;
4226 }
4227 LOG(VB_GENERAL, LOG_WARNING, LOC +
4228 "Delete failures not handled due to "
4229 "pre-existing popup.");
4230 }
4231
4232 // Since we deleted items from the UI after we set
4233 // asPendingDelete, we need to put them back now..
4235 }
4236 else if (message == "PREVIEW_SUCCESS")
4237 {
4238 HandlePreviewEvent(me->ExtraDataList());
4239 }
4240 else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
4241 {
4242 for (uint i = 4; i < (uint) me->ExtraDataCount(); i++)
4243 {
4244 const QString& token = me->ExtraData(i);
4245 QSet<QString>::iterator it = m_previewTokens.find(token);
4246 if (it != m_previewTokens.end())
4247 m_previewTokens.erase(it);
4248 }
4249 }
4250 else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
4251 {
4252 static constexpr std::chrono::milliseconds kMaxUIWaitTime = 10s;
4253 QStringList list = me->ExtraDataList();
4254 uint recordingID = list[0].toUInt();
4255 auto cat = (CheckAvailabilityType) list[1].toInt();
4256 auto availableStatus = (AvailableStatusType) list[2].toInt();
4257 uint64_t fs = list[3].toULongLong();
4258 QTime tm;
4259 tm.setHMS(list[4].toUInt(), list[5].toUInt(),
4260 list[6].toUInt(), list[7].toUInt());
4261 QTime now = QTime::currentTime();
4262 auto time_elapsed = std::chrono::milliseconds(tm.msecsTo(now));
4263 if (time_elapsed < 0ms)
4264 time_elapsed += 24h;
4265
4266 AvailableStatusType old_avail = availableStatus;
4267 ProgramInfo *pginfo = FindProgramInUILists(recordingID);
4268 if (pginfo)
4269 {
4270 pginfo->SetFilesize(std::max(pginfo->GetFilesize(), fs));
4271 old_avail = pginfo->GetAvailableStatus();
4272 pginfo->SetAvailableStatus(availableStatus, "AVAILABILITY");
4273 }
4274
4275 if (time_elapsed >= kMaxUIWaitTime)
4276 m_playListPlay.clear();
4277
4278 bool playnext = ((kCheckForPlaylistAction == cat) &&
4279 !m_playListPlay.empty());
4280
4281
4282 if (((kCheckForPlayAction == cat) ||
4284 (time_elapsed < kMaxUIWaitTime))
4285 {
4286 if (asAvailable != availableStatus)
4287 {
4288 if (kCheckForPlayAction == cat && pginfo)
4289 ShowAvailabilityPopup(*pginfo);
4290 }
4291 else if (pginfo)
4292 {
4293 playnext = false;
4294 const bool ignoreBookmark = false;
4295 const bool ignoreProgStart = false;
4296 const bool ignoreLastPlayPos = true;
4297 const bool underNetworkControl = false;
4298 Play(*pginfo, kCheckForPlaylistAction == cat,
4299 ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
4300 underNetworkControl);
4301 }
4302 }
4303
4304 if (playnext)
4305 {
4306 // failed to play this item, instead
4307 // play the next item on the list..
4308 QCoreApplication::postEvent(
4309 this, new MythEvent("PLAY_PLAYLIST"));
4310 }
4311
4312 if (old_avail != availableStatus)
4313 UpdateUIListItem(pginfo, true);
4314 }
4315 else if ((message == "PLAY_PLAYLIST") && !m_playListPlay.empty())
4316 {
4317 uint recordingID = m_playListPlay.front();
4318 m_playListPlay.pop_front();
4319
4320 if (!m_playListPlay.empty())
4321 {
4322 const ProgramInfo *pginfo =
4324 if (pginfo)
4326 }
4327
4328 ProgramInfo *pginfo = FindProgramInUILists(recordingID);
4329 const bool ignoreBookmark = false;
4330 const bool ignoreProgStart = true;
4331 const bool ignoreLastPlayPos = true;
4332 const bool underNetworkControl = false;
4333 if (pginfo)
4334 Play(*pginfo, true, ignoreBookmark, ignoreProgStart,
4335 ignoreLastPlayPos, underNetworkControl);
4336 }
4337 else if ((message == "SET_PLAYBACK_URL") && (me->ExtraDataCount() == 2))
4338 {
4339 uint recordingID = me->ExtraData(0).toUInt();
4341 if (info)
4342 info->SetPathname(me->ExtraData(1));
4343 }
4344 else if ((message == "FOUND_ARTWORK") && (me->ExtraDataCount() >= 5))
4345 {
4346 auto type = (VideoArtworkType) me->ExtraData(2).toInt();
4347 uint recordingID = me->ExtraData(3).toUInt();
4348 const QString& group = me->ExtraData(4);
4349 const QString& fn = me->ExtraData(5);
4350
4351 if (recordingID)
4352 {
4353 ProgramInfo *pginfo = m_programInfoCache.GetRecordingInfo(recordingID);
4354 if (pginfo &&
4355 m_recordingList->GetItemByData(QVariant::fromValue(pginfo)) ==
4357 m_artImage[(uint)type]->GetFilename() != fn)
4358 {
4359 m_artImage[(uint)type]->SetFilename(fn);
4360 m_artTimer[(uint)type]->start(s_artDelay[(uint)type]);
4361 }
4362 }
4363 else if (!group.isEmpty() &&
4364 (m_currentGroup == group) &&
4365 m_artImage[type] &&
4367 m_artImage[(uint)type]->GetFilename() != fn)
4368 {
4369 m_artImage[(uint)type]->SetFilename(fn);
4370 m_artTimer[(uint)type]->start(s_artDelay[(uint)type]);
4371 }
4372 }
4373 else if (message == "EXIT_TO_MENU" ||
4374 message == "CANCEL_PLAYLIST")
4375 {
4376 m_playListPlay.clear();
4377 }
4378 }
4379 else
4380 {
4382 }
4383}
4384
4386{
4387 if (!m_programInfoCache.Remove(recordingID))
4388 {
4389 LOG(VB_GENERAL, LOG_WARNING, LOC +
4390 QString("Failed to remove %1, reloading list")
4391 .arg(recordingID));
4393 return;
4394 }
4395
4397 QString groupname;
4398 if (sel_item)
4399 groupname = sel_item->GetData().toString();
4400
4401 ProgramMap::iterator git = m_progLists.begin();
4402 while (git != m_progLists.end())
4403 {
4404 auto pit = (*git).begin();
4405 while (pit != (*git).end())
4406 {
4407 if ((*pit)->GetRecordingID() == recordingID)
4408 {
4409 if (!git.key().isEmpty() && git.key() == groupname)
4410 {
4411 MythUIButtonListItem *item_by_data =
4413 QVariant::fromValue(*pit));
4414 MythUIButtonListItem *item_cur =
4416
4417 if (item_cur && (item_by_data == item_cur))
4418 {
4419 MythUIButtonListItem *item_next =
4420 m_recordingList->GetItemNext(item_cur);
4421 if (item_next)
4422 m_recordingList->SetItemCurrent(item_next);
4423 }
4424
4425 m_recordingList->RemoveItem(item_by_data);
4426 }
4427 pit = (*git).erase(pit);
4428 }
4429 else
4430 {
4431 ++pit;
4432 }
4433 }
4434
4435 if ((*git).empty())
4436 {
4437 if (!groupname.isEmpty() && (git.key() == groupname))
4438 {
4439 MythUIButtonListItem *next_item =
4440 m_groupList->GetItemNext(sel_item);
4441 if (next_item)
4442 m_groupList->SetItemCurrent(next_item);
4443
4444 m_groupList->RemoveItem(sel_item);
4445
4446 sel_item = next_item;
4447 groupname = "";
4448 if (sel_item)
4449 groupname = sel_item->GetData().toString();
4450 }
4451 git = m_progLists.erase(git);
4452 }
4453 else
4454 {
4455 ++git;
4456 }
4457 }
4458
4460}
4461
4463{
4464 m_programInfoCache.Add(evinfo);
4466}
4467
4469{
4470 // Changing recording group full reload
4472 {
4474 }
4475 else
4476 {
4477 ProgramInfo *pginfo = FindProgramInUILists(recordingID);
4478 if (pginfo == nullptr)
4479 return;
4480 bool genPreview = (flags & ProgramInfoCache::PIC_MARK_CHANGED);
4481 UpdateUIListItem(pginfo, genPreview);
4482 }
4483}
4484
4486{
4488 QCoreApplication::postEvent(this, new MythEvent("UPDATE_UI_LIST"));
4489}
4490
4492{
4493 auto *helpPopup = new HelpPopup(m_popupStack);
4494
4495 if (helpPopup->Create())
4496 m_popupStack->AddScreen(helpPopup);
4497 else
4498 delete helpPopup;
4499}
4500
4502{
4503 auto *viewPopup = new ChangeView(m_popupStack, this, m_viewMask);
4504
4505 if (viewPopup->Create())
4506 {
4507 connect(viewPopup, &ChangeView::save, this, &PlaybackBox::saveViewChanges);
4508 m_popupStack->AddScreen(viewPopup);
4509 }
4510 else
4511 {
4512 delete viewPopup;
4513 }
4514}
4515
4517{
4518 if (m_viewMask == VIEW_NONE)
4520 gCoreContext->SaveSetting("DisplayGroupDefaultViewMask", (int)m_viewMask);
4521 gCoreContext->SaveBoolSetting("PlaybackWatchList",
4522 (m_viewMask & VIEW_WATCHLIST) != 0);
4523}
4524
4526{
4527 QStringList groupNames;
4528 QStringList displayNames;
4529 QStringList groups;
4530 QStringList displayGroups;
4531
4533
4534 m_recGroupType.clear();
4535
4536 uint totalItems = 0;
4537
4538 // Add the group entries
4539 displayNames.append(QString("------- %1 -------").arg(tr("Groups")));
4540 groupNames.append("");
4541
4542 // Find each recording group, and the number of recordings in each
4543 query.prepare("SELECT recgroup, COUNT(title) FROM recorded "
4544 "WHERE deletepending = 0 AND watched <= :WATCHED "
4545 "GROUP BY recgroup");
4546 query.bindValue(":WATCHED", (m_viewMask & VIEW_WATCHED));
4547 if (query.exec())
4548 {
4549 while (query.next())
4550 {
4551 QString dispGroup = query.value(0).toString();
4552 uint items = query.value(1).toInt();
4553
4554 if ((dispGroup != "LiveTV" || (m_viewMask & VIEW_LIVETVGRP)) &&
4555 (dispGroup != "Deleted"))
4556 totalItems += items;
4557
4558 groupNames.append(dispGroup);
4559
4560 dispGroup = (dispGroup == "Default") ? tr("Default") : dispGroup;
4561 dispGroup = (dispGroup == "Deleted") ? tr("Deleted") : dispGroup;
4562 dispGroup = (dispGroup == "LiveTV") ? tr("Live TV") : dispGroup;
4563
4564 displayNames.append(tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup));
4565
4566 m_recGroupType[query.value(0).toString()] = "recgroup";
4567 }
4568 }
4569
4570 // Create and add the "All Programs" entry
4571 displayNames.push_front(tr("%1 [%n item(s)]", nullptr, totalItems)
4572 .arg(ProgramInfo::i18n("All Programs")));
4573 groupNames.push_front("All Programs");
4574 m_recGroupType["All Programs"] = "recgroup";
4575
4576 // Find each category, and the number of recordings in each
4577 query.prepare("SELECT DISTINCT category, COUNT(title) FROM recorded "
4578 "WHERE deletepending = 0 AND watched <= :WATCHED "
4579 "GROUP BY category");
4580 query.bindValue(":WATCHED", (m_viewMask & VIEW_WATCHED));
4581 if (query.exec())
4582 {
4583 int unknownCount = 0;
4584 while (query.next())
4585 {
4586 uint items = query.value(1).toInt();
4587 QString dispGroup = query.value(0).toString();
4588 if (dispGroup.isEmpty())
4589 {
4590 unknownCount += items;
4591 dispGroup = tr("Unknown");
4592 }
4593 else if (dispGroup == tr("Unknown"))
4594 {
4595 unknownCount += items;
4596 }
4597
4598 if ((!m_recGroupType.contains(dispGroup)) &&
4599 (dispGroup != tr("Unknown")))
4600 {
4601 displayGroups += tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup);
4602 groups += dispGroup;
4603
4604 m_recGroupType[dispGroup] = "category";
4605 }
4606 }
4607
4608 if (unknownCount > 0)
4609 {
4610 QString dispGroup = tr("Unknown");
4611 uint items = unknownCount;
4612 displayGroups += tr("%1 [%n item(s)]", nullptr, items).arg(dispGroup);
4613 groups += dispGroup;
4614
4615 m_recGroupType[dispGroup] = "category";
4616 }
4617 }
4618
4619 // Add the category entries
4620 displayNames.append(QString("------- %1 -------").arg(tr("Categories")));
4621 groupNames.append("");
4622 groups.sort();
4623 displayGroups.sort();
4624 QStringList::iterator it;
4625 for (it = displayGroups.begin(); it != displayGroups.end(); ++it)
4626 displayNames.append(*it);
4627 for (it = groups.begin(); it != groups.end(); ++it)
4628 groupNames.append(*it);
4629
4630 QString label = tr("Change Filter");
4631
4632 auto *recGroupPopup = new GroupSelector(m_popupStack, label, displayNames,
4633 groupNames, m_recGroup);
4634
4635 if (recGroupPopup->Create())
4636 {
4637 m_usingGroupSelector = true;
4638 m_groupSelected = false;
4639 connect(recGroupPopup, &GroupSelector::result,
4641 connect(recGroupPopup, &MythScreenType::Exiting,
4643 m_popupStack->AddScreen(recGroupPopup);
4644 }
4645 else
4646 {
4647 delete recGroupPopup;
4648 }
4649}
4650
4652{
4653 if (m_groupSelected)
4654 return;
4655
4656 if (m_firstGroup)
4657 Close();
4658
4659 m_usingGroupSelector = false;
4660}
4661
4662void PlaybackBox::setGroupFilter(const QString &recGroup)
4663{
4664 QString newRecGroup = recGroup;
4665
4666 if (newRecGroup.isEmpty())
4667 return;
4668
4669 m_firstGroup = false;
4670 m_usingGroupSelector = false;
4671
4672 if (newRecGroup == ProgramInfo::i18n("Default"))
4673 newRecGroup = "Default";
4674 else if (newRecGroup == ProgramInfo::i18n("All Programs"))
4675 newRecGroup = "All Programs";
4676 else if (newRecGroup == ProgramInfo::i18n("LiveTV"))
4677 newRecGroup = "LiveTV";
4678 else if (newRecGroup == ProgramInfo::i18n("Deleted"))
4679 newRecGroup = "Deleted";
4680
4681 m_recGroup = newRecGroup;
4682
4684
4685 // Since the group filter is changing, the current position in the lists
4686 // is meaningless -- so reset the lists so the position won't be saved.
4688 m_groupList->Reset();
4689
4690 UpdateUILists();
4691
4692 if (gCoreContext->GetBoolSetting("RememberRecGroup",true))
4693 gCoreContext->SaveSetting("DisplayRecGroup", m_recGroup);
4694
4695 if (m_recGroupType[m_recGroup] == "recgroup")
4696 gCoreContext->SaveSetting("DisplayRecGroupIsCategory", 0);
4697 else
4698 gCoreContext->SaveSetting("DisplayRecGroupIsCategory", 1);
4699}
4700
4701QString PlaybackBox::getRecGroupPassword(const QString &group)
4702{
4703 return m_recGroupPwCache.value(group);
4704}
4705
4707{
4708 m_recGroupPwCache.clear();
4709
4711 query.prepare("SELECT recgroup, password FROM recgroups "
4712 "WHERE password IS NOT NULL AND password <> '';");
4713
4714 if (query.exec())
4715 {
4716 while (query.next())
4717 {
4718 QString recgroup = query.value(0).toString();
4719
4720 if (recgroup == ProgramInfo::i18n("Default"))
4721 recgroup = "Default";
4722 else if (recgroup == ProgramInfo::i18n("All Programs"))
4723 recgroup = "All Programs";
4724 else if (recgroup == ProgramInfo::i18n("LiveTV"))
4725 recgroup = "LiveTV";
4726 else if (recgroup == ProgramInfo::i18n("Deleted"))
4727 recgroup = "Deleted";
4728
4729 m_recGroupPwCache.insert(recgroup, query.value(1).toString());
4730 }
4731 }
4732}
4733
4736{
4737 m_opOnPlaylist = use_playlist;
4738
4739 ProgramInfo *pginfo = nullptr;
4740 if (use_playlist)
4741 {
4742 if (!m_playList.empty())
4743 pginfo = FindProgramInUILists(m_playList[0]);
4744 }
4745 else
4746 {
4747 pginfo = GetCurrentProgram();
4748 }
4749
4750 if (!pginfo)
4751 return;
4752
4754 query.prepare(
4755 "SELECT g.recgroup, COUNT(r.title) FROM recgroups g "
4756 "LEFT JOIN recorded r ON g.recgroupid=r.recgroupid AND r.deletepending = 0 "
4757 "WHERE g.recgroupid != 2 AND g.recgroupid != 3 "
4758 "GROUP BY g.recgroupid ORDER BY g.recgroup");
4759
4760 QStringList displayNames(tr("Add New"));
4761 QStringList groupNames("addnewgroup");
4762
4763 if (!query.exec())
4764 return;
4765
4766 while (query.next())
4767 {
4768 QString dispGroup = query.value(0).toString();
4769 groupNames.push_back(dispGroup);
4770
4771 if (dispGroup == "Default")
4772 dispGroup = tr("Default");
4773 else if (dispGroup == "LiveTV")
4774 dispGroup = tr("Live TV");
4775 else if (dispGroup == "Deleted")
4776 dispGroup = tr("Deleted");
4777
4778 displayNames.push_back(tr("%1 [%n item(s)]", "", query.value(1).toInt())
4779 .arg(dispGroup));
4780 }
4781
4782 QString label = tr("Select Recording Group") +
4783 CreateProgramInfoString(*pginfo);
4784
4785 auto *rgChanger = new GroupSelector(m_popupStack, label, displayNames,
4786 groupNames, pginfo->GetRecordingGroup());
4787
4788 if (rgChanger->Create())
4789 {
4790 connect(rgChanger, &GroupSelector::result, this, &PlaybackBox::setRecGroup);
4791 m_popupStack->AddScreen(rgChanger);
4792 }
4793 else
4794 {
4795 delete rgChanger;
4796 }
4797}
4798
4801{
4802 m_opOnPlaylist = use_playlist;
4803
4804 ProgramInfo *pginfo = nullptr;
4805 if (use_playlist)
4806 {
4807 if (!m_playList.empty())
4808 pginfo = FindProgramInUILists(m_playList[0]);
4809 }
4810 else
4811 {
4812 pginfo = GetCurrentProgram();
4813 }
4814
4815 if (!pginfo)
4816 return;
4817
4818 QStringList groupNames(tr("Default"));
4819 QStringList displayNames("Default");
4820
4821 QStringList list = PlayGroup::GetNames();
4822 for (const auto& name : std::as_const(list))
4823 {
4824 displayNames.push_back(name);
4825 groupNames.push_back(name);
4826 }
4827
4828 QString label = tr("Select Playback Group") +
4829 CreateProgramInfoString(*pginfo);
4830
4831 auto *pgChanger = new GroupSelector(m_popupStack, label,displayNames,
4832 groupNames, pginfo->GetPlaybackGroup());
4833
4834 if (pgChanger->Create())
4835 {
4836 connect(pgChanger, &GroupSelector::result,
4838 m_popupStack->AddScreen(pgChanger);
4839 }
4840 else
4841 {
4842 delete pgChanger;
4843 }
4844}
4845
4847{
4848 QList<uint>::Iterator it;
4849
4850 for (it = m_playList.begin(); it != m_playList.end(); ++it)
4851 {
4852 ProgramInfo *tmpItem = FindProgramInUILists(*it);
4853 if (tmpItem != nullptr)
4854 {
4855 if (!tmpItem->IsAutoExpirable() && turnOn)
4856 tmpItem->SaveAutoExpire(kNormalAutoExpire, true);
4857 else if (tmpItem->IsAutoExpirable() && !turnOn)
4858 tmpItem->SaveAutoExpire(kDisableAutoExpire, true);
4859 }
4860 }
4861}
4862
4864{
4865 QList<uint>::Iterator it;
4866
4867 for (it = m_playList.begin(); it != m_playList.end(); ++it)
4868 {
4869 ProgramInfo *tmpItem = FindProgramInUILists(*it);
4870 if (tmpItem != nullptr)
4871 {
4872 tmpItem->SaveWatched(turnOn);
4873 }
4874 }
4875
4877 UpdateUILists();
4878}
4879
4881{
4882 ProgramInfo *pgInfo = GetCurrentProgram();
4883
4885
4886 auto *editMetadata = new RecMetadataEdit(mainStack, pgInfo);
4887
4888 if (editMetadata->Create())
4889 {
4890 connect(editMetadata, &RecMetadataEdit::result,
4892 mainStack->AddScreen(editMetadata);
4893 }
4894 else
4895 {
4896 delete editMetadata;
4897 }
4898}
4899
4900void PlaybackBox::saveRecMetadata(const QString &newTitle,
4901 const QString &newSubtitle,
4902 const QString &newDescription,
4903 const QString &newInetref,
4904 uint newSeason,
4905 uint newEpisode)
4906{
4908
4909 if (!item)
4910 return;
4911
4912 auto *pginfo = item->GetData().value<ProgramInfo *>();
4913
4914 if (!pginfo)
4915 return;
4916
4917 QString groupname = m_groupList->GetItemCurrent()->GetData().toString();
4918
4919 if (groupname == pginfo->GetTitle().toLower() &&
4920 newTitle != pginfo->GetTitle())
4921 {
4923 }
4924 else
4925 {
4926 QString tempSubTitle = newTitle;
4927 if (!newSubtitle.trimmed().isEmpty())
4928 tempSubTitle = QString("%1 - \"%2\"")
4929 .arg(tempSubTitle, newSubtitle);
4930
4931 QString seasone;
4932 QString seasonx;
4933 QString season;
4934 QString episode;
4935 if (newSeason > 0 || newEpisode > 0)
4936 {
4937 season = StringUtil::intToPaddedString(newSeason, 1);
4938 episode = StringUtil::intToPaddedString(newEpisode, 1);
4939 seasone = QString("s%1e%2")
4940 .arg(StringUtil::intToPaddedString(newSeason, 2),
4941 StringUtil::intToPaddedString(newEpisode, 2));
4942 seasonx = QString("%1x%2")
4943 .arg(StringUtil::intToPaddedString(newSeason, 1),
4944 StringUtil::intToPaddedString(newEpisode, 2));
4945 }
4946
4947 item->SetText(tempSubTitle, "titlesubtitle");
4948 item->SetText(newTitle, "title");
4949 item->SetText(newSubtitle, "subtitle");
4950 item->SetText(newInetref, "inetref");
4951 item->SetText(seasonx, "00x00");
4952 item->SetText(seasone, "s00e00");
4953 item->SetText(season, "season");
4954 item->SetText(episode, "episode");
4955 if (newDescription != nullptr)
4956 item->SetText(newDescription, "description");
4957 }
4958
4959 pginfo->SaveInetRef(newInetref);
4960 pginfo->SaveSeasonEpisode(newSeason, newEpisode);
4961
4962 RecordingInfo ri(*pginfo);
4963 ri.ApplyRecordRecTitleChange(newTitle, newSubtitle, newDescription);
4964 *pginfo = ri;
4965}
4966
4967void PlaybackBox::setRecGroup(QString newRecGroup)
4968{
4969 newRecGroup = newRecGroup.simplified();
4970
4971 if (newRecGroup.isEmpty())
4972 return;
4973
4974 if (newRecGroup == "addnewgroup")
4975 {
4976 MythScreenStack *popupStack =
4977 GetMythMainWindow()->GetStack("popup stack");
4978
4979 auto *newgroup = new MythTextInputDialog(popupStack,
4980 tr("New Recording Group"));
4981
4982 connect(newgroup, &MythTextInputDialog::haveResult,
4984
4985 if (newgroup->Create())
4986 popupStack->AddScreen(newgroup, false);
4987 else
4988 delete newgroup;
4989 return;
4990 }
4991
4992 RecordingRule record;
4993 record.LoadTemplate("Default");
4994 AutoExpireType defaultAutoExpire =
4996
4997 if (m_opOnPlaylist)
4998 {
4999 for (int id : std::as_const(m_playList))
5000 {
5002 if (!p)
5003 continue;
5004
5005 if ((p->GetRecordingGroup() == "LiveTV") &&
5006 (newRecGroup != "LiveTV"))
5007 {
5008 p->SaveAutoExpire(defaultAutoExpire);
5009 }
5010 else if ((p->GetRecordingGroup() != "LiveTV") &&
5011 (newRecGroup == "LiveTV"))
5012 {
5013 p->SaveAutoExpire(kLiveTVAutoExpire);
5014 }
5015
5016 RecordingInfo ri(*p);
5017 ri.ApplyRecordRecGroupChange(newRecGroup);
5018 *p = ri;
5019 }
5021 UpdateUILists();
5022 return;
5023 }
5024
5026 if (!p)
5027 return;
5028
5029 if ((p->GetRecordingGroup() == "LiveTV") && (newRecGroup != "LiveTV"))
5030 p->SaveAutoExpire(defaultAutoExpire);
5031 else if ((p->GetRecordingGroup() != "LiveTV") && (newRecGroup == "LiveTV"))
5032 p->SaveAutoExpire(kLiveTVAutoExpire);
5033
5034 RecordingInfo ri(*p);
5035 ri.ApplyRecordRecGroupChange(newRecGroup);
5036 *p = ri;
5037 UpdateUILists();
5038}
5039
5040void PlaybackBox::setPlayGroup(QString newPlayGroup)
5041{
5042 ProgramInfo *tmpItem = GetCurrentProgram();
5043
5044 if (newPlayGroup.isEmpty() || !tmpItem)
5045 return;
5046
5047 if (newPlayGroup == tr("Default"))
5048 newPlayGroup = "Default";
5049
5050 if (m_opOnPlaylist)
5051 {
5052 QList<uint>::Iterator it;
5053
5054 for (it = m_playList.begin(); it != m_playList.end(); ++it )
5055 {
5056 tmpItem = FindProgramInUILists(*it);
5057 if (tmpItem)
5058 {
5059 RecordingInfo ri(*tmpItem);
5060 ri.ApplyRecordPlayGroupChange(newPlayGroup);
5061 *tmpItem = ri;
5062 }
5063 }
5065 }
5066 else
5067 {
5068 RecordingInfo ri(*tmpItem);
5069 ri.ApplyRecordPlayGroupChange(newPlayGroup);
5070 *tmpItem = ri;
5071 }
5072}
5073
5075{
5077
5078 if (!item)
5079 return;
5080
5081 QString currentPassword = getRecGroupPassword(m_recGroup);
5082
5083 auto *pwChanger = new PasswordChange(m_popupStack, currentPassword);
5084
5085 if (pwChanger->Create())
5086 {
5087 connect(pwChanger, &PasswordChange::result,
5089 m_popupStack->AddScreen(pwChanger);
5090 }
5091 else
5092 {
5093 delete pwChanger;
5094 }
5095}
5096
5097void PlaybackBox::SetRecGroupPassword(const QString &newPassword)
5098{
5100
5101 query.prepare("UPDATE recgroups SET password = :PASSWD WHERE "
5102 "recgroup = :RECGROUP");
5103 query.bindValue(":RECGROUP", m_recGroup);
5104 query.bindValue(":PASSWD", newPassword);
5105
5106 if (!query.exec())
5107 MythDB::DBError("PlaybackBox::SetRecGroupPassword",
5108 query);
5109
5110 if (newPassword.isEmpty())
5112 else
5113 m_recGroupPwCache.insert(m_recGroup, newPassword);
5114}
5115
5117
5119{
5120 if (!LoadWindowFromXML("recordings-ui.xml", "groupselector", this))
5121 return false;
5122
5123 MythUIText *labelText = dynamic_cast<MythUIText*> (GetChild("label"));
5124 MythUIButtonList *groupList = dynamic_cast<MythUIButtonList*>
5125 (GetChild("groups"));
5126
5127 if (!groupList)
5128 {
5129 LOG(VB_GENERAL, LOG_ERR, LOC +
5130 "Theme is missing 'groups' button list.");
5131 return false;
5132 }
5133
5134 if (labelText)
5135 labelText->SetText(m_label);
5136
5137 for (int i = 0; i < m_list.size(); ++i)
5138 {
5139 new MythUIButtonListItem(groupList, m_list.at(i),
5140 QVariant::fromValue(m_data.at(i)));
5141 }
5142
5143 // Set the current position in the list
5144 groupList->SetValueByData(QVariant::fromValue(m_selected));
5145
5147
5148 connect(groupList, &MythUIButtonList::itemClicked,
5150
5151 return true;
5152}
5153
5155{
5156 if (!item)
5157 return;
5158
5159 // ignore the dividers
5160 if (item->GetData().toString().isEmpty())
5161 return;
5162
5163 QString group = item->GetData().toString();
5164 emit result(group);
5165 Close();
5166}
5167
5169
5171{
5172 if (!LoadWindowFromXML("recordings-ui.xml", "changeview", this))
5173 return false;
5174
5175 MythUICheckBox *checkBox = dynamic_cast<MythUICheckBox*>(GetChild("titles"));
5176 if (checkBox)
5177 {
5180 connect(checkBox, &MythUICheckBox::toggled,
5182 }
5183
5184 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("categories"));
5185 if (checkBox)
5186 {
5189 connect(checkBox, &MythUICheckBox::toggled,
5191 }
5192
5193 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("recgroups"));
5194 if (checkBox)
5195 {
5198 connect(checkBox, &MythUICheckBox::toggled,
5200 }
5201
5202 // TODO Do we need two separate settings to determine whether the watchlist
5203 // is shown? The filter setting be enough?
5204 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("watchlist"));
5205 if (checkBox)
5206 {
5209 connect(checkBox, &MythUICheckBox::toggled,
5211 }
5212 //
5213
5214 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("searches"));
5215 if (checkBox)
5216 {
5219 connect(checkBox, &MythUICheckBox::toggled,
5221 }
5222
5223 // TODO Do we need two separate settings to determine whether livetv
5224 // recordings are shown? Same issue as the watchlist above
5225 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("livetv"));
5226 if (checkBox)
5227 {
5230 connect(checkBox, &MythUICheckBox::toggled,
5232 }
5233 //
5234
5235 checkBox = dynamic_cast<MythUICheckBox*>(GetChild("watched"));
5236 if (checkBox)
5237 {
5240 connect(checkBox, &MythUICheckBox::toggled,
5242 }
5243
5244 MythUIButton *savebutton = dynamic_cast<MythUIButton*>(GetChild("save"));
5245 connect(savebutton, &MythUIButton::Clicked, this, &ChangeView::SaveChanges);
5246
5248
5249 return true;
5250}
5251
5253{
5254 emit save();
5255 Close();
5256}
5257
5259
5261{
5262 if (!LoadWindowFromXML("recordings-ui.xml", "passwordchanger", this))
5263 return false;
5264
5265 m_oldPasswordEdit = dynamic_cast<MythUITextEdit *>(GetChild("oldpassword"));
5266 m_newPasswordEdit = dynamic_cast<MythUITextEdit *>(GetChild("newpassword"));
5267 m_okButton = dynamic_cast<MythUIButton *>(GetChild("ok"));
5268
5270 {
5271 LOG(VB_GENERAL, LOG_ERR, LOC +
5272 "Window 'passwordchanger' is missing required elements.");
5273 return false;
5274 }
5275
5278// if (m_oldPassword.isEmpty())
5279// m_oldPasswordEdit->SetDisabled(true);
5282
5284
5288
5289 return true;
5290}
5291
5293{
5294 QString newText = m_oldPasswordEdit->GetText();
5295 bool ok = (newText == m_oldPassword);
5297}
5298
5299
5301{
5303 Close();
5304}
5305
5307
5309 : MythScreenType(lparent, "recmetadataedit"),
5310 m_progInfo(pginfo),
5311 m_metadataFactory(new MetadataFactory(this))
5312{
5313 m_popupStack = GetMythMainWindow()->GetStack("popup stack");
5314}
5315
5317{
5318 if (!LoadWindowFromXML("recordings-ui.xml", "editmetadata", this))
5319 return false;
5320
5321 m_titleEdit = dynamic_cast<MythUITextEdit*>(GetChild("title"));
5322 m_subtitleEdit = dynamic_cast<MythUITextEdit*>(GetChild("subtitle"));
5323 m_descriptionEdit = dynamic_cast<MythUITextEdit*>(GetChild("description"));
5324 m_inetrefEdit = dynamic_cast<MythUITextEdit*>(GetChild("inetref"));
5325 MythUIButton *inetrefClear = dynamic_cast<MythUIButton*>
5326 (GetChild("inetref_clear"));
5327 m_seasonSpin = dynamic_cast<MythUISpinBox*>(GetChild("season"));
5328 m_episodeSpin = dynamic_cast<MythUISpinBox*>(GetChild("episode"));
5329 MythUIButton *okButton = dynamic_cast<MythUIButton*>(GetChild("ok"));
5330 m_queryButton = dynamic_cast<MythUIButton*>(GetChild("query_button"));
5331
5333 !m_episodeSpin || !okButton)
5334 {
5335 LOG(VB_GENERAL, LOG_ERR, LOC +
5336 "Window 'editmetadata' is missing required elements.");
5337 return false;
5338 }
5339
5345 {
5348 }
5351 m_seasonSpin->SetRange(0,9999,1,5);
5353 m_episodeSpin->SetRange(0,9999,1,10);
5355
5356 connect(inetrefClear, &MythUIButton::Clicked, this, &RecMetadataEdit::ClearInetref);
5357 connect(okButton, &MythUIButton::Clicked, this, &RecMetadataEdit::SaveChanges);
5358 if (m_queryButton)
5359 {
5361 }
5362
5364
5365 return true;
5366}
5367
5369{
5371}
5372
5374{
5375 QString newRecTitle = m_titleEdit->GetText();
5376 QString newRecSubtitle = m_subtitleEdit->GetText();
5377 QString newRecDescription = nullptr;
5378 QString newRecInetref = nullptr;
5379 uint newRecSeason = 0;
5380 uint newRecEpisode = 0;
5382 newRecDescription = m_descriptionEdit->GetText();
5383 newRecInetref = m_inetrefEdit->GetText();
5384 newRecSeason = m_seasonSpin->GetIntValue();
5385 newRecEpisode = m_episodeSpin->GetIntValue();
5386
5387 if (newRecTitle.isEmpty())
5388 return;
5389
5390 emit result(newRecTitle, newRecSubtitle, newRecDescription,
5391 newRecInetref, newRecSeason, newRecEpisode);
5392 Close();
5393}
5394
5396{
5397 if (m_busyPopup)
5398 return;
5399
5400 m_busyPopup = new MythUIBusyDialog(tr("Trying to manually find this "
5401 "recording online..."),
5403 "metaoptsdialog");
5404
5405 if (m_busyPopup->Create())
5407
5408 auto *lookup = new MetadataLookup();
5409 lookup->SetStep(kLookupSearch);
5410 lookup->SetType(kMetadataRecording);
5412
5413 if (type == kUnknownVideo)
5414 {
5415 if (m_seasonSpin->GetIntValue() == 0 &&
5416 m_episodeSpin->GetIntValue() == 0 &&
5417 m_subtitleEdit->GetText().isEmpty())
5418 {
5419 lookup->SetSubtype(kProbableMovie);
5420 }
5421 else
5422 {
5423 lookup->SetSubtype(kProbableTelevision);
5424 }
5425 }
5426 else
5427 {
5428 // we could determine the type from the inetref
5429 lookup->SetSubtype(type);
5430 }
5431 lookup->SetAllowGeneric(true);
5432 lookup->SetHandleImages(false);
5433 lookup->SetHost(gCoreContext->GetMasterHostName());
5434 lookup->SetTitle(m_titleEdit->GetText());
5435 lookup->SetSubtitle(m_subtitleEdit->GetText());
5436 lookup->SetInetref(m_inetrefEdit->GetText());
5437 lookup->SetCollectionref(m_inetrefEdit->GetText());
5438 lookup->SetSeason(m_seasonSpin->GetIntValue());
5439 lookup->SetEpisode(m_episodeSpin->GetIntValue());
5440 lookup->SetAutomatic(false);
5441
5442 m_metadataFactory->Lookup(lookup);
5443}
5444
5446{
5447 if (!lookup)
5448 return;
5449
5450 m_inetrefEdit->SetText(lookup->GetInetref());
5451 m_seasonSpin->SetValue(lookup->GetSeason());
5452 m_episodeSpin->SetValue(lookup->GetEpisode());
5453 if (!lookup->GetSubtitle().isEmpty())
5454 {
5456 }
5457 if (!lookup->GetDescription().isEmpty())
5458 {
5460 }
5461}
5462
5464{
5465 QueryComplete(lookup);
5466}
5467
5469{
5470 if (levent->type() == MetadataFactoryMultiResult::kEventType)
5471 {
5472 if (m_busyPopup)
5473 {
5474 m_busyPopup->Close();
5475 m_busyPopup = nullptr;
5476 }
5477
5478 auto *mfmr = dynamic_cast<MetadataFactoryMultiResult*>(levent);
5479
5480 if (!mfmr)
5481 return;
5482
5483 MetadataLookupList list = mfmr->m_results;
5484
5485 auto *resultsdialog = new MetadataResultsDialog(m_popupStack, list);
5486
5487 connect(resultsdialog, &MetadataResultsDialog::haveResult,
5489 Qt::QueuedConnection);
5490
5491 if (resultsdialog->Create())
5492 m_popupStack->AddScreen(resultsdialog);
5493 }
5494 else if (levent->type() == MetadataFactorySingleResult::kEventType)
5495 {
5496 if (m_busyPopup)
5497 {
5498 m_busyPopup->Close();
5499 m_busyPopup = nullptr;
5500 }
5501
5502 auto *mfsr = dynamic_cast<MetadataFactorySingleResult*>(levent);
5503
5504 if (!mfsr || !mfsr->m_result)
5505 return;
5506
5507 QueryComplete(mfsr->m_result);
5508 }
5509 else if (levent->type() == MetadataFactoryNoResult::kEventType)
5510 {
5511 if (m_busyPopup)
5512 {
5513 m_busyPopup->Close();
5514 m_busyPopup = nullptr;
5515 }
5516
5517 auto *mfnr = dynamic_cast<MetadataFactoryNoResult*>(levent);
5518
5519 if (!mfnr)
5520 return;
5521
5522 QString title = tr("No match found for this recording. You can "
5523 "try entering a TVDB/TMDB number, season, and "
5524 "episode manually.");
5525
5526 auto *okPopup = new MythConfirmationDialog(m_popupStack, title, false);
5527
5528 if (okPopup->Create())
5529 m_popupStack->AddScreen(okPopup);
5530 }
5531}
5532
5534
5536{
5537 if (!LoadWindowFromXML("recordings-ui.xml", "iconhelp", this))
5538 return false;
5539
5540 m_iconList = dynamic_cast<MythUIButtonList*>(GetChild("iconlist"));
5541
5542 if (!m_iconList)
5543 {
5544 LOG(VB_GENERAL, LOG_ERR, LOC +
5545 "Window 'iconhelp' is missing required elements.");
5546 return false;
5547 }
5548
5550
5551 addItem("watched", tr("Recording has been watched"));
5552 addItem("commflagged", tr("Commercials are flagged"));
5553 addItem("cutlist", tr("An editing cutlist is present"));
5554 addItem("autoexpire", tr("The program is able to auto-expire"));
5555 addItem("processing", tr("Commercials are being flagged"));
5556 addItem("bookmark", tr("A bookmark is set"));
5557#if 0
5558 addItem("inuse", tr("Recording is in use"));
5559 addItem("transcoded", tr("Recording has been transcoded"));
5560#endif
5561
5562 addItem("mono", tr("Recording is in Mono"));
5563 addItem("stereo", tr("Recording is in Stereo"));
5564 addItem("surround", tr("Recording is in Surround Sound"));
5565 addItem("dolby", tr("Recording is in Dolby Surround Sound"));
5566
5567 addItem("cc", tr("Recording is Closed Captioned"));
5568 addItem("subtitles", tr("Recording has Subtitles Available"));
5569 addItem("onscreensub", tr("Recording is Subtitled"));
5570
5571 addItem("SD", tr("Recording is in Standard Definition"));
5572 addItem("widescreen", tr("Recording is Widescreen"));
5573 addItem("hdtv", tr("Recording is in High Definition"));
5574 addItem("hd720", tr("Recording is in 720p High Definition"));
5575 addItem("hd1080i", tr("Recording is in 1080i High Definition"));
5576 addItem("hd1080p", tr("Recording is in 1080p High Definition"));
5577 addItem("uhd4Ki", tr("Recording is in 4k(interlaced) UHD resolution"));
5578 addItem("uhd4Kp", tr("Recording is in 4k UHD resolution"));
5579 addItem("mpeg2", tr("Recording is using MPEG-2 codec"));
5580 addItem("avchd", tr("Recording is using AVC/H.264 codec"));
5581 addItem("hevc", tr("Recording is using HEVC/H.265 codec"));
5582// addItem("preserved", tr("Recording is preserved"));
5583
5584 return true;
5585}
5586
5587void HelpPopup::addItem(const QString &state, const QString &text)
5588{
5589 auto *item = new MythUIButtonListItem(m_iconList, text);
5590 item->DisplayState(state, "icons");
5591}
5592
5594{
5595 QDateTime now = QDateTime::currentDateTime();
5596 if (!m_lastUpdated.isValid() ||
5597 m_lastUpdated.msecsTo(now) >= kInvalidateTimeMs.count())
5598 {
5599 QMap<int, JobQueueEntry> jobs;
5601 m_jobs.clear();
5602 for (const auto& job : std::as_const(jobs))
5603 {
5604 m_jobs.insert(qMakePair(job.chanid, job.recstartts), job);
5605 }
5606 m_lastUpdated = now;
5607 }
5608}
5609
5611 const QDateTime &recstartts)
5612{
5613 Update();
5614 QList<JobQueueEntry> values = m_jobs.values(qMakePair(chanid, recstartts));
5615 auto end = values.cend();
5616 for (auto iter = values.cbegin(); iter != end; ++iter)
5617 {
5618 if (iter->type == jobType)
5619 return JobQueue::IsJobStatusQueued(iter->status);
5620 }
5621 return false;
5622}
5623
5625 const QDateTime &recstartts)
5626{
5627 Update();
5628 QList<JobQueueEntry> values = m_jobs.values(qMakePair(chanid, recstartts));
5629 auto end = values.cend();
5630 for (auto iter = values.cbegin(); iter != end; ++iter)
5631 {
5632 if (iter->type == jobType)
5633 return JobQueue::IsJobStatusRunning(iter->status);
5634 }
5635 return false;
5636}
5637
5639 const QDateTime &recstartts)
5640{
5641 return IsJobQueued(jobType, chanid, recstartts) ||
5642 IsJobRunning(jobType, chanid, recstartts);
5643}
5644
5645/* 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:929
static int GetJobsInQueue(QMap< int, JobQueueEntry > &jobs, int findJobs=JOB_LIST_NOT_DONE)
Definition: jobqueue.cpp:1292
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:518
static bool IsJobStatusQueued(int status)
Definition: jobqueue.cpp:1085
static bool IsJobQueuedOrRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1108
static bool IsJobStatusRunning(int status)
Definition: jobqueue.cpp:1090
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="")
T GetDurSetting(const QString &key, T defaultval=T::zero())
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
QString GetMasterHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
Basic menu dialog, message and a list of options.
void Closed(QString, int)
bool Create(void) override
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & Message() const
Definition: mythevent.h:65
static const Type kMythEventMessage
Definition: mythevent.h:79
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
void AddItem(const QString &title)
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void LoadInBackground(const QString &message="")
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
Dialog prompting the user to enter a text string.
void haveResult(QString)
bool Create(void) override
void SetFontState(const QString &state, const QString &name="")
void SetProgress1(int start, int total, int used)
void DisplayState(const QString &state, const QString &name)
void SetProgress2(int start, int total, int used)
void SetTextFromMap(const InfoMap &infoMap, const QString &state="")
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
MythUIButtonList * parent() const
QString GetImageFilename(const QString &name="") const
QString GetText(const QString &name="") const
void SetText(const QString &text, const QString &name="", const QString &state="")
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
virtual bool MoveDown(MovementUnit unit=MoveItem, uint amount=0)
void SetLCDTitles(const QString &title, const QString &columnList="")
MythUIButtonListItem * GetItemCurrent() const
void itemVisible(MythUIButtonListItem *item)
void SetItemCurrent(MythUIButtonListItem *item)
void RemoveItem(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
int GetTopItemPos(void) const
int GetItemPos(MythUIButtonListItem *item) const
MythUIButtonListItem * GetItemByData(const QVariant &data)
void itemLoaded(MythUIButtonListItem *item)
void SetSearchFields(const QString &fields)
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemAt(int pos) const
MythUIButtonListItem * GetItemNext(MythUIButtonListItem *item) const
void SetValueByData(const QVariant &data)
void LoadInBackground(int start=0, int pageSize=20)
bool MoveToNamedPosition(const QString &position_name)
void itemSelected(MythUIButtonListItem *item)
A single button widget.
Definition: mythuibutton.h:22
void Clicked()
A checkbox widget supporting three check states - on,off,half and two conditions - selected and unsel...
void SetCheckState(MythUIStateType::StateType state)
void toggled(bool)
virtual void SetTextFromMap(const InfoMap &infoMap)
virtual void ResetMap(const InfoMap &infoMap)
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h: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:348
virtual void SetVisible(bool visible)
void SetEnabled(bool enable)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
void SendResult(void)
MythUIButton * m_okButton
Definition: playbackbox.h: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:10481
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:10497
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
unsigned int uint
Definition: compat.h:60
@ JOB_LIST_ALL
Definition: jobqueue.h:67
@ JOB_USERJOB3
Definition: jobqueue.h:86
@ JOB_METADATA
Definition: jobqueue.h:80
@ JOB_USERJOB1
Definition: jobqueue.h:84
@ JOB_USERJOB2
Definition: jobqueue.h:85
@ JOB_COMMFLAG
Definition: jobqueue.h:79
@ JOB_USERJOB4
Definition: jobqueue.h:87
@ JOB_TRANSCODE
Definition: jobqueue.h:78
@ JOB_STOP
Definition: jobqueue.h:54
@ kLookupSearch
LookupType
@ kProbableTelevision
@ kUnknownVideo
@ kProbableMovie
@ kMetadataRecording
LookupType GuessLookupType(ProgramInfo *pginfo)
VideoArtworkType
@ kArtworkFanart
@ kArtworkBanner
@ kArtworkCoverart
static QMap< QString, QString > iconMap
Definition: musicutils.cpp:33
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
void ShowNotificationError(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
convenience utility to display error message as notification
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
Convenience inline random number generator functions.
static MythThemedMenu * menu
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
static constexpr const char * ACTION_1
Definition: mythuiactions.h:5
@ FilterNone
void run(const QString &name, Class *object, void(Class::*fn)())
Definition: mconcurrent.h:137
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:28
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h: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:31
dictionary info
Definition: azlyrics.py:7
def rating(profile, smoonURL, gate)
Definition: scan.py:36
Definition: pbs.py:1
static bool comp_programid_less_than(const ProgramInfo *a, const ProgramInfo *b)
#define LOC
Definition: playbackbox.cpp:53
static int comp_season_rev(const ProgramInfo *a, const ProgramInfo *b)
static PlaybackBox::ViewMask m_viewMaskToggle(PlaybackBox::ViewMask mask, PlaybackBox::ViewMask toggle)
static bool save_position(const MythUIButtonList *groupList, const MythUIButtonList *recordingList, QStringList &groupSelPref, QStringList &itemSelPref, QStringList &itemTopPref)
static bool comp_season_less_than(const ProgramInfo *a, const ProgramInfo *b)
static const std::array< const uint, 3 > s_artDelay
static int comp_programid(const ProgramInfo *a, const ProgramInfo *b)
Definition: playbackbox.cpp:59
static int comp_recordDate_rev(const ProgramInfo *a, const ProgramInfo *b)
static bool comp_recpriority2_less_than(const ProgramInfo *a, const ProgramInfo *b)
static QString construct_sort_title(QString title, const QString &sortTitle, PlaybackBox::ViewMask viewmask, PlaybackBox::ViewTitleSort sortType, int recpriority)
static void push_onto_del(QStringList &list, const ProgramInfo &pginfo)
static int comp_season(const ProgramInfo *a, const ProgramInfo *b)
static bool extract_one_del(QStringList &list, uint &recordingID)
static bool comp_originalAirDate_less_than(const ProgramInfo *a, const ProgramInfo *b)
static int comp_recordDate(const ProgramInfo *a, const ProgramInfo *b)
static bool comp_programid_rev_less_than(const ProgramInfo *a, const ProgramInfo *b)
static bool comp_originalAirDate_rev_less_than(const ProgramInfo *a, const ProgramInfo *b)
static int comp_originalAirDate(const ProgramInfo *a, const ProgramInfo *b)
Definition: playbackbox.cpp:75
static bool comp_season_rev_less_than(const ProgramInfo *a, const ProgramInfo *b)
static void restore_position(MythUIButtonList *groupList, MythUIButtonList *recordingList, const QStringList &groupSelPref, const QStringList &itemSelPref, const QStringList &itemTopPref)
static bool retrieve_SeasonEpisode(int &season, int &episode, const ProgramInfo *prog)
static const std::array< const std::string, 9 > disp_flags
static bool comp_recordDate_rev_less_than(const ProgramInfo *a, const ProgramInfo *b)
static int comp_programid_rev(const ProgramInfo *a, const ProgramInfo *b)
Definition: playbackbox.cpp:67
static QString extract_subtitle(const ProgramInfo &pginfo, const QString &groupname)
static bool comp_recordDate_less_than(const ProgramInfo *a, const ProgramInfo *b)
static int comp_originalAirDate_rev(const ProgramInfo *a, const ProgramInfo *b)
Definition: playbackbox.cpp:88
static int comp_recpriority2(const ProgramInfo *a, const ProgramInfo *b)
static const std::array< const int, kMaxJobs > kJobs
static QString extract_main_state(const ProgramInfo &pginfo, const TV *player)
static const QString sLocation
Definition: playbackbox.cpp:57
static constexpr uint8_t kArtworkCoverTimeout
Definition: playbackbox.h:59
static constexpr uint8_t kArtworkBannerTimeout
Definition: playbackbox.h:58
static constexpr size_t kMaxJobs
Definition: playbackbox.h:55
static constexpr uint16_t kArtworkFanTimeout
Definition: playbackbox.h:57
CheckAvailabilityType
@ kCheckForPlayAction
@ kCheckForMenuAction
@ kCheckForPlaylistAction
@ kCheckForCache
AutoDeleteDeque< ProgramInfo * > ProgramList
Definition: programinfo.h: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
@ 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