[video] fix selection after changing video or extra art
[xbmc.git] / xbmc / pvr / guilib / PVRGUIActionsTimers.cpp
blobe515a9dd00e9dbf761b9cdd0253c65c7df60e1dd
1 /*
2 * Copyright (C) 2016-2022 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "PVRGUIActionsTimers.h"
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "dialogs/GUIDialogProgress.h"
14 #include "dialogs/GUIDialogSelect.h"
15 #include "dialogs/GUIDialogYesNo.h"
16 #include "guilib/GUIComponent.h"
17 #include "guilib/GUIWindowManager.h"
18 #include "guilib/LocalizeStrings.h"
19 #include "guilib/WindowIDs.h"
20 #include "messaging/helpers/DialogHelper.h"
21 #include "messaging/helpers/DialogOKHelper.h"
22 #include "pvr/PVREventLogJob.h"
23 #include "pvr/PVRItem.h"
24 #include "pvr/PVRManager.h"
25 #include "pvr/PVRPlaybackState.h"
26 #include "pvr/addons/PVRClient.h"
27 #include "pvr/channels/PVRChannel.h"
28 #include "pvr/channels/PVRChannelGroupMember.h"
29 #include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
30 #include "pvr/epg/EpgInfoTag.h"
31 #include "pvr/guilib/PVRGUIActionsChannels.h"
32 #include "pvr/guilib/PVRGUIActionsParentalControl.h"
33 #include "pvr/guilib/PVRGUIActionsPlayback.h"
34 #include "pvr/recordings/PVRRecording.h"
35 #include "pvr/timers/PVRTimerInfoTag.h"
36 #include "pvr/timers/PVRTimers.h"
37 #include "settings/Settings.h"
38 #include "utils/StringUtils.h"
39 #include "utils/SystemInfo.h"
40 #include "utils/Variant.h"
41 #include "utils/log.h"
43 #include <algorithm>
44 #include <map>
45 #include <memory>
46 #include <string>
47 #include <thread>
48 #include <utility>
50 using namespace PVR;
51 using namespace KODI::MESSAGING;
53 CPVRGUIActionsTimers::CPVRGUIActionsTimers()
54 : m_settings({CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME,
55 CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION,
56 CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY,
57 CSettings::SETTING_PVRREMINDERS_AUTORECORD,
58 CSettings::SETTING_PVRREMINDERS_AUTOSWITCH})
62 bool CPVRGUIActionsTimers::ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
64 CGUIDialogPVRTimerSettings* pDlgInfo =
65 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRTimerSettings>(
66 WINDOW_DIALOG_PVR_TIMER_SETTING);
67 if (!pDlgInfo)
69 CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_TIMER_SETTING!");
70 return false;
73 pDlgInfo->SetTimer(timer);
74 pDlgInfo->Open();
76 return pDlgInfo->IsConfirmed();
79 bool CPVRGUIActionsTimers::AddReminder(const CFileItem& item) const
81 const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
82 if (!epgTag)
84 CLog::LogF(LOGERROR, "No epg tag!");
85 return false;
88 if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag))
90 HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
91 CVariant{19034}); // "There is already a timer set for this event"
92 return false;
95 const std::shared_ptr<CPVRTimerInfoTag> newTimer =
96 CPVRTimerInfoTag::CreateReminderFromEpg(epgTag);
97 if (!newTimer)
99 HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
100 CVariant{19094}); // Timer creation failed. Unsupported timer type.
101 return false;
104 return AddTimer(newTimer);
107 bool CPVRGUIActionsTimers::AddTimer(bool bRadio) const
109 const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag(bRadio));
110 if (ShowTimerSettings(newTimer))
112 return AddTimer(newTimer);
114 return false;
117 bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item, bool bShowTimerSettings) const
119 return AddTimer(item, false, bShowTimerSettings, false);
122 bool CPVRGUIActionsTimers::AddTimerRule(const CFileItem& item,
123 bool bShowTimerSettings,
124 bool bFallbackToOneShotTimer) const
126 return AddTimer(item, true, bShowTimerSettings, bFallbackToOneShotTimer);
129 bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item,
130 bool bCreateRule,
131 bool bShowTimerSettings,
132 bool bFallbackToOneShotTimer) const
134 const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
135 if (!channel)
137 CLog::LogF(LOGERROR, "No channel!");
138 return false;
141 if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
142 ParentalCheckResult::SUCCESS)
143 return false;
145 std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
146 if (epgTag)
148 if (epgTag->IsGapTag())
149 epgTag.reset(); // for gap tags, we can only create instant timers
151 else if (bCreateRule)
153 CLog::LogF(LOGERROR, "No epg tag!");
154 return false;
157 std::shared_ptr<CPVRTimerInfoTag> timer(
158 bCreateRule || !epgTag ? nullptr
159 : CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag));
160 std::shared_ptr<CPVRTimerInfoTag> rule(
161 bCreateRule ? CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer) : nullptr);
162 if (timer || rule)
164 HELPERS::ShowOKDialogText(
165 CVariant{19033},
166 CVariant{19034}); // "Information", "There is already a timer set for this event"
167 return false;
170 std::shared_ptr<CPVRTimerInfoTag> newTimer(
171 epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule)
172 : CPVRTimerInfoTag::CreateInstantTimerTag(channel));
173 if (!newTimer)
175 if (bCreateRule && bFallbackToOneShotTimer)
176 newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
178 if (!newTimer)
180 HELPERS::ShowOKDialogText(
181 CVariant{19033}, // "Information"
182 bCreateRule ? CVariant{19095} // Timer rule creation failed. Unsupported timer type.
183 : CVariant{19094}); // Timer creation failed. Unsupported timer type.
184 return false;
188 if (bShowTimerSettings)
190 if (!ShowTimerSettings(newTimer))
191 return false;
194 return AddTimer(newTimer);
197 bool CPVRGUIActionsTimers::AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& item) const
199 if (!item->Channel() && !item->GetTimerType()->IsEpgBasedTimerRule())
201 CLog::LogF(LOGERROR, "No channel given");
202 HELPERS::ShowOKDialogText(
203 CVariant{257},
204 CVariant{
205 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
206 return false;
209 if (!item->IsTimerRule() && item->GetEpgInfoTag() && !item->GetEpgInfoTag()->IsRecordable())
211 HELPERS::ShowOKDialogText(
212 CVariant{19033},
213 CVariant{19189}); // "Information", "The PVR backend does not allow to record this event."
214 return false;
217 if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(
218 item->Channel()) != ParentalCheckResult::SUCCESS)
219 return false;
221 if (!CServiceBroker::GetPVRManager().Timers()->AddTimer(item))
223 HELPERS::ShowOKDialogText(
224 CVariant{257},
225 CVariant{
226 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
227 return false;
230 return true;
233 namespace
235 enum PVRRECORD_INSTANTRECORDACTION
237 NONE = -1,
238 RECORD_CURRENT_SHOW = 0,
239 RECORD_INSTANTRECORDTIME = 1,
240 ASK = 2,
241 RECORD_30_MINUTES = 3,
242 RECORD_60_MINUTES = 4,
243 RECORD_120_MINUTES = 5,
244 RECORD_NEXT_SHOW = 6
247 class InstantRecordingActionSelector
249 public:
250 explicit InstantRecordingActionSelector(int iInstantRecordTime);
251 virtual ~InstantRecordingActionSelector() = default;
253 void AddAction(PVRRECORD_INSTANTRECORDACTION eAction, const std::string& title);
254 void PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction);
255 PVRRECORD_INSTANTRECORDACTION Select();
257 private:
258 int m_iInstantRecordTime;
259 CGUIDialogSelect* m_pDlgSelect; // not owner!
260 std::map<PVRRECORD_INSTANTRECORDACTION, int> m_actions;
263 InstantRecordingActionSelector::InstantRecordingActionSelector(int iInstantRecordTime)
264 : m_iInstantRecordTime(iInstantRecordTime),
265 m_pDlgSelect(CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
266 WINDOW_DIALOG_SELECT))
268 if (m_pDlgSelect)
270 m_pDlgSelect->Reset();
271 m_pDlgSelect->SetMultiSelection(false);
272 m_pDlgSelect->SetHeading(CVariant{19086}); // Instant recording action
274 else
276 CLog::LogF(LOGERROR, "Unable to obtain WINDOW_DIALOG_SELECT instance");
280 void InstantRecordingActionSelector::AddAction(PVRRECORD_INSTANTRECORDACTION eAction,
281 const std::string& title)
283 if (m_actions.find(eAction) == m_actions.end())
285 switch (eAction)
287 case RECORD_INSTANTRECORDTIME:
288 m_pDlgSelect->Add(
289 StringUtils::Format(g_localizeStrings.Get(19090),
290 m_iInstantRecordTime)); // Record next <default duration> minutes
291 break;
292 case RECORD_30_MINUTES:
293 m_pDlgSelect->Add(
294 StringUtils::Format(g_localizeStrings.Get(19090), 30)); // Record next 30 minutes
295 break;
296 case RECORD_60_MINUTES:
297 m_pDlgSelect->Add(
298 StringUtils::Format(g_localizeStrings.Get(19090), 60)); // Record next 60 minutes
299 break;
300 case RECORD_120_MINUTES:
301 m_pDlgSelect->Add(
302 StringUtils::Format(g_localizeStrings.Get(19090), 120)); // Record next 120 minutes
303 break;
304 case RECORD_CURRENT_SHOW:
305 m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19091),
306 title)); // Record current show (<title>)
307 break;
308 case RECORD_NEXT_SHOW:
309 m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19092),
310 title)); // Record next show (<title>)
311 break;
312 case NONE:
313 case ASK:
314 default:
315 return;
318 m_actions.insert(std::make_pair(eAction, static_cast<int>(m_actions.size())));
322 void InstantRecordingActionSelector::PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction)
324 const auto& it = m_actions.find(eAction);
325 if (it != m_actions.end())
326 m_pDlgSelect->SetSelected(it->second);
329 PVRRECORD_INSTANTRECORDACTION InstantRecordingActionSelector::Select()
331 PVRRECORD_INSTANTRECORDACTION eAction = NONE;
333 m_pDlgSelect->Open();
335 if (m_pDlgSelect->IsConfirmed())
337 int iSelection = m_pDlgSelect->GetSelectedItem();
338 const auto it =
339 std::find_if(m_actions.cbegin(), m_actions.cend(),
340 [iSelection](const auto& action) { return action.second == iSelection; });
342 if (it != m_actions.cend())
343 eAction = (*it).first;
346 return eAction;
349 } // unnamed namespace
351 bool CPVRGUIActionsTimers::ToggleRecordingOnPlayingChannel()
353 const std::shared_ptr<CPVRChannel> channel =
354 CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
355 if (channel && channel->CanRecord())
356 return SetRecordingOnChannel(
357 channel, !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel));
359 return false;
362 bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptr<CPVRChannel>& channel,
363 bool bOnOff)
365 bool bReturn = false;
367 if (!channel)
368 return bReturn;
370 if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
371 ParentalCheckResult::SUCCESS)
372 return bReturn;
374 const std::shared_ptr<const CPVRClient> client =
375 CServiceBroker::GetPVRManager().GetClient(channel->ClientID());
376 if (client && client->GetClientCapabilities().SupportsTimers())
378 /* timers are supported on this channel */
379 if (bOnOff && !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
381 std::shared_ptr<CPVREpgInfoTag> epgTag;
382 int iDuration = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
384 int iAction = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION);
385 switch (iAction)
387 case RECORD_CURRENT_SHOW:
388 epgTag = channel->GetEPGNow();
389 break;
391 case RECORD_INSTANTRECORDTIME:
392 epgTag.reset();
393 break;
395 case ASK:
397 PVRRECORD_INSTANTRECORDACTION ePreselect = RECORD_INSTANTRECORDTIME;
398 const int iDurationDefault =
399 m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
400 InstantRecordingActionSelector selector(iDurationDefault);
401 std::shared_ptr<CPVREpgInfoTag> epgTagNext;
403 // fixed length recordings
404 selector.AddAction(RECORD_30_MINUTES, "");
405 selector.AddAction(RECORD_60_MINUTES, "");
406 selector.AddAction(RECORD_120_MINUTES, "");
408 if (iDurationDefault != 30 && iDurationDefault != 60 && iDurationDefault != 120)
409 selector.AddAction(RECORD_INSTANTRECORDTIME, "");
411 // epg-based recordings
412 epgTag = channel->GetEPGNow();
413 if (epgTag)
415 bool bLocked = CServiceBroker::GetPVRManager().IsParentalLocked(epgTag);
417 // "now"
418 const std::string currentTitle =
419 bLocked ? g_localizeStrings.Get(19266) /* Parental locked */ : epgTag->Title();
420 selector.AddAction(RECORD_CURRENT_SHOW, currentTitle);
421 ePreselect = RECORD_CURRENT_SHOW;
423 // "next"
424 epgTagNext = channel->GetEPGNext();
425 if (epgTagNext)
427 const std::string nextTitle = bLocked
428 ? g_localizeStrings.Get(19266) /* Parental locked */
429 : epgTagNext->Title();
430 selector.AddAction(RECORD_NEXT_SHOW, nextTitle);
432 // be smart. if current show is almost over, preselect next show.
433 if (epgTag->ProgressPercentage() > 90.0f)
434 ePreselect = RECORD_NEXT_SHOW;
438 if (ePreselect == RECORD_INSTANTRECORDTIME)
440 if (iDurationDefault == 30)
441 ePreselect = RECORD_30_MINUTES;
442 else if (iDurationDefault == 60)
443 ePreselect = RECORD_60_MINUTES;
444 else if (iDurationDefault == 120)
445 ePreselect = RECORD_120_MINUTES;
448 selector.PreSelectAction(ePreselect);
450 PVRRECORD_INSTANTRECORDACTION eSelected = selector.Select();
451 switch (eSelected)
453 case NONE:
454 return false; // dialog canceled
456 case RECORD_30_MINUTES:
457 iDuration = 30;
458 epgTag.reset();
459 break;
461 case RECORD_60_MINUTES:
462 iDuration = 60;
463 epgTag.reset();
464 break;
466 case RECORD_120_MINUTES:
467 iDuration = 120;
468 epgTag.reset();
469 break;
471 case RECORD_INSTANTRECORDTIME:
472 iDuration = iDurationDefault;
473 epgTag.reset();
474 break;
476 case RECORD_CURRENT_SHOW:
477 break;
479 case RECORD_NEXT_SHOW:
480 epgTag = epgTagNext;
481 break;
483 default:
484 CLog::LogF(LOGERROR,
485 "Unknown instant record action selection ({}), defaulting to fixed "
486 "length recording.",
487 static_cast<int>(eSelected));
488 epgTag.reset();
489 break;
491 break;
494 default:
495 CLog::LogF(LOGERROR,
496 "Unknown instant record action setting value ({}), defaulting to fixed "
497 "length recording.",
498 iAction);
499 break;
502 const std::shared_ptr<CPVRTimerInfoTag> newTimer(
503 epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, false)
504 : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration));
506 if (newTimer)
507 bReturn = CServiceBroker::GetPVRManager().Timers()->AddTimer(newTimer);
509 if (!bReturn)
510 HELPERS::ShowOKDialogText(
511 CVariant{257},
512 CVariant{
513 19164}); // "Error", "Could not start recording. Check the log for more information about this message."
515 else if (!bOnOff && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
517 /* delete active timers */
518 bReturn =
519 CServiceBroker::GetPVRManager().Timers()->DeleteTimersOnChannel(channel, true, true);
521 if (!bReturn)
522 HELPERS::ShowOKDialogText(
523 CVariant{257},
524 CVariant{
525 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
529 return bReturn;
532 bool CPVRGUIActionsTimers::ToggleTimer(const CFileItem& item) const
534 if (!item.HasEPGInfoTag())
535 return false;
537 const std::shared_ptr<const CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
538 if (timer)
540 if (timer->IsRecording())
541 return StopRecording(item);
542 else
543 return DeleteTimer(item);
545 else
546 return AddTimer(item, false);
549 bool CPVRGUIActionsTimers::ToggleTimerState(const CFileItem& item) const
551 if (!item.HasPVRTimerInfoTag())
552 return false;
554 const std::shared_ptr<CPVRTimerInfoTag> timer = item.GetPVRTimerInfoTag();
555 if (timer->IsDisabled())
556 timer->SetState(PVR_TIMER_STATE_SCHEDULED);
557 else
558 timer->SetState(PVR_TIMER_STATE_DISABLED);
560 if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(timer))
561 return true;
563 HELPERS::ShowOKDialogText(
564 CVariant{257},
565 CVariant{
566 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
567 return false;
570 bool CPVRGUIActionsTimers::EditTimer(const CFileItem& item) const
572 const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
573 if (!timer)
575 CLog::LogF(LOGERROR, "No timer!");
576 return false;
579 // clone the timer.
580 const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag);
581 newTimer->UpdateEntry(timer);
583 if (ShowTimerSettings(newTimer) &&
584 (!timer->GetTimerType()->IsReadOnly() || timer->GetTimerType()->SupportsEnableDisable()))
586 if (newTimer->GetTimerType() == timer->GetTimerType())
588 if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(newTimer))
589 return true;
591 HELPERS::ShowOKDialogText(
592 CVariant{257},
593 CVariant{
594 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
595 return false;
597 else
599 // timer type changed. delete the original timer, then create the new timer. this order is
600 // important. for instance, the new timer might be a rule which schedules the original timer.
601 // deleting the original timer after creating the rule would do literally this and we would
602 // end up with one timer missing wrt to the rule defined by the new timer.
603 if (DeleteTimer(timer, timer->IsRecording(), false))
605 if (AddTimer(newTimer))
606 return true;
608 // rollback.
609 return AddTimer(timer);
613 return false;
616 bool CPVRGUIActionsTimers::EditTimerRule(const CFileItem& item) const
618 const std::shared_ptr<CFileItem> parentTimer = GetTimerRule(item);
619 if (parentTimer)
620 return EditTimer(*parentTimer);
622 return false;
625 std::shared_ptr<CFileItem> CPVRGUIActionsTimers::GetTimerRule(const CFileItem& item) const
627 std::shared_ptr<CPVRTimerInfoTag> timer;
628 if (item.HasEPGInfoTag())
629 timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item.GetEPGInfoTag());
630 else if (item.HasPVRTimerInfoTag())
631 timer = item.GetPVRTimerInfoTag();
633 if (timer)
635 timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
636 if (timer)
637 return std::make_shared<CFileItem>(timer);
639 return {};
642 bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item) const
644 return DeleteTimer(item, false, false);
647 bool CPVRGUIActionsTimers::DeleteTimerRule(const CFileItem& item) const
649 return DeleteTimer(item, false, true);
652 bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item,
653 bool bIsRecording,
654 bool bDeleteRule) const
656 std::shared_ptr<CPVRTimerInfoTag> timer;
657 const std::shared_ptr<const CPVRRecording> recording(CPVRItem(item).GetRecording());
658 if (recording)
659 timer = recording->GetRecordingTimer();
661 if (!timer)
662 timer = CPVRItem(item).GetTimerInfoTag();
664 if (!timer)
666 CLog::LogF(LOGERROR, "No timer!");
667 return false;
670 if (bDeleteRule && !timer->IsTimerRule())
671 timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
673 if (!timer)
675 CLog::LogF(LOGERROR, "No timer rule!");
676 return false;
679 if (bIsRecording)
681 if (ConfirmStopRecording(timer))
683 if (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, true, false) ==
684 TimerOperationResult::OK)
685 return true;
687 HELPERS::ShowOKDialogText(
688 CVariant{257},
689 CVariant{
690 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
691 return false;
694 else if (!timer->GetTimerType()->AllowsDelete())
696 return false;
698 else
700 bool bAlsoDeleteRule(false);
701 if (ConfirmDeleteTimer(timer, bAlsoDeleteRule))
702 return DeleteTimer(timer, false, bAlsoDeleteRule);
704 return false;
707 bool CPVRGUIActionsTimers::DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
708 bool bIsRecording,
709 bool bDeleteRule) const
711 TimerOperationResult result =
712 CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, bIsRecording, bDeleteRule);
713 switch (result)
715 case TimerOperationResult::RECORDING:
717 // recording running. ask the user if it should be deleted anyway
718 if (HELPERS::ShowYesNoDialogText(
719 CVariant{122}, // "Confirm delete"
720 CVariant{
721 19122}) // "This timer is still recording. Are you sure you want to delete this timer?"
722 != HELPERS::DialogResponse::CHOICE_YES)
723 return false;
725 return DeleteTimer(timer, true, bDeleteRule);
727 case TimerOperationResult::OK:
729 return true;
731 case TimerOperationResult::FAILED:
733 HELPERS::ShowOKDialogText(
734 CVariant{257},
735 CVariant{
736 19110}); // "Error", "Could not delete the timer. Check the log for more information about this message."
737 return false;
739 default:
741 CLog::LogF(LOGERROR, "Unhandled TimerOperationResult ({})!", static_cast<int>(result));
742 break;
745 return false;
748 bool CPVRGUIActionsTimers::ConfirmDeleteTimer(const std::shared_ptr<const CPVRTimerInfoTag>& timer,
749 bool& bDeleteRule) const
751 bool bConfirmed(false);
752 const std::shared_ptr<const CPVRTimerInfoTag> parentTimer(
753 CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer));
755 if (parentTimer && parentTimer->GetTimerType()->AllowsDelete())
757 // timer was scheduled by a deletable timer rule. prompt user for confirmation for deleting the timer rule, including scheduled timers.
758 bool bCancel(false);
759 bDeleteRule = CGUIDialogYesNo::ShowAndGetInput(
760 CVariant{122}, // "Confirm delete"
761 CVariant{
762 840}, // "Do you want to delete only this timer or also the timer rule that has scheduled it?"
763 CVariant{""}, CVariant{timer->Title()}, bCancel, CVariant{841}, // "Only this"
764 CVariant{593}, // "All"
765 0); // no autoclose
766 bConfirmed = !bCancel;
768 else
770 bDeleteRule = false;
772 // prompt user for confirmation for deleting the timer
773 bConfirmed = CGUIDialogYesNo::ShowAndGetInput(
774 CVariant{122}, // "Confirm delete"
775 timer->IsTimerRule()
776 ? CVariant{845}
777 // "Are you sure you want to delete this timer rule and all timers it has scheduled?"
778 : CVariant{846}, // "Are you sure you want to delete this timer?"
779 CVariant{""}, CVariant{timer->Title()});
782 return bConfirmed;
785 bool CPVRGUIActionsTimers::StopRecording(const CFileItem& item) const
787 if (!DeleteTimer(item, true, false))
788 return false;
790 CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
791 return true;
794 bool CPVRGUIActionsTimers::ConfirmStopRecording(
795 const std::shared_ptr<const CPVRTimerInfoTag>& timer) const
797 return CGUIDialogYesNo::ShowAndGetInput(
798 CVariant{847}, // "Confirm stop recording"
799 CVariant{848}, // "Are you sure you want to stop this recording?"
800 CVariant{""}, CVariant{timer->Title()});
803 namespace
805 std::string GetAnnouncerText(const std::shared_ptr<const CPVRTimerInfoTag>& timer,
806 int idEpg,
807 int idNoEpg)
809 std::string text;
810 if (timer->IsEpgBased())
812 text = StringUtils::Format(g_localizeStrings.Get(idEpg),
813 timer->Title(), // tv show title
814 timer->ChannelName(),
815 timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
817 else
819 text = StringUtils::Format(g_localizeStrings.Get(idNoEpg), timer->ChannelName(),
820 timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
822 return text;
825 void AddEventLogEntry(const std::shared_ptr<const CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
827 std::string name;
828 std::string icon;
830 const std::shared_ptr<const CPVRClient> client =
831 CServiceBroker::GetPVRManager().GetClient(timer->GetTimerType()->GetClientId());
832 if (client)
834 name = client->GetFriendlyName();
835 icon = client->Icon();
837 else
839 name = g_sysinfo.GetAppName();
840 icon = "special://xbmc/media/icon256x256.png";
843 CPVREventLogJob* job = new CPVREventLogJob;
844 job->AddEvent(false, // do not display a toast, only log event
845 EventLevel::Information, // info, no error
846 name, GetAnnouncerText(timer, idEpg, idNoEpg), icon);
847 CServiceBroker::GetJobManager()->AddJob(job, nullptr);
849 } // unnamed namespace
851 void CPVRGUIActionsTimers::AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
853 if (!timer->IsReminder())
855 CLog::LogF(LOGERROR, "No reminder timer!");
856 return;
859 if (timer->EndAsUTC() < CDateTime::GetUTCDateTime())
861 // expired. timer end is in the past. write event log entry.
862 AddEventLogEntry(timer, 19305, 19306); // Deleted missed PVR reminder ...
863 return;
866 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(timer->Channel()))
868 // no need for an announcement. channel in question is already playing.
869 return;
872 // show the reminder dialog
873 CGUIDialogProgress* dialog =
874 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
875 WINDOW_DIALOG_PROGRESS);
876 if (!dialog)
877 return;
879 dialog->Reset();
881 dialog->SetHeading(CVariant{19312}); // "PVR reminder"
882 dialog->ShowChoice(0, CVariant{19165}); // "Switch"
884 std::string text = GetAnnouncerText(timer, 19307, 19308); // Reminder for ...
886 bool bCanRecord = false;
887 const std::shared_ptr<const CPVRClient> client =
888 CServiceBroker::GetPVRManager().GetClient(timer->ClientID());
889 if (client && client->GetClientCapabilities().SupportsTimers())
891 bCanRecord = true;
892 dialog->ShowChoice(1, CVariant{264}); // "Record"
893 dialog->ShowChoice(2, CVariant{222}); // "Cancel"
895 if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD))
896 text += "\n\n" + g_localizeStrings.Get(
897 19309); // (Auto-close of this reminder will schedule a recording...)
898 else if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH))
899 text += "\n\n" + g_localizeStrings.Get(
900 19331); // (Auto-close of this reminder will switch to channel...)
902 else
904 dialog->ShowChoice(1, CVariant{222}); // "Cancel"
907 dialog->SetText(text);
908 dialog->SetPercentage(100);
910 dialog->Open();
912 int result = CGUIDialogProgress::CHOICE_NONE;
914 static constexpr int PROGRESS_TIMESLICE_MILLISECS = 50;
916 const int iWait = m_settings.GetIntValue(CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY) * 1000;
917 int iRemaining = iWait;
918 while (iRemaining > 0)
920 result = dialog->GetChoice();
921 if (result != CGUIDialogProgress::CHOICE_NONE)
922 break;
924 std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_TIMESLICE_MILLISECS));
926 iRemaining -= PROGRESS_TIMESLICE_MILLISECS;
927 dialog->SetPercentage(iRemaining * 100 / iWait);
928 dialog->Progress();
931 dialog->Close();
933 bool bAutoClosed = (iRemaining <= 0);
934 bool bSwitch = (result == 0);
935 bool bRecord = (result == 1);
937 if (bAutoClosed)
939 bRecord = (bCanRecord && m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD));
940 bSwitch = m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH);
943 if (bRecord)
945 std::shared_ptr<CPVRTimerInfoTag> newTimer;
947 std::shared_ptr<CPVREpgInfoTag> epgTag = timer->GetEpgInfoTag();
948 if (epgTag)
950 newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
951 if (newTimer)
953 // an epgtag can only have max one timer - we need to clear the reminder to be able to
954 // attach the recording timer
955 DeleteTimer(timer, false, false);
958 else
960 int iDuration = (timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal() / 60;
961 newTimer = CPVRTimerInfoTag::CreateTimerTag(timer->Channel(), timer->StartAsUTC(), iDuration);
964 if (newTimer)
966 // schedule recording
967 AddTimer(CFileItem(newTimer), false);
970 if (bAutoClosed)
972 AddEventLogEntry(timer, 19310,
973 19311); // Scheduled recording for auto-closed PVR reminder ...
977 if (bSwitch)
979 const std::shared_ptr<CPVRChannelGroupMember> groupMember =
980 CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(
981 timer->Channel());
982 if (groupMember)
984 CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
985 CFileItem(groupMember), false);
987 if (bAutoClosed)
989 AddEventLogEntry(timer, 19332,
990 19333); // Switched channel for auto-closed PVR reminder ...
996 void CPVRGUIActionsTimers::AnnounceReminders() const
998 // Prevent multiple yesno dialogs, all on same call stack, due to gui message processing while dialog is open.
999 if (m_bReminderAnnouncementRunning)
1000 return;
1002 m_bReminderAnnouncementRunning = true;
1003 std::shared_ptr<CPVRTimerInfoTag> timer =
1004 CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
1005 while (timer)
1007 AnnounceReminder(timer);
1008 timer = CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
1010 m_bReminderAnnouncementRunning = false;