diff options
Diffstat (limited to '')
29 files changed, 6590 insertions, 0 deletions
diff --git a/xbmc/settings/lib/CMakeLists.txt b/xbmc/settings/lib/CMakeLists.txt new file mode 100644 index 0000000..a39a0ca --- /dev/null +++ b/xbmc/settings/lib/CMakeLists.txt @@ -0,0 +1,31 @@ +set(SOURCES ISetting.cpp + ISettingControl.cpp + Setting.cpp + SettingCategoryAccess.cpp + SettingConditions.cpp + SettingDependency.cpp + SettingRequirement.cpp + SettingSection.cpp + SettingsManager.cpp + SettingUpdate.cpp) + +set(HEADERS ISetting.h + ISettingCallback.h + ISettingControl.h + ISettingControlCreator.h + ISettingCreator.h + ISettingsHandler.h + ISettingsValueSerializer.h + Setting.h + SettingCategoryAccess.h + SettingConditions.h + SettingDefinitions.h + SettingDependency.h + SettingLevel.h + SettingRequirement.h + SettingSection.h + SettingsManager.h + SettingType.h + SettingUpdate.h) + +core_add_library(settings_lib) diff --git a/xbmc/settings/lib/ISetting.cpp b/xbmc/settings/lib/ISetting.cpp new file mode 100644 index 0000000..a836f99 --- /dev/null +++ b/xbmc/settings/lib/ISetting.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ISetting.h" + +#include "SettingDefinitions.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" + +#include <string> + +ISetting::ISetting(const std::string &id, CSettingsManager *settingsManager /* = nullptr */) + : m_id(id) + , m_settingsManager(settingsManager) + , m_requirementCondition(settingsManager) +{ } + +bool ISetting::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (node == nullptr) + return false; + + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_VISIBLE, value)) + m_visible = value; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + int iValue = -1; + if (element->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &iValue) == TIXML_SUCCESS && iValue > 0) + m_label = iValue; + if (element->QueryIntAttribute(SETTING_XML_ATTR_HELP, &iValue) == TIXML_SUCCESS && iValue > 0) + m_help = iValue; + + auto requirementNode = node->FirstChild(SETTING_XML_ELM_REQUIREMENT); + if (requirementNode == nullptr) + return true; + + return m_requirementCondition.Deserialize(requirementNode); +} + +bool ISetting::DeserializeIdentification(const TiXmlNode* node, std::string& identification) +{ + return DeserializeIdentificationFromAttribute(node, SETTING_XML_ATTR_ID, identification); +} + +bool ISetting::DeserializeIdentificationFromAttribute(const TiXmlNode* node, + const std::string& attribute, + std::string& identification) +{ + if (node == nullptr) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + auto idAttribute = element->Attribute(attribute); + if (idAttribute == nullptr || idAttribute->empty()) + return false; + + identification = *idAttribute; + return true; +} + +void ISetting::CheckRequirements() +{ + m_meetsRequirements = m_requirementCondition.Check(); +} diff --git a/xbmc/settings/lib/ISetting.h b/xbmc/settings/lib/ISetting.h new file mode 100644 index 0000000..8a40fe3 --- /dev/null +++ b/xbmc/settings/lib/ISetting.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingRequirement.h" + +#include <string> + +class CSettingsManager; +class TiXmlNode; + +/*! + \ingroup settings + \brief Interface defining the base of all setting objects + */ +class ISetting +{ +public: + /*! + \brief Creates a new setting object with the given identifier. + + \param id Identifier of the setting object + \param settingsManager Reference to the settings manager + */ + ISetting(const std::string &id, CSettingsManager *settingsManager = nullptr); + virtual ~ISetting() = default; + + /*! + \brief Deserializes the given XML node into the properties of the setting + object. + + If the update parameter is true, the checks for mandatory properties are + skipped and values are only updated. + + \param node XML node containing the properties of the setting object + \param update Whether to perform checks for mandatory properties or not + \return True if deserialization was successful, false otherwise + */ + virtual bool Deserialize(const TiXmlNode *node, bool update = false); + + /*! + \brief Gets the identifier of the setting object. + + \return Identifier of the setting object + */ + const std::string& GetId() const { return m_id; } + /*! + \brief Whether the setting object is visible or hidden. + + \return True if the setting object is visible, false otherwise + */ + virtual bool IsVisible() const { return m_visible; } + /*! + \brief Sets the visibility state of the setting object. + + \param visible Whether the setting object shall be visible or not + */ + virtual void SetVisible(bool visible) { m_visible = visible; } + /*! + \brief Gets the localizeable label ID of the setting group. + + \return Localizeable label ID of the setting group + */ + int GetLabel() const { return m_label; } + /*! + \brief Sets the localizeable label ID of the setting group. + + \param label Localizeable label ID of the setting group + */ + void SetLabel(int label) { m_label = label; } + /*! + \brief Gets the localizeable help ID of the setting group. + + \return Localizeable help ID of the setting group + */ + int GetHelp() const { return m_help; } + /*! + \brief Sets the localizeable help ID of the setting group. + + \param label Localizeable help ID of the setting group + */ + void SetHelp(int help) { m_help = help; } + /*! + \brief Whether the setting object meets all necessary requirements. + + \return True if the setting object meets all necessary requirements, false otherwise + */ + virtual bool MeetsRequirements() const { return m_meetsRequirements; } + /*! + \brief Checks if the setting object meets all necessary requirements. + */ + virtual void CheckRequirements(); + /*! + \brief Sets whether the setting object meets all necessary requirements. + + \param visible Whether the setting object meets all necessary requirements or not + */ + virtual void SetRequirementsMet(bool requirementsMet) { m_meetsRequirements = requirementsMet; } + + /*! + \brief Deserializes the given XML node to retrieve a setting object's + identifier. + + \param node XML node containing a setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentification(const TiXmlNode *node, std::string &identification); + +protected: + static constexpr int DefaultLabel = -1; + /*! + \brief Deserializes the given XML node to retrieve a setting object's identifier from the given attribute. + + \param node XML node containing a setting object's identifier + \param attribute Attribute which contains the setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentificationFromAttribute(const TiXmlNode* node, + const std::string& attribute, + std::string& identification); + + std::string m_id; + CSettingsManager *m_settingsManager; + +private: + bool m_visible = true; + int m_label = DefaultLabel; + int m_help = -1; + bool m_meetsRequirements = true; + CSettingRequirement m_requirementCondition; +}; diff --git a/xbmc/settings/lib/ISettingCallback.h b/xbmc/settings/lib/ISettingCallback.h new file mode 100644 index 0000000..00fc428 --- /dev/null +++ b/xbmc/settings/lib/ISettingCallback.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +class CSetting; +class TiXmlNode; + +class ISettingCallback +{ +public: + virtual ~ISettingCallback() = default; + + /*! + \brief The value of the given setting is being changed. + + This callback is triggered whenever the value of a setting is being + changed. The given CSetting already contains the new value and the handler + of the callback has the possibility to allow or revert changing the value + of the setting. In case of a revert OnSettingChanging() is called again to + inform all listeners that the value change has been reverted. + + \param setting The setting whose value is being changed (already containing the changed value) + \return True if the new value is acceptable otherwise false + */ + virtual bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) { return true; } + + /*! + \brief The value of the given setting has changed. + + This callback is triggered whenever the value of a setting has been + successfully changed (i.e. none of the OnSettingChanging() handlers) + has reverted the change. + + \param setting The setting whose value has been changed + */ + virtual void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) {} + + /*! + \brief The given setting has been activated. + + This callback is triggered whenever the given setting has been activated. + This callback is only fired for CSettingAction settings. + + \param setting The setting which has been activated. + */ + virtual void OnSettingAction(const std::shared_ptr<const CSetting>& setting) {} + + /*! + \brief The given setting needs to be updated. + + This callback is triggered when a setting needs to be updated because its + value is outdated. This only happens when initially loading the value of a + setting and will not be triggered afterwards. + + \param setting The setting which needs to be updated. + \param oldSettingId The id of the previous setting. + \param oldSettingNode The old setting node + \return True if the setting has been successfully updated otherwise false + */ + virtual bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) + { + return false; + } + + /*! + \brief The given property of the given setting has changed + + This callback is triggered when a property (e.g. enabled or the list of + dynamic options) has changed. + + \param setting The setting which has a changed property + \param propertyName The string representation of the changed property + */ + virtual void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) + { + } +}; diff --git a/xbmc/settings/lib/ISettingControl.cpp b/xbmc/settings/lib/ISettingControl.cpp new file mode 100644 index 0000000..3fc51a5 --- /dev/null +++ b/xbmc/settings/lib/ISettingControl.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ISettingControl.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +Logger ISettingControl::s_logger; + +ISettingControl::ISettingControl() +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("ISettingControl"); +} + +bool ISettingControl::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strTmp = elem->Attribute(SETTING_XML_ATTR_FORMAT); + std::string format; + if (strTmp != nullptr) + format = strTmp; + if (!SetFormat(format)) + { + s_logger->error("error reading \"{}\" attribute of <control>", SETTING_XML_ATTR_FORMAT); + return false; + } + + if ((strTmp = elem->Attribute(SETTING_XML_ATTR_DELAYED)) != nullptr) + { + if (!StringUtils::EqualsNoCase(strTmp, "false") && !StringUtils::EqualsNoCase(strTmp, "true")) + { + s_logger->error("error reading \"{}\" attribute of <control>", SETTING_XML_ATTR_DELAYED); + return false; + } + else + m_delayed = StringUtils::EqualsNoCase(strTmp, "true"); + } + + return true; +} diff --git a/xbmc/settings/lib/ISettingControl.h b/xbmc/settings/lib/ISettingControl.h new file mode 100644 index 0000000..293d5ad --- /dev/null +++ b/xbmc/settings/lib/ISettingControl.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/logtypes.h" + +#include <string> + +class TiXmlNode; + +class ISettingControl +{ +public: + ISettingControl(); + virtual ~ISettingControl() = default; + + virtual std::string GetType() const = 0; + const std::string& GetFormat() const { return m_format; } + bool GetDelayed() const { return m_delayed; } + void SetDelayed(bool delayed) { m_delayed = delayed; } + + virtual bool Deserialize(const TiXmlNode *node, bool update = false); + virtual bool SetFormat(const std::string &format) { return true; } + +protected: + bool m_delayed = false; + std::string m_format; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/ISettingControlCreator.h b/xbmc/settings/lib/ISettingControlCreator.h new file mode 100644 index 0000000..2f77e1c --- /dev/null +++ b/xbmc/settings/lib/ISettingControlCreator.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class ISettingControl; + +/*! + \ingroup settings + \brief Interface for creating a new setting control of a custom setting control type. + */ +class ISettingControlCreator +{ +public: + virtual ~ISettingControlCreator() = default; + + /*! + \brief Creates a new setting control of the given custom setting control type. + + \param controlType string representation of the setting control type + \return A new setting control object of the given (custom) setting control type or nullptr if the setting control type is unknown + */ + virtual std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const = 0; +}; diff --git a/xbmc/settings/lib/ISettingCreator.h b/xbmc/settings/lib/ISettingCreator.h new file mode 100644 index 0000000..e951c85 --- /dev/null +++ b/xbmc/settings/lib/ISettingCreator.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CSetting; +class CSettingsManager; + +/*! + \ingroup settings + \brief Interface for creating a new setting of a custom setting type. + */ +class ISettingCreator +{ +public: + virtual ~ISettingCreator() = default; + + /*! + \brief Creates a new setting of the given custom setting type. + + \param settingType string representation of the setting type + \param settingId Identifier of the setting to be created + \param settingsManager Reference to the settings manager + \return A new setting object of the given (custom) setting type or nullptr if the setting type is unknown + */ + virtual std::shared_ptr<CSetting> CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager = nullptr) const = 0; +}; diff --git a/xbmc/settings/lib/ISettingsHandler.h b/xbmc/settings/lib/ISettingsHandler.h new file mode 100644 index 0000000..466b55a --- /dev/null +++ b/xbmc/settings/lib/ISettingsHandler.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Interface defining methods being called by the settings system if an + action is performed on multiple/all settings + */ +class ISettingsHandler +{ +public: + virtual ~ISettingsHandler() = default; + + /*! + \brief Settings loading has been initiated. + + \return True if the settings should be loaded, false if the loading should be aborted. + */ + virtual bool OnSettingsLoading() { return true; } + /*! + \brief Settings have been loaded. + + This callback can be used to trigger loading other settings. + */ + virtual void OnSettingsLoaded() { } + /*! + \brief Settings saving has been initiated. + + \return True if the settings should be saved, false if the saving should be aborted. + */ + virtual bool OnSettingsSaving() const { return true; } + /*! + \brief Settings have been saved. + + This callback can be used to trigger saving other settings. + */ + virtual void OnSettingsSaved() const { } + /*! + \brief Setting values have been unloaded. + + This callback can be used to trigger uninitializing any state variables + (e.g. before re-loading the settings). + */ + virtual void OnSettingsUnloaded() { } + /*! + \brief Settings have been cleared. + + This callback can be used to trigger clearing any state variables. + */ + virtual void OnSettingsCleared() { } +}; diff --git a/xbmc/settings/lib/ISettingsValueSerializer.h b/xbmc/settings/lib/ISettingsValueSerializer.h new file mode 100644 index 0000000..94bf664 --- /dev/null +++ b/xbmc/settings/lib/ISettingsValueSerializer.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CSettingsManager; + +class ISettingsValueSerializer +{ +public: + virtual ~ISettingsValueSerializer() = default; + + virtual std::string SerializeValues(const CSettingsManager* settingsManager) const = 0; +}; diff --git a/xbmc/settings/lib/Setting.cpp b/xbmc/settings/lib/Setting.cpp new file mode 100644 index 0000000..0802dd8 --- /dev/null +++ b/xbmc/settings/lib/Setting.cpp @@ -0,0 +1,1690 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Setting.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <mutex> +#include <shared_mutex> +#include <sstream> +#include <utility> + +template<typename TKey, typename TValue> +bool CheckSettingOptionsValidity(const TValue& value, const std::vector<std::pair<TKey, TValue>>& options) +{ + for (const auto& it : options) + { + if (it.second == value) + return true; + } + + return false; +} + +template<typename TKey, typename TValue> +bool CheckSettingOptionsValidity(const TValue& value, const std::vector<TKey>& options) +{ + for (const auto& it : options) + { + if (it.value == value) + return true; + } + + return false; +} + +bool DeserializeOptionsSort(const TiXmlElement* optionsElement, SettingOptionsSort& optionsSort) +{ + optionsSort = SettingOptionsSort::NoSorting; + + std::string sort; + if (optionsElement->QueryStringAttribute("sort", &sort) != TIXML_SUCCESS) + return true; + + if (StringUtils::EqualsNoCase(sort, "false") || StringUtils::EqualsNoCase(sort, "off") || + StringUtils::EqualsNoCase(sort, "no") || StringUtils::EqualsNoCase(sort, "disabled")) + optionsSort = SettingOptionsSort::NoSorting; + else if (StringUtils::EqualsNoCase(sort, "asc") || StringUtils::EqualsNoCase(sort, "ascending") || + StringUtils::EqualsNoCase(sort, "true") || StringUtils::EqualsNoCase(sort, "on") || + StringUtils::EqualsNoCase(sort, "yes") || StringUtils::EqualsNoCase(sort, "enabled")) + optionsSort = SettingOptionsSort::Ascending; + else if (StringUtils::EqualsNoCase(sort, "desc") || StringUtils::EqualsNoCase(sort, "descending")) + optionsSort = SettingOptionsSort::Descending; + else + return false; + + return true; +} + +Logger CSetting::s_logger; + +CSetting::CSetting(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSetting"); +} + +CSetting::CSetting(const std::string& id, const CSetting& setting) + : CSetting(id, setting.m_settingsManager) +{ + Copy(setting); +} + +void CSetting::MergeBasics(const CSetting& other) +{ + // ISetting + SetVisible(other.GetVisible()); + SetLabel(other.GetLabel()); + SetHelp(other.GetHelp()); + SetRequirementsMet(other.MeetsRequirements()); + // CSetting + SetEnabled(other.GetEnabled()); + SetParent(other.GetParent()); + SetLevel(other.GetLevel()); + SetControl(const_cast<CSetting&>(other).GetControl()); + SetDependencies(other.GetDependencies()); +} + +bool CSetting::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + auto parentSetting = element->Attribute(SETTING_XML_ATTR_PARENT); + if (parentSetting != nullptr) + m_parentSetting = parentSetting; + + // get <enable> + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_ENABLED, value)) + m_enabled = value; + + // get the <level> + int level = -1; + if (XMLUtils::GetInt(node, SETTING_XML_ELM_LEVEL, level)) + m_level = static_cast<SettingLevel>(level); + + if (m_level < SettingLevel::Basic || m_level > SettingLevel::Internal) + m_level = SettingLevel::Standard; + + auto dependencies = node->FirstChild(SETTING_XML_ELM_DEPENDENCIES); + if (dependencies != nullptr) + { + auto dependencyNode = dependencies->FirstChild(SETTING_XML_ELM_DEPENDENCY); + while (dependencyNode != nullptr) + { + CSettingDependency dependency(m_settingsManager); + if (dependency.Deserialize(dependencyNode)) + m_dependencies.push_back(dependency); + else + s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_DEPENDENCY, m_id); + + dependencyNode = dependencyNode->NextSibling(SETTING_XML_ELM_DEPENDENCY); + } + } + + auto control = node->FirstChildElement(SETTING_XML_ELM_CONTROL); + if (control != nullptr) + { + auto controlType = control->Attribute(SETTING_XML_ATTR_TYPE); + if (controlType == nullptr) + { + s_logger->error("error reading \"{}\" attribute of <control> tag of \"{}\"", + SETTING_XML_ATTR_TYPE, m_id); + return false; + } + + m_control = m_settingsManager->CreateControl(controlType); + if (m_control == nullptr || !m_control->Deserialize(control, update)) + { + s_logger->error("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id); + return false; + } + } + else if (!update && m_level < SettingLevel::Internal && !IsReference()) + { + s_logger->error("missing <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id); + return false; + } + + auto updates = node->FirstChild(SETTING_XML_ELM_UPDATES); + if (updates != nullptr) + { + auto updateElem = updates->FirstChildElement(SETTING_XML_ELM_UPDATE); + while (updateElem != nullptr) + { + CSettingUpdate settingUpdate; + if (settingUpdate.Deserialize(updateElem)) + { + if (!m_updates.insert(settingUpdate).second) + s_logger->warn("duplicate <{}> definition for \"{}\"", SETTING_XML_ELM_UPDATE, m_id); + } + else + s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_UPDATE, m_id); + + updateElem = updateElem->NextSiblingElement(SETTING_XML_ELM_UPDATE); + } + } + + return true; +} + +bool CSetting::IsEnabled() const +{ + if (m_dependencies.empty() && m_parentSetting.empty()) + return m_enabled; + + // if the setting has a parent setting and that parent setting is disabled + // the setting should automatically also be disabled + if (!m_parentSetting.empty()) + { + SettingPtr parentSetting = m_settingsManager->GetSetting(m_parentSetting); + if (parentSetting != nullptr && !parentSetting->IsEnabled()) + return false; + } + + bool enabled = m_enabled; + for (const auto& dep : m_dependencies) + { + if (dep.GetType() != SettingDependencyType::Enable) + continue; + + if (!dep.Check()) + { + enabled = false; + break; + } + } + + return enabled; +} + +void CSetting::SetEnabled(bool enabled) +{ + if (!m_dependencies.empty() || m_enabled == enabled) + return; + + m_enabled = enabled; + OnSettingPropertyChanged(shared_from_this(), "enabled"); +} + +void CSetting::MakeReference(const std::string& referencedId /* = "" */) +{ + auto tmpReferencedId = referencedId; + if (referencedId.empty()) + tmpReferencedId = m_id; + + m_id = StringUtils::Format("#{}[{}]", tmpReferencedId, StringUtils::CreateUUID()); + m_referencedId = tmpReferencedId; +} + +bool CSetting::IsVisible() const +{ + if (!ISetting::IsVisible()) + return false; + + bool visible = true; + for (const auto& dep : m_dependencies) + { + if (dep.GetType() != SettingDependencyType::Visible) + continue; + + if (!dep.Check()) + { + visible = false; + break; + } + } + + return visible; +} + +bool CSetting::OnSettingChanging(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return true; + + return m_callback->OnSettingChanging(setting); +} + +void CSetting::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingChanged(setting); +} + +void CSetting::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingAction(setting); +} + +bool CSetting::DeserializeIdentification(const TiXmlNode* node, + std::string& identification, + bool& isReference) +{ + isReference = false; + + // first check if we can simply retrieve the setting's identifier + if (ISetting::DeserializeIdentification(node, identification)) + return true; + + // otherwise try to retrieve a reference to another setting's identifier + if (!DeserializeIdentificationFromAttribute(node, SETTING_XML_ATTR_REFERENCE, identification)) + return false; + + isReference = true; + return true; +} + +bool CSetting::OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + if (m_callback == nullptr) + return false; + + return m_callback->OnSettingUpdate(setting, oldSettingId, oldSettingNode); +} + +void CSetting::OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingPropertyChanged(setting, propertyName); +} + +void CSetting::Copy(const CSetting &setting) +{ + SetVisible(setting.IsVisible()); + SetLabel(setting.GetLabel()); + SetHelp(setting.GetHelp()); + SetRequirementsMet(setting.MeetsRequirements()); + m_callback = setting.m_callback; + m_level = setting.m_level; + + if (setting.m_control != nullptr) + { + m_control = m_settingsManager->CreateControl(setting.m_control->GetType()); + *m_control = *setting.m_control; + } + else + m_control = nullptr; + + m_dependencies = setting.m_dependencies; + m_updates = setting.m_updates; + m_changed = setting.m_changed; +} + +Logger CSettingList::s_logger; + +CSettingList::CSettingList(const std::string& id, + std::shared_ptr<CSetting> settingDefinition, + CSettingsManager* settingsManager /* = nullptr */) + : CSetting(id, settingsManager), m_definition(std::move(settingDefinition)) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingList"); +} + +CSettingList::CSettingList(const std::string& id, + std::shared_ptr<CSetting> settingDefinition, + int label, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingList(id, std::move(settingDefinition), settingsManager) +{ + SetLabel(label); +} + +CSettingList::CSettingList(const std::string &id, const CSettingList &setting) + : CSetting(id, setting) +{ + copy(setting); +} + +SettingPtr CSettingList::Clone(const std::string &id) const +{ + if (m_definition == nullptr) + return nullptr; + + return std::make_shared<CSettingList>(id, *this); +} + +void CSettingList::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::List) + return; + + const auto& listSetting = static_cast<const CSettingList&>(other); + if (m_definition == nullptr && listSetting.m_definition != nullptr) + m_definition = listSetting.m_definition; + if (m_defaults.empty() && !listSetting.m_defaults.empty()) + m_defaults = listSetting.m_defaults; + if (m_values.empty() && !listSetting.m_values.empty()) + m_values = listSetting.m_values; + if (m_delimiter == "|" && listSetting.m_delimiter != "|") + m_delimiter = listSetting.m_delimiter; + if (m_minimumItems == 0 && listSetting.m_minimumItems != 0) + m_minimumItems = listSetting.m_minimumItems; + if (m_maximumItems == -1 && listSetting.m_maximumItems != -1) + m_maximumItems = listSetting.m_maximumItems; +} + +bool CSettingList::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (m_definition == nullptr) + return false; + + if (!CSetting::Deserialize(node, update)) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + { + s_logger->warn("unable to read type of list setting of {}", m_id); + return false; + } + + // deserialize the setting definition in update mode because we don't care + // about an invalid <default> value (which is never used) + if (!m_definition->Deserialize(node, true)) + return false; + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // read the delimiter + std::string delimiter; + if (XMLUtils::GetString(constraints, SETTING_XML_ELM_DELIMITER, delimiter) && !delimiter.empty()) + m_delimiter = delimiter; + + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM_ITEMS, m_minimumItems); + if (m_minimumItems < 0) + m_minimumItems = 0; + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems); + if (m_maximumItems <= 0) + m_maximumItems = -1; + else if (m_maximumItems < m_minimumItems) + { + s_logger->warn("invalid <{}> ({}) and/or <{}> ({}) of {}", SETTING_XML_ELM_MINIMUM_ITEMS, + m_minimumItems, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems, m_id); + return false; + } + } + + // read the default and initial values + std::string values; + if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, values)) + { + if (!fromString(values, m_defaults)) + { + s_logger->warn("invalid <{}> definition \"{}\" of {}", SETTING_XML_ELM_DEFAULT, values, m_id); + return false; + } + Reset(); + } + + return true; +} + +SettingType CSettingList::GetElementType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (m_definition == nullptr) + return SettingType::Unknown; + + return m_definition->GetType(); +} + +bool CSettingList::FromString(const std::string &value) +{ + SettingList values; + if (!fromString(value, values)) + return false; + + return SetValue(values); +} + +std::string CSettingList::ToString() const +{ + return toString(m_values); +} + +bool CSettingList::Equals(const std::string &value) const +{ + SettingList values; + if (!fromString(value, values) || values.size() != m_values.size()) + return false; + + bool ret = true; + for (size_t index = 0; index < values.size(); index++) + { + if (!m_values[index]->Equals(values[index]->ToString())) + { + ret = false; + break; + } + } + + return ret; +} + +bool CSettingList::CheckValidity(const std::string &value) const +{ + SettingList values; + return fromString(value, values); +} + +void CSettingList::Reset() +{ + std::unique_lock<CSharedSection> lock(m_critical); + SettingList values; + for (const auto& it : m_defaults) + values.push_back(it->Clone(it->GetId())); + + SetValue(values); +} + +bool CSettingList::FromString(const std::vector<std::string> &value) +{ + SettingList values; + if (!fromValues(value, values)) + return false; + + return SetValue(values); +} + +bool CSettingList::SetValue(const SettingList &values) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if ((int)values.size() < m_minimumItems || + (m_maximumItems > 0 && (int)values.size() > m_maximumItems)) + return false; + + bool equal = values.size() == m_values.size(); + for (size_t index = 0; index < values.size(); index++) + { + if (values[index]->GetType() != GetElementType()) + return false; + + if (equal && + !values[index]->Equals(m_values[index]->ToString())) + equal = false; + } + + if (equal) + return true; + + SettingList oldValues = m_values; + m_values.clear(); + m_values.insert(m_values.begin(), values.begin(), values.end()); + + if (!OnSettingChanging(shared_from_base<CSettingList>())) + { + m_values = oldValues; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingList>()); + return false; + } + + m_changed = toString(m_values) != toString(m_defaults); + OnSettingChanged(shared_from_base<CSettingList>()); + return true; +} + +void CSettingList::SetDefault(const SettingList &values) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_defaults.clear(); + m_defaults.insert(m_defaults.begin(), values.begin(), values.end()); + + if (!m_changed) + { + m_values.clear(); + for (const auto& it : m_defaults) + m_values.push_back(it->Clone(it->GetId())); + } +} + +void CSettingList::copy(const CSettingList &setting) +{ + CSetting::Copy(setting); + + copy(setting.m_values, m_values); + copy(setting.m_defaults, m_defaults); + + if (setting.m_definition != nullptr) + { + auto definitionCopy = setting.m_definition->Clone(m_id + ".definition"); + if (definitionCopy != nullptr) + m_definition = definitionCopy; + } + + m_delimiter = setting.m_delimiter; + m_minimumItems = setting.m_minimumItems; + m_maximumItems = setting.m_maximumItems; +} + +void CSettingList::copy(const SettingList &srcValues, SettingList &dstValues) +{ + dstValues.clear(); + + for (const auto& value : srcValues) + { + if (value == nullptr) + continue; + + SettingPtr valueCopy = value->Clone(value->GetId()); + if (valueCopy == nullptr) + continue; + + dstValues.emplace_back(valueCopy); + } +} + +bool CSettingList::fromString(const std::string &strValue, SettingList &values) const +{ + return fromValues(StringUtils::Split(strValue, m_delimiter), values); +} + +bool CSettingList::fromValues(const std::vector<std::string> &strValues, SettingList &values) const +{ + if ((int)strValues.size() < m_minimumItems || + (m_maximumItems > 0 && (int)strValues.size() > m_maximumItems)) + return false; + + bool ret = true; + int index = 0; + for (const auto& value : strValues) + { + auto settingValue = m_definition->Clone(StringUtils::Format("{}.{}", m_id, index++)); + if (settingValue == nullptr || + !settingValue->FromString(value)) + { + ret = false; + break; + } + + values.emplace_back(settingValue); + } + + if (!ret) + values.clear(); + + return ret; +} + +std::string CSettingList::toString(const SettingList &values) const +{ + std::vector<std::string> strValues; + for (const auto& value : values) + { + if (value != nullptr) + strValues.push_back(value->ToString()); + } + + return StringUtils::Join(strValues, m_delimiter); +} + +Logger CSettingBool::s_logger; + +CSettingBool::CSettingBool(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : CSettingBool(id, DefaultLabel, DefaultValue, settingsManager) +{ +} + +CSettingBool::CSettingBool(const std::string& id, const CSettingBool& setting) + : CSettingBool(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingBool::CSettingBool(const std::string& id, + int label, + bool value, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), m_value(value), m_default(value) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingBool"); +} + +SettingPtr CSettingBool::Clone(const std::string &id) const +{ + return std::make_shared<CSettingBool>(id, *this); +} + +void CSettingBool::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Boolean) + return; + + const auto& boolSetting = static_cast<const CSettingBool&>(other); + if (m_default == false && boolSetting.m_default == true) + m_default = boolSetting.m_default; + if (m_value == m_default && boolSetting.m_value != m_default) + m_value = boolSetting.m_value; +} + +bool CSettingBool::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + return true; +} + +bool CSettingBool::FromString(const std::string &value) +{ + bool bValue; + if (!fromString(value, bValue)) + return false; + + return SetValue(bValue); +} + +std::string CSettingBool::ToString() const +{ + return m_value ? "true" : "false"; +} + +bool CSettingBool::Equals(const std::string &value) const +{ + bool bValue; + return (fromString(value, bValue) && m_value == bValue); +} + +bool CSettingBool::CheckValidity(const std::string &value) const +{ + bool bValue; + return fromString(value, bValue); +} + +bool CSettingBool::SetValue(bool value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + bool oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingBool>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingBool>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingBool>()); + return true; +} + +void CSettingBool::SetDefault(bool value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +void CSettingBool::copy(const CSettingBool &setting) +{ + CSetting::Copy(setting); + + m_value = setting.m_value; + m_default = setting.m_default; +} + +bool CSettingBool::fromString(const std::string &strValue, bool &value) const +{ + if (StringUtils::EqualsNoCase(strValue, "true")) + { + value = true; + return true; + } + if (StringUtils::EqualsNoCase(strValue, "false")) + { + value = false; + return true; + } + + return false; +} + +Logger CSettingInt::s_logger; + +CSettingInt::CSettingInt(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingInt::CSettingInt(const std::string& id, const CSettingInt& setting) + : CSettingInt(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager) +{ + SetLabel(label); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + int minimum, + int step, + int maximum, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), + m_value(value), + m_default(value), + m_min(minimum), + m_step(step), + m_max(maximum) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingInt"); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + const TranslatableIntegerSettingOptions& options, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, label, value, settingsManager) +{ + SetTranslatableOptions(options); +} + +SettingPtr CSettingInt::Clone(const std::string &id) const +{ + return std::make_shared<CSettingInt>(id, *this); +} + +void CSettingInt::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Integer) + return; + + const auto& intSetting = static_cast<const CSettingInt&>(other); + if (m_default == 0.0 && intSetting.m_default != 0.0) + m_default = intSetting.m_default; + if (m_value == m_default && intSetting.m_value != m_default) + m_value = intSetting.m_value; + if (m_min == 0.0 && intSetting.m_min != 0.0) + m_min = intSetting.m_min; + if (m_step == 1.0 && intSetting.m_step != 1.0) + m_step = intSetting.m_step; + if (m_max == 0.0 && intSetting.m_max != 0.0) + m_max = intSetting.m_max; + if (m_translatableOptions.empty() && !intSetting.m_translatableOptions.empty()) + m_translatableOptions = intSetting.m_translatableOptions; + if (m_options.empty() && !intSetting.m_options.empty()) + m_options = intSetting.m_options; + if (m_optionsFillerName.empty() && !intSetting.m_optionsFillerName.empty()) + m_optionsFillerName = intSetting.m_optionsFillerName; + if (m_optionsFiller == nullptr && intSetting.m_optionsFiller != nullptr) + m_optionsFiller = intSetting.m_optionsFiller; + if (m_optionsFillerData == nullptr && intSetting.m_optionsFillerData != nullptr) + m_optionsFillerData = intSetting.m_optionsFillerData; + if (m_dynamicOptions.empty() && !intSetting.m_dynamicOptions.empty()) + m_dynamicOptions = intSetting.m_dynamicOptions; + if (m_optionsSort == SettingOptionsSort::NoSorting && + intSetting.m_optionsSort != SettingOptionsSort::NoSorting) + m_optionsSort = intSetting.m_optionsSort; +} + +bool CSettingInt::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + int value; + if (XMLUtils::GetInt(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get the entries + auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS); + if (options != nullptr && options->FirstChild() != nullptr) + { + if (!DeserializeOptionsSort(options, m_optionsSort)) + s_logger->warn("invalid \"sort\" attribute of <" SETTING_XML_ELM_OPTIONS "> for \"{}\"", + m_id); + + if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + { + m_optionsFillerName = options->FirstChild()->ValueStr(); + if (!m_optionsFillerName.empty()) + { + m_optionsFiller = reinterpret_cast<IntegerSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>())); + } + } + else + { + m_translatableOptions.clear(); + auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION); + while (optionElement != nullptr) + { + TranslatableIntegerSettingOption entry; + if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.label) == + TIXML_SUCCESS && + entry.label > 0) + { + entry.value = strtol(optionElement->FirstChild()->Value(), nullptr, 10); + m_translatableOptions.push_back(entry); + } + else + { + std::string label; + if (optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label) == + TIXML_SUCCESS) + { + int value = strtol(optionElement->FirstChild()->Value(), nullptr, 10); + m_options.emplace_back(label, value); + } + } + + optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION); + } + } + } + + // get minimum + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM, m_min); + // get step + XMLUtils::GetInt(constraints, SETTING_XML_ELM_STEP, m_step); + // get maximum + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM, m_max); + } + + return true; +} + +bool CSettingInt::FromString(const std::string &value) +{ + int iValue; + if (!fromString(value, iValue)) + return false; + + return SetValue(iValue); +} + +std::string CSettingInt::ToString() const +{ + std::ostringstream oss; + oss << m_value; + + return oss.str(); +} + +bool CSettingInt::Equals(const std::string &value) const +{ + int iValue; + return (fromString(value, iValue) && m_value == iValue); +} + +bool CSettingInt::CheckValidity(const std::string &value) const +{ + int iValue; + if (!fromString(value, iValue)) + return false; + + return CheckValidity(iValue); +} + +bool CSettingInt::CheckValidity(int value) const +{ + if (!m_translatableOptions.empty()) + { + if (!CheckSettingOptionsValidity(value, m_translatableOptions)) + return false; + } + else if (!m_options.empty()) + { + if (!CheckSettingOptionsValidity(value, m_options)) + return false; + } + else if (m_optionsFillerName.empty() && m_optionsFiller == nullptr && + m_min != m_max && (value < m_min || value > m_max)) + return false; + + return true; +} + +bool CSettingInt::SetValue(int value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + int oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingInt>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingInt>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingInt>()); + return true; +} + +void CSettingInt::SetDefault(int value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +SettingOptionsType CSettingInt::GetOptionsType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_translatableOptions.empty()) + return SettingOptionsType::StaticTranslatable; + if (!m_options.empty()) + return SettingOptionsType::Static; + if (!m_optionsFillerName.empty() || m_optionsFiller != nullptr) + return SettingOptionsType::Dynamic; + + return SettingOptionsType::Unknown; +} + +IntegerSettingOptions CSettingInt::UpdateDynamicOptions() +{ + std::unique_lock<CSharedSection> lock(m_critical); + IntegerSettingOptions options; + if (m_optionsFiller == nullptr && + (m_optionsFillerName.empty() || m_settingsManager == nullptr)) + return options; + + if (m_optionsFiller == nullptr) + { + m_optionsFiller = reinterpret_cast<IntegerSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>())); + if (m_optionsFiller == nullptr) + { + s_logger->warn("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id); + return options; + } + } + + int bestMatchingValue = m_value; + m_optionsFiller(shared_from_base<CSettingInt>(), options, bestMatchingValue, m_optionsFillerData); + + if (bestMatchingValue != m_value) + SetValue(bestMatchingValue); + + bool changed = m_dynamicOptions.size() != options.size(); + if (!changed) + { + for (size_t index = 0; index < options.size(); index++) + { + if (options[index].label.compare(m_dynamicOptions[index].label) != 0 || + options[index].value != m_dynamicOptions[index].value) + { + changed = true; + break; + } + } + } + + if (changed) + { + m_dynamicOptions = options; + OnSettingPropertyChanged(shared_from_base<CSettingInt>(), "options"); + } + + return options; +} + +void CSettingInt::copy(const CSettingInt &setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + + m_value = setting.m_value; + m_default = setting.m_default; + m_min = setting.m_min; + m_step = setting.m_step; + m_max = setting.m_max; + m_translatableOptions = setting.m_translatableOptions; + m_options = setting.m_options; + m_optionsFillerName = setting.m_optionsFillerName; + m_optionsFiller = setting.m_optionsFiller; + m_optionsFillerData = setting.m_optionsFillerData; + m_dynamicOptions = setting.m_dynamicOptions; +} + +bool CSettingInt::fromString(const std::string &strValue, int &value) +{ + if (strValue.empty()) + return false; + + char *end = nullptr; + value = (int)strtol(strValue.c_str(), &end, 10); + if (end != nullptr && *end != '\0') + return false; + + return true; +} + +Logger CSettingNumber::s_logger; + +CSettingNumber::CSettingNumber(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingNumber(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingNumber::CSettingNumber(const std::string& id, const CSettingNumber& setting) + : CSettingNumber(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingNumber::CSettingNumber(const std::string& id, + int label, + float value, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingNumber(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager) +{ +} + +CSettingNumber::CSettingNumber(const std::string& id, + int label, + float value, + float minimum, + float step, + float maximum, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), + m_value(static_cast<double>(value)), + m_default(static_cast<double>(value)), + m_min(static_cast<double>(minimum)), + m_step(static_cast<double>(step)), + m_max(static_cast<double>(maximum)) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingNumber"); +} + +SettingPtr CSettingNumber::Clone(const std::string &id) const +{ + return std::make_shared<CSettingNumber>(id, *this); +} + +void CSettingNumber::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Number) + return; + + const auto& numberSetting = static_cast<const CSettingNumber&>(other); + if (m_default == 0.0 && numberSetting.m_default != 0.0) + m_default = numberSetting.m_default; + if (m_value == m_default && numberSetting.m_value != m_default) + m_value = numberSetting.m_value; + if (m_min == 0.0 && numberSetting.m_min != 0.0) + m_min = numberSetting.m_min; + if (m_step == 1.0 && numberSetting.m_step != 1.0) + m_step = numberSetting.m_step; + if (m_max == 0.0 && numberSetting.m_max != 0.0) + m_max = numberSetting.m_max; +} + +bool CSettingNumber::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + double value; + if (XMLUtils::GetDouble(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get the minimum value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MINIMUM, m_min); + // get the step value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_STEP, m_step); + // get the maximum value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MAXIMUM, m_max); + } + + return true; +} + +bool CSettingNumber::FromString(const std::string &value) +{ + double dValue; + if (!fromString(value, dValue)) + return false; + + return SetValue(dValue); +} + +std::string CSettingNumber::ToString() const +{ + std::ostringstream oss; + oss << m_value; + + return oss.str(); +} + +bool CSettingNumber::Equals(const std::string &value) const +{ + double dValue; + std::shared_lock<CSharedSection> lock(m_critical); + return (fromString(value, dValue) && m_value == dValue); +} + +bool CSettingNumber::CheckValidity(const std::string &value) const +{ + double dValue; + if (!fromString(value, dValue)) + return false; + + return CheckValidity(dValue); +} + +bool CSettingNumber::CheckValidity(double value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (m_min != m_max && + (value < m_min || value > m_max)) + return false; + + return true; +} + +bool CSettingNumber::SetValue(double value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + double oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingNumber>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingNumber>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingNumber>()); + return true; +} + +void CSettingNumber::SetDefault(double value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +void CSettingNumber::copy(const CSettingNumber &setting) +{ + CSetting::Copy(setting); + std::unique_lock<CSharedSection> lock(m_critical); + + m_value = setting.m_value; + m_default = setting.m_default; + m_min = setting.m_min; + m_step = setting.m_step; + m_max = setting.m_max; +} + +bool CSettingNumber::fromString(const std::string &strValue, double &value) +{ + if (strValue.empty()) + return false; + + char *end = nullptr; + value = strtod(strValue.c_str(), &end); + if (end != nullptr && *end != '\0') + return false; + + return true; +} + +const CSettingString::Value CSettingString::DefaultValue; +Logger CSettingString::s_logger; + +CSettingString::CSettingString(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingString(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingString::CSettingString(const std::string& id, const CSettingString& setting) + : CSettingString(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingString::CSettingString(const std::string& id, + int label, + const std::string& value, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), m_value(value), m_default(value) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingString"); +} + +SettingPtr CSettingString::Clone(const std::string &id) const +{ + return std::make_shared<CSettingString>(id, *this); +} + +void CSettingString::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::String) + return; + + const auto& stringSetting = static_cast<const CSettingString&>(other); + if (m_default.empty() && !stringSetting.m_default.empty()) + m_default = stringSetting.m_default; + if (m_value == m_default && stringSetting.m_value != m_default) + m_value = stringSetting.m_value; + if (m_allowEmpty == false && stringSetting.m_allowEmpty == true) + m_allowEmpty = stringSetting.m_allowEmpty; + if (m_allowNewOption == false && stringSetting.m_allowNewOption == true) + m_allowNewOption = stringSetting.m_allowNewOption; + if (m_translatableOptions.empty() && !stringSetting.m_translatableOptions.empty()) + m_translatableOptions = stringSetting.m_translatableOptions; + if (m_options.empty() && !stringSetting.m_options.empty()) + m_options = stringSetting.m_options; + if (m_optionsFillerName.empty() && !stringSetting.m_optionsFillerName.empty()) + m_optionsFillerName = stringSetting.m_optionsFillerName; + if (m_optionsFiller == nullptr && stringSetting.m_optionsFiller != nullptr) + m_optionsFiller = stringSetting.m_optionsFiller; + if (m_optionsFillerData == nullptr && stringSetting.m_optionsFillerData != nullptr) + m_optionsFillerData = stringSetting.m_optionsFillerData; + if (m_dynamicOptions.empty() && !stringSetting.m_dynamicOptions.empty()) + m_dynamicOptions = stringSetting.m_dynamicOptions; + if (m_optionsSort == SettingOptionsSort::NoSorting && + stringSetting.m_optionsSort != SettingOptionsSort::NoSorting) + m_optionsSort = stringSetting.m_optionsSort; +} + +bool CSettingString::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get allowempty (needs to be parsed before parsing the default value) + XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWEMPTY, m_allowEmpty); + + // Values other than those in options constraints allowed to be added + XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWNEWOPTION, m_allowNewOption); + + // get the entries + auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS); + if (options != nullptr && options->FirstChild() != nullptr) + { + if (!DeserializeOptionsSort(options, m_optionsSort)) + s_logger->warn("invalid \"sort\" attribute of <" SETTING_XML_ELM_OPTIONS "> for \"{}\"", + m_id); + + if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + { + m_optionsFillerName = options->FirstChild()->ValueStr(); + if (!m_optionsFillerName.empty()) + { + m_optionsFiller = reinterpret_cast<StringSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>())); + } + } + else + { + m_translatableOptions.clear(); + auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION); + while (optionElement != nullptr) + { + TranslatableStringSettingOption entry; + if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.first) == TIXML_SUCCESS && entry.first > 0) + { + entry.second = optionElement->FirstChild()->Value(); + m_translatableOptions.push_back(entry); + } + else + { + const std::string value = optionElement->FirstChild()->Value(); + // if a specific "label" attribute is present use it otherwise use the value as label + std::string label = value; + optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label); + + m_options.emplace_back(label, value); + } + + optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION); + } + } + } + } + + // get the default value + std::string value; + if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, value) && + (!value.empty() || m_allowEmpty)) + m_value = m_default = value; + else if (!update && !m_allowEmpty) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + return true; +} + +bool CSettingString::CheckValidity(const std::string &value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_allowEmpty && value.empty()) + return false; + + if (!m_translatableOptions.empty()) + { + if (!CheckSettingOptionsValidity(value, m_translatableOptions)) + return false; + } + else if (!m_options.empty() && !m_allowNewOption) + { + if (!CheckSettingOptionsValidity(value, m_options)) + return false; + } + + return true; +} + +bool CSettingString::SetValue(const std::string &value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + std::string oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingString>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingString>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingString>()); + return true; +} + +void CSettingString::SetDefault(const std::string &value) +{ + std::shared_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +SettingOptionsType CSettingString::GetOptionsType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_translatableOptions.empty()) + return SettingOptionsType::StaticTranslatable; + if (!m_options.empty()) + return SettingOptionsType::Static; + if (!m_optionsFillerName.empty() || m_optionsFiller != nullptr) + return SettingOptionsType::Dynamic; + + return SettingOptionsType::Unknown; +} + +StringSettingOptions CSettingString::UpdateDynamicOptions() +{ + std::unique_lock<CSharedSection> lock(m_critical); + StringSettingOptions options; + if (m_optionsFiller == nullptr && + (m_optionsFillerName.empty() || m_settingsManager == nullptr)) + return options; + + if (m_optionsFiller == nullptr) + { + m_optionsFiller = reinterpret_cast<StringSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>())); + if (m_optionsFiller == nullptr) + { + s_logger->error("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id); + return options; + } + } + + std::string bestMatchingValue = m_value; + m_optionsFiller(shared_from_base<CSettingString>(), options, bestMatchingValue, m_optionsFillerData); + + if (bestMatchingValue != m_value) + SetValue(bestMatchingValue); + + // check if the list of items has changed + bool changed = m_dynamicOptions.size() != options.size(); + if (!changed) + { + for (size_t index = 0; index < options.size(); index++) + { + if (options[index].label.compare(m_dynamicOptions[index].label) != 0 || + options[index].value.compare(m_dynamicOptions[index].value) != 0) + { + changed = true; + break; + } + } + } + + if (changed) + { + m_dynamicOptions = options; + OnSettingPropertyChanged(shared_from_base<CSettingString>(), "options"); + } + + return options; +} + +void CSettingString::copy(const CSettingString &setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_value = setting.m_value; + m_default = setting.m_default; + m_allowEmpty = setting.m_allowEmpty; + m_allowNewOption = setting.m_allowNewOption; + m_translatableOptions = setting.m_translatableOptions; + m_options = setting.m_options; + m_optionsFillerName = setting.m_optionsFillerName; + m_optionsFiller = setting.m_optionsFiller; + m_optionsFillerData = setting.m_optionsFillerData; + m_dynamicOptions = setting.m_dynamicOptions; +} + +Logger CSettingAction::s_logger; + +CSettingAction::CSettingAction(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingAction(id, DefaultLabel, settingsManager) +{ } + +CSettingAction::CSettingAction(const std::string& id, + int label, + CSettingsManager* settingsManager /* = nullptr */) + : CSetting(id, settingsManager) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingAction"); +} + +CSettingAction::CSettingAction(const std::string& id, const CSettingAction& setting) + : CSettingAction(id, setting.m_settingsManager) +{ + copy(setting); +} + +SettingPtr CSettingAction::Clone(const std::string &id) const +{ + return std::make_shared<CSettingAction>(id, *this); +} + +void CSettingAction::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Action) + return; + + const auto& actionSetting = static_cast<const CSettingAction&>(other); + if (!HasData() && actionSetting.HasData()) + SetData(actionSetting.GetData()); +} + +bool CSettingAction::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + m_data = XMLUtils::GetString(node, SETTING_XML_ELM_DATA); + + return true; +} + +void CSettingAction::copy(const CSettingAction& setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_data = setting.m_data; +} diff --git a/xbmc/settings/lib/Setting.h b/xbmc/settings/lib/Setting.h new file mode 100644 index 0000000..9e3ed3f --- /dev/null +++ b/xbmc/settings/lib/Setting.h @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISetting.h" +#include "ISettingCallback.h" +#include "ISettingControl.h" +#include "SettingDefinitions.h" +#include "SettingDependency.h" +#include "SettingLevel.h" +#include "SettingType.h" +#include "SettingUpdate.h" +#include "threads/SharedSection.h" +#include "utils/logtypes.h" + +#include <memory> +#include <set> +#include <shared_mutex> +#include <string> +#include <utility> +#include <vector> + +enum class SettingOptionsType { + Unknown = 0, + StaticTranslatable, + Static, + Dynamic +}; + +class CSetting; +using SettingPtr = std::shared_ptr<CSetting>; +using SettingConstPtr = std::shared_ptr<const CSetting>; +using SettingList = std::vector<SettingPtr>; + +/*! + \ingroup settings + \brief Setting base class containing all the properties which are common to + all settings independent of the setting type. + */ +class CSetting : public ISetting, + protected ISettingCallback, + public std::enable_shared_from_this<CSetting> +{ +public: + CSetting(const std::string& id, CSettingsManager* settingsManager = nullptr); + CSetting(const std::string& id, const CSetting& setting); + ~CSetting() override = default; + + virtual std::shared_ptr<CSetting> Clone(const std::string &id) const = 0; + void MergeBasics(const CSetting& other); + virtual void MergeDetails(const CSetting& other) = 0; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + virtual SettingType GetType() const = 0; + virtual bool FromString(const std::string &value) = 0; + virtual std::string ToString() const = 0; + virtual bool Equals(const std::string &value) const = 0; + virtual bool CheckValidity(const std::string &value) const = 0; + virtual void Reset() = 0; + + bool IsEnabled() const; + bool GetEnabled() const { return m_enabled; } + void SetEnabled(bool enabled); + bool IsDefault() const { return !m_changed; } + const std::string& GetParent() const { return m_parentSetting; } + void SetParent(const std::string& parentSetting) { m_parentSetting = parentSetting; } + SettingLevel GetLevel() const { return m_level; } + void SetLevel(SettingLevel level) { m_level = level; } + std::shared_ptr<const ISettingControl> GetControl() const { return m_control; } + std::shared_ptr<ISettingControl> GetControl() { return m_control; } + void SetControl(std::shared_ptr<ISettingControl> control) { m_control = std::move(control); } + const SettingDependencies& GetDependencies() const { return m_dependencies; } + void SetDependencies(const SettingDependencies &dependencies) { m_dependencies = dependencies; } + const std::set<CSettingUpdate>& GetUpdates() const { return m_updates; } + + void SetCallback(ISettingCallback *callback) { m_callback = callback; } + + bool IsReference() const { return !m_referencedId.empty(); } + const std::string& GetReferencedId() const { return m_referencedId; } + void SetReferencedId(const std::string& referencedId) { m_referencedId = referencedId; } + void MakeReference(const std::string& referencedId = ""); + + bool GetVisible() const { return ISetting::IsVisible(); } + // overrides of ISetting + bool IsVisible() const override; + + // implementation of ISettingCallback + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + /*! + \brief Deserializes the given XML node to retrieve a setting object's identifier and + whether the setting is a reference to another setting or not. + + \param node XML node containing a setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \param isReference Whether the setting is a reference to the setting with the determined identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentification(const TiXmlNode* node, + std::string& identification, + bool& isReference); + +protected: + // implementation of ISettingCallback + bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; + void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) override; + + void Copy(const CSetting &setting); + + template<class TSetting> + std::shared_ptr<TSetting> shared_from_base() + { + return std::static_pointer_cast<TSetting>(shared_from_this()); + } + + ISettingCallback *m_callback = nullptr; + bool m_enabled = true; + std::string m_parentSetting; + SettingLevel m_level = SettingLevel::Standard; + std::shared_ptr<ISettingControl> m_control; + SettingDependencies m_dependencies; + std::set<CSettingUpdate> m_updates; + bool m_changed = false; + mutable CSharedSection m_critical; + + std::string m_referencedId; + +private: + static Logger s_logger; +}; + +template<typename TValue, SettingType TSettingType> +class CTraitedSetting : public CSetting +{ +public: + typedef TValue Value; + + // implementation of CSetting + SettingType GetType() const override { return TSettingType; } + + static SettingType Type() { return TSettingType; } + +protected: + CTraitedSetting(const std::string& id, CSettingsManager* settingsManager = nullptr) + : CSetting(id, settingsManager) + { } + CTraitedSetting(const std::string& id, const CTraitedSetting& setting) : CSetting(id, setting) {} + ~CTraitedSetting() override = default; +}; + +/*! + \ingroup settings + \brief List setting implementation + \sa CSetting + */ +class CSettingList : public CSetting +{ +public: + CSettingList(const std::string &id, std::shared_ptr<CSetting> settingDefinition, CSettingsManager *settingsManager = nullptr); + CSettingList(const std::string &id, std::shared_ptr<CSetting> settingDefinition, int label, CSettingsManager *settingsManager = nullptr); + CSettingList(const std::string &id, const CSettingList &setting); + ~CSettingList() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + SettingType GetType() const override { return SettingType::List; } + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + void Reset() override; + + SettingType GetElementType() const; + std::shared_ptr<CSetting> GetDefinition() { return m_definition; } + std::shared_ptr<const CSetting> GetDefinition() const { return m_definition; } + void SetDefinition(std::shared_ptr<CSetting> definition) { m_definition = std::move(definition); } + + const std::string& GetDelimiter() const { return m_delimiter; } + void SetDelimiter(const std::string &delimiter) { m_delimiter = delimiter; } + int GetMinimumItems() const { return m_minimumItems; } + void SetMinimumItems(int minimumItems) { m_minimumItems = minimumItems; } + int GetMaximumItems() const { return m_maximumItems; } + void SetMaximumItems(int maximumItems) { m_maximumItems = maximumItems; } + + bool FromString(const std::vector<std::string> &value); + + const SettingList& GetValue() const { return m_values; } + bool SetValue(const SettingList &values); + const SettingList& GetDefault() const { return m_defaults; } + void SetDefault(const SettingList &values); + +protected: + void copy(const CSettingList &setting); + static void copy(const SettingList &srcValues, SettingList &dstValues); + bool fromString(const std::string &strValue, SettingList &values) const; + bool fromValues(const std::vector<std::string> &strValues, SettingList &values) const; + std::string toString(const SettingList &values) const; + + SettingList m_values; + SettingList m_defaults; + std::shared_ptr<CSetting> m_definition; + std::string m_delimiter = "|"; + int m_minimumItems = 0; + int m_maximumItems = -1; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Boolean setting implementation. + \sa CSetting + */ +class CSettingBool : public CTraitedSetting<bool, SettingType::Boolean> +{ +public: + CSettingBool(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingBool(const std::string &id, const CSettingBool &setting); + CSettingBool(const std::string &id, int label, bool value, CSettingsManager *settingsManager = nullptr); + ~CSettingBool() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + void Reset() override { SetValue(m_default); } + + bool GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(bool value); + bool GetDefault() const { return m_default; } + void SetDefault(bool value); + +private: + static constexpr Value DefaultValue = false; + + void copy(const CSettingBool &setting); + bool fromString(const std::string &strValue, bool &value) const; + + bool m_value = DefaultValue; + bool m_default = DefaultValue; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Integer setting implementation + \sa CSetting + */ +class CSettingInt : public CTraitedSetting<int, SettingType::Integer> +{ +public: + CSettingInt(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, const CSettingInt &setting); + CSettingInt(const std::string &id, int label, int value, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, int label, int value, int minimum, int step, int maximum, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, int label, int value, const TranslatableIntegerSettingOptions &options, CSettingsManager *settingsManager = nullptr); + ~CSettingInt() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + virtual bool CheckValidity(int value) const; + void Reset() override { SetValue(m_default); } + + int GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(int value); + int GetDefault() const { return m_default; } + void SetDefault(int value); + + int GetMinimum() const { return m_min; } + void SetMinimum(int minimum) { m_min = minimum; } + int GetStep() const { return m_step; } + void SetStep(int step) { m_step = step; } + int GetMaximum() const { return m_max; } + void SetMaximum(int maximum) { m_max = maximum; } + + SettingOptionsType GetOptionsType() const; + const TranslatableIntegerSettingOptions& GetTranslatableOptions() const { return m_translatableOptions; } + void SetTranslatableOptions(const TranslatableIntegerSettingOptions &options) { m_translatableOptions = options; } + const IntegerSettingOptions& GetOptions() const { return m_options; } + void SetOptions(const IntegerSettingOptions &options) { m_options = options; } + const std::string& GetOptionsFillerName() const { return m_optionsFillerName; } + void SetOptionsFillerName(const std::string &optionsFillerName, void *data = nullptr) + { + m_optionsFillerName = optionsFillerName; + m_optionsFillerData = data; + } + void SetOptionsFiller(IntegerSettingOptionsFiller optionsFiller, void *data = nullptr) + { + m_optionsFiller = optionsFiller; + m_optionsFillerData = data; + } + IntegerSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + IntegerSettingOptions UpdateDynamicOptions(); + SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } + void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } + +private: + static constexpr Value DefaultValue = 0; + static constexpr Value DefaultMin = DefaultValue; + static constexpr Value DefaultStep = 1; + static constexpr Value DefaultMax = DefaultValue; + + void copy(const CSettingInt &setting); + static bool fromString(const std::string &strValue, int &value); + + int m_value = DefaultValue; + int m_default = DefaultValue; + int m_min = DefaultMin; + int m_step = DefaultStep; + int m_max = DefaultMax; + TranslatableIntegerSettingOptions m_translatableOptions; + IntegerSettingOptions m_options; + std::string m_optionsFillerName; + IntegerSettingOptionsFiller m_optionsFiller = nullptr; + void *m_optionsFillerData = nullptr; + IntegerSettingOptions m_dynamicOptions; + SettingOptionsSort m_optionsSort = SettingOptionsSort::NoSorting; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Real number setting implementation. + \sa CSetting + */ +class CSettingNumber : public CTraitedSetting<double, SettingType::Number> +{ +public: + CSettingNumber(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingNumber(const std::string &id, const CSettingNumber &setting); + CSettingNumber(const std::string &id, int label, float value, CSettingsManager *settingsManager = nullptr); + CSettingNumber(const std::string &id, int label, float value, float minimum, float step, float maximum, CSettingsManager *settingsManager = nullptr); + ~CSettingNumber() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + virtual bool CheckValidity(double value) const; + void Reset() override { SetValue(m_default); } + + double GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(double value); + double GetDefault() const { return m_default; } + void SetDefault(double value); + + double GetMinimum() const { return m_min; } + void SetMinimum(double minimum) { m_min = minimum; } + double GetStep() const { return m_step; } + void SetStep(double step) { m_step = step; } + double GetMaximum() const { return m_max; } + void SetMaximum(double maximum) { m_max = maximum; } + +private: + static constexpr Value DefaultValue = 0.0; + static constexpr Value DefaultMin = DefaultValue; + static constexpr Value DefaultStep = 1.0; + static constexpr Value DefaultMax = DefaultValue; + + virtual void copy(const CSettingNumber &setting); + static bool fromString(const std::string &strValue, double &value); + + double m_value = DefaultValue; + double m_default = DefaultValue; + double m_min = DefaultMin; + double m_step = DefaultStep; + double m_max = DefaultMax; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief String setting implementation. + \sa CSetting + */ +class CSettingString : public CTraitedSetting<std::string, SettingType::String> +{ +public: + CSettingString(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingString(const std::string &id, const CSettingString &setting); + CSettingString(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = nullptr); + ~CSettingString() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override { return SetValue(value); } + std::string ToString() const override { return m_value; } + bool Equals(const std::string &value) const override { return m_value == value; } + bool CheckValidity(const std::string &value) const override; + void Reset() override { SetValue(m_default); } + + virtual const std::string& GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + virtual bool SetValue(const std::string &value); + virtual const std::string& GetDefault() const { return m_default; } + virtual void SetDefault(const std::string &value); + + virtual bool AllowEmpty() const { return m_allowEmpty; } + void SetAllowEmpty(bool allowEmpty) { m_allowEmpty = allowEmpty; } + virtual bool AllowNewOption() const { return m_allowNewOption; } + void SetAllowNewOption(bool allowNewOption) { m_allowNewOption = allowNewOption; } + + SettingOptionsType GetOptionsType() const; + const TranslatableStringSettingOptions& GetTranslatableOptions() const { return m_translatableOptions; } + void SetTranslatableOptions(const TranslatableStringSettingOptions &options) { m_translatableOptions = options; } + const StringSettingOptions& GetOptions() const { return m_options; } + void SetOptions(const StringSettingOptions &options) { m_options = options; } + const std::string& GetOptionsFillerName() const { return m_optionsFillerName; } + void SetOptionsFillerName(const std::string &optionsFillerName, void *data = nullptr) + { + m_optionsFillerName = optionsFillerName; + m_optionsFillerData = data; + } + void SetOptionsFiller(StringSettingOptionsFiller optionsFiller, void *data = nullptr) + { + m_optionsFiller = optionsFiller; + m_optionsFillerData = data; + } + StringSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + StringSettingOptions UpdateDynamicOptions(); + SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } + void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } + +protected: + static const Value DefaultValue; + + virtual void copy(const CSettingString &setting); + + std::string m_value; + std::string m_default; + bool m_allowEmpty = false; + bool m_allowNewOption = false; + TranslatableStringSettingOptions m_translatableOptions; + StringSettingOptions m_options; + std::string m_optionsFillerName; + StringSettingOptionsFiller m_optionsFiller = nullptr; + void *m_optionsFillerData = nullptr; + StringSettingOptions m_dynamicOptions; + SettingOptionsSort m_optionsSort = SettingOptionsSort::NoSorting; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Action setting implementation. + + A setting action will trigger a call to the OnSettingAction() callback method + when activated. + + \sa CSetting + */ +class CSettingAction : public CSetting +{ +public: + CSettingAction(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingAction(const std::string &id, int label, CSettingsManager *settingsManager = nullptr); + CSettingAction(const std::string &id, const CSettingAction &setting); + ~CSettingAction() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + SettingType GetType() const override { return SettingType::Action; } + bool FromString(const std::string &value) override { return CheckValidity(value); } + std::string ToString() const override { return ""; } + bool Equals(const std::string &value) const override { return value.empty(); } + bool CheckValidity(const std::string &value) const override { return value.empty(); } + void Reset() override { } + + bool HasData() const { return !m_data.empty(); } + const std::string& GetData() const { return m_data; } + void SetData(const std::string& data) { m_data = data; } + +protected: + virtual void copy(const CSettingAction& setting); + + std::string m_data; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/SettingCategoryAccess.cpp b/xbmc/settings/lib/SettingCategoryAccess.cpp new file mode 100644 index 0000000..4640586 --- /dev/null +++ b/xbmc/settings/lib/SettingCategoryAccess.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingCategoryAccess.h" + +#include "SettingConditions.h" +#include "SettingsManager.h" + +bool CSettingCategoryAccessCondition::Check() const +{ + if (m_value.empty()) + return true; + + if (m_settingsManager == nullptr) + return false; + + bool found = m_settingsManager->GetConditions().Check(m_value, "true"); + if (m_negated) + return !found; + + return found; +} + +bool CSettingCategoryAccessConditionCombination::Check() const +{ + if (m_operations.empty() && m_values.empty()) + return true; + + return CSettingConditionCombination::Check(); +} + +CSettingCategoryAccess::CSettingCategoryAccess(CSettingsManager *settingsManager /* = nullptr */) + : CSettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingCategoryAccessConditionCombination(m_settingsManager)); +} diff --git a/xbmc/settings/lib/SettingCategoryAccess.h b/xbmc/settings/lib/SettingCategoryAccess.h new file mode 100644 index 0000000..5d4ceb8 --- /dev/null +++ b/xbmc/settings/lib/SettingCategoryAccess.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" + +#include <set> +#include <string> + +class CSettingCategoryAccessCondition : public CSettingConditionItem +{ +public: + explicit CSettingCategoryAccessCondition(CSettingsManager *settingsManager = nullptr) + : CSettingConditionItem(settingsManager) + { } + ~CSettingCategoryAccessCondition() override = default; + + bool Check() const override; +}; + +class CSettingCategoryAccessConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingCategoryAccessConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + ~CSettingCategoryAccessConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingCategoryAccessConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingCategoryAccessCondition(m_settingsManager); } +}; + +class CSettingCategoryAccess : public CSettingCondition +{ +public: + explicit CSettingCategoryAccess(CSettingsManager *settingsManager = nullptr); + ~CSettingCategoryAccess() override = default; +}; diff --git a/xbmc/settings/lib/SettingConditions.cpp b/xbmc/settings/lib/SettingConditions.cpp new file mode 100644 index 0000000..7146834 --- /dev/null +++ b/xbmc/settings/lib/SettingConditions.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingConditions.h" + +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +bool CSettingConditionItem::Deserialize(const TiXmlNode *node) +{ + if (!CBooleanLogicValue::Deserialize(node)) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + // get the "name" attribute + auto strAttribute = elem->Attribute(SETTING_XML_ATTR_NAME); + if (strAttribute != nullptr) + m_name = strAttribute; + + // get the "setting" attribute + strAttribute = elem->Attribute(SETTING_XML_ATTR_SETTING); + if (strAttribute != nullptr) + m_setting = strAttribute; + + return true; +} + +bool CSettingConditionItem::Check() const +{ + if (m_settingsManager == nullptr) + return false; + + return m_settingsManager->GetConditions().Check(m_name, m_value, m_settingsManager->GetSetting(m_setting)) == !m_negated; +} + +bool CSettingConditionCombination::Check() const +{ + bool ok = false; + for (const auto& operation : m_operations) + { + if (operation == nullptr) + continue; + + const auto combination = std::static_pointer_cast<const CSettingConditionCombination>(operation); + if (combination == nullptr) + continue; + + if (combination->Check()) + ok = true; + else if (m_operation == BooleanLogicOperationAnd) + return false; + } + + for (const auto& value : m_values) + { + if (value == nullptr) + continue; + + const auto condition = std::static_pointer_cast<const CSettingConditionItem>(value); + if (condition == nullptr) + continue; + + if (condition->Check()) + ok = true; + else if (m_operation == BooleanLogicOperationAnd) + return false; + } + + return ok; +} + +CSettingCondition::CSettingCondition(CSettingsManager *settingsManager /* = nullptr */) + : ISettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingConditionCombination(settingsManager)); +} + +bool CSettingCondition::Check() const +{ + auto combination = std::static_pointer_cast<CSettingConditionCombination>(m_operation); + if (combination == nullptr) + return false; + + return combination->Check(); +} + +void CSettingConditionsManager::AddCondition(std::string condition) +{ + if (condition.empty()) + return; + + StringUtils::ToLower(condition); + + m_defines.insert(condition); +} + +void CSettingConditionsManager::AddDynamicCondition(std::string identifier, SettingConditionCheck condition, void *data /*= nullptr*/) +{ + if (identifier.empty() || condition == nullptr) + return; + + StringUtils::ToLower(identifier); + + m_conditions.emplace(identifier, std::make_pair(condition, data)); +} + +void CSettingConditionsManager::RemoveDynamicCondition(std::string identifier) +{ + if (identifier.empty()) + return; + + StringUtils::ToLower(identifier); + + auto it = m_conditions.find(identifier); + if (it != m_conditions.end()) + m_conditions.erase(it); +} + +bool CSettingConditionsManager::Check( + std::string condition, + const std::string& value /* = "" */, + const std::shared_ptr<const CSetting>& setting /* = nullptr */) const +{ + if (condition.empty()) + return false; + + StringUtils::ToLower(condition); + + // special handling of "isdefined" conditions + if (condition == "isdefined") + { + std::string tmpValue = value; + StringUtils::ToLower(tmpValue); + + return m_defines.find(tmpValue) != m_defines.end(); + } + + auto conditionIt = m_conditions.find(condition); + if (conditionIt == m_conditions.end()) + return false; + + return conditionIt->second.first(condition, value, setting, conditionIt->second.second); +} diff --git a/xbmc/settings/lib/SettingConditions.h b/xbmc/settings/lib/SettingConditions.h new file mode 100644 index 0000000..aee2c06 --- /dev/null +++ b/xbmc/settings/lib/SettingConditions.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingDefinitions.h" +#include "utils/BooleanLogic.h" + +#include <map> +#include <set> +#include <string> +#include <utility> + +class CSettingsManager; +class CSetting; + +using SettingConditionCheck = bool (*)(const std::string& condition, + const std::string& value, + const std::shared_ptr<const CSetting>& setting, + void* data); + +class ISettingCondition +{ +public: + explicit ISettingCondition(CSettingsManager *settingsManager) + : m_settingsManager(settingsManager) + { } + virtual ~ISettingCondition() = default; + + virtual bool Check() const = 0; + +protected: + CSettingsManager *m_settingsManager; +}; + +class CSettingConditionItem : public CBooleanLogicValue, public ISettingCondition +{ +public: + explicit CSettingConditionItem(CSettingsManager *settingsManager = nullptr) + : ISettingCondition(settingsManager) + { } + ~CSettingConditionItem() override = default; + + bool Deserialize(const TiXmlNode *node) override; + const char* GetTag() const override { return SETTING_XML_ELM_CONDITION; } + bool Check() const override; + +protected: + std::string m_name; + std::string m_setting; +}; + +class CSettingConditionCombination : public CBooleanLogicOperation, public ISettingCondition +{ +public: + explicit CSettingConditionCombination(CSettingsManager *settingsManager = nullptr) + : ISettingCondition(settingsManager) + { } + ~CSettingConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingConditionItem(m_settingsManager); } +}; + +class CSettingCondition : public CBooleanLogic, public ISettingCondition +{ +public: + explicit CSettingCondition(CSettingsManager *settingsManager = nullptr); + ~CSettingCondition() override = default; + + bool Check() const override; +}; + +class CSettingConditionsManager +{ +public: + CSettingConditionsManager() = default; + CSettingConditionsManager(const CSettingConditionsManager&) = delete; + CSettingConditionsManager const& operator=(CSettingConditionsManager const&) = delete; + virtual ~CSettingConditionsManager() = default; + + void AddCondition(std::string condition); + void AddDynamicCondition(std::string identifier, SettingConditionCheck condition, void *data = nullptr); + void RemoveDynamicCondition(std::string identifier); + + bool Check( + std::string condition, + const std::string& value = "", + const std::shared_ptr<const CSetting>& setting = std::shared_ptr<const CSetting>()) const; + +private: + using SettingConditionPair = std::pair<std::string, std::pair<SettingConditionCheck, void*>>; + using SettingConditionMap = std::map<std::string, std::pair<SettingConditionCheck, void*>>; + + SettingConditionMap m_conditions; + std::set<std::string> m_defines; +}; diff --git a/xbmc/settings/lib/SettingDefinitions.h b/xbmc/settings/lib/SettingDefinitions.h new file mode 100644 index 0000000..ae18347 --- /dev/null +++ b/xbmc/settings/lib/SettingDefinitions.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Variant.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#define SETTING_XML_ROOT "settings" +#define SETTING_XML_ROOT_VERSION "version" + +#define SETTING_XML_ELM_SECTION "section" +#define SETTING_XML_ELM_CATEGORY "category" +#define SETTING_XML_ELM_GROUP "group" +#define SETTING_XML_ELM_SETTING "setting" +#define SETTING_XML_ELM_VISIBLE "visible" +#define SETTING_XML_ELM_REQUIREMENT "requirement" +#define SETTING_XML_ELM_CONDITION "condition" +#define SETTING_XML_ELM_ENABLED "enable" +#define SETTING_XML_ELM_LEVEL "level" +#define SETTING_XML_ELM_DEFAULT "default" +#define SETTING_XML_ELM_VALUE "value" +#define SETTING_XML_ELM_CONTROL "control" +#define SETTING_XML_ELM_CONSTRAINTS "constraints" +#define SETTING_XML_ELM_OPTIONS "options" +#define SETTING_XML_ELM_OPTION "option" +#define SETTING_XML_ELM_MINIMUM "minimum" +#define SETTING_XML_ELM_STEP "step" +#define SETTING_XML_ELM_MAXIMUM "maximum" +#define SETTING_XML_ELM_ALLOWEMPTY "allowempty" +#define SETTING_XML_ELM_ALLOWNEWOPTION "allownewoption" +#define SETTING_XML_ELM_DEPENDENCIES "dependencies" +#define SETTING_XML_ELM_DEPENDENCY "dependency" +#define SETTING_XML_ELM_UPDATES "updates" +#define SETTING_XML_ELM_UPDATE "update" +#define SETTING_XML_ELM_ACCESS "access" +#define SETTING_XML_ELM_DELIMITER "delimiter" +#define SETTING_XML_ELM_MINIMUM_ITEMS "minimumitems" +#define SETTING_XML_ELM_MAXIMUM_ITEMS "maximumitems" +#define SETTING_XML_ELM_DATA "data" + +#define SETTING_XML_ATTR_ID "id" +#define SETTING_XML_ATTR_REFERENCE "ref" +#define SETTING_XML_ATTR_LABEL "label" +#define SETTING_XML_ATTR_HELP "help" +#define SETTING_XML_ATTR_TYPE "type" +#define SETTING_XML_ATTR_PARENT "parent" +#define SETTING_XML_ATTR_FORMAT "format" +#define SETTING_XML_ATTR_DELAYED "delayed" +#define SETTING_XML_ATTR_ON "on" +#define SETTING_XML_ATTR_OPERATOR "operator" +#define SETTING_XML_ATTR_NAME "name" +#define SETTING_XML_ATTR_SETTING "setting" +#define SETTING_XML_ATTR_BEFORE "before" +#define SETTING_XML_ATTR_AFTER "after" + +struct IntegerSettingOption +{ + IntegerSettingOption(const std::string& _label, int _value) + : label(_label), value(_value) {} + + IntegerSettingOption(const std::string& _label, + const std::string& _label2, + int _value, + const std::vector<std::pair<std::string, CVariant>>& props) + : label(_label), label2(_label2), value(_value), properties(props) + { + } + + std::string label; + std::string label2; + int value = 0; + std::vector<std::pair<std::string, CVariant>> properties; +}; + +struct StringSettingOption +{ + StringSettingOption(const std::string& _label, const std::string& _value) + : label(_label), value(_value) {} + + StringSettingOption(const std::string& _label, + const std::string& _label2, + const std::string& _value, + const std::vector<std::pair<std::string, CVariant>>& props) + : label(_label), label2(_label2), value(_value), properties(props) + { + } + + std::string label; + std::string label2; + std::string value; + std::vector<std::pair<std::string, CVariant>> properties; +}; + +struct TranslatableIntegerSettingOption +{ + TranslatableIntegerSettingOption() = default; + TranslatableIntegerSettingOption(int _label, int _value, const std::string& _addonId = "") + : label(_label), value(_value), addonId(_addonId) + { + } + + int label = 0; + int value = 0; + std::string addonId; // Leaved empty for Kodi labels +}; + +using TranslatableIntegerSettingOptions = std::vector<TranslatableIntegerSettingOption>; +using IntegerSettingOptions = std::vector<IntegerSettingOption>; +using TranslatableStringSettingOption = std::pair<int, std::string>; +using TranslatableStringSettingOptions = std::vector<TranslatableStringSettingOption>; +using StringSettingOptions = std::vector<StringSettingOption>; + +class CSetting; +using IntegerSettingOptionsFiller = void (*)(const std::shared_ptr<const CSetting>& setting, + IntegerSettingOptions& list, + int& current, + void* data); +using StringSettingOptionsFiller = void (*)(const std::shared_ptr<const CSetting>& setting, + StringSettingOptions& list, + std::string& current, + void* data); + +enum class SettingOptionsSort +{ + NoSorting, + Ascending, + Descending +}; diff --git a/xbmc/settings/lib/SettingDependency.cpp b/xbmc/settings/lib/SettingDependency.cpp new file mode 100644 index 0000000..e3122da --- /dev/null +++ b/xbmc/settings/lib/SettingDependency.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingDependency.h" + +#include "ServiceBroker.h" +#include "Setting.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <memory> +#include <set> +#include <stdlib.h> +#include <string> + +Logger CSettingDependencyCondition::s_logger; + +CSettingDependencyCondition::CSettingDependencyCondition( + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition(settingsManager, "", "", "") +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + const std::string& setting, + const std::string& value, + SettingDependencyOperator op, + bool negated /* = false */, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition( + settingsManager, setting, setting, value, SettingDependencyTarget::Setting, op, negated) +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + const std::string& strProperty, + const std::string& value, + const std::string& setting /* = "" */, + bool negated /* = false */, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition(settingsManager, + strProperty, + setting, + value, + SettingDependencyTarget::Property, + SettingDependencyOperator::Equals, + negated) +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + CSettingsManager* settingsManager, + const std::string& strProperty, + const std::string& setting, + const std::string& value, + SettingDependencyTarget target /* = SettingDependencyTarget::Unknown */, + SettingDependencyOperator op /* = SettingDependencyOperator::Equals */, + bool negated /* = false */) + : CSettingConditionItem(settingsManager), m_target(target), m_operator(op) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingDependencyCondition"); + + m_name = strProperty; + m_setting = setting; + m_value = value; + m_negated = negated; +} + +bool CSettingDependencyCondition::Deserialize(const TiXmlNode *node) +{ + if (!CSettingConditionItem::Deserialize(node)) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + m_target = SettingDependencyTarget::Setting; + auto strTarget = elem->Attribute(SETTING_XML_ATTR_ON); + if (strTarget != nullptr && !setTarget(strTarget)) + { + s_logger->warn("unknown target \"{}\"", strTarget); + return false; + } + + if (m_target != SettingDependencyTarget::Setting && m_name.empty()) + { + s_logger->warn("missing name for dependency"); + return false; + } + + if (m_target == SettingDependencyTarget::Setting) + { + if (m_setting.empty()) + { + s_logger->warn("missing setting for dependency"); + return false; + } + + m_name = m_setting; + } + + m_operator = SettingDependencyOperator::Equals; + auto strOperator = elem->Attribute(SETTING_XML_ATTR_OPERATOR); + if (strOperator != nullptr && !setOperator(strOperator)) + { + s_logger->warn("unknown operator \"{}\"", strOperator); + return false; + } + + return true; +} + +bool CSettingDependencyCondition::Check() const +{ + if (m_name.empty() || + m_target == SettingDependencyTarget::Unknown || + m_operator == SettingDependencyOperator::Unknown || + m_settingsManager == nullptr) + return false; + + bool result = false; + switch (m_target) + { + case SettingDependencyTarget::Setting: + { + if (m_setting.empty()) + return false; + + auto setting = m_settingsManager->GetSetting(m_setting); + if (setting == nullptr) + { + s_logger->warn("unable to check condition on unknown setting \"{}\"", m_setting); + return false; + } + + switch (m_operator) + { + case SettingDependencyOperator::Equals: + result = setting->Equals(m_value); + break; + + case SettingDependencyOperator::LessThan: + { + const auto value = setting->ToString(); + if (StringUtils::IsInteger(m_value)) + result = strtol(value.c_str(), nullptr, 0) < strtol(m_value.c_str(), nullptr, 0); + else + result = value.compare(m_value) < 0; + break; + } + + case SettingDependencyOperator::GreaterThan: + { + const auto value = setting->ToString(); + if (StringUtils::IsInteger(m_value)) + result = strtol(value.c_str(), nullptr, 0) > strtol(m_value.c_str(), nullptr, 0); + else + result = value.compare(m_value) > 0; + break; + } + + case SettingDependencyOperator::Contains: + result = (setting->ToString().find(m_value) != std::string::npos); + break; + + case SettingDependencyOperator::Unknown: + default: + break; + } + + break; + } + + case SettingDependencyTarget::Property: + { + SettingConstPtr setting; + if (!m_setting.empty()) + { + setting = m_settingsManager->GetSetting(m_setting); + if (setting == nullptr) + { + s_logger->warn("unable to check condition on unknown setting \"{}\"", m_setting); + return false; + } + } + result = m_settingsManager->GetConditions().Check(m_name, m_value, setting); + break; + } + + default: + return false; + } + + return result == !m_negated; +} + +bool CSettingDependencyCondition::setTarget(const std::string &target) +{ + if (StringUtils::EqualsNoCase(target, "setting")) + m_target = SettingDependencyTarget::Setting; + else if (StringUtils::EqualsNoCase(target, "property")) + m_target = SettingDependencyTarget::Property; + else + return false; + + return true; +} + +bool CSettingDependencyCondition::setOperator(const std::string &op) +{ + size_t length = 0; + if (StringUtils::EndsWithNoCase(op, "is")) + { + m_operator = SettingDependencyOperator::Equals; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "lessthan")) + { + m_operator = SettingDependencyOperator::LessThan; + length = 8; + } + else if (StringUtils::EndsWithNoCase(op, "lt")) + { + m_operator = SettingDependencyOperator::LessThan; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "greaterthan")) + { + m_operator = SettingDependencyOperator::GreaterThan; + length = 11; + } + else if (StringUtils::EndsWithNoCase(op, "gt")) + { + m_operator = SettingDependencyOperator::GreaterThan; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "contains")) + { + m_operator = SettingDependencyOperator::Contains; + length = 8; + } + + if (op.size() > length + 1) + return false; + if (op.size() == length + 1) + { + if (!StringUtils::StartsWith(op, "!")) + return false; + m_negated = true; + } + + return true; +} + +bool CSettingDependencyConditionCombination::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + size_t numOperations = m_operations.size(); + size_t numValues = m_values.size(); + + if (!CSettingConditionCombination::Deserialize(node)) + return false; + + if (numOperations < m_operations.size()) + { + for (size_t i = numOperations; i < m_operations.size(); i++) + { + if (m_operations[i] == nullptr) + continue; + + auto combination = static_cast<CSettingDependencyConditionCombination*>(m_operations[i].get()); + if (combination == nullptr) + continue; + + const std::set<std::string>& settings = combination->GetSettings(); + m_settings.insert(settings.begin(), settings.end()); + } + } + + if (numValues < m_values.size()) + { + for (size_t i = numValues; i < m_values.size(); i++) + { + if (m_values[i] == nullptr) + continue; + + auto condition = static_cast<CSettingDependencyCondition*>(m_values[i].get()); + if (condition == nullptr) + continue; + + auto settingId = condition->GetSetting(); + if (!settingId.empty()) + m_settings.insert(settingId); + } + } + + return true; +} + +CSettingDependencyConditionCombination* CSettingDependencyConditionCombination::Add( + const CSettingDependencyConditionPtr& condition) +{ + if (condition != nullptr) + { + m_values.push_back(condition); + + auto settingId = condition->GetSetting(); + if (!settingId.empty()) + m_settings.insert(settingId); + } + + return this; +} + +CSettingDependencyConditionCombination* CSettingDependencyConditionCombination::Add( + const CSettingDependencyConditionCombinationPtr& operation) +{ + if (operation != nullptr) + { + m_operations.push_back(operation); + + const auto& settings = operation->GetSettings(); + m_settings.insert(settings.begin(), settings.end()); + } + + return this; +} + +Logger CSettingDependency::s_logger; + +CSettingDependency::CSettingDependency(CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependency(SettingDependencyType::Unknown, settingsManager) +{ +} + +CSettingDependency::CSettingDependency(SettingDependencyType type, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingCondition(settingsManager), m_type(type) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingDependency"); + + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); +} + +bool CSettingDependency::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strType = elem->Attribute(SETTING_XML_ATTR_TYPE); + if (strType == nullptr || strlen(strType) <= 0 || !setType(strType)) + { + s_logger->warn("missing or unknown dependency type definition"); + return false; + } + + return CSettingCondition::Deserialize(node); +} + +std::set<std::string> CSettingDependency::GetSettings() const +{ + if (m_operation == nullptr) + return std::set<std::string>(); + + auto combination = static_cast<CSettingDependencyConditionCombination*>(m_operation.get()); + if (combination == nullptr) + return std::set<std::string>(); + + return combination->GetSettings(); +} + +CSettingDependencyConditionCombinationPtr CSettingDependency::And() +{ + if (m_operation == nullptr) + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); + + m_operation->SetOperation(BooleanLogicOperationAnd); + + return std::dynamic_pointer_cast<CSettingDependencyConditionCombination>(m_operation); +} + +CSettingDependencyConditionCombinationPtr CSettingDependency::Or() +{ + if (m_operation == nullptr) + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); + + m_operation->SetOperation(BooleanLogicOperationOr); + + return std::dynamic_pointer_cast<CSettingDependencyConditionCombination>(m_operation); +} + +bool CSettingDependency::setType(const std::string &type) +{ + if (StringUtils::EqualsNoCase(type, "enable")) + m_type = SettingDependencyType::Enable; + else if (StringUtils::EqualsNoCase(type, "update")) + m_type = SettingDependencyType::Update; + else if (StringUtils::EqualsNoCase(type, "visible")) + m_type = SettingDependencyType::Visible; + else + return false; + + return true; +} diff --git a/xbmc/settings/lib/SettingDependency.h b/xbmc/settings/lib/SettingDependency.h new file mode 100644 index 0000000..5cb34db --- /dev/null +++ b/xbmc/settings/lib/SettingDependency.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" +#include "utils/BooleanLogic.h" +#include "utils/logtypes.h" + +#include <list> +#include <set> +#include <string> + +enum class SettingDependencyType { + Unknown = 0, + Enable, + Update, + Visible +}; + +enum class SettingDependencyOperator { + Unknown = 0, + Equals, + LessThan, + GreaterThan, + Contains +}; + +enum class SettingDependencyTarget { + Unknown = 0, + Setting, + Property +}; + +class CSettingDependencyCondition : public CSettingConditionItem +{ +public: + explicit CSettingDependencyCondition(CSettingsManager *settingsManager = nullptr); + CSettingDependencyCondition(const std::string &setting, const std::string &value, + SettingDependencyOperator op, bool negated = false, + CSettingsManager *settingsManager = nullptr); + CSettingDependencyCondition(const std::string &strProperty, const std::string &value, + const std::string &setting = "", bool negated = false, + CSettingsManager *settingsManager = nullptr); + ~CSettingDependencyCondition() override = default; + + bool Deserialize(const TiXmlNode *node) override; + bool Check() const override; + + const std::string& GetName() const { return m_name; } + const std::string& GetSetting() const { return m_setting; } + SettingDependencyTarget GetTarget() const { return m_target; } + SettingDependencyOperator GetOperator() const { return m_operator; } + +private: + CSettingDependencyCondition(CSettingsManager* settingsManager, + const std::string& strProperty, + const std::string& setting, + const std::string& value, + SettingDependencyTarget target = SettingDependencyTarget::Unknown, + SettingDependencyOperator op = SettingDependencyOperator::Equals, + bool negated = false); + + bool setTarget(const std::string &target); + bool setOperator(const std::string &op); + + SettingDependencyTarget m_target = SettingDependencyTarget::Unknown; + SettingDependencyOperator m_operator = SettingDependencyOperator::Equals; + + static Logger s_logger; +}; + +using CSettingDependencyConditionPtr = std::shared_ptr<CSettingDependencyCondition>; + +class CSettingDependencyConditionCombination; +using CSettingDependencyConditionCombinationPtr = std::shared_ptr<CSettingDependencyConditionCombination>; + +class CSettingDependencyConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingDependencyConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + CSettingDependencyConditionCombination(BooleanLogicOperation op, CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { + SetOperation(op); + } + ~CSettingDependencyConditionCombination() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + const std::set<std::string>& GetSettings() const { return m_settings; } + + CSettingDependencyConditionCombination* Add(const CSettingDependencyConditionPtr& condition); + CSettingDependencyConditionCombination* Add( + const CSettingDependencyConditionCombinationPtr& operation); + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingDependencyConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingDependencyCondition(m_settingsManager); } + + std::set<std::string> m_settings; +}; + +class CSettingDependency : public CSettingCondition +{ +public: + explicit CSettingDependency(CSettingsManager *settingsManager = nullptr); + CSettingDependency(SettingDependencyType type, CSettingsManager *settingsManager = nullptr); + ~CSettingDependency() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + SettingDependencyType GetType() const { return m_type; } + std::set<std::string> GetSettings() const; + + CSettingDependencyConditionCombinationPtr And(); + CSettingDependencyConditionCombinationPtr Or(); + +private: + bool setType(const std::string &type); + + SettingDependencyType m_type = SettingDependencyType::Unknown; + + static Logger s_logger; +}; + +using SettingDependencies = std::list<CSettingDependency>; +using SettingDependencyMap = std::map<std::string, SettingDependencies>; diff --git a/xbmc/settings/lib/SettingLevel.h b/xbmc/settings/lib/SettingLevel.h new file mode 100644 index 0000000..db8e2b5 --- /dev/null +++ b/xbmc/settings/lib/SettingLevel.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Levels which every setting is assigned to. + */ +enum class SettingLevel { + Basic = 0, + Standard, + Advanced, + Expert, + Internal +}; diff --git a/xbmc/settings/lib/SettingRequirement.cpp b/xbmc/settings/lib/SettingRequirement.cpp new file mode 100644 index 0000000..5ba81ab --- /dev/null +++ b/xbmc/settings/lib/SettingRequirement.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingRequirement.h" + +#include "SettingsManager.h" + +bool CSettingRequirementCondition::Check() const +{ + if (m_settingsManager == nullptr) + return false; + + bool found = m_settingsManager->GetConditions().Check("IsDefined", m_value); + if (m_negated) + return !found; + + return found; +} + +bool CSettingRequirementConditionCombination::Check() const +{ + if (m_operations.empty() && m_values.empty()) + return true; + + return CSettingConditionCombination::Check(); +} + +CSettingRequirement::CSettingRequirement(CSettingsManager *settingsManager /* = nullptr */) + : CSettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingRequirementConditionCombination(m_settingsManager)); +} diff --git a/xbmc/settings/lib/SettingRequirement.h b/xbmc/settings/lib/SettingRequirement.h new file mode 100644 index 0000000..f8357a5 --- /dev/null +++ b/xbmc/settings/lib/SettingRequirement.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" + +#include <set> +#include <string> + +class CSettingRequirementCondition : public CSettingConditionItem +{ +public: + explicit CSettingRequirementCondition(CSettingsManager *settingsManager = nullptr) + : CSettingConditionItem(settingsManager) + { } + ~CSettingRequirementCondition() override = default; + + bool Check() const override; +}; + +class CSettingRequirementConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingRequirementConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + ~CSettingRequirementConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingRequirementConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingRequirementCondition(m_settingsManager); } +}; + +class CSettingRequirement : public CSettingCondition +{ +public: + explicit CSettingRequirement(CSettingsManager *settingsManager = nullptr); + ~CSettingRequirement() override = default; +}; diff --git a/xbmc/settings/lib/SettingSection.cpp b/xbmc/settings/lib/SettingSection.cpp new file mode 100644 index 0000000..4f1b564 --- /dev/null +++ b/xbmc/settings/lib/SettingSection.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingSection.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> + +template<class T> +void addISetting(const TiXmlNode* node, const T& item, std::vector<T>& items, bool toBegin = false) +{ + if (node != nullptr) + { + auto element = node->ToElement(); + if (element != nullptr) + { + // check if there is a "before" or "after" attribute to place the setting at a specific position + int position = -1; // -1 => end, 0 => before, 1 => after + auto positionId = element->Attribute(SETTING_XML_ATTR_BEFORE); + if (positionId != nullptr && strlen(positionId) > 0) + position = 0; + else if ((positionId = element->Attribute(SETTING_XML_ATTR_AFTER)) != nullptr && strlen(positionId) > 0) + position = 1; + + if (positionId != nullptr && strlen(positionId) > 0 && position >= 0) + { + for (typename std::vector<T>::iterator it = items.begin(); it != items.end(); ++it) + { + if (!StringUtils::EqualsNoCase((*it)->GetId(), positionId)) + continue; + + typename std::vector<T>::iterator positionIt = it; + if (position == 1) + ++positionIt; + + items.insert(positionIt, item); + return; + } + } + } + } + + if (!toBegin) + items.emplace_back(item); + else + items.insert(items.begin(), item); +} + +Logger CSettingGroup::s_logger; + +CSettingGroup::CSettingGroup(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingGroup"); +} + +bool CSettingGroup::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto controlElement = node->FirstChildElement(SETTING_XML_ELM_CONTROL); + if (controlElement != nullptr) + { + auto controlType = controlElement->Attribute(SETTING_XML_ATTR_TYPE); + if (controlType == nullptr || strlen(controlType) <= 0) + { + s_logger->error("unable to read control type"); + return false; + } + + m_control = m_settingsManager->CreateControl(controlType); + if (m_control == nullptr) + { + s_logger->error("unable to create new control \"{}\"", controlType); + return false; + } + + if (!m_control->Deserialize(controlElement)) + { + s_logger->warn("unable to read control \"{}\"", controlType); + m_control.reset(); + } + } + + auto settingElement = node->FirstChildElement(SETTING_XML_ELM_SETTING); + while (settingElement != nullptr) + { + std::string settingId; + bool isReference; + if (CSetting::DeserializeIdentification(settingElement, settingId, isReference)) + { + auto settingIt = std::find_if(m_settings.begin(), m_settings.end(), + [&settingId](const SettingPtr& setting) + { + return setting->GetId() == settingId; + }); + + SettingPtr setting; + if (settingIt != m_settings.end()) + setting = *settingIt; + + update = (setting != nullptr); + if (!update) + { + auto settingType = settingElement->Attribute(SETTING_XML_ATTR_TYPE); + if (settingType == nullptr || strlen(settingType) <= 0) + { + s_logger->error("unable to read setting type of \"{}\"", settingId); + return false; + } + + setting = m_settingsManager->CreateSetting(settingType, settingId, m_settingsManager); + if (setting == nullptr) + s_logger->error("unknown setting type \"{}\" of \"{}\"", settingType, settingId); + } + + if (setting == nullptr) + s_logger->error("unable to create new setting \"{}\"", settingId); + else + { + if (!setting->Deserialize(settingElement, update)) + s_logger->warn("unable to read setting \"{}\"", settingId); + else + { + // if the setting is a reference turn it into one + if (isReference) + setting->MakeReference(); + + if (!update) + addISetting(settingElement, setting, m_settings); + } + } + } + + settingElement = settingElement->NextSiblingElement(SETTING_XML_ELM_SETTING); + } + + return true; +} + +SettingList CSettingGroup::GetSettings(SettingLevel level) const +{ + SettingList settings; + for (const auto& setting : m_settings) + { + if (setting->GetLevel() <= level && setting->MeetsRequirements()) + settings.push_back(setting); + } + + return settings; +} + +void CSettingGroup::AddSetting(const SettingPtr& setting) +{ + addISetting(nullptr, setting, m_settings); +} + +void CSettingGroup::AddSettings(const SettingList &settings) +{ + for (const auto& setting : settings) + addISetting(nullptr, setting, m_settings); +} + +bool CSettingGroup::ReplaceSetting(const std::shared_ptr<const CSetting>& currentSetting, + const std::shared_ptr<CSetting>& newSetting) +{ + for (auto itSetting = m_settings.begin(); itSetting != m_settings.end(); ++itSetting) + { + if (*itSetting == currentSetting) + { + if (newSetting == nullptr) + m_settings.erase(itSetting); + else + *itSetting = newSetting; + + return true; + } + } + + return false; +} + +Logger CSettingCategory::s_logger; + +CSettingCategory::CSettingCategory(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager), + m_accessCondition(settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingCategory"); +} + +bool CSettingCategory::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto accessNode = node->FirstChild(SETTING_XML_ELM_ACCESS); + if (accessNode != nullptr && !m_accessCondition.Deserialize(accessNode)) + return false; + + auto groupNode = node->FirstChild(SETTING_XML_ELM_GROUP); + while (groupNode != nullptr) + { + std::string groupId; + if (CSettingGroup::DeserializeIdentification(groupNode, groupId)) + { + auto groupIt = std::find_if(m_groups.begin(), m_groups.end(), + [&groupId](const SettingGroupPtr& group) + { + return group->GetId() == groupId; + }); + + SettingGroupPtr group; + if (groupIt != m_groups.end()) + group = *groupIt; + + update = (group != nullptr); + if (!update) + group = std::make_shared<CSettingGroup>(groupId, m_settingsManager); + + if (group->Deserialize(groupNode, update)) + { + if (!update) + addISetting(groupNode, group, m_groups); + } + else + s_logger->warn("unable to read group \"{}\"", groupId); + } + + groupNode = groupNode->NextSibling(SETTING_XML_ELM_GROUP); + } + + return true; +} + +SettingGroupList CSettingCategory::GetGroups(SettingLevel level) const +{ + SettingGroupList groups; + for (const auto& group : m_groups) + { + if (group->MeetsRequirements() && group->IsVisible() && group->GetSettings(level).size() > 0) + groups.push_back(group); + } + + return groups; +} + +bool CSettingCategory::CanAccess() const +{ + return m_accessCondition.Check(); +} + +void CSettingCategory::AddGroup(const SettingGroupPtr& group) +{ + addISetting(nullptr, group, m_groups, false); +} + +void CSettingCategory::AddGroupToFront(const SettingGroupPtr& group) +{ + addISetting(nullptr, group, m_groups, true); +} + +void CSettingCategory::AddGroups(const SettingGroupList &groups) +{ + for (const auto& group : groups) + addISetting(nullptr, group, m_groups); +} + +Logger CSettingSection::s_logger; + +CSettingSection::CSettingSection(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingSection"); +} + +bool CSettingSection::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto categoryNode = node->FirstChild(SETTING_XML_ELM_CATEGORY); + while (categoryNode != nullptr) + { + std::string categoryId; + if (CSettingCategory::DeserializeIdentification(categoryNode, categoryId)) + { + auto categoryIt = std::find_if(m_categories.begin(), m_categories.end(), + [&categoryId](const SettingCategoryPtr& category) + { + return category->GetId() == categoryId; + }); + + SettingCategoryPtr category; + if (categoryIt != m_categories.end()) + category = *categoryIt; + + update = (category != nullptr); + if (!update) + category = std::make_shared<CSettingCategory>(categoryId, m_settingsManager); + + if (category->Deserialize(categoryNode, update)) + { + if (!update) + addISetting(categoryNode, category, m_categories); + } + else + s_logger->warn("unable to read category \"{}\"", categoryId); + } + + categoryNode = categoryNode->NextSibling(SETTING_XML_ELM_CATEGORY); + } + + return true; +} + +SettingCategoryList CSettingSection::GetCategories(SettingLevel level) const +{ + SettingCategoryList categories; + for (const auto& category : m_categories) + { + if (category->MeetsRequirements() && category->IsVisible() && category->GetGroups(level).size() > 0) + categories.push_back(category); + } + + return categories; +} + +void CSettingSection::AddCategory(const SettingCategoryPtr& category) +{ + addISetting(nullptr, category, m_categories); +} + +void CSettingSection::AddCategories(const SettingCategoryList &categories) +{ + for (const auto& category : categories) + addISetting(nullptr, category, m_categories); +} diff --git a/xbmc/settings/lib/SettingSection.h b/xbmc/settings/lib/SettingSection.h new file mode 100644 index 0000000..f8e06d6 --- /dev/null +++ b/xbmc/settings/lib/SettingSection.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISetting.h" +#include "Setting.h" +#include "SettingCategoryAccess.h" +#include "utils/logtypes.h" + +#include <string> +#include <utility> +#include <vector> + +class CSettingsManager; + +/*! + \ingroup settings + \brief Group of settings being part of a category + \sa CSettingCategory + \sa CSetting + */ +class CSettingGroup : public ISetting +{ +public: + /*! + \brief Creates a new setting group with the given identifier. + + \param id Identifier of the setting group + \param settingsManager Reference to the settings manager + */ + CSettingGroup(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingGroup() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of settings belonging to the setting group. + + \return Full list of settings belonging to the setting group + */ + const SettingList& GetSettings() const { return m_settings; } + /*! + \brief Gets the list of settings assigned to the given setting level (or + below) and that meet the requirements conditions belonging to the setting + group. + + \param level Level the settings should be assigned to + \return List of settings belonging to the setting group + */ + SettingList GetSettings(SettingLevel level) const; + + void AddSetting(const std::shared_ptr<CSetting>& setting); + void AddSettings(const SettingList &settings); + + bool ReplaceSetting(const std::shared_ptr<const CSetting>& currentSetting, + const std::shared_ptr<CSetting>& newSetting); + + std::shared_ptr<const ISettingControl> GetControl() const { return m_control; } + std::shared_ptr<ISettingControl> GetControl() { return m_control; } + void SetControl(std::shared_ptr<ISettingControl> control) { m_control = std::move(control); } + +private: + SettingList m_settings; + std::shared_ptr<ISettingControl> m_control; + + static Logger s_logger; +}; + +using SettingGroupPtr = std::shared_ptr<CSettingGroup>; +using SettingGroupList = std::vector<SettingGroupPtr>; + +/*! + \ingroup settings + \brief Category of groups of settings being part of a section + \sa CSettingSection + \sa CSettingGroup + */ +class CSettingCategory : public ISetting +{ +public: + /*! + \brief Creates a new setting category with the given identifier. + + \param id Identifier of the setting category + \param settingsManager Reference to the settings manager + */ + CSettingCategory(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingCategory() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of setting groups belonging to the setting + category. + + \return Full list of setting groups belonging to the setting category + */ + const SettingGroupList& GetGroups() const { return m_groups; } + /*! + \brief Gets the list of setting groups belonging to the setting category + that contain settings assigned to the given setting level (or below) and + that meet the requirements and visibility conditions. + + \param level Level the settings should be assigned to + \return List of setting groups belonging to the setting category + */ + SettingGroupList GetGroups(SettingLevel level) const; + + /*! + \brief Whether the setting category can be accessed or not. + + \return True if the setting category can be accessed, false otherwise + */ + bool CanAccess() const; + + void AddGroup(const SettingGroupPtr& group); + void AddGroupToFront(const SettingGroupPtr& group); + void AddGroups(const SettingGroupList &groups); + +private: + SettingGroupList m_groups; + CSettingCategoryAccess m_accessCondition; + + static Logger s_logger; +}; + +using SettingCategoryPtr = std::shared_ptr<CSettingCategory>; +using SettingCategoryList = std::vector<SettingCategoryPtr>; + +/*! + \ingroup settings + \brief Section of setting categories + \sa CSettings + \sa CSettingCategory + */ +class CSettingSection : public ISetting +{ +public: + /*! + \brief Creates a new setting section with the given identifier. + + \param id Identifier of the setting section + \param settingsManager Reference to the settings manager + */ + CSettingSection(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingSection() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of setting categories belonging to the setting + section. + + \return Full list of setting categories belonging to the setting section + */ + const SettingCategoryList& GetCategories() const { return m_categories; } + /*! + \brief Gets the list of setting categories belonging to the setting section + that contain settings assigned to the given setting level (or below) and + that meet the requirements and visibility conditions. + + \param level Level the settings should be assigned to + \return List of setting categories belonging to the setting section + */ + SettingCategoryList GetCategories(SettingLevel level) const; + + void AddCategory(const SettingCategoryPtr& category); + void AddCategories(const SettingCategoryList &categories); + +private: + SettingCategoryList m_categories; + + static Logger s_logger; +}; + +using SettingSectionPtr = std::shared_ptr<CSettingSection>; +using SettingSectionList = std::vector<SettingSectionPtr>; diff --git a/xbmc/settings/lib/SettingType.h b/xbmc/settings/lib/SettingType.h new file mode 100644 index 0000000..56838ab --- /dev/null +++ b/xbmc/settings/lib/SettingType.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Basic setting types available in the settings system. + */ +enum class SettingType { + Unknown = 0, + Boolean, + Integer, + Number, + String, + Action, + List +}; diff --git a/xbmc/settings/lib/SettingUpdate.cpp b/xbmc/settings/lib/SettingUpdate.cpp new file mode 100644 index 0000000..0201aab --- /dev/null +++ b/xbmc/settings/lib/SettingUpdate.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingUpdate.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +Logger CSettingUpdate::s_logger; + +CSettingUpdate::CSettingUpdate() +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingUpdate"); +} + +bool CSettingUpdate::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strType = elem->Attribute(SETTING_XML_ATTR_TYPE); + if (strType == nullptr || strlen(strType) <= 0 || !setType(strType)) + { + s_logger->warn("missing or unknown update type definition"); + return false; + } + + if (m_type == SettingUpdateType::Rename) + { + if (node->FirstChild() == nullptr || node->FirstChild()->Type() != TiXmlNode::TINYXML_TEXT) + { + s_logger->warn("missing or invalid setting id for rename update definition"); + return false; + } + + m_value = node->FirstChild()->ValueStr(); + } + + return true; +} + +bool CSettingUpdate::setType(const std::string &type) +{ + if (StringUtils::EqualsNoCase(type, "change")) + m_type = SettingUpdateType::Change; + else if (StringUtils::EqualsNoCase(type, "rename")) + m_type = SettingUpdateType::Rename; + else + return false; + + return true; +} diff --git a/xbmc/settings/lib/SettingUpdate.h b/xbmc/settings/lib/SettingUpdate.h new file mode 100644 index 0000000..65c3e86 --- /dev/null +++ b/xbmc/settings/lib/SettingUpdate.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/logtypes.h" + +#include <string> + +class TiXmlNode; + +enum class SettingUpdateType { + Unknown = 0, + Rename, + Change +}; + +class CSettingUpdate +{ +public: + CSettingUpdate(); + virtual ~CSettingUpdate() = default; + + inline bool operator<(const CSettingUpdate& rhs) const + { + return m_type < rhs.m_type && m_value < rhs.m_value; + } + + virtual bool Deserialize(const TiXmlNode *node); + + SettingUpdateType GetType() const { return m_type; } + const std::string& GetValue() const { return m_value; } + +private: + bool setType(const std::string &type); + + SettingUpdateType m_type = SettingUpdateType::Unknown; + std::string m_value; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/SettingsManager.cpp b/xbmc/settings/lib/SettingsManager.cpp new file mode 100644 index 0000000..9ff7d61 --- /dev/null +++ b/xbmc/settings/lib/SettingsManager.cpp @@ -0,0 +1,1424 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsManager.h" + +#include "ServiceBroker.h" +#include "Setting.h" +#include "SettingDefinitions.h" +#include "SettingSection.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> +#include <map> +#include <mutex> +#include <shared_mutex> +#include <unordered_set> +#include <utility> + +const uint32_t CSettingsManager::Version = 2; +const uint32_t CSettingsManager::MinimumSupportedVersion = 0; + +bool ParseSettingIdentifier(const std::string& settingId, std::string& categoryTag, std::string& settingTag) +{ + static const std::string Separator = "."; + + if (settingId.empty()) + return false; + + auto parts = StringUtils::Split(settingId, Separator); + if (parts.size() < 1 || parts.at(0).empty()) + return false; + + if (parts.size() == 1) + { + settingTag = parts.at(0); + return true; + } + + // get the category tag and remove it from the parts + categoryTag = parts.at(0); + parts.erase(parts.begin()); + + // put together the setting tag + settingTag = StringUtils::Join(parts, Separator); + + return true; +} + +CSettingsManager::CSettingsManager() + : m_logger(CServiceBroker::GetLogging().GetLogger("CSettingsManager")) +{ +} + +CSettingsManager::~CSettingsManager() +{ + // first clear all registered settings handler and subsettings + // implementations because we can't be sure that they are still valid + m_settingsHandlers.clear(); + m_settingCreators.clear(); + m_settingControlCreators.clear(); + + Clear(); +} + +uint32_t CSettingsManager::ParseVersion(const TiXmlElement* root) const +{ + // try to get and check the version + uint32_t version = 0; + root->QueryUnsignedAttribute(SETTING_XML_ROOT_VERSION, &version); + + return version; +} + +bool CSettingsManager::Initialize(const TiXmlElement *root) +{ + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + if (m_initialized || root == nullptr) + return false; + + if (!StringUtils::EqualsNoCase(root->ValueStr(), SETTING_XML_ROOT)) + { + m_logger->error("error reading settings definition: doesn't contain <" SETTING_XML_ROOT + "> tag"); + return false; + } + + // try to get and check the version + uint32_t version = ParseVersion(root); + if (version == 0) + m_logger->warn("missing " SETTING_XML_ROOT_VERSION " attribute", SETTING_XML_ROOT_VERSION); + + if (MinimumSupportedVersion >= version+1) + { + m_logger->error("unable to read setting definitions from version {} (minimum version: {})", + version, MinimumSupportedVersion); + return false; + } + if (version > Version) + { + m_logger->error("unable to read setting definitions from version {} (current version: {})", + version, Version); + return false; + } + + auto sectionNode = root->FirstChild(SETTING_XML_ELM_SECTION); + while (sectionNode != nullptr) + { + std::string sectionId; + if (CSettingSection::DeserializeIdentification(sectionNode, sectionId)) + { + SettingSectionPtr section = nullptr; + auto itSection = m_sections.find(sectionId); + bool update = (itSection != m_sections.end()); + if (!update) + section = std::make_shared<CSettingSection>(sectionId, this); + else + section = itSection->second; + + if (section->Deserialize(sectionNode, update)) + AddSection(section); + else + { + m_logger->warn("unable to read section \"{}\"", sectionId); + } + } + + sectionNode = sectionNode->NextSibling(SETTING_XML_ELM_SECTION); + } + + return true; +} + +bool CSettingsManager::Load(const TiXmlElement *root, bool &updated, bool triggerEvents /* = true */, std::map<std::string, SettingPtr> *loadedSettings /* = nullptr */) +{ + std::shared_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + if (m_loaded || root == nullptr) + return false; + + if (triggerEvents && !OnSettingsLoading()) + return false; + + // try to get and check the version + uint32_t version = ParseVersion(root); + if (version == 0) + m_logger->warn("missing {} attribute", SETTING_XML_ROOT_VERSION); + + if (MinimumSupportedVersion >= version+1) + { + m_logger->error("unable to read setting values from version {} (minimum version: {})", version, + MinimumSupportedVersion); + return false; + } + if (version > Version) + { + m_logger->error("unable to read setting values from version {} (current version: {})", version, + Version); + return false; + } + + if (!Deserialize(root, updated, loadedSettings)) + return false; + + if (triggerEvents) + OnSettingsLoaded(); + + return true; +} + +bool CSettingsManager::Save( + const ISettingsValueSerializer* serializer, std::string& serializedValues) const +{ + if (serializer == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_critical); + std::shared_lock<CSharedSection> settingsLock(m_settingsCritical); + if (!m_initialized) + return false; + + if (!OnSettingsSaving()) + return false; + + serializedValues = serializer->SerializeValues(this); + + OnSettingsSaved(); + + return true; +} + +void CSettingsManager::Unload() +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded) + return; + + // needs to be set before calling CSetting::Reset() to avoid calls to + // OnSettingChanging() and OnSettingChanged() + m_loaded = false; + + for (auto& setting : m_settings) + setting.second.setting->Reset(); + + OnSettingsUnloaded(); +} + +void CSettingsManager::Clear() +{ + std::unique_lock<CSharedSection> lock(m_critical); + Unload(); + + m_settings.clear(); + m_sections.clear(); + + OnSettingsCleared(); + + m_initialized = false; +} + +bool CSettingsManager::LoadSetting(const TiXmlNode *node, const std::string &settingId) +{ + bool updated = false; + return LoadSetting(node, settingId, updated); +} + +bool CSettingsManager::LoadSetting(const TiXmlNode *node, const std::string &settingId, bool &updated) +{ + updated = false; + + if (node == nullptr) + return false; + + auto setting = GetSetting(settingId); + if (setting == nullptr) + return false; + + return LoadSetting(node, setting, updated); +} + +void CSettingsManager::SetInitialized() +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (m_initialized) + return; + + m_initialized = true; + + // resolve any reference settings + for (const auto& section : m_sections) + ResolveReferenceSettings(section.second); + + // remove any incomplete settings + CleanupIncompleteSettings(); + + // figure out all the dependencies between settings + for (const auto& setting : m_settings) + ResolveSettingDependencies(setting.second); +} + +void CSettingsManager::AddSection(const SettingSectionPtr& section) +{ + if (section == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + + section->CheckRequirements(); + m_sections[section->GetId()] = section; + + // get all settings and add them to the settings map + std::set<SettingPtr> newSettings; + for (const auto& category : section->GetCategories()) + { + category->CheckRequirements(); + for (auto& group : category->GetGroups()) + { + group->CheckRequirements(); + for (const auto& setting : group->GetSettings()) + { + AddSetting(setting); + + newSettings.insert(setting); + } + } + } + + if (m_initialized && !newSettings.empty()) + { + // resolve any reference settings in the new section + ResolveReferenceSettings(section); + + // cleanup any newly added incomplete settings + CleanupIncompleteSettings(); + + // resolve any dependencies for the newly added settings + for (const auto& setting : newSettings) + ResolveSettingDependencies(setting); + } +} + +bool CSettingsManager::AddSetting(const std::shared_ptr<CSetting>& setting, + const std::shared_ptr<CSettingSection>& section, + const std::shared_ptr<CSettingCategory>& category, + const std::shared_ptr<CSettingGroup>& group) +{ + if (setting == nullptr || section == nullptr || category == nullptr || group == nullptr) + return false; + + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + + // check if a setting with the given ID already exists + if (FindSetting(setting->GetId()) != m_settings.end()) + return false; + + // if the given setting has not been added to the group yet, do it now + auto settings = group->GetSettings(); + if (std::find(settings.begin(), settings.end(), setting) == settings.end()) + group->AddSetting(setting); + + // if the given group has not been added to the category yet, do it now + auto groups = category->GetGroups(); + if (std::find(groups.begin(), groups.end(), group) == groups.end()) + category->AddGroup(group); + + // if the given category has not been added to the section yet, do it now + auto categories = section->GetCategories(); + if (std::find(categories.begin(), categories.end(), category) == categories.end()) + section->AddCategory(category); + + // check if the given section exists and matches + auto sectionPtr = GetSection(section->GetId()); + if (sectionPtr != nullptr && sectionPtr != section) + return false; + + // if the section doesn't exist yet, add it + if (sectionPtr == nullptr) + AddSection(section); + else + { + // add the setting + AddSetting(setting); + + if (m_initialized) + { + // cleanup any newly added incomplete setting + CleanupIncompleteSettings(); + + // resolve any dependencies for the newly added setting + ResolveSettingDependencies(setting); + } + } + + return true; +} + +void CSettingsManager::RegisterCallback(ISettingCallback *callback, const std::set<std::string> &settingList) +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (callback == nullptr) + return; + + for (const auto& setting : settingList) + { + auto itSetting = FindSetting(setting); + if (itSetting == m_settings.end()) + { + if (m_initialized) + continue; + + Setting tmpSetting = {}; + std::pair<SettingMap::iterator, bool> tmpIt = InsertSetting(setting, tmpSetting); + itSetting = tmpIt.first; + } + + itSetting->second.callbacks.insert(callback); + } +} + +void CSettingsManager::UnregisterCallback(ISettingCallback *callback) +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + for (auto& setting : m_settings) + setting.second.callbacks.erase(callback); +} + +void CSettingsManager::RegisterSettingType(const std::string &settingType, ISettingCreator *settingCreator) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (settingType.empty() || settingCreator == nullptr) + return; + + auto creatorIt = m_settingCreators.find(settingType); + if (creatorIt == m_settingCreators.end()) + m_settingCreators.insert(std::make_pair(settingType, settingCreator)); +} + +void CSettingsManager::RegisterSettingControl(const std::string &controlType, ISettingControlCreator *settingControlCreator) +{ + if (controlType.empty() || settingControlCreator == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + auto creatorIt = m_settingControlCreators.find(controlType); + if (creatorIt == m_settingControlCreators.end()) + m_settingControlCreators.insert(std::make_pair(controlType, settingControlCreator)); +} + +void CSettingsManager::RegisterSettingsHandler(ISettingsHandler *settingsHandler, bool bFront /* = false */) +{ + if (settingsHandler == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + if (find(m_settingsHandlers.begin(), m_settingsHandlers.end(), settingsHandler) == m_settingsHandlers.end()) + { + if (bFront) + m_settingsHandlers.insert(m_settingsHandlers.begin(), settingsHandler); + else + m_settingsHandlers.emplace_back(settingsHandler); + } +} + +void CSettingsManager::UnregisterSettingsHandler(ISettingsHandler *settingsHandler) +{ + if (settingsHandler == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + auto it = std::find(m_settingsHandlers.begin(), m_settingsHandlers.end(), settingsHandler); + if (it != m_settingsHandlers.end()) + m_settingsHandlers.erase(it); +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, IntegerSettingOptionsFiller optionsFiller) +{ + if (identifier.empty() || optionsFiller == nullptr) + return; + + RegisterSettingOptionsFiller(identifier, reinterpret_cast<void*>(optionsFiller), SettingOptionsFillerType::Integer); +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, StringSettingOptionsFiller optionsFiller) +{ + if (identifier.empty() || optionsFiller == nullptr) + return; + + RegisterSettingOptionsFiller(identifier, reinterpret_cast<void*>(optionsFiller), SettingOptionsFillerType::String); +} + +void CSettingsManager::UnregisterSettingOptionsFiller(const std::string &identifier) +{ + std::unique_lock<CSharedSection> lock(m_critical); + m_optionsFillers.erase(identifier); +} + +void* CSettingsManager::GetSettingOptionsFiller(const SettingConstPtr& setting) +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (setting == nullptr) + return nullptr; + + // get the option filler's identifier + std::string filler; + if (setting->GetType() == SettingType::Integer) + filler = std::static_pointer_cast<const CSettingInt>(setting)->GetOptionsFillerName(); + else if (setting->GetType() == SettingType::String) + filler = std::static_pointer_cast<const CSettingString>(setting)->GetOptionsFillerName(); + + if (filler.empty()) + return nullptr; + + // check if such an option filler is known + auto fillerIt = m_optionsFillers.find(filler); + if (fillerIt == m_optionsFillers.end()) + return nullptr; + + if (fillerIt->second.filler == nullptr) + return nullptr; + + // make sure the option filler's type matches the setting's type + switch (fillerIt->second.type) + { + case SettingOptionsFillerType::Integer: + { + if (setting->GetType() != SettingType::Integer) + return nullptr; + + break; + } + + case SettingOptionsFillerType::String: + { + if (setting->GetType() != SettingType::String) + return nullptr; + + break; + } + + default: + return nullptr; + } + + return fillerIt->second.filler; +} + +bool CSettingsManager::HasSettings() const +{ + return !m_settings.empty(); +} + +SettingPtr CSettingsManager::GetSetting(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (id.empty()) + return nullptr; + + auto setting = FindSetting(id); + if (setting != m_settings.end()) + { + if (setting->second.setting->IsReference()) + return GetSetting(setting->second.setting->GetReferencedId()); + return setting->second.setting; + } + + m_logger->debug("requested setting ({}) was not found.", id); + return nullptr; +} + +SettingSectionList CSettingsManager::GetSections() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + SettingSectionList sections; + for (const auto& section : m_sections) + sections.push_back(section.second); + + return sections; +} + +SettingSectionPtr CSettingsManager::GetSection(std::string section) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (section.empty()) + return nullptr; + + StringUtils::ToLower(section); + + auto sectionIt = m_sections.find(section); + if (sectionIt != m_sections.end()) + return sectionIt->second; + + m_logger->debug("requested setting section ({}) was not found.", section); + return nullptr; +} + +SettingDependencyMap CSettingsManager::GetDependencies(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + auto setting = FindSetting(id); + if (setting == m_settings.end()) + return SettingDependencyMap(); + + return setting->second.dependencies; +} + +SettingDependencyMap CSettingsManager::GetDependencies(const SettingConstPtr& setting) const +{ + if (setting == nullptr) + return SettingDependencyMap(); + + return GetDependencies(setting->GetId()); +} + +bool CSettingsManager::GetBool(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return std::static_pointer_cast<CSettingBool>(setting)->GetValue(); +} + +bool CSettingsManager::SetBool(const std::string &id, bool value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return std::static_pointer_cast<CSettingBool>(setting)->SetValue(value); +} + +bool CSettingsManager::ToggleBool(const std::string &id) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return SetBool(id, !std::static_pointer_cast<CSettingBool>(setting)->GetValue()); +} + +int CSettingsManager::GetInt(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Integer) + return 0; + + return std::static_pointer_cast<CSettingInt>(setting)->GetValue(); +} + +bool CSettingsManager::SetInt(const std::string &id, int value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Integer) + return false; + + return std::static_pointer_cast<CSettingInt>(setting)->SetValue(value); +} + +double CSettingsManager::GetNumber(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Number) + return 0.0; + + return std::static_pointer_cast<CSettingNumber>(setting)->GetValue(); +} + +bool CSettingsManager::SetNumber(const std::string &id, double value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Number) + return false; + + return std::static_pointer_cast<CSettingNumber>(setting)->SetValue(value); +} + +std::string CSettingsManager::GetString(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::String) + return ""; + + return std::static_pointer_cast<CSettingString>(setting)->GetValue(); +} + +bool CSettingsManager::SetString(const std::string &id, const std::string &value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::String) + return false; + + return std::static_pointer_cast<CSettingString>(setting)->SetValue(value); +} + +std::vector< std::shared_ptr<CSetting> > CSettingsManager::GetList(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return std::vector< std::shared_ptr<CSetting> >(); + + return std::static_pointer_cast<CSettingList>(setting)->GetValue(); +} + +bool CSettingsManager::SetList(const std::string &id, const std::vector< std::shared_ptr<CSetting> > &value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return false; + + return std::static_pointer_cast<CSettingList>(setting)->SetValue(value); +} + +bool CSettingsManager::SetDefault(const std::string &id) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr) + return false; + + setting->Reset(); + return true; +} + +void CSettingsManager::SetDefaults() +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + for (auto& setting : m_settings) + setting.second.setting->Reset(); +} + +void CSettingsManager::AddCondition(const std::string &condition) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (condition.empty()) + return; + + m_conditions.AddCondition(condition); +} + +void CSettingsManager::AddDynamicCondition(const std::string &identifier, SettingConditionCheck condition, void *data /*= nullptr*/) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (identifier.empty() || condition == nullptr) + return; + + m_conditions.AddDynamicCondition(identifier, condition, data); +} + +void CSettingsManager::RemoveDynamicCondition(const std::string &identifier) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (identifier.empty()) + return; + + m_conditions.RemoveDynamicCondition(identifier); +} + +bool CSettingsManager::Serialize(TiXmlNode *parent) const +{ + if (parent == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + + for (const auto& setting : m_settings) + { + if (setting.second.setting->IsReference() || + setting.second.setting->GetType() == SettingType::Action) + continue; + + TiXmlElement settingElement(SETTING_XML_ELM_SETTING); + settingElement.SetAttribute(SETTING_XML_ATTR_ID, setting.second.setting->GetId()); + + // add the default attribute + if (setting.second.setting->IsDefault()) + settingElement.SetAttribute(SETTING_XML_ELM_DEFAULT, "true"); + + // add the value + TiXmlText value(setting.second.setting->ToString()); + settingElement.InsertEndChild(value); + + if (parent->InsertEndChild(settingElement) == nullptr) + { + m_logger->warn("unable to write <" SETTING_XML_ELM_SETTING " id=\"{}\"> tag", + setting.second.setting->GetId()); + continue; + } + } + + return true; +} + +bool CSettingsManager::Deserialize(const TiXmlNode *node, bool &updated, std::map<std::string, SettingPtr> *loadedSettings /* = nullptr */) +{ + updated = false; + + if (node == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + + // TODO: ideally this would be done by going through all <setting> elements + // in node but as long as we have to support the v1- format that's not possible + for (auto& setting : m_settings) + { + bool settingUpdated = false; + if (LoadSetting(node, setting.second.setting, settingUpdated)) + { + updated |= settingUpdated; + if (loadedSettings != nullptr) + loadedSettings->insert(make_pair(setting.first, setting.second.setting)); + } + } + + return true; +} + +bool CSettingsManager::OnSettingChanging(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded) + return true; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return false; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + { + if (!callback->OnSettingChanging(setting)) + return false; + } + + // if this is a reference setting apply the same change to the referenced setting + if (setting->IsReference()) + { + std::shared_lock<CSharedSection> lock(m_settingsCritical); + auto referencedSettingIt = FindSetting(setting->GetReferencedId()); + if (referencedSettingIt != m_settings.end()) + { + Setting referencedSettingData = referencedSettingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + referencedSettingData.setting->FromString(setting->ToString()); + } + } + else if (!settingData.references.empty()) + { + // if the changed setting is referenced by other settings apply the same change to the referencing settings + std::unordered_set<SettingPtr> referenceSettings; + std::shared_lock<CSharedSection> lock(m_settingsCritical); + for (const auto& reference : settingData.references) + { + auto referenceSettingIt = FindSetting(reference); + if (referenceSettingIt != m_settings.end()) + referenceSettings.insert(referenceSettingIt->second.setting); + } + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& referenceSetting : referenceSettings) + referenceSetting->FromString(setting->ToString()); + } + + return true; +} + +void CSettingsManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingChanged(setting); + + // now handle any settings which depend on the changed setting + auto dependencies = GetDependencies(setting); + for (const auto& deps : dependencies) + { + for (const auto& dep : deps.second) + UpdateSettingByDependency(deps.first, dep); + } +} + +void CSettingsManager::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingAction(setting); +} + +bool CSettingsManager::OnSettingUpdate(const SettingPtr& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (setting == nullptr) + return false; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return false; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + bool ret = false; + for (auto& callback : settingData.callbacks) + ret |= callback->OnSettingUpdate(setting, oldSettingId, oldSettingNode); + + return ret; +} + +void CSettingsManager::OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingPropertyChanged(setting, propertyName); + + // check the changed property and if it may have an influence on the + // children of the setting + SettingDependencyType dependencyType = SettingDependencyType::Unknown; + if (StringUtils::EqualsNoCase(propertyName, "enabled")) + dependencyType = SettingDependencyType::Enable; + else if (StringUtils::EqualsNoCase(propertyName, "visible")) + dependencyType = SettingDependencyType::Visible; + + if (dependencyType != SettingDependencyType::Unknown) + { + for (const auto& child : settingIt->second.children) + UpdateSettingByDependency(child, dependencyType); + } +} + +SettingPtr CSettingsManager::CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager /* = nullptr */) const +{ + if (StringUtils::EqualsNoCase(settingType, "boolean")) + return std::make_shared<CSettingBool>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "integer")) + return std::make_shared<CSettingInt>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "number")) + return std::make_shared<CSettingNumber>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "string")) + return std::make_shared<CSettingString>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "action")) + return std::make_shared<CSettingAction>(settingId, const_cast<CSettingsManager*>(this)); + else if (settingType.size() > 6 && + StringUtils::StartsWith(settingType, "list[") && + StringUtils::EndsWith(settingType, "]")) + { + std::string elementType = StringUtils::Mid(settingType, 5, settingType.size() - 6); + SettingPtr elementSetting = CreateSetting(elementType, settingId + ".definition", const_cast<CSettingsManager*>(this)); + if (elementSetting != nullptr) + return std::make_shared<CSettingList>(settingId, elementSetting, const_cast<CSettingsManager*>(this)); + } + + std::shared_lock<CSharedSection> lock(m_critical); + auto creator = m_settingCreators.find(settingType); + if (creator != m_settingCreators.end()) + return creator->second->CreateSetting(settingType, settingId, const_cast<CSettingsManager*>(this)); + + return nullptr; +} + +std::shared_ptr<ISettingControl> CSettingsManager::CreateControl(const std::string &controlType) const +{ + if (controlType.empty()) + return nullptr; + + std::shared_lock<CSharedSection> lock(m_critical); + auto creator = m_settingControlCreators.find(controlType); + if (creator != m_settingControlCreators.end() && creator->second != nullptr) + return creator->second->CreateControl(controlType); + + return nullptr; +} + +bool CSettingsManager::OnSettingsLoading() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + { + if (!settingsHandler->OnSettingsLoading()) + return false; + } + + return true; +} + +void CSettingsManager::OnSettingsUnloaded() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsUnloaded(); +} + +void CSettingsManager::OnSettingsLoaded() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsLoaded(); +} + +bool CSettingsManager::OnSettingsSaving() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + { + if (!settingsHandler->OnSettingsSaving()) + return false; + } + + return true; +} + +void CSettingsManager::OnSettingsSaved() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsSaved(); +} + +void CSettingsManager::OnSettingsCleared() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsCleared(); +} + +bool CSettingsManager::LoadSetting(const TiXmlNode* node, const SettingPtr& setting, bool& updated) +{ + updated = false; + + if (node == nullptr || setting == nullptr) + return false; + + if (setting->GetType() == SettingType::Action) + return false; + + auto settingId = setting->GetId(); + if (setting->IsReference()) + settingId = setting->GetReferencedId(); + + const TiXmlElement* settingElement = nullptr; + // try to split the setting identifier into category and subsetting identifier (v1-) + std::string categoryTag, settingTag; + if (ParseSettingIdentifier(settingId, categoryTag, settingTag)) + { + auto categoryNode = node; + if (!categoryTag.empty()) + categoryNode = node->FirstChild(categoryTag); + + if (categoryNode != nullptr) + settingElement = categoryNode->FirstChildElement(settingTag); + } + + if (settingElement == nullptr) + { + // check if the setting is stored using its full setting identifier (v2+) + settingElement = node->FirstChildElement(SETTING_XML_ELM_SETTING); + while (settingElement != nullptr) + { + const auto id = settingElement->Attribute(SETTING_XML_ATTR_ID); + if (id != nullptr && settingId.compare(id) == 0) + break; + + settingElement = settingElement->NextSiblingElement(SETTING_XML_ELM_SETTING); + } + } + + if (settingElement == nullptr) + return false; + + // check if the default="true" attribute is set for the value + auto isDefaultAttribute = settingElement->Attribute(SETTING_XML_ELM_DEFAULT); + bool isDefault = isDefaultAttribute != nullptr && StringUtils::EqualsNoCase(isDefaultAttribute, "true"); + + if (!setting->FromString(settingElement->FirstChild() != nullptr ? settingElement->FirstChild()->ValueStr() : StringUtils::Empty)) + { + m_logger->warn("unable to read value of setting \"{}\"", settingId); + return false; + } + + // check if we need to perform any update logic for the setting + auto updates = setting->GetUpdates(); + for (const auto& update : updates) + updated |= UpdateSetting(node, setting, update); + + // the setting's value hasn't been updated and is the default value + // so we can reset it to the default value (in case the default value has changed) + if (!updated && isDefault) + setting->Reset(); + + return true; +} + +bool CSettingsManager::UpdateSetting(const TiXmlNode* node, + const SettingPtr& setting, + const CSettingUpdate& update) +{ + if (node == nullptr || setting == nullptr || update.GetType() == SettingUpdateType::Unknown) + return false; + + bool updated = false; + const char *oldSetting = nullptr; + const TiXmlNode *oldSettingNode = nullptr; + if (update.GetType() == SettingUpdateType::Rename) + { + if (update.GetValue().empty()) + return false; + + oldSetting = update.GetValue().c_str(); + std::string categoryTag, settingTag; + if (!ParseSettingIdentifier(oldSetting, categoryTag, settingTag)) + return false; + + auto categoryNode = node; + if (!categoryTag.empty()) + { + categoryNode = node->FirstChild(categoryTag); + if (categoryNode == nullptr) + return false; + } + + oldSettingNode = categoryNode->FirstChild(settingTag); + if (oldSettingNode == nullptr) + return false; + + if (setting->FromString(oldSettingNode->FirstChild() != nullptr ? oldSettingNode->FirstChild()->ValueStr() : StringUtils::Empty)) + updated = true; + else + m_logger->warn("unable to update \"{}\" through automatically renaming from \"{}\"", + setting->GetId(), oldSetting); + } + + updated |= OnSettingUpdate(setting, oldSetting, oldSettingNode); + return updated; +} + +void CSettingsManager::UpdateSettingByDependency(const std::string &settingId, const CSettingDependency &dependency) +{ + UpdateSettingByDependency(settingId, dependency.GetType()); +} + +void CSettingsManager::UpdateSettingByDependency(const std::string &settingId, SettingDependencyType dependencyType) +{ + auto settingIt = FindSetting(settingId); + if (settingIt == m_settings.end()) + return; + SettingPtr setting = settingIt->second.setting; + if (setting == nullptr) + return; + + switch (dependencyType) + { + case SettingDependencyType::Enable: + // just trigger the property changed callback and a call to + // CSetting::IsEnabled() will automatically determine the new + // enabled state + OnSettingPropertyChanged(setting, "enabled"); + break; + + case SettingDependencyType::Update: + { + SettingType type = setting->GetType(); + if (type == SettingType::Integer) + { + auto settingInt = std::static_pointer_cast<CSettingInt>(setting); + if (settingInt->GetOptionsType() == SettingOptionsType::Dynamic) + settingInt->UpdateDynamicOptions(); + } + else if (type == SettingType::String) + { + auto settingString = std::static_pointer_cast<CSettingString>(setting); + if (settingString->GetOptionsType() == SettingOptionsType::Dynamic) + settingString->UpdateDynamicOptions(); + } + break; + } + + case SettingDependencyType::Visible: + // just trigger the property changed callback and a call to + // CSetting::IsVisible() will automatically determine the new + // visible state + OnSettingPropertyChanged(setting, "visible"); + break; + + case SettingDependencyType::Unknown: + default: + break; + } +} + +void CSettingsManager::AddSetting(const std::shared_ptr<CSetting>& setting) +{ + setting->CheckRequirements(); + + auto addedSetting = FindSetting(setting->GetId()); + if (addedSetting == m_settings.end()) + { + Setting tmpSetting = {}; + auto tmpIt = InsertSetting(setting->GetId(), tmpSetting); + addedSetting = tmpIt.first; + } + + if (addedSetting->second.setting == nullptr) + { + addedSetting->second.setting = setting; + setting->SetCallback(this); + } +} + +void CSettingsManager::ResolveReferenceSettings(const std::shared_ptr<CSettingSection>& section) +{ + struct GroupedReferenceSettings + { + SettingPtr referencedSetting; + std::unordered_set<SettingPtr> referenceSettings; + }; + std::map<std::string, GroupedReferenceSettings> groupedReferenceSettings; + + // collect and group all reference(d) settings + auto categories = section->GetCategories(); + for (const auto& category : categories) + { + auto groups = category->GetGroups(); + for (auto& group : groups) + { + auto settings = group->GetSettings(); + for (const auto& setting : settings) + { + if (setting->IsReference()) + { + auto referencedSettingId = setting->GetReferencedId(); + auto itGroupedReferenceSetting = groupedReferenceSettings.find(referencedSettingId); + if (itGroupedReferenceSetting == groupedReferenceSettings.end()) + { + SettingPtr referencedSetting = nullptr; + auto itReferencedSetting = FindSetting(referencedSettingId); + if (itReferencedSetting == m_settings.end()) + { + m_logger->warn("missing referenced setting \"{}\"", referencedSettingId); + continue; + } + + GroupedReferenceSettings groupedReferenceSetting; + groupedReferenceSetting.referencedSetting = itReferencedSetting->second.setting; + + itGroupedReferenceSetting = groupedReferenceSettings.insert( + std::make_pair(referencedSettingId, groupedReferenceSetting)).first; + } + + itGroupedReferenceSetting->second.referenceSettings.insert(setting); + } + } + } + } + + if (groupedReferenceSettings.empty()) + return; + + // merge all reference settings into the referenced setting + for (const auto& groupedReferenceSetting : groupedReferenceSettings) + { + auto itReferencedSetting = FindSetting(groupedReferenceSetting.first); + if (itReferencedSetting == m_settings.end()) + continue; + + for (const auto& referenceSetting : groupedReferenceSetting.second.referenceSettings) + { + groupedReferenceSetting.second.referencedSetting->MergeDetails(*referenceSetting); + + itReferencedSetting->second.references.insert(referenceSetting->GetId()); + } + } + + // resolve any reference settings + for (const auto& category : categories) + { + auto groups = category->GetGroups(); + for (auto& group : groups) + { + auto settings = group->GetSettings(); + for (const auto& setting : settings) + { + if (setting->IsReference()) + { + auto referencedSettingId = setting->GetReferencedId(); + auto itGroupedReferenceSetting = groupedReferenceSettings.find(referencedSettingId); + if (itGroupedReferenceSetting != groupedReferenceSettings.end()) + { + const auto referencedSetting = itGroupedReferenceSetting->second.referencedSetting; + + // clone the referenced setting and copy the general properties of the reference setting + auto clonedReferencedSetting = referencedSetting->Clone(setting->GetId()); + clonedReferencedSetting->SetReferencedId(referencedSettingId); + clonedReferencedSetting->MergeBasics(*setting); + + group->ReplaceSetting(setting, clonedReferencedSetting); + + // update the setting + auto itReferenceSetting = FindSetting(setting->GetId()); + if (itReferenceSetting != m_settings.end()) + itReferenceSetting->second.setting = clonedReferencedSetting; + } + } + } + } + } +} + +void CSettingsManager::CleanupIncompleteSettings() +{ + // remove any empty and reference settings + for (auto setting = m_settings.begin(); setting != m_settings.end(); ) + { + auto tmpIterator = setting++; + if (tmpIterator->second.setting == nullptr) + { + m_logger->warn("removing empty setting \"{}\"", tmpIterator->first); + m_settings.erase(tmpIterator); + } + } +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, void *filler, SettingOptionsFillerType type) +{ + std::unique_lock<CSharedSection> lock(m_critical); + auto it = m_optionsFillers.find(identifier); + if (it != m_optionsFillers.end()) + return; + + SettingOptionsFiller optionsFiller = { filler, type }; + m_optionsFillers.insert(make_pair(identifier, optionsFiller)); +} + +void CSettingsManager::ResolveSettingDependencies(const std::shared_ptr<CSetting>& setting) +{ + if (setting == nullptr) + return; + + ResolveSettingDependencies(FindSetting(setting->GetId())->second); +} + +void CSettingsManager::ResolveSettingDependencies(const Setting& setting) +{ + if (setting.setting == nullptr) + return; + + // if the setting has a parent setting, add it to its children + auto parentSettingId = setting.setting->GetParent(); + if (!parentSettingId.empty()) + { + auto itParentSetting = FindSetting(parentSettingId); + if (itParentSetting != m_settings.end()) + itParentSetting->second.children.insert(setting.setting->GetId()); + } + + // handle all dependencies of the setting + const auto& dependencies = setting.setting->GetDependencies(); + for (const auto& deps : dependencies) + { + const auto settingIds = deps.GetSettings(); + for (const auto& settingId : settingIds) + { + auto settingIt = FindSetting(settingId); + if (settingIt == m_settings.end()) + continue; + + bool newDep = true; + auto& settingDeps = settingIt->second.dependencies[setting.setting->GetId()]; + for (const auto& dep : settingDeps) + { + if (dep.GetType() == deps.GetType()) + { + newDep = false; + break; + } + } + + if (newDep) + settingDeps.push_back(deps); + } + } +} + +CSettingsManager::SettingMap::const_iterator CSettingsManager::FindSetting(std::string settingId) const +{ + StringUtils::ToLower(settingId); + return m_settings.find(settingId); +} + +CSettingsManager::SettingMap::iterator CSettingsManager::FindSetting(std::string settingId) +{ + StringUtils::ToLower(settingId); + return m_settings.find(settingId); +} + +std::pair<CSettingsManager::SettingMap::iterator, bool> CSettingsManager::InsertSetting(std::string settingId, const Setting& setting) +{ + StringUtils::ToLower(settingId); + return m_settings.insert(std::make_pair(settingId, setting)); +} diff --git a/xbmc/settings/lib/SettingsManager.h b/xbmc/settings/lib/SettingsManager.h new file mode 100644 index 0000000..161c1a9 --- /dev/null +++ b/xbmc/settings/lib/SettingsManager.h @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISettingCallback.h" +#include "ISettingControlCreator.h" +#include "ISettingCreator.h" +#include "ISettingsHandler.h" +#include "ISettingsValueSerializer.h" +#include "Setting.h" +#include "SettingConditions.h" +#include "SettingDefinitions.h" +#include "SettingDependency.h" +#include "threads/SharedSection.h" +#include "utils/logtypes.h" + +#include <map> +#include <set> +#include <unordered_set> +#include <vector> + +class CSettingCategory; +class CSettingGroup; +class CSettingSection; +class CSettingUpdate; + +class TiXmlElement; +class TiXmlNode; + +/*! + \ingroup settings + \brief Settings manager responsible for initializing, loading and handling + all settings. + */ +class CSettingsManager : public ISettingCreator, + public ISettingControlCreator, + private ISettingCallback, + private ISettingsHandler +{ +public: + /*! + \brief Creates a new (uninitialized) settings manager. + */ + CSettingsManager(); + ~CSettingsManager() override; + + static const uint32_t Version; + static const uint32_t MinimumSupportedVersion; + + // implementation of ISettingCreator + std::shared_ptr<CSetting> CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager = nullptr) const override; + + // implementation of ISettingControlCreator + std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const override; + + uint32_t GetVersion() const { return Version; } + uint32_t GetMinimumSupportedVersion() const { return MinimumSupportedVersion; } + + /*! + \brief Try to get the version of the setting definitions/values represented by the given XML element. + + \param root XML element representing setting definitions/values + \return Version of the setting definitions/values or 0 if no version has been specified + */ + uint32_t ParseVersion(const TiXmlElement* root) const; + + /*! + \brief Initializes the settings manager using the setting definitions + represented by the given XML element. + + \param root XML element representing setting definitions + \return True if the XML element was successfully deserialized into setting definitions, false otherwise + */ + bool Initialize(const TiXmlElement *root); + /*! + \brief Loads setting values from the given XML element. + + \param root XML element containing setting values + \param updated Whether some settings were automatically updated + \param triggerEvents Whether to trigger ISettingCallback methods + \param loadedSettings A list to fill with all the successfully loaded settings + \return True if the setting values were successfully loaded, false otherwise + */ + bool Load(const TiXmlElement *root, bool &updated, bool triggerEvents = true, std::map<std::string, std::shared_ptr<CSetting>> *loadedSettings = nullptr); + /*! + \brief Saves the setting values using the given serializer. + + \param serializer Settings value serializer to use + \return True if the setting values were successfully serialized, false otherwise + */ + bool Save(const ISettingsValueSerializer* serializer, std::string& serializedValues) const; + /*! + \brief Unloads the previously loaded setting values. + + The values of all the settings are reset to their default values. + */ + void Unload(); + /*! + \brief Clears the complete settings manager. + + This removes all initialized settings, groups, categories and sections and + returns to the uninitialized state. Any registered callbacks or + implementations stay registered. + */ + void Clear(); + + /*! + \brief Loads the setting being represented by the given XML node with the + given identifier. + + \param node XML node representing the setting to load + \param settingId Setting identifier + \return True if the setting was successfully loaded from the given XML node, false otherwise + */ + bool LoadSetting(const TiXmlNode *node, const std::string &settingId); + + /*! + \brief Loads the setting being represented by the given XML node with the + given identifier. + + \param node XML node representing the setting to load + \param settingId Setting identifier + \param updated Set to true if the setting's value was updated + \return True if the setting was successfully loaded from the given XML node, false otherwise + */ + bool LoadSetting(const TiXmlNode *node, const std::string &settingId, bool &updated); + + /*! + \brief Tells the settings system that the initialization is complete. + + Setting values can only be loaded after a complete and successful + initialization of the settings system. + */ + void SetInitialized(); + /*! + \brief Returns whether the settings system has been initialized or not. + */ + bool IsInitialized() const { return m_initialized; } + /*! + \brief Tells the settings system that all setting values + have been loaded. + + This manual trigger is necessary to enable the ISettingCallback methods + being executed. + */ + void SetLoaded() { m_loaded = true; } + /*! + \brief Returns whether the settings system has been loaded or not. + */ + bool IsLoaded() const { return m_loaded; } + + /*! + \brief Adds the given section, its categories, groups and settings. + + This is possible before and after the setting definitions have been + initialized. + */ + void AddSection(const std::shared_ptr<CSettingSection>& section); + + /*! + \brief Adds the given setting to the given group in the given category in + the given section; + + If the given section has not been added yet, it is added. If the given + category has not been added to the given section yet, it is added. If the + given group has not been added to the given category yet, it is added. If + the given setting has not been added to the given group yet, it is added. + + This is possible before and after the setting definitions have been + initialized. + + \param setting New setting to be added + \param section Section the new setting should be added to + \param category Category the new setting should be added to + \param group Group the new setting should be added to + \return True if the setting has been added, false otherwise + */ + bool AddSetting(const std::shared_ptr<CSetting>& setting, + const std::shared_ptr<CSettingSection>& section, + const std::shared_ptr<CSettingCategory>& category, + const std::shared_ptr<CSettingGroup>& group); + + /*! + \brief Registers the given ISettingCallback implementation to be triggered + for the given list of settings. + + \param settingsHandler ISettingsHandler implementation + \param settingList List of settings to trigger the given ISettingCallback implementation + */ + void RegisterCallback(ISettingCallback *callback, const std::set<std::string> &settingList); + /*! + \brief Unregisters the given ISettingCallback implementation. + + \param callback ISettingCallback implementation + */ + void UnregisterCallback(ISettingCallback *callback); + + /*! + \brief Registers a custom setting type and its ISettingCreator + implementation. + + When a setting definition for a registered custom setting type is found its + ISettingCreator implementation is called to create and deserialize the + setting definition. + + \param settingType String representation of the custom setting type + \param settingCreator ISettingCreator implementation + */ + void RegisterSettingType(const std::string &settingType, ISettingCreator *settingCreator); + + /*! + \brief Registers a custom setting control type and its + ISettingControlCreator implementation + + When a setting control definition for a registered custom setting control + type is found its ISettingControlCreator implementation is called to create + and deserialize the setting control definition. + + \param controlType String representation of the custom setting control type + \param settingControlCreator ISettingControlCreator implementation + */ + void RegisterSettingControl(const std::string &controlType, ISettingControlCreator *settingControlCreator); + + /*! + \brief Registers the given ISettingsHandler implementation. + + \param settingsHandler ISettingsHandler implementation + \param bFront If True, insert the handler in front of other registered handlers, insert at the end otherwise. + */ + void RegisterSettingsHandler(ISettingsHandler *settingsHandler, bool bFront = false); + /*! + \brief Unregisters the given ISettingsHandler implementation. + + \param settingsHandler ISettingsHandler implementation + */ + void UnregisterSettingsHandler(ISettingsHandler *settingsHandler); + + /*! + \brief Registers the given integer setting options filler under the given identifier. + + \param identifier Setting options filler identifier + \param optionsFiller Integer setting options filler implementation + */ + void RegisterSettingOptionsFiller(const std::string &identifier, IntegerSettingOptionsFiller optionsFiller); + /*! + \brief Registers the given string setting options filler under the given identifier. + + \param identifier Setting options filler identifier + \param optionsFiller String setting options filler implementation + */ + void RegisterSettingOptionsFiller(const std::string &identifier, StringSettingOptionsFiller optionsFiller); + /*! + \brief Unregisters the setting options filler registered under the given identifier. + + \param identifier Setting options filler identifier + */ + void UnregisterSettingOptionsFiller(const std::string &identifier); + /*! + \brief Gets the implementation of the setting options filler used by the + given setting. + + \param setting Setting object + \return Implementation of the setting options filler (either IntegerSettingOptionsFiller or StringSettingOptionsFiller) + */ + void* GetSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting); + + /*! + \brief Checks whether any settings have been initialized. + + \return True if at least one setting has been initialized, false otherwise*/ + bool HasSettings() const; + + /*! + \brief Gets the setting with the given identifier. + + \param id Setting identifier + \return Setting object with the given identifier or nullptr if the identifier is unknown + */ + std::shared_ptr<CSetting> GetSetting(const std::string &id) const; + /*! + \brief Gets the full list of setting sections. + + \return List of setting sections + */ + std::vector<std::shared_ptr<CSettingSection>> GetSections() const; + /*! + \brief Gets the setting section with the given identifier. + + \param section Setting section identifier + \return Setting section with the given identifier or nullptr if the identifier is unknown + */ + std::shared_ptr<CSettingSection> GetSection(std::string section) const; + /*! + \brief Gets a map of settings (and their dependencies) which depend on + the setting with the given identifier. + + It is important to note that the returned dependencies are not the + dependencies of the setting with the given identifier but the settings + (and their dependencies) which depend on the setting with the given + identifier. + + \param id Setting identifier + \return Map of settings (and their dependencies) which depend on the setting with the given identifier + */ + SettingDependencyMap GetDependencies(const std::string &id) const; + /*! + \brief Gets a map of settings (and their dependencies) which depend on + the given setting. + + It is important to note that the returned dependencies are not the + dependencies of the given setting but the settings (and their dependencies) + which depend on the given setting. + + \param setting Setting object + \return Map of settings (and their dependencies) which depend on the given setting + */ + SettingDependencyMap GetDependencies(const std::shared_ptr<const CSetting>& setting) const; + + /*! + \brief Gets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return Boolean value of the setting with the given identifier + */ + bool GetBool(const std::string &id) const; + /*! + \brief Gets the integer value of the setting with the given identifier. + + \param id Setting identifier + \return Integer value of the setting with the given identifier + */ + int GetInt(const std::string &id) const; + /*! + \brief Gets the real number value of the setting with the given identifier. + + \param id Setting identifier + \return Real number value of the setting with the given identifier + */ + double GetNumber(const std::string &id) const; + /*! + \brief Gets the string value of the setting with the given identifier. + + \param id Setting identifier + \return String value of the setting with the given identifier + */ + std::string GetString(const std::string &id) const; + /*! + \brief Gets the values of the list setting with the given identifier. + + \param id Setting identifier + \return List of values of the setting with the given identifier + */ + std::vector< std::shared_ptr<CSetting> > GetList(const std::string &id) const; + + /*! + \brief Sets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \param value Boolean value to set + \return True if setting the value was successful, false otherwise + */ + bool SetBool(const std::string &id, bool value); + /*! + \brief Toggles the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return True if toggling the boolean value was successful, false otherwise + */ + bool ToggleBool(const std::string &id); + /*! + \brief Sets the integer value of the setting with the given identifier. + + \param id Setting identifier + \param value Integer value to set + \return True if setting the value was successful, false otherwise + */ + bool SetInt(const std::string &id, int value); + /*! + \brief Sets the real number value of the setting with the given identifier. + + \param id Setting identifier + \param value Real number value to set + \return True if setting the value was successful, false otherwise + */ + bool SetNumber(const std::string &id, double value); + /*! + \brief Sets the string value of the setting with the given identifier. + + \param id Setting identifier + \param value String value to set + \return True if setting the value was successful, false otherwise + */ + bool SetString(const std::string &id, const std::string &value); + /*! + \brief Sets the values of the list setting with the given identifier. + + \param id Setting identifier + \param value Values to set + \return True if setting the values was successful, false otherwise + */ + bool SetList(const std::string &id, const std::vector< std::shared_ptr<CSetting> > &value); + + /*! + \brief Sets the value of the setting to its default. + + \param id Setting identifier + \return True if setting the value to its default was successful, false otherwise + */ + bool SetDefault(const std::string &id); + /*! + \brief Sets the value of all settings to their default. + */ + void SetDefaults(); + + /*! + \brief Gets the setting conditions manager used by the settings manager. + + \return Setting conditions manager used by the settings manager. + */ + const CSettingConditionsManager& GetConditions() const { return m_conditions; } + /*! + \brief Adds the given static condition. + + A static condition is just a string. If a static condition is evaluated, + the result depends on whether the condition's value is defined or not. + + \param condition Static condition string/value + */ + void AddCondition(const std::string &condition); + /*! + \brief Adds the given dynamic condition. + + A dynamic condition has an identifier and an implementation which is + triggered when the condition is evaluated. + + \param identifier Identifier of the dynamic condition + \param condition Implementation of the dynamic condition + \param data Opaque data pointer, will be passed back to SettingConditionCheck function + */ + void AddDynamicCondition(const std::string &identifier, SettingConditionCheck condition, void *data = nullptr); + + /*! + \brief Removes the given dynamic condition. + + \param identifier Identifier of the dynamic condition + */ + void RemoveDynamicCondition(const std::string &identifier); + +private: + // implementation of ISettingCallback + bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; + void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) override; + + // implementation of ISettingsHandler + bool OnSettingsLoading() override; + void OnSettingsLoaded() override; + void OnSettingsUnloaded() override; + bool OnSettingsSaving() const override; + void OnSettingsSaved() const override; + void OnSettingsCleared() override; + + bool Serialize(TiXmlNode *parent) const; + bool Deserialize(const TiXmlNode *node, bool &updated, std::map<std::string, std::shared_ptr<CSetting>> *loadedSettings = nullptr); + + bool LoadSetting(const TiXmlNode* node, const std::shared_ptr<CSetting>& setting, bool& updated); + bool UpdateSetting(const TiXmlNode* node, + const std::shared_ptr<CSetting>& setting, + const CSettingUpdate& update); + void UpdateSettingByDependency(const std::string &settingId, const CSettingDependency &dependency); + void UpdateSettingByDependency(const std::string &settingId, SettingDependencyType dependencyType); + + void AddSetting(const std::shared_ptr<CSetting>& setting); + + void ResolveReferenceSettings(const std::shared_ptr<CSettingSection>& section); + void CleanupIncompleteSettings(); + + enum class SettingOptionsFillerType { + Unknown = 0, + Integer, + String + }; + + void RegisterSettingOptionsFiller(const std::string &identifier, void *filler, SettingOptionsFillerType type); + + using CallbackSet = std::set<ISettingCallback *>; + struct Setting { + std::shared_ptr<CSetting> setting; + SettingDependencyMap dependencies; + std::set<std::string> children; + CallbackSet callbacks; + std::unordered_set<std::string> references; + }; + + using SettingMap = std::map<std::string, Setting>; + + void ResolveSettingDependencies(const std::shared_ptr<CSetting>& setting); + void ResolveSettingDependencies(const Setting& setting); + + SettingMap::const_iterator FindSetting(std::string settingId) const; + SettingMap::iterator FindSetting(std::string settingId); + std::pair<SettingMap::iterator, bool> InsertSetting(std::string settingId, const Setting& setting); + + bool m_initialized = false; + bool m_loaded = false; + + SettingMap m_settings; + using SettingSectionMap = std::map<std::string, std::shared_ptr<CSettingSection>>; + SettingSectionMap m_sections; + + using SettingCreatorMap = std::map<std::string, ISettingCreator*>; + SettingCreatorMap m_settingCreators; + + using SettingControlCreatorMap = std::map<std::string, ISettingControlCreator*>; + SettingControlCreatorMap m_settingControlCreators; + + using SettingsHandlers = std::vector<ISettingsHandler*>; + SettingsHandlers m_settingsHandlers; + + CSettingConditionsManager m_conditions; + + struct SettingOptionsFiller { + void *filler; + SettingOptionsFillerType type; + }; + using SettingOptionsFillerMap = std::map<std::string, SettingOptionsFiller>; + SettingOptionsFillerMap m_optionsFillers; + + mutable CSharedSection m_critical; + mutable CSharedSection m_settingsCritical; + + Logger m_logger; +}; |