1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIDialogPVRTimerSettings.h"
10 
11 #include "ServiceBroker.h"
12 #include "dialogs/GUIDialogNumeric.h"
13 #include "guilib/GUIMessage.h"
14 #include "guilib/LocalizeStrings.h"
15 #include "messaging/helpers/DialogOKHelper.h"
16 #include "pvr/PVRManager.h"
17 #include "pvr/addons/PVRClient.h"
18 #include "pvr/addons/PVRClients.h"
19 #include "pvr/channels/PVRChannel.h"
20 #include "pvr/channels/PVRChannelGroup.h"
21 #include "pvr/channels/PVRChannelGroupsContainer.h"
22 #include "pvr/epg/EpgInfoTag.h"
23 #include "pvr/timers/PVRTimerInfoTag.h"
24 #include "pvr/timers/PVRTimerType.h"
25 #include "settings/SettingUtils.h"
26 #include "settings/dialogs/GUIDialogSettingsBase.h"
27 #include "settings/lib/Setting.h"
28 #include "settings/lib/SettingsManager.h"
29 #include "settings/windows/GUIControlSettings.h"
30 #include "utils/StringUtils.h"
31 #include "utils/Variant.h"
32 #include "utils/log.h"
33 
34 #include <memory>
35 #include <string>
36 #include <utility>
37 #include <vector>
38 
39 using namespace PVR;
40 using namespace KODI::MESSAGING;
41 
42 #define SETTING_TMR_TYPE          "timer.type"
43 #define SETTING_TMR_ACTIVE        "timer.active"
44 #define SETTING_TMR_NAME          "timer.name"
45 #define SETTING_TMR_EPGSEARCH     "timer.epgsearch"
46 #define SETTING_TMR_FULLTEXT      "timer.fulltext"
47 #define SETTING_TMR_CHANNEL       "timer.channel"
48 #define SETTING_TMR_START_ANYTIME "timer.startanytime"
49 #define SETTING_TMR_END_ANYTIME   "timer.endanytime"
50 #define SETTING_TMR_START_DAY     "timer.startday"
51 #define SETTING_TMR_END_DAY       "timer.endday"
52 #define SETTING_TMR_BEGIN         "timer.begin"
53 #define SETTING_TMR_END           "timer.end"
54 #define SETTING_TMR_WEEKDAYS      "timer.weekdays"
55 #define SETTING_TMR_FIRST_DAY     "timer.firstday"
56 #define SETTING_TMR_NEW_EPISODES  "timer.newepisodes"
57 #define SETTING_TMR_BEGIN_PRE     "timer.startmargin"
58 #define SETTING_TMR_END_POST      "timer.endmargin"
59 #define SETTING_TMR_PRIORITY      "timer.priority"
60 #define SETTING_TMR_LIFETIME      "timer.lifetime"
61 #define SETTING_TMR_MAX_REC       "timer.maxrecordings"
62 #define SETTING_TMR_DIR           "timer.directory"
63 #define SETTING_TMR_REC_GROUP     "timer.recgroup"
64 
65 #define TYPE_DEP_VISIBI_COND_ID_POSTFIX     "visibi.typedep"
66 #define TYPE_DEP_ENABLE_COND_ID_POSTFIX     "enable.typedep"
67 #define CHANNEL_DEP_VISIBI_COND_ID_POSTFIX  "visibi.channeldep"
68 #define START_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX  "visibi.startanytimedep"
69 #define END_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX    "visibi.endanytimedep"
70 
CGUIDialogPVRTimerSettings()71 CGUIDialogPVRTimerSettings::CGUIDialogPVRTimerSettings() :
72   CGUIDialogSettingsManualBase(WINDOW_DIALOG_PVR_TIMER_SETTING, "DialogSettings.xml"),
73   m_iWeekdays(PVR_WEEKDAY_NONE)
74 {
75   m_loadType = LOAD_EVERY_TIME;
76 }
77 
78 CGUIDialogPVRTimerSettings::~CGUIDialogPVRTimerSettings() = default;
79 
CanBeActivated() const80 bool CGUIDialogPVRTimerSettings::CanBeActivated() const
81 {
82   if (!m_timerInfoTag)
83   {
84     CLog::LogF(LOGERROR, "No timer info tag");
85     return false;
86   }
87   return true;
88 }
89 
SetTimer(const std::shared_ptr<CPVRTimerInfoTag> & timer)90 void CGUIDialogPVRTimerSettings::SetTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer)
91 {
92   if (!timer)
93   {
94     CLog::LogF(LOGERROR, "No timer given");
95     return;
96   }
97 
98   m_timerInfoTag = timer;
99 
100   // Copy data we need from tag. Do not modify the tag itself until Save()!
101   m_timerType = m_timerInfoTag->GetTimerType();
102   m_bIsRadio = m_timerInfoTag->m_bIsRadio;
103   m_bIsNewTimer = m_timerInfoTag->m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX;
104   m_bTimerActive = m_bIsNewTimer || !m_timerType->SupportsEnableDisable() || !(m_timerInfoTag->m_state == PVR_TIMER_STATE_DISABLED);
105   m_bStartAnyTime = m_bIsNewTimer || !m_timerType->SupportsStartAnyTime() || m_timerInfoTag->m_bStartAnyTime;
106   m_bEndAnyTime = m_bIsNewTimer || !m_timerType->SupportsEndAnyTime() || m_timerInfoTag->m_bEndAnyTime;
107   m_strTitle = m_timerInfoTag->m_strTitle;
108 
109   m_startLocalTime = m_timerInfoTag->StartAsLocalTime();
110   m_endLocalTime = m_timerInfoTag->EndAsLocalTime();
111 
112   m_timerStartTimeStr = m_startLocalTime.GetAsLocalizedTime("", false);
113   m_timerEndTimeStr = m_endLocalTime.GetAsLocalizedTime("", false);
114   m_firstDayLocalTime = m_timerInfoTag->FirstDayAsLocalTime();
115 
116   m_strEpgSearchString = m_timerInfoTag->m_strEpgSearchString;
117   if ((m_bIsNewTimer || !m_timerType->SupportsEpgTitleMatch()) && m_strEpgSearchString.empty())
118     m_strEpgSearchString = m_strTitle;
119 
120   m_bFullTextEpgSearch = m_timerInfoTag->m_bFullTextEpgSearch;
121 
122   m_iWeekdays = m_timerInfoTag->m_iWeekdays;
123   if ((m_bIsNewTimer || !m_timerType->SupportsWeekdays()) && m_iWeekdays == PVR_WEEKDAY_NONE)
124     m_iWeekdays = PVR_WEEKDAY_ALLDAYS;
125 
126   m_iPreventDupEpisodes = m_timerInfoTag->m_iPreventDupEpisodes;
127   m_iMarginStart = m_timerInfoTag->m_iMarginStart;
128   m_iMarginEnd = m_timerInfoTag->m_iMarginEnd;
129   m_iPriority = m_timerInfoTag->m_iPriority;
130   m_iLifetime = m_timerInfoTag->m_iLifetime;
131   m_iMaxRecordings = m_timerInfoTag->m_iMaxRecordings;
132 
133   if (m_bIsNewTimer && m_timerInfoTag->m_strDirectory.empty() && m_timerType->SupportsRecordingFolders())
134     m_strDirectory = m_strTitle;
135   else
136     m_strDirectory = m_timerInfoTag->m_strDirectory;
137 
138   m_iRecordingGroup = m_timerInfoTag->m_iRecordingGroup;
139 
140   InitializeChannelsList();
141   InitializeTypesList();
142 
143   // Channel
144   m_channel = ChannelDescriptor();
145 
146   if (m_timerInfoTag->m_iClientChannelUid == PVR_CHANNEL_INVALID_UID)
147   {
148     bool bChannelSet(false);
149     if (m_timerType->SupportsAnyChannel())
150     {
151       // Select first matching "Any channel" entry.
152       for (const auto& channel : m_channelEntries)
153       {
154         if (channel.second.channelUid == PVR_CHANNEL_INVALID_UID &&
155             channel.second.clientId == m_timerInfoTag->m_iClientId)
156         {
157           m_channel = channel.second;
158           bChannelSet = true;
159         }
160       }
161     }
162     else if (m_bIsNewTimer)
163     {
164       // Select first matching regular (not "Any channel") entry.
165       for (const auto& channel : m_channelEntries)
166       {
167         if (channel.second.channelUid != PVR_CHANNEL_INVALID_UID &&
168             channel.second.clientId == m_timerInfoTag->m_iClientId)
169         {
170           m_channel = channel.second;
171           bChannelSet = true;
172           break;
173         }
174       }
175     }
176 
177     if (!bChannelSet)
178       CLog::LogF(LOGERROR, "Unable to map PVR_CHANNEL_INVALID_UID to channel entry!");
179   }
180   else
181   {
182     // Find matching channel entry
183     bool bChannelSet(false);
184     for (const auto& channel : m_channelEntries)
185     {
186       if ((channel.second.channelUid == m_timerInfoTag->m_iClientChannelUid) &&
187           (channel.second.clientId == m_timerInfoTag->m_iClientId))
188       {
189         m_channel = channel.second;
190         bChannelSet = true;
191         break;
192       }
193     }
194 
195     if (!bChannelSet)
196       CLog::LogF(LOGERROR, "Unable to map channel uid to channel entry!");
197   }
198 }
199 
SetupView()200 void CGUIDialogPVRTimerSettings::SetupView()
201 {
202   CGUIDialogSettingsManualBase::SetupView();
203   SetHeading(19065);
204   SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON);
205   SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186);
206   SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222);
207   SetButtonLabels();
208 }
209 
InitializeSettings()210 void CGUIDialogPVRTimerSettings::InitializeSettings()
211 {
212   CGUIDialogSettingsManualBase::InitializeSettings();
213 
214   const std::shared_ptr<CSettingCategory> category = AddCategory("pvrtimersettings", -1);
215   if (category == NULL)
216   {
217     CLog::LogF(LOGERROR, "Unable to add settings category");
218     return;
219   }
220 
221   const std::shared_ptr<CSettingGroup> group = AddGroup(category);
222   if (group == NULL)
223   {
224     CLog::LogF(LOGERROR, "Unable to add settings group");
225     return;
226   }
227 
228   std::shared_ptr<CSetting> setting = NULL;
229 
230   // Timer type
231   setting = AddList(group, SETTING_TMR_TYPE, 803, SettingLevel::Basic, 0, TypesFiller, 803);
232   AddTypeDependentEnableCondition(setting, SETTING_TMR_TYPE);
233 
234   // Timer enabled/disabled
235   setting = AddToggle(group, SETTING_TMR_ACTIVE, 305, SettingLevel::Basic, m_bTimerActive);
236   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_ACTIVE);
237   AddTypeDependentEnableCondition(setting, SETTING_TMR_ACTIVE);
238 
239   // Name
240   setting = AddEdit(group, SETTING_TMR_NAME, 19075, SettingLevel::Basic, m_strTitle, true, false, 19097);
241   AddTypeDependentEnableCondition(setting, SETTING_TMR_NAME);
242 
243   // epg search string (only for epg-based timer rules)
244   setting = AddEdit(group, SETTING_TMR_EPGSEARCH, 804, SettingLevel::Basic, m_strEpgSearchString, true, false, 805);
245   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_EPGSEARCH);
246   AddTypeDependentEnableCondition(setting, SETTING_TMR_EPGSEARCH);
247 
248   // epg fulltext search (only for epg-based timer rules)
249   setting = AddToggle(group, SETTING_TMR_FULLTEXT, 806, SettingLevel::Basic, m_bFullTextEpgSearch);
250   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_FULLTEXT);
251   AddTypeDependentEnableCondition(setting, SETTING_TMR_FULLTEXT);
252 
253   // Channel
254   setting = AddList(group, SETTING_TMR_CHANNEL, 19078, SettingLevel::Basic, 0, ChannelsFiller, 19078);
255   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_CHANNEL);
256   AddTypeDependentEnableCondition(setting, SETTING_TMR_CHANNEL);
257 
258   // Days of week (only for timer rules)
259   std::vector<int> weekdaysPreselect;
260   if (m_iWeekdays & PVR_WEEKDAY_MONDAY)
261     weekdaysPreselect.push_back(PVR_WEEKDAY_MONDAY);
262   if (m_iWeekdays & PVR_WEEKDAY_TUESDAY)
263     weekdaysPreselect.push_back(PVR_WEEKDAY_TUESDAY);
264   if (m_iWeekdays & PVR_WEEKDAY_WEDNESDAY)
265     weekdaysPreselect.push_back(PVR_WEEKDAY_WEDNESDAY);
266   if (m_iWeekdays & PVR_WEEKDAY_THURSDAY)
267     weekdaysPreselect.push_back(PVR_WEEKDAY_THURSDAY);
268   if (m_iWeekdays & PVR_WEEKDAY_FRIDAY)
269     weekdaysPreselect.push_back(PVR_WEEKDAY_FRIDAY);
270   if (m_iWeekdays & PVR_WEEKDAY_SATURDAY)
271     weekdaysPreselect.push_back(PVR_WEEKDAY_SATURDAY);
272   if (m_iWeekdays & PVR_WEEKDAY_SUNDAY)
273     weekdaysPreselect.push_back(PVR_WEEKDAY_SUNDAY);
274 
275   setting = AddList(group, SETTING_TMR_WEEKDAYS, 19079, SettingLevel::Basic, weekdaysPreselect, WeekdaysFiller, 19079, 1, -1, true, -1, WeekdaysValueFormatter);
276   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_WEEKDAYS);
277   AddTypeDependentEnableCondition(setting, SETTING_TMR_WEEKDAYS);
278 
279   // "Start any time" (only for timer rules)
280   setting = AddToggle(group, SETTING_TMR_START_ANYTIME, 810, SettingLevel::Basic, m_bStartAnyTime);
281   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_START_ANYTIME);
282   AddTypeDependentEnableCondition(setting, SETTING_TMR_START_ANYTIME);
283 
284   // Start day (day + month + year only, no hours, minutes)
285   setting = AddSpinner(group, SETTING_TMR_START_DAY, 19128, SettingLevel::Basic, GetDateAsIndex(m_startLocalTime), DaysFiller);
286   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_START_DAY);
287   AddTypeDependentEnableCondition(setting, SETTING_TMR_START_DAY);
288   AddStartAnytimeDependentVisibilityCondition(setting, SETTING_TMR_START_DAY);
289 
290   // Start time (hours + minutes only, no day, month, year)
291   setting = AddButton(group, SETTING_TMR_BEGIN, 19126, SettingLevel::Basic);
292   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_BEGIN);
293   AddTypeDependentEnableCondition(setting, SETTING_TMR_BEGIN);
294   AddStartAnytimeDependentVisibilityCondition(setting, SETTING_TMR_BEGIN);
295 
296   // "End any time" (only for timer rules)
297   setting = AddToggle(group, SETTING_TMR_END_ANYTIME, 817, SettingLevel::Basic, m_bEndAnyTime);
298   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_END_ANYTIME);
299   AddTypeDependentEnableCondition(setting, SETTING_TMR_END_ANYTIME);
300 
301   // End day (day + month + year only, no hours, minutes)
302   setting = AddSpinner(group, SETTING_TMR_END_DAY, 19129, SettingLevel::Basic, GetDateAsIndex(m_endLocalTime), DaysFiller);
303   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_END_DAY);
304   AddTypeDependentEnableCondition(setting, SETTING_TMR_END_DAY);
305   AddEndAnytimeDependentVisibilityCondition(setting, SETTING_TMR_END_DAY);
306 
307   // End time (hours + minutes only, no day, month, year)
308   setting = AddButton(group, SETTING_TMR_END, 19127, SettingLevel::Basic);
309   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_END);
310   AddTypeDependentEnableCondition(setting, SETTING_TMR_END);
311   AddEndAnytimeDependentVisibilityCondition(setting, SETTING_TMR_END);
312 
313   // First day (only for timer rules)
314   setting = AddSpinner(group, SETTING_TMR_FIRST_DAY, 19084, SettingLevel::Basic, GetDateAsIndex(m_firstDayLocalTime), DaysFiller);
315   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_FIRST_DAY);
316   AddTypeDependentEnableCondition(setting, SETTING_TMR_FIRST_DAY);
317 
318   // "Prevent duplicate episodes" (only for timer rules)
319   setting = AddList(group, SETTING_TMR_NEW_EPISODES, 812, SettingLevel::Basic, m_iPreventDupEpisodes, DupEpisodesFiller, 812);
320   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_NEW_EPISODES);
321   AddTypeDependentEnableCondition(setting, SETTING_TMR_NEW_EPISODES);
322 
323   // Pre and post record time
324   setting = AddList(group, SETTING_TMR_BEGIN_PRE, 813, SettingLevel::Basic, m_iMarginStart, MarginTimeFiller, 813);
325   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_BEGIN_PRE);
326   AddTypeDependentEnableCondition(setting, SETTING_TMR_BEGIN_PRE);
327 
328   setting = AddList(group, SETTING_TMR_END_POST,  814, SettingLevel::Basic, m_iMarginEnd, MarginTimeFiller, 814);
329   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_END_POST);
330   AddTypeDependentEnableCondition(setting, SETTING_TMR_END_POST);
331 
332   // Priority
333   setting = AddList(group, SETTING_TMR_PRIORITY, 19082, SettingLevel::Basic, m_iPriority, PrioritiesFiller, 19082);
334   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_PRIORITY);
335   AddTypeDependentEnableCondition(setting, SETTING_TMR_PRIORITY);
336 
337   // Lifetime
338   setting = AddList(group, SETTING_TMR_LIFETIME, 19083, SettingLevel::Basic, m_iLifetime, LifetimesFiller, 19083);
339   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_LIFETIME);
340   AddTypeDependentEnableCondition(setting, SETTING_TMR_LIFETIME);
341 
342   // MaxRecordings
343   setting = AddList(group, SETTING_TMR_MAX_REC, 818, SettingLevel::Basic, m_iMaxRecordings, MaxRecordingsFiller, 818);
344   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_MAX_REC);
345   AddTypeDependentEnableCondition(setting, SETTING_TMR_MAX_REC);
346 
347   // Recording folder
348   setting = AddEdit(group, SETTING_TMR_DIR, 19076, SettingLevel::Basic, m_strDirectory, true, false, 19104);
349   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_DIR);
350   AddTypeDependentEnableCondition(setting, SETTING_TMR_DIR);
351 
352   // Recording group
353   setting = AddList(group, SETTING_TMR_REC_GROUP, 811, SettingLevel::Basic, m_iRecordingGroup, RecordingGroupFiller, 811);
354   AddTypeDependentVisibilityCondition(setting, SETTING_TMR_REC_GROUP);
355   AddTypeDependentEnableCondition(setting, SETTING_TMR_REC_GROUP);
356 }
357 
GetWeekdaysFromSetting(const SettingConstPtr & setting)358 int CGUIDialogPVRTimerSettings::GetWeekdaysFromSetting(const SettingConstPtr& setting)
359 {
360   std::shared_ptr<const CSettingList> settingList = std::static_pointer_cast<const CSettingList>(setting);
361   if (settingList->GetElementType() != SettingType::Integer)
362   {
363     CLog::LogF(LOGERROR, "Wrong weekdays element type");
364     return 0;
365   }
366   int weekdays = 0;
367   std::vector<CVariant> list = CSettingUtils::GetList(settingList);
368   for (const auto& value : list)
369   {
370     if (!value.isInteger())
371     {
372       CLog::LogF(LOGERROR, "Wrong weekdays value type");
373       return 0;
374     }
375     weekdays += static_cast<int>(value.asInteger());
376   }
377 
378   return weekdays;
379 }
380 
OnSettingChanged(const std::shared_ptr<const CSetting> & setting)381 void CGUIDialogPVRTimerSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
382 {
383   if (setting == NULL)
384   {
385     CLog::LogF(LOGERROR, "No setting");
386     return;
387   }
388 
389   CGUIDialogSettingsManualBase::OnSettingChanged(setting);
390 
391   const std::string& settingId = setting->GetId();
392 
393   if (settingId == SETTING_TMR_TYPE)
394   {
395     int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
396     const auto it = m_typeEntries.find(idx);
397     if (it != m_typeEntries.end())
398     {
399       m_timerType = it->second;
400 
401       if (m_timerType->IsTimerRule() && (m_iWeekdays == PVR_WEEKDAY_ALLDAYS))
402         SetButtonLabels(); // update "Any day" vs. "Every day"
403     }
404     else
405     {
406       CLog::LogF(LOGERROR, "Unable to get 'type' value");
407     }
408   }
409   else if (settingId == SETTING_TMR_ACTIVE)
410   {
411     m_bTimerActive = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
412   }
413   else if (settingId == SETTING_TMR_NAME)
414   {
415     m_strTitle = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
416   }
417   else if (settingId == SETTING_TMR_EPGSEARCH)
418   {
419     m_strEpgSearchString = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
420   }
421   else if (settingId == SETTING_TMR_FULLTEXT)
422   {
423     m_bFullTextEpgSearch = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
424   }
425   else if (settingId == SETTING_TMR_CHANNEL)
426   {
427     int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
428     const auto it = m_channelEntries.find(idx);
429     if (it != m_channelEntries.end())
430     {
431       m_channel = it->second;
432     }
433     else
434     {
435       CLog::LogF(LOGERROR, "Unable to get 'type' value");
436     }
437   }
438   else if (settingId == SETTING_TMR_WEEKDAYS)
439   {
440     m_iWeekdays = GetWeekdaysFromSetting(setting);
441   }
442   else if (settingId == SETTING_TMR_START_ANYTIME)
443   {
444     m_bStartAnyTime = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
445   }
446   else if (settingId == SETTING_TMR_END_ANYTIME)
447   {
448     m_bEndAnyTime = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
449   }
450   else if (settingId == SETTING_TMR_START_DAY)
451   {
452     SetDateFromIndex(m_startLocalTime, std::static_pointer_cast<const CSettingInt>(setting)->GetValue());
453   }
454   else if (settingId == SETTING_TMR_END_DAY)
455   {
456     SetDateFromIndex(m_endLocalTime, std::static_pointer_cast<const CSettingInt>(setting)->GetValue());
457   }
458   else if (settingId == SETTING_TMR_FIRST_DAY)
459   {
460     SetDateFromIndex(m_firstDayLocalTime, std::static_pointer_cast<const CSettingInt>(setting)->GetValue());
461   }
462   else if (settingId == SETTING_TMR_NEW_EPISODES)
463   {
464     m_iPreventDupEpisodes = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
465   }
466   else if (settingId == SETTING_TMR_BEGIN_PRE)
467   {
468     m_iMarginStart = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
469   }
470   else if (settingId == SETTING_TMR_END_POST)
471   {
472     m_iMarginEnd = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
473   }
474   else if (settingId == SETTING_TMR_PRIORITY)
475   {
476     m_iPriority = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
477   }
478   else if (settingId == SETTING_TMR_LIFETIME)
479   {
480     m_iLifetime = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
481   }
482   else if (settingId == SETTING_TMR_MAX_REC)
483   {
484     m_iMaxRecordings = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
485   }
486   else if (settingId == SETTING_TMR_DIR)
487   {
488     m_strDirectory = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
489   }
490   else if (settingId == SETTING_TMR_REC_GROUP)
491   {
492     m_iRecordingGroup = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
493   }
494 }
495 
OnSettingAction(const std::shared_ptr<const CSetting> & setting)496 void CGUIDialogPVRTimerSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
497 {
498   if (setting == NULL)
499   {
500     CLog::LogF(LOGERROR, "No setting");
501     return;
502   }
503 
504   CGUIDialogSettingsManualBase::OnSettingAction(setting);
505 
506   const std::string& settingId = setting->GetId();
507   if (settingId == SETTING_TMR_BEGIN)
508   {
509     KODI::TIME::SystemTime timerStartTime;
510     m_startLocalTime.GetAsSystemTime(timerStartTime);
511     if (CGUIDialogNumeric::ShowAndGetTime(timerStartTime, g_localizeStrings.Get(14066)))
512     {
513       SetTimeFromSystemTime(m_startLocalTime, timerStartTime);
514       m_timerStartTimeStr = m_startLocalTime.GetAsLocalizedTime("", false);
515       SetButtonLabels();
516     }
517   }
518   else if (settingId == SETTING_TMR_END)
519   {
520     KODI::TIME::SystemTime timerEndTime;
521     m_endLocalTime.GetAsSystemTime(timerEndTime);
522     if (CGUIDialogNumeric::ShowAndGetTime(timerEndTime, g_localizeStrings.Get(14066)))
523     {
524       SetTimeFromSystemTime(m_endLocalTime, timerEndTime);
525       m_timerEndTimeStr = m_endLocalTime.GetAsLocalizedTime("", false);
526       SetButtonLabels();
527     }
528   }
529 }
530 
Validate()531 bool CGUIDialogPVRTimerSettings::Validate()
532 {
533   bool bStartAnyTime = m_bStartAnyTime;
534   bool bEndAnyTime = m_bEndAnyTime;
535 
536   if (!m_timerType->SupportsStartAnyTime() ||
537       !m_timerType->IsEpgBased()) // Start anytime toggle is not displayed
538     bStartAnyTime = false; // Assume start time change needs checking for
539 
540   if (!m_timerType->SupportsEndAnyTime() ||
541       !m_timerType->IsEpgBased()) // End anytime toggle is not displayed
542     bEndAnyTime = false; // Assume end time change needs checking for
543 
544   // Begin and end time
545   if (!bStartAnyTime && !bEndAnyTime)
546   {
547     // Not in the set of having neither or both of start clock entry and
548     // end clock entry while also being a timer rule
549     if (!(m_timerType->SupportsStartTime() == m_timerType->SupportsEndTime() &&
550           m_timerType->IsTimerRule()) &&
551         m_endLocalTime < m_startLocalTime)
552     {
553       HELPERS::ShowOKDialogText(CVariant{19065}, // "Timer settings"
554                                 CVariant{19072}); // In order to add/update a timer
555       return false;
556     }
557   }
558 
559   return true;
560 }
561 
Save()562 bool CGUIDialogPVRTimerSettings::Save()
563 {
564   if (!Validate())
565     return false;
566 
567   // Timer type
568   m_timerInfoTag->SetTimerType(m_timerType);
569 
570   // Timer active/inactive
571   m_timerInfoTag->m_state = m_bTimerActive ? PVR_TIMER_STATE_SCHEDULED : PVR_TIMER_STATE_DISABLED;
572 
573   // Name
574   m_timerInfoTag->m_strTitle = m_strTitle;
575 
576   // epg search string (only for epg-based timer rules)
577   m_timerInfoTag->m_strEpgSearchString = m_strEpgSearchString;
578 
579   // epg fulltext search, instead of just title match. (only for epg-based timer rules)
580   m_timerInfoTag->m_bFullTextEpgSearch = m_bFullTextEpgSearch;
581 
582   // Channel
583   m_timerInfoTag->m_iClientChannelUid = m_channel.channelUid;
584   m_timerInfoTag->m_iClientId = m_channel.clientId;
585   m_timerInfoTag->m_bIsRadio = m_bIsRadio;
586   m_timerInfoTag->UpdateChannel();
587 
588   if (!m_timerType->SupportsStartAnyTime() || !m_timerType->IsEpgBased()) // Start anytime toggle is not displayed
589     m_bStartAnyTime = false; // Assume start time change needs checking for
590   m_timerInfoTag->m_bStartAnyTime = m_bStartAnyTime;
591 
592   if (!m_timerType->SupportsEndAnyTime() || !m_timerType->IsEpgBased()) // End anytime toggle is not displayed
593     m_bEndAnyTime = false; // Assume end time change needs checking for
594   m_timerInfoTag->m_bEndAnyTime = m_bEndAnyTime;
595 
596   // Begin and end time
597   if (!m_bStartAnyTime && !m_bEndAnyTime)
598   {
599     if (m_timerType->SupportsStartTime() && // has start clock entry
600         m_timerType->SupportsEndTime() && // and end clock entry
601         m_timerType->IsTimerRule()) // but no associated start/end day spinners
602     {
603       if (m_endLocalTime < m_startLocalTime) // And the end clock is earlier than the start clock
604       {
605         CLog::LogFC(LOGDEBUG, LOGPVR, "End before start, adding a day.");
606         m_endLocalTime += CDateTimeSpan(1, 0, 0, 0);
607         if (m_endLocalTime < m_startLocalTime)
608         {
609           CLog::Log(LOGWARNING, "Timer settings dialog: End before start. Setting end time to start time.");
610           m_endLocalTime = m_startLocalTime;
611         }
612       }
613       else if (m_endLocalTime > (m_startLocalTime + CDateTimeSpan(1, 0, 0, 0))) // Or the duration is more than a day
614       {
615         CLog::LogFC(LOGDEBUG, LOGPVR, "End > 1 day after start, removing a day.");
616         m_endLocalTime -= CDateTimeSpan(1, 0, 0, 0);
617         if (m_endLocalTime > (m_startLocalTime + CDateTimeSpan(1, 0, 0, 0)))
618         {
619           CLog::Log(LOGWARNING, "Timer settings dialog: End > 1 day after start. Setting end time to start time.");
620           m_endLocalTime = m_startLocalTime;
621         }
622       }
623     }
624     else if (m_endLocalTime < m_startLocalTime)
625     {
626       // this case will fail validation so this can't be reached.
627     }
628     m_timerInfoTag->SetStartFromLocalTime(m_startLocalTime);
629     m_timerInfoTag->SetEndFromLocalTime(m_endLocalTime);
630   }
631   else if (!m_bStartAnyTime)
632     m_timerInfoTag->SetStartFromLocalTime(m_startLocalTime);
633   else if (!m_bEndAnyTime)
634     m_timerInfoTag->SetEndFromLocalTime(m_endLocalTime);
635 
636   // Days of week (only for timer rules)
637   if (m_timerType->IsTimerRule())
638     m_timerInfoTag->m_iWeekdays = m_iWeekdays;
639   else
640     m_timerInfoTag->m_iWeekdays = PVR_WEEKDAY_NONE;
641 
642   // First day (only for timer rules)
643   m_timerInfoTag->SetFirstDayFromLocalTime(m_firstDayLocalTime);
644 
645   // "New episodes only" (only for timer rules)
646   m_timerInfoTag->m_iPreventDupEpisodes = m_iPreventDupEpisodes;
647 
648   // Pre and post record time
649   m_timerInfoTag->m_iMarginStart = m_iMarginStart;
650   m_timerInfoTag->m_iMarginEnd = m_iMarginEnd;
651 
652   // Priority
653   m_timerInfoTag->m_iPriority = m_iPriority;
654 
655   // Lifetime
656   m_timerInfoTag->m_iLifetime = m_iLifetime;
657 
658   // MaxRecordings
659   m_timerInfoTag->m_iMaxRecordings = m_iMaxRecordings;
660 
661   // Recording folder
662   m_timerInfoTag->m_strDirectory = m_strDirectory;
663 
664   // Recording group
665   m_timerInfoTag->m_iRecordingGroup = m_iRecordingGroup;
666 
667   // Set the timer's title to the channel name if it's empty or 'New Timer'
668   if (m_strTitle.empty() || m_strTitle == g_localizeStrings.Get(19056))
669   {
670     const std::string channelName = m_timerInfoTag->ChannelName();
671     if (!channelName.empty())
672       m_timerInfoTag->m_strTitle = channelName;
673   }
674 
675   // Update summary
676   m_timerInfoTag->UpdateSummary();
677 
678   return true;
679 }
680 
SetButtonLabels()681 void CGUIDialogPVRTimerSettings::SetButtonLabels()
682 {
683   // timer start time
684   BaseSettingControlPtr settingControl = GetSettingControl(SETTING_TMR_BEGIN);
685   if (settingControl != NULL && settingControl->GetControl() != NULL)
686   {
687     SET_CONTROL_LABEL2(settingControl->GetID(), m_timerStartTimeStr);
688   }
689 
690   // timer end time
691   settingControl = GetSettingControl(SETTING_TMR_END);
692   if (settingControl != NULL && settingControl->GetControl() != NULL)
693   {
694     SET_CONTROL_LABEL2(settingControl->GetID(), m_timerEndTimeStr);
695   }
696 }
697 
AddCondition(const std::shared_ptr<CSetting> & setting,const std::string & identifier,SettingConditionCheck condition,SettingDependencyType depType,const std::string & settingId)698 void CGUIDialogPVRTimerSettings::AddCondition(const std::shared_ptr<CSetting>& setting,
699                                               const std::string& identifier,
700                                               SettingConditionCheck condition,
701                                               SettingDependencyType depType,
702                                               const std::string& settingId)
703 {
704   GetSettingsManager()->AddDynamicCondition(identifier, condition, this);
705   CSettingDependency dep(depType, GetSettingsManager());
706   dep.And()->Add(
707     CSettingDependencyConditionPtr(
708       new CSettingDependencyCondition(identifier, "true", settingId, false, GetSettingsManager())));
709   SettingDependencies deps(setting->GetDependencies());
710   deps.push_back(dep);
711   setting->SetDependencies(deps);
712 }
713 
GetDateAsIndex(const CDateTime & datetime)714 int CGUIDialogPVRTimerSettings::GetDateAsIndex(const CDateTime& datetime)
715 {
716   const CDateTime date(datetime.GetYear(), datetime.GetMonth(), datetime.GetDay(), 0, 0, 0);
717   time_t t(0);
718   date.GetAsTime(t);
719   return static_cast<int>(t);
720 }
721 
SetDateFromIndex(CDateTime & datetime,int date)722 void CGUIDialogPVRTimerSettings::SetDateFromIndex(CDateTime& datetime, int date)
723 {
724   const CDateTime newDate(static_cast<time_t>(date));
725   datetime.SetDateTime(
726     newDate.GetYear(), newDate.GetMonth(), newDate.GetDay(),
727     datetime.GetHour(), datetime.GetMinute(), datetime.GetSecond());
728 }
729 
SetTimeFromSystemTime(CDateTime & datetime,const KODI::TIME::SystemTime & time)730 void CGUIDialogPVRTimerSettings::SetTimeFromSystemTime(CDateTime& datetime,
731                                                        const KODI::TIME::SystemTime& time)
732 {
733   const CDateTime newTime(time);
734   datetime.SetDateTime(
735     datetime.GetYear(), datetime.GetMonth(), datetime.GetDay(),
736     newTime.GetHour(), newTime.GetMinute(), newTime.GetSecond());
737 }
738 
InitializeTypesList()739 void CGUIDialogPVRTimerSettings::InitializeTypesList()
740 {
741   m_typeEntries.clear();
742 
743   // If timer is read-only or was created by a timer rule, only add current type, for information. Type can't be changed.
744   if (m_timerType->IsReadOnly() || m_timerInfoTag->GetTimerRuleId() != PVR_TIMER_NO_PARENT)
745   {
746     m_typeEntries.insert(std::make_pair(0, m_timerType));
747     return;
748   }
749 
750   bool bFoundThisType(false);
751   int idx(0);
752   const std::vector<std::shared_ptr<CPVRTimerType>> types(CPVRTimerType::GetAllTypes());
753   for (const auto& type : types)
754   {
755     // Type definition prohibits created of new instances.
756     // But the dialog can act as a viewer for these types.
757     if (type->ForbidsNewInstances())
758       continue;
759 
760     // Read-only timers cannot be created using this dialog.
761     // But the dialog can act as a viewer for read-only types.
762     if (type->IsReadOnly())
763       continue;
764 
765     // Drop TimerTypes that require EPGInfo, if none is populated
766     if (type->RequiresEpgTagOnCreate() && !m_timerInfoTag->GetEpgInfoTag())
767       continue;
768 
769     // Drop TimerTypes without 'Series' EPG attributes if none are set
770     if (type->RequiresEpgSeriesOnCreate())
771     {
772       const std::shared_ptr<CPVREpgInfoTag> epgTag(m_timerInfoTag->GetEpgInfoTag());
773       if (epgTag && !epgTag->IsSeries())
774         continue;
775     }
776 
777     // Drop TimerTypes which need series link if none is set
778     if (type->RequiresEpgSeriesLinkOnCreate())
779     {
780       const std::shared_ptr<CPVREpgInfoTag> epgTag(m_timerInfoTag->GetEpgInfoTag());
781       if (!epgTag || epgTag->SeriesLink().empty())
782         continue;
783     }
784 
785     // Drop TimerTypes that forbid EPGInfo, if it is populated
786     if (type->ForbidsEpgTagOnCreate() && m_timerInfoTag->GetEpgInfoTag())
787       continue;
788 
789     // Drop TimerTypes that aren't rules and cannot be recorded
790     if (!type->IsTimerRule())
791     {
792       const std::shared_ptr<CPVREpgInfoTag> epgTag(m_timerInfoTag->GetEpgInfoTag());
793       bool bCanRecord = epgTag ? epgTag->IsRecordable() : m_timerInfoTag->EndAsLocalTime() > CDateTime::GetCurrentDateTime();
794       if (!bCanRecord)
795         continue;
796     }
797 
798     if (!bFoundThisType && *type == *m_timerType)
799       bFoundThisType = true;
800 
801     m_typeEntries.insert(std::make_pair(idx++, type));
802   }
803 
804   if (!bFoundThisType)
805     m_typeEntries.insert(std::make_pair(idx++, m_timerType));
806 }
807 
InitializeChannelsList()808 void CGUIDialogPVRTimerSettings::InitializeChannelsList()
809 {
810   m_channelEntries.clear();
811 
812   int index = 0;
813 
814   // Add special "any channel" entries - one for every client (used for epg-based timer rules).
815   CPVRClientMap clients;
816   CServiceBroker::GetPVRManager().Clients()->GetCreatedClients(clients);
817   for (const auto& client : clients)
818   {
819     m_channelEntries.insert({index, ChannelDescriptor(PVR_CHANNEL_INVALID_UID,
820                                                       client.second->GetID(),
821                                                       g_localizeStrings.Get(809))}); // "Any channel"
822     ++index;
823   }
824 
825   // Add regular channels
826   const std::shared_ptr<CPVRChannelGroup> allGroup = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio);
827   const std::vector<std::shared_ptr<PVRChannelGroupMember>> groupMembers = allGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE);
828   for (const auto& groupMember : groupMembers)
829   {
830     const std::shared_ptr<CPVRChannel> channel = groupMember->channel;
831     const std::string channelDescription
832       = StringUtils::Format("%s %s", channel->ChannelNumber().FormattedChannelNumber().c_str(), channel->ChannelName().c_str());
833     m_channelEntries.insert({index, ChannelDescriptor(channel->UniqueID(), channel->ClientID(), channelDescription)});
834     ++index;
835   }
836 }
837 
TypesFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)838 void CGUIDialogPVRTimerSettings::TypesFiller(const SettingConstPtr& setting,
839                                              std::vector<IntegerSettingOption>& list,
840                                              int& current,
841                                              void* data)
842 {
843   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
844   if (pThis)
845   {
846     list.clear();
847     current = 0;
848 
849     static const std::vector<std::pair<std::string, CVariant>> reminderTimerProps{std::make_pair("PVR.IsRemindingTimer", CVariant{true})};
850     static const std::vector<std::pair<std::string, CVariant>> recordingTimerProps{std::make_pair("PVR.IsRecordingTimer", CVariant{true})};
851 
852     bool foundCurrent(false);
853     for (const auto& typeEntry : pThis->m_typeEntries)
854     {
855       list.emplace_back(typeEntry.second->GetDescription(), typeEntry.first,
856                         typeEntry.second->IsReminder() ? reminderTimerProps : recordingTimerProps);
857 
858       if (!foundCurrent && (*(pThis->m_timerType) == *(typeEntry.second)))
859       {
860         current = typeEntry.first;
861         foundCurrent = true;
862       }
863     }
864   }
865   else
866     CLog::LogF(LOGERROR, "No dialog");
867 }
868 
ChannelsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)869 void CGUIDialogPVRTimerSettings::ChannelsFiller(const SettingConstPtr& setting,
870                                                 std::vector<IntegerSettingOption>& list,
871                                                 int& current,
872                                                 void* data)
873 {
874   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
875   if (pThis)
876   {
877     list.clear();
878     current = 0;
879 
880     bool foundCurrent(false);
881     for (const auto& channelEntry : pThis->m_channelEntries)
882     {
883       // Only include channels for the currently selected timer type or all channels if type is client-independent.
884       if (pThis->m_timerType->GetClientId() == -1 || // client-independent
885           pThis->m_timerType->GetClientId() == channelEntry.second.clientId)
886       {
887         // Do not add "any channel" entry if not supported by selected timer type.
888         if (channelEntry.second.channelUid == PVR_CHANNEL_INVALID_UID &&
889             !pThis->m_timerType->SupportsAnyChannel())
890           continue;
891 
892         list.emplace_back(IntegerSettingOption(channelEntry.second.description, channelEntry.first));
893       }
894 
895       if (!foundCurrent && (pThis->m_channel == channelEntry.second))
896       {
897         current = channelEntry.first;
898         foundCurrent = true;
899       }
900     }
901   }
902   else
903     CLog::LogF(LOGERROR, "No dialog");
904 }
905 
DaysFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)906 void CGUIDialogPVRTimerSettings::DaysFiller(const SettingConstPtr& setting,
907                                             std::vector<IntegerSettingOption>& list,
908                                             int& current,
909                                             void* data)
910 {
911   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
912   if (pThis)
913   {
914     list.clear();
915     current = 0;
916 
917     // Data range: "today" until "yesterday next year"
918     const CDateTime now(CDateTime::GetCurrentDateTime());
919     CDateTime time(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
920     const CDateTime yesterdayPlusOneYear(CDateTime(time.GetYear() + 1, time.GetMonth(), time.GetDay(), time.GetHour(), time.GetMinute(), time.GetSecond())
921 		- CDateTimeSpan(1, 0, 0, 0));
922 
923     CDateTime oldCDateTime;
924     if (setting->GetId() == SETTING_TMR_FIRST_DAY)
925       oldCDateTime = pThis->m_timerInfoTag->FirstDayAsLocalTime();
926     else if (setting->GetId() == SETTING_TMR_START_DAY)
927       oldCDateTime = pThis->m_timerInfoTag->StartAsLocalTime();
928     else
929       oldCDateTime = pThis->m_timerInfoTag->EndAsLocalTime();
930     const CDateTime oldCDate(oldCDateTime.GetYear(), oldCDateTime.GetMonth(), oldCDateTime.GetDay(), 0, 0, 0);
931 
932     if ((oldCDate < time) || (oldCDate > yesterdayPlusOneYear))
933       list.emplace_back(oldCDate.GetAsLocalizedDate(true /*long date*/), GetDateAsIndex(oldCDate));
934 
935     while (time <= yesterdayPlusOneYear)
936     {
937       list.emplace_back(time.GetAsLocalizedDate(true /*long date*/), GetDateAsIndex(time));
938       time += CDateTimeSpan(1, 0, 0, 0);
939     }
940 
941     if (setting->GetId() == SETTING_TMR_FIRST_DAY)
942       current = GetDateAsIndex(pThis->m_firstDayLocalTime);
943     else if (setting->GetId() == SETTING_TMR_START_DAY)
944       current = GetDateAsIndex(pThis->m_startLocalTime);
945     else
946       current = GetDateAsIndex(pThis->m_endLocalTime);
947   }
948   else
949     CLog::LogF(LOGERROR, "No dialog");
950 }
951 
DupEpisodesFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)952 void CGUIDialogPVRTimerSettings::DupEpisodesFiller(const SettingConstPtr& setting,
953                                                    std::vector<IntegerSettingOption>& list,
954                                                    int& current,
955                                                    void* data)
956 {
957   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
958   if (pThis)
959   {
960     list.clear();
961 
962     std::vector<std::pair<std::string,int>> values;
963     pThis->m_timerType->GetPreventDuplicateEpisodesValues(values);
964     for (const auto& value : values)
965       list.emplace_back(IntegerSettingOption(value.first, value.second));
966 
967     current = pThis->m_iPreventDupEpisodes;
968   }
969   else
970     CLog::LogF(LOGERROR, "No dialog");
971 }
972 
WeekdaysFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)973 void CGUIDialogPVRTimerSettings::WeekdaysFiller(const SettingConstPtr& setting,
974                                                 std::vector<IntegerSettingOption>& list,
975                                                 int& current,
976                                                 void* data)
977 {
978   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
979   if (pThis)
980   {
981     list.clear();
982     list.emplace_back(g_localizeStrings.Get(831), PVR_WEEKDAY_MONDAY); // "Mondays"
983     list.emplace_back(g_localizeStrings.Get(832), PVR_WEEKDAY_TUESDAY); // "Tuesdays"
984     list.emplace_back(g_localizeStrings.Get(833), PVR_WEEKDAY_WEDNESDAY); // "Wednesdays"
985     list.emplace_back(g_localizeStrings.Get(834), PVR_WEEKDAY_THURSDAY); // "Thursdays"
986     list.emplace_back(g_localizeStrings.Get(835), PVR_WEEKDAY_FRIDAY); // "Fridays"
987     list.emplace_back(g_localizeStrings.Get(836), PVR_WEEKDAY_SATURDAY); // "Saturdays"
988     list.emplace_back(g_localizeStrings.Get(837), PVR_WEEKDAY_SUNDAY); // "Sundays"
989 
990     current = pThis->m_iWeekdays;
991   }
992   else
993     CLog::LogF(LOGERROR, "No dialog");
994 }
995 
PrioritiesFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)996 void CGUIDialogPVRTimerSettings::PrioritiesFiller(const SettingConstPtr& setting,
997                                                   std::vector<IntegerSettingOption>& list,
998                                                   int& current,
999                                                   void* data)
1000 {
1001   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1002   if (pThis)
1003   {
1004     list.clear();
1005 
1006     std::vector<std::pair<std::string,int>> values;
1007     pThis->m_timerType->GetPriorityValues(values);
1008     for (const auto& value : values)
1009       list.emplace_back(IntegerSettingOption(value.first, value.second));
1010 
1011     current = pThis->m_iPriority;
1012 
1013     auto it = list.begin();
1014     while (it != list.end())
1015     {
1016       if (it->value == current)
1017         break; // value already in list
1018 
1019       ++it;
1020     }
1021 
1022     if (it == list.end())
1023     {
1024       // PVR backend supplied value is not in the list of predefined values. Insert it.
1025       list.insert(it, IntegerSettingOption(StringUtils::Format("%d", current), current));
1026     }
1027   }
1028   else
1029     CLog::LogF(LOGERROR, "No dialog");
1030 }
1031 
LifetimesFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)1032 void CGUIDialogPVRTimerSettings::LifetimesFiller(const SettingConstPtr& setting,
1033                                                  std::vector<IntegerSettingOption>& list,
1034                                                  int& current,
1035                                                  void* data)
1036 {
1037   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1038   if (pThis)
1039   {
1040     list.clear();
1041 
1042     std::vector<std::pair<std::string,int>> values;
1043     pThis->m_timerType->GetLifetimeValues(values);
1044     for (const auto& value : values)
1045       list.emplace_back(IntegerSettingOption(value.first, value.second));
1046 
1047     current = pThis->m_iLifetime;
1048 
1049     auto it = list.begin();
1050     while (it != list.end())
1051     {
1052       if (it->value == current)
1053         break; // value already in list
1054 
1055       ++it;
1056     }
1057 
1058     if (it == list.end())
1059     {
1060       // PVR backend supplied value is not in the list of predefined values. Insert it.
1061       list.insert(it, IntegerSettingOption(StringUtils::Format(g_localizeStrings.Get(17999).c_str(), current) /* %i days */, current));
1062     }
1063   }
1064   else
1065     CLog::LogF(LOGERROR, "No dialog");
1066 }
1067 
MaxRecordingsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)1068 void CGUIDialogPVRTimerSettings::MaxRecordingsFiller(const SettingConstPtr& setting,
1069                                                      std::vector<IntegerSettingOption>& list,
1070                                                      int& current,
1071                                                      void* data)
1072 {
1073   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1074   if (pThis)
1075   {
1076     list.clear();
1077 
1078     std::vector<std::pair<std::string,int>> values;
1079     pThis->m_timerType->GetMaxRecordingsValues(values);
1080     for (const auto& value : values)
1081       list.emplace_back(IntegerSettingOption(value.first, value.second));
1082 
1083     current = pThis->m_iMaxRecordings;
1084 
1085     auto it = list.begin();
1086     while (it != list.end())
1087     {
1088       if (it->value == current)
1089         break; // value already in list
1090 
1091       ++it;
1092     }
1093 
1094     if (it == list.end())
1095     {
1096       // PVR backend supplied value is not in the list of predefined values. Insert it.
1097       list.insert(it, IntegerSettingOption(StringUtils::Format("%d", current), current));
1098     }
1099   }
1100   else
1101     CLog::LogF(LOGERROR, "No dialog");
1102 }
1103 
RecordingGroupFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)1104 void CGUIDialogPVRTimerSettings::RecordingGroupFiller(const SettingConstPtr& setting,
1105                                                       std::vector<IntegerSettingOption>& list,
1106                                                       int& current,
1107                                                       void* data)
1108 {
1109   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1110   if (pThis)
1111   {
1112     list.clear();
1113 
1114     std::vector<std::pair<std::string,int>> values;
1115     pThis->m_timerType->GetRecordingGroupValues(values);
1116     for (const auto& value : values)
1117       list.emplace_back(IntegerSettingOption(value.first, value.second));
1118 
1119     current = pThis->m_iRecordingGroup;
1120   }
1121   else
1122     CLog::LogF(LOGERROR, "No dialog");
1123 }
1124 
MarginTimeFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)1125 void CGUIDialogPVRTimerSettings::MarginTimeFiller(const SettingConstPtr& setting,
1126                                                   std::vector<IntegerSettingOption>& list,
1127                                                   int& current,
1128                                                   void* data)
1129 {
1130   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1131   if (pThis)
1132   {
1133     list.clear();
1134 
1135     // Get global settings values
1136     CPVRSettings::MarginTimeFiller(setting, list, current, data);
1137 
1138     if (setting->GetId() == SETTING_TMR_BEGIN_PRE)
1139       current = pThis->m_iMarginStart;
1140     else
1141       current = pThis->m_iMarginEnd;
1142 
1143     bool bInsertValue = true;
1144     auto it = list.begin();
1145     while (it != list.end())
1146     {
1147       if (it->value == current)
1148       {
1149         bInsertValue = false;
1150         break; // value already in list
1151       }
1152 
1153       if (it->value > current)
1154         break;
1155 
1156       ++it;
1157     }
1158 
1159     if (bInsertValue)
1160     {
1161       // PVR backend supplied value is not in the list of predefined values. Insert it.
1162       list.insert(it, IntegerSettingOption(StringUtils::Format(g_localizeStrings.Get(14044).c_str(), current) /* %i min */, current));
1163     }
1164   }
1165   else
1166     CLog::LogF(LOGERROR, "No dialog");
1167 }
1168 
WeekdaysValueFormatter(const SettingConstPtr & setting)1169 std::string CGUIDialogPVRTimerSettings::WeekdaysValueFormatter(const SettingConstPtr& setting)
1170 {
1171   return CPVRTimerInfoTag::GetWeekdaysString(GetWeekdaysFromSetting(setting), true, true);
1172 }
1173 
AddTypeDependentEnableCondition(const std::shared_ptr<CSetting> & setting,const std::string & identifier)1174 void CGUIDialogPVRTimerSettings::AddTypeDependentEnableCondition(
1175     const std::shared_ptr<CSetting>& setting, const std::string& identifier)
1176 {
1177   // Enable setting depending on read-only attribute of the selected timer type
1178   std::string id(identifier);
1179   id.append(TYPE_DEP_ENABLE_COND_ID_POSTFIX);
1180   AddCondition(setting, id, TypeReadOnlyCondition, SettingDependencyType::Enable, SETTING_TMR_TYPE);
1181 }
1182 
TypeReadOnlyCondition(const std::string & condition,const std::string & value,const SettingConstPtr & setting,void * data)1183 bool CGUIDialogPVRTimerSettings::TypeReadOnlyCondition(const std::string& condition,
1184                                                        const std::string& value,
1185                                                        const SettingConstPtr& setting,
1186                                                        void* data)
1187 {
1188   if (setting == NULL)
1189     return false;
1190 
1191   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1192   if (pThis == NULL)
1193   {
1194     CLog::LogF(LOGERROR, "No dialog");
1195     return false;
1196   }
1197 
1198   if (!StringUtils::EqualsNoCase(value, "true"))
1199     return false;
1200 
1201   std::string cond(condition);
1202   cond.erase(cond.find(TYPE_DEP_ENABLE_COND_ID_POSTFIX));
1203 
1204   // If only one type is available, disable type selector.
1205   if (pThis->m_typeEntries.size() == 1)
1206   {
1207     if (cond == SETTING_TMR_TYPE)
1208       return false;
1209   }
1210 
1211   // For existing one time epg-based timers, disable editing of epg-filled data.
1212   if (!pThis->m_bIsNewTimer && pThis->m_timerType->IsEpgBasedOnetime())
1213   {
1214     if ((cond == SETTING_TMR_NAME)      ||
1215         (cond == SETTING_TMR_CHANNEL)   ||
1216         (cond == SETTING_TMR_START_DAY) ||
1217         (cond == SETTING_TMR_END_DAY)   ||
1218         (cond == SETTING_TMR_BEGIN)     ||
1219         (cond == SETTING_TMR_END))
1220       return false;
1221   }
1222 
1223   /* Always enable enable/disable, if supported by the timer type. */
1224   if (pThis->m_timerType->SupportsEnableDisable() && !pThis->m_timerInfoTag->IsBroken())
1225   {
1226     if (cond == SETTING_TMR_ACTIVE)
1227       return true;
1228   }
1229 
1230   // Let the PVR client decide...
1231   int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
1232   const auto entry = pThis->m_typeEntries.find(idx);
1233   if (entry != pThis->m_typeEntries.end())
1234     return !entry->second->IsReadOnly();
1235   else
1236     CLog::LogF(LOGERROR, "No type entry");
1237 
1238   return false;
1239 }
1240 
AddTypeDependentVisibilityCondition(const std::shared_ptr<CSetting> & setting,const std::string & identifier)1241 void CGUIDialogPVRTimerSettings::AddTypeDependentVisibilityCondition(
1242     const std::shared_ptr<CSetting>& setting, const std::string& identifier)
1243 {
1244   // Show or hide setting depending on attributes of the selected timer type
1245   std::string id(identifier);
1246   id.append(TYPE_DEP_VISIBI_COND_ID_POSTFIX);
1247   AddCondition(setting, id, TypeSupportsCondition, SettingDependencyType::Visible, SETTING_TMR_TYPE);
1248 }
1249 
TypeSupportsCondition(const std::string & condition,const std::string & value,const SettingConstPtr & setting,void * data)1250 bool CGUIDialogPVRTimerSettings::TypeSupportsCondition(const std::string& condition,
1251                                                        const std::string& value,
1252                                                        const SettingConstPtr& setting,
1253                                                        void* data)
1254 {
1255   if (setting == NULL)
1256     return false;
1257 
1258   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1259   if (pThis == NULL)
1260   {
1261     CLog::LogF(LOGERROR, "No dialog");
1262     return false;
1263   }
1264 
1265   if (!StringUtils::EqualsNoCase(value, "true"))
1266     return false;
1267 
1268   int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
1269   const auto entry = pThis->m_typeEntries.find(idx);
1270   if (entry != pThis->m_typeEntries.end())
1271   {
1272     std::string cond(condition);
1273     cond.erase(cond.find(TYPE_DEP_VISIBI_COND_ID_POSTFIX));
1274 
1275     if (cond == SETTING_TMR_EPGSEARCH)
1276       return entry->second->SupportsEpgTitleMatch() || entry->second->SupportsEpgFulltextMatch();
1277     else if (cond == SETTING_TMR_FULLTEXT)
1278       return entry->second->SupportsEpgFulltextMatch();
1279     else if (cond == SETTING_TMR_ACTIVE)
1280       return entry->second->SupportsEnableDisable();
1281     else if (cond == SETTING_TMR_CHANNEL)
1282       return entry->second->SupportsChannels();
1283     else if (cond == SETTING_TMR_START_ANYTIME)
1284       return entry->second->SupportsStartAnyTime() && entry->second->IsEpgBased();
1285     else if (cond == SETTING_TMR_END_ANYTIME)
1286       return entry->second->SupportsEndAnyTime() && entry->second->IsEpgBased();
1287     else if (cond == SETTING_TMR_START_DAY)
1288       return entry->second->SupportsStartTime() && entry->second->IsOnetime();
1289     else if (cond == SETTING_TMR_END_DAY)
1290       return entry->second->SupportsEndTime() && entry->second->IsOnetime();
1291     else if (cond == SETTING_TMR_BEGIN)
1292       return entry->second->SupportsStartTime();
1293     else if (cond == SETTING_TMR_END)
1294       return entry->second->SupportsEndTime();
1295     else if (cond == SETTING_TMR_WEEKDAYS)
1296       return entry->second->SupportsWeekdays();
1297     else if (cond == SETTING_TMR_FIRST_DAY)
1298       return entry->second->SupportsFirstDay();
1299     else if (cond == SETTING_TMR_NEW_EPISODES)
1300       return entry->second->SupportsRecordOnlyNewEpisodes();
1301     else if (cond == SETTING_TMR_BEGIN_PRE)
1302       return entry->second->SupportsStartMargin();
1303     else if (cond == SETTING_TMR_END_POST)
1304       return entry->second->SupportsEndMargin();
1305     else if (cond == SETTING_TMR_PRIORITY)
1306       return entry->second->SupportsPriority();
1307     else if (cond == SETTING_TMR_LIFETIME)
1308       return entry->second->SupportsLifetime();
1309     else if (cond == SETTING_TMR_MAX_REC)
1310       return entry->second->SupportsMaxRecordings();
1311     else if (cond == SETTING_TMR_DIR)
1312       return entry->second->SupportsRecordingFolders();
1313     else if (cond == SETTING_TMR_REC_GROUP)
1314       return entry->second->SupportsRecordingGroup();
1315     else
1316       CLog::LogF(LOGERROR, "Unknown condition");
1317   }
1318   else
1319   {
1320     CLog::LogF(LOGERROR, "No type entry");
1321   }
1322   return false;
1323 }
1324 
AddStartAnytimeDependentVisibilityCondition(const std::shared_ptr<CSetting> & setting,const std::string & identifier)1325 void CGUIDialogPVRTimerSettings::AddStartAnytimeDependentVisibilityCondition(
1326     const std::shared_ptr<CSetting>& setting, const std::string& identifier)
1327 {
1328   // Show or hide setting depending on value of setting "any time"
1329   std::string id(identifier);
1330   id.append(START_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX);
1331   AddCondition(setting, id, StartAnytimeSetCondition, SettingDependencyType::Visible, SETTING_TMR_START_ANYTIME);
1332 }
1333 
StartAnytimeSetCondition(const std::string & condition,const std::string & value,const SettingConstPtr & setting,void * data)1334 bool CGUIDialogPVRTimerSettings::StartAnytimeSetCondition(const std::string& condition,
1335                                                           const std::string& value,
1336                                                           const SettingConstPtr& setting,
1337                                                           void* data)
1338 {
1339   if (setting == NULL)
1340     return false;
1341 
1342   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1343   if (pThis == NULL)
1344   {
1345     CLog::LogF(LOGERROR, "No dialog");
1346     return false;
1347   }
1348 
1349   if (!StringUtils::EqualsNoCase(value, "true"))
1350     return false;
1351 
1352   // "any time" setting is only relevant for epg-based timers.
1353   if (!pThis->m_timerType->IsEpgBased())
1354     return true;
1355 
1356   // If 'Start anytime' option isn't supported, don't hide start time
1357   if (!pThis->m_timerType->SupportsStartAnyTime())
1358     return true;
1359 
1360   std::string cond(condition);
1361   cond.erase(cond.find(START_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX));
1362 
1363   if ((cond == SETTING_TMR_START_DAY) ||
1364       (cond == SETTING_TMR_BEGIN))
1365   {
1366     bool bAnytime = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
1367     return !bAnytime;
1368   }
1369   return false;
1370 }
1371 
AddEndAnytimeDependentVisibilityCondition(const std::shared_ptr<CSetting> & setting,const std::string & identifier)1372 void CGUIDialogPVRTimerSettings::AddEndAnytimeDependentVisibilityCondition(
1373     const std::shared_ptr<CSetting>& setting, const std::string& identifier)
1374 {
1375   // Show or hide setting depending on value of setting "any time"
1376   std::string id(identifier);
1377   id.append(END_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX);
1378   AddCondition(setting, id, EndAnytimeSetCondition, SettingDependencyType::Visible, SETTING_TMR_END_ANYTIME);
1379 }
1380 
EndAnytimeSetCondition(const std::string & condition,const std::string & value,const SettingConstPtr & setting,void * data)1381 bool CGUIDialogPVRTimerSettings::EndAnytimeSetCondition(const std::string& condition,
1382                                                         const std::string& value,
1383                                                         const SettingConstPtr& setting,
1384                                                         void* data)
1385 {
1386   if (setting == NULL)
1387     return false;
1388 
1389   CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
1390   if (pThis == NULL)
1391   {
1392     CLog::LogF(LOGERROR, "No dialog");
1393     return false;
1394   }
1395 
1396   if (!StringUtils::EqualsNoCase(value, "true"))
1397     return false;
1398 
1399   // "any time" setting is only relevant for epg-based timers.
1400   if (!pThis->m_timerType->IsEpgBased())
1401     return true;
1402 
1403   // If 'End anytime' option isn't supported, don't hide end time
1404   if (!pThis->m_timerType->SupportsEndAnyTime())
1405     return true;
1406 
1407   std::string cond(condition);
1408   cond.erase(cond.find(END_ANYTIME_DEP_VISIBI_COND_ID_POSTFIX));
1409 
1410   if ((cond == SETTING_TMR_END_DAY)   ||
1411       (cond == SETTING_TMR_END))
1412   {
1413     bool bAnytime = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
1414     return !bAnytime;
1415   }
1416   return false;
1417 }
1418