summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/settings
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/addons/settings
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xbmc/addons/settings/AddonSettings.cpp1726
-rw-r--r--xbmc/addons/settings/AddonSettings.h203
-rw-r--r--xbmc/addons/settings/CMakeLists.txt7
-rw-r--r--xbmc/addons/settings/SettingUrlEncodedString.cpp44
-rw-r--r--xbmc/addons/settings/SettingUrlEncodedString.h33
5 files changed, 2013 insertions, 0 deletions
diff --git a/xbmc/addons/settings/AddonSettings.cpp b/xbmc/addons/settings/AddonSettings.cpp
new file mode 100644
index 0000000..83bbdd9
--- /dev/null
+++ b/xbmc/addons/settings/AddonSettings.cpp
@@ -0,0 +1,1726 @@
+/*
+ * 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.
+ */
+
+#include "AddonSettings.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/binary-addons/AddonInstanceHandler.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "addons/settings/SettingUrlEncodedString.h"
+#include "filesystem/Directory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/LocalizeStrings.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/SettingAddon.h"
+#include "settings/SettingConditions.h"
+#include "settings/SettingControl.h"
+#include "settings/SettingDateTime.h"
+#include "settings/SettingPath.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "settings/lib/SettingSection.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+#include <vector>
+
+namespace
+{
+
+constexpr auto OldSettingValuesSeparator = "|";
+
+constexpr int UnknownSettingLabelIdStart = 100000;
+
+bool InfoBool(const std::string& condition,
+ const std::string& value,
+ const SettingConstPtr& setting,
+ void* data)
+{
+ return CServiceBroker::GetGUI()->GetInfoManager().EvaluateBool(value, INFO::DEFAULT_CONTEXT);
+}
+
+template<class TSetting>
+SettingPtr InitializeFromOldSettingWithoutDefinition(ADDON::CAddonSettings& settings,
+ const std::string& settingId,
+ const typename TSetting::Value& defaultValue)
+{
+ std::shared_ptr<TSetting> setting =
+ std::make_shared<TSetting>(settingId, settings.GetSettingsManager());
+ setting->SetLevel(SettingLevel::Internal);
+ setting->SetVisible(false);
+ setting->SetDefault(defaultValue);
+
+ return setting;
+}
+
+template<>
+SettingPtr InitializeFromOldSettingWithoutDefinition<CSettingString>(
+ ADDON::CAddonSettings& settings,
+ const std::string& settingId,
+ const typename CSettingString::Value& defaultValue)
+{
+ std::shared_ptr<CSettingString> setting =
+ std::make_shared<CSettingString>(settingId, settings.GetSettingsManager());
+ setting->SetLevel(SettingLevel::Internal);
+ setting->SetVisible(false);
+ setting->SetDefault(defaultValue);
+ setting->SetAllowEmpty(true);
+
+ return setting;
+}
+
+template<class TSetting>
+SettingPtr AddSettingWithoutDefinition(ADDON::CAddonSettings& settings,
+ const std::string& settingId,
+ typename TSetting::Value defaultValue,
+ const Logger& logger)
+{
+ if (settingId.empty())
+ return nullptr;
+
+ // if necessary try to initialize the settings manager on-the-fly without any definitions
+ if (!settings.IsInitialized() && !settings.Initialize(CXBMCTinyXML(), true))
+ {
+ logger->warn("failed to initialize settings on-the-fly");
+ return nullptr;
+ }
+
+ // check if we need to add a section on-the-fly
+ auto sections = settings.GetSettingsManager()->GetSections();
+ SettingSectionPtr section;
+ if (sections.empty())
+ section =
+ std::make_shared<CSettingSection>(settings.GetAddonId(), settings.GetSettingsManager());
+ else
+ section = sections.back();
+
+ // check if we need to add a category on-the-fly
+ auto categories = section->GetCategories();
+ SettingCategoryPtr category;
+ if (categories.empty())
+ category = std::make_shared<CSettingCategory>("category0", settings.GetSettingsManager());
+ else
+ category = categories.back();
+
+ // check if we need to add a group on-the-fly
+ auto groups = category->GetGroups();
+ SettingGroupPtr group;
+ if (groups.empty())
+ group = std::make_shared<CSettingGroup>("0", settings.GetSettingsManager());
+ else
+ group = groups.back();
+
+ // create a new setting on-the-fly
+ auto setting =
+ InitializeFromOldSettingWithoutDefinition<TSetting>(settings, settingId, defaultValue);
+ if (setting == nullptr)
+ {
+ logger->warn("failed to create setting \"{}\" on-the-fly", settingId);
+ return nullptr;
+ }
+
+ // add the setting (and if necessary the section, category and/or group)
+ if (!settings.GetSettingsManager()->AddSetting(setting, section, category, group))
+ {
+ logger->warn("failed to add setting \"{}\" on-the-fly", settingId);
+ return nullptr;
+ }
+
+ return setting;
+}
+
+} // namespace
+
+namespace ADDON
+{
+
+CAddonSettings::CAddonSettings(const std::shared_ptr<const IAddon>& addon,
+ AddonInstanceId instanceId)
+ : CSettingsBase(),
+ m_addonId(addon->ID()),
+ m_addonPath(addon->Path()),
+ m_addonProfile(addon->Profile()),
+ m_instanceId(instanceId),
+ m_unidentifiedSettingId(0),
+ m_unknownSettingLabelId(UnknownSettingLabelIdStart),
+ m_logger(CServiceBroker::GetLogging().GetLogger(
+ StringUtils::Format("CAddonSettings[{}@{}]", m_instanceId, m_addonId)))
+{
+}
+
+std::shared_ptr<CSetting> CAddonSettings::CreateSetting(
+ const std::string& settingType,
+ const std::string& settingId,
+ CSettingsManager* settingsManager /* = nullptr */) const
+{
+ if (StringUtils::EqualsNoCase(settingType, "urlencodedstring"))
+ return std::make_shared<CSettingUrlEncodedString>(settingId, settingsManager);
+
+ return CSettingCreator::CreateSetting(settingType, settingId, settingsManager);
+}
+
+void CAddonSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
+{
+ std::string actionData;
+ bool closeDialog = false;
+
+ // check if it's an action setting
+ if (setting->GetType() == SettingType::Action)
+ {
+ auto settingAction = std::dynamic_pointer_cast<const CSettingAction>(setting);
+ if (settingAction != nullptr && settingAction->HasData())
+ {
+ actionData = settingAction->GetData();
+ // replace $CWD with the url of the add-on
+ StringUtils::Replace(actionData, "$CWD", m_addonPath);
+ // replace $ID with the id of the add-on
+ StringUtils::Replace(actionData, "$ID", m_addonId);
+ }
+ }
+
+ // check if the setting control's is a button and its format is action
+ if (setting->GetControl()->GetType() == "button" &&
+ setting->GetControl()->GetFormat() == "action")
+ {
+ auto controlButton =
+ std::dynamic_pointer_cast<const CSettingControlButton>(setting->GetControl());
+ if (controlButton != nullptr)
+ {
+ if (actionData.empty() && controlButton->HasActionData())
+ actionData = controlButton->GetActionData();
+
+ closeDialog = controlButton->CloseDialog();
+ }
+ }
+
+ if (actionData.empty())
+ return;
+
+ if (closeDialog)
+ CGUIDialogAddonSettings::SaveAndClose();
+
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, actionData);
+}
+
+bool CAddonSettings::AddInstanceSettings()
+{
+ if (GetSetting(ADDON_SETTING_INSTANCE_NAME_VALUE) ||
+ GetSetting(ADDON_SETTING_INSTANCE_ENABLED_VALUE))
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "CAddonSettings::{} - Add-on {} using instance setting values byself, Kodi's add ignored",
+ __func__, m_addonId);
+ return true;
+ }
+
+ auto mgr = GetSettingsManager();
+ if (!mgr)
+ return false;
+
+ auto sections = mgr->GetSections();
+ if (sections.empty())
+ return false;
+
+ SettingSectionPtr section = *sections.begin();
+
+ auto categories = section->GetCategories();
+ if (categories.empty())
+ return false;
+
+ SettingCategoryPtr category = *categories.begin();
+
+ auto groups = category->GetGroups();
+ auto itr = std::find_if(groups.begin(), groups.end(),
+ [](const SettingGroupPtr& group)
+ { return group->GetId() == ADDON_SETTING_INSTANCE_GROUP; });
+
+ SettingGroupPtr group;
+ if (itr != groups.end())
+ {
+ group = *itr;
+ }
+ else
+ {
+ group = std::make_shared<CSettingGroup>(ADDON_SETTING_INSTANCE_GROUP, mgr);
+ group->SetLabel(10017); // Add-on configuration
+ category->AddGroupToFront(group);
+ }
+
+ const std::shared_ptr<CSettingString> name =
+ std::make_shared<CSettingString>(ADDON_SETTING_INSTANCE_NAME_VALUE, 551, "", mgr); // Name
+ name->SetAllowEmpty(false);
+ name->SetControl(std::make_shared<CSettingControlEdit>());
+ if (!mgr->AddSetting(name, section, category, group))
+ return false;
+
+ const std::shared_ptr<CSettingBool> enabled = std::make_shared<CSettingBool>(
+ ADDON_SETTING_INSTANCE_ENABLED_VALUE, 305, true, mgr); // Enabled
+ enabled->SetControl(std::make_shared<CSettingControlCheckmark>());
+ if (!mgr->AddSetting(enabled, section, category, group))
+ return false;
+
+ return true;
+}
+
+bool CAddonSettings::Initialize(const CXBMCTinyXML& doc, bool allowEmpty /* = false */)
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ if (m_initialized)
+ return false;
+
+ // register custom setting types
+ InitializeSettingTypes();
+ // register custom setting controls
+ InitializeControls();
+
+ // conditions need to be initialized before the setting definitions
+ InitializeConditions();
+
+ // load the settings definitions
+ if (!InitializeDefinitions(doc) && !allowEmpty)
+ return false;
+
+ // Add internal settings to set values about instance set
+ if (m_instanceId > 0 && !AddInstanceSettings())
+ return false;
+
+ GetSettingsManager()->SetInitialized();
+
+ m_initialized = true;
+
+ return true;
+}
+
+bool CAddonSettings::Load(const CXBMCTinyXML& doc)
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ if (!m_initialized)
+ return false;
+
+ // figure out the version of the setting definitions
+ uint32_t version = 0;
+ if (!ParseSettingVersion(doc, version))
+ {
+ m_logger->error("failed to determine setting values version");
+ return false;
+ }
+
+ std::map<std::string, std::string> settingValues;
+
+ // for new/"normal" setting values use the standard process
+ if (version != 0)
+ {
+ bool updated;
+ if (!LoadValuesFromXml(doc, updated))
+ return false;
+
+ // helper lambda for parsing a setting's ID and value from XML
+ auto parseSettingValue = [&settingValues](const TiXmlNode* setting,
+ const std::string& categoryId = "") {
+ // put together the setting ID
+ auto settingId = categoryId;
+ if (!settingId.empty())
+ settingId += ".";
+ auto id = setting->ToElement()->Attribute("id");
+ if (id)
+ settingId += id;
+
+ // parse the setting value
+ std::string settingValue;
+ if (setting->FirstChild())
+ settingValue = setting->FirstChild()->ValueStr();
+
+ // add the setting to the map
+ settingValues.emplace(std::make_pair(settingId, settingValue));
+ };
+
+ // check if there were any setting values without a definition
+ auto category = doc.RootElement()->FirstChild();
+ while (category != nullptr)
+ {
+ // check if this really is a category with setting elements
+ if (category->FirstChild() && category->FirstChild()->Type() == CXBMCTinyXML::TINYXML_ELEMENT)
+ {
+ const auto& categoryId = category->ValueStr();
+ auto setting = category->FirstChild();
+ while (setting != nullptr)
+ {
+ parseSettingValue(setting, categoryId);
+
+ setting = setting->NextSibling();
+ }
+ }
+ else
+ parseSettingValue(category);
+
+ category = category->NextSibling();
+ }
+ }
+ // for old setting values do it manually
+ else if (!LoadOldSettingValues(doc, settingValues))
+ {
+ m_logger->error("failed to determine setting values from old format");
+ return false;
+ }
+
+ // process all settings
+ for (const auto& setting : settingValues)
+ {
+ // ignore setting values without a setting identifier
+ if (setting.first.empty())
+ continue;
+
+ // try to find a matching setting
+ SettingPtr newSetting = GetSetting(setting.first);
+ if (newSetting == nullptr)
+ {
+ // create a hidden/internal string setting on-the-fly
+ newSetting = AddSettingWithoutDefinition<CSettingString>(*this, setting.first, setting.second,
+ m_logger);
+ }
+
+ // try to load the old setting value
+ if (!newSetting)
+ {
+ m_logger->error("had null newSetting for value \"{}\" for setting {}", setting.second,
+ setting.first);
+ }
+ else if (!newSetting->FromString(setting.second))
+ {
+ m_logger->warn("failed to load value \"{}\" for setting {}", setting.second, setting.first);
+ }
+ }
+
+ SetLoaded();
+
+ return true;
+}
+
+bool CAddonSettings::Save(CXBMCTinyXML& doc) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ if (!m_initialized)
+ return false;
+
+ if (!SaveValuesToXml(doc))
+ {
+ m_logger->error("failed to save settings");
+ return false;
+ }
+
+ return true;
+}
+
+bool CAddonSettings::HasSettings() const
+{
+ return IsInitialized() && GetSettingsManager()->HasSettings();
+}
+
+std::string CAddonSettings::GetSettingLabel(int label) const
+{
+ if (label < UnknownSettingLabelIdStart || label >= m_unknownSettingLabelId)
+ return "";
+
+ const auto labelIt = m_unknownSettingLabels.find(label);
+ if (labelIt == m_unknownSettingLabels.end())
+ return "";
+
+ return labelIt->second;
+}
+
+std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, bool value)
+{
+ return AddSettingWithoutDefinition<CSettingBool>(*this, settingId, value, m_logger);
+}
+
+std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, int value)
+{
+ return AddSettingWithoutDefinition<CSettingInt>(*this, settingId, value, m_logger);
+}
+
+std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId, double value)
+{
+ return AddSettingWithoutDefinition<CSettingNumber>(*this, settingId, value, m_logger);
+}
+
+std::shared_ptr<CSetting> CAddonSettings::AddSetting(const std::string& settingId,
+ const std::string& value)
+{
+ return AddSettingWithoutDefinition<CSettingString>(*this, settingId, value, m_logger);
+}
+
+void CAddonSettings::InitializeSettingTypes()
+{
+ GetSettingsManager()->RegisterSettingType("addon", this);
+ GetSettingsManager()->RegisterSettingType("date", this);
+ GetSettingsManager()->RegisterSettingType("path", this);
+ GetSettingsManager()->RegisterSettingType("time", this);
+ GetSettingsManager()->RegisterSettingType("urlencodedstring", this);
+}
+
+void CAddonSettings::InitializeControls()
+{
+ GetSettingsManager()->RegisterSettingControl("toggle", this);
+ GetSettingsManager()->RegisterSettingControl("spinner", this);
+ GetSettingsManager()->RegisterSettingControl("edit", this);
+ GetSettingsManager()->RegisterSettingControl("button", this);
+ GetSettingsManager()->RegisterSettingControl("list", this);
+ GetSettingsManager()->RegisterSettingControl("slider", this);
+ GetSettingsManager()->RegisterSettingControl("range", this);
+ GetSettingsManager()->RegisterSettingControl("title", this);
+ GetSettingsManager()->RegisterSettingControl("colorbutton", this);
+}
+
+void CAddonSettings::InitializeConditions()
+{
+ CSettingConditions::Initialize();
+
+ // add basic conditions
+ const std::set<std::string>& simpleConditions = CSettingConditions::GetSimpleConditions();
+ for (const auto& condition : simpleConditions)
+ GetSettingsManager()->AddCondition(condition);
+
+ GetSettingsManager()->AddDynamicCondition("InfoBool", InfoBool);
+}
+
+bool CAddonSettings::InitializeDefinitions(const CXBMCTinyXML& doc)
+{
+ // figure out the version of the setting definitions
+ uint32_t version = 0;
+ if (!ParseSettingVersion(doc, version))
+ {
+ m_logger->error("failed to determine setting definitions version");
+ return false;
+ }
+
+ // for new/"normal" setting definitions use the standard process
+ if (version != 0)
+ return InitializeDefinitionsFromXml(doc);
+
+ // for old setting definitions do it manually
+ return InitializeFromOldSettingDefinitions(doc);
+}
+
+bool CAddonSettings::ParseSettingVersion(const CXBMCTinyXML& doc, uint32_t& version) const
+{
+ const TiXmlElement* root = doc.RootElement();
+ if (root == nullptr)
+ return false;
+
+ if (!StringUtils::EqualsNoCase(root->ValueStr(), SETTING_XML_ROOT))
+ {
+ m_logger->error("error reading setting definitions: no <settings> tag");
+ return false;
+ }
+
+ version = GetSettingsManager()->ParseVersion(root);
+ return true;
+}
+
+std::shared_ptr<CSettingGroup> CAddonSettings::ParseOldSettingElement(
+ const TiXmlElement* categoryElement,
+ const std::shared_ptr<CSettingCategory>& category,
+ std::set<std::string>& settingIds)
+{
+ // build a vector of settings from the same category
+ std::vector<std::shared_ptr<const CSetting>> categorySettings;
+
+ // prepare for settings with enable/visible conditions
+ struct SettingWithConditions
+ {
+ SettingPtr setting;
+ std::string enableCondition;
+ std::string visibleCondition;
+ SettingDependencies deps;
+ };
+ std::vector<SettingWithConditions> settingsWithConditions;
+
+ auto group = std::make_shared<CSettingGroup>("0", GetSettingsManager());
+ uint32_t groupId = 1;
+
+ // go through all settings in the category
+ const TiXmlElement* settingElement = categoryElement->FirstChildElement("setting");
+ while (settingElement != nullptr)
+ {
+ // read the possible attributes
+ const auto settingType = XMLUtils::GetAttribute(settingElement, "type");
+ const auto settingId = XMLUtils::GetAttribute(settingElement, "id");
+ const auto defaultValue = XMLUtils::GetAttribute(settingElement, "default");
+ const auto settingValues = XMLUtils::GetAttribute(settingElement, "values");
+ const auto settingLValues = StringUtils::Split(
+ XMLUtils::GetAttribute(settingElement, "lvalues"), OldSettingValuesSeparator);
+ int settingLabel = -1;
+ bool settingLabelParsed = ParseOldLabel(settingElement, settingId, settingLabel);
+
+ SettingPtr setting;
+ if (settingType == "sep" || settingType == "lsep")
+ {
+ // check if we need to create a new group
+ if (!group->GetSettings().empty())
+ {
+ // add the current group to the category
+ category->AddGroup(group);
+
+ // and create a new one
+ group.reset(new CSettingGroup(std::to_string(groupId), GetSettingsManager()));
+ groupId += 1;
+ }
+
+ if (settingType == "lsep" && settingLabelParsed)
+ group->SetLabel(settingLabel);
+ }
+ else if (settingId.empty() || settingType == "action")
+ {
+ if (settingType == "action")
+ setting = InitializeFromOldSettingAction(settingId, settingElement, defaultValue);
+ else
+ setting = InitializeFromOldSettingLabel();
+ }
+ else if (settingType == "bool")
+ setting = InitializeFromOldSettingBool(settingId, settingElement, defaultValue);
+ else if (settingType == "text" || settingType == "ipaddress")
+ setting = InitializeFromOldSettingTextIpAddress(settingId, settingType, settingElement,
+ defaultValue, settingLabel);
+ else if (settingType == "number")
+ setting =
+ InitializeFromOldSettingNumber(settingId, settingElement, defaultValue, settingLabel);
+ else if (settingType == "video" || settingType == "audio" || settingType == "image" ||
+ settingType == "executable" || settingType == "file" || settingType == "folder")
+ setting = InitializeFromOldSettingPath(settingId, settingType, settingElement, defaultValue,
+ settingLabel);
+ else if (settingType == "date")
+ setting = InitializeFromOldSettingDate(settingId, settingElement, defaultValue, settingLabel);
+ else if (settingType == "time")
+ setting = InitializeFromOldSettingTime(settingId, settingElement, defaultValue, settingLabel);
+ else if (settingType == "select")
+ setting = InitializeFromOldSettingSelect(settingId, settingElement, defaultValue,
+ settingLabel, settingValues, settingLValues);
+ else if (settingType == "addon")
+ setting =
+ InitializeFromOldSettingAddon(settingId, settingElement, defaultValue, settingLabel);
+ else if (settingType == "enum" || settingType == "labelenum")
+ setting = InitializeFromOldSettingEnums(settingId, settingType, settingElement, defaultValue,
+ settingValues, settingLValues);
+ else if (settingType == "fileenum")
+ setting =
+ InitializeFromOldSettingFileEnum(settingId, settingElement, defaultValue, settingValues);
+ else if (settingType == "rangeofnum")
+ setting = InitializeFromOldSettingRangeOfNum(settingId, settingElement, defaultValue);
+ else if (settingType == "slider")
+ setting = InitializeFromOldSettingSlider(settingId, settingElement, defaultValue);
+ else if (settingType.empty())
+ {
+ // setting definitions without a type are considered as "text" / strings but are hidden
+ setting = InitializeFromOldSettingTextIpAddress(settingId, "text", settingElement,
+ defaultValue, settingLabel);
+ setting->SetLevel(SettingLevel::Internal);
+ }
+ else
+ {
+ m_logger->warn("failed to parse old setting definition for \"{}\" of type \"{}\"", settingId,
+ settingType);
+ }
+
+ // process general properties
+ if (setting != nullptr)
+ {
+ // set the default level to be Basic
+ if (setting->GetLevel() != SettingLevel::Internal)
+ {
+ setting->SetLevel(SettingLevel::Basic);
+ }
+
+ // use the setting's ID if there's no label
+ if (settingLabel < 0)
+ {
+ settingLabel = m_unknownSettingLabelId;
+ m_unknownSettingLabelId += 1;
+
+ m_unknownSettingLabels.emplace(settingLabel, settingId);
+ }
+
+ // set the setting's label
+ setting->SetLabel(settingLabel);
+
+ // handle subsettings
+ bool isSubsetting = false;
+ if (settingElement->QueryBoolAttribute("subsetting", &isSubsetting) == TIXML_SUCCESS &&
+ isSubsetting)
+ {
+ // find the last non-subsetting in the current group and use that as the parent setting
+ const auto groupSettings = group->GetSettings();
+ const auto parentSetting = std::find_if(
+ groupSettings.crbegin(), groupSettings.crend(),
+ [](const SettingConstPtr& setting) { return setting->GetParent().empty(); });
+
+ if (parentSetting != groupSettings.crend())
+ {
+ if ((*parentSetting)->IsReference())
+ setting->SetParent((*parentSetting)->GetReferencedId());
+ else
+ setting->SetParent((*parentSetting)->GetId());
+ }
+ }
+
+ SettingWithConditions settingWithConditions;
+
+ // parse enable status
+ const auto conditionEnable = XMLUtils::GetAttribute(settingElement, "enable");
+ if (StringUtils::EqualsNoCase(conditionEnable, "true"))
+ setting->SetEnabled(true);
+ else if (StringUtils::EqualsNoCase(conditionEnable, "false"))
+ setting->SetEnabled(false);
+ else if (!conditionEnable.empty())
+ settingWithConditions.enableCondition = conditionEnable;
+
+ // parse visible status
+ const auto conditionVisible = XMLUtils::GetAttribute(settingElement, "visible");
+ if (StringUtils::EqualsNoCase(conditionVisible, "true"))
+ setting->SetVisible(true);
+ else if (StringUtils::EqualsNoCase(conditionVisible, "false"))
+ setting->SetVisible(false);
+ else if (!conditionVisible.empty())
+ settingWithConditions.visibleCondition = conditionVisible;
+
+ // check if there already is a setting with the setting identifier
+ if (settingIds.find(settingId) != settingIds.end())
+ {
+ // turn the setting into a reference setting
+ setting->MakeReference();
+ }
+ else
+ {
+ // add the setting's identifier to the list of all identifiers
+ settingIds.insert(setting->GetId());
+ }
+
+ if (!settingWithConditions.enableCondition.empty() ||
+ !settingWithConditions.visibleCondition.empty())
+ {
+ settingWithConditions.setting = setting;
+ settingsWithConditions.push_back(settingWithConditions);
+ }
+
+ // add the setting to the list of settings from the same category
+ categorySettings.push_back(setting);
+
+ // add the setting to the current group
+ group->AddSetting(setting);
+ }
+ else
+ {
+ // add a dummy setting for the group / separator to the list of settings from the same category
+ categorySettings.push_back(nullptr);
+ }
+
+ // look for the next setting
+ settingElement = settingElement->NextSiblingElement("setting");
+ }
+
+ // process settings with enable/visible conditions
+ for (auto setting : settingsWithConditions)
+ {
+ if (!setting.enableCondition.empty())
+ {
+ CSettingDependency dependencyEnable(SettingDependencyType::Enable, GetSettingsManager());
+ if (ParseOldCondition(setting.setting, categorySettings, setting.enableCondition,
+ dependencyEnable))
+ setting.deps.push_back(dependencyEnable);
+ else
+ {
+ m_logger->warn(
+ "failed to parse enable condition \"{}\" of old setting definition for \"{}\"",
+ setting.enableCondition, setting.setting->GetId());
+ }
+ }
+
+ if (!setting.visibleCondition.empty())
+ {
+ CSettingDependency dependencyVisible(SettingDependencyType::Visible, GetSettingsManager());
+ if (ParseOldCondition(setting.setting, categorySettings, setting.visibleCondition,
+ dependencyVisible))
+ setting.deps.push_back(dependencyVisible);
+ else
+ {
+ m_logger->warn(
+ "failed to parse visible condition \"{}\" of old setting definition for \"{}\"",
+ setting.visibleCondition, setting.setting->GetId());
+ }
+ }
+
+ // set dependencies
+ setting.setting->SetDependencies(setting.deps);
+ }
+
+ return group;
+}
+
+std::shared_ptr<CSettingCategory> CAddonSettings::ParseOldCategoryElement(
+ uint32_t& categoryId, const TiXmlElement* categoryElement, std::set<std::string>& settingIds)
+{
+ // create the category
+ auto category = std::make_shared<CSettingCategory>(StringUtils::Format("category{}", categoryId),
+ GetSettingsManager());
+ categoryId += 1;
+
+ // try to get the category's label and fall back to "General"
+ int categoryLabel = 128;
+ ParseOldLabel(categoryElement, g_localizeStrings.Get(categoryLabel), categoryLabel);
+ category->SetLabel(categoryLabel);
+
+ // prepare a setting group
+ auto group = ParseOldSettingElement(categoryElement, category, settingIds);
+
+ // add the group to the category
+ category->AddGroup(group);
+
+ return category;
+}
+
+bool CAddonSettings::InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc)
+{
+ m_logger->debug("trying to load setting definitions from old format...");
+
+ const TiXmlElement* root = doc.RootElement();
+ if (root == nullptr)
+ return false;
+
+ std::shared_ptr<CSettingSection> section =
+ std::make_shared<CSettingSection>(m_addonId, GetSettingsManager());
+
+ std::shared_ptr<CSettingCategory> category;
+ uint32_t categoryId = 0;
+
+ // Settings id set
+ std::set<std::string> settingIds;
+
+ // Special case for no category settings
+ section->AddCategory(ParseOldCategoryElement(categoryId, root, settingIds));
+
+ const TiXmlElement* categoryElement = root->FirstChildElement("category");
+ while (categoryElement != nullptr)
+ {
+ section->AddCategory(ParseOldCategoryElement(categoryId, categoryElement, settingIds));
+
+ // look for the next category
+ categoryElement = categoryElement->NextSiblingElement("category");
+ }
+
+ // add the section to the settingsmanager
+ GetSettingsManager()->AddSection(section);
+
+ return true;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingAction(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue)
+{
+ // parse the action attribute
+ std::string action = XMLUtils::GetAttribute(settingElement, "action");
+ // replace $CWD with the url of the add-on
+ StringUtils::Replace(action, "$CWD", m_addonPath);
+ // replace $ID with the id of the add-on
+ StringUtils::Replace(action, "$ID", m_addonId);
+
+ // prepare the setting's control
+ auto control = std::make_shared<CSettingControlButton>();
+ control->SetFormat("action");
+
+ SettingPtr setting = nullptr;
+ // action settings don't require a setting id
+ if (settingId.empty())
+ {
+ auto actionSettingId = StringUtils::Format("action{}", m_unidentifiedSettingId);
+ m_unidentifiedSettingId += 1;
+
+ auto settingAction = std::make_shared<CSettingAction>(actionSettingId, GetSettingsManager());
+ settingAction->SetData(action);
+
+ setting = settingAction;
+ }
+ else
+ {
+ // assume that the setting might store a value as a string
+ auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager());
+ settingString->SetDefault(defaultValue);
+ settingString->SetAllowEmpty(true);
+
+ control->SetActionData(action);
+
+ setting = settingString;
+ }
+
+ // get any options
+ std::string option = XMLUtils::GetAttribute(settingElement, "option");
+ // handle the "close" option
+ if (StringUtils::EqualsNoCase(option, "close"))
+ control->SetCloseDialog(true);
+
+ setting->SetControl(control);
+
+ return setting;
+}
+
+std::shared_ptr<CSetting> CAddonSettings::InitializeFromOldSettingLabel()
+{
+ // label settings don't require a setting id
+ auto labelSettingId = StringUtils::Format("label{}", m_unidentifiedSettingId);
+ m_unidentifiedSettingId += 1;
+
+ auto settingLabel = std::make_shared<CSettingString>(labelSettingId, GetSettingsManager());
+
+ // create the setting's control
+ settingLabel->SetControl(std::make_shared<CSettingControlLabel>());
+
+ return settingLabel;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingBool(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue)
+{
+ auto setting = std::make_shared<CSettingBool>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ setting->SetControl(std::make_shared<CSettingControlCheckmark>());
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingTextIpAddress(const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ std::shared_ptr<CSettingString> setting;
+ auto control = std::make_shared<CSettingControlEdit>();
+ control->SetHeading(settingLabel);
+
+ // get any options
+ std::string option = XMLUtils::GetAttribute(settingElement, "option");
+
+ if (settingType == "ipaddress")
+ {
+ setting = std::make_shared<CSettingString>(settingId, GetSettingsManager());
+ control->SetFormat("ip");
+ }
+ else if (settingType == "text")
+ {
+
+ if (StringUtils::EqualsNoCase(option, "urlencoded"))
+ {
+ setting = std::make_shared<CSettingUrlEncodedString>(settingId, GetSettingsManager());
+ control->SetFormat("urlencoded");
+ }
+ else
+ {
+ setting = std::make_shared<CSettingString>(settingId, GetSettingsManager());
+ control->SetFormat("string");
+ control->SetHidden(StringUtils::EqualsNoCase(option, "hidden"));
+ }
+ }
+
+ setting->SetDefault(defaultValue);
+ setting->SetAllowEmpty(true);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingNumber(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ auto setting = std::make_shared<CSettingInt>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ auto control = std::make_shared<CSettingControlEdit>();
+ control->SetHeading(settingLabel);
+ control->SetFormat("integer");
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingPath(const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ auto setting = std::make_shared<CSettingPath>(settingId, GetSettingsManager());
+ setting->SetDefault(defaultValue);
+
+ // parse sources/shares
+ const auto source = XMLUtils::GetAttribute(settingElement, "source");
+ if (!source.empty())
+ setting->SetSources({source});
+
+ // setup masking
+ const auto audioMask = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions();
+ const auto videoMask = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions();
+ const auto imageMask = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
+ auto execMask = "";
+#if defined(TARGET_WINDOWS)
+ execMask = ".exe|.bat|.cmd|.py";
+#endif // defined(TARGET_WINDOWS)
+
+ std::string mask = XMLUtils::GetAttribute(settingElement, "mask");
+ if (!mask.empty())
+ {
+ // convert mask qualifiers
+ StringUtils::Replace(mask, "$AUDIO", audioMask);
+ StringUtils::Replace(mask, "$VIDEO", videoMask);
+ StringUtils::Replace(mask, "$IMAGE", imageMask);
+ StringUtils::Replace(mask, "$EXECUTABLE", execMask);
+ }
+ else
+ {
+ if (settingType == "video")
+ mask = videoMask;
+ else if (settingType == "audio")
+ mask = audioMask;
+ else if (settingType == "image")
+ mask = imageMask;
+ else if (settingType == "executable")
+ mask = execMask;
+ }
+ setting->SetMasking(mask);
+
+ // parse options
+ const auto option = XMLUtils::GetAttribute(settingElement, "option");
+ setting->SetWritable(StringUtils::EqualsNoCase(option, "writeable"));
+
+ auto control = std::make_shared<CSettingControlButton>();
+ if (settingType == "folder")
+ control->SetFormat("path");
+ else if (settingType == "image")
+ control->SetFormat("image");
+ else
+ {
+ control->SetFormat("file");
+
+ // parse the options
+ const auto options = StringUtils::Split(option, OldSettingValuesSeparator);
+ control->SetUseImageThumbs(std::find(options.cbegin(), options.cend(), "usethumbs") !=
+ options.cend());
+ control->SetUseFileDirectories(std::find(options.cbegin(), options.cend(), "treatasfolder") !=
+ options.cend());
+ }
+ control->SetHeading(settingLabel);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingDate(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ auto setting = std::make_shared<CSettingDate>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ auto control = std::make_shared<CSettingControlButton>();
+ control->SetFormat("date");
+ control->SetHeading(settingLabel);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingTime(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ auto setting = std::make_shared<CSettingTime>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ auto control = std::make_shared<CSettingControlButton>();
+ control->SetFormat("time");
+ control->SetHeading(settingLabel);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingSelect(
+ const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel,
+ const std::string& settingValues,
+ const std::vector<std::string>& settingLValues)
+{
+ // process values and lvalues
+ std::vector<std::string> values;
+ if (!settingLValues.empty())
+ values = settingLValues;
+ else
+ values = StringUtils::Split(settingValues, OldSettingValuesSeparator);
+
+ SettingPtr setting = nullptr;
+ if (!values.empty())
+ {
+ if (settingLValues.empty())
+ {
+ auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager());
+ settingString->SetDefault(defaultValue);
+
+ StringSettingOptions options;
+ for (const auto& value : values)
+ options.push_back(StringSettingOption(value, value));
+ settingString->SetOptions(options);
+
+ setting = settingString;
+ }
+ else
+ {
+ auto settingInt = std::make_shared<CSettingInt>(settingId, GetSettingsManager());
+ if (settingInt->FromString(defaultValue))
+ settingInt->SetDefault(settingInt->GetValue());
+
+ TranslatableIntegerSettingOptions options;
+ for (uint32_t i = 0; i < values.size(); ++i)
+ options.push_back(TranslatableIntegerSettingOption(
+ static_cast<int>(strtol(values[i].c_str(), nullptr, 0)), i));
+ settingInt->SetTranslatableOptions(options);
+
+ setting = settingInt;
+ }
+ }
+ else
+ {
+ // parse sources/shares
+ const auto source = XMLUtils::GetAttribute(settingElement, "source");
+ if (!source.empty())
+ setting = InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue,
+ settingValues);
+ else
+ m_logger->warn("failed to parse old setting definition for \"{}\" of type \"select\"",
+ settingId);
+ }
+
+ if (setting != nullptr)
+ {
+ auto control = std::make_shared<CSettingControlList>();
+ control->SetHeading(settingLabel);
+ control->SetFormat("string");
+ setting->SetControl(control);
+ }
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingAddon(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel)
+{
+ // get addon types
+ std::string addonTypeStr = XMLUtils::GetAttribute(settingElement, "addontype");
+ const auto addonTypesStr = StringUtils::Split(addonTypeStr, ",");
+ std::set<AddonType> addonTypes;
+ for (auto addonType : addonTypesStr)
+ {
+ auto type = ADDON::CAddonInfo::TranslateType(StringUtils::Trim(addonType));
+ if (type != ADDON::AddonType::UNKNOWN)
+ addonTypes.insert(type);
+ }
+
+ if (addonTypes.empty())
+ {
+ m_logger->error("missing addon type for addon setting \"{}\"", settingId);
+ return nullptr;
+ }
+
+ // TODO: support multiple addon types
+ if (addonTypes.size() > 1)
+ {
+ m_logger->error("multiple addon types are not supported (addon setting \"{}\")", settingId);
+ return nullptr;
+ }
+
+ // parse addon ids
+ auto addonIds = StringUtils::Split(defaultValue, ",");
+
+ // parse multiselect option
+ bool multiselect = false;
+ settingElement->QueryBoolAttribute("multiselect", &multiselect);
+
+ // sanity check
+ if (addonIds.size() > 1 && !multiselect)
+ {
+ m_logger->warn("multiple default addon ids on non-multiselect addon setting \"{}\"", settingId);
+ addonIds.erase(++addonIds.begin(), addonIds.end());
+ }
+
+ auto settingAddon = std::make_shared<CSettingAddon>(settingId, GetSettingsManager());
+ settingAddon->SetAddonType(*addonTypes.begin());
+
+ SettingPtr setting = settingAddon;
+ if (multiselect)
+ {
+ auto settingList =
+ std::make_shared<CSettingList>(settingId, settingAddon, GetSettingsManager());
+ settingList->SetDelimiter(",");
+ if (settingList->FromString(addonIds))
+ settingList->SetDefault(settingList->GetValue());
+
+ setting = settingList;
+ }
+ else if (!addonIds.empty())
+ settingAddon->SetDefault(addonIds.front());
+
+ auto control = std::make_shared<CSettingControlButton>();
+ control->SetFormat("addon");
+ control->SetHeading(settingLabel);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingEnums(
+ const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const std::string& settingValues,
+ const std::vector<std::string>& settingLValues)
+{
+ // process values and lvalues
+ std::vector<std::string> values;
+ if (!settingLValues.empty())
+ values = settingLValues;
+ else if (settingValues == "$HOURS")
+ {
+ for (uint32_t hour = 0; hour < 24; hour++)
+ values.push_back(
+ CDateTime(2000, 1, 1, hour, 0, 0).GetAsLocalizedTime(g_langInfo.GetTimeFormat(), false));
+ }
+ else
+ values = StringUtils::Split(settingValues, OldSettingValuesSeparator);
+
+ // process entries
+ const auto settingEntries = StringUtils::Split(XMLUtils::GetAttribute(settingElement, "entries"),
+ OldSettingValuesSeparator);
+
+ // process sort
+ bool sortAscending = false;
+ std::string sort = XMLUtils::GetAttribute(settingElement, "sort");
+ if (sort == "true" || sort == "yes")
+ sortAscending = true;
+
+ SettingPtr setting = nullptr;
+ if (settingType == "enum")
+ {
+ auto settingInt = std::make_shared<CSettingInt>(settingId, GetSettingsManager());
+
+ if (settingLValues.empty())
+ {
+ IntegerSettingOptions options;
+ for (uint32_t i = 0; i < values.size(); ++i)
+ {
+ std::string label = values[i];
+ int value = i;
+ if (settingEntries.size() > i)
+ value = static_cast<int>(strtol(settingEntries[i].c_str(), nullptr, 0));
+
+ options.push_back(IntegerSettingOption(label, value));
+ }
+
+ settingInt->SetOptions(options);
+ }
+ else
+ {
+ TranslatableIntegerSettingOptions options;
+ for (uint32_t i = 0; i < values.size(); ++i)
+ {
+ int label = static_cast<int>(strtol(values[i].c_str(), nullptr, 0));
+ int value = i;
+ if (settingEntries.size() > i)
+ value = static_cast<int>(strtol(settingEntries[i].c_str(), nullptr, 0));
+
+ options.push_back(TranslatableIntegerSettingOption(label, value));
+ }
+
+ settingInt->SetTranslatableOptions(options);
+ }
+
+ if (sortAscending)
+ settingInt->SetOptionsSort(SettingOptionsSort::Ascending);
+
+ // set the default value
+ if (settingInt->FromString(defaultValue))
+ settingInt->SetDefault(settingInt->GetValue());
+
+ setting = settingInt;
+ }
+ else
+ {
+ auto settingString = std::make_shared<CSettingString>(settingId, GetSettingsManager());
+
+ if (settingLValues.empty())
+ {
+ StringSettingOptions options;
+ for (uint32_t i = 0; i < values.size(); ++i)
+ {
+ std::string value = values[i];
+ if (settingEntries.size() > i)
+ value = settingEntries[i];
+
+ options.push_back(StringSettingOption(value, value));
+ }
+
+ settingString->SetOptions(options);
+ }
+ else
+ {
+ TranslatableStringSettingOptions options;
+ for (uint32_t i = 0; i < values.size(); ++i)
+ {
+ int label = static_cast<int>(strtol(values[i].c_str(), nullptr, 0));
+ std::string value = g_localizeStrings.GetAddonString(m_addonId, label);
+ if (settingEntries.size() > i)
+ value = settingEntries[i];
+
+ options.push_back(std::make_pair(label, value));
+ }
+
+ settingString->SetTranslatableOptions(options);
+ }
+
+ if (sortAscending)
+ settingString->SetOptionsSort(SettingOptionsSort::Ascending);
+
+ // set the default value
+ settingString->SetDefault(defaultValue);
+
+ setting = settingString;
+ }
+
+ auto control = std::make_shared<CSettingControlSpinner>();
+ control->SetFormat("string");
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingFileEnum(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const std::string& settingValues)
+{
+ auto setting = InitializeFromOldSettingFileWithSource(settingId, settingElement, defaultValue,
+ settingValues);
+
+ auto control = std::make_shared<CSettingControlSpinner>();
+ control->SetFormat("string");
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingRangeOfNum(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue)
+{
+ auto setting = std::make_shared<CSettingNumber>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ // parse rangestart and rangeend
+ double rangeStart = 0.0, rangeEnd = 1.0;
+ settingElement->QueryDoubleAttribute("rangestart", &rangeStart);
+ settingElement->QueryDoubleAttribute("rangeend", &rangeEnd);
+ setting->SetMinimum(rangeStart);
+ setting->SetMaximum(rangeEnd);
+
+ // parse elements
+ uint32_t elements = 2;
+ settingElement->QueryUnsignedAttribute("elements", &elements);
+ if (elements > 1)
+ setting->SetStep((rangeEnd - rangeStart) / (elements - 1));
+
+ // parse valueformat
+ int valueFormat = -1;
+ settingElement->QueryIntAttribute("valueformat", &valueFormat);
+
+ auto control = std::make_shared<CSettingControlSpinner>();
+ control->SetFormat("string");
+ control->SetFormatLabel(valueFormat);
+ setting->SetControl(control);
+
+ return setting;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingSlider(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue)
+{
+ // parse range
+ double min = 0.0, max = 100.0, step = 1.0;
+ const auto range = StringUtils::Split(XMLUtils::GetAttribute(settingElement, "range"), ',');
+
+ if (range.size() > 1)
+ {
+ min = strtod(range[0].c_str(), nullptr);
+
+ if (range.size() > 2)
+ {
+ max = strtod(range[2].c_str(), nullptr);
+ step = strtod(range[1].c_str(), nullptr);
+ }
+ else
+ max = strtod(range[1].c_str(), nullptr);
+ }
+
+ // parse option
+ auto option = XMLUtils::GetAttribute(settingElement, "option");
+ if (option.empty() || StringUtils::EqualsNoCase(option, "float"))
+ {
+ auto setting = std::make_shared<CSettingNumber>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ setting->SetMinimum(min);
+ setting->SetStep(step);
+ setting->SetMaximum(max);
+
+ auto control = std::make_shared<CSettingControlSlider>();
+ control->SetFormat("number");
+ control->SetPopup(false);
+ setting->SetControl(control);
+
+ return setting;
+ }
+
+ if (StringUtils::EqualsNoCase(option, "int") || StringUtils::EqualsNoCase(option, "percent"))
+ {
+ auto setting = std::make_shared<CSettingInt>(settingId, GetSettingsManager());
+ if (setting->FromString(defaultValue))
+ setting->SetDefault(setting->GetValue());
+
+ setting->SetMinimum(static_cast<int>(min));
+ setting->SetStep(static_cast<int>(step));
+ setting->SetMaximum(static_cast<int>(max));
+
+ auto control = std::make_shared<CSettingControlSlider>();
+ control->SetFormat(StringUtils::EqualsNoCase(option, "int") ? "integer" : "percentage");
+ control->SetPopup(false);
+ setting->SetControl(control);
+
+ return setting;
+ }
+
+ m_logger->warn("ignoring old setting definition for \"{}\" of type \"slider\" because of unknown "
+ "option \"{}\"",
+ settingId, option);
+
+ return nullptr;
+}
+
+SettingPtr CAddonSettings::InitializeFromOldSettingFileWithSource(
+ const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ std::string source)
+{
+ auto setting = std::make_shared<CSettingPath>(settingId, GetSettingsManager());
+ setting->SetDefault(defaultValue);
+
+ if (source.find("$PROFILE") != std::string::npos)
+ StringUtils::Replace(source, "$PROFILE", m_addonProfile);
+ else
+ source = URIUtils::AddFileToFolder(m_addonPath, source);
+
+ setting->SetSources({source});
+
+ // process the path/file mask
+ setting->SetMasking(XMLUtils::GetAttribute(settingElement, "mask"));
+
+ // process option
+ std::string option = XMLUtils::GetAttribute(settingElement, "option");
+ setting->SetHideExtension(StringUtils::EqualsNoCase(option, "hideext"));
+
+ setting->SetOptionsFiller(FileEnumSettingOptionsFiller);
+
+ return setting;
+}
+
+bool CAddonSettings::LoadOldSettingValues(const CXBMCTinyXML& doc,
+ std::map<std::string, std::string>& settings) const
+{
+ if (!doc.RootElement())
+ return false;
+
+ const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
+ if (category == nullptr)
+ category = doc.RootElement();
+
+ while (category != nullptr)
+ {
+ const TiXmlElement* setting = category->FirstChildElement("setting");
+ while (setting != nullptr)
+ {
+ const char* id = setting->Attribute("id");
+ const char* value = setting->Attribute("value");
+ if (id != nullptr && value != nullptr)
+ settings[id] = value;
+
+ setting = setting->NextSiblingElement("setting");
+ }
+
+ category = category->NextSiblingElement("category");
+ }
+
+ return !settings.empty();
+}
+
+bool CAddonSettings::ParseOldLabel(const TiXmlElement* element,
+ const std::string& settingId,
+ int& labelId)
+{
+ labelId = -1;
+ if (element == nullptr)
+ return false;
+
+ // label value as a string
+ std::string labelString;
+ element->QueryStringAttribute("label", &labelString);
+
+ bool parsed = !labelString.empty();
+
+ // try to parse the label as a pure number, i.e. a localized string
+ if (parsed)
+ {
+ char* endptr;
+ labelId = std::strtol(labelString.c_str(), &endptr, 10);
+ if (endptr == nullptr || *endptr == '\0')
+ return true;
+ }
+ // make sure the label string is not empty
+ else
+ labelString = " ";
+
+ labelId = m_unknownSettingLabelId;
+ m_unknownSettingLabelId += 1;
+ m_unknownSettingLabels.emplace(labelId, labelString);
+
+ return parsed;
+}
+
+bool CAddonSettings::ParseOldCondition(const std::shared_ptr<const CSetting>& setting,
+ const std::vector<std::shared_ptr<const CSetting>>& settings,
+ const std::string& condition,
+ CSettingDependency& dependeny) const
+{
+ if (setting == nullptr)
+ return false;
+
+ if (condition.empty())
+ return true;
+
+ // find the index of the setting in the list of all settings of the category
+ auto settingIt = std::find_if(settings.cbegin(), settings.cend(),
+ [setting](const SettingConstPtr& otherSetting) {
+ if (otherSetting == nullptr)
+ return false;
+
+ return setting->GetId() == otherSetting->GetId();
+ });
+ if (settingIt == settings.cend())
+ {
+ m_logger->warn("failed to parse old setting conditions \"{}\" for \"{}\"", condition,
+ setting->GetId());
+ return false;
+ }
+ int32_t currentSettingIndex = std::distance(settings.cbegin(), settingIt);
+
+ CSettingDependencyConditionCombinationPtr dependencyCombination;
+ std::vector<std::string> conditions;
+ if (condition.find('+') != std::string::npos)
+ {
+ StringUtils::Tokenize(condition, conditions, '+');
+ dependencyCombination = dependeny.And();
+ }
+ else
+ {
+ StringUtils::Tokenize(condition, conditions, '|');
+ dependencyCombination = dependeny.Or();
+ }
+
+ bool error = false;
+ for (const auto& cond : conditions)
+ {
+ ConditionExpression expression;
+ if (!ParseOldConditionExpression(cond, expression))
+ continue;
+
+ // determine the absolute setting index
+ int32_t absoluteSettingIndex = currentSettingIndex + expression.m_relativeSettingIndex;
+
+ // we cannot handle relative indices pointing to settings not belonging to the same category
+ if (absoluteSettingIndex < 0 || static_cast<size_t>(absoluteSettingIndex) >= settings.size())
+ {
+ m_logger->warn("cannot reference setting (relative index: {}; absolute index: {}) in another "
+ "category in old setting condition \"{}\" for \"{}\"",
+ expression.m_relativeSettingIndex, absoluteSettingIndex, cond,
+ setting->GetId());
+ error = true;
+ continue;
+ }
+
+ const SettingConstPtr& referencedSetting = settings.at(absoluteSettingIndex);
+ if (referencedSetting == nullptr)
+ {
+ m_logger->warn(
+ "cannot reference separator setting in old setting condition \"{}\" for \"{}\"", cond,
+ setting->GetId());
+ error = true;
+ continue;
+ }
+
+ // try to handle some odd cases where the setting is of type string but the comparison value references the index of the value in the list of options
+ if (referencedSetting->GetType() == SettingType::String &&
+ StringUtils::IsNaturalNumber(expression.m_value))
+ {
+ // try to parse the comparison value
+ size_t valueIndex = static_cast<size_t>(strtoul(expression.m_value.c_str(), nullptr, 10));
+
+ const auto referencedSettingString =
+ std::static_pointer_cast<const CSettingString>(referencedSetting);
+ switch (referencedSettingString->GetOptionsType())
+ {
+ case SettingOptionsType::Static:
+ {
+ const auto& options = referencedSettingString->GetOptions();
+ if (options.size() > valueIndex)
+ expression.m_value = options.at(valueIndex).value;
+ break;
+ }
+
+ case SettingOptionsType::StaticTranslatable:
+ {
+ const auto& options = referencedSettingString->GetTranslatableOptions();
+ if (options.size() > valueIndex)
+ expression.m_value = options.at(valueIndex).second;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ // add the condition to the value of the referenced setting
+ dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>(
+ referencedSetting->GetId(), expression.m_value, expression.m_operator, expression.m_negated,
+ GetSettingsManager()));
+ }
+
+ // if the condition doesn't depend on other settings it might be an infobool expression
+ if (!error && dependencyCombination->GetOperations().empty() &&
+ dependencyCombination->GetValues().empty())
+ dependencyCombination->Add(std::make_shared<CSettingDependencyCondition>(
+ "InfoBool", condition, "", false, GetSettingsManager()));
+
+ return !error;
+}
+
+bool CAddonSettings::ParseOldConditionExpression(std::string str, ConditionExpression& expression)
+{
+ StringUtils::Trim(str);
+
+ size_t posOpen = str.find('(');
+ size_t posSep = str.find(',', posOpen);
+ size_t posClose = str.find(')', posSep);
+
+ if (posOpen == std::string::npos || posSep == std::string::npos || posClose == std::string::npos)
+ return false;
+
+ auto op = str.substr(0, posOpen);
+
+ // check if the operator is negated
+ expression.m_negated = StringUtils::StartsWith(op, "!");
+ if (expression.m_negated)
+ op = op.substr(1);
+
+ // parse the operator
+ if (StringUtils::EqualsNoCase(op, "eq"))
+ expression.m_operator = SettingDependencyOperator::Equals;
+ else if (StringUtils::EqualsNoCase(op, "gt"))
+ expression.m_operator = SettingDependencyOperator::GreaterThan;
+ else if (StringUtils::EqualsNoCase(op, "lt"))
+ expression.m_operator = SettingDependencyOperator::LessThan;
+ else
+ return false;
+
+ expression.m_relativeSettingIndex = static_cast<int32_t>(
+ strtol(str.substr(posOpen + 1, posSep - posOpen - 1).c_str(), nullptr, 10));
+ expression.m_value = str.substr(posSep + 1, posClose - posSep - 1);
+
+ return true;
+}
+
+void CAddonSettings::FileEnumSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ if (setting == nullptr)
+ return;
+
+ auto settingPath = std::dynamic_pointer_cast<const CSettingPath>(setting);
+ if (settingPath == nullptr)
+ return;
+
+ if (settingPath->GetSources().empty())
+ return;
+
+ const std::string& masking = settingPath->GetMasking(CServiceBroker::GetFileExtensionProvider());
+
+ // fetch the matching files/directories
+ CFileItemList items;
+ XFILE::CDirectory::GetDirectory(settingPath->GetSources().front(), items, masking,
+ XFILE::DIR_FLAG_NO_FILE_DIRS);
+
+ // process the matching files/directories
+ for (const auto& item : items)
+ {
+ if ((masking == "/" && item->m_bIsFolder) || !item->m_bIsFolder)
+ {
+ if (settingPath->HideExtension())
+ item->RemoveExtension();
+ list.emplace_back(item->GetLabel(), item->GetLabel());
+ }
+ }
+}
+
+} // namespace ADDON
diff --git a/xbmc/addons/settings/AddonSettings.h b/xbmc/addons/settings/AddonSettings.h
new file mode 100644
index 0000000..811ded1
--- /dev/null
+++ b/xbmc/addons/settings/AddonSettings.h
@@ -0,0 +1,203 @@
+/*
+ * 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
+
+#include "addons/IAddon.h"
+#include "settings/SettingControl.h"
+#include "settings/SettingCreator.h"
+#include "settings/SettingsBase.h"
+#include "settings/lib/ISettingCallback.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+enum class SettingDependencyOperator;
+
+class CSettingCategory;
+class CSettingGroup;
+class CSettingDependency;
+class CXBMCTinyXML;
+
+struct StringSettingOption;
+
+namespace ADDON
+{
+
+class IAddon;
+class IAddonInstanceHandler;
+
+class CAddonSettings : public CSettingControlCreator,
+ public CSettingCreator,
+ public CSettingsBase,
+ public ISettingCallback
+{
+public:
+ CAddonSettings(const std::shared_ptr<const IAddon>& addon, AddonInstanceId instanceId);
+ ~CAddonSettings() override = default;
+
+ // specialization of CSettingsBase
+ bool Initialize() override { return false; }
+
+ // implementations of CSettingsBase
+ bool Load() override { return false; }
+ bool Save() override { return false; }
+
+ // specialization of CSettingCreator
+ std::shared_ptr<CSetting> CreateSetting(
+ const std::string& settingType,
+ const std::string& settingId,
+ CSettingsManager* settingsManager = nullptr) const override;
+
+ // implementation of ISettingCallback
+ void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override;
+
+ const std::string& GetAddonId() const { return m_addonId; }
+
+ bool Initialize(const CXBMCTinyXML& doc, bool allowEmpty = false);
+ bool Load(const CXBMCTinyXML& doc);
+ bool Save(CXBMCTinyXML& doc) const;
+
+ bool HasSettings() const;
+
+ std::string GetSettingLabel(int label) const;
+
+ std::shared_ptr<CSetting> AddSetting(const std::string& settingId, bool value);
+ std::shared_ptr<CSetting> AddSetting(const std::string& settingId, int value);
+ std::shared_ptr<CSetting> AddSetting(const std::string& settingId, double value);
+ std::shared_ptr<CSetting> AddSetting(const std::string& settingId, const std::string& value);
+
+protected:
+ // specializations of CSettingsBase
+ void InitializeSettingTypes() override;
+ void InitializeControls() override;
+ void InitializeConditions() override;
+
+ // implementation of CSettingsBase
+ bool InitializeDefinitions() override { return false; }
+
+private:
+ bool AddInstanceSettings();
+ bool InitializeDefinitions(const CXBMCTinyXML& doc);
+
+ bool ParseSettingVersion(const CXBMCTinyXML& doc, uint32_t& version) const;
+
+ std::shared_ptr<CSettingGroup> ParseOldSettingElement(
+ const TiXmlElement* categoryElement,
+ const std::shared_ptr<CSettingCategory>& category,
+ std::set<std::string>& settingIds);
+
+ std::shared_ptr<CSettingCategory> ParseOldCategoryElement(uint32_t& categoryId,
+ const TiXmlElement* categoryElement,
+ std::set<std::string>& settingIds);
+
+ bool InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc);
+ std::shared_ptr<CSetting> InitializeFromOldSettingAction(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue);
+ std::shared_ptr<CSetting> InitializeFromOldSettingLabel();
+ std::shared_ptr<CSetting> InitializeFromOldSettingBool(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue);
+ std::shared_ptr<CSetting> InitializeFromOldSettingTextIpAddress(
+ const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingNumber(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingPath(const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingDate(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingTime(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingSelect(
+ const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel,
+ const std::string& settingValues,
+ const std::vector<std::string>& settingLValues);
+ std::shared_ptr<CSetting> InitializeFromOldSettingAddon(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const int settingLabel);
+ std::shared_ptr<CSetting> InitializeFromOldSettingEnums(
+ const std::string& settingId,
+ const std::string& settingType,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const std::string& settingValues,
+ const std::vector<std::string>& settingLValues);
+ std::shared_ptr<CSetting> InitializeFromOldSettingFileEnum(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ const std::string& settingValues);
+ std::shared_ptr<CSetting> InitializeFromOldSettingRangeOfNum(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue);
+ std::shared_ptr<CSetting> InitializeFromOldSettingSlider(const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue);
+ std::shared_ptr<CSetting> InitializeFromOldSettingFileWithSource(
+ const std::string& settingId,
+ const TiXmlElement* settingElement,
+ const std::string& defaultValue,
+ std::string source);
+
+ bool LoadOldSettingValues(const CXBMCTinyXML& doc,
+ std::map<std::string, std::string>& settings) const;
+
+ struct ConditionExpression
+ {
+ SettingDependencyOperator m_operator;
+ bool m_negated;
+ int32_t m_relativeSettingIndex;
+ std::string m_value;
+ };
+
+ bool ParseOldLabel(const TiXmlElement* element, const std::string& settingId, int& labelId);
+ bool ParseOldCondition(const std::shared_ptr<const CSetting>& setting,
+ const std::vector<std::shared_ptr<const CSetting>>& settings,
+ const std::string& condition,
+ CSettingDependency& dependeny) const;
+ static bool ParseOldConditionExpression(std::string str, ConditionExpression& expression);
+
+ static void FileEnumSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+
+ // store these values so that we don't always have to access the weak pointer
+ const std::string m_addonId;
+ const std::string m_addonPath;
+ const std::string m_addonProfile;
+ const AddonInstanceId m_instanceId{ADDON_SETTINGS_ID};
+
+ uint32_t m_unidentifiedSettingId;
+ int m_unknownSettingLabelId;
+ std::map<int, std::string> m_unknownSettingLabels;
+
+ Logger m_logger;
+};
+
+} // namespace ADDON
diff --git a/xbmc/addons/settings/CMakeLists.txt b/xbmc/addons/settings/CMakeLists.txt
new file mode 100644
index 0000000..6774f68
--- /dev/null
+++ b/xbmc/addons/settings/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES AddonSettings.cpp
+ SettingUrlEncodedString.cpp)
+
+set(HEADERS AddonSettings.h
+ SettingUrlEncodedString.h)
+
+core_add_library(addons_settings)
diff --git a/xbmc/addons/settings/SettingUrlEncodedString.cpp b/xbmc/addons/settings/SettingUrlEncodedString.cpp
new file mode 100644
index 0000000..f09875f
--- /dev/null
+++ b/xbmc/addons/settings/SettingUrlEncodedString.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#include "SettingUrlEncodedString.h"
+
+#include "URL.h"
+#include "settings/lib/SettingsManager.h"
+
+namespace ADDON
+{
+
+CSettingUrlEncodedString::CSettingUrlEncodedString(
+ const std::string& id, CSettingsManager* settingsManager /* = nullptr */)
+ : CSettingString(id, settingsManager)
+{ }
+
+CSettingUrlEncodedString::CSettingUrlEncodedString(
+ const std::string& id,
+ int label,
+ const std::string& value,
+ CSettingsManager* settingsManager /* = nullptr */)
+ : CSettingString(id, label, value, settingsManager)
+{ }
+
+CSettingUrlEncodedString::CSettingUrlEncodedString(const std::string &id, const CSettingUrlEncodedString &setting)
+ : CSettingString(id, setting)
+{ }
+
+std::string CSettingUrlEncodedString::GetDecodedValue() const
+{
+ return CURL::Decode(CSettingString::GetValue());
+}
+
+bool CSettingUrlEncodedString::SetDecodedValue(const std::string &decodedValue)
+{
+ return CSettingString::SetValue(CURL::Encode(decodedValue));
+}
+
+} /* namespace ADDON */
diff --git a/xbmc/addons/settings/SettingUrlEncodedString.h b/xbmc/addons/settings/SettingUrlEncodedString.h
new file mode 100644
index 0000000..80bad4d
--- /dev/null
+++ b/xbmc/addons/settings/SettingUrlEncodedString.h
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+#include "settings/lib/Setting.h"
+
+class CSettingsManager;
+
+namespace ADDON
+{
+ class CSettingUrlEncodedString : public CSettingString
+ {
+ public:
+ CSettingUrlEncodedString(const std::string& id, CSettingsManager* settingsManager = nullptr);
+ CSettingUrlEncodedString(const std::string& id,
+ int label,
+ const std::string& value,
+ CSettingsManager* settingsManager = nullptr);
+ CSettingUrlEncodedString(const std::string &id, const CSettingUrlEncodedString &setting);
+ ~CSettingUrlEncodedString() override = default;
+
+ SettingPtr Clone(const std::string &id) const override { return std::make_shared<CSettingUrlEncodedString>(id, *this); }
+
+ std::string GetDecodedValue() const;
+ bool SetDecodedValue(const std::string& decodedValue);
+ };
+}