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