From 151f69305b91a52b5a5d5f6c4b420d1285fa2dfe Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:48:53 +0200 Subject: [PATCH] Add undo / redo support for macros --- CMakeLists.txt | 2 + data/locale/en-US.ini | 11 + lib/macro/macro-edit.cpp | 156 +++++- lib/macro/macro-edit.hpp | 10 +- lib/macro/macro-tab.cpp | 20 +- lib/macro/macro-tree.cpp | 4 + lib/macro/macro-undo-redo.cpp | 931 ++++++++++++++++++++++++++++++++++ lib/macro/macro-undo-redo.hpp | 24 + tests/stubs/macro-edit.cpp | 4 +- 9 files changed, 1142 insertions(+), 20 deletions(-) create mode 100644 lib/macro/macro-undo-redo.cpp create mode 100644 lib/macro/macro-undo-redo.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index df1e47c3..7dc00a3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,8 @@ target_sources( lib/macro/macro-signals.hpp lib/macro/macro-tab.cpp lib/macro/macro-tree.cpp + lib/macro/macro-undo-redo.cpp + lib/macro/macro-undo-redo.hpp lib/macro/macro-tree.hpp lib/macro/macro-websocket-trigger.cpp lib/macro/macro.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e5b5154c..188c6dbf 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -198,6 +198,17 @@ AdvSceneSwitcher.macroTab.groupNameExists="The name \"%1\" is already used by a AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Are you sure you want to remove \"%1\"?" AdvSceneSwitcher.macroTab.removeMultipleMacrosPopup.text="Are you sure you want to remove %1 macros/groups?" AdvSceneSwitcher.macroTab.removeGroupPopup.text="Are you sure you want to remove \"%1\" group and all its elements?" +AdvSceneSwitcher.undo.renameMacro="Rename Macro '%1'" +AdvSceneSwitcher.undo.addMacro="Add Macro '%1'" +AdvSceneSwitcher.undo.removeMacro="Remove Macro '%1'" +AdvSceneSwitcher.undo.addSegment="Add %1 in '%2'" +AdvSceneSwitcher.undo.removeSegment="Remove %1 in '%2'" +AdvSceneSwitcher.undo.groupMacros="Group Macros '%1'" +AdvSceneSwitcher.undo.ungroupMacros="Ungroup Macros '%1'" +AdvSceneSwitcher.undo.removeMacroGroup="Remove Macro Group '%1'" +AdvSceneSwitcher.undo.segment.action="Action" +AdvSceneSwitcher.undo.segment.elseAction="Else Action" +AdvSceneSwitcher.undo.segment.condition="Condition" AdvSceneSwitcher.macroTab.contextMenuAdd="Add" AdvSceneSwitcher.macroTab.copy="Duplicate Macro" AdvSceneSwitcher.macroTab.group="Group Selected Macros" diff --git a/lib/macro/macro-edit.cpp b/lib/macro/macro-edit.cpp index 4f5e8f4e..a3aa8d2d 100644 --- a/lib/macro/macro-edit.cpp +++ b/lib/macro/macro-edit.cpp @@ -1,6 +1,7 @@ #include "macro-edit.hpp" #include "condition-logic.hpp" +#include "macro-undo-redo.hpp" #include "cursor-shape-changer.hpp" #include "macro-action-edit.hpp" #include "macro-action-macro.hpp" @@ -257,6 +258,92 @@ std::shared_ptr MacroEdit::GetMacro() const return _currentMacro; } +void MacroEdit::InsertSegmentWidget(SegmentType type, int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + switch (type) { + case SegmentType::ACTION: { + if (idx < 0 || idx > (int)macro->Actions().size()) { + break; + } + ui->actionsList->Insert( + idx, new MacroActionEdit(this, ¯o->Actions()[idx])); + SetActionData(*macro); + ui->actionsList->SetHelpMsgVisible(false); + break; + } + case SegmentType::ELSE_ACTION: { + if (idx < 0 || idx > (int)macro->ElseActions().size()) { + break; + } + ui->elseActionsList->Insert( + idx, + new MacroActionEdit(this, ¯o->ElseActions()[idx])); + SetElseActionData(*macro); + ui->elseActionsList->SetHelpMsgVisible(false); + break; + } + case SegmentType::CONDITION: { + if (idx < 0 || idx > (int)macro->Conditions().size()) { + break; + } + ui->conditionsList->Insert( + idx, + new MacroConditionEdit(this, ¯o->Conditions()[idx], + idx == 0)); + if (idx == 0 && macro->Conditions().size() > 1) { + static_cast( + ui->conditionsList->WidgetAt(1)) + ->SetRootNode(false); + } + SetConditionData(*macro); + ui->conditionsList->SetHelpMsgVisible(false); + break; + } + } +} + +void MacroEdit::RemoveSegmentWidget(SegmentType type, int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + switch (type) { + case SegmentType::ACTION: + if (idx >= 0 && + idx < ui->actionsList->ContentLayout()->count()) { + ui->actionsList->Remove(idx); + SetActionData(*macro); + } + break; + case SegmentType::ELSE_ACTION: + if (idx >= 0 && + idx < ui->elseActionsList->ContentLayout()->count()) { + ui->elseActionsList->Remove(idx); + SetElseActionData(*macro); + } + break; + case SegmentType::CONDITION: + if (idx >= 0 && + idx < ui->conditionsList->ContentLayout()->count()) { + ui->conditionsList->Remove(idx); + if (idx == 0 && !macro->Conditions().empty()) { + static_cast( + ui->conditionsList->WidgetAt(0)) + ->SetRootNode(true); + } + SetConditionData(*macro); + } + break; + } +} + void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const { ui->conditionsList->ClearWidgetsFromCacheFor(macro); @@ -295,7 +382,7 @@ isValidMacroSegmentIdx(const std::deque> &list, return (idx > 0 || (unsigned)idx < list.size()); } -void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) +void MacroEdit::SetupMacroSegmentSelection(SegmentType type, int idx) { auto macro = _currentMacro; if (!macro) { @@ -308,7 +395,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) std::deque> segments; switch (type) { - case MacroEdit::MacroSection::CONDITIONS: + case MacroEdit::SegmentType::CONDITION: setList = ui->conditionsList; setIdx = ¤tConditionIdx; segments = {macro->Conditions().begin(), @@ -319,7 +406,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) resetIdx1 = ¤tActionIdx; resetIdx2 = ¤tElseActionIdx; break; - case MacroEdit::MacroSection::ACTIONS: + case MacroEdit::SegmentType::ACTION: setList = ui->actionsList; setIdx = ¤tActionIdx; segments = {macro->Actions().begin(), macro->Actions().end()}; @@ -329,7 +416,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) resetIdx1 = ¤tConditionIdx; resetIdx2 = ¤tElseActionIdx; break; - case MacroEdit::MacroSection::ELSE_ACTIONS: + case MacroEdit::SegmentType::ELSE_ACTION: setList = ui->elseActionsList; setIdx = ¤tElseActionIdx; segments = {macro->ElseActions().begin(), @@ -965,7 +1052,7 @@ void MacroEdit::UpMacroSegmentHotkey() int conditionSize = macro->Conditions().size(); if (currentActionIdx == -1 && currentConditionIdx == -1) { - if (lastInteracted == MacroSection::CONDITIONS) { + if (lastInteracted == SegmentType::CONDITION) { if (conditionSize == 0) { MacroActionSelectionChanged(0); } else { @@ -1021,7 +1108,7 @@ void MacroEdit::DownMacroSegmentHotkey() int conditionSize = macro->Conditions().size(); if (currentActionIdx == -1 && currentConditionIdx == -1) { - if (lastInteracted == MacroSection::CONDITIONS) { + if (lastInteracted == SegmentType::CONDITION) { if (conditionSize == 0) { MacroActionSelectionChanged(0); } else { @@ -1098,6 +1185,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id, idx, new MacroActionEdit(this, ¯o->Actions()[idx])); SetActionData(*macro); } + RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx); HighlightAction(idx); ui->actionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1163,6 +1251,19 @@ void MacroEdit::RemoveMacroAction(int idx) return; } + { + OBSDataAutoRelease segData = obs_data_create(); + std::string segId; + { + auto lock = LockContext(); + macro->Actions().at(idx)->Save(segData); + segId = macro->Actions().at(idx)->GetId(); + } + RegisterSegmentRemoveUndoRedo(macro.get(), SegmentType::ACTION, + idx, segId, segData, + (int)Logic::Type::ROOT_NONE); + } + { auto lock = LockContext(); ui->actionsList->Remove(idx); @@ -1173,7 +1274,7 @@ void MacroEdit::RemoveMacroAction(int idx) SetActionData(*macro); } MacroActionSelectionChanged(-1); - lastInteracted = MacroSection::ACTIONS; + lastInteracted = SegmentType::ACTION; emit(MacroSegmentOrderChanged()); } @@ -1353,7 +1454,7 @@ void MacroEdit::MoveMacroActionDown(int idx) void MacroEdit::MacroElseActionSelectionChanged(int idx) { - SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx); + SetupMacroSegmentSelection(SegmentType::ELSE_ACTION, idx); } void MacroEdit::MacroElseActionReorder(int to, int from) @@ -1406,6 +1507,7 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id, new MacroActionEdit(this, ¯o->ElseActions()[idx])); SetElseActionData(*macro); } + RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION, idx); HighlightElseAction(idx); ui->elseActionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1455,6 +1557,20 @@ void MacroEdit::RemoveMacroElseAction(int idx) return; } + { + OBSDataAutoRelease segData = obs_data_create(); + std::string segId; + { + auto lock = LockContext(); + macro->ElseActions().at(idx)->Save(segData); + segId = macro->ElseActions().at(idx)->GetId(); + } + RegisterSegmentRemoveUndoRedo(macro.get(), + SegmentType::ELSE_ACTION, idx, + segId, segData, + (int)Logic::Type::ROOT_NONE); + } + { auto lock = LockContext(); ui->elseActionsList->Remove(idx); @@ -1465,7 +1581,7 @@ void MacroEdit::RemoveMacroElseAction(int idx) SetElseActionData(*macro); } MacroElseActionSelectionChanged(-1); - lastInteracted = MacroSection::ELSE_ACTIONS; + lastInteracted = SegmentType::ELSE_ACTION; emit(MacroSegmentOrderChanged()); } @@ -1526,7 +1642,7 @@ void MacroEdit::MoveMacroElseActionDown(int idx) void MacroEdit::MacroActionSelectionChanged(int idx) { - SetupMacroSegmentSelection(MacroSection::ACTIONS, idx); + SetupMacroSegmentSelection(SegmentType::ACTION, idx); } void MacroEdit::MacroActionReorder(int to, int from) @@ -1617,6 +1733,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id, idx == 0)); SetConditionData(*macro); } + RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx); HighlightCondition(idx); ui->conditionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1652,6 +1769,21 @@ void MacroEdit::RemoveMacroCondition(int idx) return; } + { + OBSDataAutoRelease segData = obs_data_create(); + std::string segId; + int logic; + { + auto lock = LockContext(); + macro->Conditions().at(idx)->Save(segData); + segId = macro->Conditions().at(idx)->GetId(); + logic = (int)macro->Conditions().at(idx)->GetLogicType(); + } + RegisterSegmentRemoveUndoRedo(macro.get(), + SegmentType::CONDITION, idx, + segId, segData, logic); + } + { auto lock = LockContext(); ui->conditionsList->Remove(idx); @@ -1667,7 +1799,7 @@ void MacroEdit::RemoveMacroCondition(int idx) SetConditionData(*macro); } MacroConditionSelectionChanged(-1); - lastInteracted = MacroSection::CONDITIONS; + lastInteracted = SegmentType::CONDITION; emit(MacroSegmentOrderChanged()); } @@ -1792,7 +1924,7 @@ void MacroEdit::MoveMacroConditionDown(int idx) void MacroEdit::MacroConditionSelectionChanged(int idx) { - SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx); + SetupMacroSegmentSelection(SegmentType::CONDITION, idx); } void MacroEdit::MacroConditionReorder(int to, int from) diff --git a/lib/macro/macro-edit.hpp b/lib/macro/macro-edit.hpp index 8b2ff6b3..79db7393 100644 --- a/lib/macro/macro-edit.hpp +++ b/lib/macro/macro-edit.hpp @@ -12,11 +12,15 @@ class MacroEdit : public QWidget { Q_OBJECT public: + enum class SegmentType { CONDITION, ACTION, ELSE_ACTION }; + MacroEdit(QWidget *parent, QStringList helpMsg = {}); void SetMacro(const std::shared_ptr &); std::shared_ptr GetMacro() const; void ClearSegmentWidgetCacheFor(Macro *) const; void SetControlsDisabled(bool disable) const; + void InsertSegmentWidget(SegmentType type, int idx); + void RemoveSegmentWidget(SegmentType type, int idx); void HighlightAction(int idx, QColor color = QColor(Qt::green)) const; void HighlightElseAction(int idx, QColor color = QColor(Qt::green)) const; @@ -112,14 +116,12 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override; private: - enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS }; - void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0); void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx, int *currentIdx); - void SetupMacroSegmentSelection(MacroSection type, int idx); + void SetupMacroSegmentSelection(SegmentType type, int idx); void SetupContextMenu(const QPoint &pos, const std::function &remove, @@ -131,7 +133,7 @@ private: void RunSegmentHighlightChecks(); bool ElseSectionIsVisible() const; - MacroSection lastInteracted = MacroSection::CONDITIONS; + SegmentType lastInteracted = SegmentType::CONDITION; int currentConditionIdx = -1; int currentActionIdx = -1; int currentElseActionIdx = -1; diff --git a/lib/macro/macro-tab.cpp b/lib/macro/macro-tab.cpp index 98ed78c1..08dfdfc2 100644 --- a/lib/macro/macro-tab.cpp +++ b/lib/macro/macro-tab.cpp @@ -7,6 +7,7 @@ #include "macro-settings.hpp" #include "macro-signals.hpp" #include "macro-tree.hpp" +#include "macro-undo-redo.hpp" #include "macro.hpp" #include "math-helpers.hpp" #include "name-dialog.hpp" @@ -122,6 +123,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked() ui->macros->Add(newMacro); MacroSignalManager::Instance()->Add( QString::fromStdString(name)); + RegisterMacroAddUndoRedo(name); return; } @@ -131,6 +133,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked() ui->macros->AddToGroup(newMacro, selectedMacro); MacroSignalManager::Instance()->Add( QString::fromStdString(name)); + RegisterMacroAddUndoRedo(name); return; } @@ -139,12 +142,14 @@ void AdvSceneSwitcher::on_macroAdd_clicked() ui->macros->Add(newMacro, selectedMacro); MacroSignalManager::Instance()->Add( QString::fromStdString(name)); + RegisterMacroAddUndoRedo(name); return; } Macro::PrepareMoveToGroup(selectedMacroGroup, newMacro); ui->macros->Add(newMacro, selectedMacro); MacroSignalManager::Instance()->Add(QString::fromStdString(name)); + RegisterMacroAddUndoRedo(name); } static void addGroupSubitems(std::vector> ¯os, @@ -185,6 +190,12 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr ¯o) } } + if (macro->IsGroup()) { + RegisterGroupDeleteUndoRedo(macro.get()); + } else { + RegisterMacroRemoveUndoRedo(macro.get()); + } + if (macro->IsGroup()) { std::vector> macros = {macro}; addGroupSubitems(macros, macro); @@ -241,13 +252,16 @@ void AdvSceneSwitcher::RemoveSelectedMacros() void AdvSceneSwitcher::RenameMacro(std::shared_ptr ¯o, const QString &name) { - auto oldName = QString::fromStdString(macro->Name()); + const std::string oldName = macro->Name(); + const std::string newName = name.toStdString(); { auto lock = LockContext(); - macro->SetName(name.toStdString()); + macro->SetName(newName); } - MacroSignalManager::Instance()->Rename(oldName, name); + RegisterMacroRenameUndoRedo(oldName, newName); + MacroSignalManager::Instance()->Rename(QString::fromStdString(oldName), + name); } void AdvSceneSwitcher::on_macroRemove_clicked() diff --git a/lib/macro/macro-tree.cpp b/lib/macro/macro-tree.cpp index 0b5c2e44..25203448 100644 --- a/lib/macro/macro-tree.cpp +++ b/lib/macro/macro-tree.cpp @@ -1,4 +1,5 @@ #include "macro-tree.hpp" +#include "macro-undo-redo.hpp" #include "macro-helpers.hpp" #include "macro-search.hpp" @@ -824,6 +825,8 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices) Reset(_macros); assert(IsInValidState()); + + RegisterGroupCreateUndoRedo(name.toStdString()); } void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices) @@ -837,6 +840,7 @@ void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices) std::shared_ptr item = _macros[ModelIndexToMacroIndex( indices[i].row(), _macros)]; if (item->IsGroup()) { + RegisterGroupRemoveUndoRedo(item->Name()); Macro::RemoveGroup(item); } } diff --git a/lib/macro/macro-undo-redo.cpp b/lib/macro/macro-undo-redo.cpp new file mode 100644 index 00000000..763b605f --- /dev/null +++ b/lib/macro/macro-undo-redo.cpp @@ -0,0 +1,931 @@ +#include "macro-undo-redo.hpp" + +#include "advanced-scene-switcher.hpp" +#include "condition-logic.hpp" +#include "macro-action-factory.hpp" +#include "macro-condition-factory.hpp" +#include "macro-edit.hpp" +#include "macro-helpers.hpp" +#include "macro-segment-list.hpp" +#include "macro-settings.hpp" +#include "macro-signals.hpp" +#include "macro.hpp" +#include "obs-module-helper.hpp" +#include "plugin-state-helpers.hpp" +#include "sync-helpers.hpp" + +#include +#include + +#include + +namespace advss { + +using SegmentType = MacroEdit::SegmentType; + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +enum class SegmentOp { ADD, REMOVE }; + +static const char *segmentTypeName(SegmentType type) +{ + switch (type) { + case SegmentType::ACTION: + return obs_module_text("AdvSceneSwitcher.undo.segment.action"); + case SegmentType::ELSE_ACTION: + return obs_module_text( + "AdvSceneSwitcher.undo.segment.elseAction"); + case SegmentType::CONDITION: + return obs_module_text( + "AdvSceneSwitcher.undo.segment.condition"); + } + return ""; +} + +static std::string fmtUndoName(const char *key, const std::string &arg1) +{ + return QString(obs_module_text(key)) + .arg(QString::fromStdString(arg1)) + .toStdString(); +} + +static std::string fmtUndoName(const char *key, const char *arg1, + const std::string &arg2) +{ + return QString(obs_module_text(key)) + .arg(arg1) + .arg(QString::fromStdString(arg2)) + .toStdString(); +} + +static void updateMacroEditSegment(AdvSceneSwitcher *window, + const std::string ¯oName, SegmentOp op, + SegmentType type, int idx) +{ + if (!SettingsWindowIsOpened()) { + return; + } + auto *const macroEdit = window->ui->macroEdit; + const auto current = macroEdit->GetMacro(); + if (!current || current->Name() != macroName) { + return; + } + + // std::deque invalidates all element addresses on any insert or erase. + // Re-point every existing widget before inserting/removing one, so none + // hold stale pointers. + macroEdit->SetActionData(*current); + macroEdit->SetElseActionData(*current); + macroEdit->SetConditionData(*current); + + if (op == SegmentOp::ADD) { + macroEdit->InsertSegmentWidget(type, idx); + } else { + macroEdit->RemoveSegmentWidget(type, idx); + } +} + +// --------------------------------------------------------------------------- +// Macro-level undo/redo callbacks +// --------------------------------------------------------------------------- + +// Callback data format for deletion: {"name": "MacroName"} +static void deleteMacroByName(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string name = obs_data_get_string(data, "name"); + const auto macro = GetWeakMacroByName(name.c_str()).lock(); + if (!macro) { + return; + } + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macroEdit->ClearSegmentWidgetCacheFor(macro.get()); + MacroSegmentList::SetCachingEnabled(false); + window->ui->macros->Remove(macro); + MacroSegmentList::SetCachingEnabled(true); + } else { + auto lock = LockContext(); + auto ¯os = GetTopLevelMacros(); + const auto it = std::find(macros.begin(), macros.end(), macro); + if (it != macros.end()) { + macros.erase(it); + } + } + + MacroSignalManager::Instance()->Remove(QString::fromStdString(name)); +} + +// Callback data format for restoration: +// {"index": N, "parent_group": "GroupName", "macro": {...}} +static void restoreMacroFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const int index = (int)obs_data_get_int(data, "index"); + const std::string parentGroup = + obs_data_get_string(data, "parent_group"); + OBSDataAutoRelease macroData = obs_data_get_obj(data, "macro"); + if (!macroData) { + return; + } + + auto macro = std::make_shared(); + std::string macroName; + { + auto lock = LockContext(); + macro->Load(macroData); + macro->PostLoad(); + RunAndClearPostLoadSteps(); + + if (!parentGroup.empty()) { + const auto parent = + GetWeakMacroByName(parentGroup.c_str()).lock(); + if (parent) { + Macro::PrepareMoveToGroup(parent, macro); + } + } + + auto ¯os = GetTopLevelMacros(); + const int insertIdx = std::min(index, (int)macros.size()); + macros.insert(macros.begin() + insertIdx, macro); + macroName = macro->Name(); + } + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macros->Reset( + GetTopLevelMacros(), + GetGlobalMacroSettings()._highlightExecuted); + } + + MacroSignalManager::Instance()->Add(QString::fromStdString(macroName)); +} + +// --------------------------------------------------------------------------- +// Group-delete undo/redo callbacks +// --------------------------------------------------------------------------- + +// Callback data format: +// {"group_name": "...", "group_index": N, "group_data": {...}, +// "children": [{"data": {...}}, ...]} +static void deleteGroupWithChildrenFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string groupName = obs_data_get_string(data, "group_name"); + const auto group = GetWeakMacroByName(groupName.c_str()).lock(); + if (!group || !group->IsGroup()) { + return; + } + + const auto children = GetGroupMacroEntries(group.get()); + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macroEdit->ClearSegmentWidgetCacheFor(group.get()); + for (const auto &child : children) { + window->ui->macroEdit->ClearSegmentWidgetCacheFor( + child.get()); + } + MacroSegmentList::SetCachingEnabled(false); + window->ui->macros->Remove(group); + MacroSegmentList::SetCachingEnabled(true); + } else { + auto lock = LockContext(); + auto ¯os = GetTopLevelMacros(); + const auto it = std::find(macros.begin(), macros.end(), group); + if (it != macros.end()) { + macros.erase(it, std::next(it, 1 + group->GroupSize())); + } + } + + for (const auto &child : children) { + MacroSignalManager::Instance()->Remove( + QString::fromStdString(child->Name())); + } + MacroSignalManager::Instance()->Remove( + QString::fromStdString(groupName)); +} + +static void restoreGroupWithChildrenFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const int groupIndex = (int)obs_data_get_int(data, "group_index"); + OBSDataAutoRelease groupData = obs_data_get_obj(data, "group_data"); + OBSDataArrayAutoRelease childArray = + obs_data_get_array(data, "children"); + + std::string groupName; + bool collapsed = false; + if (groupData) { + groupName = obs_data_get_string(groupData, "name"); + OBSDataAutoRelease gd = + obs_data_get_obj(groupData, "groupData"); + if (gd) { + collapsed = obs_data_get_bool(gd, "collapsed"); + } + } + + std::vector> children; + const size_t count = obs_data_array_count(childArray); + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease entry = obs_data_array_item(childArray, i); + OBSDataAutoRelease childData = obs_data_get_obj(entry, "data"); + if (!childData) { + continue; + } + auto child = std::make_shared(); + { + auto lock = LockContext(); + child->Load(childData); + child->PostLoad(); + RunAndClearPostLoadSteps(); + } + children.push_back(child); + } + + std::string restoredGroupName; + { + auto lock = LockContext(); + auto group = Macro::CreateGroup(groupName, children); + group->SetCollapsed(collapsed); + restoredGroupName = group->Name(); + + auto ¯os = GetTopLevelMacros(); + const int insertIdx = std::min(groupIndex, (int)macros.size()); + macros.insert(macros.begin() + insertIdx, group); + int offset = 1; + for (const auto &child : children) { + macros.insert(macros.begin() + insertIdx + offset, + child); + offset++; + } + } + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macros->Reset( + GetTopLevelMacros(), + GetGlobalMacroSettings()._highlightExecuted); + } + + MacroSignalManager::Instance()->Add( + QString::fromStdString(restoredGroupName)); + for (const auto &child : children) { + MacroSignalManager::Instance()->Add( + QString::fromStdString(child->Name())); + } +} + +// --------------------------------------------------------------------------- +// Group-level undo/redo callbacks +// --------------------------------------------------------------------------- + +// Callback data format for both group and ungroup: +// {"group_name": "...", "group_index": N, "children": [{"name": "..."}, ...]} +static void removeGroupFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string groupName = obs_data_get_string(data, "group_name"); + const auto group = GetWeakMacroByName(groupName.c_str()).lock(); + if (!group || !group->IsGroup()) { + return; + } + + { + auto lock = LockContext(); + Macro::RemoveGroup(group); + } + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macros->Reset( + GetTopLevelMacros(), + GetGlobalMacroSettings()._highlightExecuted); + } + + MacroSignalManager::Instance()->Remove( + QString::fromStdString(groupName)); +} + +static void restoreGroupFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string groupName = obs_data_get_string(data, "group_name"); + const int groupIndex = (int)obs_data_get_int(data, "group_index"); + OBSDataArrayAutoRelease childArray = + obs_data_get_array(data, "children"); + + std::vector> children; + const size_t count = obs_data_array_count(childArray); + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease item = obs_data_array_item(childArray, i); + const std::string name = obs_data_get_string(item, "name"); + const auto macro = GetWeakMacroByName(name.c_str()).lock(); + if (macro) { + children.push_back(macro); + } + } + + { + auto lock = LockContext(); + auto group = Macro::CreateGroup(groupName, children); + + auto ¯os = GetTopLevelMacros(); + const int insertIdx = std::min(groupIndex, (int)macros.size()); + macros.insert(macros.begin() + insertIdx, group); + + int offset = 1; + for (const auto &child : children) { + const auto it = + std::find(macros.begin(), macros.end(), child); + if (it != macros.end()) { + macros.erase(it); + } + macros.insert(macros.begin() + insertIdx + offset, + child); + offset++; + } + } + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macros->Reset( + GetTopLevelMacros(), + GetGlobalMacroSettings()._highlightExecuted); + } + + MacroSignalManager::Instance()->Add(QString::fromStdString(groupName)); +} + +// --------------------------------------------------------------------------- +// Segment-level undo/redo callbacks +// --------------------------------------------------------------------------- + +// Callback data format for segment removal: +// {"macro": "Name", "type": N, "index": N} +static void removeSegmentFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string macroName = obs_data_get_string(data, "macro"); + const int type = (int)obs_data_get_int(data, "type"); + const int index = (int)obs_data_get_int(data, "index"); + + const auto macro = GetWeakMacroByName(macroName.c_str()).lock(); + if (!macro) { + return; + } + + { + auto lock = LockContext(); + switch ((SegmentType)type) { + case SegmentType::ACTION: + if (index >= 0 && + index < (int)macro->Actions().size()) { + macro->Actions().erase( + macro->Actions().begin() + index); + SetMacroAbortWait(true); + GetMacroWaitCV().notify_all(); + macro->UpdateActionIndices(); + } + break; + case SegmentType::ELSE_ACTION: + if (index >= 0 && + index < (int)macro->ElseActions().size()) { + macro->ElseActions().erase( + macro->ElseActions().begin() + index); + SetMacroAbortWait(true); + GetMacroWaitCV().notify_all(); + macro->UpdateElseActionIndices(); + } + break; + case SegmentType::CONDITION: + if (index >= 0 && + index < (int)macro->Conditions().size()) { + macro->Conditions().erase( + macro->Conditions().begin() + index); + macro->UpdateConditionIndices(); + if (index == 0 && + !macro->Conditions().empty()) { + macro->Conditions().at(0)->SetLogicType( + Logic::Type::ROOT_NONE); + } + } + break; + } + } + + if (auto *window = AdvSceneSwitcher::window) { + updateMacroEditSegment(window, macroName, SegmentOp::REMOVE, + (SegmentType)type, index); + } +} + +// Callback data format for segment addition: +// {"macro": "Name", "type": N, "index": N, "id": "...", "logic": N, +// "segment": {...}} +static void addSegmentFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string macroName = obs_data_get_string(data, "macro"); + const int type = (int)obs_data_get_int(data, "type"); + const int index = (int)obs_data_get_int(data, "index"); + const std::string id = obs_data_get_string(data, "id"); + const int logic = (int)obs_data_get_int(data, "logic"); + OBSDataAutoRelease segData = obs_data_get_obj(data, "segment"); + + const auto macro = GetWeakMacroByName(macroName.c_str()).lock(); + if (!macro) { + return; + } + + { + auto lock = LockContext(); + switch ((SegmentType)type) { + case SegmentType::ACTION: { + if (index < 0 || index > (int)macro->Actions().size()) { + break; + } + macro->Actions().emplace( + macro->Actions().begin() + index, + MacroActionFactory::Create(id, macro.get())); + if (segData) { + macro->Actions().at(index)->Load(segData); + } + macro->Actions().at(index)->PostLoad(); + RunAndClearPostLoadSteps(); + macro->UpdateActionIndices(); + break; + } + case SegmentType::ELSE_ACTION: { + if (index < 0 || + index > (int)macro->ElseActions().size()) { + break; + } + macro->ElseActions().emplace( + macro->ElseActions().begin() + index, + MacroActionFactory::Create(id, macro.get())); + if (segData) { + macro->ElseActions().at(index)->Load(segData); + } + macro->ElseActions().at(index)->PostLoad(); + RunAndClearPostLoadSteps(); + macro->UpdateElseActionIndices(); + break; + } + case SegmentType::CONDITION: { + if (index < 0 || + index > (int)macro->Conditions().size()) { + break; + } + macro->Conditions().emplace( + macro->Conditions().begin() + index, + MacroConditionFactory::Create(id, macro.get())); + if (segData) { + macro->Conditions().at(index)->Load(segData); + } + macro->Conditions().at(index)->PostLoad(); + RunAndClearPostLoadSteps(); + macro->Conditions().at(index)->SetLogicType( + (Logic::Type)logic); + macro->UpdateConditionIndices(); + break; + } + } + } + + if (auto *window = AdvSceneSwitcher::window) { + updateMacroEditSegment(window, macroName, SegmentOp::ADD, + (SegmentType)type, index); + } +} + +// --------------------------------------------------------------------------- +// Rename undo/redo callback +// --------------------------------------------------------------------------- + +// Callback data format: {"old_name": "...", "new_name": "..."} +// Used for both undo (new->old) and redo (old->new) by swapping order in +// obs_frontend_add_undo_redo_action. +static void renameMacroFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string oldName = obs_data_get_string(data, "old_name"); + const std::string newName = obs_data_get_string(data, "new_name"); + + const auto macro = GetWeakMacroByName(oldName.c_str()).lock(); + if (!macro) { + return; + } + + { + auto lock = LockContext(); + macro->SetName(newName); + } + + MacroSignalManager::Instance()->Rename(QString::fromStdString(oldName), + QString::fromStdString(newName)); + + auto *const window = AdvSceneSwitcher::window; + if (SettingsWindowIsOpened() && window) { + window->ui->macros->Reset( + GetTopLevelMacros(), + GetGlobalMacroSettings()._highlightExecuted); + } +} + +// --------------------------------------------------------------------------- +// Public registration functions +// --------------------------------------------------------------------------- + +void RegisterMacroAddUndoRedo(const std::string ¯oName) +{ + const auto macro = GetWeakMacroByName(macroName.c_str()).lock(); + if (!macro || macro->IsGroup()) { + return; + } + + const auto ¯os = GetTopLevelMacros(); + int index = -1; + for (int i = 0; i < (int)macros.size(); i++) { + if (macros[i]->Name() == macroName) { + index = i; + break; + } + } + if (index < 0) { + return; + } + + std::string parentGroup; + if (macro->Parent()) { + parentGroup = macro->Parent()->Name(); + } + + OBSDataAutoRelease macroData = obs_data_create(); + macro->Save(macroData); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "name", macroName.c_str()); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_int(redoData, "index", index); + obs_data_set_string(redoData, "parent_group", parentGroup.c_str()); + obs_data_set_obj(redoData, "macro", macroData); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.addMacro", macroName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &deleteMacroByName, + &restoreMacroFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +bool RegisterMacroRemoveUndoRedo(Macro *macro) +{ + if (!macro || macro->IsGroup()) { + return false; + } + + const std::string macroName = macro->Name(); + + const auto ¯os = GetTopLevelMacros(); + int index = -1; + for (int i = 0; i < (int)macros.size(); i++) { + if (macros[i].get() == macro) { + index = i; + break; + } + } + if (index < 0) { + return false; + } + + std::string parentGroup; + if (macro->Parent()) { + parentGroup = macro->Parent()->Name(); + } + + OBSDataAutoRelease macroData = obs_data_create(); + macro->Save(macroData); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_int(undoData, "index", index); + obs_data_set_string(undoData, "parent_group", parentGroup.c_str()); + obs_data_set_obj(undoData, "macro", macroData); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "name", macroName.c_str()); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.removeMacro", macroName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &restoreMacroFromData, + &deleteMacroByName, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); + return true; +} + +void RegisterSegmentAddUndoRedo(Macro *macro, SegmentType type, int index) +{ + if (!macro) { + return; + } + + const std::string macroName = macro->Name(); + std::string id; + int logic = (int)Logic::Type::ROOT_NONE; + OBSDataAutoRelease segData = obs_data_create(); + + { + auto lock = LockContext(); + switch (type) { + case SegmentType::ACTION: + if (index < (int)macro->Actions().size()) { + macro->Actions().at(index)->Save(segData); + id = macro->Actions().at(index)->GetId(); + } + break; + case SegmentType::ELSE_ACTION: + if (index < (int)macro->ElseActions().size()) { + macro->ElseActions().at(index)->Save(segData); + id = macro->ElseActions().at(index)->GetId(); + } + break; + case SegmentType::CONDITION: + if (index < (int)macro->Conditions().size()) { + macro->Conditions().at(index)->Save(segData); + id = macro->Conditions().at(index)->GetId(); + logic = (int)macro->Conditions() + .at(index) + ->GetLogicType(); + } + break; + } + } + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "macro", macroName.c_str()); + obs_data_set_int(undoData, "type", (int)type); + obs_data_set_int(undoData, "index", index); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "macro", macroName.c_str()); + obs_data_set_int(redoData, "type", (int)type); + obs_data_set_int(redoData, "index", index); + obs_data_set_string(redoData, "id", id.c_str()); + obs_data_set_int(redoData, "logic", logic); + obs_data_set_obj(redoData, "segment", segData); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.addSegment", + segmentTypeName(type), macroName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &removeSegmentFromData, + &addSegmentFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +void RegisterSegmentRemoveUndoRedo(Macro *macro, SegmentType type, int index, + const std::string &segmentId, + obs_data_t *segmentData, int logic) +{ + if (!macro) { + return; + } + + const std::string macroName = macro->Name(); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "macro", macroName.c_str()); + obs_data_set_int(undoData, "type", (int)type); + obs_data_set_int(undoData, "index", index); + obs_data_set_string(undoData, "id", segmentId.c_str()); + obs_data_set_int(undoData, "logic", logic); + obs_data_set_obj(undoData, "segment", segmentData); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "macro", macroName.c_str()); + obs_data_set_int(redoData, "type", (int)type); + obs_data_set_int(redoData, "index", index); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.removeSegment", + segmentTypeName(type), macroName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &addSegmentFromData, + &removeSegmentFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +static OBSDataArrayAutoRelease +buildChildArray(const std::vector> &children) +{ + OBSDataArrayAutoRelease arr = obs_data_array_create(); + for (const auto &child : children) { + OBSDataAutoRelease item = obs_data_create(); + obs_data_set_string(item, "name", child->Name().c_str()); + obs_data_array_push_back(arr, item); + } + return arr; +} + +void RegisterGroupCreateUndoRedo(const std::string &groupName) +{ + const auto group = GetWeakMacroByName(groupName.c_str()).lock(); + if (!group || !group->IsGroup()) { + return; + } + + const auto ¯os = GetTopLevelMacros(); + int groupIndex = -1; + for (int i = 0; i < (int)macros.size(); i++) { + if (macros[i].get() == group.get()) { + groupIndex = i; + break; + } + } + if (groupIndex < 0) { + return; + } + + const auto children = GetGroupMacroEntries(group.get()); + OBSDataArrayAutoRelease childArray = buildChildArray(children); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "group_name", groupName.c_str()); + obs_data_set_int(undoData, "group_index", groupIndex); + obs_data_set_array(undoData, "children", childArray); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "group_name", groupName.c_str()); + obs_data_set_int(redoData, "group_index", groupIndex); + obs_data_set_array(redoData, "children", childArray); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.groupMacros", groupName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &removeGroupFromData, + &restoreGroupFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +void RegisterGroupRemoveUndoRedo(const std::string &groupName) +{ + const auto group = GetWeakMacroByName(groupName.c_str()).lock(); + if (!group || !group->IsGroup()) { + return; + } + + const auto ¯os = GetTopLevelMacros(); + int groupIndex = -1; + for (int i = 0; i < (int)macros.size(); i++) { + if (macros[i].get() == group.get()) { + groupIndex = i; + break; + } + } + if (groupIndex < 0) { + return; + } + + const auto children = GetGroupMacroEntries(group.get()); + OBSDataArrayAutoRelease childArray = buildChildArray(children); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "group_name", groupName.c_str()); + obs_data_set_int(undoData, "group_index", groupIndex); + obs_data_set_array(undoData, "children", childArray); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "group_name", groupName.c_str()); + obs_data_set_int(redoData, "group_index", groupIndex); + obs_data_set_array(redoData, "children", childArray); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.ungroupMacros", groupName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &restoreGroupFromData, + &removeGroupFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +void RegisterGroupDeleteUndoRedo(Macro *macro) +{ + if (!macro || !macro->IsGroup()) { + return; + } + + const std::string groupName = macro->Name(); + + const auto ¯os = GetTopLevelMacros(); + int groupIndex = -1; + for (int i = 0; i < (int)macros.size(); i++) { + if (macros[i].get() == macro) { + groupIndex = i; + break; + } + } + if (groupIndex < 0) { + return; + } + + OBSDataAutoRelease groupData = obs_data_create(); + macro->Save(groupData); + + const auto children = GetGroupMacroEntries(macro); + OBSDataArrayAutoRelease childArray = obs_data_array_create(); + for (const auto &child : children) { + OBSDataAutoRelease childData = obs_data_create(); + child->Save(childData); + OBSDataAutoRelease entry = obs_data_create(); + obs_data_set_obj(entry, "data", childData); + obs_data_array_push_back(childArray, entry); + } + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "group_name", groupName.c_str()); + obs_data_set_int(undoData, "group_index", groupIndex); + obs_data_set_obj(undoData, "group_data", groupData); + obs_data_set_array(undoData, "children", childArray); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "group_name", groupName.c_str()); + obs_data_set_int(redoData, "group_index", groupIndex); + obs_data_set_obj(redoData, "group_data", groupData); + obs_data_set_array(redoData, "children", childArray); + + const std::string actionName = fmtUndoName( + "AdvSceneSwitcher.undo.removeMacroGroup", groupName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &restoreGroupWithChildrenFromData, + &deleteGroupWithChildrenFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +void RegisterMacroRenameUndoRedo(const std::string &oldName, + const std::string &newName) +{ + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "old_name", newName.c_str()); + obs_data_set_string(undoData, "new_name", oldName.c_str()); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "old_name", oldName.c_str()); + obs_data_set_string(redoData, "new_name", newName.c_str()); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.renameMacro", oldName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &renameMacroFromData, + &renameMacroFromData, + obs_data_get_json(undoData), + obs_data_get_json(redoData), false); +} + +} // namespace advss diff --git a/lib/macro/macro-undo-redo.hpp b/lib/macro/macro-undo-redo.hpp new file mode 100644 index 00000000..45144fa1 --- /dev/null +++ b/lib/macro/macro-undo-redo.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "macro-edit.hpp" + +#include +#include + +namespace advss { + +class Macro; + +void RegisterMacroAddUndoRedo(const std::string ¯oName); +bool RegisterMacroRemoveUndoRedo(Macro *macro); +void RegisterSegmentAddUndoRedo(Macro *macro, MacroEdit::SegmentType type, + int index); +void RegisterSegmentRemoveUndoRedo(Macro *macro, MacroEdit::SegmentType type, + int index, const std::string &segmentId, + obs_data_t *segmentData, int logic); +void RegisterMacroRenameUndoRedo(const std::string &oldName, + const std::string &newName); +void RegisterGroupCreateUndoRedo(const std::string &groupName); +void RegisterGroupRemoveUndoRedo(const std::string &groupName); +void RegisterGroupDeleteUndoRedo(Macro *macro); + +} // namespace advss diff --git a/tests/stubs/macro-edit.cpp b/tests/stubs/macro-edit.cpp index 629040b3..7ea6e98c 100644 --- a/tests/stubs/macro-edit.cpp +++ b/tests/stubs/macro-edit.cpp @@ -110,7 +110,9 @@ void MacroEdit::HighlightControls() const {} void MacroEdit::PopulateMacroActions(Macro &, uint32_t) {} void MacroEdit::PopulateMacroElseActions(Macro &, uint32_t) {} void MacroEdit::PopulateMacroConditions(Macro &, uint32_t) {} -void MacroEdit::SetupMacroSegmentSelection(MacroSection, int) {} +void MacroEdit::InsertSegmentWidget(SegmentType, int) {} +void MacroEdit::RemoveSegmentWidget(SegmentType, int) {} +void MacroEdit::SetupMacroSegmentSelection(SegmentType, int) {} void MacroEdit::SetupContextMenu(const QPoint &, const std::function &, const std::function &,