summaryrefslogtreecommitdiffstats
path: root/xbmc/settings/windows
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/settings/windows')
-rw-r--r--xbmc/settings/windows/CMakeLists.txt11
-rw-r--r--xbmc/settings/windows/GUIControlSettings.cpp1798
-rw-r--r--xbmc/settings/windows/GUIControlSettings.h366
-rw-r--r--xbmc/settings/windows/GUIWindowSettings.cpp19
-rw-r--r--xbmc/settings/windows/GUIWindowSettings.h19
-rw-r--r--xbmc/settings/windows/GUIWindowSettingsCategory.cpp244
-rw-r--r--xbmc/settings/windows/GUIWindowSettingsCategory.h51
-rw-r--r--xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp630
-rw-r--r--xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h47
9 files changed, 3185 insertions, 0 deletions
diff --git a/xbmc/settings/windows/CMakeLists.txt b/xbmc/settings/windows/CMakeLists.txt
new file mode 100644
index 0000000..c1b69ac
--- /dev/null
+++ b/xbmc/settings/windows/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES GUIControlSettings.cpp
+ GUIWindowSettings.cpp
+ GUIWindowSettingsCategory.cpp
+ GUIWindowSettingsScreenCalibration.cpp)
+
+set(HEADERS GUIControlSettings.h
+ GUIWindowSettings.h
+ GUIWindowSettingsCategory.h
+ GUIWindowSettingsScreenCalibration.h)
+
+core_add_library(settings_windows)
diff --git a/xbmc/settings/windows/GUIControlSettings.cpp b/xbmc/settings/windows/GUIControlSettings.cpp
new file mode 100644
index 0000000..fcbf1fa
--- /dev/null
+++ b/xbmc/settings/windows/GUIControlSettings.cpp
@@ -0,0 +1,1798 @@
+/*
+ * Copyright (C) 2005-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 "GUIControlSettings.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "addons/AddonManager.h"
+#include "addons/gui/GUIWindowAddonBrowser.h"
+#include "addons/settings/SettingUrlEncodedString.h"
+#include "dialogs/GUIDialogColorPicker.h"
+#include "dialogs/GUIDialogFileBrowser.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogSlider.h"
+#include "guilib/GUIColorButtonControl.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIEditControl.h"
+#include "guilib/GUIImage.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUILabelControl.h"
+#include "guilib/GUIRadioButtonControl.h"
+#include "guilib/GUISettingsSliderControl.h"
+#include "guilib/GUISpinControlEx.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/MediaSourceSettings.h"
+#include "settings/SettingAddon.h"
+#include "settings/SettingControl.h"
+#include "settings/SettingDateTime.h"
+#include "settings/SettingPath.h"
+#include "settings/SettingUtils.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "storage/MediaManager.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <set>
+#include <utility>
+
+using namespace ADDON;
+
+static std::string Localize(std::uint32_t code,
+ ILocalizer* localizer,
+ const std::string& addonId = "")
+{
+ if (localizer == nullptr)
+ return "";
+
+ if (!addonId.empty())
+ {
+ std::string label = g_localizeStrings.GetAddonString(addonId, code);
+ if (!label.empty())
+ return label;
+ }
+
+ return localizer->Localize(code);
+}
+
+template<typename TValueType>
+static CFileItemPtr GetFileItem(const std::string& label,
+ const std::string& label2,
+ const TValueType& value,
+ const std::vector<std::pair<std::string, CVariant>>& properties,
+ const std::set<TValueType>& selectedValues)
+{
+ CFileItemPtr item(new CFileItem(label));
+ item->SetProperty("value", value);
+ item->SetLabel2(label2);
+
+ for (const auto& prop : properties)
+ item->SetProperty(prop.first, prop.second);
+
+ if (selectedValues.find(value) != selectedValues.end())
+ item->Select(true);
+
+ return item;
+}
+
+template<class SettingOption>
+static bool CompareSettingOptionAseconding(const SettingOption& lhs, const SettingOption& rhs)
+{
+ return StringUtils::CompareNoCase(lhs.label, rhs.label) < 0;
+}
+
+template<class SettingOption>
+static bool CompareSettingOptionDeseconding(const SettingOption& lhs, const SettingOption& rhs)
+{
+ return StringUtils::CompareNoCase(lhs.label, rhs.label) > 0;
+}
+
+static bool GetIntegerOptions(const SettingConstPtr& setting,
+ IntegerSettingOptions& options,
+ std::set<int>& selectedOptions,
+ ILocalizer* localizer,
+ bool updateOptions)
+{
+ std::shared_ptr<const CSettingInt> pSettingInt = NULL;
+ if (setting->GetType() == SettingType::Integer)
+ pSettingInt = std::static_pointer_cast<const CSettingInt>(setting);
+ else if (setting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<const CSettingList> settingList =
+ std::static_pointer_cast<const CSettingList>(setting);
+ if (settingList->GetElementType() != SettingType::Integer)
+ return false;
+
+ pSettingInt = std::static_pointer_cast<const CSettingInt>(settingList->GetDefinition());
+ }
+
+ switch (pSettingInt->GetOptionsType())
+ {
+ case SettingOptionsType::StaticTranslatable:
+ {
+ const TranslatableIntegerSettingOptions& settingOptions =
+ pSettingInt->GetTranslatableOptions();
+ for (const auto& option : settingOptions)
+ options.push_back(
+ IntegerSettingOption(Localize(option.label, localizer, option.addonId), option.value));
+ break;
+ }
+
+ case SettingOptionsType::Static:
+ {
+ const IntegerSettingOptions& settingOptions = pSettingInt->GetOptions();
+ options.insert(options.end(), settingOptions.begin(), settingOptions.end());
+ break;
+ }
+
+ case SettingOptionsType::Dynamic:
+ {
+ IntegerSettingOptions settingOptions;
+ if (updateOptions)
+ settingOptions = std::const_pointer_cast<CSettingInt>(pSettingInt)->UpdateDynamicOptions();
+ else
+ settingOptions = pSettingInt->GetDynamicOptions();
+ options.insert(options.end(), settingOptions.begin(), settingOptions.end());
+ break;
+ }
+
+ case SettingOptionsType::Unknown:
+ default:
+ {
+ std::shared_ptr<const CSettingControlFormattedRange> control =
+ std::static_pointer_cast<const CSettingControlFormattedRange>(pSettingInt->GetControl());
+ for (int i = pSettingInt->GetMinimum(); i <= pSettingInt->GetMaximum();
+ i += pSettingInt->GetStep())
+ {
+ std::string strLabel;
+ if (i == pSettingInt->GetMinimum() && control->GetMinimumLabel() > -1)
+ strLabel = Localize(control->GetMinimumLabel(), localizer);
+ else if (control->GetFormatLabel() > -1)
+ strLabel = StringUtils::Format(Localize(control->GetFormatLabel(), localizer), i);
+ else
+ strLabel = StringUtils::Format(control->GetFormatString(), i);
+
+ options.push_back(IntegerSettingOption(strLabel, i));
+ }
+
+ break;
+ }
+ }
+
+ switch (pSettingInt->GetOptionsSort())
+ {
+ case SettingOptionsSort::Ascending:
+ std::sort(options.begin(), options.end(),
+ CompareSettingOptionAseconding<IntegerSettingOption>);
+ break;
+
+ case SettingOptionsSort::Descending:
+ std::sort(options.begin(), options.end(),
+ CompareSettingOptionDeseconding<IntegerSettingOption>);
+ break;
+
+ case SettingOptionsSort::NoSorting:
+ default:
+ break;
+ }
+
+ // this must be done after potentially calling CSettingInt::UpdateDynamicOptions() because it can
+ // change the value of the setting
+ if (setting->GetType() == SettingType::Integer)
+ selectedOptions.insert(pSettingInt->GetValue());
+ else if (setting->GetType() == SettingType::List)
+ {
+ std::vector<CVariant> list =
+ CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting));
+ for (const auto& itValue : list)
+ selectedOptions.insert((int)itValue.asInteger());
+ }
+ else
+ return false;
+
+ return true;
+}
+
+static bool GetStringOptions(const SettingConstPtr& setting,
+ StringSettingOptions& options,
+ std::set<std::string>& selectedOptions,
+ ILocalizer* localizer,
+ bool updateOptions)
+{
+ std::shared_ptr<const CSettingString> pSettingString = NULL;
+ if (setting->GetType() == SettingType::String)
+ pSettingString = std::static_pointer_cast<const CSettingString>(setting);
+ else if (setting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<const CSettingList> settingList =
+ std::static_pointer_cast<const CSettingList>(setting);
+ if (settingList->GetElementType() != SettingType::String)
+ return false;
+
+ pSettingString = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition());
+ }
+
+ switch (pSettingString->GetOptionsType())
+ {
+ case SettingOptionsType::StaticTranslatable:
+ {
+ const TranslatableStringSettingOptions& settingOptions =
+ pSettingString->GetTranslatableOptions();
+ for (const auto& option : settingOptions)
+ options.push_back(StringSettingOption(Localize(option.first, localizer), option.second));
+ break;
+ }
+
+ case SettingOptionsType::Static:
+ {
+ const StringSettingOptions& settingOptions = pSettingString->GetOptions();
+ options.insert(options.end(), settingOptions.begin(), settingOptions.end());
+ break;
+ }
+
+ case SettingOptionsType::Dynamic:
+ {
+ StringSettingOptions settingOptions;
+ if (updateOptions)
+ settingOptions =
+ std::const_pointer_cast<CSettingString>(pSettingString)->UpdateDynamicOptions();
+ else
+ settingOptions = pSettingString->GetDynamicOptions();
+ options.insert(options.end(), settingOptions.begin(), settingOptions.end());
+ break;
+ }
+
+ case SettingOptionsType::Unknown:
+ default:
+ return false;
+ }
+
+ switch (pSettingString->GetOptionsSort())
+ {
+ case SettingOptionsSort::Ascending:
+ std::sort(options.begin(), options.end(),
+ CompareSettingOptionAseconding<StringSettingOption>);
+ break;
+
+ case SettingOptionsSort::Descending:
+ std::sort(options.begin(), options.end(),
+ CompareSettingOptionDeseconding<StringSettingOption>);
+ break;
+
+ case SettingOptionsSort::NoSorting:
+ default:
+ break;
+ }
+
+ // this must be done after potentially calling CSettingString::UpdateDynamicOptions() because it
+ // can change the value of the setting
+ if (setting->GetType() == SettingType::String)
+ selectedOptions.insert(pSettingString->GetValue());
+ else if (setting->GetType() == SettingType::List)
+ {
+ std::vector<CVariant> list =
+ CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting));
+ for (const auto& itValue : list)
+ selectedOptions.insert(itValue.asString());
+ }
+ else
+ return false;
+
+ return true;
+}
+
+CGUIControlBaseSetting::CGUIControlBaseSetting(int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : m_id(id),
+ m_pSetting(std::move(pSetting)),
+ m_localizer(localizer),
+ m_delayed(false),
+ m_valid(true)
+{
+}
+
+bool CGUIControlBaseSetting::IsEnabled() const
+{
+ return m_pSetting != NULL && m_pSetting->IsEnabled();
+}
+
+void CGUIControlBaseSetting::UpdateFromControl()
+{
+ Update(true, true);
+}
+
+void CGUIControlBaseSetting::UpdateFromSetting(bool updateDisplayOnly /* = false */)
+{
+ Update(false, updateDisplayOnly);
+}
+
+std::string CGUIControlBaseSetting::Localize(std::uint32_t code) const
+{
+ return ::Localize(code, m_localizer);
+}
+
+void CGUIControlBaseSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || updateDisplayOnly)
+ return;
+
+ CGUIControl* control = GetControl();
+ if (control == NULL)
+ return;
+
+ control->SetEnabled(IsEnabled());
+ if (m_pSetting)
+ control->SetVisible(m_pSetting->IsVisible());
+ SetValid(true);
+}
+
+CGUIControlRadioButtonSetting::CGUIControlRadioButtonSetting(CGUIRadioButtonControl* pRadioButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pRadioButton = pRadioButton;
+ if (m_pRadioButton == NULL)
+ return;
+
+ m_pRadioButton->SetID(id);
+}
+
+CGUIControlRadioButtonSetting::~CGUIControlRadioButtonSetting() = default;
+
+bool CGUIControlRadioButtonSetting::OnClick()
+{
+ SetValid(std::static_pointer_cast<CSettingBool>(m_pSetting)
+ ->SetValue(!std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue()));
+ return IsValid();
+}
+
+void CGUIControlRadioButtonSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || m_pRadioButton == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ m_pRadioButton->SetSelected(std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue());
+}
+
+CGUIControlColorButtonSetting::CGUIControlColorButtonSetting(
+ CGUIColorButtonControl* pColorControl,
+ int id,
+ const std::shared_ptr<CSetting>& pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, pSetting, localizer)
+{
+ m_pColorButton = pColorControl;
+ if (!m_pColorButton)
+ return;
+
+ m_pColorButton->SetID(id);
+}
+
+CGUIControlColorButtonSetting::~CGUIControlColorButtonSetting() = default;
+
+bool CGUIControlColorButtonSetting::OnClick()
+{
+ if (!m_pColorButton)
+ return false;
+
+ std::shared_ptr<CSettingString> settingHexColor =
+ std::static_pointer_cast<CSettingString>(m_pSetting);
+
+ CGUIDialogColorPicker* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogColorPicker>(
+ WINDOW_DIALOG_COLOR_PICKER);
+ if (!dialog)
+ return false;
+
+ dialog->Reset();
+ dialog->SetHeading(CVariant{Localize(m_pSetting->GetLabel())});
+ dialog->LoadColors();
+ std::string hexColor;
+ if (settingHexColor)
+ hexColor = settingHexColor.get()->GetValue();
+ dialog->SetSelectedColor(hexColor);
+ dialog->Open();
+
+ if (!dialog->IsConfirmed())
+ return false;
+
+ SetValid(
+ std::static_pointer_cast<CSettingString>(m_pSetting)->SetValue(dialog->GetSelectedColor()));
+ return IsValid();
+}
+
+void CGUIControlColorButtonSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || !m_pColorButton)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+ // Set the color to apply to the preview color box
+ m_pColorButton->SetImageBoxColor(
+ std::static_pointer_cast<CSettingString>(m_pSetting)->GetValue());
+}
+
+CGUIControlSpinExSetting::CGUIControlSpinExSetting(CGUISpinControlEx* pSpin,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pSpin = pSpin;
+ if (m_pSpin == NULL)
+ return;
+
+ m_pSpin->SetID(id);
+
+ const std::string& controlFormat = m_pSetting->GetControl()->GetFormat();
+ if (controlFormat == "number")
+ {
+ std::shared_ptr<CSettingNumber> pSettingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ m_pSpin->SetType(SPIN_CONTROL_TYPE_FLOAT);
+ m_pSpin->SetFloatRange(static_cast<float>(pSettingNumber->GetMinimum()),
+ static_cast<float>(pSettingNumber->GetMaximum()));
+ m_pSpin->SetFloatInterval(static_cast<float>(pSettingNumber->GetStep()));
+ }
+ else if (controlFormat == "integer")
+ m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT);
+ else if (controlFormat == "string")
+ {
+ m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT);
+
+ if (m_pSetting->GetType() == SettingType::Integer)
+ FillIntegerSettingControl(false);
+ else if (m_pSetting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> pSettingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ std::shared_ptr<const CSettingControlFormattedRange> control =
+ std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl());
+ int index = 0;
+ for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum();
+ value += pSettingNumber->GetStep(), index++)
+ {
+ std::string strLabel;
+ if (value == pSettingNumber->GetMinimum() && control->GetMinimumLabel() > -1)
+ strLabel = Localize(control->GetMinimumLabel());
+ else if (control->GetFormatLabel() > -1)
+ strLabel = StringUtils::Format(Localize(control->GetFormatLabel()), value);
+ else
+ strLabel = StringUtils::Format(control->GetFormatString(), value);
+
+ m_pSpin->AddLabel(strLabel, index);
+ }
+ }
+ }
+}
+
+CGUIControlSpinExSetting::~CGUIControlSpinExSetting() = default;
+
+bool CGUIControlSpinExSetting::OnClick()
+{
+ if (m_pSpin == NULL)
+ return false;
+
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ SetValid(std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSpin->GetValue()));
+ break;
+
+ case SettingType::Number:
+ {
+ auto pSettingNumber = std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ const auto& controlFormat = m_pSetting->GetControl()->GetFormat();
+ if (controlFormat == "number")
+ SetValid(pSettingNumber->SetValue(static_cast<double>(m_pSpin->GetFloatValue())));
+ else
+ SetValid(pSettingNumber->SetValue(pSettingNumber->GetMinimum() +
+ pSettingNumber->GetStep() * m_pSpin->GetValue()));
+
+ break;
+ }
+
+ case SettingType::String:
+ SetValid(std::static_pointer_cast<CSettingString>(m_pSetting)
+ ->SetValue(m_pSpin->GetStringValue()));
+ break;
+
+ default:
+ return false;
+ }
+
+ return IsValid();
+}
+
+void CGUIControlSpinExSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || m_pSpin == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ FillControl(!updateDisplayOnly);
+
+ if (!updateDisplayOnly)
+ {
+ // disable the spinner if it has less than two items
+ if (!m_pSpin->IsDisabled() && (m_pSpin->GetMaximum() - m_pSpin->GetMinimum()) == 0)
+ m_pSpin->SetEnabled(false);
+ }
+}
+
+void CGUIControlSpinExSetting::FillControl(bool updateValues)
+{
+ if (m_pSpin == NULL)
+ return;
+
+ if (updateValues)
+ m_pSpin->Clear();
+
+ const std::string& controlFormat = m_pSetting->GetControl()->GetFormat();
+ if (controlFormat == "number")
+ {
+ std::shared_ptr<CSettingNumber> pSettingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ m_pSpin->SetFloatValue((float)pSettingNumber->GetValue());
+ }
+ else if (controlFormat == "integer")
+ FillIntegerSettingControl(updateValues);
+ else if (controlFormat == "string")
+ {
+ if (m_pSetting->GetType() == SettingType::Integer)
+ FillIntegerSettingControl(updateValues);
+ else if (m_pSetting->GetType() == SettingType::Number)
+ FillFloatSettingControl();
+ else if (m_pSetting->GetType() == SettingType::String)
+ FillStringSettingControl(updateValues);
+ }
+}
+
+void CGUIControlSpinExSetting::FillIntegerSettingControl(bool updateValues)
+{
+ IntegerSettingOptions options;
+ std::set<int> selectedValues;
+ // get the integer options
+ if (!GetIntegerOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) ||
+ selectedValues.size() != 1)
+ return;
+
+ if (updateValues)
+ {
+ // add them to the spinner
+ for (const auto& option : options)
+ m_pSpin->AddLabel(option.label, option.value);
+ }
+
+ // and set the current value
+ m_pSpin->SetValue(*selectedValues.begin());
+}
+
+void CGUIControlSpinExSetting::FillFloatSettingControl()
+{
+ std::shared_ptr<CSettingNumber> pSettingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ std::shared_ptr<const CSettingControlFormattedRange> control =
+ std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl());
+ int index = 0;
+ int currentIndex = 0;
+ for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum();
+ value += pSettingNumber->GetStep(), index++)
+ {
+ if (value == pSettingNumber->GetValue())
+ {
+ currentIndex = index;
+ break;
+ }
+ }
+
+ m_pSpin->SetValue(currentIndex);
+}
+
+void CGUIControlSpinExSetting::FillStringSettingControl(bool updateValues)
+{
+ StringSettingOptions options;
+ std::set<std::string> selectedValues;
+ // get the string options
+ if (!GetStringOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) ||
+ selectedValues.size() != 1)
+ return;
+
+ if (updateValues)
+ {
+ // add them to the spinner
+ for (const auto& option : options)
+ m_pSpin->AddLabel(option.label, option.value);
+ }
+
+ // and set the current value
+ m_pSpin->SetStringValue(*selectedValues.begin());
+}
+
+CGUIControlListSetting::CGUIControlListSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pButton = pButton;
+ if (m_pButton == NULL)
+ return;
+
+ m_pButton->SetID(id);
+}
+
+CGUIControlListSetting::~CGUIControlListSetting() = default;
+
+bool CGUIControlListSetting::OnClick()
+{
+ if (m_pButton == NULL)
+ return false;
+
+ CGUIDialogSelect* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (dialog == NULL)
+ return false;
+
+ CFileItemList options;
+ std::shared_ptr<const CSettingControlList> control =
+ std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl());
+ bool optionsValid = GetItems(m_pSetting, options, false);
+
+ bool bValueAdded = false;
+ bool bAllowNewOption = false;
+ if (m_pSetting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<const CSettingList> settingList =
+ std::static_pointer_cast<const CSettingList>(m_pSetting);
+ if (settingList->GetElementType() == SettingType::String)
+ {
+ bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition())
+ ->AllowNewOption();
+ }
+ }
+ if (!bAllowNewOption)
+ {
+ // Do not show dialog if
+ // there are no items to be chosen
+ if (!optionsValid || options.Size() <= 0)
+ return false;
+
+ dialog->Reset();
+ dialog->SetHeading(CVariant{Localize(m_pSetting->GetLabel())});
+ dialog->SetItems(options);
+ dialog->SetMultiSelection(control->CanMultiSelect());
+ dialog->SetUseDetails(control->UseDetails());
+ dialog->Open();
+
+ if (!dialog->IsConfirmed())
+ return false;
+ }
+ else
+ {
+ // Possible to add items, as well as select from any options given
+ // Add any current values that are not options as items in list
+ std::vector<CVariant> list =
+ CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(m_pSetting));
+ for (const auto& value : list)
+ {
+ bool found = std::any_of(options.begin(), options.end(), [&](const auto& p) {
+ return p->GetProperty("value").asString() == value.asString();
+ });
+ if (!found)
+ {
+ CFileItemPtr item(new CFileItem(value.asString()));
+ item->SetProperty("value", value.asString());
+ item->Select(true);
+ options.Add(item);
+ }
+ }
+
+ bool bRepeat = true;
+ while (bRepeat)
+ {
+ std::string strAddButton = Localize(control->GetAddButtonLabel());
+ if (strAddButton.empty())
+ strAddButton = Localize(15019); // "ADD"
+ dialog->Reset(); // Clears AddButtonPressed
+ dialog->SetHeading(CVariant{ Localize(m_pSetting->GetLabel()) });
+ dialog->SetItems(options);
+ dialog->SetMultiSelection(control->CanMultiSelect());
+ dialog->EnableButton2(bAllowNewOption, strAddButton);
+
+ dialog->Open();
+
+ if (!dialog->IsConfirmed())
+ return false;
+
+ if (dialog->IsButton2Pressed())
+ {
+ // Get new list value
+ std::string strLabel;
+ bool bValidType = false;
+ while (!bValidType && CGUIKeyboardFactory::ShowAndGetInput(
+ strLabel, CVariant{ Localize(control->GetAddButtonLabel()) }, false))
+ {
+ // Validate new value is unique and truncate at any comma
+ StringUtils::Trim(strLabel);
+ strLabel = strLabel.substr(0, strLabel.find(','));
+ if (!strLabel.empty())
+ {
+ bValidType = !std::any_of(options.begin(), options.end(), [&](const auto& p) {
+ return p->GetProperty("value").asString() == strLabel;
+ });
+ }
+ if (bValidType)
+ { // Add new value to the list of options
+ CFileItemPtr pItem(new CFileItem(strLabel));
+ pItem->Select(true);
+ pItem->SetProperty("value", strLabel);
+ options.Add(pItem);
+ bValueAdded = true;
+ }
+ }
+ }
+ bRepeat = dialog->IsButton2Pressed();
+ }
+ }
+
+ std::vector<CVariant> values;
+ for (int i : dialog->GetSelectedItems())
+ {
+ const CFileItemPtr item = options.Get(i);
+ if (item == NULL || !item->HasProperty("value"))
+ return false;
+
+ values.push_back(item->GetProperty("value"));
+ }
+
+ bool ret = false;
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ if (values.size() > 1)
+ return false;
+ ret = std::static_pointer_cast<CSettingInt>(m_pSetting)
+ ->SetValue((int)values.at(0).asInteger());
+ break;
+
+ case SettingType::String:
+ if (values.size() > 1)
+ return false;
+ ret = std::static_pointer_cast<CSettingString>(m_pSetting)->SetValue(values.at(0).asString());
+ break;
+
+ case SettingType::List:
+ ret = CSettingUtils::SetList(std::static_pointer_cast<CSettingList>(m_pSetting), values);
+ break;
+
+ default:
+ return false;
+ }
+
+ if (ret)
+ UpdateFromSetting(!bValueAdded);
+ else
+ SetValid(false);
+
+ return IsValid();
+}
+
+void CGUIControlListSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || m_pButton == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ CFileItemList options;
+ std::shared_ptr<const CSettingControlList> control =
+ std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl());
+ bool optionsValid = GetItems(m_pSetting, options, !updateDisplayOnly);
+
+ bool bAllowNewOption = false;
+ if (m_pSetting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<const CSettingList> settingList =
+ std::static_pointer_cast<const CSettingList>(m_pSetting);
+ if (settingList->GetElementType() == SettingType::String)
+ {
+ bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition())
+ ->AllowNewOption();
+ }
+ }
+
+ std::string label2;
+ if (optionsValid && !control->HideValue())
+ {
+ SettingControlListValueFormatter formatter = control->GetFormatter();
+ if (formatter)
+ label2 = formatter(m_pSetting);
+
+ if (label2.empty() && bAllowNewOption)
+ {
+ const std::shared_ptr<const CSettingList> settingList =
+ std::static_pointer_cast<const CSettingList>(m_pSetting);
+ label2 = settingList->ToString();
+ }
+
+ if (label2.empty())
+ {
+ std::vector<std::string> labels;
+ for (int index = 0; index < options.Size(); index++)
+ {
+ const CFileItemPtr pItem = options.Get(index);
+ if (pItem->IsSelected())
+ labels.push_back(pItem->GetLabel());
+ }
+
+ label2 = StringUtils::Join(labels, ", ");
+ }
+ }
+
+ m_pButton->SetLabel2(label2);
+
+ if (!updateDisplayOnly)
+ {
+ // Disable the control if no items can be added and
+ // there are no items to be chosen
+ if (!m_pButton->IsDisabled() && !bAllowNewOption && (options.Size() <= 0))
+ m_pButton->SetEnabled(false);
+ }
+}
+
+bool CGUIControlListSetting::GetItems(const SettingConstPtr& setting,
+ CFileItemList& items,
+ bool updateItems) const
+{
+ std::shared_ptr<const CSettingControlList> control =
+ std::static_pointer_cast<const CSettingControlList>(setting->GetControl());
+ const std::string& controlFormat = control->GetFormat();
+
+ if (controlFormat == "integer")
+ return GetIntegerItems(setting, items, updateItems);
+ else if (controlFormat == "string")
+ {
+ if (setting->GetType() == SettingType::Integer ||
+ (setting->GetType() == SettingType::List &&
+ std::static_pointer_cast<const CSettingList>(setting)->GetElementType() ==
+ SettingType::Integer))
+ return GetIntegerItems(setting, items, updateItems);
+ else if (setting->GetType() == SettingType::String ||
+ (setting->GetType() == SettingType::List &&
+ std::static_pointer_cast<const CSettingList>(setting)->GetElementType() ==
+ SettingType::String))
+ return GetStringItems(setting, items, updateItems);
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CGUIControlListSetting::GetIntegerItems(const SettingConstPtr& setting,
+ CFileItemList& items,
+ bool updateItems) const
+{
+ IntegerSettingOptions options;
+ std::set<int> selectedValues;
+ // get the integer options
+ if (!GetIntegerOptions(setting, options, selectedValues, m_localizer, updateItems))
+ return false;
+
+ // turn them into CFileItems and add them to the item list
+ for (const auto& option : options)
+ items.Add(
+ GetFileItem(option.label, option.label2, option.value, option.properties, selectedValues));
+
+ return true;
+}
+
+bool CGUIControlListSetting::GetStringItems(const SettingConstPtr& setting,
+ CFileItemList& items,
+ bool updateItems) const
+{
+ StringSettingOptions options;
+ std::set<std::string> selectedValues;
+ // get the string options
+ if (!GetStringOptions(setting, options, selectedValues, m_localizer, updateItems))
+ return false;
+
+ // turn them into CFileItems and add them to the item list
+ for (const auto& option : options)
+ items.Add(
+ GetFileItem(option.label, option.label2, option.value, option.properties, selectedValues));
+
+ return true;
+}
+
+CGUIControlButtonSetting::CGUIControlButtonSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pButton = pButton;
+ if (m_pButton == NULL)
+ return;
+
+ m_pButton->SetID(id);
+}
+
+CGUIControlButtonSetting::~CGUIControlButtonSetting() = default;
+
+bool CGUIControlButtonSetting::OnClick()
+{
+ if (m_pButton == NULL)
+ return false;
+
+ std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl();
+ const std::string& controlType = control->GetType();
+ const std::string& controlFormat = control->GetFormat();
+ if (controlType == "button")
+ {
+ std::shared_ptr<const CSettingControlButton> buttonControl =
+ std::static_pointer_cast<const CSettingControlButton>(control);
+ if (controlFormat == "addon")
+ {
+ // prompt for the addon
+ std::shared_ptr<CSettingAddon> setting;
+ std::vector<std::string> addonIDs;
+ if (m_pSetting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<CSettingList> settingList =
+ std::static_pointer_cast<CSettingList>(m_pSetting);
+ setting = std::static_pointer_cast<CSettingAddon>(settingList->GetDefinition());
+ for (const SettingPtr& addon : settingList->GetValue())
+ addonIDs.push_back(std::static_pointer_cast<CSettingAddon>(addon)->GetValue());
+ }
+ else
+ {
+ setting = std::static_pointer_cast<CSettingAddon>(m_pSetting);
+ addonIDs.push_back(setting->GetValue());
+ }
+
+ if (CGUIWindowAddonBrowser::SelectAddonID(
+ setting->GetAddonType(), addonIDs, setting->AllowEmpty(),
+ buttonControl->ShowAddonDetails(), m_pSetting->GetType() == SettingType::List,
+ buttonControl->ShowInstalledAddons(), buttonControl->ShowInstallableAddons(),
+ buttonControl->ShowMoreAddons()) != 1)
+ return false;
+
+ if (m_pSetting->GetType() == SettingType::List)
+ std::static_pointer_cast<CSettingList>(m_pSetting)->FromString(addonIDs);
+ else
+ SetValid(setting->SetValue(addonIDs[0]));
+ }
+ else if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image")
+ SetValid(GetPath(std::static_pointer_cast<CSettingPath>(m_pSetting), m_localizer));
+ else if (controlFormat == "date")
+ {
+ std::shared_ptr<CSettingDate> settingDate =
+ std::static_pointer_cast<CSettingDate>(m_pSetting);
+
+ KODI::TIME::SystemTime systemdate;
+ settingDate->GetDate().GetAsSystemTime(systemdate);
+ if (CGUIDialogNumeric::ShowAndGetDate(systemdate, Localize(buttonControl->GetHeading())))
+ SetValid(settingDate->SetDate(CDateTime(systemdate)));
+ }
+ else if (controlFormat == "time")
+ {
+ std::shared_ptr<CSettingTime> settingTime =
+ std::static_pointer_cast<CSettingTime>(m_pSetting);
+
+ KODI::TIME::SystemTime systemtime;
+ settingTime->GetTime().GetAsSystemTime(systemtime);
+
+ if (CGUIDialogNumeric::ShowAndGetTime(systemtime, Localize(buttonControl->GetHeading())))
+ SetValid(settingTime->SetTime(CDateTime(systemtime)));
+ }
+ else if (controlFormat == "action")
+ {
+ // simply call the OnSettingAction callback and whoever knows what to
+ // do can do so (based on the setting's identification)
+ m_pSetting->OnSettingAction(m_pSetting);
+ SetValid(true);
+ }
+ }
+ else if (controlType == "slider")
+ {
+ float value, min, step, max;
+ if (m_pSetting->GetType() == SettingType::Integer)
+ {
+ std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
+ value = (float)settingInt->GetValue();
+ min = (float)settingInt->GetMinimum();
+ step = (float)settingInt->GetStep();
+ max = (float)settingInt->GetMaximum();
+ }
+ else if (m_pSetting->GetType() == SettingType::Number)
+ {
+ std::shared_ptr<CSettingNumber> settingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ value = (float)settingNumber->GetValue();
+ min = (float)settingNumber->GetMinimum();
+ step = (float)settingNumber->GetStep();
+ max = (float)settingNumber->GetMaximum();
+ }
+ else
+ return false;
+
+ std::shared_ptr<const CSettingControlSlider> sliderControl =
+ std::static_pointer_cast<const CSettingControlSlider>(control);
+ CGUIDialogSlider::ShowAndGetInput(Localize(sliderControl->GetHeading()), value, min, step, max,
+ this, NULL);
+ SetValid(true);
+ }
+
+ // update the displayed value
+ UpdateFromSetting(true);
+
+ return IsValid();
+}
+
+void CGUIControlButtonSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || m_pButton == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ std::string strText;
+ std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl();
+ const std::string& controlType = control->GetType();
+ const std::string& controlFormat = control->GetFormat();
+
+ if (controlType == "button")
+ {
+ if (!std::static_pointer_cast<const CSettingControlButton>(control)->HideValue())
+ {
+ auto setting = m_pSetting;
+ if (m_pSetting->GetType() == SettingType::List)
+ setting = std::static_pointer_cast<CSettingList>(m_pSetting)->GetDefinition();
+
+ switch (setting->GetType())
+ {
+ case SettingType::String:
+ {
+ if (controlFormat == "addon")
+ {
+ std::vector<std::string> addonIDs;
+ if (m_pSetting->GetType() == SettingType::List)
+ {
+ for (const auto& addonSetting :
+ std::static_pointer_cast<CSettingList>(m_pSetting)->GetValue())
+ addonIDs.push_back(
+ std::static_pointer_cast<CSettingAddon>(addonSetting)->GetValue());
+ }
+ else
+ addonIDs.push_back(std::static_pointer_cast<CSettingString>(setting)->GetValue());
+
+ std::vector<std::string> addonNames;
+ for (const auto& addonID : addonIDs)
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(addonID, addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ addonNames.push_back(addon->Name());
+ }
+
+ if (addonNames.empty())
+ strText = g_localizeStrings.Get(231); // None
+ else
+ strText = StringUtils::Join(addonNames, ", ");
+ }
+ else
+ {
+ std::string strValue = std::static_pointer_cast<CSettingString>(setting)->GetValue();
+ if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image")
+ {
+ std::string shortPath;
+ if (CUtil::MakeShortenPath(strValue, shortPath, 30))
+ strText = shortPath;
+ }
+ else if (controlFormat == "infolabel")
+ {
+ strText = strValue;
+ if (strText.empty())
+ strText = g_localizeStrings.Get(231); // None
+ }
+ else
+ strText = strValue;
+ }
+
+ break;
+ }
+
+ case SettingType::Action:
+ {
+ // CSettingAction.
+ // Note: This can be removed once all settings use a proper control & format combination.
+ // CSettingAction is strictly speaking not designed to have a label2, it does not even have a value.
+ strText = m_pButton->GetLabel2();
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ else if (controlType == "slider")
+ {
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ {
+ std::shared_ptr<const CSettingInt> settingInt =
+ std::static_pointer_cast<CSettingInt>(m_pSetting);
+ strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(),
+ settingInt->GetMinimum(), settingInt->GetStep(),
+ settingInt->GetMaximum(), m_localizer);
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<const CSettingNumber> settingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ strText = CGUIControlSliderSetting::GetText(
+ m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(),
+ settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ m_pButton->SetLabel2(strText);
+}
+
+bool CGUIControlButtonSetting::GetPath(const std::shared_ptr<CSettingPath>& pathSetting,
+ ILocalizer* localizer)
+{
+ if (pathSetting == NULL)
+ return false;
+
+ std::string path = pathSetting->GetValue();
+
+ VECSOURCES shares;
+ bool localSharesOnly = false;
+ const std::vector<std::string>& sources = pathSetting->GetSources();
+ for (const auto& source : sources)
+ {
+ if (StringUtils::EqualsNoCase(source, "local"))
+ localSharesOnly = true;
+ else
+ {
+ VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(source);
+ if (sources != NULL)
+ shares.insert(shares.end(), sources->begin(), sources->end());
+ }
+ }
+
+ CServiceBroker::GetMediaManager().GetLocalDrives(shares);
+ if (!localSharesOnly)
+ CServiceBroker::GetMediaManager().GetNetworkLocations(shares);
+
+ bool result = false;
+ std::shared_ptr<const CSettingControlButton> control =
+ std::static_pointer_cast<const CSettingControlButton>(pathSetting->GetControl());
+ const auto heading = ::Localize(control->GetHeading(), localizer);
+ if (control->GetFormat() == "file")
+ result = CGUIDialogFileBrowser::ShowAndGetFile(
+ shares, pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider()), heading, path,
+ control->UseImageThumbs(), control->UseFileDirectories());
+ else if (control->GetFormat() == "image")
+ {
+ /* Check setting contains own masking, to filter required image type.
+ * e.g. png only needed
+ * <constraints>
+ * <masking>*.png</masking>
+ * </constraints>
+ * <control type="button" format="image">
+ * ...
+ */
+ std::string ext = pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider());
+ if (ext.empty())
+ ext = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
+ result = CGUIDialogFileBrowser::ShowAndGetFile(shares, ext, heading, path, true);
+ }
+ else
+ result =
+ CGUIDialogFileBrowser::ShowAndGetDirectory(shares, heading, path, pathSetting->Writable());
+
+ if (!result)
+ return false;
+
+ return pathSetting->SetValue(path);
+}
+
+void CGUIControlButtonSetting::OnSliderChange(void* data, CGUISliderControl* slider)
+{
+ if (slider == NULL)
+ return;
+
+ std::string strText;
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ {
+ std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
+ if (settingInt->SetValue(slider->GetIntValue()))
+ strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(),
+ settingInt->GetMinimum(), settingInt->GetStep(),
+ settingInt->GetMaximum(), m_localizer);
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<CSettingNumber> settingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ if (settingNumber->SetValue(static_cast<double>(slider->GetFloatValue())))
+ strText = CGUIControlSliderSetting::GetText(
+ m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(),
+ settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!strText.empty())
+ slider->SetTextValue(strText);
+}
+
+CGUIControlEditSetting::CGUIControlEditSetting(CGUIEditControl* pEdit,
+ int id,
+ const std::shared_ptr<CSetting>& pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, pSetting, localizer)
+{
+ std::shared_ptr<const CSettingControlEdit> control =
+ std::static_pointer_cast<const CSettingControlEdit>(pSetting->GetControl());
+ m_pEdit = pEdit;
+ if (m_pEdit == NULL)
+ return;
+
+ m_pEdit->SetID(id);
+ int heading = m_pSetting->GetLabel();
+ if (control->GetHeading() > 0)
+ heading = control->GetHeading();
+ if (heading < 0)
+ heading = 0;
+
+ CGUIEditControl::INPUT_TYPE inputType = CGUIEditControl::INPUT_TYPE_TEXT;
+ const std::string& controlFormat = control->GetFormat();
+ if (controlFormat == "string")
+ {
+ if (control->IsHidden())
+ inputType = CGUIEditControl::INPUT_TYPE_PASSWORD;
+ }
+ else if (controlFormat == "integer" || controlFormat == "number")
+ {
+ if (control->VerifyNewValue())
+ inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW;
+ else
+ inputType = CGUIEditControl::INPUT_TYPE_NUMBER;
+ }
+ else if (controlFormat == "ip")
+ inputType = CGUIEditControl::INPUT_TYPE_IPADDRESS;
+ else if (controlFormat == "md5")
+ inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_MD5;
+
+ m_pEdit->SetInputType(inputType, heading);
+
+ // this will automatically trigger validation so it must be executed after
+ // having set the value of the control based on the value of the setting
+ m_pEdit->SetInputValidation(InputValidation, this);
+}
+
+CGUIControlEditSetting::~CGUIControlEditSetting() = default;
+
+bool CGUIControlEditSetting::OnClick()
+{
+ if (m_pEdit == NULL)
+ return false;
+
+ // update our string
+ if (m_pSetting->GetControl()->GetFormat() == "urlencoded")
+ {
+ std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting =
+ std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting);
+ SetValid(urlEncodedSetting->SetDecodedValue(m_pEdit->GetLabel2()));
+ }
+ else
+ SetValid(m_pSetting->FromString(m_pEdit->GetLabel2()));
+
+ return IsValid();
+}
+
+void CGUIControlEditSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (fromControl || m_pEdit == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ std::shared_ptr<const CSettingControlEdit> control =
+ std::static_pointer_cast<const CSettingControlEdit>(m_pSetting->GetControl());
+
+ if (control->GetFormat() == "urlencoded")
+ {
+ std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting =
+ std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting);
+ m_pEdit->SetLabel2(urlEncodedSetting->GetDecodedValue());
+ }
+ else
+ m_pEdit->SetLabel2(m_pSetting->ToString());
+}
+
+bool CGUIControlEditSetting::InputValidation(const std::string& input, void* data)
+{
+ if (data == NULL)
+ return true;
+
+ CGUIControlEditSetting* editControl = reinterpret_cast<CGUIControlEditSetting*>(data);
+ if (editControl->GetSetting() == NULL)
+ return true;
+
+ editControl->SetValid(editControl->GetSetting()->CheckValidity(input));
+ return editControl->IsValid();
+}
+
+CGUIControlSliderSetting::CGUIControlSliderSetting(CGUISettingsSliderControl* pSlider,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pSlider = pSlider;
+ if (m_pSlider == NULL)
+ return;
+
+ m_pSlider->SetID(id);
+
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ {
+ std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting);
+ if (m_pSetting->GetControl()->GetFormat() == "percentage")
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE);
+ else
+ {
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT);
+ m_pSlider->SetRange(settingInt->GetMinimum(), settingInt->GetMaximum());
+ }
+ m_pSlider->SetIntInterval(settingInt->GetStep());
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<CSettingNumber> settingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT);
+ m_pSlider->SetFloatRange(static_cast<float>(settingNumber->GetMinimum()),
+ static_cast<float>(settingNumber->GetMaximum()));
+ m_pSlider->SetFloatInterval(static_cast<float>(settingNumber->GetStep()));
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+CGUIControlSliderSetting::~CGUIControlSliderSetting() = default;
+
+bool CGUIControlSliderSetting::OnClick()
+{
+ if (m_pSlider == NULL)
+ return false;
+
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ SetValid(
+ std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSlider->GetIntValue()));
+ break;
+
+ case SettingType::Number:
+ SetValid(std::static_pointer_cast<CSettingNumber>(m_pSetting)
+ ->SetValue(static_cast<double>(m_pSlider->GetFloatValue())));
+ break;
+
+ default:
+ return false;
+ }
+
+ return IsValid();
+}
+
+void CGUIControlSliderSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (m_pSlider == NULL)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ std::string strText;
+ switch (m_pSetting->GetType())
+ {
+ case SettingType::Integer:
+ {
+ std::shared_ptr<const CSettingInt> settingInt =
+ std::static_pointer_cast<CSettingInt>(m_pSetting);
+ int value;
+ if (fromControl)
+ value = m_pSlider->GetIntValue();
+ else
+ {
+ value = std::static_pointer_cast<CSettingInt>(m_pSetting)->GetValue();
+ m_pSlider->SetIntValue(value);
+ }
+
+ strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingInt->GetMinimum(),
+ settingInt->GetStep(), settingInt->GetMaximum(),
+ m_localizer);
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<const CSettingNumber> settingNumber =
+ std::static_pointer_cast<CSettingNumber>(m_pSetting);
+ double value;
+ if (fromControl)
+ value = static_cast<double>(m_pSlider->GetFloatValue());
+ else
+ {
+ value = std::static_pointer_cast<CSettingNumber>(m_pSetting)->GetValue();
+ m_pSlider->SetFloatValue((float)value);
+ }
+
+ strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingNumber->GetMinimum(),
+ settingNumber->GetStep(),
+ settingNumber->GetMaximum(), m_localizer);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!strText.empty())
+ m_pSlider->SetTextValue(strText);
+}
+
+std::string CGUIControlSliderSetting::GetText(const std::shared_ptr<CSetting>& setting,
+ const CVariant& value,
+ const CVariant& minimum,
+ const CVariant& step,
+ const CVariant& maximum,
+ ILocalizer* localizer)
+{
+ if (setting == NULL || !(value.isInteger() || value.isDouble()))
+ return "";
+
+ const auto control = std::static_pointer_cast<const CSettingControlSlider>(setting->GetControl());
+ if (control == NULL)
+ return "";
+
+ SettingControlSliderFormatter formatter = control->GetFormatter();
+ if (formatter != NULL)
+ return formatter(control, value, minimum, step, maximum);
+
+ std::string formatString = control->GetFormatString();
+ if (control->GetFormatLabel() > -1)
+ formatString = ::Localize(control->GetFormatLabel(), localizer);
+
+ std::string formattedString;
+ if (FormatText(formatString, value, setting->GetId(), formattedString))
+ return formattedString;
+
+ // fall back to default formatting
+ formatString = control->GetDefaultFormatString();
+ if (FormatText(formatString, value, setting->GetId(), formattedString))
+ return formattedString;
+
+ return "";
+}
+
+bool CGUIControlSliderSetting::FormatText(const std::string& formatString,
+ const CVariant& value,
+ const std::string& settingId,
+ std::string& formattedText)
+{
+ try
+ {
+ if (value.isDouble())
+ formattedText = StringUtils::Format(formatString, value.asDouble());
+ else
+ formattedText = StringUtils::Format(formatString, static_cast<int>(value.asInteger()));
+ }
+ catch (const std::runtime_error& err)
+ {
+ CLog::Log(LOGERROR, "Invalid formatting with string \"{}\" for setting \"{}\": {}",
+ formatString, settingId, err.what());
+ return false;
+ }
+
+ return true;
+}
+
+CGUIControlRangeSetting::CGUIControlRangeSetting(CGUISettingsSliderControl* pSlider,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pSlider = pSlider;
+ if (m_pSlider == NULL)
+ return;
+
+ m_pSlider->SetID(id);
+ m_pSlider->SetRangeSelection(true);
+
+ if (m_pSetting->GetType() == SettingType::List)
+ {
+ std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
+ SettingConstPtr listDefintion = settingList->GetDefinition();
+ switch (listDefintion->GetType())
+ {
+ case SettingType::Integer:
+ {
+ std::shared_ptr<const CSettingInt> listDefintionInt =
+ std::static_pointer_cast<const CSettingInt>(listDefintion);
+ if (m_pSetting->GetControl()->GetFormat() == "percentage")
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE);
+ else
+ {
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT);
+ m_pSlider->SetRange(listDefintionInt->GetMinimum(), listDefintionInt->GetMaximum());
+ }
+ m_pSlider->SetIntInterval(listDefintionInt->GetStep());
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ std::shared_ptr<const CSettingNumber> listDefinitionNumber =
+ std::static_pointer_cast<const CSettingNumber>(listDefintion);
+ m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT);
+ m_pSlider->SetFloatRange(static_cast<float>(listDefinitionNumber->GetMinimum()),
+ static_cast<float>(listDefinitionNumber->GetMaximum()));
+ m_pSlider->SetFloatInterval(static_cast<float>(listDefinitionNumber->GetStep()));
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+}
+
+CGUIControlRangeSetting::~CGUIControlRangeSetting() = default;
+
+bool CGUIControlRangeSetting::OnClick()
+{
+ if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List)
+ return false;
+
+ std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
+ const SettingList& settingListValues = settingList->GetValue();
+ if (settingListValues.size() != 2)
+ return false;
+
+ std::vector<CVariant> values;
+ SettingConstPtr listDefintion = settingList->GetDefinition();
+ switch (listDefintion->GetType())
+ {
+ case SettingType::Integer:
+ values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower));
+ values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper));
+ break;
+
+ case SettingType::Number:
+ values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower));
+ values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper));
+ break;
+
+ default:
+ return false;
+ }
+
+ if (values.size() != 2)
+ return false;
+
+ SetValid(CSettingUtils::SetList(settingList, values));
+ return IsValid();
+}
+
+void CGUIControlRangeSetting::Update(bool fromControl, bool updateDisplayOnly)
+{
+ if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List)
+ return;
+
+ CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly);
+
+ std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting);
+ const SettingList& settingListValues = settingList->GetValue();
+ if (settingListValues.size() != 2)
+ return;
+
+ SettingConstPtr listDefintion = settingList->GetDefinition();
+ std::shared_ptr<const CSettingControlRange> controlRange =
+ std::static_pointer_cast<const CSettingControlRange>(m_pSetting->GetControl());
+ const std::string& controlFormat = controlRange->GetFormat();
+
+ std::string strText;
+ std::string strTextLower, strTextUpper;
+ std::string formatString =
+ Localize(controlRange->GetFormatLabel() > -1 ? controlRange->GetFormatLabel() : 21469);
+ std::string valueFormat = controlRange->GetValueFormat();
+ if (controlRange->GetValueFormatLabel() > -1)
+ valueFormat = Localize(controlRange->GetValueFormatLabel());
+
+ switch (listDefintion->GetType())
+ {
+ case SettingType::Integer:
+ {
+ int valueLower, valueUpper;
+ if (fromControl)
+ {
+ valueLower = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower);
+ valueUpper = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper);
+ }
+ else
+ {
+ valueLower = std::static_pointer_cast<CSettingInt>(settingListValues[0])->GetValue();
+ valueUpper = std::static_pointer_cast<CSettingInt>(settingListValues[1])->GetValue();
+ m_pSlider->SetIntValue(valueLower, CGUISliderControl::RangeSelectorLower);
+ m_pSlider->SetIntValue(valueUpper, CGUISliderControl::RangeSelectorUpper);
+ }
+
+ if (controlFormat == "date" || controlFormat == "time")
+ {
+ CDateTime dateLower((time_t)valueLower);
+ CDateTime dateUpper((time_t)valueUpper);
+
+ if (controlFormat == "date")
+ {
+ if (valueFormat.empty())
+ {
+ strTextLower = dateLower.GetAsLocalizedDate();
+ strTextUpper = dateUpper.GetAsLocalizedDate();
+ }
+ else
+ {
+ strTextLower = dateLower.GetAsLocalizedDate(valueFormat);
+ strTextUpper = dateUpper.GetAsLocalizedDate(valueFormat);
+ }
+ }
+ else
+ {
+ if (valueFormat.empty())
+ valueFormat = "mm:ss";
+
+ strTextLower = dateLower.GetAsLocalizedTime(valueFormat);
+ strTextUpper = dateUpper.GetAsLocalizedTime(valueFormat);
+ }
+ }
+ else
+ {
+ strTextLower = StringUtils::Format(valueFormat, valueLower);
+ strTextUpper = StringUtils::Format(valueFormat, valueUpper);
+ }
+
+ if (valueLower != valueUpper)
+ strText = StringUtils::Format(formatString, strTextLower, strTextUpper);
+ else
+ strText = strTextLower;
+ break;
+ }
+
+ case SettingType::Number:
+ {
+ double valueLower, valueUpper;
+ if (fromControl)
+ {
+ valueLower =
+ static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower));
+ valueUpper =
+ static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper));
+ }
+ else
+ {
+ valueLower = std::static_pointer_cast<CSettingNumber>(settingListValues[0])->GetValue();
+ valueUpper = std::static_pointer_cast<CSettingNumber>(settingListValues[1])->GetValue();
+ m_pSlider->SetFloatValue((float)valueLower, CGUISliderControl::RangeSelectorLower);
+ m_pSlider->SetFloatValue((float)valueUpper, CGUISliderControl::RangeSelectorUpper);
+ }
+
+ strTextLower = StringUtils::Format(valueFormat, valueLower);
+ if (valueLower != valueUpper)
+ {
+ strTextUpper = StringUtils::Format(valueFormat, valueUpper);
+ strText = StringUtils::Format(formatString, strTextLower, strTextUpper);
+ }
+ else
+ strText = strTextLower;
+ break;
+ }
+
+ default:
+ strText.clear();
+ break;
+ }
+
+ if (!strText.empty())
+ m_pSlider->SetTextValue(strText);
+}
+
+CGUIControlSeparatorSetting::CGUIControlSeparatorSetting(CGUIImage* pImage,
+ int id,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, NULL, localizer)
+{
+ m_pImage = pImage;
+ if (m_pImage == NULL)
+ return;
+
+ m_pImage->SetID(id);
+}
+
+CGUIControlSeparatorSetting::~CGUIControlSeparatorSetting() = default;
+
+CGUIControlGroupTitleSetting::CGUIControlGroupTitleSetting(CGUILabelControl* pLabel,
+ int id,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, NULL, localizer)
+{
+ m_pLabel = pLabel;
+ if (m_pLabel == NULL)
+ return;
+
+ m_pLabel->SetID(id);
+}
+
+CGUIControlGroupTitleSetting::~CGUIControlGroupTitleSetting() = default;
+
+CGUIControlLabelSetting::CGUIControlLabelSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer)
+ : CGUIControlBaseSetting(id, std::move(pSetting), localizer)
+{
+ m_pButton = pButton;
+ if (m_pButton == NULL)
+ return;
+
+ m_pButton->SetID(id);
+ UpdateFromSetting();
+}
diff --git a/xbmc/settings/windows/GUIControlSettings.h b/xbmc/settings/windows/GUIControlSettings.h
new file mode 100644
index 0000000..777b60d
--- /dev/null
+++ b/xbmc/settings/windows/GUIControlSettings.h
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2005-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 "guilib/ISliderCallback.h"
+#include "utils/ILocalizer.h"
+
+#include <functional>
+#include <memory>
+#include <stdlib.h>
+#include <string>
+
+class CGUIControl;
+class CGUIImage;
+class CGUISpinControlEx;
+class CGUIEditControl;
+class CGUIButtonControl;
+class CGUIRadioButtonControl;
+class CGUISettingsSliderControl;
+class CGUILabelControl;
+class CGUIColorButtonControl;
+
+class CSetting;
+class CSettingControlSlider;
+class CSettingString;
+class CSettingPath;
+
+class CFileItemList;
+class CVariant;
+
+class CGUIControlBaseSetting : protected ILocalizer
+{
+public:
+ CGUIControlBaseSetting(int id, std::shared_ptr<CSetting> pSetting, ILocalizer* localizer);
+ ~CGUIControlBaseSetting() override = default;
+
+ int GetID() const { return m_id; }
+ std::shared_ptr<CSetting> GetSetting() { return m_pSetting; }
+
+ /*!
+ \brief Specifies that this setting should update after a delay
+ Useful for settings that have options to navigate through
+ and may take a while, or require additional input to update
+ once the final setting is chosen. Settings default to updating
+ instantly.
+ \sa IsDelayed()
+ */
+ void SetDelayed() { m_delayed = true; }
+
+ /*!
+ \brief Returns whether this setting should have delayed update
+ \return true if the setting's update should be delayed
+ \sa SetDelayed()
+ */
+ bool IsDelayed() const { return m_delayed; }
+
+ /*!
+ \brief Returns whether this setting is enabled or disabled
+ This state is independent of the real enabled state of a
+ setting control but represents the enabled state of the
+ setting itself based on specific conditions.
+ \return true if the setting is enabled otherwise false
+ \sa SetEnabled()
+ */
+ bool IsEnabled() const;
+
+ /*!
+ \brief Returns whether the setting's value is valid or not
+ */
+ bool IsValid() const { return m_valid; }
+
+ void SetValid(bool valid) { m_valid = valid; }
+
+ virtual CGUIControl* GetControl() { return NULL; }
+ virtual bool OnClick() { return false; }
+ void UpdateFromControl();
+ void UpdateFromSetting(bool updateDisplayOnly = false);
+ virtual void Clear() = 0; ///< Clears the attached control
+protected:
+ // implementation of ILocalizer
+ std::string Localize(std::uint32_t code) const override;
+
+ virtual void Update(bool fromControl, bool updateDisplayOnly);
+
+ int m_id;
+ std::shared_ptr<CSetting> m_pSetting;
+ ILocalizer* m_localizer;
+ bool m_delayed;
+ bool m_valid;
+};
+
+class CGUIControlRadioButtonSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlRadioButtonSetting(CGUIRadioButtonControl* pRadioButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlRadioButtonSetting() override;
+
+ void Select(bool bSelect);
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pRadioButton); }
+ bool OnClick() override;
+ void Clear() override { m_pRadioButton = NULL; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ CGUIRadioButtonControl* m_pRadioButton;
+};
+
+class CGUIControlColorButtonSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlColorButtonSetting(CGUIColorButtonControl* pColorControl,
+ int id,
+ const std::shared_ptr<CSetting>& pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlColorButtonSetting() override;
+
+ void Select(bool bSelect);
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pColorButton); }
+ bool OnClick() override;
+ void Clear() override { m_pColorButton = nullptr; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ CGUIColorButtonControl* m_pColorButton;
+};
+
+class CGUIControlSpinExSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlSpinExSetting(CGUISpinControlEx* pSpin,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlSpinExSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSpin); }
+ bool OnClick() override;
+ void Clear() override { m_pSpin = NULL; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ void FillControl(bool updateDisplayOnly);
+ void FillIntegerSettingControl(bool updateValues);
+ void FillFloatSettingControl();
+ void FillStringSettingControl(bool updateValues);
+ CGUISpinControlEx* m_pSpin;
+};
+
+class CGUIControlListSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlListSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlListSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); }
+ bool OnClick() override;
+ void Clear() override { m_pButton = NULL; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ bool GetItems(const std::shared_ptr<const CSetting>& setting,
+ CFileItemList& items,
+ bool updateItems) const;
+ bool GetIntegerItems(const std::shared_ptr<const CSetting>& setting,
+ CFileItemList& items,
+ bool updateItems) const;
+ bool GetStringItems(const std::shared_ptr<const CSetting>& setting,
+ CFileItemList& items,
+ bool updateItems) const;
+
+ CGUIButtonControl* m_pButton;
+};
+
+class CGUIControlListColorSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlListColorSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlListColorSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); }
+ bool OnClick() override;
+ void Clear() override { m_pButton = nullptr; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ CGUIButtonControl* m_pButton;
+};
+
+class CGUIControlButtonSetting : public CGUIControlBaseSetting, protected ISliderCallback
+{
+public:
+ CGUIControlButtonSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlButtonSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); }
+ bool OnClick() override;
+ void Clear() override { m_pButton = NULL; }
+
+ static bool GetPath(const std::shared_ptr<CSettingPath>& pathSetting, ILocalizer* localizer);
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+ // implementations of ISliderCallback
+ void OnSliderChange(void* data, CGUISliderControl* slider) override;
+
+private:
+ CGUIButtonControl* m_pButton;
+};
+
+class CGUIControlEditSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlEditSetting(CGUIEditControl* pButton,
+ int id,
+ const std::shared_ptr<CSetting>& pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlEditSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pEdit); }
+ bool OnClick() override;
+ void Clear() override { m_pEdit = NULL; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ static bool InputValidation(const std::string& input, void* data);
+
+ CGUIEditControl* m_pEdit;
+};
+
+class CGUIControlSliderSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlSliderSetting(CGUISettingsSliderControl* pSlider,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlSliderSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSlider); }
+ bool OnClick() override;
+ void Clear() override { m_pSlider = NULL; }
+
+ static std::string GetText(const std::shared_ptr<CSetting>& setting,
+ const CVariant& value,
+ const CVariant& minimum,
+ const CVariant& step,
+ const CVariant& maximum,
+ ILocalizer* localizer);
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ static bool FormatText(const std::string& formatString,
+ const CVariant& value,
+ const std::string& settingId,
+ std::string& formattedText);
+
+ CGUISettingsSliderControl* m_pSlider;
+};
+
+class CGUIControlRangeSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlRangeSetting(CGUISettingsSliderControl* pSlider,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlRangeSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSlider); }
+ bool OnClick() override;
+ void Clear() override { m_pSlider = NULL; }
+
+protected:
+ // specialization of CGUIControlBaseSetting
+ void Update(bool fromControl, bool updateDisplayOnly) override;
+
+private:
+ CGUISettingsSliderControl* m_pSlider;
+};
+
+class CGUIControlSeparatorSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlSeparatorSetting(CGUIImage* pImage, int id, ILocalizer* localizer);
+ ~CGUIControlSeparatorSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pImage); }
+ bool OnClick() override { return false; }
+ void Clear() override { m_pImage = NULL; }
+
+private:
+ CGUIImage* m_pImage;
+};
+
+class CGUIControlGroupTitleSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlGroupTitleSetting(CGUILabelControl* pLabel, int id, ILocalizer* localizer);
+ ~CGUIControlGroupTitleSetting() override;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pLabel); }
+ bool OnClick() override { return false; }
+ void Clear() override { m_pLabel = NULL; }
+
+private:
+ CGUILabelControl* m_pLabel;
+};
+
+class CGUIControlLabelSetting : public CGUIControlBaseSetting
+{
+public:
+ CGUIControlLabelSetting(CGUIButtonControl* pButton,
+ int id,
+ std::shared_ptr<CSetting> pSetting,
+ ILocalizer* localizer);
+ ~CGUIControlLabelSetting() override = default;
+
+ CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); }
+ void Clear() override { m_pButton = NULL; }
+
+private:
+ CGUIButtonControl* m_pButton;
+};
diff --git a/xbmc/settings/windows/GUIWindowSettings.cpp b/xbmc/settings/windows/GUIWindowSettings.cpp
new file mode 100644
index 0000000..d8edede
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettings.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-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 "GUIWindowSettings.h"
+
+#include "guilib/WindowIDs.h"
+
+CGUIWindowSettings::CGUIWindowSettings(void)
+ : CGUIWindow(WINDOW_SETTINGS_MENU, "Settings.xml")
+{
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+CGUIWindowSettings::~CGUIWindowSettings(void) = default;
diff --git a/xbmc/settings/windows/GUIWindowSettings.h b/xbmc/settings/windows/GUIWindowSettings.h
new file mode 100644
index 0000000..c658186
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettings.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-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 "guilib/GUIWindow.h"
+
+class CGUIWindowSettings :
+ public CGUIWindow
+{
+public:
+ CGUIWindowSettings(void);
+ ~CGUIWindowSettings(void) override;
+};
diff --git a/xbmc/settings/windows/GUIWindowSettingsCategory.cpp b/xbmc/settings/windows/GUIWindowSettingsCategory.cpp
new file mode 100644
index 0000000..18be9cd
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettingsCategory.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2005-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 "GUIWindowSettingsCategory.h"
+
+#include "GUIPassword.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "input/Key.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingSection.h"
+#include "settings/windows/GUIControlSettings.h"
+#include "utils/log.h"
+#include "view/ViewStateSettings.h"
+
+#include <string>
+
+#define SETTINGS_SYSTEM WINDOW_SETTINGS_SYSTEM - WINDOW_SETTINGS_START
+#define SETTINGS_SERVICE WINDOW_SETTINGS_SERVICE - WINDOW_SETTINGS_START
+#define SETTINGS_PVR WINDOW_SETTINGS_MYPVR - WINDOW_SETTINGS_START
+#define SETTINGS_PLAYER WINDOW_SETTINGS_PLAYER - WINDOW_SETTINGS_START
+#define SETTINGS_MEDIA WINDOW_SETTINGS_MEDIA - WINDOW_SETTINGS_START
+#define SETTINGS_INTERFACE WINDOW_SETTINGS_INTERFACE - WINDOW_SETTINGS_START
+#define SETTINGS_GAMES WINDOW_SETTINGS_MYGAMES - WINDOW_SETTINGS_START
+
+#define CONTROL_BTN_LEVELS 20
+
+typedef struct {
+ int id;
+ std::string name;
+} SettingGroup;
+
+static const SettingGroup s_settingGroupMap[] = { { SETTINGS_SYSTEM, "system" },
+ { SETTINGS_SERVICE, "services" },
+ { SETTINGS_PVR, "pvr" },
+ { SETTINGS_PLAYER, "player" },
+ { SETTINGS_MEDIA, "media" },
+ { SETTINGS_INTERFACE, "interface" },
+ { SETTINGS_GAMES, "games" } };
+
+#define SettingGroupSize sizeof(s_settingGroupMap) / sizeof(SettingGroup)
+
+CGUIWindowSettingsCategory::CGUIWindowSettingsCategory()
+ : CGUIDialogSettingsManagerBase(WINDOW_SETTINGS_SYSTEM, "SettingsCategory.xml"),
+ m_settings(CServiceBroker::GetSettingsComponent()->GetSettings())
+{
+ // set the correct ID range...
+ m_idRange.clear();
+ m_idRange.push_back(WINDOW_SETTINGS_SYSTEM);
+ m_idRange.push_back(WINDOW_SETTINGS_SERVICE);
+ m_idRange.push_back(WINDOW_SETTINGS_MYPVR);
+ m_idRange.push_back(WINDOW_SETTINGS_PLAYER);
+ m_idRange.push_back(WINDOW_SETTINGS_MEDIA);
+ m_idRange.push_back(WINDOW_SETTINGS_INTERFACE);
+ m_idRange.push_back(WINDOW_SETTINGS_MYGAMES);
+}
+
+CGUIWindowSettingsCategory::~CGUIWindowSettingsCategory() = default;
+
+bool CGUIWindowSettingsCategory::OnMessage(CGUIMessage &message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ m_iSection = message.GetParam2() - CGUIDialogSettingsManagerBase::GetID();
+ CGUIDialogSettingsManagerBase::OnMessage(message);
+ m_returningFromSkinLoad = false;
+
+ if (!message.GetStringParam(0).empty())
+ FocusElement(message.GetStringParam(0));
+
+ return true;
+ }
+
+ case GUI_MSG_FOCUSED:
+ {
+ if (!m_returningFromSkinLoad)
+ CGUIDialogSettingsManagerBase::OnMessage(message);
+ return true;
+ }
+
+ case GUI_MSG_LOAD_SKIN:
+ {
+ if (IsActive())
+ m_returningFromSkinLoad = true;
+ break;
+ }
+
+ case GUI_MSG_NOTIFY_ALL:
+ {
+ if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
+ {
+ if (IsActive() && CDisplaySettings::GetInstance().GetCurrentResolution() != CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution())
+ {
+ CDisplaySettings::GetInstance().SetCurrentResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), true);
+ CreateSettings();
+ }
+ }
+ break;
+ }
+
+ case GUI_MSG_PLAYBACK_STARTED:
+ case GUI_MSG_PLAYBACK_ENDED:
+ case GUI_MSG_PLAYBACK_STOPPED:
+ {
+ if (IsActive())
+ {
+ UpdateSettings();
+ }
+ break;
+ }
+ }
+
+ return CGUIDialogSettingsManagerBase::OnMessage(message);
+}
+
+bool CGUIWindowSettingsCategory::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_SETTINGS_LEVEL_CHANGE:
+ {
+ //Test if we can access the new level
+ if (!g_passwordManager.CheckSettingLevelLock(CViewStateSettings::GetInstance().GetNextSettingLevel(), true))
+ return false;
+
+ CViewStateSettings::GetInstance().CycleSettingLevel();
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+
+ // try to keep the current position
+ std::string oldCategory;
+ if (m_iCategory >= 0 && m_iCategory < (int)m_categories.size())
+ oldCategory = m_categories[m_iCategory]->GetId();
+
+ SET_CONTROL_LABEL(CONTROL_BTN_LEVELS, 10036 + (int)CViewStateSettings::GetInstance().GetSettingLevel());
+ // only re-create the categories, the settings will be created later
+ SetupControls(false);
+
+ m_iCategory = 0;
+ // try to find the category that was previously selected
+ if (!oldCategory.empty())
+ {
+ for (int i = 0; i < (int)m_categories.size(); i++)
+ {
+ if (m_categories[i]->GetId() == oldCategory)
+ {
+ m_iCategory = i;
+ break;
+ }
+ }
+ }
+
+ CreateSettings();
+ return true;
+ }
+
+ default:
+ break;
+ }
+
+ return CGUIDialogSettingsManagerBase::OnAction(action);
+}
+
+bool CGUIWindowSettingsCategory::OnBack(int actionID)
+{
+ Save();
+ return CGUIDialogSettingsManagerBase::OnBack(actionID);
+}
+
+void CGUIWindowSettingsCategory::OnWindowLoaded()
+{
+ SET_CONTROL_LABEL(CONTROL_BTN_LEVELS, 10036 + (int)CViewStateSettings::GetInstance().GetSettingLevel());
+ CGUIDialogSettingsManagerBase::OnWindowLoaded();
+}
+
+int CGUIWindowSettingsCategory::GetSettingLevel() const
+{
+ return (int)CViewStateSettings::GetInstance().GetSettingLevel();
+}
+
+SettingSectionPtr CGUIWindowSettingsCategory::GetSection()
+{
+ for (const SettingGroup& settingGroup : s_settingGroupMap)
+ {
+ if (settingGroup.id == m_iSection)
+ return m_settings->GetSection(settingGroup.name);
+ }
+
+ return NULL;
+}
+
+bool CGUIWindowSettingsCategory::Save()
+{
+ m_settings->Save();
+
+ return true;
+}
+
+CSettingsManager* CGUIWindowSettingsCategory::GetSettingsManager() const
+{
+ return m_settings->GetSettingsManager();
+}
+
+void CGUIWindowSettingsCategory::FocusElement(const std::string& elementId)
+{
+ for (size_t i = 0; i < m_categories.size(); ++i)
+ {
+ if (m_categories[i]->GetId() == elementId)
+ {
+ SET_CONTROL_FOCUS(CONTROL_SETTINGS_START_BUTTONS + i, 0);
+ return;
+ }
+ for (const auto& group: m_categories[i]->GetGroups())
+ {
+ for (const auto& setting : group->GetSettings())
+ {
+ if (setting->GetId() == elementId)
+ {
+ SET_CONTROL_FOCUS(CONTROL_SETTINGS_START_BUTTONS + i, 0);
+
+ auto control = GetSettingControl(elementId);
+ if (control)
+ SET_CONTROL_FOCUS(control->GetID(), 0);
+ else
+ CLog::Log(LOGERROR,
+ "CGUIWindowSettingsCategory: failed to get control for setting '{}'.",
+ elementId);
+ return;
+ }
+ }
+ }
+ }
+ CLog::Log(LOGERROR,
+ "CGUIWindowSettingsCategory: failed to set focus. unknown category/setting id '{}'.",
+ elementId);
+}
diff --git a/xbmc/settings/windows/GUIWindowSettingsCategory.h b/xbmc/settings/windows/GUIWindowSettingsCategory.h
new file mode 100644
index 0000000..88c3ac8
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettingsCategory.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2005-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/dialogs/GUIDialogSettingsManagerBase.h"
+
+class CSettings;
+
+class CGUIWindowSettingsCategory : public CGUIDialogSettingsManagerBase
+{
+public:
+ CGUIWindowSettingsCategory();
+ ~CGUIWindowSettingsCategory() override;
+
+ // specialization of CGUIControl
+ bool OnMessage(CGUIMessage &message) override;
+ bool OnAction(const CAction &action) override;
+ bool OnBack(int actionID) override;
+ int GetID() const override { return CGUIDialogSettingsManagerBase::GetID() + m_iSection; }
+
+ // specialization of CGUIWindow
+ bool IsDialog() const override { return false; }
+
+protected:
+ // specialization of CGUIWindow
+ void OnWindowLoaded() override;
+
+ // implementation of CGUIDialogSettingsBase
+ int GetSettingLevel() const override;
+ std::shared_ptr<CSettingSection> GetSection() override;
+ bool Save() override;
+
+ // implementation of CGUIDialogSettingsManagerBase
+ CSettingsManager* GetSettingsManager() const override;
+
+ /*!
+ * Set focus to a category or setting in this window. The setting/category must be active in the
+ * current level.
+ */
+ void FocusElement(const std::string& elementId);
+
+ std::shared_ptr<CSettings> m_settings;
+ int m_iSection = 0;
+ bool m_returningFromSkinLoad = false; // true if we are returning from loading the skin
+};
diff --git a/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp
new file mode 100644
index 0000000..7e67de6
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2005-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 "GUIWindowSettingsScreenCalibration.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMoverControl.h"
+#include "guilib/GUIResizeControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SubtitlesSettings.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include <string>
+#include <utility>
+
+using namespace KODI;
+
+namespace
+{
+constexpr int CONTROL_LABEL_RES = 2;
+constexpr int CONTROL_LABEL_DESCRIPTION = 3;
+constexpr int CONTROL_LABEL_VALUE = 4;
+constexpr int CONTROL_TOP_LEFT = 8;
+constexpr int CONTROL_BOTTOM_RIGHT = 9;
+constexpr int CONTROL_SUBTITLES = 10;
+constexpr int CONTROL_PIXEL_RATIO = 11;
+constexpr int CONTROL_RESET = 12;
+constexpr int CONTROL_VIDEO = 20;
+
+constexpr int DEFAULT_GUI_HEIGHT = 1080;
+constexpr int DEFAULT_GUI_WIDTH = 1920;
+
+// Fixed transparent space of the subtitle bar (on top + below) for touch screen
+// must match with the space of the skin bar image
+constexpr int CONTROL_SUBTITLES_SPACE = 80;
+} // unnamed namespace
+
+CGUIWindowSettingsScreenCalibration::CGUIWindowSettingsScreenCalibration(void)
+ : CGUIWindow(WINDOW_SCREEN_CALIBRATION, "SettingsScreenCalibration.xml")
+{
+ m_iCurRes = 0;
+ m_iControl = 0;
+ m_fPixelRatioBoxHeight = 0.0f;
+ m_needsScaling = false; // we handle all the scaling
+}
+
+CGUIWindowSettingsScreenCalibration::~CGUIWindowSettingsScreenCalibration(void) = default;
+
+
+void CGUIWindowSettingsScreenCalibration::ResetCalibration()
+{
+ // We ask to reset the calibration
+ // Reset will be applied to: windowed mode or per fullscreen resolution
+ CGUIDialogYesNo* pDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
+ pDialog->SetHeading(CVariant{20325});
+ std::string strText = StringUtils::Format(
+ g_localizeStrings.Get(20326),
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]).strMode);
+ pDialog->SetText(CVariant{std::move(strText)});
+ pDialog->SetChoice(0, CVariant{222});
+ pDialog->SetChoice(1, CVariant{186});
+ pDialog->Open();
+ if (pDialog->IsConfirmed())
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetScreenParameters(m_Res[m_iCurRes]);
+ ResetControls();
+ // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(
+ GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE);
+ }
+}
+
+bool CGUIWindowSettingsScreenCalibration::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_CALIBRATE_SWAP_ARROWS:
+ {
+ NextControl();
+ return true;
+ }
+ break;
+
+ case ACTION_CALIBRATE_RESET:
+ {
+ ResetCalibration();
+ return true;
+ }
+ break;
+
+ case ACTION_CHANGE_RESOLUTION:
+ // choose the next resolution in our list
+ {
+ m_iCurRes = (m_iCurRes + 1) % m_Res.size();
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(m_Res[m_iCurRes], false);
+ ResetControls();
+ // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(
+ GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE);
+ return true;
+ }
+ break;
+
+ // ignore all gesture meta actions
+ case ACTION_GESTURE_BEGIN:
+ case ACTION_GESTURE_END:
+ case ACTION_GESTURE_ABORT:
+ case ACTION_GESTURE_NOTIFY:
+ case ACTION_GESTURE_PAN:
+ case ACTION_GESTURE_ROTATE:
+ case ACTION_GESTURE_ZOOM:
+ return true;
+
+ case ACTION_MOUSE_LEFT_CLICK:
+ case ACTION_TOUCH_TAP:
+ if (GetFocusedControlID() == CONTROL_RESET)
+ {
+ ResetCalibration();
+ return true;
+ }
+ break;
+ }
+
+ // if we see a mouse move event without dx and dy (amount2 and amount3) these
+ // are the focus actions which are generated on touch events and those should
+ // be eaten/ignored here. Else we will switch to the screencalibration controls
+ // which are at that x/y value on each touch/tap/swipe which makes the whole window
+ // unusable for touch screens
+ if (action.GetID() == ACTION_MOUSE_MOVE && action.GetAmount(2) == 0 && action.GetAmount(3) == 0)
+ return true;
+
+ return CGUIWindow::OnAction(action); // base class to handle basic movement etc.
+}
+
+void CGUIWindowSettingsScreenCalibration::AllocResources(bool forceLoad)
+{
+ CGUIWindow::AllocResources(forceLoad);
+}
+
+void CGUIWindowSettingsScreenCalibration::FreeResources(bool forceUnload)
+{
+ CGUIWindow::FreeResources(forceUnload);
+}
+
+
+bool CGUIWindowSettingsScreenCalibration::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ CDisplaySettings::GetInstance().UpdateCalibrations();
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCalibrating(false);
+ // reset our screen resolution to what it was initially
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(
+ CDisplaySettings::GetInstance().GetCurrentResolution(), false);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(
+ GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE);
+ }
+ break;
+
+ case GUI_MSG_WINDOW_INIT:
+ {
+ CGUIWindow::OnMessage(message);
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCalibrating(true);
+
+ // Get the default XML size values of controls,
+ // we will use these values to scale controls when the resolution change
+ for (int id = CONTROL_TOP_LEFT; id <= CONTROL_RESET; id++)
+ {
+ CGUIControl* control = GetControl(id);
+ if (control)
+ {
+ m_controlsSize.emplace(id, std::make_pair(control->GetHeight(), control->GetWidth()));
+ }
+ }
+
+ // Get the allowable resolutions that we can calibrate...
+ m_Res.clear();
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ bool isPlayingVideo{appPlayer->IsPlayingVideo()};
+ if (isPlayingVideo)
+ { // don't allow resolution switching if we are playing a video
+
+ appPlayer->TriggerUpdateResolution();
+
+ m_iCurRes = 0;
+ m_Res.push_back(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
+ SET_CONTROL_VISIBLE(CONTROL_VIDEO);
+ }
+ else
+ {
+ SET_CONTROL_HIDDEN(CONTROL_VIDEO);
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(m_Res);
+ // find our starting resolution
+ m_iCurRes = FindCurrentResolution();
+ }
+
+ // Setup the first control
+ m_iControl = CONTROL_TOP_LEFT;
+
+ m_isSubtitleBarEnabled =
+ !(CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetAlignment() !=
+ SUBTITLES::Align::MANUAL &&
+ isPlayingVideo);
+
+ ResetControls();
+ return true;
+ }
+ break;
+ case GUI_MSG_CLICKED:
+ {
+ // On click event select the next control
+ NextControl();
+ }
+ break;
+ case GUI_MSG_NOTIFY_ALL:
+ {
+ if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE &&
+ message.GetSenderId() != WINDOW_SCREEN_CALIBRATION && IsActive())
+ {
+ m_Res.clear();
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(m_Res);
+ m_iCurRes = FindCurrentResolution();
+ ResetControls();
+ }
+ }
+ break;
+ // send before touch for requesting gesture features - we don't want this
+ // it would result in unfocus in the onmessage below ...
+ case GUI_MSG_GESTURE_NOTIFY:
+ // send after touch for unfocussing - we don't want this in this window!
+ case GUI_MSG_UNFOCUS_ALL:
+ return true;
+ break;
+ }
+ return CGUIWindow::OnMessage(message);
+}
+
+unsigned int CGUIWindowSettingsScreenCalibration::FindCurrentResolution()
+{
+ RESOLUTION curRes = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ for (size_t i = 0; i < m_Res.size(); i++)
+ {
+ // If it's a CUSTOM (monitor) resolution, then CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions()
+ // returns just one entry with CUSTOM in it. Update that entry to point to the current
+ // CUSTOM resolution.
+ if (curRes >= RES_CUSTOM)
+ {
+ if (m_Res[i] == RES_CUSTOM)
+ {
+ m_Res[i] = curRes;
+ return i;
+ }
+ }
+ else if (m_Res[i] == CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution())
+ return i;
+ }
+ CLog::Log(LOGERROR, "CALIBRATION: Reported current resolution: {}",
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
+ CLog::Log(LOGERROR,
+ "CALIBRATION: Could not determine current resolution, falling back to default");
+ return 0;
+}
+
+void CGUIWindowSettingsScreenCalibration::NextControl()
+{ // set the old control invisible and not focused, and choose the next control
+ CGUIControl* pControl = GetControl(m_iControl);
+ if (pControl)
+ {
+ pControl->SetVisible(false);
+ pControl->SetFocus(false);
+ }
+ // If the current control is the reset button
+ // ask to reset the calibration settings
+ if (m_iControl == CONTROL_RESET)
+ {
+ ResetCalibration();
+ }
+ // switch to the next control
+ m_iControl++;
+ if (m_iControl > CONTROL_RESET)
+ m_iControl = CONTROL_TOP_LEFT;
+ // enable the new control
+ EnableControl(m_iControl);
+}
+
+void CGUIWindowSettingsScreenCalibration::EnableControl(int iControl)
+{
+ SET_CONTROL_VISIBLE(CONTROL_TOP_LEFT);
+ SET_CONTROL_VISIBLE(CONTROL_BOTTOM_RIGHT);
+ SET_CONTROL_VISIBLE(CONTROL_SUBTITLES);
+ SET_CONTROL_VISIBLE(CONTROL_PIXEL_RATIO);
+ SET_CONTROL_VISIBLE(CONTROL_RESET);
+ SET_CONTROL_FOCUS(iControl, 0);
+}
+
+void CGUIWindowSettingsScreenCalibration::ResetControls()
+{
+ // disable the video control, so that our other controls take mouse clicks etc.
+ CONTROL_DISABLE(CONTROL_VIDEO);
+ // disable the UI calibration for our controls
+ // and set their limits
+ // also, set them to invisible if they don't have focus
+ CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_TOP_LEFT));
+ RESOLUTION_INFO info =
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]);
+
+ m_subtitleVerticalMargin = static_cast<int>(
+ static_cast<float>(info.iHeight) / 100 *
+ CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetVerticalMarginPerc());
+
+ if (pControl)
+ {
+ pControl->SetLimits(-info.iWidth / 4, -info.iHeight / 4, info.iWidth / 4, info.iHeight / 4);
+ auto& size = m_controlsSize[CONTROL_TOP_LEFT];
+ pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight);
+ pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth);
+ pControl->SetPosition(static_cast<float>(info.Overscan.left),
+ static_cast<float>(info.Overscan.top));
+ pControl->SetLocation(info.Overscan.left, info.Overscan.top, false);
+ }
+ pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_BOTTOM_RIGHT));
+ if (pControl)
+ {
+ pControl->SetLimits(info.iWidth * 3 / 4, info.iHeight * 3 / 4, info.iWidth * 5 / 4,
+ info.iHeight * 5 / 4);
+ auto& size = m_controlsSize[CONTROL_BOTTOM_RIGHT];
+ pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight);
+ pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth);
+ pControl->SetPosition(static_cast<float>(info.Overscan.right) - pControl->GetWidth(),
+ static_cast<float>(info.Overscan.bottom) - pControl->GetHeight());
+ pControl->SetLocation(info.Overscan.right, info.Overscan.bottom, false);
+ }
+ // Subtitles and OSD controls can only move up and down
+ pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_SUBTITLES));
+ if (pControl)
+ {
+ auto& size = m_controlsSize[CONTROL_SUBTITLES];
+ float scaledHeight = size.first / DEFAULT_GUI_HEIGHT * info.iHeight;
+ float scaledSpace =
+ static_cast<float>(CONTROL_SUBTITLES_SPACE) / DEFAULT_GUI_HEIGHT * info.iHeight;
+ m_subtitlesHalfSpace = static_cast<int>(scaledSpace / 2);
+ int barHeight = static_cast<int>(scaledHeight - scaledSpace);
+ pControl->SetLimits(0, m_subtitlesHalfSpace + barHeight + info.Overscan.top, 0,
+ info.Overscan.bottom + m_subtitlesHalfSpace);
+ pControl->SetHeight(scaledHeight);
+ pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth);
+ // If the vertical margin has been changed from the previous calibration,
+ // the text bar could appear offscreen, then force move to visible area
+ if (info.iSubtitles - m_subtitleVerticalMargin > info.iHeight)
+ info.iSubtitles = info.Overscan.bottom;
+ // We want the text to be at the base of the bar,
+ // then we shift the position to include the vertical margin
+ pControl->SetPosition((info.iWidth - pControl->GetWidth()) * 0.5f,
+ info.iSubtitles - pControl->GetHeight() + m_subtitlesHalfSpace -
+ m_subtitleVerticalMargin);
+ pControl->SetLocation(0, info.iSubtitles + m_subtitlesHalfSpace - m_subtitleVerticalMargin,
+ false);
+ pControl->SetEnabled(m_isSubtitleBarEnabled);
+ }
+ // The pixel ratio control
+ CGUIResizeControl* pResize = dynamic_cast<CGUIResizeControl*>(GetControl(CONTROL_PIXEL_RATIO));
+ if (pResize)
+ {
+ pResize->SetLimits(info.iWidth * 0.25f, info.iHeight * 0.5f, info.iWidth * 0.75f,
+ info.iHeight * 0.5f);
+ pResize->SetHeight(info.iHeight * 0.5f);
+ pResize->SetWidth(pResize->GetHeight() / info.fPixelRatio);
+ pResize->SetPosition((info.iWidth - pResize->GetWidth()) / 2,
+ (info.iHeight - pResize->GetHeight()) / 2);
+ }
+ // The calibration reset
+ pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET));
+ if (pControl)
+ {
+ auto& size = m_controlsSize[CONTROL_RESET];
+ pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight);
+ pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth);
+ float posX = 0;
+ float posY = info.iHeight - pControl->GetHeight();
+ pControl->SetLimits(posX, posY, posX, posY);
+ pControl->SetPosition(posX, posY);
+ pControl->SetLocation(posX, posY, false);
+ }
+ // Enable the default control
+ EnableControl(m_iControl);
+}
+
+bool CGUIWindowSettingsScreenCalibration::UpdateFromControl(int iControl)
+{
+ RESOLUTION_INFO info =
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]);
+ RESOLUTION_INFO infoPrev = info;
+ std::string labelDescription;
+ std::string labelValue;
+
+ if (iControl == CONTROL_PIXEL_RATIO)
+ {
+ CGUIControl* pControl = GetControl(CONTROL_PIXEL_RATIO);
+ if (pControl)
+ {
+ float fWidth = pControl->GetWidth();
+ float fHeight = pControl->GetHeight();
+ info.fPixelRatio = fHeight / fWidth;
+ // recenter our control...
+ pControl->SetPosition((static_cast<float>(info.iWidth) - pControl->GetWidth()) / 2,
+ (static_cast<float>(info.iHeight) - pControl->GetHeight()) / 2);
+ labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(272),
+ g_localizeStrings.Get(273));
+ labelValue = StringUtils::Format("{:5.3f}", info.fPixelRatio);
+ labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue);
+ }
+ }
+ else
+ {
+ CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(iControl));
+ if (pControl)
+ {
+ switch (iControl)
+ {
+ case CONTROL_TOP_LEFT:
+ {
+ info.Overscan.left = pControl->GetXLocation();
+ info.Overscan.top = pControl->GetYLocation();
+ labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(274),
+ g_localizeStrings.Get(276));
+ labelValue =
+ StringUtils::Format("{}, {}", pControl->GetXLocation(), pControl->GetYLocation());
+ labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue);
+ // Update reset control position
+ CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET));
+ if (pControl)
+ {
+ float posX = info.Overscan.left;
+ float posY = info.Overscan.bottom - pControl->GetHeight();
+ pControl->SetLimits(posX, posY, posX, posY);
+ pControl->SetPosition(posX, posY);
+ pControl->SetLocation(posX, posY, false);
+ }
+ }
+ break;
+
+ case CONTROL_BOTTOM_RIGHT:
+ {
+ info.Overscan.right = pControl->GetXLocation();
+ info.Overscan.bottom = pControl->GetYLocation();
+ int iXOff1 = info.iWidth - pControl->GetXLocation();
+ int iYOff1 = info.iHeight - pControl->GetYLocation();
+ labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(275),
+ g_localizeStrings.Get(276));
+ labelValue = StringUtils::Format("{}, {}", iXOff1, iYOff1);
+ labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue);
+ // Update reset control position
+ pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET));
+ if (pControl)
+ {
+ float posX = info.Overscan.left;
+ float posY = info.Overscan.bottom - pControl->GetHeight();
+ pControl->SetLimits(posX, posY, posX, posY);
+ pControl->SetPosition(posX, posY);
+ pControl->SetLocation(posX, posY, false);
+ }
+ }
+ break;
+
+ case CONTROL_SUBTITLES:
+ {
+ if (m_isSubtitleBarEnabled)
+ {
+ info.iSubtitles =
+ pControl->GetYLocation() - m_subtitlesHalfSpace + m_subtitleVerticalMargin;
+
+ labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(277),
+ g_localizeStrings.Get(278));
+ labelValue = StringUtils::Format(g_localizeStrings.Get(39184), info.iSubtitles,
+ info.iSubtitles - m_subtitleVerticalMargin);
+ }
+ else
+ {
+ labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(277),
+ g_localizeStrings.Get(39189));
+ }
+ }
+ break;
+
+ case CONTROL_RESET:
+ {
+ labelDescription = g_localizeStrings.Get(20325);
+ }
+ break;
+ }
+ }
+ }
+
+ SET_CONTROL_LABEL(CONTROL_LABEL_DESCRIPTION, labelDescription);
+ SET_CONTROL_LABEL(CONTROL_LABEL_VALUE, labelValue);
+
+ // Set resolution info text
+ std::string resInfo;
+ if (CServiceBroker::GetWinSystem()->IsFullScreen())
+ {
+ resInfo =
+ StringUtils::Format("{} {}x{}@{:.2f} - {}", g_localizeStrings.Get(13287), info.iScreenWidth,
+ info.iScreenHeight, info.fRefreshRate, g_localizeStrings.Get(244));
+ }
+ else
+ {
+ resInfo = StringUtils::Format("{} {}x{} - {}", g_localizeStrings.Get(13287), info.iScreenWidth,
+ info.iScreenHeight, g_localizeStrings.Get(242));
+ }
+ SET_CONTROL_LABEL(CONTROL_LABEL_RES, resInfo);
+
+ // Detect overscan changes
+ bool isOverscanChanged = info.Overscan != infoPrev.Overscan;
+
+ // Adjust subtitle bar position due to overscan changes
+ if (isOverscanChanged)
+ {
+ CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_SUBTITLES));
+ if (pControl)
+ {
+ // Keep the subtitle bar within the overscan boundary
+ if (info.Overscan.bottom + m_subtitleVerticalMargin < info.iSubtitles)
+ {
+ info.iSubtitles = info.Overscan.bottom + m_subtitleVerticalMargin;
+
+ // We want the text to be at the base of the bar,
+ // then we shift the position to include the vertical margin
+ pControl->SetPosition((info.iWidth - pControl->GetWidth()) * 0.5f,
+ info.iSubtitles - pControl->GetHeight() + m_subtitlesHalfSpace -
+ m_subtitleVerticalMargin);
+ pControl->SetLocation(0, info.iSubtitles + m_subtitlesHalfSpace - m_subtitleVerticalMargin,
+ false);
+ }
+
+ // Recalculate limits based on overscan values
+ const auto& size = m_controlsSize[CONTROL_SUBTITLES];
+ const float scaledHeight = size.first / DEFAULT_GUI_HEIGHT * info.iHeight;
+ const float scaledSpace =
+ static_cast<float>(CONTROL_SUBTITLES_SPACE) / DEFAULT_GUI_HEIGHT * info.iHeight;
+
+ m_subtitlesHalfSpace = static_cast<int>(scaledSpace / 2);
+ const int barHeight = static_cast<int>(scaledHeight - scaledSpace);
+
+ pControl->SetLimits(0, m_subtitlesHalfSpace + barHeight + info.Overscan.top, 0,
+ info.Overscan.bottom + m_subtitlesHalfSpace);
+ }
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetResInfo(m_Res[m_iCurRes], info);
+
+ return isOverscanChanged;
+}
+
+void CGUIWindowSettingsScreenCalibration::FrameMove()
+{
+ m_iControl = GetFocusedControlID();
+ if (m_iControl >= 0)
+ {
+ if (UpdateFromControl(m_iControl))
+ {
+ // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(
+ GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE);
+ }
+ }
+ else
+ {
+ SET_CONTROL_LABEL(CONTROL_LABEL_DESCRIPTION, "");
+ SET_CONTROL_LABEL(CONTROL_LABEL_VALUE, "");
+ SET_CONTROL_LABEL(CONTROL_LABEL_RES, "");
+ }
+ CGUIWindow::FrameMove();
+}
+
+void CGUIWindowSettingsScreenCalibration::DoProcess(unsigned int currentTime,
+ CDirtyRegionList& dirtyregions)
+{
+ MarkDirtyRegion();
+
+ for (int i = CONTROL_TOP_LEFT; i <= CONTROL_RESET; i++)
+ SET_CONTROL_HIDDEN(i);
+ m_needsScaling = true;
+ CGUIWindow::DoProcess(currentTime, dirtyregions);
+ m_needsScaling = false;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_Res[m_iCurRes], false);
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform();
+
+ // process the movers etc.
+ for (int i = CONTROL_TOP_LEFT; i <= CONTROL_RESET; i++)
+ {
+ SET_CONTROL_VISIBLE(i);
+ CGUIControl* control = GetControl(i);
+ if (control)
+ control->DoProcess(currentTime, dirtyregions);
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+void CGUIWindowSettingsScreenCalibration::DoRender()
+{
+ // we set that we need scaling here to render so that anything else on screen scales correctly
+ m_needsScaling = true;
+ CGUIWindow::DoRender();
+ m_needsScaling = false;
+}
diff --git a/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h
new file mode 100644
index 0000000..dd3e6b1
--- /dev/null
+++ b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-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 "guilib/GUIWindow.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+class CGUIWindowSettingsScreenCalibration : public CGUIWindow
+{
+public:
+ CGUIWindowSettingsScreenCalibration(void);
+ ~CGUIWindowSettingsScreenCalibration(void) override;
+ bool OnMessage(CGUIMessage& message) override;
+ bool OnAction(const CAction& action) override;
+ void DoProcess(unsigned int currentTime, CDirtyRegionList& dirtyregions) override;
+ void FrameMove() override;
+ void DoRender() override;
+ void AllocResources(bool forceLoad = false) override;
+ void FreeResources(bool forceUnLoad = false) override;
+
+protected:
+ unsigned int FindCurrentResolution();
+ void NextControl();
+ void ResetControls();
+ void EnableControl(int iControl);
+ bool UpdateFromControl(int iControl);
+ void ResetCalibration();
+ unsigned int m_iCurRes;
+ std::vector<RESOLUTION> m_Res;
+ int m_iControl;
+ float m_fPixelRatioBoxHeight;
+
+private:
+ std::map<int, std::pair<float, float>> m_controlsSize;
+ int m_subtitlesHalfSpace{0};
+ int m_subtitleVerticalMargin{0};
+ bool m_isSubtitleBarEnabled{false};
+};