#include "macro-action-variable-string.hpp" #include "layout-helpers.hpp" #include "log-helper.hpp" #include "utility.hpp" #include namespace advss { using Action = MacroActionVariable::Action; static bool registered = RegisterVariableActionGroup( {10, { {Action::PAD, "AdvSceneSwitcher.action.variable.type.padValue", "AdvSceneSwitcher.action.variable.layout.pad"}, {Action::TRUNCATE, "AdvSceneSwitcher.action.variable.type.truncateValue", "AdvSceneSwitcher.action.variable.layout.truncate"}, {Action::SUBSTRING, "AdvSceneSwitcher.action.variable.type.subString"}, {Action::FIND_AND_REPLACE, "AdvSceneSwitcher.action.variable.type.findAndReplace"}, {Action::STRING_LENGTH, "AdvSceneSwitcher.action.variable.type.stringLength", "AdvSceneSwitcher.action.variable.layout.strValue", true}, {Action::TRIM, "AdvSceneSwitcher.action.variable.type.trim"}, {Action::CHANGE_CASE, "AdvSceneSwitcher.action.variable.type.changeCase", "AdvSceneSwitcher.action.variable.layout.changeCase"}, }, []() { return std::make_unique(); }, VariableActionStringEdit::Create}); static std::string truncate(const std::string &input, VariableActionString::Direction direction, size_t length) { if (input.length() <= length) { return input; } if (direction == VariableActionString::Direction::RIGHT) { return input.substr(0, length); } else { return input.substr(input.length() - length); } } static std::string pad(const std::string &input, VariableActionString::Direction direction, size_t length, char paddingChar) { if (input.length() >= length) { return input; } if (direction == VariableActionString::Direction::RIGHT) { return input + std::string(length - input.length(), paddingChar); } else { return std::string(length - input.length(), paddingChar) + input; } } std::unique_ptr VariableActionString::Copy() const { return std::make_unique(*this); } void VariableActionString::Save(obs_data_t *obj, const MacroActionVariable &) const { _subStringStart.Save(obj, "subStringStart"); _subStringSize.Save(obj, "subStringSize"); obs_data_set_string(obj, "regexPattern", _regexPattern.c_str()); _regexMatchIdx.Save(obj, "regexMatchIdx"); _findRegex.Save(obj, "findRegex"); _findStr.Save(obj, "findStr"); _replaceStr.Save(obj, "replaceStr"); _subStringRegex.Save(obj); obs_data_set_int(obj, "direction", static_cast(_direction)); _stringLength.Save(obj, "stringLength"); obs_data_set_int(obj, "paddingChar", _paddingChar); obs_data_set_int(obj, "caseType", static_cast(_caseType)); } void VariableActionString::Load(obs_data_t *obj, MacroActionVariable &) { _subStringRegex.Load(obj); _regexPattern = obs_data_get_string(obj, "regexPattern"); _findRegex.Load(obj, "findRegex"); _findStr.Load(obj, "findStr"); _replaceStr.Load(obj, "replaceStr"); _direction = static_cast(obs_data_get_int(obj, "direction")); _stringLength.Load(obj, "stringLength"); if (obs_data_has_user_value(obj, "paddingChar")) { _paddingChar = obs_data_get_int(obj, "paddingChar"); } else { _paddingChar = ' '; } _caseType = static_cast(obs_data_get_int(obj, "caseType")); // Convert old data format // TODO: Remove in future version if (!obs_data_has_user_value(obj, "version")) { _subStringStart = obs_data_get_int(obj, "subStringStart") + 1; _subStringSize = obs_data_get_int(obj, "subStringSize"); _regexMatchIdx = obs_data_get_int(obj, "regexMatchIdx") + 1; } else { _subStringStart.Load(obj, "subStringStart"); _subStringSize.Load(obj, "subStringSize"); _regexMatchIdx.Load(obj, "regexMatchIdx"); } } void VariableActionString::ResolveVariables() { _findStr.ResolveVariables(); _replaceStr.ResolveVariables(); } void VariableActionString::HandleIndexSubString(Variable &var) { const auto curValue = var.Value(); const auto subStringSize = _subStringSize.GetValue(); const auto subStringStart = _subStringStart.GetValue() - 1; try { if (subStringSize == 0) { var.SetValue(curValue.substr(subStringStart)); return; } var.SetValue(curValue.substr(subStringStart, subStringSize)); } catch (const std::out_of_range &) { vblog(LOG_WARNING, "invalid start index \"%d\" selected for substring of \"%s\" of variable \"%s\"", subStringStart, curValue.c_str(), var.Name().c_str()); } } void VariableActionString::HandleRegexSubString(Variable &var) { const auto curValue = var.Value(); auto regex = _subStringRegex.GetRegularExpression(_regexPattern); if (!regex.isValid()) { return; } auto it = regex.globalMatch(QString::fromStdString(curValue)); const auto regexMatchIndex = _regexMatchIdx.GetValue() - 1; for (int idx = 0; idx < regexMatchIndex; idx++) { if (!it.hasNext()) { return; } it.next(); } if (!it.hasNext()) { return; } auto match = it.next(); var.SetValue(match.captured(0).toStdString()); } void VariableActionString::HandleFindAndReplace(Variable &var) { auto value = var.Value(); if (!_findRegex.Enabled()) { ReplaceAll(value, _findStr, _replaceStr); var.SetValue(value); return; } QString replacement = QString::fromStdString(_replaceStr); auto regex = _findRegex.GetRegularExpression(_findStr); auto result = QString::fromStdString(value); result = result.replace(regex, replacement); var.SetValue(result.toStdString()); } void VariableActionString::HandleCaseChange(Variable &var) { auto value = QString::fromStdString(var.Value()); switch (_caseType) { case CaseType::LOWER_CASE: var.SetValue(value.toLower().toStdString()); break; case CaseType::UPPER_CASE: var.SetValue(value.toUpper().toStdString()); break; case CaseType::CAPITALIZED: { if (value.isEmpty()) { return; } value[0] = value[0].toUpper(); var.SetValue(value.toStdString()); break; } case CaseType::START_CASE: { auto partList = value.split(QRegularExpression("\\b")); for (auto &part : partList) { if (part.isEmpty()) { continue; } part[0] = part[0].toUpper(); } var.SetValue(partList.join("").toStdString()); break; } } } bool VariableActionString::Perform(MacroActionVariable &action, MacroActionVariable::Action type, Variable &var) { switch (type) { case Action::PAD: var.SetValue(pad(var.Value(), _direction, _stringLength, _paddingChar)); break; case Action::TRUNCATE: var.SetValue(truncate(var.Value(), _direction, _stringLength)); break; case Action::SUBSTRING: if (_subStringRegex.Enabled()) { HandleRegexSubString(var); break; } HandleIndexSubString(var); break; case Action::FIND_AND_REPLACE: HandleFindAndReplace(var); break; case Action::STRING_LENGTH: var.SetValue(std::string(action._strValue).length()); break; case Action::TRIM: var.SetValue(QString::fromStdString(var.Value()) .trimmed() .toStdString()); break; case Action::CHANGE_CASE: HandleCaseChange(var); break; default: break; } return true; } static void populateCaseTypeSelection(QComboBox *list) { const static std::map caseTypes = { {VariableActionString::CaseType::LOWER_CASE, "AdvSceneSwitcher.action.variable.case.type.lowerCase"}, {VariableActionString::CaseType::UPPER_CASE, "AdvSceneSwitcher.action.variable.case.type.upperCase"}, {VariableActionString::CaseType::CAPITALIZED, "AdvSceneSwitcher.action.variable.case.type.capitalized"}, {VariableActionString::CaseType::START_CASE, "AdvSceneSwitcher.action.variable.case.type.startCase"}, }; for (const auto &[type, name] : caseTypes) { list->addItem(obs_module_text(name.c_str()), static_cast(type)); } } static void populateDirectionSelection(QComboBox *list) { list->addItems( QStringList() << obs_module_text( "AdvSceneSwitcher.action.variable.truncateOrPadDirection.left") << obs_module_text( "AdvSceneSwitcher.action.variable.truncateOrPadDirection.right")); } VariableActionStringEdit::VariableActionStringEdit( QWidget *parent, std::shared_ptr entryData) : VariableActionGroupEdit(parent, entryData), _data(entryData ? entryData->GetGroup() : nullptr), _substringLayout(new QVBoxLayout()), _subStringControlsLayout(new QHBoxLayout()), _subStringStart(new VariableSpinBox(this)), _subStringSize(new VariableSpinBox(this)), _subStringRegex(new RegexConfigWidget(this)), _regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)), _regexMatchIdx(new VariableSpinBox(this)), _findReplaceLayout(new QHBoxLayout()), _findRegex(new RegexConfigWidget(this)), _findStr(new VariableTextEdit(this, 10, 1, 1)), _replaceStr(new VariableTextEdit(this, 10, 1, 1)), _direction(new QComboBox(this)), _stringLength(new VariableSpinBox(this)), _paddingCharSelection(new SingleCharSelection(this)), _caseType(new FilterComboBox(this)) { _subStringStart->setMinimum(1); _subStringStart->setMaximum(99999); _subStringStart->setSpecialValueText(obs_module_text( "AdvSceneSwitcher.action.variable.subString.begin")); _subStringSize->setMinimum(0); _subStringSize->setMaximum(99999); _subStringSize->setSpecialValueText(obs_module_text( "AdvSceneSwitcher.action.variable.subString.all")); _regexMatchIdx->setMinimum(1); _regexMatchIdx->setMaximum(999); _regexMatchIdx->setSuffix("."); _replaceStr->setToolTip(obs_module_text( "AdvSceneSwitcher.action.variable.findAndReplace.replace.tooltip")); _stringLength->setMaximum(999); populateDirectionSelection(_direction); populateCaseTypeSelection(_caseType); QWidget::connect( _subStringStart, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(SubStringStartChanged(const NumberVariable &))); QWidget::connect( _subStringSize, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(SubStringSizeChanged(const NumberVariable &))); QWidget::connect(_subStringRegex, SIGNAL(RegexConfigChanged(const RegexConfig &)), this, SLOT(SubStringRegexChanged(const RegexConfig &))); QWidget::connect(_regexPattern, SIGNAL(textChanged()), this, SLOT(RegexPatternChanged())); QWidget::connect( _regexMatchIdx, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(RegexMatchIdxChanged(const NumberVariable &))); QWidget::connect(_findRegex, SIGNAL(RegexConfigChanged(const RegexConfig &)), this, SLOT(FindRegexChanged(const RegexConfig &))); QWidget::connect(_findStr, SIGNAL(textChanged()), this, SLOT(FindStrValueChanged())); QWidget::connect(_replaceStr, SIGNAL(textChanged()), this, SLOT(ReplaceStrValueChanged())); QWidget::connect(_direction, SIGNAL(currentIndexChanged(int)), this, SLOT(DirectionChanged(int))); QWidget::connect( _stringLength, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(StringLengthChanged(const NumberVariable &))); QWidget::connect(_paddingCharSelection, SIGNAL(CharChanged(const QString &)), this, SLOT(CharSelectionChanged(const QString &))); QWidget::connect(_caseType, SIGNAL(currentIndexChanged(int)), this, SLOT(CaseTypeChanged(int))); PlaceWidgets( obs_module_text( "AdvSceneSwitcher.action.variable.layout.findAndReplace"), _findReplaceLayout, {{"{{findStr}}", _findStr}, {"{{findRegex}}", _findRegex}, {"{{replaceStr}}", _replaceStr}}, false); auto regexConfigLayout = new QHBoxLayout; regexConfigLayout->addWidget(_subStringRegex); regexConfigLayout->addStretch(); _substringLayout->addLayout(_subStringControlsLayout); _substringLayout->addWidget(_regexPattern); _substringLayout->addLayout(regexConfigLayout); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->addLayout(_substringLayout); layout->addLayout(_findReplaceLayout); setLayout(layout); } VariableActionGroupEdit * VariableActionStringEdit::Create(QWidget *parent, std::shared_ptr entryData) { return new VariableActionStringEdit(parent, entryData); } std::unordered_map VariableActionStringEdit::GetInlineWidgets() { return { {"{{direction}}", _direction}, {"{{stringLength}}", _stringLength}, {"{{paddingCharSelection}}", _paddingCharSelection}, {"{{caseType}}", _caseType}, }; } void VariableActionStringEdit::UpdateEntryData() { if (!_data) { return; } _subStringStart->SetValue(_data->_subStringStart); _subStringSize->SetValue(_data->_subStringSize); _subStringRegex->SetRegexConfig(_data->_subStringRegex); _findRegex->SetRegexConfig(_data->_findRegex); _regexPattern->setPlainText( QString::fromStdString(_data->_regexPattern)); _regexMatchIdx->SetValue(_data->_regexMatchIdx); _findStr->setPlainText(_data->_findStr); _replaceStr->setPlainText(_data->_replaceStr); _direction->setCurrentIndex(static_cast(_data->_direction)); _stringLength->SetValue(_data->_stringLength); _paddingCharSelection->setText(QChar::fromLatin1(_data->_paddingChar)); _caseType->setCurrentIndex( _caseType->findData(static_cast(_data->_caseType))); _loading = false; } void VariableActionStringEdit::SetWidgetVisibility( MacroActionVariable::Action action) { if (!_data) { return; } const std::unordered_map substringWidgets = { {"{{subStringRegex}}", _subStringRegex}, {"{{subStringStart}}", _subStringStart}, {"{{subStringSize}}", _subStringSize}, {"{{regexMatchIdx}}", _regexMatchIdx}, }; for (const auto &[_, widget] : substringWidgets) { _subStringControlsLayout->removeWidget(widget); } ClearLayout(_subStringControlsLayout); const bool showRegex = _data->_subStringRegex.Enabled(); PlaceWidgets( obs_module_text( showRegex ? "AdvSceneSwitcher.action.variable.layout.substringRegex" : "AdvSceneSwitcher.action.variable.layout.substringIndex"), _subStringControlsLayout, substringWidgets); _subStringStart->setVisible(!showRegex); _subStringSize->setVisible(!showRegex); _regexMatchIdx->setVisible(showRegex); const bool isSubstring = action == Action::SUBSTRING; const bool isFindAndReplace = action == Action::FIND_AND_REPLACE; SetLayoutVisible(_substringLayout, isSubstring); _regexPattern->setVisible(showRegex && isSubstring); SetLayoutVisible(_findReplaceLayout, isFindAndReplace); setVisible(isSubstring || isFindAndReplace); UpdateSize(); } void VariableActionStringEdit::SubStringStartChanged( const NumberVariable &start) { GUARD_LOADING_AND_LOCK(); _data->_subStringStart = start; } void VariableActionStringEdit::SubStringSizeChanged( const NumberVariable &size) { GUARD_LOADING_AND_LOCK(); _data->_subStringSize = size; } void VariableActionStringEdit::SubStringRegexChanged(const RegexConfig &conf) { GUARD_LOADING_AND_LOCK(); _data->_subStringRegex = conf; SetWidgetVisibility(_entryData->_action); } void VariableActionStringEdit::RegexPatternChanged() { GUARD_LOADING_AND_LOCK(); _data->_regexPattern = _regexPattern->toPlainText().toStdString(); UpdateSize(); } void VariableActionStringEdit::RegexMatchIdxChanged( const NumberVariable &index) { GUARD_LOADING_AND_LOCK(); _data->_regexMatchIdx = index; } void VariableActionStringEdit::FindStrValueChanged() { GUARD_LOADING_AND_LOCK(); _data->_findStr = _findStr->toPlainText().toStdString(); UpdateSize(); } void VariableActionStringEdit::FindRegexChanged(const RegexConfig &conf) { GUARD_LOADING_AND_LOCK(); _data->_findRegex = conf; UpdateSize(); } void VariableActionStringEdit::ReplaceStrValueChanged() { GUARD_LOADING_AND_LOCK(); _data->_replaceStr = _replaceStr->toPlainText().toStdString(); UpdateSize(); } void VariableActionStringEdit::DirectionChanged(int value) { GUARD_LOADING_AND_LOCK(); _data->_direction = static_cast(value); } void VariableActionStringEdit::StringLengthChanged( const NumberVariable &value) { GUARD_LOADING_AND_LOCK(); _data->_stringLength = value; } void VariableActionStringEdit::CharSelectionChanged(const QString &character) { GUARD_LOADING_AND_LOCK(); if (character.isEmpty()) { _data->_paddingChar = ' '; } else { _data->_paddingChar = character.toStdString().at(0); } } void VariableActionStringEdit::CaseTypeChanged(int index) { GUARD_LOADING_AND_LOCK(); _data->_caseType = static_cast( _caseType->itemData(index).toInt()); } } // namespace advss