2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
15 #include "addons/addoninfo/AddonType.h"
16 #include "dialogs/GUIDialogKaiToast.h"
17 #include "filesystem/Directory.h"
18 #include "filesystem/SpecialProtocol.h"
19 #include "guilib/GUIComponent.h"
20 #include "guilib/GUIWindowManager.h"
21 #include "guilib/LocalizeStrings.h"
22 #include "guilib/WindowIDs.h"
23 #include "messaging/ApplicationMessenger.h"
24 #include "messaging/helpers/DialogHelper.h"
25 #include "settings/Settings.h"
26 #include "settings/SettingsComponent.h"
27 #include "settings/lib/Setting.h"
28 #include "settings/lib/SettingDefinitions.h"
29 #include "threads/Timer.h"
30 #include "utils/FileUtils.h"
31 #include "utils/StringUtils.h"
32 #include "utils/URIUtils.h"
33 #include "utils/Variant.h"
34 #include "utils/XMLUtils.h"
35 #include "utils/log.h"
40 #define XML_SETTINGS "settings"
41 #define XML_SETTING "setting"
42 #define XML_ATTR_TYPE "type"
43 #define XML_ATTR_NAME "name"
44 #define XML_ATTR_ID "id"
46 using namespace XFILE
;
47 using namespace KODI::MESSAGING
;
48 using namespace std::chrono_literals
;
50 using KODI::MESSAGING::HELPERS::DialogResponse
;
52 std::shared_ptr
<ADDON::CSkinInfo
> g_SkinInfo
;
56 constexpr auto DELAY
= 500ms
;
62 class CSkinSettingUpdateHandler
: private ITimerCallback
65 CSkinSettingUpdateHandler(CAddon
& addon
)
66 : m_addon(addon
), m_timer(this) {}
67 ~CSkinSettingUpdateHandler() override
= default;
69 void OnTimeout() override
;
76 bool CSkinSetting::Serialize(TiXmlElement
* parent
) const
78 if (parent
== nullptr)
81 TiXmlElement
setting(XML_SETTING
);
82 setting
.SetAttribute(XML_ATTR_ID
, name
.c_str());
83 setting
.SetAttribute(XML_ATTR_TYPE
, GetType());
85 if (!SerializeSetting(&setting
))
88 parent
->InsertEndChild(setting
);
93 bool CSkinSetting::Deserialize(const TiXmlElement
* element
)
95 if (element
== nullptr)
98 name
= XMLUtils::GetAttribute(element
, XML_ATTR_ID
);
100 // backwards compatibility for guisettings.xml
102 name
= XMLUtils::GetAttribute(element
, XML_ATTR_NAME
);
107 bool CSkinSettingString::Deserialize(const TiXmlElement
* element
)
111 if (!CSkinSetting::Deserialize(element
))
114 if (element
->FirstChild() != nullptr)
115 value
= element
->FirstChild()->Value();
120 bool CSkinSettingString::SerializeSetting(TiXmlElement
* element
) const
122 if (element
== nullptr)
125 TiXmlText
xmlValue(value
);
126 element
->InsertEndChild(xmlValue
);
131 bool CSkinSettingBool::Deserialize(const TiXmlElement
* element
)
135 if (!CSkinSetting::Deserialize(element
))
138 if (element
->FirstChild() != nullptr)
139 value
= StringUtils::EqualsNoCase(element
->FirstChild()->ValueStr(), "true");
144 bool CSkinSettingBool::SerializeSetting(TiXmlElement
* element
) const
146 if (element
== nullptr)
149 TiXmlText
xmlValue(value
? "true" : "false");
150 element
->InsertEndChild(xmlValue
);
155 CSkinInfo::CSkinInfo(const AddonInfoPtr
& addonInfo
,
156 const RESOLUTION_INFO
& resolution
/* = RESOLUTION_INFO() */)
157 : CAddon(addonInfo
, AddonType::SKIN
),
158 m_defaultRes(resolution
),
159 m_effectsSlowDown(1.f
),
162 m_settingsUpdateHandler
= std::make_unique
<CSkinSettingUpdateHandler
>(*this);
165 CSkinInfo::CSkinInfo(const AddonInfoPtr
& addonInfo
) : CAddon(addonInfo
, AddonType::SKIN
)
167 for (const auto& values
: Type(AddonType::SKIN
)->GetValues())
169 if (values
.first
!= "res")
172 int width
= values
.second
.GetValue("res@width").asInteger();
173 int height
= values
.second
.GetValue("res@height").asInteger();
174 bool defRes
= values
.second
.GetValue("res@default").asBoolean();
175 std::string folder
= values
.second
.GetValue("res@folder").asString();
176 std::string strAspect
= values
.second
.GetValue("res@aspect").asString();
179 std::vector
<std::string
> fracs
= StringUtils::Split(strAspect
, ':');
180 if (fracs
.size() == 2)
181 aspect
= (float)(atof(fracs
[0].c_str()) / atof(fracs
[1].c_str()));
182 if (width
> 0 && height
> 0)
184 RESOLUTION_INFO
res(width
, height
, aspect
, folder
);
185 res
.strId
= strAspect
; // for skin usage, store aspect string in strId
188 m_resolutions
.push_back(res
);
192 m_effectsSlowDown
= Type(AddonType::SKIN
)->GetValue("@effectslowdown").asFloat();
193 if (m_effectsSlowDown
== 0.0f
)
194 m_effectsSlowDown
= 1.f
;
196 m_debugging
= Type(AddonType::SKIN
)->GetValue("@debugging").asBoolean();
198 m_settingsUpdateHandler
= std::make_unique
<CSkinSettingUpdateHandler
>(*this);
199 LoadStartupWindows(addonInfo
);
202 CSkinInfo::~CSkinInfo() = default;
206 explicit closestRes(const RESOLUTION_INFO
&target
) : m_target(target
) { };
207 bool operator()(const RESOLUTION_INFO
&i
, const RESOLUTION_INFO
&j
)
209 float diff
= fabs(i
.DisplayRatio() - m_target
.DisplayRatio()) - fabs(j
.DisplayRatio() - m_target
.DisplayRatio());
210 if (diff
< 0) return true;
211 if (diff
> 0) return false;
212 diff
= fabs((float)i
.iHeight
- m_target
.iHeight
) - fabs((float)j
.iHeight
- m_target
.iHeight
);
213 if (diff
< 0) return true;
214 if (diff
> 0) return false;
215 return fabs((float)i
.iWidth
- m_target
.iWidth
) < fabs((float)j
.iWidth
- m_target
.iWidth
);
217 RESOLUTION_INFO m_target
;
220 void CSkinInfo::Start()
222 if (!LoadUserSettings())
223 CLog::Log(LOGWARNING
, "CSkinInfo: failed to load skin settings");
225 if (!m_resolutions
.size())
226 { // try falling back to whatever resolutions exist in the directory
228 CDirectory::GetDirectory(Path(), items
, "", DIR_FLAG_NO_FILE_DIRS
);
229 for (int i
= 0; i
< items
.Size(); i
++)
232 if (items
[i
]->m_bIsFolder
&& TranslateResolution(items
[i
]->GetLabel(), res
))
233 m_resolutions
.push_back(res
);
237 if (!m_resolutions
.empty())
239 // find the closest resolution
240 const RESOLUTION_INFO
&target
= CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
241 RESOLUTION_INFO
& res
= *std::min_element(m_resolutions
.begin(), m_resolutions
.end(), closestRes(target
));
242 m_currentAspect
= res
.strId
;
246 std::string
CSkinInfo::GetSkinPath(const std::string
& strFile
, RESOLUTION_INFO
*res
, const std::string
& strBaseDir
/* = "" */) const
248 if (m_resolutions
.empty())
249 return ""; // invalid skin
251 std::string strPathToUse
= Path();
252 if (!strBaseDir
.empty())
253 strPathToUse
= strBaseDir
;
255 // if the caller doesn't care about the resolution just use a temporary
256 RESOLUTION_INFO tempRes
;
260 // find the closest resolution
261 const RESOLUTION_INFO
&target
= CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
262 *res
= *std::min_element(m_resolutions
.begin(), m_resolutions
.end(), closestRes(target
));
264 std::string strPath
= URIUtils::AddFileToFolder(strPathToUse
, res
->strMode
, strFile
);
265 if (CFileUtils::Exists(strPath
))
268 // use the default resolution
271 return URIUtils::AddFileToFolder(strPathToUse
, res
->strMode
, strFile
);
274 bool CSkinInfo::HasSkinFile(const std::string
&strFile
) const
276 return CFileUtils::Exists(GetSkinPath(strFile
));
279 void CSkinInfo::LoadIncludes()
281 std::string includesPath
=
282 CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("Includes.xml"));
283 CLog::Log(LOGINFO
, "Loading skin includes from {}", includesPath
);
285 m_includes
.Load(includesPath
);
288 void CSkinInfo::LoadTimers()
291 std::make_unique
<CSkinTimerManager
>(CServiceBroker::GetGUI()->GetInfoManager());
292 const std::string timersPath
=
293 CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("Timers.xml"));
294 CLog::LogF(LOGINFO
, "Trying to load skin timers from {}", timersPath
);
295 m_skinTimerManager
->LoadTimers(timersPath
);
298 void CSkinInfo::ProcessTimers()
300 m_skinTimerManager
->Process();
302 void CSkinInfo::ResolveIncludes(TiXmlElement
* node
,
303 std::map
<INFO::InfoPtr
, bool>* xmlIncludeConditions
/* = nullptr */)
305 if(xmlIncludeConditions
)
306 xmlIncludeConditions
->clear();
308 m_includes
.Resolve(node
, xmlIncludeConditions
);
311 int CSkinInfo::GetStartWindow() const
313 int windowID
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_LOOKANDFEEL_STARTUPWINDOW
);
314 assert(m_startupWindows
.size());
315 for (std::vector
<CStartupWindow
>::const_iterator it
= m_startupWindows
.begin(); it
!= m_startupWindows
.end(); ++it
)
317 if (windowID
== (*it
).m_id
)
320 // return our first one
321 return m_startupWindows
[0].m_id
;
324 bool CSkinInfo::LoadStartupWindows(const AddonInfoPtr
& addonInfo
)
326 m_startupWindows
.clear();
327 m_startupWindows
.emplace_back(WINDOW_HOME
, "513");
328 m_startupWindows
.emplace_back(WINDOW_TV_CHANNELS
, "19180");
329 m_startupWindows
.emplace_back(WINDOW_TV_GUIDE
, "19273");
330 m_startupWindows
.emplace_back(WINDOW_RADIO_CHANNELS
, "19183");
331 m_startupWindows
.emplace_back(WINDOW_RADIO_GUIDE
, "19274");
332 m_startupWindows
.emplace_back(WINDOW_PROGRAMS
, "0");
333 m_startupWindows
.emplace_back(WINDOW_PICTURES
, "1");
334 m_startupWindows
.emplace_back(WINDOW_MUSIC_NAV
, "2");
335 m_startupWindows
.emplace_back(WINDOW_VIDEO_NAV
, "3");
336 m_startupWindows
.emplace_back(WINDOW_FILES
, "7");
337 m_startupWindows
.emplace_back(WINDOW_SETTINGS_MENU
, "5");
338 m_startupWindows
.emplace_back(WINDOW_WEATHER
, "8");
339 m_startupWindows
.emplace_back(WINDOW_FAVOURITES
, "1036");
343 void CSkinInfo::GetSkinPaths(std::vector
<std::string
> &paths
) const
346 GetSkinPath("Home.xml", &res
);
347 if (!res
.strMode
.empty())
348 paths
.push_back(URIUtils::AddFileToFolder(Path(), res
.strMode
));
349 if (res
.strMode
!= m_defaultRes
.strMode
)
350 paths
.push_back(URIUtils::AddFileToFolder(Path(), m_defaultRes
.strMode
));
353 bool CSkinInfo::TranslateResolution(const std::string
&name
, RESOLUTION_INFO
&res
)
355 std::string
lower(name
); StringUtils::ToLower(lower
);
357 res
= RESOLUTION_INFO(720, 576, 4.0f
/3, "pal");
358 else if (lower
== "pal16x9")
359 res
= RESOLUTION_INFO(720, 576, 16.0f
/9, "pal16x9");
360 else if (lower
== "ntsc")
361 res
= RESOLUTION_INFO(720, 480, 4.0f
/3, "ntsc");
362 else if (lower
== "ntsc16x9")
363 res
= RESOLUTION_INFO(720, 480, 16.0f
/9, "ntsc16x9");
364 else if (lower
== "720p")
365 res
= RESOLUTION_INFO(1280, 720, 0, "720p");
366 else if (lower
== "1080i")
367 res
= RESOLUTION_INFO(1920, 1080, 0, "1080i");
373 int CSkinInfo::GetFirstWindow() const
375 int startWindow
= GetStartWindow();
376 if (HasSkinFile("Startup.xml"))
377 startWindow
= WINDOW_STARTUP_ANIM
;
381 bool CSkinInfo::IsInUse() const
383 // Could extend this to prompt for reverting to the standard skin perhaps
384 return CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN
) == ID();
387 const INFO::CSkinVariableString
* CSkinInfo::CreateSkinVariable(const std::string
& name
, int context
)
389 return m_includes
.CreateSkinVariable(name
, context
);
392 void CSkinInfo::OnPreInstall()
394 bool skinLoaded
= g_SkinInfo
!= nullptr;
395 if (IsInUse() && skinLoaded
)
396 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN
, -1, -1, nullptr,
400 void CSkinInfo::OnPostInstall(bool update
, bool modal
)
405 if (IsInUse() || (!update
&& !modal
&&
406 HELPERS::ShowYesNoDialogText(CVariant
{Name()}, CVariant
{24099}) ==
407 DialogResponse::CHOICE_YES
))
409 CGUIDialogKaiToast
*toast
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogKaiToast
>(WINDOW_DIALOG_KAI_TOAST
);
415 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN
) == ID())
416 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN
, -1, -1, nullptr,
419 CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN
, ID());
423 void CSkinInfo::Unload()
425 m_skinTimerManager
->Stop();
428 bool CSkinInfo::TimerIsRunning(const std::string
& timer
) const
430 return m_skinTimerManager
->TimerIsRunning(timer
);
433 float CSkinInfo::GetTimerElapsedSeconds(const std::string
& timer
) const
435 return m_skinTimerManager
->GetTimerElapsedSeconds(timer
);
438 void CSkinInfo::TimerStart(const std::string
& timer
) const
440 m_skinTimerManager
->TimerStart(timer
);
443 void CSkinInfo::TimerStop(const std::string
& timer
) const
445 m_skinTimerManager
->TimerStop(timer
);
448 void CSkinInfo::SettingOptionsSkinColorsFiller(const SettingConstPtr
& setting
,
449 std::vector
<StringSettingOption
>& list
,
450 std::string
& current
,
456 std::string settingValue
= std::static_pointer_cast
<const CSettingString
>(setting
)->GetValue();
457 // Remove the .xml extension from the Themes
458 if (URIUtils::HasExtension(settingValue
, ".xml"))
459 URIUtils::RemoveExtension(settingValue
);
460 current
= "SKINDEFAULT";
462 // There is a default theme (just defaults.xml)
463 // any other *.xml files are additional color themes on top of this one.
465 // add the default label
466 list
.emplace_back(g_localizeStrings
.Get(15109), "SKINDEFAULT"); // the standard defaults.xml will be used!
468 // Search for colors in the Current skin!
469 std::vector
<std::string
> vecColors
;
470 std::string strPath
= URIUtils::AddFileToFolder(g_SkinInfo
->Path(), "colors");
473 CDirectory::GetDirectory(CSpecialProtocol::TranslatePathConvertCase(strPath
), items
, ".xml", DIR_FLAG_DEFAULTS
);
474 // Search for Themes in the Current skin!
475 for (int i
= 0; i
< items
.Size(); ++i
)
477 CFileItemPtr pItem
= items
[i
];
478 if (!pItem
->m_bIsFolder
&& !StringUtils::EqualsNoCase(pItem
->GetLabel(), "defaults.xml"))
479 { // not the default one
480 vecColors
.push_back(pItem
->GetLabel().substr(0, pItem
->GetLabel().size() - 4));
483 sort(vecColors
.begin(), vecColors
.end(), sortstringbyname());
484 for (int i
= 0; i
< (int) vecColors
.size(); ++i
)
485 list
.emplace_back(vecColors
[i
], vecColors
[i
]);
487 // try to find the best matching value
488 for (const auto& elem
: list
)
490 if (StringUtils::EqualsNoCase(elem
.value
, settingValue
))
491 current
= settingValue
;
497 void GetFontsetsFromFile(const std::string
& fontsetFilePath
,
498 std::vector
<StringSettingOption
>& list
,
499 const std::string
& settingValue
,
500 bool* currentValueSet
)
503 if (xmlDoc
.LoadFile(fontsetFilePath
))
505 TiXmlElement
* rootElement
= xmlDoc
.RootElement();
506 g_SkinInfo
->ResolveIncludes(rootElement
);
507 if (rootElement
&& (rootElement
->ValueStr() == "fonts"))
509 const TiXmlElement
* fontsetElement
= rootElement
->FirstChildElement("fontset");
510 while (fontsetElement
)
512 const char* idAttr
= fontsetElement
->Attribute("id");
513 const char* idLocAttr
= fontsetElement
->Attribute("idloc");
517 list
.emplace_back(g_localizeStrings
.Get(atoi(idLocAttr
)), idAttr
);
519 list
.emplace_back(idAttr
, idAttr
);
521 if (StringUtils::EqualsNoCase(idAttr
, settingValue
))
522 *currentValueSet
= true;
524 fontsetElement
= fontsetElement
->NextSiblingElement("fontset");
529 } // unnamed namespace
531 void CSkinInfo::SettingOptionsSkinFontsFiller(const SettingConstPtr
& setting
,
532 std::vector
<StringSettingOption
>& list
,
533 std::string
& current
,
539 const std::string settingValue
=
540 std::static_pointer_cast
<const CSettingString
>(setting
)->GetValue();
541 bool currentValueSet
= false;
543 // Look for fontsets that are defined in the skin's Font.xml file
544 const std::string fontsetFilePath
= g_SkinInfo
->GetSkinPath("Font.xml");
545 GetFontsetsFromFile(fontsetFilePath
, list
, settingValue
, ¤tValueSet
);
547 // Look for additional fontsets that are defined in .xml files in the skin's fonts directory
548 CFileItemList xmlFileItems
;
549 CDirectory::GetDirectory(CSpecialProtocol::TranslatePath("special://skin/fonts"), xmlFileItems
,
550 ".xml", DIR_FLAG_DEFAULTS
);
551 for (int i
= 0; i
< xmlFileItems
.Size(); i
++)
552 GetFontsetsFromFile(xmlFileItems
[i
]->GetPath(), list
, settingValue
, ¤tValueSet
);
555 { // Since no fontset is defined, there is no selection of a fontset, so disable the component
556 CLog::LogF(LOGERROR
, "No fontsets found");
557 list
.emplace_back(g_localizeStrings
.Get(13278), "");
559 currentValueSet
= true;
562 if (!currentValueSet
)
563 current
= list
[0].value
;
566 void CSkinInfo::SettingOptionsSkinThemesFiller(const SettingConstPtr
& setting
,
567 std::vector
<StringSettingOption
>& list
,
568 std::string
& current
,
571 // get the chosen theme and remove the extension from the current theme (backward compat)
572 std::string settingValue
= std::static_pointer_cast
<const CSettingString
>(setting
)->GetValue();
573 URIUtils::RemoveExtension(settingValue
);
574 current
= "SKINDEFAULT";
576 // there is a default theme (just Textures.xbt)
577 // any other *.xbt files are additional themes on top of this one.
579 // add the default Label
580 list
.emplace_back(g_localizeStrings
.Get(15109), "SKINDEFAULT"); // the standard Textures.xbt will be used
582 // search for themes in the current skin!
583 std::vector
<std::string
> vecTheme
;
584 CUtil::GetSkinThemes(vecTheme
);
586 // sort the themes for GUI and list them
587 for (int i
= 0; i
< (int) vecTheme
.size(); ++i
)
588 list
.emplace_back(vecTheme
[i
], vecTheme
[i
]);
590 // try to find the best matching value
591 for (const auto& elem
: list
)
593 if (StringUtils::EqualsNoCase(elem
.value
, settingValue
))
594 current
= settingValue
;
598 void CSkinInfo::SettingOptionsStartupWindowsFiller(const SettingConstPtr
& setting
,
599 std::vector
<IntegerSettingOption
>& list
,
606 int settingValue
= std::static_pointer_cast
<const CSettingInt
>(setting
)->GetValue();
609 const std::vector
<CStartupWindow
> &startupWindows
= g_SkinInfo
->GetStartupWindows();
611 for (std::vector
<CStartupWindow
>::const_iterator it
= startupWindows
.begin(); it
!= startupWindows
.end(); ++it
)
613 std::string windowName
= it
->m_name
;
614 if (StringUtils::IsNaturalNumber(windowName
))
615 windowName
= g_localizeStrings
.Get(atoi(windowName
.c_str()));
616 int windowID
= it
->m_id
;
618 list
.emplace_back(windowName
, windowID
);
620 if (settingValue
== windowID
)
621 current
= settingValue
;
624 // if the current value hasn't been properly set, set it to the first window in the list
626 current
= list
[0].value
;
629 void CSkinInfo::ToggleDebug()
631 m_debugging
= !m_debugging
;
634 int CSkinInfo::TranslateString(const std::string
&setting
)
636 // run through and see if we have this setting
637 for (const auto& it
: m_strings
)
639 if (StringUtils::EqualsNoCase(setting
, it
.second
->name
))
643 // didn't find it - insert it
644 CSkinSettingStringPtr
skinString(new CSkinSettingString());
645 skinString
->name
= setting
;
647 int number
= m_bools
.size() + m_strings
.size();
648 m_strings
.insert(std::pair
<int, CSkinSettingStringPtr
>(number
, skinString
));
653 int CSkinInfo::GetInt(int setting
) const
655 const std::string settingValue
= GetString(setting
);
656 if (settingValue
.empty())
660 int settingValueInt
{-1};
661 std::from_chars(settingValue
.data(), settingValue
.data() + settingValue
.size(), settingValueInt
);
662 return settingValueInt
;
665 const std::string
& CSkinInfo::GetString(int setting
) const
667 const auto& it
= m_strings
.find(setting
);
668 if (it
!= m_strings
.end())
669 return it
->second
->value
;
671 return StringUtils::Empty
;
674 void CSkinInfo::SetString(int setting
, const std::string
&label
)
676 auto&& it
= m_strings
.find(setting
);
677 if (it
!= m_strings
.end())
679 it
->second
->value
= label
;
680 m_settingsUpdateHandler
->TriggerSave();
684 CLog::Log(LOGFATAL
, "{}: unknown setting ({}) requested", __FUNCTION__
, setting
);
688 int CSkinInfo::TranslateBool(const std::string
&setting
)
690 // run through and see if we have this setting
691 for (const auto& it
: m_bools
)
693 if (StringUtils::EqualsNoCase(setting
, it
.second
->name
))
697 // didn't find it - insert it
698 CSkinSettingBoolPtr
skinBool(new CSkinSettingBool());
699 skinBool
->name
= setting
;
701 int number
= m_bools
.size() + m_strings
.size();
702 m_bools
.insert(std::pair
<int, CSkinSettingBoolPtr
>(number
, skinBool
));
703 m_settingsUpdateHandler
->TriggerSave();
708 bool CSkinInfo::GetBool(int setting
) const
710 const auto& it
= m_bools
.find(setting
);
711 if (it
!= m_bools
.end())
712 return it
->second
->value
;
714 // default is to return false
718 void CSkinInfo::SetBool(int setting
, bool set
)
720 auto&& it
= m_bools
.find(setting
);
721 if (it
!= m_bools
.end())
723 it
->second
->value
= set
;
724 m_settingsUpdateHandler
->TriggerSave();
728 CLog::Log(LOGFATAL
, "{}: unknown setting ({}) requested", __FUNCTION__
, setting
);
732 std::set
<CSkinSettingPtr
> CSkinInfo::GetSkinSettings() const
734 std::set
<CSkinSettingPtr
> settings
;
736 for (const auto& setting
: m_settings
)
737 settings
.insert(setting
.second
);
742 CSkinSettingPtr
CSkinInfo::GetSkinSetting(const std::string
& settingId
)
744 const auto& it
= m_settings
.find(settingId
);
745 if (it
!= m_settings
.end())
751 std::shared_ptr
<const CSkinSetting
> CSkinInfo::GetSkinSetting(const std::string
& settingId
) const
753 const auto& it
= m_settings
.find(settingId
);
754 if (it
!= m_settings
.end())
760 void CSkinInfo::Reset(const std::string
&setting
)
762 // run through and see if we have this setting as a string
763 for (auto& it
: m_strings
)
765 if (StringUtils::EqualsNoCase(setting
, it
.second
->name
))
767 it
.second
->value
.clear();
768 m_settingsUpdateHandler
->TriggerSave();
773 // and now check for the skin bool
774 for (auto& it
: m_bools
)
776 if (StringUtils::EqualsNoCase(setting
, it
.second
->name
))
778 it
.second
->value
= false;
779 m_settingsUpdateHandler
->TriggerSave();
785 void CSkinInfo::Reset()
787 // clear all the settings and strings from this skin.
788 for (auto& it
: m_bools
)
789 it
.second
->value
= false;
791 for (auto& it
: m_strings
)
792 it
.second
->value
.clear();
794 m_settingsUpdateHandler
->TriggerSave();
797 std::set
<CSkinSettingPtr
> CSkinInfo::ParseSettings(const TiXmlElement
* rootElement
)
799 std::set
<CSkinSettingPtr
> settings
;
800 if (rootElement
== nullptr)
803 const TiXmlElement
*settingElement
= rootElement
->FirstChildElement(XML_SETTING
);
804 while (settingElement
!= nullptr)
806 CSkinSettingPtr setting
= ParseSetting(settingElement
);
807 if (setting
!= nullptr)
808 settings
.insert(setting
);
810 settingElement
= settingElement
->NextSiblingElement(XML_SETTING
);
816 CSkinSettingPtr
CSkinInfo::ParseSetting(const TiXmlElement
* element
)
818 if (element
== nullptr)
819 return CSkinSettingPtr();
821 std::string settingType
= XMLUtils::GetAttribute(element
, XML_ATTR_TYPE
);
822 CSkinSettingPtr setting
;
823 if (settingType
== "string")
824 setting
= CSkinSettingPtr(new CSkinSettingString());
825 else if (settingType
== "bool")
826 setting
= CSkinSettingPtr(new CSkinSettingBool());
828 return CSkinSettingPtr();
830 if (setting
== nullptr)
831 return CSkinSettingPtr();
833 if (!setting
->Deserialize(element
))
834 return CSkinSettingPtr();
839 bool CSkinInfo::SettingsLoaded(AddonInstanceId id
/* = ADDON_SETTINGS_ID */) const
841 if (id
!= ADDON_SETTINGS_ID
)
844 return !m_strings
.empty() || !m_bools
.empty();
847 bool CSkinInfo::SettingsFromXML(const CXBMCTinyXML
& doc
,
849 AddonInstanceId id
/* = ADDON_SETTINGS_ID */)
851 const TiXmlElement
*rootElement
= doc
.RootElement();
852 if (rootElement
== nullptr || rootElement
->ValueStr().compare(XML_SETTINGS
) != 0)
854 CLog::Log(LOGWARNING
, "CSkinInfo: no <settings> tag found");
863 std::set
<CSkinSettingPtr
> settings
= ParseSettings(rootElement
);
864 for (const auto& setting
: settings
)
866 if (setting
->GetType() == "string")
868 m_settings
.insert(std::make_pair(setting
->name
, setting
));
870 std::make_pair(number
++, std::dynamic_pointer_cast
<CSkinSettingString
>(setting
)));
872 else if (setting
->GetType() == "bool")
874 m_settings
.insert(std::make_pair(setting
->name
, setting
));
876 std::make_pair(number
++, std::dynamic_pointer_cast
<CSkinSettingBool
>(setting
)));
879 CLog::Log(LOGWARNING
, "CSkinInfo: ignoring setting of unknown type \"{}\"",
886 bool CSkinInfo::SettingsToXML(CXBMCTinyXML
& doc
, AddonInstanceId id
/* = ADDON_SETTINGS_ID */) const
888 // add the <skinsettings> tag
889 TiXmlElement
rootElement(XML_SETTINGS
);
890 TiXmlNode
*settingsNode
= doc
.InsertEndChild(rootElement
);
891 if (settingsNode
== nullptr)
893 CLog::Log(LOGWARNING
, "CSkinInfo: could not create <settings> tag");
897 TiXmlElement
* settingsElement
= settingsNode
->ToElement();
898 for (const auto& it
: m_bools
)
900 if (!it
.second
->Serialize(settingsElement
))
901 CLog::Log(LOGWARNING
, "CSkinInfo: failed to save string setting \"{}\"", it
.second
->name
);
904 for (const auto& it
: m_strings
)
906 if (!it
.second
->Serialize(settingsElement
))
907 CLog::Log(LOGWARNING
, "CSkinInfo: failed to save bool setting \"{}\"", it
.second
->name
);
913 void CSkinSettingUpdateHandler::OnTimeout()
915 m_addon
.SaveSettings();
918 void CSkinSettingUpdateHandler::TriggerSave()
920 if (m_timer
.IsRunning())
923 m_timer
.Start(DELAY
);
926 } /*namespace ADDON*/