/* * 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 #include 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 static CFileItemPtr GetFileItem(const std::string& label, const std::string& label2, const TValueType& value, const std::vector>& properties, const std::set& 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 static bool CompareSettingOptionAseconding(const SettingOption& lhs, const SettingOption& rhs) { return StringUtils::CompareNoCase(lhs.label, rhs.label) < 0; } template 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& selectedOptions, ILocalizer* localizer, bool updateOptions) { std::shared_ptr pSettingInt = NULL; if (setting->GetType() == SettingType::Integer) pSettingInt = std::static_pointer_cast(setting); else if (setting->GetType() == SettingType::List) { std::shared_ptr settingList = std::static_pointer_cast(setting); if (settingList->GetElementType() != SettingType::Integer) return false; pSettingInt = std::static_pointer_cast(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(pSettingInt)->UpdateDynamicOptions(); else settingOptions = pSettingInt->GetDynamicOptions(); options.insert(options.end(), settingOptions.begin(), settingOptions.end()); break; } case SettingOptionsType::Unknown: default: { std::shared_ptr control = std::static_pointer_cast(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); break; case SettingOptionsSort::Descending: std::sort(options.begin(), options.end(), CompareSettingOptionDeseconding); 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 list = CSettingUtils::GetList(std::static_pointer_cast(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& selectedOptions, ILocalizer* localizer, bool updateOptions) { std::shared_ptr pSettingString = NULL; if (setting->GetType() == SettingType::String) pSettingString = std::static_pointer_cast(setting); else if (setting->GetType() == SettingType::List) { std::shared_ptr settingList = std::static_pointer_cast(setting); if (settingList->GetElementType() != SettingType::String) return false; pSettingString = std::static_pointer_cast(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(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); break; case SettingOptionsSort::Descending: std::sort(options.begin(), options.end(), CompareSettingOptionDeseconding); 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 list = CSettingUtils::GetList(std::static_pointer_cast(setting)); for (const auto& itValue : list) selectedOptions.insert(itValue.asString()); } else return false; return true; } CGUIControlBaseSetting::CGUIControlBaseSetting(int id, std::shared_ptr 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 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(m_pSetting) ->SetValue(!std::static_pointer_cast(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(m_pSetting)->GetValue()); } CGUIControlColorButtonSetting::CGUIControlColorButtonSetting( CGUIColorButtonControl* pColorControl, int id, const std::shared_ptr& 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 settingHexColor = std::static_pointer_cast(m_pSetting); CGUIDialogColorPicker* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow( 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(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(m_pSetting)->GetValue()); } CGUIControlSpinExSetting::CGUIControlSpinExSetting(CGUISpinControlEx* pSpin, int id, std::shared_ptr 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 pSettingNumber = std::static_pointer_cast(m_pSetting); m_pSpin->SetType(SPIN_CONTROL_TYPE_FLOAT); m_pSpin->SetFloatRange(static_cast(pSettingNumber->GetMinimum()), static_cast(pSettingNumber->GetMaximum())); m_pSpin->SetFloatInterval(static_cast(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 pSettingNumber = std::static_pointer_cast(m_pSetting); std::shared_ptr control = std::static_pointer_cast(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(m_pSetting)->SetValue(m_pSpin->GetValue())); break; case SettingType::Number: { auto pSettingNumber = std::static_pointer_cast(m_pSetting); const auto& controlFormat = m_pSetting->GetControl()->GetFormat(); if (controlFormat == "number") SetValid(pSettingNumber->SetValue(static_cast(m_pSpin->GetFloatValue()))); else SetValid(pSettingNumber->SetValue(pSettingNumber->GetMinimum() + pSettingNumber->GetStep() * m_pSpin->GetValue())); break; } case SettingType::String: SetValid(std::static_pointer_cast(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 pSettingNumber = std::static_pointer_cast(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 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 pSettingNumber = std::static_pointer_cast(m_pSetting); std::shared_ptr control = std::static_pointer_cast(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 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 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( WINDOW_DIALOG_SELECT); if (dialog == NULL) return false; CFileItemList options; std::shared_ptr control = std::static_pointer_cast(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 settingList = std::static_pointer_cast(m_pSetting); if (settingList->GetElementType() == SettingType::String) { bAllowNewOption = std::static_pointer_cast(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 list = CSettingUtils::GetList(std::static_pointer_cast(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 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(m_pSetting) ->SetValue((int)values.at(0).asInteger()); break; case SettingType::String: if (values.size() > 1) return false; ret = std::static_pointer_cast(m_pSetting)->SetValue(values.at(0).asString()); break; case SettingType::List: ret = CSettingUtils::SetList(std::static_pointer_cast(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 control = std::static_pointer_cast(m_pSetting->GetControl()); bool optionsValid = GetItems(m_pSetting, options, !updateDisplayOnly); bool bAllowNewOption = false; if (m_pSetting->GetType() == SettingType::List) { std::shared_ptr settingList = std::static_pointer_cast(m_pSetting); if (settingList->GetElementType() == SettingType::String) { bAllowNewOption = std::static_pointer_cast(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 settingList = std::static_pointer_cast(m_pSetting); label2 = settingList->ToString(); } if (label2.empty()) { std::vector 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 control = std::static_pointer_cast(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(setting)->GetElementType() == SettingType::Integer)) return GetIntegerItems(setting, items, updateItems); else if (setting->GetType() == SettingType::String || (setting->GetType() == SettingType::List && std::static_pointer_cast(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 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 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 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 control = m_pSetting->GetControl(); const std::string& controlType = control->GetType(); const std::string& controlFormat = control->GetFormat(); if (controlType == "button") { std::shared_ptr buttonControl = std::static_pointer_cast(control); if (controlFormat == "addon") { // prompt for the addon std::shared_ptr setting; std::vector addonIDs; if (m_pSetting->GetType() == SettingType::List) { std::shared_ptr settingList = std::static_pointer_cast(m_pSetting); setting = std::static_pointer_cast(settingList->GetDefinition()); for (const SettingPtr& addon : settingList->GetValue()) addonIDs.push_back(std::static_pointer_cast(addon)->GetValue()); } else { setting = std::static_pointer_cast(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(m_pSetting)->FromString(addonIDs); else SetValid(setting->SetValue(addonIDs[0])); } else if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image") SetValid(GetPath(std::static_pointer_cast(m_pSetting), m_localizer)); else if (controlFormat == "date") { std::shared_ptr settingDate = std::static_pointer_cast(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 settingTime = std::static_pointer_cast(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 settingInt = std::static_pointer_cast(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 settingNumber = std::static_pointer_cast(m_pSetting); value = (float)settingNumber->GetValue(); min = (float)settingNumber->GetMinimum(); step = (float)settingNumber->GetStep(); max = (float)settingNumber->GetMaximum(); } else return false; std::shared_ptr sliderControl = std::static_pointer_cast(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 control = m_pSetting->GetControl(); const std::string& controlType = control->GetType(); const std::string& controlFormat = control->GetFormat(); if (controlType == "button") { if (!std::static_pointer_cast(control)->HideValue()) { auto setting = m_pSetting; if (m_pSetting->GetType() == SettingType::List) setting = std::static_pointer_cast(m_pSetting)->GetDefinition(); switch (setting->GetType()) { case SettingType::String: { if (controlFormat == "addon") { std::vector addonIDs; if (m_pSetting->GetType() == SettingType::List) { for (const auto& addonSetting : std::static_pointer_cast(m_pSetting)->GetValue()) addonIDs.push_back( std::static_pointer_cast(addonSetting)->GetValue()); } else addonIDs.push_back(std::static_pointer_cast(setting)->GetValue()); std::vector 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(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 settingInt = std::static_pointer_cast(m_pSetting); strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(), settingInt->GetMinimum(), settingInt->GetStep(), settingInt->GetMaximum(), m_localizer); break; } case SettingType::Number: { std::shared_ptr settingNumber = std::static_pointer_cast(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& pathSetting, ILocalizer* localizer) { if (pathSetting == NULL) return false; std::string path = pathSetting->GetValue(); VECSOURCES shares; bool localSharesOnly = false; const std::vector& 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 control = std::static_pointer_cast(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 * * *.png * * * ... */ 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 settingInt = std::static_pointer_cast(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 settingNumber = std::static_pointer_cast(m_pSetting); if (settingNumber->SetValue(static_cast(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& pSetting, ILocalizer* localizer) : CGUIControlBaseSetting(id, pSetting, localizer) { std::shared_ptr control = std::static_pointer_cast(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 urlEncodedSetting = std::static_pointer_cast(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 control = std::static_pointer_cast(m_pSetting->GetControl()); if (control->GetFormat() == "urlencoded") { std::shared_ptr urlEncodedSetting = std::static_pointer_cast(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(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 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 settingInt = std::static_pointer_cast(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 settingNumber = std::static_pointer_cast(m_pSetting); m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT); m_pSlider->SetFloatRange(static_cast(settingNumber->GetMinimum()), static_cast(settingNumber->GetMaximum())); m_pSlider->SetFloatInterval(static_cast(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(m_pSetting)->SetValue(m_pSlider->GetIntValue())); break; case SettingType::Number: SetValid(std::static_pointer_cast(m_pSetting) ->SetValue(static_cast(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 settingInt = std::static_pointer_cast(m_pSetting); int value; if (fromControl) value = m_pSlider->GetIntValue(); else { value = std::static_pointer_cast(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 settingNumber = std::static_pointer_cast(m_pSetting); double value; if (fromControl) value = static_cast(m_pSlider->GetFloatValue()); else { value = std::static_pointer_cast(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& 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(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(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 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 settingList = std::static_pointer_cast(m_pSetting); SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: { std::shared_ptr listDefintionInt = std::static_pointer_cast(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 listDefinitionNumber = std::static_pointer_cast(listDefintion); m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT); m_pSlider->SetFloatRange(static_cast(listDefinitionNumber->GetMinimum()), static_cast(listDefinitionNumber->GetMaximum())); m_pSlider->SetFloatInterval(static_cast(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 settingList = std::static_pointer_cast(m_pSetting); const SettingList& settingListValues = settingList->GetValue(); if (settingListValues.size() != 2) return false; std::vector 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 settingList = std::static_pointer_cast(m_pSetting); const SettingList& settingListValues = settingList->GetValue(); if (settingListValues.size() != 2) return; SettingConstPtr listDefintion = settingList->GetDefinition(); std::shared_ptr controlRange = std::static_pointer_cast(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(settingListValues[0])->GetValue(); valueUpper = std::static_pointer_cast(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(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower)); valueUpper = static_cast(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper)); } else { valueLower = std::static_pointer_cast(settingListValues[0])->GetValue(); valueUpper = std::static_pointer_cast(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 pSetting, ILocalizer* localizer) : CGUIControlBaseSetting(id, std::move(pSetting), localizer) { m_pButton = pButton; if (m_pButton == NULL) return; m_pButton->SetID(id); UpdateFromSetting(); }