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