diff options
Diffstat (limited to 'xbmc/settings/windows')
-rw-r--r-- | xbmc/settings/windows/CMakeLists.txt | 11 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIControlSettings.cpp | 1798 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIControlSettings.h | 366 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettings.cpp | 19 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettings.h | 19 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettingsCategory.cpp | 244 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettingsCategory.h | 51 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp | 630 | ||||
-rw-r--r-- | xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h | 47 |
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}; +}; |