diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 88091ff2..598c316c 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -199,6 +199,7 @@ AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Are you sure you want to 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.modifyMacro="Modify Macro '%1'" AdvSceneSwitcher.undo.addMacro="Add Macro '%1'" AdvSceneSwitcher.undo.removeMacro="Remove Macro '%1'" AdvSceneSwitcher.undo.addSegment="Add %1 in '%2'" diff --git a/lib/macro/macro-action-edit.cpp b/lib/macro/macro-action-edit.cpp index 92f78453..70c7f72a 100644 --- a/lib/macro/macro-action-edit.cpp +++ b/lib/macro/macro-action-edit.cpp @@ -112,6 +112,14 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text) return; } + Macro *const parentMacro = + (*_entryData)->GetMacro()->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } + const std::string oldId = (*_entryData)->GetId(); OBSDataAutoRelease oldData = obs_data_create(); (*_entryData)->Save(oldData); @@ -141,8 +149,13 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text) OBSDataAutoRelease newData = obs_data_create(); (*_entryData)->Save(newData); - RegisterSegmentTypeChangeUndoRedo(macro, segmentType, idx, oldId, - oldData, 0, id, newData, 0); + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } else { + RegisterSegmentTypeChangeUndoRedo(macro, segmentType, idx, + oldId, oldData, 0, id, + newData, 0); + } auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData); QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), this, SLOT(HeaderInfoChanged(const QString &))); diff --git a/lib/macro/macro-action-loop.cpp b/lib/macro/macro-action-loop.cpp index e69f7310..4274e81a 100644 --- a/lib/macro/macro-action-loop.cpp +++ b/lib/macro/macro-action-loop.cpp @@ -83,6 +83,7 @@ bool MacroActionLoop::PostLoad() MacroAction::PostLoad(); _loopMacro->PostLoad(); _loopMacro->SetActionTriggerMode(Macro::ActionTriggerMode::ALWAYS); + _loopMacro->SetNestedParentMacro(GetMacro()); return true; } diff --git a/lib/macro/macro-action-macro.cpp b/lib/macro/macro-action-macro.cpp index cf4b8112..0ee65fa7 100644 --- a/lib/macro/macro-action-macro.cpp +++ b/lib/macro/macro-action-macro.cpp @@ -281,6 +281,7 @@ bool MacroActionMacro::PostLoad() MacroAction::PostLoad(); _runOptions.macro.PostLoad(); _nestedMacro->PostLoad(); + _nestedMacro->SetNestedParentMacro(GetMacro()); return true; } diff --git a/lib/macro/macro-condition-edit.cpp b/lib/macro/macro-condition-edit.cpp index 22cdd56f..61222df7 100644 --- a/lib/macro/macro-condition-edit.cpp +++ b/lib/macro/macro-condition-edit.cpp @@ -251,6 +251,14 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text) return; } + Macro *const parentMacro = + (*_entryData)->GetMacro()->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } + const std::string oldId = (*_entryData)->GetId(); const int oldLogic = (int)(*_entryData)->GetLogicType(); OBSDataAutoRelease oldData = obs_data_create(); @@ -276,9 +284,14 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text) OBSDataAutoRelease newData = obs_data_create(); (*_entryData)->Save(newData); - RegisterSegmentTypeChangeUndoRedo( - macro, MacroEdit::SegmentType::CONDITION, idx, oldId, oldData, - oldLogic, id, newData, (int)(*_entryData)->GetLogicType()); + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } else { + RegisterSegmentTypeChangeUndoRedo( + macro, MacroEdit::SegmentType::CONDITION, idx, oldId, + oldData, oldLogic, id, newData, + (int)(*_entryData)->GetLogicType()); + } auto widget = MacroConditionFactory::CreateWidget(id, this, *_entryData); QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), diff --git a/lib/macro/macro-edit.cpp b/lib/macro/macro-edit.cpp index a3aa8d2d..f20ecb64 100644 --- a/lib/macro/macro-edit.cpp +++ b/lib/macro/macro-edit.cpp @@ -1171,6 +1171,13 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id, return; } + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } + { auto lock = LockContext(); macro->Actions().emplace(macro->Actions().begin() + idx, @@ -1185,7 +1192,11 @@ 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); + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } else { + RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx); + } HighlightAction(idx); ui->actionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1251,7 +1262,12 @@ void MacroEdit::RemoveMacroAction(int idx) return; } - { + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } else { OBSDataAutoRelease segData = obs_data_create(); std::string segId; { @@ -1273,6 +1289,9 @@ void MacroEdit::RemoveMacroAction(int idx) macro->UpdateActionIndices(); SetActionData(*macro); } + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } MacroActionSelectionChanged(-1); lastInteracted = SegmentType::ACTION; emit(MacroSegmentOrderChanged()); @@ -1491,6 +1510,13 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id, return; } + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } + { auto lock = LockContext(); macro->ElseActions().emplace(macro->ElseActions().begin() + idx, @@ -1507,7 +1533,12 @@ 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); + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } else { + RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION, + idx); + } HighlightElseAction(idx); ui->elseActionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1557,7 +1588,12 @@ void MacroEdit::RemoveMacroElseAction(int idx) return; } - { + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } else { OBSDataAutoRelease segData = obs_data_create(); std::string segId; { @@ -1580,6 +1616,9 @@ void MacroEdit::RemoveMacroElseAction(int idx) macro->UpdateElseActionIndices(); SetElseActionData(*macro); } + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } MacroElseActionSelectionChanged(-1); lastInteracted = SegmentType::ELSE_ACTION; emit(MacroSegmentOrderChanged()); @@ -1715,6 +1754,13 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id, return; } + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } + { auto lock = LockContext(); auto cond = macro->Conditions().emplace( @@ -1733,7 +1779,11 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id, idx == 0)); SetConditionData(*macro); } - RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx); + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } else { + RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx); + } HighlightCondition(idx); ui->conditionsList->SetHelpMsgVisible(false); emit(MacroSegmentOrderChanged()); @@ -1769,7 +1819,12 @@ void MacroEdit::RemoveMacroCondition(int idx) return; } - { + Macro *const parentMacro = macro->GetNestedParentMacro(); + OBSDataAutoRelease parentBeforeData; + if (parentMacro) { + parentBeforeData = obs_data_create(); + parentMacro->Save(parentBeforeData); + } else { OBSDataAutoRelease segData = obs_data_create(); std::string segId; int logic; @@ -1798,6 +1853,9 @@ void MacroEdit::RemoveMacroCondition(int idx) } SetConditionData(*macro); } + if (parentMacro) { + RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData); + } MacroConditionSelectionChanged(-1); lastInteracted = SegmentType::CONDITION; emit(MacroSegmentOrderChanged()); diff --git a/lib/macro/macro-undo-redo.cpp b/lib/macro/macro-undo-redo.cpp index 29f7bffc..7e0cd938 100644 --- a/lib/macro/macro-undo-redo.cpp +++ b/lib/macro/macro-undo-redo.cpp @@ -17,6 +17,7 @@ #include #include +#include #include namespace advss { @@ -630,6 +631,78 @@ static void replaceSegmentFromData(const char *jsonData) } } +// --------------------------------------------------------------------------- +// Macro state restore callback (for nested macro edits) +// --------------------------------------------------------------------------- + +// Callback data format: {"name": "MacroName", "macro": {...}} +static void restoreMacroStateFromData(const char *jsonData) +{ + OBSDataAutoRelease data = obs_data_create_from_json(jsonData); + if (!data) { + return; + } + + const std::string macroName = obs_data_get_string(data, "name"); + OBSDataAutoRelease macroData = obs_data_get_obj(data, "macro"); + if (!macroData) { + return; + } + + const auto macro = GetWeakMacroByName(macroName.c_str()).lock(); + if (!macro) { + return; + } + + auto *const window = AdvSceneSwitcher::window; + const bool uiVisible = SettingsWindowIsOpened() && window; + auto *const macroEdit = uiVisible ? window->ui->macroEdit : nullptr; + const bool isCurrent = macroEdit && macroEdit->GetMacro() && + macroEdit->GetMacro()->Name() == macroName; + + // Clear widgets while data is still valid; also saves splitter positions. + if (isCurrent) { + MacroSegmentList::SetCachingEnabled(false); + macroEdit->SetMacro({}); + } + + // Preserve splitter positions: Load() overwrites them with snapshot values. + const QList savedCondSplitter = + macro->GetActionConditionSplitterPosition(); + const QList savedElseSplitter = + macro->GetElseActionSplitterPosition(); + + // Destroy deleteLater()-queued widgets now so their timers cannot fire + // with a dangling _entryData after the deques are cleared below. + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + + if (isCurrent) { + MacroSegmentList::SetCachingEnabled(true); + } + + { + auto lock = LockContext(); + // Load() appends; clear first to avoid duplicate segments. + macro->Conditions().clear(); + macro->Actions().clear(); + macro->ElseActions().clear(); + macro->Load(macroData); + macro->PostLoad(); + RunAndClearPostLoadSteps(); + } + + if (isCurrent) { + if (!savedCondSplitter.isEmpty()) { + macro->SetActionConditionSplitterPosition( + savedCondSplitter); + } + if (!savedElseSplitter.isEmpty()) { + macro->SetElseActionSplitterPosition(savedElseSplitter); + } + macroEdit->SetMacro(macro); + } +} + // --------------------------------------------------------------------------- // Rename undo/redo callback // --------------------------------------------------------------------------- @@ -1062,4 +1135,32 @@ void RegisterSegmentTypeChangeUndoRedo(Macro *macro, SegmentType type, obs_data_get_json(redoData), false); } +void RegisterMacroModifyUndoRedo(Macro *parentMacro, obs_data_t *beforeData) +{ + if (!parentMacro || !beforeData) { + return; + } + + const std::string macroName = parentMacro->Name(); + + OBSDataAutoRelease afterMacroData = obs_data_create(); + parentMacro->Save(afterMacroData); + + OBSDataAutoRelease undoData = obs_data_create(); + obs_data_set_string(undoData, "name", macroName.c_str()); + obs_data_set_obj(undoData, "macro", beforeData); + + OBSDataAutoRelease redoData = obs_data_create(); + obs_data_set_string(redoData, "name", macroName.c_str()); + obs_data_set_obj(redoData, "macro", afterMacroData); + + const std::string actionName = + fmtUndoName("AdvSceneSwitcher.undo.modifyMacro", macroName); + obs_frontend_add_undo_redo_action(actionName.c_str(), + &restoreMacroStateFromData, + &restoreMacroStateFromData, + 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 index 0001ca17..85ae7830 100644 --- a/lib/macro/macro-undo-redo.hpp +++ b/lib/macro/macro-undo-redo.hpp @@ -26,5 +26,6 @@ void RegisterMacroRenameUndoRedo(const std::string &oldName, void RegisterGroupCreateUndoRedo(const std::string &groupName); void RegisterGroupRemoveUndoRedo(const std::string &groupName); void RegisterGroupDeleteUndoRedo(Macro *macro); +void RegisterMacroModifyUndoRedo(Macro *parentMacro, obs_data_t *beforeData); } // namespace advss diff --git a/lib/macro/macro.hpp b/lib/macro/macro.hpp index 30601a88..7d8d62ca 100644 --- a/lib/macro/macro.hpp +++ b/lib/macro/macro.hpp @@ -130,6 +130,13 @@ public: void SetParent(std::shared_ptr m) { _parent = m; } std::shared_ptr Parent() const; + // For nested macros embedded in actions (e.g. loop, nested-macro action) + void SetNestedParentMacro(Macro *parent) + { + _nestedParentMacro = parent; + } + Macro *GetNestedParentMacro() const { return _nestedParentMacro; } + // Saving and loading bool Save(obs_data_t *obj, bool saveForCopy = false) const; bool Load(obs_data_t *obj); @@ -187,6 +194,7 @@ private: std::deque> _elseActions; std::weak_ptr _parent; + Macro *_nestedParentMacro = nullptr; uint32_t _groupSize = 0; bool _isGroup = false; bool _isCollapsed = false;