From 3e1fdbde45cc275c10a581961bfb4e006527c38d Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:58:27 +0200 Subject: [PATCH] Add option to set and check list source settings by name This makes it easier to select the intended settings value as the underlying value often has no direct connection to the user facing name. It also makes it possible to select list entries whos underlying value changes frequently, but the user facing value does not. (E.g. device IDs based on the input port compared to the device name) --- data/locale/en-US.ini | 4 + lib/utils/ui-helpers.cpp | 14 ++ lib/utils/ui-helpers.hpp | 3 + plugins/base/macro-action-filter.cpp | 56 +++++- plugins/base/macro-action-filter.hpp | 1 + plugins/base/macro-action-source.cpp | 61 ++++++- plugins/base/macro-action-source.hpp | 1 + plugins/base/macro-condition-filter.cpp | 79 +++++++-- plugins/base/macro-condition-filter.hpp | 1 + plugins/base/macro-condition-source.cpp | 79 +++++++-- plugins/base/macro-condition-source.hpp | 1 + plugins/base/utils/source-setting.cpp | 222 +++++++++++++++++++++++- plugins/base/utils/source-setting.hpp | 13 +- tests/mocks/ui-helpers.cpp | 5 + 14 files changed, 492 insertions(+), 48 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 35927948..aad5dbaf 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -507,6 +507,7 @@ AdvSceneSwitcher.condition.source.type.showing="Is showing" AdvSceneSwitcher.condition.source.type.settingsMatch="Settings match" AdvSceneSwitcher.condition.source.type.settingsChanged="Settings changed" AdvSceneSwitcher.condition.source.type.individualSettingMatches="Setting value matches" +AdvSceneSwitcher.condition.source.type.individualSettingListSelectionMatches="Settings value list selection matches" AdvSceneSwitcher.condition.source.type.individualSettingChanged="Setting value changed" AdvSceneSwitcher.condition.source.type.width="Width" AdvSceneSwitcher.condition.source.type.height="Height" @@ -530,6 +531,7 @@ AdvSceneSwitcher.condition.filter.type.showing="Is disabled" AdvSceneSwitcher.condition.filter.type.settingsMatch="Settings match" AdvSceneSwitcher.condition.filter.type.settingsChanged="Settings changed" AdvSceneSwitcher.condition.filter.type.individualSettingMatches="Settings value matches" +AdvSceneSwitcher.condition.filter.type.individualSettingListSelectionMatches="Settings value list selection matches" AdvSceneSwitcher.condition.filter.type.individualSettingChanged="Settings value changed" AdvSceneSwitcher.condition.filter.refresh="Refresh" AdvSceneSwitcher.condition.filter.refresh.tooltip="Repopulate the filter settings selection with the settings of the filter which's name matches the variable value." @@ -887,6 +889,7 @@ AdvSceneSwitcher.action.filter.entry="On{{sources}}{{actions}}{{filters}}{{refre AdvSceneSwitcher.action.filter.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}" AdvSceneSwitcher.action.filter.getSettings="Get current settings" AdvSceneSwitcher.action.filter.inputMethod.individualManual="Set to fixed value" +AdvSceneSwitcher.action.filter.inputMethod.individualListEntryManual="Set to list entry" AdvSceneSwitcher.action.filter.inputMethod.individualTempvar="Set to macro property" AdvSceneSwitcher.action.filter.inputMethod.json="Set setting JSON string" AdvSceneSwitcher.action.source="Source" @@ -917,6 +920,7 @@ AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x="Yadif 2x" AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst="Top Field First" AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst="Bottom Field First" AdvSceneSwitcher.action.source.inputMethod.individualManual="Set to fixed value" +AdvSceneSwitcher.action.source.inputMethod.individualListEntryManual="Set to list entry" AdvSceneSwitcher.action.source.inputMethod.individualTempvar="Set to macro property" AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string" AdvSceneSwitcher.action.source.refresh="Refresh" diff --git a/lib/utils/ui-helpers.cpp b/lib/utils/ui-helpers.cpp index 902086aa..d40b2135 100644 --- a/lib/utils/ui-helpers.cpp +++ b/lib/utils/ui-helpers.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -104,6 +105,19 @@ int FindIdxInRagne(QComboBox *list, int start, int stop, return foundIdx; } +void SetRowVisibleByValue(QComboBox *list, const QString &value, bool show) +{ + int index = list->findText(value); + if (index == -1) { + return; + } + + auto view = qobject_cast(list->view()); + if (view) { + view->setRowHidden(index, !show); + } +} + QWidget *GetSettingsWindow() { return SettingsWindowIsOpened() ? AdvSceneSwitcher::window : nullptr; diff --git a/lib/utils/ui-helpers.hpp b/lib/utils/ui-helpers.hpp index 19c2ebae..6172a153 100644 --- a/lib/utils/ui-helpers.hpp +++ b/lib/utils/ui-helpers.hpp @@ -20,9 +20,12 @@ EXPORT QObject *HighlightWidget(QWidget *widget, QColor startColor, EXPORT void SetHeightToContentHeight(QListWidget *list); EXPORT void SetButtonIcon(QAbstractButton *button, const char *path); + EXPORT int FindIdxInRagne(QComboBox *list, int start, int stop, const std::string &value, Qt::MatchFlags = Qt::MatchExactly | Qt::MatchCaseSensitive); +EXPORT void SetRowVisibleByValue(QComboBox *list, const QString &value, + bool show); EXPORT bool DisplayMessage(const QString &msg, bool question = false, bool modal = true); diff --git a/plugins/base/macro-action-filter.cpp b/plugins/base/macro-action-filter.cpp index bddfd04d..c01c8b66 100644 --- a/plugins/base/macro-action-filter.cpp +++ b/plugins/base/macro-action-filter.cpp @@ -3,6 +3,7 @@ #include "json-helpers.hpp" #include "source-settings-helpers.hpp" #include "selection-helpers.hpp" +#include "ui-helpers.hpp" namespace advss { @@ -26,10 +27,13 @@ const static std::map actionTypes = { "AdvSceneSwitcher.action.filter.type.pressSettingsButton"}, }; -const static std::map +const static std::vector< + std::pair> inputMethods = { {MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL, "AdvSceneSwitcher.action.filter.inputMethod.individualManual"}, + {MacroActionFilter::SettingsInputMethod::INDIVIDUAL_LIST_ENTRY, + "AdvSceneSwitcher.action.filter.inputMethod.individualListEntryManual"}, {MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR, "AdvSceneSwitcher.action.filter.inputMethod.individualTempvar"}, {MacroActionFilter::SettingsInputMethod::JSON_STRING, @@ -59,6 +63,10 @@ static void performActionHelper( case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL: SetSourceSetting(source, setting, manualSettingValue); break; + case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_LIST_ENTRY: + SetSourceSettingListEntryValueByName( + source, setting, manualSettingValue); + break; case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR: { auto var = tempVar.GetTempVariable(macro); if (!var) { @@ -373,6 +381,16 @@ void MacroActionFilterEdit::GetSettingsClicked() .value_or("")); break; } + case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_LIST_ENTRY: { + const auto filters = + _entryData->_filter.GetFilters(_entryData->_source); + _manualSettingValue->setPlainText( + GetSourceSettingListEntryName( + filters.empty() ? nullptr : filters.at(0), + _entryData->_setting) + .value_or("")); + break; + } case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR: break; case MacroActionFilter::SettingsInputMethod::JSON_STRING: { @@ -415,6 +433,7 @@ void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting) { GUARD_LOADING_AND_LOCK(); _entryData->_setting = setting; + SetWidgetVisibility(); } void MacroActionFilterEdit::ManualSettingsValueChanged() @@ -440,6 +459,22 @@ void MacroActionFilterEdit::ButtonChanged(const SourceSettingButton &button) _entryData->_button = button; } +static QString GetIndividualListEntryName() +{ + static const auto matchesInput = + [](const std::pair &p) { + return p.first == + MacroActionFilter::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY; + }; + static const QString listValueText( + obs_module_text(std::find_if(inputMethods.begin(), + inputMethods.end(), matchesInput) + ->second.c_str())); + return listValueText; +} + void MacroActionFilterEdit::SetWidgetVisibility() { SetLayoutVisible(_settingsLayout, @@ -464,9 +499,16 @@ void MacroActionFilterEdit::SetWidgetVisibility() MacroActionFilter::SettingsInputMethod:: INDIVIDUAL_TEMPVAR); + SetRowVisibleByValue(_settingsInputMethods, + GetIndividualListEntryName(), + _entryData->_setting.IsList()); + if (_entryData->_action == MacroActionFilter::Action::SETTINGS && - _entryData->_settingsInputMethod == - MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL) { + (_entryData->_settingsInputMethod == + MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL || + _entryData->_settingsInputMethod == + MacroActionFilter::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY)) { RemoveStretchIfPresent(_settingsLayout); _manualSettingValue->show(); } else { @@ -475,8 +517,12 @@ void MacroActionFilterEdit::SetWidgetVisibility() } _refreshSettingSelection->setVisible( - _entryData->_settingsInputMethod == - MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL && + (_entryData->_settingsInputMethod == + MacroActionFilter::SettingsInputMethod:: + INDIVIDUAL_MANUAL || + _entryData->_settingsInputMethod == + MacroActionFilter::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY) && (_entryData->_source.GetType() == SourceSelection::Type::VARIABLE || _entryData->_filter.GetType() == diff --git a/plugins/base/macro-action-filter.hpp b/plugins/base/macro-action-filter.hpp index e6eebed4..19da4425 100644 --- a/plugins/base/macro-action-filter.hpp +++ b/plugins/base/macro-action-filter.hpp @@ -36,6 +36,7 @@ public: INDIVIDUAL_MANUAL, INDIVIDUAL_TEMPVAR, JSON_STRING, + INDIVIDUAL_LIST_ENTRY, }; SettingsInputMethod _settingsInputMethod = SettingsInputMethod::INDIVIDUAL_MANUAL; diff --git a/plugins/base/macro-action-source.cpp b/plugins/base/macro-action-source.cpp index a3abd4f1..f7ae5743 100644 --- a/plugins/base/macro-action-source.cpp +++ b/plugins/base/macro-action-source.cpp @@ -3,6 +3,7 @@ #include "json-helpers.hpp" #include "selection-helpers.hpp" #include "source-settings-helpers.hpp" +#include "ui-helpers.hpp" #include @@ -67,10 +68,13 @@ const static std::map "AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst"}, }; -const static std::map +static const std::vector< + std::pair> inputMethods = { {MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL, "AdvSceneSwitcher.action.source.inputMethod.individualManual"}, + {MacroActionSource::SettingsInputMethod::INDIVIDUAL_LIST_ENTRY, + "AdvSceneSwitcher.action.source.inputMethod.individualListEntryManual"}, {MacroActionSource::SettingsInputMethod::INDIVIDUAL_TEMPVAR, "AdvSceneSwitcher.action.source.inputMethod.individualTempvar"}, {MacroActionSource::SettingsInputMethod::JSON_STRING, @@ -119,6 +123,10 @@ bool MacroActionSource::PerformAction() case SettingsInputMethod::INDIVIDUAL_MANUAL: SetSourceSetting(s, _setting, _manualSettingValue); break; + case SettingsInputMethod::INDIVIDUAL_LIST_ENTRY: + SetSourceSettingListEntryValueByName( + s, _setting, _manualSettingValue); + break; case SettingsInputMethod::INDIVIDUAL_TEMPVAR: { auto var = _tempVar.GetTempVariable(GetMacro()); if (!var) { @@ -263,9 +271,8 @@ static inline void populateActionSelection(QComboBox *list) } } -template -static inline void populateModeSelection(QComboBox *list, - const std::map &modes) +template +static inline void populateModeSelection(QComboBox *list, const T &modes) { list->clear(); for (const auto &[value, name] : modes) { @@ -343,7 +350,7 @@ MacroActionSourceEdit::MacroActionSourceEdit( auto entryLayout = new QHBoxLayout; entryLayout->setContentsMargins(0, 0, 0, 0); - std::unordered_map widgetPlaceholders = { + const std::unordered_map widgetPlaceholders = { {"{{sources}}", _sources}, {"{{actions}}", _actions}, {"{{settings}}", _sourceSettings}, @@ -444,6 +451,13 @@ void MacroActionSourceEdit::GetSettingsClicked() _entryData->_setting) .value_or("")); break; + case MacroActionSource::SettingsInputMethod::INDIVIDUAL_LIST_ENTRY: + _manualSettingValue->setPlainText( + GetSourceSettingListEntryName( + _entryData->_source.GetSource(), + _entryData->_setting) + .value_or("")); + break; case MacroActionSource::SettingsInputMethod::INDIVIDUAL_TEMPVAR: break; case MacroActionSource::SettingsInputMethod::JSON_STRING: @@ -497,6 +511,7 @@ void MacroActionSourceEdit::SelectionChanged(const SourceSetting &setting) { GUARD_LOADING_AND_LOCK(); _entryData->_setting = setting; + SetWidgetVisibility(); } void MacroActionSourceEdit::ManualSettingsValueChanged() @@ -514,6 +529,22 @@ void MacroActionSourceEdit::RefreshVariableSourceSelectionValue() _sourceSettings->SetSource(_entryData->_source.GetSource()); } +static QString GetIndividualListEntryName() +{ + static const auto matchesInput = + [](const std::pair &p) { + return p.first == + MacroActionSource::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY; + }; + static const QString listValueText( + obs_module_text(std::find_if(inputMethods.begin(), + inputMethods.end(), matchesInput) + ->second.c_str())); + return listValueText; +} + void MacroActionSourceEdit::SetWidgetVisibility() { SetLayoutVisible(_settingsLayout, @@ -532,6 +563,11 @@ void MacroActionSourceEdit::SetWidgetVisibility() _entryData->_settingsInputMethod != MacroActionSource::SettingsInputMethod:: INDIVIDUAL_TEMPVAR); + + SetRowVisibleByValue(_settingsInputMethods, + GetIndividualListEntryName(), + _entryData->_setting.IsList()); + _tempVars->setVisible(_entryData->_action == MacroActionSource::Action::SETTINGS && _entryData->_settingsInputMethod == @@ -539,8 +575,11 @@ void MacroActionSourceEdit::SetWidgetVisibility() INDIVIDUAL_TEMPVAR); if (_entryData->_action == MacroActionSource::Action::SETTINGS && - _entryData->_settingsInputMethod == - MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL) { + (_entryData->_settingsInputMethod == + MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL || + _entryData->_settingsInputMethod == + MacroActionSource::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY)) { RemoveStretchIfPresent(_settingsLayout); _manualSettingValue->show(); } else { @@ -563,8 +602,12 @@ void MacroActionSourceEdit::SetWidgetVisibility() MacroActionSource::Action::DEINTERLACE_FIELD_ORDER); _refreshSettingSelection->setVisible( - _entryData->_settingsInputMethod == - MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL && + (_entryData->_settingsInputMethod == + MacroActionSource::SettingsInputMethod:: + INDIVIDUAL_MANUAL || + _entryData->_settingsInputMethod == + MacroActionSource::SettingsInputMethod:: + INDIVIDUAL_LIST_ENTRY) && _entryData->_source.GetType() == SourceSelection::Type::VARIABLE); diff --git a/plugins/base/macro-action-source.hpp b/plugins/base/macro-action-source.hpp index e1452ce7..64543d33 100644 --- a/plugins/base/macro-action-source.hpp +++ b/plugins/base/macro-action-source.hpp @@ -52,6 +52,7 @@ public: INDIVIDUAL_MANUAL, INDIVIDUAL_TEMPVAR, JSON_STRING, + INDIVIDUAL_LIST_ENTRY, }; SettingsInputMethod _settingsInputMethod = SettingsInputMethod::INDIVIDUAL_MANUAL; diff --git a/plugins/base/macro-condition-filter.cpp b/plugins/base/macro-condition-filter.cpp index 23df67bf..7b171f7d 100644 --- a/plugins/base/macro-condition-filter.cpp +++ b/plugins/base/macro-condition-filter.cpp @@ -4,6 +4,7 @@ #include "text-helpers.hpp" #include "source-settings-helpers.hpp" #include "selection-helpers.hpp" +#include "ui-helpers.hpp" #include @@ -16,7 +17,7 @@ bool MacroConditionFilter::_registered = MacroConditionFactory::Register( {MacroConditionFilter::Create, MacroConditionFilterEdit::Create, "AdvSceneSwitcher.condition.filter"}); -const static std::map +static const std::vector> filterConditionTypes = { {MacroConditionFilter::Condition::ENABLED, "AdvSceneSwitcher.condition.filter.type.active"}, @@ -30,6 +31,9 @@ const static std::map "AdvSceneSwitcher.condition.filter.type.individualSettingMatches"}, {MacroConditionFilter::Condition::INDIVIDUAL_SETTING_CHANGED, "AdvSceneSwitcher.condition.filter.type.individualSettingChanged"}, + {MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH, + "AdvSceneSwitcher.condition.filter.type.individualSettingListSelectionMatches"}, }; bool MacroConditionFilter::CheckConditionHelper(const OBSWeakSource &filter) @@ -73,6 +77,18 @@ bool MacroConditionFilter::CheckConditionHelper(const OBSWeakSource &filter) SetTempVarValue("setting", *value); break; } + case Condition::INDIVIDUAL_SETTING_LIST_ENTRY_MATCH: { + const auto entryName = + GetSourceSettingListEntryName(filter, _setting); + if (!entryName) { + return false; + } + ret = _regex.Enabled() ? _regex.Matches(*entryName, _settings) + : entryName == std::string(_settings); + SetVariableValue(*entryName); + SetTempVarValue("setting", *entryName); + break; + } case Condition::INDIVIDUAL_SETTING_CHANGED: { auto value = GetSourceSettingValue(filter, _setting); if (!value) { @@ -168,6 +184,7 @@ void MacroConditionFilter::SetupTempVars() "AdvSceneSwitcher.tempVar.filter.settings")); break; case Condition::INDIVIDUAL_SETTING_MATCH: + case Condition::INDIVIDUAL_SETTING_LIST_ENTRY_MATCH: case Condition::INDIVIDUAL_SETTING_CHANGED: AddTempvar("setting", obs_module_text( @@ -180,8 +197,9 @@ void MacroConditionFilter::SetupTempVars() static inline void populateConditionSelection(QComboBox *list) { - for (const auto &[_, name] : filterConditionTypes) { - list->addItem(obs_module_text(name.c_str())); + for (const auto &[value, name] : filterConditionTypes) { + list->addItem(obs_module_text(name.c_str()), + static_cast(value)); } } @@ -232,7 +250,7 @@ MacroConditionFilterEdit::MacroConditionFilterEdit( QWidget::connect(_refreshSettingSelection, SIGNAL(clicked()), this, SLOT(RefreshVariableSourceSelectionValue())); - std::unordered_map widgetPlaceholders = { + const std::unordered_map widgetPlaceholders = { {"{{sources}}", _sources}, {"{{filters}}", _filters}, {"{{conditions}}", _conditions}, @@ -292,8 +310,8 @@ void MacroConditionFilterEdit::FilterChanged(const FilterSelection &filter) void MacroConditionFilterEdit::ConditionChanged(int index) { GUARD_LOADING_AND_LOCK(); - _entryData->SetCondition( - static_cast(index)); + _entryData->SetCondition(static_cast( + _conditions->itemData(index).toInt())); SetWidgetVisibility(); } @@ -315,6 +333,14 @@ void MacroConditionFilterEdit::GetSettingsClicked() if (_entryData->GetCondition() == MacroConditionFilter::Condition::SETTINGS_MATCH) { value = FormatJsonString(GetSourceSettings(filters.at(0))); + } else if (_entryData->GetCondition() == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH) { + value = QString::fromStdString( + GetSourceSettingListEntryName( + filters.empty() ? nullptr : filters.at(0), + _entryData->_setting) + .value_or("")); } else { value = QString::fromStdString( GetSourceSettingValue(filters.at(0), @@ -351,6 +377,7 @@ void MacroConditionFilterEdit::SettingSelectionChanged( { GUARD_LOADING_AND_LOCK(); _entryData->_setting = setting; + SetWidgetVisibility(); } void MacroConditionFilterEdit::RefreshVariableSourceSelectionValue() @@ -360,29 +387,59 @@ void MacroConditionFilterEdit::RefreshVariableSourceSelectionValue() _settingSelection->SetSource(filters.empty() ? nullptr : filters.at(0)); } +static QString GetIndividualListEntryName() +{ + static const auto matchesInput = + [](const std::pair + &p) { + return p.first == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH; + }; + static const QString listValueText(obs_module_text( + std::find_if(filterConditionTypes.begin(), + filterConditionTypes.end(), matchesInput) + ->second.c_str())); + return listValueText; +} + void MacroConditionFilterEdit::SetWidgetVisibility() { const bool showSettingsControls = _entryData->GetCondition() == MacroConditionFilter::Condition::SETTINGS_MATCH || _entryData->GetCondition() == - MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH; + MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH; _settings->setVisible(showSettingsControls); _getSettings->setVisible(showSettingsControls); _regex->setVisible(showSettingsControls); _settingSelection->setVisible( _entryData->GetCondition() == MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH || _entryData->GetCondition() == MacroConditionFilter::Condition:: INDIVIDUAL_SETTING_CHANGED); _refreshSettingSelection->setVisible( - _entryData->GetCondition() == - MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH && + (_entryData->GetCondition() == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionFilter::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH) && (_entryData->_source.GetType() == SourceSelection::Type::VARIABLE || _entryData->_filter.GetType() == FilterSelection::Type::VARIABLE)); + + SetRowVisibleByValue(_conditions, GetIndividualListEntryName(), + _entryData->_setting.IsList()); + adjustSize(); updateGeometry(); } @@ -395,8 +452,8 @@ void MacroConditionFilterEdit::UpdateEntryData() _sources->SetSource(_entryData->_source); _filters->SetFilter(_entryData->_source, _entryData->_filter); - _conditions->setCurrentIndex( - static_cast(_entryData->GetCondition())); + _conditions->setCurrentIndex(_conditions->findData( + static_cast(_entryData->GetCondition()))); _settings->setPlainText(_entryData->_settings); _regex->SetRegexConfig(_entryData->_regex); const auto filters = diff --git a/plugins/base/macro-condition-filter.hpp b/plugins/base/macro-condition-filter.hpp index 313718c5..364fc56e 100644 --- a/plugins/base/macro-condition-filter.hpp +++ b/plugins/base/macro-condition-filter.hpp @@ -32,6 +32,7 @@ public: SETTINGS_CHANGED, INDIVIDUAL_SETTING_MATCH, INDIVIDUAL_SETTING_CHANGED, + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH, }; void SetCondition(Condition); Condition GetCondition() const { return _condition; } diff --git a/plugins/base/macro-condition-source.cpp b/plugins/base/macro-condition-source.cpp index 79898334..9fcc0cbb 100644 --- a/plugins/base/macro-condition-source.cpp +++ b/plugins/base/macro-condition-source.cpp @@ -4,6 +4,7 @@ #include "text-helpers.hpp" #include "selection-helpers.hpp" #include "source-settings-helpers.hpp" +#include "ui-helpers.hpp" namespace advss { @@ -14,7 +15,7 @@ bool MacroConditionSource::_registered = MacroConditionFactory::Register( {MacroConditionSource::Create, MacroConditionSourceEdit::Create, "AdvSceneSwitcher.condition.source"}); -static const std::map +static const std::vector> sourceConditionTypes = { {MacroConditionSource::Condition::ACTIVE, "AdvSceneSwitcher.condition.source.type.active"}, @@ -26,6 +27,9 @@ static const std::map "AdvSceneSwitcher.condition.source.type.settingsChanged"}, {MacroConditionSource::Condition::INDIVIDUAL_SETTING_MATCH, "AdvSceneSwitcher.condition.source.type.individualSettingMatches"}, + {MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH, + "AdvSceneSwitcher.condition.source.type.individualSettingListSelectionMatches"}, {MacroConditionSource::Condition::INDIVIDUAL_SETTING_CHANGED, "AdvSceneSwitcher.condition.source.type.individualSettingChanged"}, {MacroConditionSource::Condition::HEIGHT, @@ -105,6 +109,18 @@ bool MacroConditionSource::CheckCondition() SetTempVarValue("setting", *value); break; } + case Condition::INDIVIDUAL_SETTING_LIST_ENTRY_MATCH: { + const auto entryName = GetSourceSettingListEntryName( + _source.GetSource(), _setting); + if (!entryName) { + return false; + } + ret = _regex.Enabled() ? _regex.Matches(*entryName, _settings) + : entryName == std::string(_settings); + SetVariableValue(*entryName); + SetTempVarValue("setting", *entryName); + break; + } case Condition::INDIVIDUAL_SETTING_CHANGED: { const auto value = GetSourceSettingValue(_source.GetSource(), _setting); @@ -200,6 +216,7 @@ void MacroConditionSource::SetupTempVars() "AdvSceneSwitcher.tempVar.source.settings")); break; case Condition::INDIVIDUAL_SETTING_MATCH: + case Condition::INDIVIDUAL_SETTING_LIST_ENTRY_MATCH: case Condition::INDIVIDUAL_SETTING_CHANGED: AddTempvar("setting", obs_module_text( @@ -221,11 +238,11 @@ void MacroConditionSource::SetupTempVars() } template -static inline void populateSelection(QComboBox *list, - const std::map &map) +static inline void populateSelection(QComboBox *list, const T &map) { - for (const auto &[_, name] : map) { - list->addItem(obs_module_text(name.c_str())); + for (const auto &[value, name] : map) { + list->addItem(obs_module_text(name.c_str()), + static_cast(value)); } } @@ -333,8 +350,8 @@ void MacroConditionSourceEdit::SourceChanged(const SourceSelection &source) void MacroConditionSourceEdit::ConditionChanged(int index) { GUARD_LOADING_AND_LOCK(); - _entryData->SetCondition( - static_cast(index)); + _entryData->SetCondition(static_cast( + _conditions->itemData(index).toInt())); SetWidgetVisibility(); } @@ -349,6 +366,14 @@ void MacroConditionSourceEdit::GetSettingsClicked() MacroConditionSource::Condition::ALL_SETTINGS_MATCH) { value = FormatJsonString( GetSourceSettings(_entryData->_source.GetSource())); + } else if (_entryData->GetCondition() == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH) { + value = QString::fromStdString( + GetSourceSettingListEntryName( + _entryData->_source.GetSource(), + _entryData->_setting) + .value_or("")); } else { value = QString::fromStdString( GetSourceSettingValue(_entryData->_source.GetSource(), @@ -385,6 +410,7 @@ void MacroConditionSourceEdit::SettingSelectionChanged( { GUARD_LOADING_AND_LOCK(); _entryData->_setting = setting; + SetWidgetVisibility(); } void MacroConditionSourceEdit::RefreshVariableSourceSelectionValue() @@ -405,19 +431,41 @@ void MacroConditionSourceEdit::CompareMethodChanged(int index) static_cast(index); } +static QString GetIndividualListEntryName() +{ + static const auto matchesInput = + [](const std::pair + &p) { + return p.first == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH; + }; + static const QString listValueText(obs_module_text( + std::find_if(sourceConditionTypes.begin(), + sourceConditionTypes.end(), matchesInput) + ->second.c_str())); + return listValueText; +} + void MacroConditionSourceEdit::SetWidgetVisibility() { const bool settingsMatch = _entryData->GetCondition() == MacroConditionSource::Condition::ALL_SETTINGS_MATCH || _entryData->GetCondition() == - MacroConditionSource::Condition::INDIVIDUAL_SETTING_MATCH; + MacroConditionSource::Condition::INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH; _settings->setVisible(settingsMatch); _getSettings->setVisible(settingsMatch); _regex->setVisible(settingsMatch); _settingSelection->setVisible( _entryData->GetCondition() == MacroConditionSource::Condition::INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH || _entryData->GetCondition() == MacroConditionSource::Condition:: INDIVIDUAL_SETTING_CHANGED); @@ -432,8 +480,12 @@ void MacroConditionSourceEdit::SetWidgetVisibility() : ""); _refreshSettingSelection->setVisible( - _entryData->GetCondition() == - MacroConditionSource::Condition::INDIVIDUAL_SETTING_MATCH && + (_entryData->GetCondition() == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_MATCH || + _entryData->GetCondition() == + MacroConditionSource::Condition:: + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH) && _entryData->_source.GetType() == SourceSelection::Type::VARIABLE); @@ -445,6 +497,9 @@ void MacroConditionSourceEdit::SetWidgetVisibility() _size->setVisible(isSizeCheck); _sizeCompareMethods->setVisible(isSizeCheck); + SetRowVisibleByValue(_conditions, GetIndividualListEntryName(), + _entryData->_setting.IsList()); + adjustSize(); updateGeometry(); } @@ -456,8 +511,8 @@ void MacroConditionSourceEdit::UpdateEntryData() } _sources->SetSource(_entryData->_source); - _conditions->setCurrentIndex( - static_cast(_entryData->GetCondition())); + _conditions->setCurrentIndex(_conditions->findData( + static_cast(_entryData->GetCondition()))); _settings->setPlainText(_entryData->_settings); _regex->SetRegexConfig(_entryData->_regex); _settingSelection->SetSelection(_entryData->_source.GetSource(), diff --git a/plugins/base/macro-condition-source.hpp b/plugins/base/macro-condition-source.hpp index 88f41b75..89f90810 100644 --- a/plugins/base/macro-condition-source.hpp +++ b/plugins/base/macro-condition-source.hpp @@ -33,6 +33,7 @@ public: INDIVIDUAL_SETTING_CHANGED, HEIGHT, WIDTH, + INDIVIDUAL_SETTING_LIST_ENTRY_MATCH, }; void SetCondition(Condition); Condition GetCondition() const { return _condition; } diff --git a/plugins/base/utils/source-setting.cpp b/plugins/base/utils/source-setting.cpp index 8acee1a8..9ca3d17a 100644 --- a/plugins/base/utils/source-setting.cpp +++ b/plugins/base/utils/source-setting.cpp @@ -9,12 +9,16 @@ Q_DECLARE_METATYPE(advss::SourceSetting); +using properties_delete_t = decltype(&obs_properties_destroy); +using properties_t = std::unique_ptr; + namespace advss { -SourceSetting::SourceSetting(const std::string &id, +SourceSetting::SourceSetting(const std::string &id, obs_property_type type, const std::string &description, const std::string &longDescription) : _id(id), + _type(type), _description(description), _longDescription(longDescription) { @@ -24,6 +28,7 @@ bool SourceSetting::Save(obs_data_t *obj) const { OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "id", _id.c_str()); + obs_data_set_int(data, "type", _type); obs_data_set_string(data, "description", _description.c_str()); obs_data_set_obj(obj, "sourceSetting", data); return true; @@ -33,10 +38,21 @@ bool SourceSetting::Load(obs_data_t *obj) { OBSDataAutoRelease data = obs_data_get_obj(obj, "sourceSetting"); _id = obs_data_get_string(data, "id"); + _type = static_cast(obs_data_get_int(data, "type")); _description = obs_data_get_string(data, "description"); return true; } +static bool isListType(obs_property_type type) +{ + return type == OBS_PROPERTY_LIST || type == OBS_PROPERTY_EDITABLE_LIST; +} + +bool SourceSetting::IsList() const +{ + return isListType(_type); +} + bool SourceSetting::operator==(const SourceSetting &other) const { return _id == other._id; @@ -85,7 +101,7 @@ static void addSettingsHelper(obs_property_t *property, if (!property) { continue; } - auto type = obs_property_get_type(property); + const auto type = obs_property_get_type(property); if (type == OBS_PROPERTY_GROUP) { auto group = obs_property_group_content(property); auto description = obs_property_description(property); @@ -110,7 +126,8 @@ static void addSettingsHelper(obs_property_t *property, prefix + description + obs_module_text(getPropertySuffix(type)); auto longDescription = obs_property_long_description(property); - SourceSetting setting(name, descriptionWithPrefixAndSuffix, + SourceSetting setting(name, type, + descriptionWithPrefixAndSuffix, longDescription ? longDescription : ""); settings.emplace_back(setting); } while (obs_property_next(&property)); @@ -118,19 +135,20 @@ static void addSettingsHelper(obs_property_t *property, std::vector GetSoruceSettings(obs_source_t *source) { - auto properties = obs_source_properties(source); + properties_t properties(obs_source_properties(source), + obs_properties_destroy); if (!properties) { return {}; } std::vector settings; - auto it = obs_properties_first(properties); + + auto it = obs_properties_first(properties.get()); addSettingsHelper(it, settings); - obs_properties_destroy(properties); return settings; } -std::optional GetSourceSettingValue(const OBSWeakSource &ws, - const SourceSetting &setting) +static std::optional +getDataJsonWithDefaults(const OBSWeakSource &ws) { OBSSourceAutoRelease source = obs_weak_source_get_source(ws); OBSDataAutoRelease data = obs_source_get_settings(source); @@ -143,10 +161,139 @@ std::optional GetSourceSettingValue(const OBSWeakSource &ws, if (!json) { return {}; } - auto value = GetJsonField(json, setting.GetID()); + return json; +} + +std::optional GetSourceSettingValue(const OBSWeakSource &source, + const SourceSetting &setting) +{ + const auto json = getDataJsonWithDefaults(source); + if (!json) { + return {}; + } + auto value = GetJsonField(*json, setting.GetID()); return value; } +static obs_property_t *findListPropertyByIdHelper(obs_property_t *property, + const std::string &id) +{ + do { + if (!property) { + continue; + } + + const auto type = obs_property_get_type(property); + if (type == OBS_PROPERTY_GROUP) { + auto group = obs_property_group_content(property); + findListPropertyByIdHelper(obs_properties_first(group), + id); + continue; + } + + if (!isListType(type)) { + continue; + } + + auto name = obs_property_name(property); + if (name != id) { + continue; + } + + return property; + + } while (obs_property_next(&property)); + + return nullptr; +} + +static std::pair +findListPropertyById(const obs_source_t *source, const std::string &id) +{ + // Note: + // Using obs_source_properties() here might cause flickering for some + // sources, as this will call obs_properties_apply_settings() + // implicitly. + // So, we use obs_get_source_properties() instead. + // + // 20250617: This might cause issues with the move plugin as it doesn't + // handle obs_get_source_properties() properly in the move filter + auto properties = obs_get_source_properties(obs_source_get_id(source)); + if (!properties) { + return {nullptr, properties_t(nullptr, obs_properties_destroy)}; + } + auto property = obs_properties_first(properties); + return {findListPropertyByIdHelper(property, id), + properties_t(nullptr, obs_properties_destroy)}; +} + +static std::pair +findListPropertyById(const OBSWeakSource &ws, const std::string &id) +{ + OBSSourceAutoRelease source = obs_weak_source_get_source(ws); + return findListPropertyById(source.Get(), id); +} + +std::optional +GetSourceSettingListEntryName(const OBSWeakSource &source, + const SourceSetting &setting) +{ + if (!setting.IsList()) { + return {}; + } + + const auto json = getDataJsonWithDefaults(source); + if (!json) { + return {}; + } + + const auto &[property, properties] = + findListPropertyById(source, setting.GetID()); + if (!property) { + return {}; + } + + const auto type = obs_property_list_format(property); + if (type == OBS_COMBO_FORMAT_INVALID) { + return {}; + } + + auto currentValue = GetJsonField(*json, setting.GetID()); + if (!currentValue) { + return {}; + } + + const auto count = obs_property_list_item_count(property); + for (size_t i = 0; i < count; i++) { + std::string value; + switch (type) { + case OBS_COMBO_FORMAT_INT: + value = std::to_string( + obs_property_list_item_int(property, i)); + break; + case OBS_COMBO_FORMAT_FLOAT: + value = std::to_string( + obs_property_list_item_float(property, i)); + break; + case OBS_COMBO_FORMAT_STRING: + value = obs_property_list_item_string(property, i); + break; + case OBS_COMBO_FORMAT_BOOL: + value = std::to_string( + obs_property_list_item_bool(property, i)); + break; + default: + break; + } + + if (value == currentValue) { + return obs_property_list_item_name(property, i); + } + } + + return {}; +} + void SetSourceSetting(obs_source_t *source, const SourceSetting &setting, const std::string &value) { @@ -233,6 +380,63 @@ void SetSourceSetting(obs_source_t *source, const SourceSetting &setting, obs_source_update(source, data); } +void SetSourceSettingListEntryValueByName(obs_source_t *source, + const SourceSetting &setting, + const std::string &name) +{ + if (!setting.IsList()) { + return; + } + + const auto &[property, properties] = + findListPropertyById(source, setting.GetID()); + if (!property) { + return; + } + + const auto type = obs_property_list_format(property); + if (type == OBS_COMBO_FORMAT_INVALID) { + return; + } + + const auto count = obs_property_list_item_count(property); + bool found = false; + size_t idx = 0; + for (; idx < count; idx++) { + if (obs_property_list_item_name(property, idx) == name) { + found = true; + break; + } + } + + if (!found) { + return; + } + + std::string value; + switch (type) { + case OBS_COMBO_FORMAT_INT: + value = std::to_string( + obs_property_list_item_int(property, idx)); + break; + case OBS_COMBO_FORMAT_FLOAT: + value = std::to_string( + obs_property_list_item_float(property, idx)); + break; + case OBS_COMBO_FORMAT_STRING: + value = obs_property_list_item_string(property, idx); + break; + case OBS_COMBO_FORMAT_BOOL: + value = std::to_string( + obs_property_list_item_bool(property, idx)); + break; + default: + break; + } + + SetSourceSetting(source, setting, value); +} + SourceSettingSelection::SourceSettingSelection(QWidget *parent) : QWidget(parent), _settings(new FilterComboBox( diff --git a/plugins/base/utils/source-setting.hpp b/plugins/base/utils/source-setting.hpp index 0b78841d..343a6e31 100644 --- a/plugins/base/utils/source-setting.hpp +++ b/plugins/base/utils/source-setting.hpp @@ -14,15 +14,18 @@ namespace advss { class SourceSetting { public: SourceSetting() = default; - SourceSetting(const std::string &id, const std::string &description, + SourceSetting(const std::string &id, obs_property_type type, + const std::string &description, const std::string &longDescription = ""); bool Save(obs_data_t *obj) const; bool Load(obs_data_t *obj); std::string GetID() const { return _id; } - EXPORT bool operator==(const SourceSetting &other) const; + bool IsList() const; + bool operator==(const SourceSetting &other) const; private: std::string _id = ""; + obs_property_type _type = OBS_PROPERTY_INVALID; std::string _description = ""; std::string _longDescription = ""; @@ -32,8 +35,14 @@ private: std::vector GetSoruceSettings(obs_source_t *source); std::optional GetSourceSettingValue(const OBSWeakSource &source, const SourceSetting &setting); +std::optional +GetSourceSettingListEntryName(const OBSWeakSource &source, + const SourceSetting &setting); void SetSourceSetting(obs_source_t *source, const SourceSetting &setting, const std::string &value); +void SetSourceSettingListEntryValueByName(obs_source_t *source, + const SourceSetting &setting, + const std::string &name); class SourceSettingSelection : public QWidget { Q_OBJECT diff --git a/tests/mocks/ui-helpers.cpp b/tests/mocks/ui-helpers.cpp index ea2d5625..0d049280 100644 --- a/tests/mocks/ui-helpers.cpp +++ b/tests/mocks/ui-helpers.cpp @@ -20,6 +20,11 @@ int FindIdxInRagne(QComboBox *list, int start, int stop, return -1; } +void SetRowMatchingValueVisible(QComboBox *list, const QString &value, + bool show) +{ +} + bool DisplayMessage(const QString &msg, bool question, bool modal) { return false;