1 /*
2  *  Copyright (C) 2005-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 "GUIControlSettings.h"
10 
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "Util.h"
14 #include "addons/AddonManager.h"
15 #include "addons/gui/GUIWindowAddonBrowser.h"
16 #include "addons/settings/SettingUrlEncodedString.h"
17 #include "dialogs/GUIDialogFileBrowser.h"
18 #include "dialogs/GUIDialogNumeric.h"
19 #include "dialogs/GUIDialogSelect.h"
20 #include "dialogs/GUIDialogSlider.h"
21 #include "guilib/GUIComponent.h"
22 #include "guilib/GUIEditControl.h"
23 #include "guilib/GUIImage.h"
24 #include "guilib/GUIKeyboardFactory.h"
25 #include "guilib/GUILabelControl.h"
26 #include "guilib/GUIRadioButtonControl.h"
27 #include "guilib/GUISettingsSliderControl.h"
28 #include "guilib/GUISpinControlEx.h"
29 #include "guilib/GUIWindowManager.h"
30 #include "guilib/LocalizeStrings.h"
31 #include "settings/MediaSourceSettings.h"
32 #include "settings/SettingAddon.h"
33 #include "settings/SettingControl.h"
34 #include "settings/SettingDateTime.h"
35 #include "settings/SettingPath.h"
36 #include "settings/SettingUtils.h"
37 #include "settings/lib/Setting.h"
38 #include "settings/lib/SettingDefinitions.h"
39 #include "storage/MediaManager.h"
40 #include "utils/FileExtensionProvider.h"
41 #include "utils/StringUtils.h"
42 #include "utils/Variant.h"
43 #include "utils/log.h"
44 
45 #include <set>
46 #include <utility>
47 
48 using namespace ADDON;
49 
Localize(std::uint32_t code,ILocalizer * localizer,const std::string & addonId="")50 static std::string Localize(std::uint32_t code,
51                             ILocalizer* localizer,
52                             const std::string& addonId = "")
53 {
54   if (localizer == nullptr)
55     return "";
56 
57   if (!addonId.empty())
58   {
59     std::string label = g_localizeStrings.GetAddonString(addonId, code);
60     if (!label.empty())
61       return label;
62   }
63 
64   return localizer->Localize(code);
65 }
66 
67 template<typename TValueType>
GetFileItem(const std::string & label,const TValueType & value,const std::vector<std::pair<std::string,CVariant>> & properties,const std::set<TValueType> & selectedValues)68 static CFileItemPtr GetFileItem(const std::string& label,
69                                 const TValueType& value,
70                                 const std::vector<std::pair<std::string, CVariant>>& properties,
71                                 const std::set<TValueType>& selectedValues)
72 {
73   CFileItemPtr item(new CFileItem(label));
74   item->SetProperty("value", value);
75 
76   for (const auto& prop : properties)
77     item->SetProperty(prop.first, prop.second);
78 
79   if (selectedValues.find(value) != selectedValues.end())
80     item->Select(true);
81 
82   return item;
83 }
84 
85 template<class SettingOption>
CompareSettingOptionAseconding(const SettingOption & lhs,const SettingOption & rhs)86 static bool CompareSettingOptionAseconding(const SettingOption& lhs, const SettingOption& rhs)
87 {
88   return StringUtils::CompareNoCase(lhs.label, rhs.label) < 0;
89 }
90 
91 template<class SettingOption>
CompareSettingOptionDeseconding(const SettingOption & lhs,const SettingOption & rhs)92 static bool CompareSettingOptionDeseconding(const SettingOption& lhs, const SettingOption& rhs)
93 {
94   return StringUtils::CompareNoCase(lhs.label, rhs.label) > 0;
95 }
96 
GetIntegerOptions(const SettingConstPtr & setting,IntegerSettingOptions & options,std::set<int> & selectedOptions,ILocalizer * localizer,bool updateOptions)97 static bool GetIntegerOptions(const SettingConstPtr& setting,
98                               IntegerSettingOptions& options,
99                               std::set<int>& selectedOptions,
100                               ILocalizer* localizer,
101                               bool updateOptions)
102 {
103   std::shared_ptr<const CSettingInt> pSettingInt = NULL;
104   if (setting->GetType() == SettingType::Integer)
105     pSettingInt = std::static_pointer_cast<const CSettingInt>(setting);
106   else if (setting->GetType() == SettingType::List)
107   {
108     std::shared_ptr<const CSettingList> settingList =
109         std::static_pointer_cast<const CSettingList>(setting);
110     if (settingList->GetElementType() != SettingType::Integer)
111       return false;
112 
113     pSettingInt = std::static_pointer_cast<const CSettingInt>(settingList->GetDefinition());
114   }
115 
116   switch (pSettingInt->GetOptionsType())
117   {
118     case SettingOptionsType::StaticTranslatable:
119     {
120       const TranslatableIntegerSettingOptions& settingOptions =
121           pSettingInt->GetTranslatableOptions();
122       for (const auto& option : settingOptions)
123         options.push_back(
124             IntegerSettingOption(Localize(option.label, localizer, option.addonId), option.value));
125       break;
126     }
127 
128     case SettingOptionsType::Static:
129     {
130       const IntegerSettingOptions& settingOptions = pSettingInt->GetOptions();
131       options.insert(options.end(), settingOptions.begin(), settingOptions.end());
132       break;
133     }
134 
135     case SettingOptionsType::Dynamic:
136     {
137       IntegerSettingOptions settingOptions;
138       if (updateOptions)
139         settingOptions = std::const_pointer_cast<CSettingInt>(pSettingInt)->UpdateDynamicOptions();
140       else
141         settingOptions = pSettingInt->GetDynamicOptions();
142       options.insert(options.end(), settingOptions.begin(), settingOptions.end());
143       break;
144     }
145 
146     case SettingOptionsType::Unknown:
147     default:
148     {
149       std::shared_ptr<const CSettingControlFormattedRange> control =
150           std::static_pointer_cast<const CSettingControlFormattedRange>(pSettingInt->GetControl());
151       for (int i = pSettingInt->GetMinimum(); i <= pSettingInt->GetMaximum();
152            i += pSettingInt->GetStep())
153       {
154         std::string strLabel;
155         if (i == pSettingInt->GetMinimum() && control->GetMinimumLabel() > -1)
156           strLabel = Localize(control->GetMinimumLabel(), localizer);
157         else if (control->GetFormatLabel() > -1)
158           strLabel = StringUtils::Format(Localize(control->GetFormatLabel(), localizer).c_str(), i);
159         else
160           strLabel = StringUtils::Format(control->GetFormatString().c_str(), i);
161 
162         options.push_back(IntegerSettingOption(strLabel, i));
163       }
164 
165       break;
166     }
167   }
168 
169   switch (pSettingInt->GetOptionsSort())
170   {
171     case SettingOptionsSort::Ascending:
172       std::sort(options.begin(), options.end(),
173                 CompareSettingOptionAseconding<IntegerSettingOption>);
174       break;
175 
176     case SettingOptionsSort::Descending:
177       std::sort(options.begin(), options.end(),
178                 CompareSettingOptionDeseconding<IntegerSettingOption>);
179       break;
180 
181     case SettingOptionsSort::NoSorting:
182     default:
183       break;
184   }
185 
186   // this must be done after potentially calling CSettingInt::UpdateDynamicOptions() because it can
187   // change the value of the setting
188   if (setting->GetType() == SettingType::Integer)
189     selectedOptions.insert(pSettingInt->GetValue());
190   else if (setting->GetType() == SettingType::List)
191   {
192     std::vector<CVariant> list =
193         CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting));
194     for (const auto& itValue : list)
195       selectedOptions.insert((int)itValue.asInteger());
196   }
197   else
198     return false;
199 
200   return true;
201 }
202 
GetStringOptions(const SettingConstPtr & setting,StringSettingOptions & options,std::set<std::string> & selectedOptions,ILocalizer * localizer,bool updateOptions)203 static bool GetStringOptions(const SettingConstPtr& setting,
204                              StringSettingOptions& options,
205                              std::set<std::string>& selectedOptions,
206                              ILocalizer* localizer,
207                              bool updateOptions)
208 {
209   std::shared_ptr<const CSettingString> pSettingString = NULL;
210   if (setting->GetType() == SettingType::String)
211     pSettingString = std::static_pointer_cast<const CSettingString>(setting);
212   else if (setting->GetType() == SettingType::List)
213   {
214     std::shared_ptr<const CSettingList> settingList =
215         std::static_pointer_cast<const CSettingList>(setting);
216     if (settingList->GetElementType() != SettingType::String)
217       return false;
218 
219     pSettingString = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition());
220   }
221 
222   switch (pSettingString->GetOptionsType())
223   {
224     case SettingOptionsType::StaticTranslatable:
225     {
226       const TranslatableStringSettingOptions& settingOptions =
227           pSettingString->GetTranslatableOptions();
228       for (const auto& option : settingOptions)
229         options.push_back(StringSettingOption(Localize(option.first, localizer), option.second));
230       break;
231     }
232 
233     case SettingOptionsType::Static:
234     {
235       const StringSettingOptions& settingOptions = pSettingString->GetOptions();
236       options.insert(options.end(), settingOptions.begin(), settingOptions.end());
237       break;
238     }
239 
240     case SettingOptionsType::Dynamic:
241     {
242       StringSettingOptions settingOptions;
243       if (updateOptions)
244         settingOptions =
245             std::const_pointer_cast<CSettingString>(pSettingString)->UpdateDynamicOptions();
246       else
247         settingOptions = pSettingString->GetDynamicOptions();
248       options.insert(options.end(), settingOptions.begin(), settingOptions.end());
249       break;
250     }
251 
252     case SettingOptionsType::Unknown:
253     default:
254       return false;
255   }
256 
257   switch (pSettingString->GetOptionsSort())
258   {
259     case SettingOptionsSort::Ascending:
260       std::sort(options.begin(), options.end(),
261                 CompareSettingOptionAseconding<StringSettingOption>);
262       break;
263 
264     case SettingOptionsSort::Descending:
265       std::sort(options.begin(), options.end(),
266                 CompareSettingOptionDeseconding<StringSettingOption>);
267       break;
268 
269     case SettingOptionsSort::NoSorting:
270     default:
271       break;
272   }
273 
274   // this must be done after potentially calling CSettingString::UpdateDynamicOptions() because it
275   // can change the value of the setting
276   if (setting->GetType() == SettingType::String)
277     selectedOptions.insert(pSettingString->GetValue());
278   else if (setting->GetType() == SettingType::List)
279   {
280     std::vector<CVariant> list =
281         CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting));
282     for (const auto& itValue : list)
283       selectedOptions.insert(itValue.asString());
284   }
285   else
286     return false;
287 
288   return true;
289 }
290 
CGUIControlBaseSetting(int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)291 CGUIControlBaseSetting::CGUIControlBaseSetting(int id,
292                                                std::shared_ptr<CSetting> pSetting,
293                                                ILocalizer* localizer)
294   : m_id(id),
295     m_pSetting(std::move(pSetting)),
296     m_localizer(localizer),
297     m_delayed(false),
298     m_valid(true)
299 {
300 }
301 
IsEnabled() const302 bool CGUIControlBaseSetting::IsEnabled() const
303 {
304   return m_pSetting != NULL && m_pSetting->IsEnabled();
305 }
306 
UpdateFromControl()307 void CGUIControlBaseSetting::UpdateFromControl()
308 {
309   Update(true, true);
310 }
311 
UpdateFromSetting(bool updateDisplayOnly)312 void CGUIControlBaseSetting::UpdateFromSetting(bool updateDisplayOnly /* = false */)
313 {
314   Update(false, updateDisplayOnly);
315 }
316 
Localize(std::uint32_t code) const317 std::string CGUIControlBaseSetting::Localize(std::uint32_t code) const
318 {
319   return ::Localize(code, m_localizer);
320 }
321 
Update(bool fromControl,bool updateDisplayOnly)322 void CGUIControlBaseSetting::Update(bool fromControl, bool updateDisplayOnly)
323 {
324   if (fromControl || updateDisplayOnly)
325     return;
326 
327   CGUIControl* control = GetControl();
328   if (control == NULL)
329     return;
330 
331   control->SetEnabled(IsEnabled());
332   if (m_pSetting)
333     control->SetVisible(m_pSetting->IsVisible());
334   SetValid(true);
335 }
336 
CGUIControlRadioButtonSetting(CGUIRadioButtonControl * pRadioButton,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)337 CGUIControlRadioButtonSetting::CGUIControlRadioButtonSetting(CGUIRadioButtonControl* pRadioButton,
338                                                              int id,
339                                                              std::shared_ptr<CSetting> pSetting,
340                                                              ILocalizer* localizer)
341   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
342 {
343   m_pRadioButton = pRadioButton;
344   if (m_pRadioButton == NULL)
345     return;
346 
347   m_pRadioButton->SetID(id);
348 }
349 
350 CGUIControlRadioButtonSetting::~CGUIControlRadioButtonSetting() = default;
351 
OnClick()352 bool CGUIControlRadioButtonSetting::OnClick()
353 {
354   SetValid(std::static_pointer_cast<CSettingBool>(m_pSetting)
355                ->SetValue(!std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue()));
356   return IsValid();
357 }
358 
Update(bool fromControl,bool updateDisplayOnly)359 void CGUIControlRadioButtonSetting::Update(bool fromControl, bool updateDisplayOnly)
360 {
361   if (fromControl || m_pRadioButton == NULL)
362     return;
363 
364   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
365 
366   m_pRadioButton->SetSelected(std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue());
367 }
368 
CGUIControlSpinExSetting(CGUISpinControlEx * pSpin,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)369 CGUIControlSpinExSetting::CGUIControlSpinExSetting(CGUISpinControlEx* pSpin,
370                                                    int id,
371                                                    std::shared_ptr<CSetting> pSetting,
372                                                    ILocalizer* localizer)
373   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
374 {
375   m_pSpin = pSpin;
376   if (m_pSpin == NULL)
377     return;
378 
379   m_pSpin->SetID(id);
380 
381   const std::string& controlFormat = m_pSetting->GetControl()->GetFormat();
382   if (controlFormat == "number")
383   {
384     std::shared_ptr<CSettingNumber> pSettingNumber =
385         std::static_pointer_cast<CSettingNumber>(m_pSetting);
386     m_pSpin->SetType(SPIN_CONTROL_TYPE_FLOAT);
387     m_pSpin->SetFloatRange(static_cast<float>(pSettingNumber->GetMinimum()),
388                            static_cast<float>(pSettingNumber->GetMaximum()));
389     m_pSpin->SetFloatInterval(static_cast<float>(pSettingNumber->GetStep()));
390   }
391   else if (controlFormat == "integer")
392     m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT);
393   else if (controlFormat == "string")
394   {
395     m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT);
396 
397     if (m_pSetting->GetType() == SettingType::Integer)
398       FillIntegerSettingControl(false);
399     else if (m_pSetting->GetType() == SettingType::Number)
400     {
401       std::shared_ptr<CSettingNumber> pSettingNumber =
402           std::static_pointer_cast<CSettingNumber>(m_pSetting);
403       std::shared_ptr<const CSettingControlFormattedRange> control =
404           std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl());
405       int index = 0;
406       for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum();
407            value += pSettingNumber->GetStep(), index++)
408       {
409         std::string strLabel;
410         if (value == pSettingNumber->GetMinimum() && control->GetMinimumLabel() > -1)
411           strLabel = Localize(control->GetMinimumLabel());
412         else if (control->GetFormatLabel() > -1)
413           strLabel = StringUtils::Format(Localize(control->GetFormatLabel()).c_str(), value);
414         else
415           strLabel = StringUtils::Format(control->GetFormatString().c_str(), value);
416 
417         m_pSpin->AddLabel(strLabel, index);
418       }
419     }
420   }
421 }
422 
423 CGUIControlSpinExSetting::~CGUIControlSpinExSetting() = default;
424 
OnClick()425 bool CGUIControlSpinExSetting::OnClick()
426 {
427   if (m_pSpin == NULL)
428     return false;
429 
430   switch (m_pSetting->GetType())
431   {
432     case SettingType::Integer:
433       SetValid(std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSpin->GetValue()));
434       break;
435 
436     case SettingType::Number:
437     {
438       auto pSettingNumber = std::static_pointer_cast<CSettingNumber>(m_pSetting);
439       const auto& controlFormat = m_pSetting->GetControl()->GetFormat();
440       if (controlFormat == "number")
441         SetValid(pSettingNumber->SetValue(m_pSpin->GetFloatValue()));
442       else
443         SetValid(pSettingNumber->SetValue(pSettingNumber->GetMinimum() +
444                                           pSettingNumber->GetStep() * m_pSpin->GetValue()));
445 
446       break;
447     }
448 
449     case SettingType::String:
450       SetValid(std::static_pointer_cast<CSettingString>(m_pSetting)
451                    ->SetValue(m_pSpin->GetStringValue()));
452       break;
453 
454     default:
455       return false;
456   }
457 
458   return IsValid();
459 }
460 
Update(bool fromControl,bool updateDisplayOnly)461 void CGUIControlSpinExSetting::Update(bool fromControl, bool updateDisplayOnly)
462 {
463   if (fromControl || m_pSpin == NULL)
464     return;
465 
466   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
467 
468   FillControl(!updateDisplayOnly);
469 
470   if (!updateDisplayOnly)
471   {
472     // disable the spinner if it has less than two items
473     if (!m_pSpin->IsDisabled() && (m_pSpin->GetMaximum() - m_pSpin->GetMinimum()) == 0)
474       m_pSpin->SetEnabled(false);
475   }
476 }
477 
FillControl(bool updateValues)478 void CGUIControlSpinExSetting::FillControl(bool updateValues)
479 {
480   if (m_pSpin == NULL)
481     return;
482 
483   if (updateValues)
484     m_pSpin->Clear();
485 
486   const std::string& controlFormat = m_pSetting->GetControl()->GetFormat();
487   if (controlFormat == "number")
488   {
489     std::shared_ptr<CSettingNumber> pSettingNumber =
490         std::static_pointer_cast<CSettingNumber>(m_pSetting);
491     m_pSpin->SetFloatValue((float)pSettingNumber->GetValue());
492   }
493   else if (controlFormat == "integer")
494     FillIntegerSettingControl(updateValues);
495   else if (controlFormat == "string")
496   {
497     if (m_pSetting->GetType() == SettingType::Integer)
498       FillIntegerSettingControl(updateValues);
499     else if (m_pSetting->GetType() == SettingType::Number)
500       FillFloatSettingControl();
501     else if (m_pSetting->GetType() == SettingType::String)
502       FillStringSettingControl(updateValues);
503   }
504 }
505 
FillIntegerSettingControl(bool updateValues)506 void CGUIControlSpinExSetting::FillIntegerSettingControl(bool updateValues)
507 {
508   IntegerSettingOptions options;
509   std::set<int> selectedValues;
510   // get the integer options
511   if (!GetIntegerOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) ||
512       selectedValues.size() != 1)
513     return;
514 
515   if (updateValues)
516   {
517     // add them to the spinner
518     for (const auto& option : options)
519       m_pSpin->AddLabel(option.label, option.value);
520   }
521 
522   // and set the current value
523   m_pSpin->SetValue(*selectedValues.begin());
524 }
525 
FillFloatSettingControl()526 void CGUIControlSpinExSetting::FillFloatSettingControl()
527 {
528   std::shared_ptr<CSettingNumber> pSettingNumber =
529       std::static_pointer_cast<CSettingNumber>(m_pSetting);
530   std::shared_ptr<const CSettingControlFormattedRange> control =
531       std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl());
532   int index = 0;
533   int currentIndex = 0;
534   for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum();
535        value += pSettingNumber->GetStep(), index++)
536   {
537     if (value == pSettingNumber->GetValue())
538     {
539       currentIndex = index;
540       break;
541     }
542   }
543 
544   m_pSpin->SetValue(currentIndex);
545 }
546 
FillStringSettingControl(bool updateValues)547 void CGUIControlSpinExSetting::FillStringSettingControl(bool updateValues)
548 {
549   StringSettingOptions options;
550   std::set<std::string> selectedValues;
551   // get the string options
552   if (!GetStringOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) ||
553       selectedValues.size() != 1)
554     return;
555 
556   if (updateValues)
557   {
558     // add them to the spinner
559     for (const auto& option : options)
560       m_pSpin->AddLabel(option.label, option.value);
561   }
562 
563   // and set the current value
564   m_pSpin->SetStringValue(*selectedValues.begin());
565 }
566 
CGUIControlListSetting(CGUIButtonControl * pButton,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)567 CGUIControlListSetting::CGUIControlListSetting(CGUIButtonControl* pButton,
568                                                int id,
569                                                std::shared_ptr<CSetting> pSetting,
570                                                ILocalizer* localizer)
571   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
572 {
573   m_pButton = pButton;
574   if (m_pButton == NULL)
575     return;
576 
577   m_pButton->SetID(id);
578 }
579 
580 CGUIControlListSetting::~CGUIControlListSetting() = default;
581 
OnClick()582 bool CGUIControlListSetting::OnClick()
583 {
584   if (m_pButton == NULL)
585     return false;
586 
587   CGUIDialogSelect* dialog =
588       CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
589           WINDOW_DIALOG_SELECT);
590   if (dialog == NULL)
591     return false;
592 
593   CFileItemList options;
594   std::shared_ptr<const CSettingControlList> control =
595       std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl());
596   bool optionsValid = GetItems(m_pSetting, options, false);
597 
598   bool bValueAdded = false;
599   bool bAllowNewOption = false;
600   if (m_pSetting->GetType() == SettingType::List)
601   {
602     std::shared_ptr<const CSettingList> settingList =
603       std::static_pointer_cast<const CSettingList>(m_pSetting);
604     if (settingList->GetElementType() == SettingType::String)
605     {
606       bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition())
607         ->AllowNewOption();
608     }
609   }
610   if (!bAllowNewOption)
611   {
612     // Do not show dialog if
613     // * there are no items to be chosen or
614     // * only one value can be chosen and there are less than two items available
615     if (!optionsValid || options.Size() <= 0 || (!control->CanMultiSelect() && options.Size() <= 1))
616       return false;
617 
618     dialog->Reset();
619     dialog->SetHeading(CVariant{Localize(m_pSetting->GetLabel())});
620     dialog->SetItems(options);
621     dialog->SetMultiSelection(control->CanMultiSelect());
622     dialog->Open();
623 
624     if (!dialog->IsConfirmed())
625       return false;
626   }
627   else
628   {
629     // Possible to add items, as well as select from any options given
630     // Add any current values that are not options as items in list
631     std::vector<CVariant> list =
632       CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(m_pSetting));
633     for (const auto& value : list)
634     {
635       bool found = std::any_of(options.begin(), options.end(), [&](const auto& p) {
636         return p->GetProperty("value").asString() == value.asString();
637       });
638       if (!found)
639       {
640         CFileItemPtr item(new CFileItem(value.asString()));
641         item->SetProperty("value", value.asString());
642         item->Select(true);
643         options.Add(item);
644       }
645     }
646 
647     bool bRepeat = true;
648     while (bRepeat)
649     {
650       std::string strAddButton = Localize(control->GetAddButtonLabel());
651       if (strAddButton.empty())
652         strAddButton = Localize(15019); // "ADD"
653       dialog->Reset(); // Clears AddButtonPressed
654       dialog->SetHeading(CVariant{ Localize(m_pSetting->GetLabel()) });
655       dialog->SetItems(options);
656       dialog->SetMultiSelection(control->CanMultiSelect());
657       dialog->EnableButton2(bAllowNewOption, strAddButton);
658 
659       dialog->Open();
660 
661       if (!dialog->IsConfirmed())
662         return false;
663 
664       if (dialog->IsButton2Pressed())
665       {
666         // Get new list value
667         std::string strLabel;
668         bool bValidType = false;
669         while (!bValidType && CGUIKeyboardFactory::ShowAndGetInput(
670           strLabel, CVariant{ Localize(control->GetAddButtonLabel()) }, false))
671         {
672           // Validate new value is unique and truncate at any comma
673           StringUtils::Trim(strLabel);
674           strLabel = strLabel.substr(0, strLabel.find(','));
675           if (!strLabel.empty())
676           {
677             bValidType = !std::any_of(options.begin(), options.end(), [&](const auto& p) {
678               return p->GetProperty("value").asString() == strLabel;
679             });
680           }
681           if (bValidType)
682           { // Add new value to the list of options
683             CFileItemPtr pItem(new CFileItem(strLabel));
684             pItem->Select(true);
685             pItem->SetProperty("value", strLabel);
686             options.Add(pItem);
687             bValueAdded = true;
688           }
689         }
690       }
691       bRepeat = dialog->IsButton2Pressed();
692     }
693   }
694 
695   std::vector<CVariant> values;
696   for (int i : dialog->GetSelectedItems())
697   {
698     const CFileItemPtr item = options.Get(i);
699     if (item == NULL || !item->HasProperty("value"))
700       return false;
701 
702     values.push_back(item->GetProperty("value"));
703   }
704 
705   bool ret = false;
706   switch (m_pSetting->GetType())
707   {
708     case SettingType::Integer:
709       if (values.size() > 1)
710         return false;
711       ret = std::static_pointer_cast<CSettingInt>(m_pSetting)
712                 ->SetValue((int)values.at(0).asInteger());
713       break;
714 
715     case SettingType::String:
716       if (values.size() > 1)
717         return false;
718       ret = std::static_pointer_cast<CSettingString>(m_pSetting)->SetValue(values.at(0).asString());
719       break;
720 
721     case SettingType::List:
722       ret = CSettingUtils::SetList(std::static_pointer_cast<CSettingList>(m_pSetting), values);
723       break;
724 
725     default:
726       return false;
727   }
728 
729   if (ret)
730     UpdateFromSetting(!bValueAdded);
731   else
732     SetValid(false);
733 
734   return IsValid();
735 }
736 
Update(bool fromControl,bool updateDisplayOnly)737 void CGUIControlListSetting::Update(bool fromControl, bool updateDisplayOnly)
738 {
739   if (fromControl || m_pButton == NULL)
740     return;
741 
742   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
743 
744   CFileItemList options;
745   std::shared_ptr<const CSettingControlList> control =
746       std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl());
747   bool optionsValid = GetItems(m_pSetting, options, !updateDisplayOnly);
748 
749   bool bAllowNewOption = false;
750   if (m_pSetting->GetType() == SettingType::List)
751   {
752     std::shared_ptr<const CSettingList> settingList =
753         std::static_pointer_cast<const CSettingList>(m_pSetting);
754     if (settingList->GetElementType() == SettingType::String)
755     {
756       bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition())
757                             ->AllowNewOption();
758     }
759   }
760 
761   std::string label2;
762   if (optionsValid && !control->HideValue())
763   {
764     SettingControlListValueFormatter formatter = control->GetFormatter();
765     if (formatter)
766       label2 = formatter(m_pSetting);
767 
768     if (label2.empty() && bAllowNewOption)
769     {
770       const std::shared_ptr<const CSettingList> settingList =
771           std::static_pointer_cast<const CSettingList>(m_pSetting);
772       label2 = settingList->ToString();
773     }
774 
775     if (label2.empty())
776     {
777       std::vector<std::string> labels;
778       for (int index = 0; index < options.Size(); index++)
779       {
780         const CFileItemPtr pItem = options.Get(index);
781         if (pItem->IsSelected())
782           labels.push_back(pItem->GetLabel());
783       }
784 
785       label2 = StringUtils::Join(labels, ", ");
786     }
787   }
788 
789   m_pButton->SetLabel2(label2);
790 
791   if (!updateDisplayOnly)
792   {
793     // Disable the control if no items can be added and
794     // * there are no items to be chosen
795     // * only one value can be chosen and there are less than two items available
796     if (!m_pButton->IsDisabled() && !bAllowNewOption &&
797         (options.Size() <= 0 || (!control->CanMultiSelect() && options.Size() <= 1)))
798       m_pButton->SetEnabled(false);
799   }
800 }
801 
GetItems(const SettingConstPtr & setting,CFileItemList & items,bool updateItems) const802 bool CGUIControlListSetting::GetItems(const SettingConstPtr& setting,
803                                       CFileItemList& items,
804                                       bool updateItems) const
805 {
806   std::shared_ptr<const CSettingControlList> control =
807       std::static_pointer_cast<const CSettingControlList>(setting->GetControl());
808   const std::string& controlFormat = control->GetFormat();
809 
810   if (controlFormat == "integer")
811     return GetIntegerItems(setting, items, updateItems);
812   else if (controlFormat == "string")
813   {
814     if (setting->GetType() == SettingType::Integer ||
815         (setting->GetType() == SettingType::List &&
816          std::static_pointer_cast<const CSettingList>(setting)->GetElementType() ==
817              SettingType::Integer))
818       return GetIntegerItems(setting, items, updateItems);
819     else if (setting->GetType() == SettingType::String ||
820              (setting->GetType() == SettingType::List &&
821               std::static_pointer_cast<const CSettingList>(setting)->GetElementType() ==
822                   SettingType::String))
823       return GetStringItems(setting, items, updateItems);
824   }
825   else
826     return false;
827 
828   return true;
829 }
830 
GetIntegerItems(const SettingConstPtr & setting,CFileItemList & items,bool updateItems) const831 bool CGUIControlListSetting::GetIntegerItems(const SettingConstPtr& setting,
832                                              CFileItemList& items,
833                                              bool updateItems) const
834 {
835   IntegerSettingOptions options;
836   std::set<int> selectedValues;
837   // get the integer options
838   if (!GetIntegerOptions(setting, options, selectedValues, m_localizer, updateItems))
839     return false;
840 
841   // turn them into CFileItems and add them to the item list
842   for (const auto& option : options)
843     items.Add(GetFileItem(option.label, option.value, option.properties, selectedValues));
844 
845   return true;
846 }
847 
GetStringItems(const SettingConstPtr & setting,CFileItemList & items,bool updateItems) const848 bool CGUIControlListSetting::GetStringItems(const SettingConstPtr& setting,
849                                             CFileItemList& items,
850                                             bool updateItems) const
851 {
852   StringSettingOptions options;
853   std::set<std::string> selectedValues;
854   // get the string options
855   if (!GetStringOptions(setting, options, selectedValues, m_localizer, updateItems))
856     return false;
857 
858   // turn them into CFileItems and add them to the item list
859   for (const auto& option : options)
860     items.Add(GetFileItem(option.label, option.value, option.properties, selectedValues));
861 
862   return true;
863 }
864 
CGUIControlButtonSetting(CGUIButtonControl * pButton,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)865 CGUIControlButtonSetting::CGUIControlButtonSetting(CGUIButtonControl* pButton,
866                                                    int id,
867                                                    std::shared_ptr<CSetting> pSetting,
868                                                    ILocalizer* localizer)
869   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
870 {
871   m_pButton = pButton;
872   if (m_pButton == NULL)
873     return;
874 
875   m_pButton->SetID(id);
876 }
877 
878 CGUIControlButtonSetting::~CGUIControlButtonSetting() = default;
879 
OnClick()880 bool CGUIControlButtonSetting::OnClick()
881 {
882   if (m_pButton == NULL)
883     return false;
884 
885   std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl();
886   const std::string& controlType = control->GetType();
887   const std::string& controlFormat = control->GetFormat();
888   if (controlType == "button")
889   {
890     std::shared_ptr<const CSettingControlButton> buttonControl =
891         std::static_pointer_cast<const CSettingControlButton>(control);
892     if (controlFormat == "addon")
893     {
894       // prompt for the addon
895       std::shared_ptr<CSettingAddon> setting;
896       std::vector<std::string> addonIDs;
897       if (m_pSetting->GetType() == SettingType::List)
898       {
899         std::shared_ptr<CSettingList> settingList =
900             std::static_pointer_cast<CSettingList>(m_pSetting);
901         setting = std::static_pointer_cast<CSettingAddon>(settingList->GetDefinition());
902         for (const SettingPtr& addon : settingList->GetValue())
903           addonIDs.push_back(std::static_pointer_cast<CSettingAddon>(addon)->GetValue());
904       }
905       else
906       {
907         setting = std::static_pointer_cast<CSettingAddon>(m_pSetting);
908         addonIDs.push_back(setting->GetValue());
909       }
910 
911       if (CGUIWindowAddonBrowser::SelectAddonID(
912               setting->GetAddonType(), addonIDs, setting->AllowEmpty(),
913               buttonControl->ShowAddonDetails(), m_pSetting->GetType() == SettingType::List,
914               buttonControl->ShowInstalledAddons(), buttonControl->ShowInstallableAddons(),
915               buttonControl->ShowMoreAddons()) != 1)
916         return false;
917 
918       if (m_pSetting->GetType() == SettingType::List)
919         std::static_pointer_cast<CSettingList>(m_pSetting)->FromString(addonIDs);
920       else
921         SetValid(setting->SetValue(addonIDs[0]));
922     }
923     else if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image")
924       SetValid(GetPath(std::static_pointer_cast<CSettingPath>(m_pSetting), m_localizer));
925     else if (controlFormat == "date")
926     {
927       std::shared_ptr<CSettingDate> settingDate =
928           std::static_pointer_cast<CSettingDate>(m_pSetting);
929 
930       KODI::TIME::SystemTime systemdate;
931       settingDate->GetDate().GetAsSystemTime(systemdate);
932       if (CGUIDialogNumeric::ShowAndGetDate(systemdate, Localize(buttonControl->GetHeading())))
933         SetValid(settingDate->SetDate(CDateTime(systemdate)));
934     }
935     else if (controlFormat == "time")
936     {
937       std::shared_ptr<CSettingTime> settingTime =
938           std::static_pointer_cast<CSettingTime>(m_pSetting);
939 
940       KODI::TIME::SystemTime systemtime;
941       settingTime->GetTime().GetAsSystemTime(systemtime);
942 
943       if (CGUIDialogNumeric::ShowAndGetTime(systemtime, Localize(buttonControl->GetHeading())))
944         SetValid(settingTime->SetTime(CDateTime(systemtime)));
945     }
946     else if (controlFormat == "action")
947     {
948       // simply call the OnSettingAction callback and whoever knows what to
949       // do can do so (based on the setting's identification)
950       m_pSetting->OnSettingAction(m_pSetting);
951       SetValid(true);
952     }
953   }
954   else if (controlType == "slider")
955   {
956     float value, min, step, max;
957     if (m_pSetting->GetType() == SettingType::Integer)
958     {
959       std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
960       value = (float)settingInt->GetValue();
961       min = (float)settingInt->GetMinimum();
962       step = (float)settingInt->GetStep();
963       max = (float)settingInt->GetMaximum();
964     }
965     else if (m_pSetting->GetType() == SettingType::Number)
966     {
967       std::shared_ptr<CSettingNumber> settingNumber =
968           std::static_pointer_cast<CSettingNumber>(m_pSetting);
969       value = (float)settingNumber->GetValue();
970       min = (float)settingNumber->GetMinimum();
971       step = (float)settingNumber->GetStep();
972       max = (float)settingNumber->GetMaximum();
973     }
974     else
975       return false;
976 
977     std::shared_ptr<const CSettingControlSlider> sliderControl =
978         std::static_pointer_cast<const CSettingControlSlider>(control);
979     CGUIDialogSlider::ShowAndGetInput(Localize(sliderControl->GetHeading()), value, min, step, max,
980                                       this, NULL);
981     SetValid(true);
982   }
983 
984   // update the displayed value
985   UpdateFromSetting(true);
986 
987   return IsValid();
988 }
989 
Update(bool fromControl,bool updateDisplayOnly)990 void CGUIControlButtonSetting::Update(bool fromControl, bool updateDisplayOnly)
991 {
992   if (fromControl || m_pButton == NULL)
993     return;
994 
995   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
996 
997   std::string strText;
998   std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl();
999   const std::string& controlType = control->GetType();
1000   const std::string& controlFormat = control->GetFormat();
1001 
1002   if (controlType == "button")
1003   {
1004     if (!std::static_pointer_cast<const CSettingControlButton>(control)->HideValue())
1005     {
1006       auto setting = m_pSetting;
1007       if (m_pSetting->GetType() == SettingType::List)
1008         setting = std::static_pointer_cast<CSettingList>(m_pSetting)->GetDefinition();
1009 
1010       switch (setting->GetType())
1011       {
1012         case SettingType::String:
1013         {
1014           if (controlFormat == "addon")
1015           {
1016             std::vector<std::string> addonIDs;
1017             if (m_pSetting->GetType() == SettingType::List)
1018             {
1019               for (const auto& addonSetting :
1020                    std::static_pointer_cast<CSettingList>(m_pSetting)->GetValue())
1021                 addonIDs.push_back(
1022                     std::static_pointer_cast<CSettingAddon>(addonSetting)->GetValue());
1023             }
1024             else
1025               addonIDs.push_back(std::static_pointer_cast<CSettingString>(setting)->GetValue());
1026 
1027             std::vector<std::string> addonNames;
1028             for (const auto& addonID : addonIDs)
1029             {
1030               ADDON::AddonPtr addon;
1031               if (CServiceBroker::GetAddonMgr().GetAddon(addonID, addon, ADDON::ADDON_UNKNOWN,
1032                                                          ADDON::OnlyEnabled::YES))
1033                 addonNames.push_back(addon->Name());
1034             }
1035 
1036             if (addonNames.empty())
1037               strText = g_localizeStrings.Get(231); // None
1038             else
1039               strText = StringUtils::Join(addonNames, ", ");
1040           }
1041           else
1042           {
1043             std::string strValue = std::static_pointer_cast<CSettingString>(setting)->GetValue();
1044             if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image")
1045             {
1046               std::string shortPath;
1047               if (CUtil::MakeShortenPath(strValue, shortPath, 30))
1048                 strText = shortPath;
1049             }
1050             else if (controlFormat == "infolabel")
1051             {
1052               strText = strValue;
1053               if (strText.empty())
1054                 strText = g_localizeStrings.Get(231); // None
1055             }
1056             else
1057               strText = strValue;
1058           }
1059 
1060           break;
1061         }
1062 
1063         case SettingType::Action:
1064         {
1065           // CSettingAction.
1066           // Note: This can be removed once all settings use a proper control & format combination.
1067           // CSettingAction is strictly speaking not designed to have a label2, it does not even have a value.
1068           strText = m_pButton->GetLabel2();
1069 
1070           break;
1071         }
1072 
1073         default:
1074           break;
1075       }
1076     }
1077   }
1078   else if (controlType == "slider")
1079   {
1080     switch (m_pSetting->GetType())
1081     {
1082       case SettingType::Integer:
1083       {
1084         std::shared_ptr<const CSettingInt> settingInt =
1085             std::static_pointer_cast<CSettingInt>(m_pSetting);
1086         strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(),
1087                                                     settingInt->GetMinimum(), settingInt->GetStep(),
1088                                                     settingInt->GetMaximum(), m_localizer);
1089         break;
1090       }
1091 
1092       case SettingType::Number:
1093       {
1094         std::shared_ptr<const CSettingNumber> settingNumber =
1095             std::static_pointer_cast<CSettingNumber>(m_pSetting);
1096         strText = CGUIControlSliderSetting::GetText(
1097             m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(),
1098             settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer);
1099         break;
1100       }
1101 
1102       default:
1103         break;
1104     }
1105   }
1106 
1107   m_pButton->SetLabel2(strText);
1108 }
1109 
GetPath(const std::shared_ptr<CSettingPath> & pathSetting,ILocalizer * localizer)1110 bool CGUIControlButtonSetting::GetPath(const std::shared_ptr<CSettingPath>& pathSetting,
1111                                        ILocalizer* localizer)
1112 {
1113   if (pathSetting == NULL)
1114     return false;
1115 
1116   std::string path = pathSetting->GetValue();
1117 
1118   VECSOURCES shares;
1119   bool localSharesOnly = false;
1120   const std::vector<std::string>& sources = pathSetting->GetSources();
1121   for (const auto& source : sources)
1122   {
1123     if (StringUtils::EqualsNoCase(source, "local"))
1124       localSharesOnly = true;
1125     else
1126     {
1127       VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(source);
1128       if (sources != NULL)
1129         shares.insert(shares.end(), sources->begin(), sources->end());
1130     }
1131   }
1132 
1133   CServiceBroker::GetMediaManager().GetLocalDrives(shares);
1134   if (!localSharesOnly)
1135     CServiceBroker::GetMediaManager().GetNetworkLocations(shares);
1136 
1137   bool result = false;
1138   std::shared_ptr<const CSettingControlButton> control =
1139       std::static_pointer_cast<const CSettingControlButton>(pathSetting->GetControl());
1140   const auto heading = ::Localize(control->GetHeading(), localizer);
1141   if (control->GetFormat() == "file")
1142     result = CGUIDialogFileBrowser::ShowAndGetFile(
1143         shares, pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider()), heading, path,
1144         control->UseImageThumbs(), control->UseFileDirectories());
1145   else if (control->GetFormat() == "image")
1146   {
1147     /* Check setting contains own masking, to filter required image type.
1148      * e.g. png only needed
1149      * <constraints>
1150      *   <masking>*.png</masking>
1151      * </constraints>
1152      * <control type="button" format="image">
1153      *   ...
1154      */
1155     std::string ext = pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider());
1156     if (ext.empty())
1157       ext = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
1158     result = CGUIDialogFileBrowser::ShowAndGetFile(shares, ext, heading, path, true);
1159   }
1160   else
1161     result =
1162         CGUIDialogFileBrowser::ShowAndGetDirectory(shares, heading, path, pathSetting->Writable());
1163 
1164   if (!result)
1165     return false;
1166 
1167   return pathSetting->SetValue(path);
1168 }
1169 
OnSliderChange(void * data,CGUISliderControl * slider)1170 void CGUIControlButtonSetting::OnSliderChange(void* data, CGUISliderControl* slider)
1171 {
1172   if (slider == NULL)
1173     return;
1174 
1175   std::string strText;
1176   switch (m_pSetting->GetType())
1177   {
1178     case SettingType::Integer:
1179     {
1180       std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
1181       if (settingInt->SetValue(slider->GetIntValue()))
1182         strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(),
1183                                                     settingInt->GetMinimum(), settingInt->GetStep(),
1184                                                     settingInt->GetMaximum(), m_localizer);
1185       break;
1186     }
1187 
1188     case SettingType::Number:
1189     {
1190       std::shared_ptr<CSettingNumber> settingNumber =
1191           std::static_pointer_cast<CSettingNumber>(m_pSetting);
1192       if (settingNumber->SetValue(static_cast<double>(slider->GetFloatValue())))
1193         strText = CGUIControlSliderSetting::GetText(
1194             m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(),
1195             settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer);
1196       break;
1197     }
1198 
1199     default:
1200       break;
1201   }
1202 
1203   if (!strText.empty())
1204     slider->SetTextValue(strText);
1205 }
1206 
CGUIControlEditSetting(CGUIEditControl * pEdit,int id,const std::shared_ptr<CSetting> & pSetting,ILocalizer * localizer)1207 CGUIControlEditSetting::CGUIControlEditSetting(CGUIEditControl* pEdit,
1208                                                int id,
1209                                                const std::shared_ptr<CSetting>& pSetting,
1210                                                ILocalizer* localizer)
1211   : CGUIControlBaseSetting(id, pSetting, localizer)
1212 {
1213   std::shared_ptr<const CSettingControlEdit> control =
1214       std::static_pointer_cast<const CSettingControlEdit>(pSetting->GetControl());
1215   m_pEdit = pEdit;
1216   if (m_pEdit == NULL)
1217     return;
1218 
1219   m_pEdit->SetID(id);
1220   int heading = m_pSetting->GetLabel();
1221   if (control->GetHeading() > 0)
1222     heading = control->GetHeading();
1223   if (heading < 0)
1224     heading = 0;
1225 
1226   CGUIEditControl::INPUT_TYPE inputType = CGUIEditControl::INPUT_TYPE_TEXT;
1227   const std::string& controlFormat = control->GetFormat();
1228   if (controlFormat == "string")
1229   {
1230     if (control->IsHidden())
1231       inputType = CGUIEditControl::INPUT_TYPE_PASSWORD;
1232   }
1233   else if (controlFormat == "integer" || controlFormat == "number")
1234   {
1235     if (control->VerifyNewValue())
1236       inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW;
1237     else
1238       inputType = CGUIEditControl::INPUT_TYPE_NUMBER;
1239   }
1240   else if (controlFormat == "ip")
1241     inputType = CGUIEditControl::INPUT_TYPE_IPADDRESS;
1242   else if (controlFormat == "md5")
1243     inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_MD5;
1244 
1245   m_pEdit->SetInputType(inputType, heading);
1246 
1247   // this will automatically trigger validation so it must be executed after
1248   // having set the value of the control based on the value of the setting
1249   m_pEdit->SetInputValidation(InputValidation, this);
1250 }
1251 
1252 CGUIControlEditSetting::~CGUIControlEditSetting() = default;
1253 
OnClick()1254 bool CGUIControlEditSetting::OnClick()
1255 {
1256   if (m_pEdit == NULL)
1257     return false;
1258 
1259   // update our string
1260   if (m_pSetting->GetControl()->GetFormat() == "urlencoded")
1261   {
1262     std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting =
1263         std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting);
1264     SetValid(urlEncodedSetting->SetDecodedValue(m_pEdit->GetLabel2()));
1265   }
1266   else
1267     SetValid(m_pSetting->FromString(m_pEdit->GetLabel2()));
1268 
1269   return IsValid();
1270 }
1271 
Update(bool fromControl,bool updateDisplayOnly)1272 void CGUIControlEditSetting::Update(bool fromControl, bool updateDisplayOnly)
1273 {
1274   if (fromControl || m_pEdit == NULL)
1275     return;
1276 
1277   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
1278 
1279   std::shared_ptr<const CSettingControlEdit> control =
1280       std::static_pointer_cast<const CSettingControlEdit>(m_pSetting->GetControl());
1281 
1282   if (control->GetFormat() == "urlencoded")
1283   {
1284     std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting =
1285         std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting);
1286     m_pEdit->SetLabel2(urlEncodedSetting->GetDecodedValue());
1287   }
1288   else
1289     m_pEdit->SetLabel2(m_pSetting->ToString());
1290 }
1291 
InputValidation(const std::string & input,void * data)1292 bool CGUIControlEditSetting::InputValidation(const std::string& input, void* data)
1293 {
1294   if (data == NULL)
1295     return true;
1296 
1297   CGUIControlEditSetting* editControl = reinterpret_cast<CGUIControlEditSetting*>(data);
1298   if (editControl == NULL || editControl->GetSetting() == NULL)
1299     return true;
1300 
1301   editControl->SetValid(editControl->GetSetting()->CheckValidity(input));
1302   return editControl->IsValid();
1303 }
1304 
CGUIControlSliderSetting(CGUISettingsSliderControl * pSlider,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)1305 CGUIControlSliderSetting::CGUIControlSliderSetting(CGUISettingsSliderControl* pSlider,
1306                                                    int id,
1307                                                    std::shared_ptr<CSetting> pSetting,
1308                                                    ILocalizer* localizer)
1309   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
1310 {
1311   m_pSlider = pSlider;
1312   if (m_pSlider == NULL)
1313     return;
1314 
1315   m_pSlider->SetID(id);
1316 
1317   switch (m_pSetting->GetType())
1318   {
1319     case SettingType::Integer:
1320     {
1321       std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
1322       if (m_pSetting->GetControl()->GetFormat() == "percentage")
1323         m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE);
1324       else
1325       {
1326         m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT);
1327         m_pSlider->SetRange(settingInt->GetMinimum(), settingInt->GetMaximum());
1328       }
1329       m_pSlider->SetIntInterval(settingInt->GetStep());
1330       break;
1331     }
1332 
1333     case SettingType::Number:
1334     {
1335       std::shared_ptr<CSettingNumber> settingNumber =
1336           std::static_pointer_cast<CSettingNumber>(m_pSetting);
1337       m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT);
1338       m_pSlider->SetFloatRange(static_cast<float>(settingNumber->GetMinimum()),
1339                                static_cast<float>(settingNumber->GetMaximum()));
1340       m_pSlider->SetFloatInterval(static_cast<float>(settingNumber->GetStep()));
1341       break;
1342     }
1343 
1344     default:
1345       break;
1346   }
1347 }
1348 
1349 CGUIControlSliderSetting::~CGUIControlSliderSetting() = default;
1350 
OnClick()1351 bool CGUIControlSliderSetting::OnClick()
1352 {
1353   if (m_pSlider == NULL)
1354     return false;
1355 
1356   switch (m_pSetting->GetType())
1357   {
1358     case SettingType::Integer:
1359       SetValid(
1360           std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSlider->GetIntValue()));
1361       break;
1362 
1363     case SettingType::Number:
1364       SetValid(std::static_pointer_cast<CSettingNumber>(m_pSetting)
1365                    ->SetValue(m_pSlider->GetFloatValue()));
1366       break;
1367 
1368     default:
1369       return false;
1370   }
1371 
1372   return IsValid();
1373 }
1374 
Update(bool fromControl,bool updateDisplayOnly)1375 void CGUIControlSliderSetting::Update(bool fromControl, bool updateDisplayOnly)
1376 {
1377   if (m_pSlider == NULL)
1378     return;
1379 
1380   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
1381 
1382   std::string strText;
1383   switch (m_pSetting->GetType())
1384   {
1385     case SettingType::Integer:
1386     {
1387       std::shared_ptr<const CSettingInt> settingInt =
1388           std::static_pointer_cast<CSettingInt>(m_pSetting);
1389       int value;
1390       if (fromControl)
1391         value = m_pSlider->GetIntValue();
1392       else
1393       {
1394         value = std::static_pointer_cast<CSettingInt>(m_pSetting)->GetValue();
1395         m_pSlider->SetIntValue(value);
1396       }
1397 
1398       strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingInt->GetMinimum(),
1399                                                   settingInt->GetStep(), settingInt->GetMaximum(),
1400                                                   m_localizer);
1401       break;
1402     }
1403 
1404     case SettingType::Number:
1405     {
1406       std::shared_ptr<const CSettingNumber> settingNumber =
1407           std::static_pointer_cast<CSettingNumber>(m_pSetting);
1408       double value;
1409       if (fromControl)
1410         value = m_pSlider->GetFloatValue();
1411       else
1412       {
1413         value = std::static_pointer_cast<CSettingNumber>(m_pSetting)->GetValue();
1414         m_pSlider->SetFloatValue((float)value);
1415       }
1416 
1417       strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingNumber->GetMinimum(),
1418                                                   settingNumber->GetStep(),
1419                                                   settingNumber->GetMaximum(), m_localizer);
1420       break;
1421     }
1422 
1423     default:
1424       break;
1425   }
1426 
1427   if (!strText.empty())
1428     m_pSlider->SetTextValue(strText);
1429 }
1430 
GetText(const std::shared_ptr<CSetting> & setting,const CVariant & value,const CVariant & minimum,const CVariant & step,const CVariant & maximum,ILocalizer * localizer)1431 std::string CGUIControlSliderSetting::GetText(const std::shared_ptr<CSetting>& setting,
1432                                               const CVariant& value,
1433                                               const CVariant& minimum,
1434                                               const CVariant& step,
1435                                               const CVariant& maximum,
1436                                               ILocalizer* localizer)
1437 {
1438   if (setting == NULL || !(value.isInteger() || value.isDouble()))
1439     return "";
1440 
1441   const auto control = std::static_pointer_cast<const CSettingControlSlider>(setting->GetControl());
1442   if (control == NULL)
1443     return "";
1444 
1445   SettingControlSliderFormatter formatter = control->GetFormatter();
1446   if (formatter != NULL)
1447     return formatter(control, value, minimum, step, maximum);
1448 
1449   std::string formatString = control->GetFormatString();
1450   if (control->GetFormatLabel() > -1)
1451     formatString = ::Localize(control->GetFormatLabel(), localizer);
1452 
1453   std::string formattedString;
1454   if (FormatText(formatString, value, setting->GetId(), formattedString))
1455     return formattedString;
1456 
1457   // fall back to default formatting
1458   formatString = control->GetDefaultFormatString();
1459   if (FormatText(formatString, value, setting->GetId(), formattedString))
1460     return formattedString;
1461 
1462   return "";
1463 }
1464 
FormatText(const std::string & formatString,const CVariant & value,const std::string & settingId,std::string & formattedText)1465 bool CGUIControlSliderSetting::FormatText(const std::string& formatString,
1466                                           const CVariant& value,
1467                                           const std::string& settingId,
1468                                           std::string& formattedText)
1469 {
1470   try
1471   {
1472     if (value.isDouble())
1473       formattedText = StringUtils::Format(formatString.c_str(), value.asDouble());
1474     else
1475       formattedText =
1476           StringUtils::Format(formatString.c_str(), static_cast<int>(value.asInteger()));
1477   }
1478   catch (const std::runtime_error& err)
1479   {
1480     CLog::Log(LOGERROR, "Invalid formatting with string \"{}\" for setting \"{}\": {}",
1481               formatString, settingId, err.what());
1482     return false;
1483   }
1484 
1485   return true;
1486 }
1487 
CGUIControlRangeSetting(CGUISettingsSliderControl * pSlider,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)1488 CGUIControlRangeSetting::CGUIControlRangeSetting(CGUISettingsSliderControl* pSlider,
1489                                                  int id,
1490                                                  std::shared_ptr<CSetting> pSetting,
1491                                                  ILocalizer* localizer)
1492   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
1493 {
1494   m_pSlider = pSlider;
1495   if (m_pSlider == NULL)
1496     return;
1497 
1498   m_pSlider->SetID(id);
1499   m_pSlider->SetRangeSelection(true);
1500 
1501   if (m_pSetting->GetType() == SettingType::List)
1502   {
1503     std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
1504     SettingConstPtr listDefintion = settingList->GetDefinition();
1505     switch (listDefintion->GetType())
1506     {
1507       case SettingType::Integer:
1508       {
1509         std::shared_ptr<const CSettingInt> listDefintionInt =
1510             std::static_pointer_cast<const CSettingInt>(listDefintion);
1511         if (m_pSetting->GetControl()->GetFormat() == "percentage")
1512           m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE);
1513         else
1514         {
1515           m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT);
1516           m_pSlider->SetRange(listDefintionInt->GetMinimum(), listDefintionInt->GetMaximum());
1517         }
1518         m_pSlider->SetIntInterval(listDefintionInt->GetStep());
1519         break;
1520       }
1521 
1522       case SettingType::Number:
1523       {
1524         std::shared_ptr<const CSettingNumber> listDefinitionNumber =
1525             std::static_pointer_cast<const CSettingNumber>(listDefintion);
1526         m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT);
1527         m_pSlider->SetFloatRange(static_cast<float>(listDefinitionNumber->GetMinimum()),
1528                                  static_cast<float>(listDefinitionNumber->GetMaximum()));
1529         m_pSlider->SetFloatInterval(static_cast<float>(listDefinitionNumber->GetStep()));
1530         break;
1531       }
1532 
1533       default:
1534         break;
1535     }
1536   }
1537 }
1538 
1539 CGUIControlRangeSetting::~CGUIControlRangeSetting() = default;
1540 
OnClick()1541 bool CGUIControlRangeSetting::OnClick()
1542 {
1543   if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List)
1544     return false;
1545 
1546   std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
1547   const SettingList& settingListValues = settingList->GetValue();
1548   if (settingListValues.size() != 2)
1549     return false;
1550 
1551   std::vector<CVariant> values;
1552   SettingConstPtr listDefintion = settingList->GetDefinition();
1553   switch (listDefintion->GetType())
1554   {
1555     case SettingType::Integer:
1556       values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower));
1557       values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper));
1558       break;
1559 
1560     case SettingType::Number:
1561       values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower));
1562       values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper));
1563       break;
1564 
1565     default:
1566       return false;
1567   }
1568 
1569   if (values.size() != 2)
1570     return false;
1571 
1572   SetValid(CSettingUtils::SetList(settingList, values));
1573   return IsValid();
1574 }
1575 
Update(bool fromControl,bool updateDisplayOnly)1576 void CGUIControlRangeSetting::Update(bool fromControl, bool updateDisplayOnly)
1577 {
1578   if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List)
1579     return;
1580 
1581   CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
1582 
1583   std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
1584   const SettingList& settingListValues = settingList->GetValue();
1585   if (settingListValues.size() != 2)
1586     return;
1587 
1588   SettingConstPtr listDefintion = settingList->GetDefinition();
1589   std::shared_ptr<const CSettingControlRange> controlRange =
1590       std::static_pointer_cast<const CSettingControlRange>(m_pSetting->GetControl());
1591   const std::string& controlFormat = controlRange->GetFormat();
1592 
1593   std::string strText;
1594   std::string strTextLower, strTextUpper;
1595   std::string formatString =
1596       Localize(controlRange->GetFormatLabel() > -1 ? controlRange->GetFormatLabel() : 21469);
1597   std::string valueFormat = controlRange->GetValueFormat();
1598   if (controlRange->GetValueFormatLabel() > -1)
1599     valueFormat = Localize(controlRange->GetValueFormatLabel());
1600 
1601   switch (listDefintion->GetType())
1602   {
1603     case SettingType::Integer:
1604     {
1605       int valueLower, valueUpper;
1606       if (fromControl)
1607       {
1608         valueLower = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower);
1609         valueUpper = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper);
1610       }
1611       else
1612       {
1613         valueLower = std::static_pointer_cast<CSettingInt>(settingListValues[0])->GetValue();
1614         valueUpper = std::static_pointer_cast<CSettingInt>(settingListValues[1])->GetValue();
1615         m_pSlider->SetIntValue(valueLower, CGUISliderControl::RangeSelectorLower);
1616         m_pSlider->SetIntValue(valueUpper, CGUISliderControl::RangeSelectorUpper);
1617       }
1618 
1619       if (controlFormat == "date" || controlFormat == "time")
1620       {
1621         CDateTime dateLower((time_t)valueLower);
1622         CDateTime dateUpper((time_t)valueUpper);
1623 
1624         if (controlFormat == "date")
1625         {
1626           if (valueFormat.empty())
1627           {
1628             strTextLower = dateLower.GetAsLocalizedDate();
1629             strTextUpper = dateUpper.GetAsLocalizedDate();
1630           }
1631           else
1632           {
1633             strTextLower = dateLower.GetAsLocalizedDate(valueFormat);
1634             strTextUpper = dateUpper.GetAsLocalizedDate(valueFormat);
1635           }
1636         }
1637         else
1638         {
1639           if (valueFormat.empty())
1640             valueFormat = "mm:ss";
1641 
1642           strTextLower = dateLower.GetAsLocalizedTime(valueFormat);
1643           strTextUpper = dateUpper.GetAsLocalizedTime(valueFormat);
1644         }
1645       }
1646       else
1647       {
1648         strTextLower = StringUtils::Format(valueFormat.c_str(), valueLower);
1649         strTextUpper = StringUtils::Format(valueFormat.c_str(), valueUpper);
1650       }
1651 
1652       if (valueLower != valueUpper)
1653         strText =
1654             StringUtils::Format(formatString.c_str(), strTextLower.c_str(), strTextUpper.c_str());
1655       else
1656         strText = strTextLower;
1657       break;
1658     }
1659 
1660     case SettingType::Number:
1661     {
1662       double valueLower, valueUpper;
1663       if (fromControl)
1664       {
1665         valueLower =
1666             static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower));
1667         valueUpper =
1668             static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper));
1669       }
1670       else
1671       {
1672         valueLower = std::static_pointer_cast<CSettingNumber>(settingListValues[0])->GetValue();
1673         valueUpper = std::static_pointer_cast<CSettingNumber>(settingListValues[1])->GetValue();
1674         m_pSlider->SetFloatValue((float)valueLower, CGUISliderControl::RangeSelectorLower);
1675         m_pSlider->SetFloatValue((float)valueUpper, CGUISliderControl::RangeSelectorUpper);
1676       }
1677 
1678       strTextLower = StringUtils::Format(valueFormat.c_str(), valueLower);
1679       if (valueLower != valueUpper)
1680       {
1681         strTextUpper = StringUtils::Format(valueFormat.c_str(), valueUpper);
1682         strText =
1683             StringUtils::Format(formatString.c_str(), strTextLower.c_str(), strTextUpper.c_str());
1684       }
1685       else
1686         strText = strTextLower;
1687       break;
1688     }
1689 
1690     default:
1691       strText.clear();
1692       break;
1693   }
1694 
1695   if (!strText.empty())
1696     m_pSlider->SetTextValue(strText);
1697 }
1698 
CGUIControlSeparatorSetting(CGUIImage * pImage,int id,ILocalizer * localizer)1699 CGUIControlSeparatorSetting::CGUIControlSeparatorSetting(CGUIImage* pImage,
1700                                                          int id,
1701                                                          ILocalizer* localizer)
1702   : CGUIControlBaseSetting(id, NULL, localizer)
1703 {
1704   m_pImage = pImage;
1705   if (m_pImage == NULL)
1706     return;
1707 
1708   m_pImage->SetID(id);
1709 }
1710 
1711 CGUIControlSeparatorSetting::~CGUIControlSeparatorSetting() = default;
1712 
CGUIControlGroupTitleSetting(CGUILabelControl * pLabel,int id,ILocalizer * localizer)1713 CGUIControlGroupTitleSetting::CGUIControlGroupTitleSetting(CGUILabelControl* pLabel,
1714                                                            int id,
1715                                                            ILocalizer* localizer)
1716   : CGUIControlBaseSetting(id, NULL, localizer)
1717 {
1718   m_pLabel = pLabel;
1719   if (m_pLabel == NULL)
1720     return;
1721 
1722   m_pLabel->SetID(id);
1723 }
1724 
1725 CGUIControlGroupTitleSetting::~CGUIControlGroupTitleSetting() = default;
1726 
CGUIControlLabelSetting(CGUIButtonControl * pButton,int id,std::shared_ptr<CSetting> pSetting,ILocalizer * localizer)1727 CGUIControlLabelSetting::CGUIControlLabelSetting(CGUIButtonControl* pButton,
1728                                                  int id,
1729                                                  std::shared_ptr<CSetting> pSetting,
1730                                                  ILocalizer* localizer)
1731   : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
1732 {
1733   m_pButton = pButton;
1734   if (m_pButton == NULL)
1735     return;
1736 
1737   m_pButton->SetID(id);
1738   UpdateFromSetting();
1739 }
1740