Compare commits

..

3 Commits

Author SHA1 Message Date
WarmUpTill
151f69305b Add undo / redo support for macros 2026-08-17 18:47:51 +02:00
WarmUpTill
71e1e395df Clear widget cache for active macro to enable undo / redo 2026-08-17 18:47:51 +02:00
WarmUpTill
74e5ca1b57 Fix crash when rapidly inserting / removing macro segments 2026-08-14 22:46:20 +02:00
13 changed files with 1175 additions and 62 deletions

View File

@@ -173,6 +173,8 @@ target_sources(
lib/macro/macro-signals.hpp lib/macro/macro-signals.hpp
lib/macro/macro-tab.cpp lib/macro/macro-tab.cpp
lib/macro/macro-tree.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-tree.hpp
lib/macro/macro-websocket-trigger.cpp lib/macro/macro-websocket-trigger.cpp
lib/macro/macro.cpp lib/macro/macro.cpp

View File

@@ -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.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.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.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.contextMenuAdd="Add"
AdvSceneSwitcher.macroTab.copy="Duplicate Macro" AdvSceneSwitcher.macroTab.copy="Duplicate Macro"
AdvSceneSwitcher.macroTab.group="Group Selected Macros" AdvSceneSwitcher.macroTab.group="Group Selected Macros"
@@ -3107,4 +3118,3 @@ AdvSceneSwitcher.sceneGroupTab.defaultname="Scene Group %1"
AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already" AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group." AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group." AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
AdvSceneSwitcher.kwin.flatpakPermissionWarning="Window detection via KWin is unavailable.\nThe OBS Studio Flatpak requires additional permissions to enable this feature.\nSee the Advanced Scene Switcher wiki for details."

View File

@@ -1,9 +1,5 @@
#include "platform-funcs.hpp" #include "platform-funcs.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
@@ -632,39 +628,27 @@ int ignoreXerror(Display *d, XErrorEvent *e)
return 0; return 0;
} }
static void handleKWinInitFailure(bool kwinAvailable)
{
blog(LOG_INFO, "not using KWin compat");
const bool onKDE = qEnvironmentVariable("XDG_CURRENT_DESKTOP")
.contains("KDE", Qt::CaseInsensitive);
const bool inFlatpak = qEnvironmentVariableIsSet("FLATPAK_ID");
if (!kwinAvailable && onKDE && inFlatpak)
AddFinishedLoadingStep([]() {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
obs_module_text("AdvSceneSwitcher.kwin.flatpakPermissionWarning"));
});
}
void PlatformInit() void PlatformInit()
{ {
const bool kwinAvailable = isKWinAvailable();
KWin = kwinAvailable && registerKWinDBusListener(&notifier) &&
startKWinScript(KWinScriptObjectPath);
if (KWin)
blog(LOG_INFO, "using KWin compat");
else
handleKWinInitFailure(kwinAvailable);
initProcps();
initProc2();
auto display = disp(); auto display = disp();
if (!display) { if (!display) {
return; return;
} }
KWin = isKWinAvailable();
if (!(KWin && startKWinScript(KWinScriptObjectPath) &&
registerKWinDBusListener(&notifier))) {
// something bad happened while trying to initialize
// the KWin script/dbus so disable it
KWin = false;
blog(LOG_INFO, "not using KWin compat");
} else {
blog(LOG_INFO, "using KWin compat");
}
initXss(); initXss();
initProcps();
initProc2();
XSetErrorHandler(ignoreXerror); XSetErrorHandler(ignoreXerror);
} }

View File

@@ -5,7 +5,6 @@
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QFileDevice> #include <QFileDevice>
#include <QStandardPaths>
#include <QTextStream> #include <QTextStream>
#include <QtDBus/QDBusConnectionInterface> #include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusInterface> #include <QtDBus/QDBusInterface>
@@ -98,13 +97,11 @@ bool isKWinAvailable()
bool startKWinScript(QString &scriptObjectPath) bool startKWinScript(QString &scriptObjectPath)
{ {
const QString scriptPath = const QString scriptPath =
QStandardPaths::writableLocation( "/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js";
QStandardPaths::RuntimeLocation) +
"/AdvancedSceneSwitcher/KWinFocusNotifier.js";
const QString script = const QString script =
R"(var adss = "com.obsproject.Studio.Plugin.SceneSwitcher"; R"(var adss = "com.github.AdvancedSceneSwitcher";
var adssPath = "/com/obsproject/Studio/Plugin/SceneSwitcher"; var adssPath = "/com/github/AdvancedSceneSwitcher";
function trackWindow(window) { function trackWindow(window) {
var id = window.internalId.toString(); var id = window.internalId.toString();
@@ -132,8 +129,7 @@ workspace.windowActivated.connect(function(client) {
}))"; }))";
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) { if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {
blog(LOG_ERROR, blog(LOG_ERROR, "error creating /tmp/AdvancedSceneSwitcher");
"error creating KWinFocusNotifier script directory");
return false; return false;
} }
@@ -202,8 +198,8 @@ bool stopKWinScript(const QString &scriptObjectPath)
bool registerKWinDBusListener(FocusNotifier *notifier) bool registerKWinDBusListener(FocusNotifier *notifier)
{ {
static const QString serviceName = "com.obsproject.Studio.Plugin.SceneSwitcher"; static const QString serviceName = "com.github.AdvancedSceneSwitcher";
static const QString objectPath = "/com/obsproject/Studio/Plugin/SceneSwitcher"; static const QString objectPath = "/com/github/AdvancedSceneSwitcher";
auto bus = QDBusConnection::sessionBus(); auto bus = QDBusConnection::sessionBus();
if (bus.objectRegisteredAt(objectPath)) { if (bus.objectRegisteredAt(objectPath)) {

View File

@@ -12,7 +12,7 @@ namespace advss {
class FocusNotifier final : public QObject { class FocusNotifier final : public QObject {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "com.obsproject.Studio.Plugin.SceneSwitcher") Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher")
static std::mutex _mutex; static std::mutex _mutex;
static int activePID; static int activePID;

View File

@@ -1,6 +1,7 @@
#include "macro-edit.hpp" #include "macro-edit.hpp"
#include "condition-logic.hpp" #include "condition-logic.hpp"
#include "macro-undo-redo.hpp"
#include "cursor-shape-changer.hpp" #include "cursor-shape-changer.hpp"
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "macro-action-macro.hpp" #include "macro-action-macro.hpp"
@@ -257,6 +258,92 @@ std::shared_ptr<Macro> MacroEdit::GetMacro() const
return _currentMacro; 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, &macro->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, &macro->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, &macro->Conditions()[idx],
idx == 0));
if (idx == 0 && macro->Conditions().size() > 1) {
static_cast<MacroConditionEdit *>(
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<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(true);
}
SetConditionData(*macro);
}
break;
}
}
void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const
{ {
ui->conditionsList->ClearWidgetsFromCacheFor(macro); ui->conditionsList->ClearWidgetsFromCacheFor(macro);
@@ -295,7 +382,7 @@ isValidMacroSegmentIdx(const std::deque<std::shared_ptr<MacroSegment>> &list,
return (idx > 0 || (unsigned)idx < list.size()); return (idx > 0 || (unsigned)idx < list.size());
} }
void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) void MacroEdit::SetupMacroSegmentSelection(SegmentType type, int idx)
{ {
auto macro = _currentMacro; auto macro = _currentMacro;
if (!macro) { if (!macro) {
@@ -308,7 +395,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
std::deque<std::shared_ptr<MacroSegment>> segments; std::deque<std::shared_ptr<MacroSegment>> segments;
switch (type) { switch (type) {
case MacroEdit::MacroSection::CONDITIONS: case MacroEdit::SegmentType::CONDITION:
setList = ui->conditionsList; setList = ui->conditionsList;
setIdx = &currentConditionIdx; setIdx = &currentConditionIdx;
segments = {macro->Conditions().begin(), segments = {macro->Conditions().begin(),
@@ -319,7 +406,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
resetIdx1 = &currentActionIdx; resetIdx1 = &currentActionIdx;
resetIdx2 = &currentElseActionIdx; resetIdx2 = &currentElseActionIdx;
break; break;
case MacroEdit::MacroSection::ACTIONS: case MacroEdit::SegmentType::ACTION:
setList = ui->actionsList; setList = ui->actionsList;
setIdx = &currentActionIdx; setIdx = &currentActionIdx;
segments = {macro->Actions().begin(), macro->Actions().end()}; segments = {macro->Actions().begin(), macro->Actions().end()};
@@ -329,7 +416,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
resetIdx1 = &currentConditionIdx; resetIdx1 = &currentConditionIdx;
resetIdx2 = &currentElseActionIdx; resetIdx2 = &currentElseActionIdx;
break; break;
case MacroEdit::MacroSection::ELSE_ACTIONS: case MacroEdit::SegmentType::ELSE_ACTION:
setList = ui->elseActionsList; setList = ui->elseActionsList;
setIdx = &currentElseActionIdx; setIdx = &currentElseActionIdx;
segments = {macro->ElseActions().begin(), segments = {macro->ElseActions().begin(),
@@ -965,7 +1052,7 @@ void MacroEdit::UpMacroSegmentHotkey()
int conditionSize = macro->Conditions().size(); int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) { if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) { if (lastInteracted == SegmentType::CONDITION) {
if (conditionSize == 0) { if (conditionSize == 0) {
MacroActionSelectionChanged(0); MacroActionSelectionChanged(0);
} else { } else {
@@ -1021,7 +1108,7 @@ void MacroEdit::DownMacroSegmentHotkey()
int conditionSize = macro->Conditions().size(); int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) { if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) { if (lastInteracted == SegmentType::CONDITION) {
if (conditionSize == 0) { if (conditionSize == 0) {
MacroActionSelectionChanged(0); MacroActionSelectionChanged(0);
} else { } else {
@@ -1098,6 +1185,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
idx, new MacroActionEdit(this, &macro->Actions()[idx])); idx, new MacroActionEdit(this, &macro->Actions()[idx]));
SetActionData(*macro); SetActionData(*macro);
} }
RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx);
HighlightAction(idx); HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false); ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
@@ -1163,6 +1251,19 @@ void MacroEdit::RemoveMacroAction(int idx)
return; 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(); auto lock = LockContext();
ui->actionsList->Remove(idx); ui->actionsList->Remove(idx);
@@ -1173,7 +1274,7 @@ void MacroEdit::RemoveMacroAction(int idx)
SetActionData(*macro); SetActionData(*macro);
} }
MacroActionSelectionChanged(-1); MacroActionSelectionChanged(-1);
lastInteracted = MacroSection::ACTIONS; lastInteracted = SegmentType::ACTION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1353,7 +1454,7 @@ void MacroEdit::MoveMacroActionDown(int idx)
void MacroEdit::MacroElseActionSelectionChanged(int idx) void MacroEdit::MacroElseActionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx); SetupMacroSegmentSelection(SegmentType::ELSE_ACTION, idx);
} }
void MacroEdit::MacroElseActionReorder(int to, int from) 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, &macro->ElseActions()[idx])); new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro); SetElseActionData(*macro);
} }
RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION, idx);
HighlightElseAction(idx); HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false); ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
@@ -1455,6 +1557,20 @@ void MacroEdit::RemoveMacroElseAction(int idx)
return; 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(); auto lock = LockContext();
ui->elseActionsList->Remove(idx); ui->elseActionsList->Remove(idx);
@@ -1465,7 +1581,7 @@ void MacroEdit::RemoveMacroElseAction(int idx)
SetElseActionData(*macro); SetElseActionData(*macro);
} }
MacroElseActionSelectionChanged(-1); MacroElseActionSelectionChanged(-1);
lastInteracted = MacroSection::ELSE_ACTIONS; lastInteracted = SegmentType::ELSE_ACTION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1526,7 +1642,7 @@ void MacroEdit::MoveMacroElseActionDown(int idx)
void MacroEdit::MacroActionSelectionChanged(int idx) void MacroEdit::MacroActionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::ACTIONS, idx); SetupMacroSegmentSelection(SegmentType::ACTION, idx);
} }
void MacroEdit::MacroActionReorder(int to, int from) void MacroEdit::MacroActionReorder(int to, int from)
@@ -1617,6 +1733,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
idx == 0)); idx == 0));
SetConditionData(*macro); SetConditionData(*macro);
} }
RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx);
HighlightCondition(idx); HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false); ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
@@ -1652,6 +1769,21 @@ void MacroEdit::RemoveMacroCondition(int idx)
return; 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(); auto lock = LockContext();
ui->conditionsList->Remove(idx); ui->conditionsList->Remove(idx);
@@ -1667,7 +1799,7 @@ void MacroEdit::RemoveMacroCondition(int idx)
SetConditionData(*macro); SetConditionData(*macro);
} }
MacroConditionSelectionChanged(-1); MacroConditionSelectionChanged(-1);
lastInteracted = MacroSection::CONDITIONS; lastInteracted = SegmentType::CONDITION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1792,7 +1924,7 @@ void MacroEdit::MoveMacroConditionDown(int idx)
void MacroEdit::MacroConditionSelectionChanged(int idx) void MacroEdit::MacroConditionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx); SetupMacroSegmentSelection(SegmentType::CONDITION, idx);
} }
void MacroEdit::MacroConditionReorder(int to, int from) void MacroEdit::MacroConditionReorder(int to, int from)

View File

@@ -12,11 +12,15 @@ class MacroEdit : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
enum class SegmentType { CONDITION, ACTION, ELSE_ACTION };
MacroEdit(QWidget *parent, QStringList helpMsg = {}); MacroEdit(QWidget *parent, QStringList helpMsg = {});
void SetMacro(const std::shared_ptr<Macro> &); void SetMacro(const std::shared_ptr<Macro> &);
std::shared_ptr<Macro> GetMacro() const; std::shared_ptr<Macro> GetMacro() const;
void ClearSegmentWidgetCacheFor(Macro *) const; void ClearSegmentWidgetCacheFor(Macro *) const;
void SetControlsDisabled(bool disable) 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 HighlightAction(int idx, QColor color = QColor(Qt::green)) const;
void HighlightElseAction(int idx, void HighlightElseAction(int idx,
QColor color = QColor(Qt::green)) const; QColor color = QColor(Qt::green)) const;
@@ -112,14 +116,12 @@ protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
private: private:
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx, void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
int *currentIdx); int *currentIdx);
void SetupMacroSegmentSelection(MacroSection type, int idx); void SetupMacroSegmentSelection(SegmentType type, int idx);
void void
SetupContextMenu(const QPoint &pos, SetupContextMenu(const QPoint &pos,
const std::function<void(MacroEdit *, int)> &remove, const std::function<void(MacroEdit *, int)> &remove,
@@ -131,7 +133,7 @@ private:
void RunSegmentHighlightChecks(); void RunSegmentHighlightChecks();
bool ElseSectionIsVisible() const; bool ElseSectionIsVisible() const;
MacroSection lastInteracted = MacroSection::CONDITIONS; SegmentType lastInteracted = SegmentType::CONDITION;
int currentConditionIdx = -1; int currentConditionIdx = -1;
int currentActionIdx = -1; int currentActionIdx = -1;
int currentElseActionIdx = -1; int currentElseActionIdx = -1;

View File

@@ -201,6 +201,8 @@ bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
_contentLayout->addWidget(widget); _contentLayout->addWidget(widget);
widget->show(); widget->show();
} }
// Widgets are now owned by the layout again, so the cache entry is stale.
_widgetCache.erase(it);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -463,7 +465,17 @@ void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
const auto viewportRect = viewport()->rect(); const auto viewportRect = viewport()->rect();
for (auto segment : widget()->findChildren<MacroSegmentEdit *>()) { for (int i = 0; i < _contentLayout->count(); ++i) {
auto item = _contentLayout->itemAt(i);
if (!item) {
continue;
}
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (!segment) {
continue;
}
const auto pos = segment->mapTo(viewport(), QPoint(0, 0)); const auto pos = segment->mapTo(viewport(), QPoint(0, 0));
const QRect rect(pos, segment->size()); const QRect rect(pos, segment->size());
if (!viewportRect.intersects(rect)) { if (!viewportRect.intersects(rect)) {

View File

@@ -7,6 +7,7 @@
#include "macro-settings.hpp" #include "macro-settings.hpp"
#include "macro-signals.hpp" #include "macro-signals.hpp"
#include "macro-tree.hpp" #include "macro-tree.hpp"
#include "macro-undo-redo.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "math-helpers.hpp" #include "math-helpers.hpp"
#include "name-dialog.hpp" #include "name-dialog.hpp"
@@ -122,6 +123,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->Add(newMacro); ui->macros->Add(newMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
@@ -131,6 +133,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->AddToGroup(newMacro, selectedMacro); ui->macros->AddToGroup(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
@@ -139,12 +142,14 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->Add(newMacro, selectedMacro); ui->macros->Add(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
Macro::PrepareMoveToGroup(selectedMacroGroup, newMacro); Macro::PrepareMoveToGroup(selectedMacroGroup, newMacro);
ui->macros->Add(newMacro, selectedMacro); ui->macros->Add(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add(QString::fromStdString(name)); MacroSignalManager::Instance()->Add(QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
} }
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros, static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
@@ -185,6 +190,12 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
} }
} }
if (macro->IsGroup()) {
RegisterGroupDeleteUndoRedo(macro.get());
} else {
RegisterMacroRemoveUndoRedo(macro.get());
}
if (macro->IsGroup()) { if (macro->IsGroup()) {
std::vector<std::shared_ptr<Macro>> macros = {macro}; std::vector<std::shared_ptr<Macro>> macros = {macro};
addGroupSubitems(macros, macro); addGroupSubitems(macros, macro);
@@ -241,13 +252,16 @@ void AdvSceneSwitcher::RemoveSelectedMacros()
void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro, void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro,
const QString &name) const QString &name)
{ {
auto oldName = QString::fromStdString(macro->Name()); const std::string oldName = macro->Name();
const std::string newName = name.toStdString();
{ {
auto lock = LockContext(); 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() void AdvSceneSwitcher::on_macroRemove_clicked()

View File

@@ -1,4 +1,5 @@
#include "macro-tree.hpp" #include "macro-tree.hpp"
#include "macro-undo-redo.hpp"
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "macro-search.hpp" #include "macro-search.hpp"
@@ -824,6 +825,8 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
Reset(_macros); Reset(_macros);
assert(IsInValidState()); assert(IsInValidState());
RegisterGroupCreateUndoRedo(name.toStdString());
} }
void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices) void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
@@ -837,6 +840,7 @@ void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
std::shared_ptr<Macro> item = _macros[ModelIndexToMacroIndex( std::shared_ptr<Macro> item = _macros[ModelIndexToMacroIndex(
indices[i].row(), _macros)]; indices[i].row(), _macros)];
if (item->IsGroup()) { if (item->IsGroup()) {
RegisterGroupRemoveUndoRedo(item->Name());
Macro::RemoveGroup(item); Macro::RemoveGroup(item);
} }
} }

View File

@@ -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 <obs-frontend-api.h>
#include <obs.hpp>
#include <QString>
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 &macroName, 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 &macros = 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<Macro>();
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 &macros = 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 &macros = 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<std::shared_ptr<Macro>> 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<Macro>();
{
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 &macros = 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<std::shared_ptr<Macro>> 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 &macros = 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 &macroName)
{
const auto macro = GetWeakMacroByName(macroName.c_str()).lock();
if (!macro || macro->IsGroup()) {
return;
}
const auto &macros = 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 &macros = 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<std::shared_ptr<Macro>> &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 &macros = 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 &macros = 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 &macros = 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

View File

@@ -0,0 +1,24 @@
#pragma once
#include "macro-edit.hpp"
#include <obs-data.h>
#include <string>
namespace advss {
class Macro;
void RegisterMacroAddUndoRedo(const std::string &macroName);
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

View File

@@ -110,7 +110,9 @@ void MacroEdit::HighlightControls() const {}
void MacroEdit::PopulateMacroActions(Macro &, uint32_t) {} void MacroEdit::PopulateMacroActions(Macro &, uint32_t) {}
void MacroEdit::PopulateMacroElseActions(Macro &, uint32_t) {} void MacroEdit::PopulateMacroElseActions(Macro &, uint32_t) {}
void MacroEdit::PopulateMacroConditions(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 &, void MacroEdit::SetupContextMenu(const QPoint &,
const std::function<void(MacroEdit *, int)> &, const std::function<void(MacroEdit *, int)> &,
const std::function<void(MacroEdit *)> &, const std::function<void(MacroEdit *)> &,