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 "Skin.h"
10 #include "AddonManager.h"
11 #include "ServiceBroker.h"
12 #include "Util.h"
13 #include "dialogs/GUIDialogKaiToast.h"
14 // fallback for new skin resolution code
15 #include "filesystem/Directory.h"
16 #include "filesystem/File.h"
17 #include "filesystem/SpecialProtocol.h"
18 #include "guilib/GUIComponent.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "guilib/LocalizeStrings.h"
21 #include "guilib/WindowIDs.h"
22 #include "messaging/ApplicationMessenger.h"
23 #include "messaging/helpers/DialogHelper.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "settings/lib/Setting.h"
27 #include "settings/lib/SettingDefinitions.h"
28 #include "threads/Timer.h"
29 #include "utils/log.h"
30 #include "utils/StringUtils.h"
31 #include "utils/URIUtils.h"
32 #include "utils/XMLUtils.h"
33 #include "utils/Variant.h"
34 
35 #define XML_SETTINGS      "settings"
36 #define XML_SETTING       "setting"
37 #define XML_ATTR_TYPE     "type"
38 #define XML_ATTR_NAME     "name"
39 #define XML_ATTR_ID       "id"
40 
41 using namespace XFILE;
42 using namespace KODI::MESSAGING;
43 
44 using KODI::MESSAGING::HELPERS::DialogResponse;
45 
46 std::shared_ptr<ADDON::CSkinInfo> g_SkinInfo;
47 
48 namespace ADDON
49 {
50 
51 class CSkinSettingUpdateHandler : private ITimerCallback
52 {
53 public:
CSkinSettingUpdateHandler(CAddon & addon)54   CSkinSettingUpdateHandler(CAddon& addon)
55   : m_addon(addon), m_timer(this) {}
56   ~CSkinSettingUpdateHandler() override = default;
57 
58   void OnTimeout() override;
59   void TriggerSave();
60 private:
61   static constexpr int DELAY = 500;
62 
63   CAddon &m_addon;
64   CTimer m_timer;
65 };
66 
Serialize(TiXmlElement * parent) const67 bool CSkinSetting::Serialize(TiXmlElement* parent) const
68 {
69   if (parent == nullptr)
70     return false;
71 
72   TiXmlElement setting(XML_SETTING);
73   setting.SetAttribute(XML_ATTR_ID, name.c_str());
74   setting.SetAttribute(XML_ATTR_TYPE, GetType());
75 
76   if (!SerializeSetting(&setting))
77     return false;
78 
79   parent->InsertEndChild(setting);
80 
81   return true;
82 }
83 
Deserialize(const TiXmlElement * element)84 bool CSkinSetting::Deserialize(const TiXmlElement* element)
85 {
86   if (element == nullptr)
87     return false;
88 
89   name = XMLUtils::GetAttribute(element, XML_ATTR_ID);
90 
91   // backwards compatibility for guisettings.xml
92   if (name.empty())
93     name = XMLUtils::GetAttribute(element, XML_ATTR_NAME);
94 
95   return true;
96 }
97 
Deserialize(const TiXmlElement * element)98 bool CSkinSettingString::Deserialize(const TiXmlElement* element)
99 {
100   value.clear();
101 
102   if (!CSkinSetting::Deserialize(element))
103     return false;
104 
105   if (element->FirstChild() != nullptr)
106     value = element->FirstChild()->Value();
107 
108   return true;
109 }
110 
SerializeSetting(TiXmlElement * element) const111 bool CSkinSettingString::SerializeSetting(TiXmlElement* element) const
112 {
113   if (element == nullptr)
114     return false;
115 
116   TiXmlText xmlValue(value);
117   element->InsertEndChild(xmlValue);
118 
119   return true;
120 }
121 
Deserialize(const TiXmlElement * element)122 bool CSkinSettingBool::Deserialize(const TiXmlElement* element)
123 {
124   value = false;
125 
126   if (!CSkinSetting::Deserialize(element))
127     return false;
128 
129   if (element->FirstChild() != nullptr)
130     value = StringUtils::EqualsNoCase(element->FirstChild()->ValueStr(), "true");
131 
132   return true;
133 }
134 
SerializeSetting(TiXmlElement * element) const135 bool CSkinSettingBool::SerializeSetting(TiXmlElement* element) const
136 {
137   if (element == nullptr)
138     return false;
139 
140   TiXmlText xmlValue(value ? "true" : "false");
141   element->InsertEndChild(xmlValue);
142 
143   return true;
144 }
145 
CSkinInfo(const AddonInfoPtr & addonInfo,const RESOLUTION_INFO & resolution)146 CSkinInfo::CSkinInfo(
147     const AddonInfoPtr& addonInfo,
148     const RESOLUTION_INFO& resolution /* = RESOLUTION_INFO() */)
149     : CAddon(addonInfo, ADDON_SKIN),
150       m_defaultRes(resolution),
151       m_effectsSlowDown(1.f),
152       m_debugging(false)
153   {
154     m_settingsUpdateHandler.reset(new CSkinSettingUpdateHandler(*this));
155   }
156 
CSkinInfo(const AddonInfoPtr & addonInfo)157 CSkinInfo::CSkinInfo(const AddonInfoPtr& addonInfo) : CAddon(addonInfo, ADDON_SKIN)
158 {
159   for (const auto& values : Type(ADDON_SKIN)->GetValues())
160   {
161     if (values.first != "res")
162       continue;
163 
164     int width = values.second.GetValue("res@width").asInteger();
165     int height = values.second.GetValue("res@height").asInteger();
166     bool defRes = values.second.GetValue("res@default").asBoolean();
167     std::string folder = values.second.GetValue("res@folder").asString();
168     std::string strAspect = values.second.GetValue("res@aspect").asString();
169     float aspect = 0;
170 
171     std::vector<std::string> fracs = StringUtils::Split(strAspect, ':');
172     if (fracs.size() == 2)
173       aspect = (float)(atof(fracs[0].c_str())/atof(fracs[1].c_str()));
174     if (width > 0 && height > 0)
175     {
176       RESOLUTION_INFO res(width, height, aspect, folder);
177       res.strId = strAspect; // for skin usage, store aspect string in strId
178       if (defRes)
179         m_defaultRes = res;
180       m_resolutions.push_back(res);
181     }
182   }
183 
184   m_effectsSlowDown = Type(ADDON_SKIN)->GetValue("@effectslowdown").asFloat();
185   if (m_effectsSlowDown == 0.0f)
186     m_effectsSlowDown = 1.f;
187 
188   m_debugging = Type(ADDON_SKIN)->GetValue("@debugging").asBoolean();
189 
190   m_settingsUpdateHandler.reset(new CSkinSettingUpdateHandler(*this));
191   LoadStartupWindows(addonInfo);
192 }
193 
194 CSkinInfo::~CSkinInfo() = default;
195 
196 struct closestRes
197 {
closestResADDON::closestRes198   explicit closestRes(const RESOLUTION_INFO &target) : m_target(target) { };
operator ()ADDON::closestRes199   bool operator()(const RESOLUTION_INFO &i, const RESOLUTION_INFO &j)
200   {
201     float diff = fabs(i.DisplayRatio() - m_target.DisplayRatio()) - fabs(j.DisplayRatio() - m_target.DisplayRatio());
202     if (diff < 0) return true;
203     if (diff > 0) return false;
204     diff = fabs((float)i.iHeight - m_target.iHeight) - fabs((float)j.iHeight - m_target.iHeight);
205     if (diff < 0) return true;
206     if (diff > 0) return false;
207     return fabs((float)i.iWidth - m_target.iWidth) < fabs((float)j.iWidth - m_target.iWidth);
208   }
209   RESOLUTION_INFO m_target;
210 };
211 
Start()212 void CSkinInfo::Start()
213 {
214   if (!LoadUserSettings())
215     CLog::Log(LOGWARNING, "CSkinInfo: failed to load skin settings");
216 
217   if (!m_resolutions.size())
218   { // try falling back to whatever resolutions exist in the directory
219     CFileItemList items;
220     CDirectory::GetDirectory(Path(), items, "", DIR_FLAG_NO_FILE_DIRS);
221     for (int i = 0; i < items.Size(); i++)
222     {
223       RESOLUTION_INFO res;
224       if (items[i]->m_bIsFolder && TranslateResolution(items[i]->GetLabel(), res))
225         m_resolutions.push_back(res);
226     }
227   }
228 
229   if (!m_resolutions.empty())
230   {
231     // find the closest resolution
232     const RESOLUTION_INFO &target = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
233     RESOLUTION_INFO& res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
234     m_currentAspect = res.strId;
235   }
236 }
237 
GetSkinPath(const std::string & strFile,RESOLUTION_INFO * res,const std::string & strBaseDir) const238 std::string CSkinInfo::GetSkinPath(const std::string& strFile, RESOLUTION_INFO *res, const std::string& strBaseDir /* = "" */) const
239 {
240   if (m_resolutions.empty())
241     return ""; // invalid skin
242 
243   std::string strPathToUse = Path();
244   if (!strBaseDir.empty())
245     strPathToUse = strBaseDir;
246 
247   // if the caller doesn't care about the resolution just use a temporary
248   RESOLUTION_INFO tempRes;
249   if (!res)
250     res = &tempRes;
251 
252   // find the closest resolution
253   const RESOLUTION_INFO &target = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
254   *res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
255 
256   std::string strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode, strFile);
257   if (CFile::Exists(strPath))
258     return strPath;
259 
260   // use the default resolution
261   *res = m_defaultRes;
262 
263   return URIUtils::AddFileToFolder(strPathToUse, res->strMode, strFile);
264 }
265 
HasSkinFile(const std::string & strFile) const266 bool CSkinInfo::HasSkinFile(const std::string &strFile) const
267 {
268   return CFile::Exists(GetSkinPath(strFile));
269 }
270 
LoadIncludes()271 void CSkinInfo::LoadIncludes()
272 {
273   std::string includesPath = CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("includes.xml"));
274   CLog::Log(LOGINFO, "Loading skin includes from %s", includesPath.c_str());
275   m_includes.Clear();
276   m_includes.Load(includesPath);
277 }
278 
ResolveIncludes(TiXmlElement * node,std::map<INFO::InfoPtr,bool> * xmlIncludeConditions)279 void CSkinInfo::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
280 {
281   if(xmlIncludeConditions)
282     xmlIncludeConditions->clear();
283 
284   m_includes.Resolve(node, xmlIncludeConditions);
285 }
286 
GetStartWindow() const287 int CSkinInfo::GetStartWindow() const
288 {
289   int windowID = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_LOOKANDFEEL_STARTUPWINDOW);
290   assert(m_startupWindows.size());
291   for (std::vector<CStartupWindow>::const_iterator it = m_startupWindows.begin(); it != m_startupWindows.end(); ++it)
292   {
293     if (windowID == (*it).m_id)
294       return windowID;
295   }
296   // return our first one
297   return m_startupWindows[0].m_id;
298 }
299 
LoadStartupWindows(const AddonInfoPtr & addonInfo)300 bool CSkinInfo::LoadStartupWindows(const AddonInfoPtr& addonInfo)
301 {
302   m_startupWindows.clear();
303   m_startupWindows.emplace_back(WINDOW_HOME, "513");
304   m_startupWindows.emplace_back(WINDOW_TV_CHANNELS, "19180");
305   m_startupWindows.emplace_back(WINDOW_TV_GUIDE, "19273");
306   m_startupWindows.emplace_back(WINDOW_RADIO_CHANNELS, "19183");
307   m_startupWindows.emplace_back(WINDOW_RADIO_GUIDE, "19274");
308   m_startupWindows.emplace_back(WINDOW_PROGRAMS, "0");
309   m_startupWindows.emplace_back(WINDOW_PICTURES, "1");
310   m_startupWindows.emplace_back(WINDOW_MUSIC_NAV, "2");
311   m_startupWindows.emplace_back(WINDOW_VIDEO_NAV, "3");
312   m_startupWindows.emplace_back(WINDOW_FILES, "7");
313   m_startupWindows.emplace_back(WINDOW_SETTINGS_MENU, "5");
314   m_startupWindows.emplace_back(WINDOW_WEATHER, "8");
315   return true;
316 }
317 
GetSkinPaths(std::vector<std::string> & paths) const318 void CSkinInfo::GetSkinPaths(std::vector<std::string> &paths) const
319 {
320   RESOLUTION_INFO res;
321   GetSkinPath("Home.xml", &res);
322   if (!res.strMode.empty())
323     paths.push_back(URIUtils::AddFileToFolder(Path(), res.strMode));
324   if (res.strMode != m_defaultRes.strMode)
325     paths.push_back(URIUtils::AddFileToFolder(Path(), m_defaultRes.strMode));
326 }
327 
TranslateResolution(const std::string & name,RESOLUTION_INFO & res)328 bool CSkinInfo::TranslateResolution(const std::string &name, RESOLUTION_INFO &res)
329 {
330   std::string lower(name); StringUtils::ToLower(lower);
331   if (lower == "pal")
332     res = RESOLUTION_INFO(720, 576, 4.0f/3, "pal");
333   else if (lower == "pal16x9")
334     res = RESOLUTION_INFO(720, 576, 16.0f/9, "pal16x9");
335   else if (lower == "ntsc")
336     res = RESOLUTION_INFO(720, 480, 4.0f/3, "ntsc");
337   else if (lower == "ntsc16x9")
338     res = RESOLUTION_INFO(720, 480, 16.0f/9, "ntsc16x9");
339   else if (lower == "720p")
340     res = RESOLUTION_INFO(1280, 720, 0, "720p");
341   else if (lower == "1080i")
342     res = RESOLUTION_INFO(1920, 1080, 0, "1080i");
343   else
344     return false;
345   return true;
346 }
347 
GetFirstWindow() const348 int CSkinInfo::GetFirstWindow() const
349 {
350   int startWindow = GetStartWindow();
351   if (HasSkinFile("Startup.xml"))
352     startWindow = WINDOW_STARTUP_ANIM;
353   return startWindow;
354 }
355 
IsInUse() const356 bool CSkinInfo::IsInUse() const
357 {
358   // Could extend this to prompt for reverting to the standard skin perhaps
359   return CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN) == ID();
360 }
361 
CreateSkinVariable(const std::string & name,int context)362 const INFO::CSkinVariableString* CSkinInfo::CreateSkinVariable(const std::string& name, int context)
363 {
364   return m_includes.CreateSkinVariable(name, context);
365 }
366 
OnPreInstall()367 void CSkinInfo::OnPreInstall()
368 {
369   bool skinLoaded = g_SkinInfo != nullptr;
370   if (IsInUse() && skinLoaded)
371     CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, "UnloadSkin");
372 }
373 
OnPostInstall(bool update,bool modal)374 void CSkinInfo::OnPostInstall(bool update, bool modal)
375 {
376   if (!g_SkinInfo)
377     return;
378 
379   if (IsInUse() || (!update && !modal &&
380     HELPERS::ShowYesNoDialogText(CVariant{Name()}, CVariant{24099}) == DialogResponse::YES))
381   {
382     CGUIDialogKaiToast *toast = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>(WINDOW_DIALOG_KAI_TOAST);
383     if (toast)
384     {
385       toast->ResetTimer();
386       toast->Close(true);
387     }
388     if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN) == ID())
389       CApplicationMessenger::GetInstance().PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, "ReloadSkin");
390     else
391       CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN, ID());
392   }
393 }
394 
SettingOptionsSkinColorsFiller(const SettingConstPtr & setting,std::vector<StringSettingOption> & list,std::string & current,void * data)395 void CSkinInfo::SettingOptionsSkinColorsFiller(const SettingConstPtr& setting,
396                                                std::vector<StringSettingOption>& list,
397                                                std::string& current,
398                                                void* data)
399 {
400   if (!g_SkinInfo)
401     return;
402 
403   std::string settingValue = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
404   // Remove the .xml extension from the Themes
405   if (URIUtils::HasExtension(settingValue, ".xml"))
406     URIUtils::RemoveExtension(settingValue);
407   current = "SKINDEFAULT";
408 
409   // There is a default theme (just defaults.xml)
410   // any other *.xml files are additional color themes on top of this one.
411 
412   // add the default label
413   list.emplace_back(g_localizeStrings.Get(15109), "SKINDEFAULT"); // the standard defaults.xml will be used!
414 
415   // Search for colors in the Current skin!
416   std::vector<std::string> vecColors;
417   std::string strPath = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors");
418 
419   CFileItemList items;
420   CDirectory::GetDirectory(CSpecialProtocol::TranslatePathConvertCase(strPath), items, ".xml", DIR_FLAG_DEFAULTS);
421   // Search for Themes in the Current skin!
422   for (int i = 0; i < items.Size(); ++i)
423   {
424     CFileItemPtr pItem = items[i];
425     if (!pItem->m_bIsFolder && !StringUtils::EqualsNoCase(pItem->GetLabel(), "defaults.xml"))
426     { // not the default one
427       vecColors.push_back(pItem->GetLabel().substr(0, pItem->GetLabel().size() - 4));
428     }
429   }
430   sort(vecColors.begin(), vecColors.end(), sortstringbyname());
431   for (int i = 0; i < (int) vecColors.size(); ++i)
432     list.emplace_back(vecColors[i], vecColors[i]);
433 
434   // try to find the best matching value
435   for (const auto& elem : list)
436   {
437     if (StringUtils::EqualsNoCase(elem.value, settingValue))
438       current = settingValue;
439   }
440 }
441 
SettingOptionsSkinFontsFiller(const SettingConstPtr & setting,std::vector<StringSettingOption> & list,std::string & current,void * data)442 void CSkinInfo::SettingOptionsSkinFontsFiller(const SettingConstPtr& setting,
443                                               std::vector<StringSettingOption>& list,
444                                               std::string& current,
445                                               void* data)
446 {
447   if (!g_SkinInfo)
448     return;
449 
450   std::string settingValue = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
451   bool currentValueSet = false;
452   std::string strPath = g_SkinInfo->GetSkinPath("Font.xml");
453 
454   CXBMCTinyXML xmlDoc;
455   if (!xmlDoc.LoadFile(strPath))
456   {
457     CLog::Log(LOGERROR, "FillInSkinFonts: Couldn't load %s", strPath.c_str());
458     return;
459   }
460 
461   const TiXmlElement* pRootElement = xmlDoc.RootElement();
462   if (!pRootElement || pRootElement->ValueStr() != "fonts")
463   {
464     CLog::Log(LOGERROR, "FillInSkinFonts: file %s doesn't start with <fonts>", strPath.c_str());
465     return;
466   }
467 
468   const TiXmlElement *pChild = pRootElement->FirstChildElement("fontset");
469   while (pChild)
470   {
471     const char* idAttr = pChild->Attribute("id");
472     const char* idLocAttr = pChild->Attribute("idloc");
473     if (idAttr != NULL)
474     {
475       if (idLocAttr)
476         list.emplace_back(g_localizeStrings.Get(atoi(idLocAttr)), idAttr);
477       else
478         list.emplace_back(idAttr, idAttr);
479 
480       if (StringUtils::EqualsNoCase(idAttr, settingValue))
481         currentValueSet = true;
482     }
483     pChild = pChild->NextSiblingElement("fontset");
484   }
485 
486   if (list.empty())
487   { // Since no fontset is defined, there is no selection of a fontset, so disable the component
488     list.emplace_back(g_localizeStrings.Get(13278), "");
489     current = "";
490     currentValueSet = true;
491   }
492 
493   if (!currentValueSet)
494     current = list[0].value;
495 }
496 
SettingOptionsSkinThemesFiller(const SettingConstPtr & setting,std::vector<StringSettingOption> & list,std::string & current,void * data)497 void CSkinInfo::SettingOptionsSkinThemesFiller(const SettingConstPtr& setting,
498                                                std::vector<StringSettingOption>& list,
499                                                std::string& current,
500                                                void* data)
501 {
502   // get the chosen theme and remove the extension from the current theme (backward compat)
503   std::string settingValue = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
504   URIUtils::RemoveExtension(settingValue);
505   current = "SKINDEFAULT";
506 
507   // there is a default theme (just Textures.xbt)
508   // any other *.xbt files are additional themes on top of this one.
509 
510   // add the default Label
511   list.emplace_back(g_localizeStrings.Get(15109), "SKINDEFAULT"); // the standard Textures.xbt will be used
512 
513   // search for themes in the current skin!
514   std::vector<std::string> vecTheme;
515   CUtil::GetSkinThemes(vecTheme);
516 
517   // sort the themes for GUI and list them
518   for (int i = 0; i < (int) vecTheme.size(); ++i)
519     list.emplace_back(vecTheme[i], vecTheme[i]);
520 
521   // try to find the best matching value
522   for (const auto& elem : list)
523   {
524     if (StringUtils::EqualsNoCase(elem.value, settingValue))
525       current = settingValue;
526   }
527 }
528 
SettingOptionsStartupWindowsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)529 void CSkinInfo::SettingOptionsStartupWindowsFiller(const SettingConstPtr& setting,
530                                                    std::vector<IntegerSettingOption>& list,
531                                                    int& current,
532                                                    void* data)
533 {
534   if (!g_SkinInfo)
535     return;
536 
537   int settingValue = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
538   current = -1;
539 
540   const std::vector<CStartupWindow> &startupWindows = g_SkinInfo->GetStartupWindows();
541 
542   for (std::vector<CStartupWindow>::const_iterator it = startupWindows.begin(); it != startupWindows.end(); ++it)
543   {
544     std::string windowName = it->m_name;
545     if (StringUtils::IsNaturalNumber(windowName))
546       windowName = g_localizeStrings.Get(atoi(windowName.c_str()));
547     int windowID = it->m_id;
548 
549     list.emplace_back(windowName, windowID);
550 
551     if (settingValue == windowID)
552       current = settingValue;
553   }
554 
555   // if the current value hasn't been properly set, set it to the first window in the list
556   if (current < 0)
557     current = list[0].value;
558 }
559 
ToggleDebug()560 void CSkinInfo::ToggleDebug()
561 {
562   m_debugging = !m_debugging;
563 }
564 
TranslateString(const std::string & setting)565 int CSkinInfo::TranslateString(const std::string &setting)
566 {
567   // run through and see if we have this setting
568   for (const auto& it : m_strings)
569   {
570     if (StringUtils::EqualsNoCase(setting, it.second->name))
571       return it.first;
572   }
573 
574   // didn't find it - insert it
575   CSkinSettingStringPtr skinString(new CSkinSettingString());
576   skinString->name = setting;
577 
578   int number = m_bools.size() + m_strings.size();
579   m_strings.insert(std::pair<int, CSkinSettingStringPtr>(number, skinString));
580 
581   return number;
582 }
583 
GetString(int setting) const584 const std::string& CSkinInfo::GetString(int setting) const
585 {
586   const auto& it = m_strings.find(setting);
587   if (it != m_strings.end())
588     return it->second->value;
589 
590   return StringUtils::Empty;
591 }
592 
SetString(int setting,const std::string & label)593 void CSkinInfo::SetString(int setting, const std::string &label)
594 {
595   auto&& it = m_strings.find(setting);
596   if (it != m_strings.end())
597   {
598     it->second->value = label;
599     m_settingsUpdateHandler->TriggerSave();
600     return;
601   }
602 
603   CLog::Log(LOGFATAL, "%s: unknown setting (%d) requested", __FUNCTION__, setting);
604   assert(false);
605 }
606 
TranslateBool(const std::string & setting)607 int CSkinInfo::TranslateBool(const std::string &setting)
608 {
609   // run through and see if we have this setting
610   for (const auto& it : m_bools)
611   {
612     if (StringUtils::EqualsNoCase(setting, it.second->name))
613       return it.first;
614   }
615 
616   // didn't find it - insert it
617   CSkinSettingBoolPtr skinBool(new CSkinSettingBool());
618   skinBool->name = setting;
619 
620   int number = m_bools.size() + m_strings.size();
621   m_bools.insert(std::pair<int, CSkinSettingBoolPtr>(number, skinBool));
622   m_settingsUpdateHandler->TriggerSave();
623 
624   return number;
625 }
626 
GetBool(int setting) const627 bool CSkinInfo::GetBool(int setting) const
628 {
629   const auto& it = m_bools.find(setting);
630   if (it != m_bools.end())
631     return it->second->value;
632 
633   // default is to return false
634   return false;
635 }
636 
SetBool(int setting,bool set)637 void CSkinInfo::SetBool(int setting, bool set)
638 {
639   auto&& it = m_bools.find(setting);
640   if (it != m_bools.end())
641   {
642     it->second->value = set;
643     m_settingsUpdateHandler->TriggerSave();
644     return;
645   }
646 
647   CLog::Log(LOGFATAL, "%s: unknown setting (%d) requested", __FUNCTION__, setting);
648   assert(false);
649 }
650 
Reset(const std::string & setting)651 void CSkinInfo::Reset(const std::string &setting)
652 {
653   // run through and see if we have this setting as a string
654   for (auto& it : m_strings)
655   {
656     if (StringUtils::EqualsNoCase(setting, it.second->name))
657     {
658       it.second->value.clear();
659       m_settingsUpdateHandler->TriggerSave();
660       return;
661     }
662   }
663 
664   // and now check for the skin bool
665   for (auto& it : m_bools)
666   {
667     if (StringUtils::EqualsNoCase(setting, it.second->name))
668     {
669       it.second->value = false;
670       m_settingsUpdateHandler->TriggerSave();
671       return;
672     }
673   }
674 }
675 
Reset()676 void CSkinInfo::Reset()
677 {
678   // clear all the settings and strings from this skin.
679   for (auto& it : m_bools)
680     it.second->value = false;
681 
682   for (auto& it : m_strings)
683     it.second->value.clear();
684 
685   m_settingsUpdateHandler->TriggerSave();
686 }
687 
ParseSettings(const TiXmlElement * rootElement)688 std::set<CSkinSettingPtr> CSkinInfo::ParseSettings(const TiXmlElement* rootElement)
689 {
690   std::set<CSkinSettingPtr> settings;
691   if (rootElement == nullptr)
692     return settings;
693 
694   const TiXmlElement *settingElement = rootElement->FirstChildElement(XML_SETTING);
695   while (settingElement != nullptr)
696   {
697     CSkinSettingPtr setting = ParseSetting(settingElement);
698     if (setting != nullptr)
699       settings.insert(setting);
700 
701     settingElement = settingElement->NextSiblingElement(XML_SETTING);
702   }
703 
704   return settings;
705 }
706 
ParseSetting(const TiXmlElement * element)707 CSkinSettingPtr CSkinInfo::ParseSetting(const TiXmlElement* element)
708 {
709   if (element == nullptr)
710     return CSkinSettingPtr();
711 
712   std::string settingType = XMLUtils::GetAttribute(element, XML_ATTR_TYPE);
713   CSkinSettingPtr setting;
714   if (settingType == "string")
715     setting = CSkinSettingPtr(new CSkinSettingString());
716   else if (settingType == "bool")
717     setting = CSkinSettingPtr(new CSkinSettingBool());
718   else
719     return CSkinSettingPtr();
720 
721   if (setting == nullptr)
722     return CSkinSettingPtr();
723 
724   if (!setting->Deserialize(element))
725     return CSkinSettingPtr();
726 
727   return setting;
728 }
729 
SettingsInitialized() const730 bool CSkinInfo::SettingsInitialized() const
731 {
732   return true;
733 }
734 
SettingsLoaded() const735 bool CSkinInfo::SettingsLoaded() const
736 {
737   return !m_strings.empty() || !m_bools.empty();
738 }
739 
SettingsFromXML(const CXBMCTinyXML & doc,bool loadDefaults)740 bool CSkinInfo::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /* = false */)
741 {
742   const TiXmlElement *rootElement = doc.RootElement();
743   if (rootElement == nullptr || rootElement->ValueStr().compare(XML_SETTINGS) != 0)
744   {
745     CLog::Log(LOGWARNING, "CSkinInfo: no <settings> tag found");
746     return false;
747   }
748 
749   m_strings.clear();
750   m_bools.clear();
751 
752   int number = 0;
753   std::set<CSkinSettingPtr> settings = ParseSettings(rootElement);
754   for (const auto& setting : settings)
755   {
756     if (setting->GetType() == "string")
757       m_strings.insert(std::pair<int, CSkinSettingStringPtr>(number++, std::dynamic_pointer_cast<CSkinSettingString>(setting)));
758     else if (setting->GetType() == "bool")
759       m_bools.insert(std::pair<int, CSkinSettingBoolPtr>(number++, std::dynamic_pointer_cast<CSkinSettingBool>(setting)));
760     else
761       CLog::Log(LOGWARNING, "CSkinInfo: ignoring setting of unknown type \"%s\"", setting->GetType().c_str());
762   }
763 
764   return true;
765 }
766 
SettingsToXML(CXBMCTinyXML & doc) const767 bool CSkinInfo::SettingsToXML(CXBMCTinyXML &doc) const
768 {
769   // add the <skinsettings> tag
770   TiXmlElement rootElement(XML_SETTINGS);
771   TiXmlNode *settingsNode = doc.InsertEndChild(rootElement);
772   if (settingsNode == NULL)
773   {
774     CLog::Log(LOGWARNING, "CSkinInfo: could not create <settings> tag");
775     return false;
776   }
777 
778   TiXmlElement* settingsElement = settingsNode->ToElement();
779   for (const auto& it : m_bools)
780   {
781     if (!it.second->Serialize(settingsElement))
782       CLog::Log(LOGWARNING, "CSkinInfo: failed to save string setting \"%s\"", it.second->name.c_str());
783   }
784 
785   for (const auto& it : m_strings)
786   {
787     if (!it.second->Serialize(settingsElement))
788       CLog::Log(LOGWARNING, "CSkinInfo: failed to save bool setting \"%s\"", it.second->name.c_str());
789   }
790 
791   return true;
792 }
793 
OnTimeout()794 void CSkinSettingUpdateHandler::OnTimeout()
795 {
796   m_addon.SaveSettings();
797 }
798 
TriggerSave()799 void CSkinSettingUpdateHandler::TriggerSave()
800 {
801   if (m_timer.IsRunning())
802     m_timer.Restart();
803   else
804     m_timer.Start(DELAY);
805 }
806 
807 } /*namespace ADDON*/
808