Compare commits

...

13 Commits

Author SHA1 Message Date
WarmUpTill
9bedbf6fb8 Display warning if plugin could not be loaded 2023-04-11 03:55:30 -07:00
WarmUpTill
853150e458 Resize MacroPropertiesDialog to fit contents 2023-04-11 03:55:30 -07:00
WarmUpTill
1ddd48e008 Improve dock handling when changing scene collections
Note that the positions of the docks will still not be restored
perfectly when changing scene collections.
To my understanding this would somehow have to be handled by the
QMainWindow which is controlled by OBS.

This might have to be revisited at a later date.
2023-04-11 03:55:30 -07:00
WarmUpTill
17d24d642e Add more error handling for reordering macros 2023-04-11 03:55:30 -07:00
WarmUpTill
87a46684cd Remove "Studio mode" backwards compatabilty fallback
This should remove the frequent logs of complaining about a name
conflict between different action types.
2023-04-11 03:55:30 -07:00
WarmUpTill
e4c5193945 CI: Use git tag as version identifier in packaging step 2023-04-11 03:55:30 -07:00
WarmUpTill
2e969bf6dd Fix video condition's pattern match mode not loading correctly 2023-04-11 03:55:30 -07:00
WarmUpTill
6db26d9f7b Fix crash when using OCR check of video condition 2023-04-11 03:55:30 -07:00
WarmUpTill
4f02c9ec36 Ask for confirmation even if only a single macro is being deleted 2023-04-06 00:36:07 -07:00
WarmUpTill
2fdd39fba5 Use exprtk lib to evaluate mathematical expressions 2023-04-01 05:37:40 -07:00
WarmUpTill
fe1ddc3a94 Fix connection selection being lost after renaming connection 2023-04-01 05:37:40 -07:00
WarmUpTill
cff2d805db Fix variable selection being lost after renaming variable 2023-04-01 05:37:40 -07:00
WarmUpTill
34425d3e47 CI: minor improvements
* Print checksums in log
* Log if tests were skipped
* Switch to gh-release action version 1
2023-03-27 12:37:08 -07:00
36 changed files with 41441 additions and 431 deletions

View File

@@ -16,6 +16,7 @@ runs:
shell: zsh {0}
run: |
if [[ '${{ inputs.target }}' != 'x86_64' ]]; then
echo tests skipped!
exit 0
fi
${{ inputs.workingDirectory }}/build_x86_64/tests/advanced-scene-switcher-tests

View File

@@ -170,11 +170,17 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
local product_name
local product_version
local git_tag="$(git describe --tags)"
read -r product_name product_version <<< \
"$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
if [[ "${git_tag}" =~ '^([0-9]+\.){0,2}(\*|[0-9]+)$' ]] {
log_info "Using git tag as version identifier '${git_tag}'"
product_version="${git_tag}"
} else {
log_info "Using buildspec.json version identifier '${product_version}'"
}
case ${host_os} {
macos)

View File

@@ -128,9 +128,18 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
local product_name
local product_version
local git_tag="$(git describe --tags)"
read -r product_name product_version <<< \
"$(jq -r '. | {name, version} | join(" ")' ${project_root}/buildspec.json)"
if [[ "${git_tag}" =~ '^([0-9]+\.){0,2}(\*|[0-9]+)$' ]] {
log_info "Using git tag as version identifier '${git_tag}'"
product_version="${git_tag}"
} else {
log_info "Using buildspec.json version identifier '${product_version}'"
}
if [[ ${host_os} == 'macos' ]] {
autoload -Uz check_packages read_codesign read_codesign_installer read_codesign_pass

View File

@@ -425,10 +425,11 @@ jobs:
for file in ${{ github.workspace }}/**/@(*.pkg|*.exe|*.deb|*.zip); do
echo " ${file##*/}: $(sha256sum "${file}" | cut -d " " -f 1)" >> ${{ github.workspace }}/CHECKSUMS.txt
done
cat ${{ github.workspace }}/CHECKSUMS.txt
- name: Create Release
id: create_release
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
uses: softprops/action-gh-release@v1
with:
draft: true
prerelease: true

View File

@@ -343,7 +343,8 @@ target_include_directories(
${LIB_NAME}
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib")
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
# --- End of section ---
@@ -354,7 +355,8 @@ if(OS_WINDOWS)
target_compile_definitions(${LIB_NAME} PRIVATE UNICODE _UNICODE)
if(MSVC)
target_compile_options(${LIB_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267)
target_compile_options(${LIB_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
/bigobj)
endif()
target_sources(${LIB_NAME} PRIVATE src/win/advanced-scene-switcher-win.cpp)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)

View File

@@ -30,6 +30,8 @@ AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose loggi
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warning if plugins cannot be loaded"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
AdvSceneSwitcher.generalTab.priority="Priority"
@@ -83,6 +85,7 @@ AdvSceneSwitcher.macroTab.defaultGroupName="Group %1"
AdvSceneSwitcher.macroTab.exists="Macro name exists already"
AdvSceneSwitcher.macroTab.groupDeleteConfirm="Are you sure you want to delete \"%1\" and all its elements?"
AdvSceneSwitcher.macroTab.deleteMultipleMacrosConfirmation="Are you sure you want to delete %1 macros?"
AdvSceneSwitcher.macroTab.deleteSingleMacroConfirmation="Are you sure you want to delete \"%1\"?"
AdvSceneSwitcher.macroTab.contextMenuAdd="Add"
AdvSceneSwitcher.macroTab.copy="Duplicate Macro"
AdvSceneSwitcher.macroTab.group="Group Selected Macros"
@@ -943,10 +946,7 @@ AdvSceneSwitcher.process.addArgumentDescription="Add new argument:"
AdvSceneSwitcher.process.entry="Run{{filePath}}{{advancedSettings}}"
AdvSceneSwitcher.process.entry.workingDirectory="Working directory:{{workingDirectory}}"
AdvSceneSwitcher.math.notANumber="not a valid number"
AdvSceneSwitcher.math.expressionFail="failed evaluate expression"
AdvSceneSwitcher.math.expressionFailParentheses="failed evaluate expression (missing Parentheses?)"
AdvSceneSwitcher.math.notFullyResolved="not all operands were used"
AdvSceneSwitcher.math.expressionFail="Failed evaluate expression"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"

41032
deps/exprtk/exprtk.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -256,6 +256,30 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_26">
<item>
<widget class="QCheckBox" name="warnPluginLoadFailure">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_19">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_24">
<item>

View File

@@ -43,9 +43,7 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
{
switcher->settingsWindowOpened = true;
ui->setupUi(this);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune();
loadUI();
}
@@ -58,26 +56,51 @@ AdvSceneSwitcher::~AdvSceneSwitcher()
}
}
bool translationAvailable()
static bool translationAvailable()
{
return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"),
"AdvSceneSwitcher.pluginName");
}
static void DisplayMissingDependencyWarning()
{
if (!switcher->warnPluginLoadFailure ||
switcher->loadFailureLibs.isEmpty()) {
return;
}
QString failedLibsString = "<ul>";
for (const auto &lib : switcher->loadFailureLibs) {
failedLibsString += "<li>" + lib + "</li>";
}
failedLibsString += "</ul>";
QString warning(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage"));
DisplayMessage(warning.arg(failedLibsString));
}
static void DisplayMissingDataDirWarning()
{
if (translationAvailable()) {
return;
}
QString msg = "Failed to find plug-in's 'data' directory.\n"
"Please check installation instructions!\n\n"
"Data most likely expected at:\n\n";
#ifdef _WIN32
msg += QString::fromStdString(
(std::filesystem::current_path().string()));
msg += "/";
#endif
msg += obs_get_module_data_path(obs_current_module());
DisplayMessage(msg);
}
void AdvSceneSwitcher::loadUI()
{
if (!translationAvailable()) {
QString msg = "Failed to find plug-in's 'data' directory.\n"
"Please check installation instructions!\n\n"
"Data most likely expected at:\n\n";
#ifdef _WIN32
msg += QString::fromStdString(
(std::filesystem::current_path().string()));
msg += "/";
#endif
msg += obs_get_module_data_path(obs_current_module());
(void)DisplayMessage(msg);
}
DisplayMissingDataDirWarning();
DisplayMissingDependencyWarning();
setupGeneralTab();
setupTitleTab();
@@ -687,6 +710,7 @@ void LoadPlugins()
blog(LOG_WARNING, "failed to load \"%s\": %s",
file.toStdString().c_str(),
lib->errorString().toStdString().c_str());
switcher->loadFailureLibs << file;
}
}
}

View File

@@ -202,6 +202,7 @@ public slots:
void on_saveWindowGeo_stateChanged(int state);
void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_warnPluginLoadFailure_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state);
void on_exportSettings_clicked();

View File

@@ -172,6 +172,15 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
switcher->disableHints = state;
}
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
{
if (loading) {
return;
}
switcher->warnPluginLoadFailure = state;
}
bool isLegacyTab(const QString &name)
{
return name == obs_module_text(
@@ -538,8 +547,8 @@ void SwitcherData::loadSettings(obs_data_t *obj)
// selections to be available.
loadSceneGroups(obj);
loadVariables(obj);
loadMacros(obj);
loadConnections(obj);
loadMacros(obj);
loadWindowTitleSwitches(obj);
loadScreenRegionSwitches(obj);
loadPauseSwitches(obj);
@@ -617,6 +626,7 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "showSystemTrayNotifications",
showSystemTrayNotifications);
obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
@@ -673,6 +683,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
showSystemTrayNotifications =
obs_data_get_bool(obj, "showSystemTrayNotifications");
disableHints = obs_data_get_bool(obj, "disableHints");
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
@@ -971,6 +983,7 @@ void AdvSceneSwitcher::setupGeneralTab()
ui->showTrayNotifications->setChecked(
switcher->showSystemTrayNotifications);
ui->uiHintsDisable->setChecked(switcher->disableHints);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
for (int p : switcher->functionNamesByPriority) {

View File

@@ -9,16 +9,6 @@ bool MacroActionSudioMode::_registered = MacroActionFactory::Register(
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
// TODO: Remove in future version (backwards compatibility)
static const std::string idOld1 = "scene_swap";
static const std::string idOld2 = "preview_scene";
static bool oldRegisterd1 = MacroActionFactory::Register(
idOld1, {MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
static bool oldRegisterd2 = MacroActionFactory::Register(
idOld2, {MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
const static std::map<StudioModeAction, std::string> actionTypes = {
{StudioModeAction::SWAP_SCENE,
"AdvSceneSwitcher.action.studioMode.type.swap"},
@@ -98,10 +88,6 @@ bool MacroActionSudioMode::Load(obs_data_t *obj)
_action =
static_cast<StudioModeAction>(obs_data_get_int(obj, "action"));
_scene.Load(obj);
// TODO: Remove in future version (backwards compatibility)
if (obs_data_get_string(obj, "id") == idOld2) {
_action = StudioModeAction::SET_SCENE;
}
return true;
}

View File

@@ -119,7 +119,7 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
bool MacroActionVariable::PerformAction()
{
auto var = GetVariableByName(_variableName);
auto var = _variable.lock();
if (!var) {
return true;
}
@@ -132,7 +132,7 @@ bool MacroActionVariable::PerformAction()
apppend(*var, _strValue);
break;
case Type::APPEND_VAR: {
auto var2 = GetVariableByName(_variable2Name);
auto var2 = _variable2.lock();
if (!var2) {
return true;
}
@@ -171,18 +171,18 @@ bool MacroActionVariable::PerformAction()
}
case Type::SUBSTRING: {
if (_regex.Enabled()) {
HandleRegexSubString(var);
HandleRegexSubString(var.get());
return true;
}
HandleIndexSubString(var);
HandleIndexSubString(var.get());
return true;
}
case Type::FIND_AND_REPLACE: {
HandleFindAndReplace(var);
HandleFindAndReplace(var.get());
return true;
}
case Type::MATH_EXPRESSION: {
HandleMathExpression(var);
HandleMathExpression(var.get());
return true;
}
}
@@ -193,8 +193,10 @@ bool MacroActionVariable::PerformAction()
bool MacroActionVariable::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
obs_data_set_string(obj, "variableName",
GetWeakVariableName(_variable).c_str());
obs_data_set_string(obj, "variable2Name",
GetWeakVariableName(_variable2).c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
@@ -213,8 +215,10 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
bool MacroActionVariable::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_variable2Name = obs_data_get_string(obj, "variable2Name");
_variable =
GetWeakVariableByName(obs_data_get_string(obj, "variableName"));
_variable2 = GetWeakVariableByName(
obs_data_get_string(obj, "variable2Name"));
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
@@ -238,7 +242,7 @@ bool MacroActionVariable::PostLoad()
std::string MacroActionVariable::GetShortDesc() const
{
return _variableName;
return GetWeakVariableName(_variable);
}
void MacroActionVariable::SetSegmentIndexValue(int value)
@@ -463,8 +467,8 @@ void MacroActionVariableEdit::UpdateEntryData()
return;
}
_variables->SetVariable(_entryData->_variableName);
_variables2->SetVariable(_entryData->_variable2Name);
_variables->SetVariable(_entryData->_variable);
_variables2->SetVariable(_entryData->_variable2);
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setPlainText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
@@ -489,7 +493,7 @@ void MacroActionVariableEdit::VariableChanged(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
_entryData->_variable = GetWeakVariableByQString(text);
}
void MacroActionVariableEdit::Variable2Changed(const QString &text)
@@ -499,7 +503,7 @@ void MacroActionVariableEdit::Variable2Changed(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variable2Name = text.toStdString();
_entryData->_variable2 = GetWeakVariableByQString(text);
}
void MacroActionVariableEdit::ActionChanged(int value)

View File

@@ -36,8 +36,8 @@ public:
};
Type _type = Type::SET_FIXED_VALUE;
std::string _variableName = "";
std::string _variable2Name = "";
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
std::string _strValue = "";
double _numValue = 0;
int _subStringStart = 0;

View File

@@ -18,7 +18,7 @@ static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
void MacroActionWebsocket::SendRequest()
{
auto connection = GetConnectionByName(_connection);
auto connection = _connection.lock();
if (!connection) {
return;
}
@@ -46,7 +46,7 @@ void MacroActionWebsocket::LogAction() const
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
GetWeakConnectionName(_connection).c_str());
break;
case MacroActionWebsocket::Type::EVENT:
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
@@ -62,7 +62,8 @@ bool MacroActionWebsocket::Save(obs_data_t *obj) const
MacroAction::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
_message.Save(obj, "message");
obs_data_set_string(obj, "connection", _connection.c_str());
obs_data_set_string(obj, "connection",
GetWeakConnectionName(_connection).c_str());
return true;
}
@@ -71,14 +72,15 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message.Load(obj, "message");
_connection = obs_data_get_string(obj, "connection");
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
return true;
}
std::string MacroActionWebsocket::GetShortDesc() const
{
if (_type == Type::REQUEST) {
return _connection;
return GetWeakConnectionName(_connection);
}
return "";
}
@@ -206,6 +208,6 @@ void MacroActionWebsocketEdit::ConnectionSelectionChanged(
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_connection = connection.toStdString();
_entryData->_connection = GetWeakConnectionByQString(connection);
emit(HeaderInfoChanged(connection));
}

View File

@@ -29,7 +29,7 @@ public:
Type _type = Type::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
std::string _connection;
std::weak_ptr<Connection> _connection;
private:
void SendRequest();

View File

@@ -76,8 +76,8 @@ bool MacroConditionVariable::ValueChanged(const Variable &var)
bool MacroConditionVariable::CompareVariables()
{
auto var1 = GetVariableByName(_variableName);
auto var2 = GetVariableByName(_variable2Name);
auto var1 = _variable.lock();
auto var2 = _variable2.lock();
if (!var1 || !var2) {
return false;
}
@@ -105,7 +105,7 @@ bool MacroConditionVariable::CompareVariables()
bool MacroConditionVariable::CheckCondition()
{
auto var = GetVariableByName(_variableName);
auto var = _variable.lock();
if (!var) {
return false;
}
@@ -137,8 +137,10 @@ bool MacroConditionVariable::CheckCondition()
bool MacroConditionVariable::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
obs_data_set_string(obj, "variableName",
GetWeakVariableName(_variable).c_str());
obs_data_set_string(obj, "variable2Name",
GetWeakVariableName(_variable2).c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
@@ -149,8 +151,10 @@ bool MacroConditionVariable::Save(obs_data_t *obj) const
bool MacroConditionVariable::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_variable2Name = obs_data_get_string(obj, "variable2Name");
_variable =
GetWeakVariableByName(obs_data_get_string(obj, "variableName"));
_variable2 = GetWeakVariableByName(
obs_data_get_string(obj, "variable2Name"));
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
@@ -165,7 +169,7 @@ bool MacroConditionVariable::Load(obs_data_t *obj)
std::string MacroConditionVariable::GetShortDesc() const
{
return _variableName;
return GetWeakVariableName(_variable);
}
static inline void populateConditionSelection(QComboBox *list)
@@ -236,8 +240,8 @@ void MacroConditionVariableEdit::UpdateEntryData()
return;
}
_variables->SetVariable(_entryData->_variableName);
_variables2->SetVariable(_entryData->_variable2Name);
_variables->SetVariable(_entryData->_variable);
_variables2->SetVariable(_entryData->_variable2);
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setPlainText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
@@ -252,7 +256,7 @@ void MacroConditionVariableEdit::VariableChanged(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
_entryData->_variable = GetWeakVariableByQString(text);
}
void MacroConditionVariableEdit::Variable2Changed(const QString &text)
@@ -263,7 +267,7 @@ void MacroConditionVariableEdit::Variable2Changed(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variable2Name = text.toStdString();
_entryData->_variable2 = GetWeakVariableByQString(text);
}
void MacroConditionVariableEdit::ConditionChanged(int value)

View File

@@ -35,8 +35,8 @@ public:
};
Type _type = Type::EQUALS;
std::string _variableName = "";
std::string _variable2Name = "";
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
std::string _strValue = "";
double _numValue = 0;
RegexConfig _regex;

View File

@@ -38,7 +38,7 @@ bool MacroConditionWebsocket::CheckCondition()
messages = &switcher->websocketMessages;
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = GetConnectionByName(_connection);
auto connection = _connection.lock();
if (!connection) {
return false;
}
@@ -76,7 +76,8 @@ bool MacroConditionWebsocket::Save(obs_data_t *obj) const
obs_data_set_int(obj, "type", static_cast<int>(_type));
_message.Save(obj, "message");
_regex.Save(obj);
obs_data_set_string(obj, "connection", _connection.c_str());
obs_data_set_string(obj, "connection",
GetWeakConnectionName(_connection).c_str());
return true;
}
@@ -91,7 +92,8 @@ bool MacroConditionWebsocket::Load(obs_data_t *obj)
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"), false);
}
_connection = obs_data_get_string(obj, "connection");
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
return true;
}
@@ -100,7 +102,7 @@ std::string MacroConditionWebsocket::GetShortDesc() const
if (_type == Type::REQUEST) {
return "";
}
return _connection;
return GetWeakConnectionName(_connection);
}
static inline void populateConditionSelection(QComboBox *list)
@@ -237,7 +239,7 @@ void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_connection = connection.toStdString();
_entryData->_connection = GetWeakConnectionByQString(connection);
emit(HeaderInfoChanged(connection));
}

View File

@@ -27,7 +27,7 @@ public:
Type _type = Type::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
std::string _connection;
std::weak_ptr<Connection> _connection;
private:
static bool _registered;

View File

@@ -44,6 +44,9 @@ MacroDock::MacroDock(Macro *m, QWidget *parent)
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
}
void MacroDock::SetName(const QString &name)

View File

@@ -1,7 +1,6 @@
#include "macro-properties.hpp"
#include <QVBoxLayout>
#include <QGroupBox>
#include <QDialogButtonBox>
#include <obs-module.h>
@@ -52,8 +51,6 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setMinimumWidth(500);
setMinimumHeight(300);
auto highlightOptions = new QGroupBox(
obs_module_text("AdvSceneSwitcher.macroTab.highlightSettings"));
@@ -70,13 +67,13 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
hotkeyLayout->addWidget(_currentMacroRegisterHotkeys);
hotkeyOptions->setLayout(hotkeyLayout);
auto dockOptions = new QGroupBox(
_dockOptions = new QGroupBox(
obs_module_text("AdvSceneSwitcher.macroTab.dockSettings"));
QVBoxLayout *dockLayout = new QVBoxLayout;
dockLayout->addWidget(_currentMacroRegisterDock);
dockLayout->addWidget(_currentMacroDockAddRunButton);
dockLayout->addWidget(_currentMacroDockAddPauseButton);
dockOptions->setLayout(dockLayout);
_dockOptions->setLayout(dockLayout);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
@@ -90,7 +87,7 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(highlightOptions);
layout->addWidget(hotkeyOptions);
layout->addWidget(dockOptions);
layout->addWidget(_dockOptions);
layout->addWidget(buttonbox);
setLayout(layout);
@@ -100,7 +97,7 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
if (!macro || macro->IsGroup()) {
hotkeyOptions->hide();
dockOptions->hide();
_dockOptions->hide();
return;
}
_currentMacroRegisterHotkeys->setChecked(macro->PauseHotkeysEnabled());
@@ -117,6 +114,10 @@ void MacroPropertiesDialog::DockEnableChanged(int enabled)
{
_currentMacroDockAddRunButton->setVisible(enabled);
_currentMacroDockAddPauseButton->setVisible(enabled);
_dockOptions->adjustSize();
_dockOptions->updateGeometry();
adjustSize();
updateGeometry();
}
bool MacroPropertiesDialog::AskForSettings(QWidget *parent,

View File

@@ -4,6 +4,7 @@
#include <QWidget>
#include <QDialog>
#include <QCheckBox>
#include <QGroupBox>
#include <obs-data.h>
class MacroProperties {
@@ -39,4 +40,5 @@ private:
QCheckBox *_currentMacroRegisterDock;
QCheckBox *_currentMacroDockAddRunButton;
QCheckBox *_currentMacroDockAddPauseButton;
QGroupBox *_dockOptions;
};

View File

@@ -125,6 +125,13 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
}
if (macros.size() == 1) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.macroTab.deleteSingleMacroConfirmation");
if (!DisplayMessage(deleteWarning.arg(QString::fromStdString(
macros.at(0)->Name())),
true)) {
return;
}
RemoveMacro(macros.at(0));
return;
}

View File

@@ -245,7 +245,7 @@ MacroIndexToModelIndex(int realIdx,
inCollapsedGroup = false;
continue;
}
const auto &m = macros[i];
const auto &m = macros.at(i);
inCollapsedGroup = m->IsGroup() && m->IsCollapsed();
groupSize = m->GroupSize();
modelIdx++;
@@ -256,15 +256,24 @@ MacroIndexToModelIndex(int realIdx,
void MacroTreeModel::MoveItemBefore(const std::shared_ptr<Macro> &item,
const std::shared_ptr<Macro> &before)
{
if (item == before) {
if (!item || !before || item == before) {
return;
}
auto modelFromEndIdx = GetItemModelIndex(item);
auto modelFromIdx = modelFromEndIdx;
auto modelTo = GetItemModelIndex(before);
auto macroFrom = GetItemMacroIndex(item);
auto macroTo = GetItemMacroIndex(before);
int modelFromIdx = 0, modelFromEndIdx = 0, modelTo = 0, macroFrom = 0,
macroTo = 0;
try {
modelFromEndIdx = GetItemModelIndex(item);
modelFromIdx = modelFromEndIdx;
modelTo = GetItemModelIndex(before);
macroFrom = GetItemMacroIndex(item);
macroTo = GetItemMacroIndex(before);
} catch (const std::out_of_range &) {
blog(LOG_WARNING,
"error while trying to move item \"%s\" before \"%s\"",
item->Name().c_str(), before->Name().c_str());
return;
}
if (before->IsSubitem()) {
modelTo -= before->IsCollapsed() ? 0 : before->GroupSize();
@@ -300,15 +309,24 @@ void MacroTreeModel::MoveItemBefore(const std::shared_ptr<Macro> &item,
void MacroTreeModel::MoveItemAfter(const std::shared_ptr<Macro> &item,
const std::shared_ptr<Macro> &after)
{
if (item == after) {
if (!item || !after || item == after) {
return;
}
auto modelFromIdx = GetItemModelIndex(item);
auto modelFromEndIdx = modelFromIdx;
auto modelTo = GetItemModelIndex(after);
auto macroFrom = GetItemMacroIndex(item);
auto macroTo = GetItemMacroIndex(after);
int modelFromIdx = 0, modelFromEndIdx = 0, modelTo = 0, macroFrom = 0,
macroTo = 0;
try {
modelFromIdx = GetItemModelIndex(item);
modelFromEndIdx = modelFromIdx;
modelTo = GetItemModelIndex(after);
macroFrom = GetItemMacroIndex(item);
macroTo = GetItemMacroIndex(after);
} catch (const std::out_of_range &) {
blog(LOG_WARNING,
"error while trying to move item \"%s\" after \"%s\"",
item->Name().c_str(), after->Name().c_str());
return;
}
if (after->IsGroup()) {
modelTo += after->IsCollapsed() ? 0 : after->GroupSize();

View File

@@ -267,6 +267,11 @@ void Macro::SetOnChangeHighlight()
_onChangeTriggered = true;
}
bool Macro::DockIsVisible() const
{
return _dock && _dockAction && _dock->isVisible();
}
void Macro::SetPaused(bool pause)
{
if (_paused && !pause) {
@@ -341,13 +346,7 @@ bool Macro::Save(obs_data_t *obj) const
return true;
}
obs_data_set_bool(obj, "registerDock", _registerDock);
// The object name is used to restore the position of the dock
if (_registerDock) {
SetDockWidgetName();
}
obs_data_set_bool(obj, "dockHasRunButton", _dockHasRunButton);
obs_data_set_bool(obj, "dockHasPauseButton", _dockHasPauseButton);
SaveDockSettings(obj);
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey);
@@ -438,9 +437,7 @@ bool Macro::Load(obs_data_t *obj)
return true;
}
_dockHasRunButton = obs_data_get_bool(obj, "dockHasRunButton");
_dockHasPauseButton = obs_data_get_bool(obj, "dockHasPauseButton");
EnableDock(obs_data_get_bool(obj, "registerDock"));
LoadDockSettings(obj);
obs_data_set_default_bool(obj, "registerHotkeys", true);
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
@@ -583,22 +580,103 @@ bool Macro::PauseHotkeysEnabled()
return _registerHotkeys;
}
void Macro::SaveDockSettings(obs_data_t *obj) const
{
auto dockSettings = obs_data_create();
obs_data_set_bool(dockSettings, "register", _registerDock);
// The object name is used to restore the position of the dock
if (_registerDock) {
SetDockWidgetName();
}
obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton);
if (_dock) {
auto window = static_cast<QMainWindow *>(
obs_frontend_get_main_window());
obs_data_set_bool(dockSettings, "isFloating",
_dock->isFloating());
obs_data_set_bool(dockSettings, "isVisible", DockIsVisible());
obs_data_set_int(dockSettings, "area",
window->dockWidgetArea(_dock));
obs_data_set_string(
dockSettings, "geometry",
_dock->saveGeometry().toBase64().constData());
}
obs_data_set_obj(obj, "dockSettings", dockSettings);
obs_data_release(dockSettings);
}
void Macro::LoadDockSettings(obs_data_t *obj)
{
auto dockSettings = obs_data_get_obj(obj, "dockSettings");
if (!dockSettings) {
// TODO: Remove this fallback
_dockHasRunButton = obs_data_get_bool(obj, "dockHasRunButton");
_dockHasPauseButton =
obs_data_get_bool(obj, "dockHasPauseButton");
EnableDock(obs_data_get_bool(obj, "registerDock"));
return;
}
const bool dockEnabled = obs_data_get_bool(dockSettings, "register");
_dockIsVisible = obs_data_get_bool(dockSettings, "isVisible");
if (dockEnabled) {
_dockHasRunButton =
obs_data_get_bool(dockSettings, "hasRunButton");
_dockHasPauseButton =
obs_data_get_bool(dockSettings, "hasPauseButton");
_dockIsFloating = obs_data_get_bool(dockSettings, "isFloating");
_dockArea = static_cast<Qt::DockWidgetArea>(
obs_data_get_int(dockSettings, "area"));
auto geometryStr =
obs_data_get_string(dockSettings, "geometry");
if (geometryStr && strlen(geometryStr)) {
_dockGeo =
QByteArray::fromBase64(QByteArray(geometryStr));
}
}
EnableDock(dockEnabled);
obs_data_release(dockSettings);
}
void Macro::EnableDock(bool value)
{
if (_registerDock == value) {
return;
}
// Reset dock regardless
RemoveDock();
if (!_registerDock) {
_dock = new MacroDock(this,
static_cast<QMainWindow *>(
obs_frontend_get_main_window()));
_dockAction =
static_cast<QAction *>(obs_frontend_add_dock(_dock));
SetDockWidgetName();
// Unregister dock
if (_registerDock) {
_dockIsFloating = true;
_dockGeo = QByteArray();
_registerDock = value;
return;
}
// Create new dock widget
auto window =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
_dock = new MacroDock(this, window);
SetDockWidgetName(); // Used by OBS to restore position
// Register new dock
_dockAction = static_cast<QAction *>(obs_frontend_add_dock(_dock));
// Note that OBSBasic::OBSInit() has precedence over the visibility and
// geometry set here.
// The function calls here are only intended to attempt to restore the
// dock status when switching scene collections.
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
}
if (_dock->isFloating() != _dockIsFloating) {
_dock->setFloating(_dockIsFloating);
}
_dock->restoreGeometry(_dockGeo);
_registerDock = value;
}

View File

@@ -4,6 +4,7 @@
#include "macro-ref.hpp"
#include <QString>
#include <QByteArray>
#include <string>
#include <deque>
#include <memory>
@@ -96,7 +97,10 @@ private:
void RunActions(bool &ret, bool ignorePause);
void RunActions(bool ignorePause);
void SetOnChangeHighlight();
bool DockIsVisible() const;
void SetDockWidgetName() const;
void SaveDockSettings(obs_data_t *obj) const;
void LoadDockSettings(obs_data_t *obj);
void RemoveDock();
std::string _name = "";
@@ -125,6 +129,10 @@ private:
bool _registerDock = false;
bool _dockHasRunButton = true;
bool _dockHasPauseButton = true;
bool _dockIsFloating = true;
bool _dockIsVisible = false;
Qt::DockWidgetArea _dockArea;
QByteArray _dockGeo;
MacroDock *_dock = nullptr;
QAction *_dockAction = nullptr;

View File

@@ -147,6 +147,10 @@ cv::Mat preprocessForOCR(const QImage &image, const QColor &color)
std::string runOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
const QColor &color)
{
if (image.isNull()) {
return "";
}
#ifdef OCR_SUPPORT
auto mat = preprocessForOCR(image, color);
ocr->SetImage(mat.data, mat.cols, mat.rows, 1, mat.step);

View File

@@ -32,7 +32,7 @@ bool PatternMatchParameters::Load(obs_data_t *obj)
}
useAlphaAsMask = obs_data_get_bool(data, "useAlphaAsMask");
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "matchMode")) {
if (!obs_data_has_user_value(data, "matchMode")) {
matchMode = cv::TM_CCORR_NORMED;
} else {
matchMode = static_cast<cv::TemplateMatchModes>(

View File

@@ -146,6 +146,9 @@ StatusDock::StatusDock(QWidget *parent) : OBSDock(parent)
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
}
void SetupDock()

View File

@@ -106,6 +106,9 @@ struct SwitcherData {
int interval = default_interval;
QStringList loadFailureLibs;
bool warnPluginLoadFailure = true;
obs_source_t *waitScene = nullptr;
OBSWeakSource currentScene = nullptr;
OBSWeakSource previousScene = nullptr;

View File

@@ -133,6 +133,32 @@ Connection *GetConnectionByName(const std::string &name)
return nullptr;
}
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
{
for (const auto &c : switcher->connections) {
if (c->Name() == name) {
std::weak_ptr<Connection> wp =
std::dynamic_pointer_cast<Connection>(c);
return wp;
}
}
return std::weak_ptr<Connection>();
}
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name)
{
return GetWeakConnectionByName(name.toStdString());
}
std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
{
auto con = connection.lock();
if (!con) {
return "invalid connection selection";
}
return con->Name();
}
bool ConnectionNameAvailable(const QString &name)
{
return !GetConnectionByName(name);
@@ -187,6 +213,18 @@ void ConnectionSelection::SetConnection(const std::string &con)
}
}
void ConnectionSelection::SetConnection(
const std::weak_ptr<Connection> &connection_)
{
const QSignalBlocker blocker(_selection);
auto connection = connection_.lock();
if (connection) {
SetConnection(connection->Name());
} else {
_selection->setCurrentIndex(0);
}
}
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings)
: ItemSettingsDialog(settings, switcher->connections,

View File

@@ -56,6 +56,9 @@ private:
Connection *GetConnectionByName(const QString &);
Connection *GetConnectionByName(const std::string &);
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
class ConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
@@ -92,4 +95,5 @@ class ConnectionSelection : public ItemSelection {
public:
ConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::string &);
void SetConnection(const std::weak_ptr<Connection> &);
};

View File

@@ -1,10 +1,5 @@
#include "math-helpers.hpp"
#include <string_view>
#include <algorithm>
#include <vector>
#include <stack>
#include <cmath>
#include <exprtk.hpp>
#ifdef UNIT_TEST
const char *obs_module_text(const char *text)
@@ -15,313 +10,21 @@ const char *obs_module_text(const char *text)
#include <obs-module.h>
#endif
static std::vector<std::string_view> nonOperatorSeparators = {
" ",
"\t",
};
typedef exprtk::expression<double> expression_t;
typedef exprtk::parser<double> parser_t;
static std::vector<std::string_view> brackets = {
"(",
")",
};
static std::vector<std::string_view> unaryOperators = {
"abs(", "sin(", "cos(", "tan(", "sqrt(",
};
static std::vector<std::string_view> binaryOperators = {
"+",
"-",
"*",
"/",
};
static bool isBracket(const std::string &token)
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
{
return std::find(brackets.begin(), brackets.end(), token) !=
brackets.end();
}
expression_t expression;
static bool isUnaryOperator(const std::string &token)
{
return std::find(unaryOperators.begin(), unaryOperators.end(), token) !=
unaryOperators.end();
}
parser_t parser;
static bool isBinaryOperator(const std::string &token)
{
return std::find(binaryOperators.begin(), binaryOperators.end(),
token) != binaryOperators.end();
}
static bool isOperator(const std::string &token)
{
return isUnaryOperator(token) || isBinaryOperator(token);
}
static void addOperatorToken(std::vector<std::string> &tokens,
const std::string &separator)
{
if (separator.back() != '(') {
tokens.push_back(separator);
return;
if (parser.compile(expr, expression)) {
return expression.value();
}
tokens.push_back("(");
tokens.push_back(separator);
}
static std::vector<std::string> splitStringIntoTokens(const std::string &input)
{
auto separators = nonOperatorSeparators;
separators.insert(separators.end(), brackets.begin(), brackets.end());
separators.insert(separators.end(), unaryOperators.begin(),
unaryOperators.end());
separators.insert(separators.end(), binaryOperators.begin(),
binaryOperators.end());
std::vector<std::string> tokens;
std::string::size_type start = 0;
while (start < input.length()) {
std::string::size_type min_pos = std::string::npos;
std::string separator;
// Find the next occurrence of each separator
for (const auto &sep : separators) {
std::string::size_type pos = input.find(sep, start);
if (pos != std::string::npos && pos < min_pos) {
min_pos = pos;
separator = sep;
}
}
// If a separator was found, add the token before it to the vector
if (min_pos != std::string::npos) {
const auto token = input.substr(start, min_pos - start);
start = min_pos + separator.length();
if (!std::all_of(token.begin(), token.end(), isspace)) {
tokens.push_back(token);
}
// If the separator itself was an operator, add it as a
// token to the vector
if (isOperator(separator)) {
addOperatorToken(tokens, separator);
}
if (isBracket(separator)) {
tokens.push_back(separator);
}
}
// Otherwise, add the remaining string to the vector and exit the
// loop
else {
tokens.push_back(input.substr(start));
break;
}
}
return tokens;
}
static int precedence(const std::string &op)
{
if (op == "*" || op == "/") {
return 2;
} else if (op == "+" || op == "-") {
return 1;
} else {
return 0;
}
}
static std::string getErrorMsg(const std::string &expr, const std::string &msg)
{
return std::string(obs_module_text(
"AdvSceneSwitcher.math.expressionFail")) +
" \"" + expr + "\":\n" + msg;
}
static std::string getErrorMsg(const std::string &expr,
std::stack<std::string> binaryOperators,
std::stack<double> operands,
const std::string &msg)
{
std::string errMsg = getErrorMsg(expr, msg);
errMsg += "\n\n ---" +
std::string(
obs_module_text("AdvSceneSwitcher.math.operators")) +
" ---\n";
while (!binaryOperators.empty()) {
errMsg += binaryOperators.top() + "\n";
binaryOperators.pop();
}
errMsg +=
"\n\n --- " +
std::string(obs_module_text("AdvSceneSwitcher.math.operands")) +
" ---\n";
while (!operands.empty()) {
errMsg += std::to_string(operands.top()) + "\n";
operands.pop();
}
return errMsg;
}
static bool evalHelper(std::stack<std::string> &operators,
std::stack<double> &operands)
{
std::string op = operators.top();
operators.pop();
if (isUnaryOperator(op)) {
if (operands.size() < 1) {
return false;
}
double value = operands.top();
operands.pop();
double result;
if (op == "abs(") {
result = abs(value);
} else if (op == "sin(") {
result = sin(value);
} else if (op == "cos(") {
result = cos(value);
} else if (op == "tan(") {
result = cos(value);
} else if (op == "sqrt(") {
result = sqrt(value);
}
operands.push(result);
return true;
}
if (operands.size() < 2) {
return false;
}
double op2 = operands.top();
operands.pop();
double op1 = operands.top();
operands.pop();
double result;
if (op == "+") {
result = op1 + op2;
} else if (op == "-") {
result = op1 - op2;
} else if (op == "*") {
result = op1 * op2;
} else if (op == "/") {
result = op1 / op2;
}
operands.push(result);
return true;
}
std::variant<double, std::string>
EvalMathExpression(const std::string &expression)
{
// Create a stack to store operands and operators
std::stack<double> operands;
std::stack<std::string> operators;
auto tokens = splitStringIntoTokens(expression);
// Loop through each token in the expression
for (const auto &token : tokens) {
// If the token is a number, push it onto the operand stack
if (isdigit(token[0])) {
auto operand = GetDouble(token);
if (!operand.has_value()) {
return getErrorMsg(
expression,
"\"" + token + "\" " +
obs_module_text(
"AdvSceneSwitcher.math.notANumber"));
}
operands.push(*operand);
}
// If the token is an operator, evaluate higher-precedence
// operators first
else if (isBinaryOperator(token)) {
while (!operators.empty() &&
precedence(operators.top()) >=
precedence(token)) {
if (operators.empty() || operands.empty()) {
return getErrorMsg(expression,
operators, operands,
"");
}
if (!evalHelper(operators, operands)) {
return getErrorMsg(expression,
operators, operands,
"");
}
}
operators.push(token);
}
// If the token is an opening bracket, push it onto the
// operator stack
else if (token == "(") {
operators.push(token);
}
// If the token is a closing bracket, evaluate the expression
// inside the brackets
else if (token == ")") {
if (operators.empty()) {
return getErrorMsg(
expression, operators, operands,
obs_module_text(
"AdvSceneSwitcher.math.expressionFailParentheses"));
}
while (operators.top() != "(") {
if (operators.empty() || operands.empty()) {
return getErrorMsg(expression,
operators, operands,
"");
}
if (!evalHelper(operators, operands) ||
operators.empty()) {
return getErrorMsg(
expression, operators, operands,
obs_module_text(
"AdvSceneSwitcher.math.expressionFailParentheses"));
}
}
operators.pop(); // Pop the opening bracket
}
else if (isUnaryOperator(token)) {
operators.push(token);
}
else if (!token.empty()) {
return getErrorMsg(
expression,
std::string(obs_module_text(
"AdvSceneSwitcher.math.invalidToken")) +
" \"" + token + "\"");
}
}
// Evaluate any remaining operators in the stack
while (!operators.empty()) {
if (!evalHelper(operators, operands) || operands.empty()) {
return getErrorMsg(expression, operators, operands, "");
}
}
// The result is the top operand on the stack
if (operands.size() != 1) {
return getErrorMsg(
expression, operators, operands,
std::string(obs_module_text(
"AdvSceneSwitcher.math.notFullyResolved")));
}
return operands.top();
" \"" + expr;
}
bool IsValidNumber(const std::string &str)

View File

@@ -11,8 +11,10 @@ target_include_directories(
${PROJECT_NAME}
PRIVATE "${ADVSS_SOURCE_DIR}/src" "${ADVSS_SOURCE_DIR}/src/legacy"
"${ADVSS_SOURCE_DIR}/src/macro-core" "${ADVSS_SOURCE_DIR}/src/utils"
"${ADVSS_SOURCE_DIR}/forms")
install(TARGETS advanced-scene-switcher-lib
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
"${ADVSS_SOURCE_DIR}/forms" "${ADVSS_SOURCE_DIR}/deps/exprtk")
if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
/bigobj)
endif()
enable_testing()

View File

@@ -11,6 +11,12 @@ TEST_CASE("Expressions are evaluated successfully", "[math-helpers]")
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == 1.0);
expressionResult = EvalMathExpression("-1");
doubleValuePtr = std::get_if<double>(&expressionResult);
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == -1.0);
expressionResult = EvalMathExpression("1 + 2");
doubleValuePtr = std::get_if<double>(&expressionResult);
@@ -29,8 +35,27 @@ TEST_CASE("Expressions are evaluated successfully", "[math-helpers]")
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == 9.0);
expressionResult = EvalMathExpression("(1 - 2) * 3");
doubleValuePtr = std::get_if<double>(&expressionResult);
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == -3.0);
expressionResult = EvalMathExpression("(1-2)*3");
doubleValuePtr = std::get_if<double>(&expressionResult);
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == -3.0);
expressionResult =
EvalMathExpression("cos(abs(1 - sqrt(10 - 2 * 3)) -1)");
EvalMathExpression("cos(abs(1 - sqrt(10 - 2 * 3)) - 1)");
doubleValuePtr = std::get_if<double>(&expressionResult);
REQUIRE(doubleValuePtr != nullptr);
REQUIRE(*doubleValuePtr == 1.0);
expressionResult =
EvalMathExpression("cos(abs(1 - sqrt(10 -2 * 3)) -1)");
doubleValuePtr = std::get_if<double>(&expressionResult);
REQUIRE(doubleValuePtr != nullptr);