diff --git a/CMakeLists.txt b/CMakeLists.txt
index 781a990c..a82e11cb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -217,8 +217,6 @@ target_sources(
src/macro-core/macro-condition-window.hpp
src/macro-core/macro-condition.cpp
src/macro-core/macro-condition.hpp
- src/macro-core/macro-list-entry-widget.cpp
- src/macro-core/macro-list-entry-widget.hpp
src/macro-core/macro-properties.cpp
src/macro-core/macro-properties.hpp
src/macro-core/macro-ref.cpp
@@ -230,6 +228,8 @@ target_sources(
src/macro-core/macro-selection.cpp
src/macro-core/macro-selection.hpp
src/macro-core/macro-tab.cpp
+ src/macro-core/macro-tree.cpp
+ src/macro-core/macro-tree.hpp
src/macro-core/macro.cpp
src/macro-core/macro.hpp)
diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index 2ce84699..cd6219fe 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -79,8 +79,12 @@ AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the act
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
+AdvSceneSwitcher.macroTab.defaultGroupName="Group %1"
AdvSceneSwitcher.macroTab.exists="Macro name exists already"
-AdvSceneSwitcher.macroTab.copy="Create copy"
+AdvSceneSwitcher.macroTab.groupDeleteConfirm="Are you sure you want to delete \"%1\" and all its elements?"
+AdvSceneSwitcher.macroTab.copy="Create copy of current macro"
+AdvSceneSwitcher.macroTab.group="Group selected items"
+AdvSceneSwitcher.macroTab.ungroup="Ungroup selected groups"
AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"
diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui
index 1cf0228c..29da7d31 100644
--- a/forms/advanced-scene-switcher.ui
+++ b/forms/advanced-scene-switcher.ui
@@ -634,49 +634,26 @@
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- true
-
-
- QAbstractItemView::InternalMove
-
-
- Qt::MoveAction
-
-
- 0
-
-
- false
-
-
-
- -
-
-
- false
-
-
- AdvSceneSwitcher.macroTab.help
-
-
- Qt::AlignCenter
-
-
- true
-
-
-
-
+
+
+ true
+
+
+ true
+
+
+ QAbstractItemView::InternalMove
+
+
+ Qt::TargetMoveAction
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+ 0
+
+
-
@@ -4560,6 +4537,13 @@
+
+
+ MacroTree
+ QListView
+
+
+
diff --git a/src/advanced-scene-switcher.hpp b/src/advanced-scene-switcher.hpp
index 8bbc7e3a..fe19d5db 100644
--- a/src/advanced-scene-switcher.hpp
+++ b/src/advanced-scene-switcher.hpp
@@ -44,8 +44,9 @@ public:
void OpenSequenceExtendEdit(SequenceWidget *sw);
void SetEditSceneGroup(SceneGroup &sg);
- bool addNewMacro(std::string &name, std::string format = "");
- Macro *getSelectedMacro();
+ bool addNewMacro(std::shared_ptr &res, std::string &name,
+ std::string format = "");
+ std::shared_ptr getSelectedMacro();
void SetEditMacro(Macro &m);
void SetMacroEditAreaDisabled(bool);
void HighlightAction(int idx);
@@ -126,7 +127,6 @@ public slots:
void on_runMacro_clicked();
void on_runMacroInParallel_stateChanged(int value);
void on_runMacroOnChange_stateChanged(int value);
- void on_macros_currentRowChanged(int idx);
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_conditionUp_clicked();
@@ -135,6 +135,8 @@ public slots:
void on_actionRemove_clicked();
void on_actionUp_clicked();
void on_actionDown_clicked();
+ void MacroSelectionChanged(const QItemSelection &,
+ const QItemSelection &);
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
void DeleteMacroSegementHotkey();
@@ -165,7 +167,6 @@ public slots:
void ResetOpacityActionControls();
void ResetOpacityConditionControls();
void HighlightControls();
- void MacroDragDropReorder(QModelIndex, int, int, QModelIndex, int);
void HighlightOnChange();
void on_macroProperties_clicked();
diff --git a/src/macro-core/macro-action-edit.cpp b/src/macro-core/macro-action-edit.cpp
index 06bce2f3..c06bcad8 100644
--- a/src/macro-core/macro-action-edit.cpp
+++ b/src/macro-core/macro-action-edit.cpp
@@ -172,8 +172,9 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
}
{
std::lock_guard lock(switcher->m);
- macro->Actions().emplace(macro->Actions().begin() + idx,
- MacroActionFactory::Create(id, macro));
+ macro->Actions().emplace(
+ macro->Actions().begin() + idx,
+ MacroActionFactory::Create(id, macro.get()));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Actions().at(idx - 1)->Save(data);
@@ -298,7 +299,7 @@ void AdvSceneSwitcher::MoveMacroActionUp(int idx)
return;
}
- SwapActions(macro, idx, idx - 1);
+ SwapActions(macro.get(), idx, idx - 1);
HighlightAction(idx - 1);
}
@@ -313,7 +314,7 @@ void AdvSceneSwitcher::MoveMacroActionDown(int idx)
return;
}
- SwapActions(macro, idx, idx + 1);
+ SwapActions(macro.get(), idx, idx + 1);
HighlightAction(idx + 1);
}
diff --git a/src/macro-core/macro-condition-edit.cpp b/src/macro-core/macro-condition-edit.cpp
index 3806cb06..3550a060 100644
--- a/src/macro-core/macro-condition-edit.cpp
+++ b/src/macro-core/macro-condition-edit.cpp
@@ -380,7 +380,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
logic = macro->Conditions().at(idx - 1)->GetLogicType();
}
} else {
- MacroConditionScene temp(macro);
+ MacroConditionScene temp(macro.get());
id = temp.GetId();
logic = LogicType::ROOT_NONE;
}
@@ -388,7 +388,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
std::lock_guard lock(switcher->m);
auto cond = macro->Conditions().emplace(
macro->Conditions().begin() + idx,
- MacroConditionFactory::Create(id, macro));
+ MacroConditionFactory::Create(id, macro.get()));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Conditions().at(idx - 1)->Save(data);
@@ -535,7 +535,7 @@ void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
return;
}
- SwapConditions(macro, idx, idx - 1);
+ SwapConditions(macro.get(), idx, idx - 1);
HighlightCondition(idx - 1);
}
@@ -550,7 +550,7 @@ void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
return;
}
- SwapConditions(macro, idx, idx + 1);
+ SwapConditions(macro.get(), idx, idx + 1);
HighlightCondition(idx + 1);
}
diff --git a/src/macro-core/macro-list-entry-widget.cpp b/src/macro-core/macro-list-entry-widget.cpp
deleted file mode 100644
index 0131496a..00000000
--- a/src/macro-core/macro-list-entry-widget.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "macro-list-entry-widget.hpp"
-#include "macro.hpp"
-#include "utility.hpp"
-
-MacroListEntryWidget::MacroListEntryWidget(std::shared_ptr macro,
- bool highlight, QWidget *parent)
- : QWidget(parent),
- _name(new QLabel(QString::fromStdString(macro->Name()))),
- _running(new QCheckBox),
- _macro(macro),
- _highlightExecutedMacros(highlight)
-{
- _running->setChecked(!macro->Paused());
-
- setStyleSheet("\
- QCheckBox { background-color: rgba(0,0,0,0); }\
- QLabel { background-color: rgba(0,0,0,0); }");
-
- auto layout = new QHBoxLayout;
- layout->setContentsMargins(0, 0, 0, 0);
- layout->addWidget(_running);
- layout->addWidget(_name);
- layout->addStretch();
- layout->setSizeConstraint(QLayout::SetFixedSize);
- setLayout(layout);
-
- connect(_running, SIGNAL(stateChanged(int)), this,
- SLOT(PauseChanged(int)));
- connect(window(), SIGNAL(HighlightMacrosChanged(bool)), this,
- SLOT(EnableHighlight(bool)));
- _timer.setInterval(1500);
- connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightExecuted()));
- connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
- _timer.start();
-}
-
-void MacroListEntryWidget::PauseChanged(int state)
-{
- _macro->SetPaused(!state);
-}
-
-void MacroListEntryWidget::SetName(const QString &name)
-{
- _name->setText(name);
-}
-
-void MacroListEntryWidget::SetMacro(std::shared_ptr &m)
-{
- _macro = m;
-}
-
-void MacroListEntryWidget::EnableHighlight(bool value)
-{
- _highlightExecutedMacros = value;
-}
-
-void MacroListEntryWidget::HighlightExecuted()
-{
- if (!_highlightExecutedMacros) {
- return;
- }
-
- if (_macro && _macro->WasExecutedRecently()) {
- PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
- }
-}
-
-void MacroListEntryWidget::UpdatePaused()
-{
- const QSignalBlocker b(_running);
- _running->setChecked(!_macro->Paused());
-}
diff --git a/src/macro-core/macro-list-entry-widget.hpp b/src/macro-core/macro-list-entry-widget.hpp
deleted file mode 100644
index 8084cea1..00000000
--- a/src/macro-core/macro-list-entry-widget.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-
-class Macro;
-
-class MacroListEntryWidget : public QWidget {
- Q_OBJECT
-
-public:
- MacroListEntryWidget(std::shared_ptr, bool highlight,
- QWidget *parent);
- void SetName(const QString &);
- void SetMacro(std::shared_ptr &);
-
-private slots:
- void PauseChanged(int);
- void HighlightExecuted();
- void UpdatePaused();
- void EnableHighlight(bool);
-
-private:
- QTimer _timer;
- QLabel *_name;
- QCheckBox *_running;
- std::shared_ptr _macro;
-
- bool _highlightExecutedMacros = false;
-};
diff --git a/src/macro-core/macro-selection.cpp b/src/macro-core/macro-selection.cpp
index d480c358..260ba496 100644
--- a/src/macro-core/macro-selection.cpp
+++ b/src/macro-core/macro-selection.cpp
@@ -17,7 +17,10 @@ MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent)
firstItem->setSelectable(false);
firstItem->setEnabled(false);
- for (auto &m : switcher->macros) {
+ for (const auto &m : switcher->macros) {
+ if (m->IsGroup()) {
+ continue;
+ }
addItem(QString::fromStdString(m->Name()));
}
@@ -51,7 +54,12 @@ void MacroSelection::HideSelectedMacro()
if (!ssWindow) {
return;
}
- int idx = ssWindow->ui->macros->currentRow();
+
+ const auto m = ssWindow->ui->macros->GetCurrentMacro();
+ if (!m) {
+ return;
+ }
+ int idx = findText(QString::fromStdString(m->Name()));
if (idx == -1) {
return;
}
diff --git a/src/macro-core/macro-tab.cpp b/src/macro-core/macro-tab.cpp
index 2c5892c8..33e31cb6 100644
--- a/src/macro-core/macro-tab.cpp
+++ b/src/macro-core/macro-tab.cpp
@@ -1,5 +1,5 @@
#include "macro.hpp"
-#include "macro-list-entry-widget.hpp"
+#include "macro-tree.hpp"
#include "macro-action-edit.hpp"
#include "macro-condition-edit.hpp"
#include "advanced-scene-switcher.hpp"
@@ -20,7 +20,8 @@ bool macroNameExists(std::string name)
return !!GetMacroByName(name.c_str());
}
-bool AdvSceneSwitcher::addNewMacro(std::string &name, std::string format)
+bool AdvSceneSwitcher::addNewMacro(std::shared_ptr &res,
+ std::string &name, std::string format)
{
QString fmt;
int i = 1;
@@ -58,116 +59,78 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name, std::string format)
{
std::lock_guard lock(switcher->m);
- switcher->macros.emplace_back(std::make_shared(
+ res = std::make_shared(
name,
- switcher->macroProperties._newMacroRegisterHotkeys));
+ switcher->macroProperties._newMacroRegisterHotkeys);
}
return true;
}
-QListWidgetItem *AddNewMacroListEntry(QListWidget *list,
- std::shared_ptr ¯o)
-{
- QListWidgetItem *item = new QListWidgetItem(list);
- item->setData(Qt::UserRole, QString::fromStdString(macro->Name()));
- auto listEntry = new MacroListEntryWidget(
- macro, switcher->macroProperties._highlightExecuted, list);
- list->setItemWidget(item, listEntry);
- return item;
-}
-
void AdvSceneSwitcher::on_macroAdd_clicked()
{
std::string name;
- if (!addNewMacro(name)) {
+ std::shared_ptr newMacro;
+ if (!addNewMacro(newMacro, name)) {
return;
}
- QString text = QString::fromStdString(name);
- auto item = AddNewMacroListEntry(ui->macros, switcher->macros.back());
- ui->macros->setCurrentItem(item);
+ {
+ std::lock_guard lock(switcher->m);
+ ui->macros->Add(newMacro);
+ }
ui->macroAdd->disconnect(addPulse);
- ui->macroHelp->setVisible(false);
-
emit MacroAdded(QString::fromStdString(name));
}
void AdvSceneSwitcher::on_macroRemove_clicked()
{
- QListWidgetItem *item = ui->macros->currentItem();
- if (!item) {
+ auto macro = getSelectedMacro();
+ if (!macro) {
return;
}
- int idx = ui->macros->currentRow();
- delete item;
- QString name;
- {
- std::lock_guard lock(switcher->m);
- switcher->abortMacroWait = true;
- switcher->macroWaitCv.notify_all();
- name = QString::fromStdString(switcher->macros[idx]->Name());
- switcher->macros.erase(switcher->macros.begin() + idx);
- for (auto &m : switcher->macros) {
- m->ResolveMacroRef();
+ auto name = QString::fromStdString(macro->Name());
+ if (macro->IsGroup()) {
+ QString deleteWarning = obs_module_text(
+ "AdvSceneSwitcher.macroTab.groupDeleteConfirm");
+ if (!DisplayMessage(deleteWarning.arg(name), true)) {
+ return;
}
}
- if (ui->macros->count() == 0) {
- ui->macroHelp->setVisible(true);
+ {
+ std::lock_guard lock(switcher->m);
+ ui->macros->Remove(macro);
}
+
emit MacroRemoved(name);
}
void AdvSceneSwitcher::on_macroUp_clicked()
{
std::lock_guard lock(switcher->m);
- if (!listMoveUp(ui->macros)) {
+ auto macro = getSelectedMacro();
+ if (!macro) {
return;
}
-
- int index = ui->macros->currentRow() + 1;
- auto *entry1 = static_cast(
- ui->macros->itemWidget(ui->macros->item(index)));
- auto *entry2 = static_cast(
- ui->macros->itemWidget(ui->macros->item(index - 1)));
- entry1->SetMacro(*(switcher->macros.begin() + index - 1));
- entry2->SetMacro(*(switcher->macros.begin() + index));
- iter_swap(switcher->macros.begin() + index,
- switcher->macros.begin() + index - 1);
-
- for (auto &m : switcher->macros) {
- m->ResolveMacroRef();
- }
+ ui->macros->Up(macro);
}
void AdvSceneSwitcher::on_macroDown_clicked()
{
std::lock_guard lock(switcher->m);
- if (!listMoveDown(ui->macros)) {
+ auto macro = getSelectedMacro();
+ if (!macro) {
return;
}
-
- int index = ui->macros->currentRow() - 1;
- auto *entry1 = static_cast(
- ui->macros->itemWidget(ui->macros->item(index)));
- auto *entry2 = static_cast(
- ui->macros->itemWidget(ui->macros->item(index + 1)));
- entry1->SetMacro(*(switcher->macros.begin() + index + 1));
- entry2->SetMacro(*(switcher->macros.begin() + index));
- iter_swap(switcher->macros.begin() + index,
- switcher->macros.begin() + index + 1);
-
- for (auto &m : switcher->macros) {
- m->ResolveMacroRef();
- }
+ ui->macros->Down(macro);
}
void AdvSceneSwitcher::on_macroName_editingFinished()
{
bool nameValid = true;
- Macro *macro = getSelectedMacro();
+ auto macro = getSelectedMacro();
if (!macro) {
return;
}
@@ -189,22 +152,24 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
std::lock_guard lock(switcher->m);
if (nameValid) {
macro->SetName(newName.toUtf8().constData());
- QListWidgetItem *item = ui->macros->currentItem();
- item->setData(Qt::UserRole, newName);
- auto listEntry = static_cast(
- ui->macros->itemWidget(item));
- listEntry->SetName(newName);
+ auto macro = getSelectedMacro();
+ if (!macro) {
+ return;
+ }
+ macro->SetName(newName.toStdString());
} else {
ui->macroName->setText(oldName);
}
}
- emit MacroRenamed(oldName, newName);
+ if (nameValid) {
+ emit MacroRenamed(oldName, newName);
+ }
}
void AdvSceneSwitcher::on_runMacro_clicked()
{
- Macro *macro = getSelectedMacro();
+ auto macro = getSelectedMacro();
if (!macro) {
return;
}
@@ -219,7 +184,7 @@ void AdvSceneSwitcher::on_runMacro_clicked()
void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value)
{
- Macro *macro = getSelectedMacro();
+ auto macro = getSelectedMacro();
if (!macro) {
return;
}
@@ -229,7 +194,7 @@ void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value)
void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value)
{
- Macro *macro = getSelectedMacro();
+ auto macro = getSelectedMacro();
if (!macro) {
return;
}
@@ -313,6 +278,10 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
PopulateMacroConditions(m);
PopulateMacroActions(m);
SetMacroEditAreaDisabled(false);
+ if (m.IsGroup()) {
+ SetMacroEditAreaDisabled(true);
+ ui->macroName->setEnabled(true);
+ }
currentActionIdx = -1;
currentConditionIdx = -1;
@@ -340,25 +309,20 @@ void AdvSceneSwitcher::HighlightCondition(int idx)
conditionsList->Highlight(idx);
}
-Macro *AdvSceneSwitcher::getSelectedMacro()
+std::shared_ptr AdvSceneSwitcher::getSelectedMacro()
{
- QListWidgetItem *item = ui->macros->currentItem();
-
- if (!item) {
- return nullptr;
- }
-
- QString name = item->data(Qt::UserRole).toString();
- return GetMacroByQString(name);
+ return ui->macros->GetCurrentMacro();
}
-void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
+void AdvSceneSwitcher::MacroSelectionChanged(const QItemSelection &,
+ const QItemSelection &)
{
if (loading) {
return;
}
- if (idx == -1) {
+ auto macro = getSelectedMacro();
+ if (!macro) {
SetMacroEditAreaDisabled(true);
conditionsList->Clear();
actionsList->Clear();
@@ -366,44 +330,21 @@ void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
actionsList->SetHelpMsgVisible(true);
return;
}
-
- QListWidgetItem *item = ui->macros->item(idx);
- QString macroName = item->data(Qt::UserRole).toString();
-
- auto macro = GetMacroByQString(macroName);
- if (macro) {
- SetEditMacro(*macro);
- }
-}
-
-void AdvSceneSwitcher::MacroDragDropReorder(QModelIndex, int from, int,
- QModelIndex, int to)
-{
- std::lock_guard lock(switcher->m);
- if (from > to) {
- std::rotate(switcher->macros.rend() - from - 1,
- switcher->macros.rend() - from,
- switcher->macros.rend() - to);
- } else {
- std::rotate(switcher->macros.begin() + from,
- switcher->macros.begin() + from + 1,
- switcher->macros.begin() + to);
- }
-
- for (auto &m : switcher->macros) {
- m->ResolveMacroRef();
- }
+ SetEditMacro(*macro);
}
void AdvSceneSwitcher::HighlightOnChange()
{
+ if (!switcher->macroProperties._highlightExecuted) {
+ return;
+ }
+
auto macro = getSelectedMacro();
if (!macro) {
return;
}
- if (switcher->macroProperties._highlightExecuted &&
- macro->OnChangePreventedActionsRecently()) {
+ if (macro->OnChangePreventedActionsRecently()) {
PulseWidget(ui->runMacroOnChange, Qt::yellow, Qt::transparent,
true);
}
@@ -413,7 +354,7 @@ void AdvSceneSwitcher::on_macroProperties_clicked()
{
MacroProperties prop = switcher->macroProperties;
bool accepted = MacroPropertiesDialog::AskForSettings(
- this, prop, getSelectedMacro());
+ this, prop, getSelectedMacro().get());
if (!accepted) {
return;
}
@@ -440,26 +381,17 @@ bool shouldResotreSplitterPos(const QList &pos)
void AdvSceneSwitcher::setupMacroTab()
{
- const QSignalBlocker signalBlocker(ui->macros);
- ui->macros->clear();
- for (auto &m : switcher->macros) {
- AddNewMacroListEntry(ui->macros, m);
+ if (switcher->macros.size() == 0 && !switcher->disableHints) {
+ addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
}
-
- if (switcher->macros.size() == 0) {
- if (!switcher->disableHints) {
- addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
- }
- ui->macroHelp->setVisible(true);
- } else {
- ui->macroHelp->setVisible(false);
- }
-
- connect(ui->macros->model(),
- SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)),
+ ui->macros->Reset(switcher->macros,
+ switcher->macroProperties._highlightExecuted);
+ connect(ui->macros->selectionModel(),
+ SIGNAL(selectionChanged(const QItemSelection &,
+ const QItemSelection &)),
this,
- SLOT(MacroDragDropReorder(QModelIndex, int, int, QModelIndex,
- int)));
+ SLOT(MacroSelectionChanged(const QItemSelection &,
+ const QItemSelection &)));
delete conditionsList;
conditionsList = new MacroSegmentList(this);
@@ -531,42 +463,56 @@ void AdvSceneSwitcher::setupMacroTab()
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
{
QPoint globalPos = ui->macros->mapToGlobal(pos);
- QMenu myMenu;
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.copy"),
- this, &AdvSceneSwitcher::CopyMacro);
- myMenu.exec(globalPos);
+ QMenu menu;
+ auto copy = menu.addAction(
+ obs_module_text("AdvSceneSwitcher.macroTab.copy"), this,
+ &AdvSceneSwitcher::CopyMacro);
+ copy->setDisabled(ui->macros->GroupsSelected());
+
+ auto group = menu.addAction(
+ obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros,
+ &MacroTree::GroupSelectedItems);
+ // Nested groups are not supported
+ group->setDisabled(ui->macros->GroupedItemsSelected() ||
+ ui->macros->GroupsSelected() ||
+ ui->macros->SelectionEmpty());
+
+ auto ungroup = menu.addAction(
+ obs_module_text("AdvSceneSwitcher.macroTab.ungroup"),
+ ui->macros, &MacroTree::UngroupSelectedGroups);
+ ungroup->setEnabled(ui->macros->GroupsSelected());
+
+ menu.exec(globalPos);
}
void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos)
{
QPoint globalPos = actionsList->mapToGlobal(pos);
- QMenu myMenu;
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
- this, &AdvSceneSwitcher::ExpandAllActions);
- myMenu.addAction(
- obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), this,
- &AdvSceneSwitcher::CollapseAllActions);
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
- this, &AdvSceneSwitcher::MinimizeConditions);
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
- this, &AdvSceneSwitcher::MinimizeActions);
- myMenu.exec(globalPos);
+ QMenu menu;
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
+ this, &AdvSceneSwitcher::ExpandAllActions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
+ this, &AdvSceneSwitcher::CollapseAllActions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
+ this, &AdvSceneSwitcher::MinimizeConditions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
+ this, &AdvSceneSwitcher::MinimizeActions);
+ menu.exec(globalPos);
}
void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos)
{
QPoint globalPos = conditionsList->mapToGlobal(pos);
- QMenu myMenu;
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
- this, &AdvSceneSwitcher::ExpandAllConditions);
- myMenu.addAction(
- obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), this,
- &AdvSceneSwitcher::CollapseAllConditions);
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
- this, &AdvSceneSwitcher::MinimizeActions);
- myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
- this, &AdvSceneSwitcher::MinimizeConditions);
- myMenu.exec(globalPos);
+ QMenu menu;
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
+ this, &AdvSceneSwitcher::ExpandAllConditions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
+ this, &AdvSceneSwitcher::CollapseAllConditions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
+ this, &AdvSceneSwitcher::MinimizeActions);
+ menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
+ this, &AdvSceneSwitcher::MinimizeConditions);
+ menu.exec(globalPos);
}
void AdvSceneSwitcher::CopyMacro()
@@ -578,18 +524,20 @@ void AdvSceneSwitcher::CopyMacro()
std::string format = macro->Name() + " %1";
std::string name;
- if (!addNewMacro(name, format)) {
+ std::shared_ptr newMacro;
+ if (!addNewMacro(newMacro, name, format)) {
return;
}
obs_data_t *data = obs_data_create();
macro->Save(data);
- switcher->macros.back()->Load(data);
- switcher->macros.back()->SetName(name);
+ newMacro->Load(data);
+ newMacro->SetName(name);
obs_data_release(data);
- auto item = AddNewMacroListEntry(ui->macros, switcher->macros.back());
- ui->macros->setCurrentItem(item);
+ ui->macros->Add(newMacro);
+ ui->macroAdd->disconnect(addPulse);
+ emit MacroAdded(QString::fromStdString(name));
}
void AdvSceneSwitcher::ExpandAllActions()
diff --git a/src/macro-core/macro-tree.cpp b/src/macro-core/macro-tree.cpp
new file mode 100644
index 00000000..f2de5fc2
--- /dev/null
+++ b/src/macro-core/macro-tree.cpp
@@ -0,0 +1,1201 @@
+#include "macro-tree.hpp"
+#include "macro.hpp"
+#include "utility.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr macroItem,
+ bool highlight)
+ : _tree(tree), _highlight(highlight), _macro(macroItem)
+{
+ setAttribute(Qt::WA_TranslucentBackground);
+
+ auto name = _macro->Name();
+ bool macroPaused = _macro->Paused();
+ bool isGroup = _macro->IsGroup();
+
+ if (isGroup) {
+ QIcon icon;
+ icon.addFile(
+ QString::fromUtf8(":/res/images/sources/group.svg"),
+ QSize(), QIcon::Normal, QIcon::Off);
+ QPixmap pixmap = icon.pixmap(QSize(16, 16));
+ _iconLabel = new QLabel();
+ _iconLabel->setPixmap(pixmap);
+ _iconLabel->setStyleSheet("background: none");
+ }
+
+ _running = new QCheckBox();
+ _running->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ _running->setChecked(!macroPaused);
+ _running->setStyleSheet("background: none");
+
+ _label = new QLabel(QString::fromStdString(name));
+ _label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ _label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ _label->setAttribute(Qt::WA_TranslucentBackground);
+
+#ifdef __APPLE__
+ _running->setAttribute(Qt::WA_LayoutUsesWidgetRect);
+#endif
+
+ _boxLayout = new QHBoxLayout();
+ _boxLayout->setContentsMargins(0, 0, 0, 0);
+ _boxLayout->addWidget(_running);
+ if (isGroup) {
+ _boxLayout->addWidget(_iconLabel);
+ _boxLayout->addSpacing(2);
+ _running->hide();
+ }
+ _boxLayout->addWidget(_label);
+#ifdef __APPLE__
+ /* Hack: Fixes a bug where scrollbars would be above the lock icon */
+ _boxLayout->addSpacing(16);
+#endif
+
+ Update(true);
+ setLayout(_boxLayout);
+
+ auto setRunning = [this](bool val) { _macro->SetPaused(!val); };
+ connect(_running, &QAbstractButton::clicked, setRunning);
+ connect(_tree->window(), SIGNAL(HighlightMacrosChanged(bool)), this,
+ SLOT(EnableHighlight(bool)));
+ connect(_tree->window(),
+ SIGNAL(MacroRenamed(const QString &, const QString &)), this,
+ SLOT(MacroRenamed(const QString &, const QString &)));
+ connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightIfExecuted()));
+ connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
+ _timer.start(1500);
+}
+
+void MacroTreeItem::EnableHighlight(bool enable)
+{
+ _highlight = enable;
+}
+
+void MacroTreeItem::UpdatePaused()
+{
+ const QSignalBlocker blocker(_running);
+ _running->setChecked(!_macro->Paused());
+}
+
+void MacroTreeItem::HighlightIfExecuted()
+{
+ if (!_highlight) {
+ // Run check regardless to reset "_wasExecutedRecently"
+ (void)_macro->WasExecutedRecently();
+ return;
+ }
+
+ if (_macro && _macro->WasExecutedRecently()) {
+ PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
+ }
+}
+
+void MacroTreeItem::MacroRenamed(const QString &oldName, const QString &newName)
+{
+ if (_label->text() == oldName) {
+ _label->setText(newName);
+ }
+}
+
+void MacroTreeItem::paintEvent(QPaintEvent *event)
+{
+ QStyleOption opt;
+ opt.initFrom(this);
+ QPainter p(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+
+ QWidget::paintEvent(event);
+}
+
+void MacroTreeItem::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ QWidget::mouseDoubleClickEvent(event);
+ if (_expand) {
+ _expand->setChecked(!_expand->isChecked());
+ }
+}
+
+void MacroTreeItem::Update(bool force)
+{
+ Type newType;
+
+ if (_macro->IsGroup()) {
+ newType = Type::Group;
+ } else if (_macro->IsSubitem()) {
+ newType = Type::SubItem;
+ } else {
+ newType = Type::Item;
+ }
+
+ if (!force && newType == _type) {
+ return;
+ }
+
+ if (_spacer) {
+ _boxLayout->removeItem(_spacer);
+ delete _spacer;
+ _spacer = nullptr;
+ }
+
+ if (_type == Type::Group) {
+ _boxLayout->removeWidget(_expand);
+ _expand->deleteLater();
+ _expand = nullptr;
+ }
+
+ _type = newType;
+ if (_type == Type::SubItem) {
+ _spacer = new QSpacerItem(16, 1);
+ _boxLayout->insertItem(0, _spacer);
+
+ } else if (_type == Type::Group) {
+ _expand = new SourceTreeSubItemCheckBox();
+ _expand->setSizePolicy(QSizePolicy::Maximum,
+ QSizePolicy::Maximum);
+ _expand->setMaximumSize(10, 16);
+ _expand->setMinimumSize(10, 0);
+#ifdef __APPLE__
+ _expand->setAttribute(Qt::WA_LayoutUsesWidgetRect);
+#endif
+ _boxLayout->insertWidget(0, _expand);
+ _expand->blockSignals(true);
+ _expand->setChecked(_macro->IsCollapsed());
+ _expand->blockSignals(false);
+ connect(_expand, &QPushButton::toggled, this,
+ &MacroTreeItem::ExpandClicked);
+ } else {
+ _spacer = new QSpacerItem(3, 1);
+ _boxLayout->insertItem(0, _spacer);
+ }
+ _label->setText(QString::fromStdString(_macro->Name()));
+}
+
+void MacroTreeItem::ExpandClicked(bool checked)
+{
+ if (!checked) {
+ _tree->GetModel()->ExpandGroup(_macro);
+ } else {
+ _tree->GetModel()->CollapseGroup(_macro);
+ }
+}
+
+/* ========================================================================= */
+
+void MacroTreeModel::Reset(std::deque> &newItems)
+{
+ beginResetModel();
+ _macros = newItems;
+ endResetModel();
+
+ UpdateGroupState(false);
+ _mt->ResetWidgets();
+}
+
+static inline int
+ModelIndexToMacroIndex(int modelIdx,
+ const std::deque> ¯os)
+{
+ assert(macros.size() >= modelIdx);
+ int realIdx = 0;
+ const auto &m = macros[0];
+ bool inCollapsedGroup = m->IsGroup() && m->IsCollapsed();
+ uint32_t groupSize = m->GroupSize();
+ for (int i = 0; i < modelIdx; i++) {
+ if (inCollapsedGroup) {
+ realIdx += groupSize;
+ groupSize = 0;
+ inCollapsedGroup = false;
+ }
+ realIdx++;
+ const auto &m = macros.at(realIdx);
+ inCollapsedGroup = m->IsGroup() && m->IsCollapsed();
+ groupSize = m->GroupSize();
+ }
+ return realIdx;
+}
+
+static inline int
+MacroIndexToModelIndex(int realIdx,
+ const std::deque> ¯os)
+{
+ int modelIdx = 0;
+ bool inCollapsedGroup = false;
+ uint32_t groupSize = 0;
+ for (int i = 0; i < realIdx; i++) {
+ if (inCollapsedGroup) {
+ i += groupSize - 1;
+ groupSize = 0;
+ inCollapsedGroup = false;
+ continue;
+ }
+ const auto &m = macros[i];
+ inCollapsedGroup = m->IsGroup() && m->IsCollapsed();
+ groupSize = m->GroupSize();
+ modelIdx++;
+ }
+ return modelIdx;
+}
+
+void MacroTreeModel::MoveItemBefore(const std::shared_ptr &item,
+ const std::shared_ptr &before)
+{
+ if (item == before) {
+ return;
+ }
+
+ auto modelFromEndIdx = GetItemModelIndex(item);
+ auto modelFromIdx = modelFromEndIdx;
+ auto modelTo = GetItemModelIndex(before);
+ auto macroFrom = GetItemMacroIndex(item);
+ auto macroTo = GetItemMacroIndex(before);
+
+ if (before->IsSubitem()) {
+ modelTo -= before->IsCollapsed() ? 0 : before->GroupSize();
+ macroTo -= before->GroupSize();
+ }
+
+ if (!item->IsGroup()) {
+ beginMoveRows(QModelIndex(), modelFromIdx, modelFromIdx,
+ QModelIndex(), modelTo);
+ auto it = std::next(_macros.begin(), macroFrom);
+ std::shared_ptr tmp = *it;
+ _macros.erase(it);
+ _macros.insert(std::next(_macros.begin(), macroTo), tmp);
+ endMoveRows();
+ return;
+ }
+
+ if (!item->IsCollapsed()) {
+ modelFromEndIdx += item->GroupSize();
+ }
+
+ beginMoveRows(QModelIndex(), modelFromIdx, modelFromEndIdx,
+ QModelIndex(), modelTo);
+ for (uint32_t i = 0; i <= item->GroupSize(); i++) {
+ auto it = std::next(_macros.begin(), macroFrom + i);
+ auto tmp = *it;
+ _macros.erase(it);
+ _macros.insert(std::next(_macros.begin(), macroTo + i), tmp);
+ }
+ endMoveRows();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::MoveItemAfter(const std::shared_ptr &item,
+ const std::shared_ptr &after)
+{
+ if (item == after) {
+ return;
+ }
+
+ auto modelFromIdx = GetItemModelIndex(item);
+ auto modelFromEndIdx = modelFromIdx;
+ auto modelTo = GetItemModelIndex(after);
+ auto macroFrom = GetItemMacroIndex(item);
+ auto macroTo = GetItemMacroIndex(after);
+
+ if (after->IsGroup()) {
+ modelTo += after->IsCollapsed() ? 0 : after->GroupSize();
+ macroTo += after->GroupSize();
+ }
+
+ if (!item->IsGroup()) {
+ beginMoveRows(QModelIndex(), modelFromIdx, modelFromEndIdx,
+ QModelIndex(), modelTo + 1);
+ auto it = std::next(_macros.begin(), macroFrom);
+ std::shared_ptr tmp = *it;
+ _macros.erase(it);
+ _macros.insert(std::next(_macros.begin(), macroTo), tmp);
+ endMoveRows();
+ return;
+ }
+
+ if (!item->IsCollapsed()) {
+ modelFromEndIdx += item->GroupSize();
+ }
+
+ beginMoveRows(QModelIndex(), modelFromIdx, modelFromEndIdx,
+ QModelIndex(), modelTo + 1);
+ for (uint32_t i = 0; i <= item->GroupSize(); i++) {
+ auto it = std::next(_macros.begin(), macroFrom);
+ auto tmp = *it;
+ _macros.erase(it);
+ _macros.insert(std::next(_macros.begin(), macroTo), tmp);
+ }
+ endMoveRows();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+static inline int
+CountItemsVisibleInModel(const std::deque> ¯os)
+{
+ int count = macros.size();
+ for (const auto &m : macros) {
+ if (m->IsGroup() && m->IsCollapsed()) {
+ count -= m->GroupSize();
+ }
+ }
+ return count;
+}
+
+void MacroTreeModel::Add(std::shared_ptr item)
+{
+ auto idx = CountItemsVisibleInModel(_macros);
+ beginInsertRows(QModelIndex(), idx, idx);
+ _macros.emplace_back(item);
+ endInsertRows();
+ _mt->UpdateWidget(createIndex(idx, 0, nullptr), item);
+ _mt->selectionModel()->clear();
+ _mt->selectionModel()->select(createIndex(idx, 0, nullptr),
+ QItemSelectionModel::Select);
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::Remove(std::shared_ptr item)
+{
+ auto startIdx = GetItemModelIndex(item);
+ if (startIdx == -1) {
+ return;
+ }
+ auto macroStartIdx = ModelIndexToMacroIndex(startIdx, _macros);
+
+ auto endIdx = startIdx;
+ auto macroEndIdx = macroStartIdx;
+
+ bool isGroup = item->IsGroup();
+ if (isGroup) {
+ macroEndIdx += item->GroupSize();
+ if (!item->IsCollapsed()) {
+ endIdx = item->GroupSize();
+ }
+ }
+
+ beginRemoveRows(QModelIndex(), startIdx, endIdx);
+ _macros.erase(std::next(_macros.begin(), macroStartIdx),
+ std::next(_macros.begin(), macroEndIdx + 1));
+ endRemoveRows();
+
+ _mt->selectionModel()->clear();
+
+ if (isGroup) {
+ UpdateGroupState(true);
+ }
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+std::shared_ptr MacroTreeModel::Neighbor(const std::shared_ptr &m,
+ bool above) const
+{
+ if (!m) {
+ return std::shared_ptr();
+ }
+
+ auto it = std::find(_macros.begin(), _macros.end(), m);
+ if (it == _macros.end()) {
+ return std::shared_ptr();
+ }
+ if (above) {
+ if (it == _macros.begin()) {
+ return std::shared_ptr();
+ }
+ return *std::prev(it, 1);
+ }
+ auto result = std::next(it, 1);
+ if (result == _macros.end()) {
+ return std::shared_ptr();
+ }
+ return *result;
+}
+
+std::shared_ptr
+MacroTreeModel::FindEndOfGroup(const std::shared_ptr &m,
+ bool above) const
+{
+ auto endOfGroup = Neighbor(m, above);
+ if (!endOfGroup) {
+ return m;
+ }
+
+ if (above) {
+ while (!endOfGroup->IsGroup()) {
+ endOfGroup = Neighbor(endOfGroup, above);
+ }
+ } else {
+ while (endOfGroup->IsSubitem() &&
+ GetItemMacroIndex(endOfGroup) + 1 !=
+ (int)_macros.size()) {
+ endOfGroup = Neighbor(endOfGroup, above);
+ }
+ if (!endOfGroup->IsSubitem()) {
+ endOfGroup = Neighbor(endOfGroup, !above);
+ }
+ }
+
+ return endOfGroup;
+}
+
+std::shared_ptr MacroTreeModel::GetCurrentMacro() const
+{
+ auto sel = _mt->selectionModel()->selection();
+ if (sel.empty()) {
+ return std::shared_ptr();
+ }
+ auto idx = sel.indexes().back().row();
+ if (idx >= (int)_macros.size()) {
+ return std::shared_ptr();
+ }
+ return _macros[ModelIndexToMacroIndex(idx, _macros)];
+}
+
+MacroTreeModel::MacroTreeModel(MacroTree *st_,
+ std::deque> ¯os)
+ : QAbstractListModel(st_), _mt(st_), _macros(macros)
+{
+ UpdateGroupState(false);
+}
+
+int MacroTreeModel::rowCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : CountItemsVisibleInModel(_macros);
+}
+
+QVariant MacroTreeModel::data(const QModelIndex &index, int role) const
+{
+ if (role == Qt::AccessibleTextRole) {
+ std::shared_ptr item =
+ _macros[ModelIndexToMacroIndex(index.row(), _macros)];
+ if (!item) {
+ return QVariant();
+ }
+ return QVariant(QString::fromStdString(item->Name()));
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags MacroTreeModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid()) {
+ return QAbstractListModel::flags(index) | Qt::ItemIsDropEnabled;
+ }
+
+ std::shared_ptr item =
+ _macros[ModelIndexToMacroIndex(index.row(), _macros)];
+ bool isGroup = item->IsGroup();
+
+ return QAbstractListModel::flags(index) | Qt::ItemIsEditable |
+ Qt::ItemIsDragEnabled |
+ (isGroup ? Qt::ItemIsDropEnabled : Qt::NoItemFlags);
+}
+
+Qt::DropActions MacroTreeModel::supportedDropActions() const
+{
+ return QAbstractItemModel::supportedDropActions() | Qt::MoveAction;
+}
+
+QString MacroTreeModel::GetNewGroupName()
+{
+ QString fmt =
+ obs_module_text("AdvSceneSwitcher.macroTab.defaultGroupName");
+ QString name = fmt.arg("1");
+
+ int i = 2;
+ for (;;) {
+ if (!GetMacroByQString(name)) {
+ break;
+ }
+ name = fmt.arg(QString::number(i++));
+ }
+
+ return name;
+}
+
+int MacroTreeModel::GetItemMacroIndex(const std::shared_ptr &item) const
+{
+ auto it = std::find(_macros.begin(), _macros.end(), item);
+ if (it == _macros.end()) {
+ return -1;
+ }
+
+ return it - _macros.begin();
+}
+
+int MacroTreeModel::GetItemModelIndex(const std::shared_ptr &item) const
+{
+ return MacroIndexToModelIndex(GetItemMacroIndex(item), _macros);
+}
+
+bool MacroTreeModel::IsLastItem(std::shared_ptr item) const
+{
+ return GetItemModelIndex(item) + 1 == (int)_macros.size();
+}
+
+void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
+{
+ if (indices.count() == 0) {
+ return;
+ }
+
+ QString name = GetNewGroupName();
+ std::vector> items;
+ items.reserve(indices.size());
+ for (int i = 0; i < indices.count(); i++) {
+ std::shared_ptr item = _macros[ModelIndexToMacroIndex(
+ indices[i].row(), _macros)];
+ items.emplace_back(item);
+ }
+ std::shared_ptr item =
+ Macro::CreateGroup(name.toStdString(), items);
+ if (!item) {
+ return;
+ }
+
+ // A new list entry for group
+ Add(item);
+
+ // Move all selected items after new group entry
+ for (const auto &item : items) {
+ auto it = _macros.begin() + GetItemMacroIndex(item);
+ std::rotate(it, std::next(it, 1), _macros.end());
+ }
+
+ _hasGroups = true;
+ _mt->selectionModel()->clear();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+
+ Reset(_macros);
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
+{
+ if (indices.count() == 0) {
+ return;
+ }
+
+ for (int i = indices.count() - 1; i >= 0; i--) {
+ std::shared_ptr item = _macros[ModelIndexToMacroIndex(
+ indices[i].row(), _macros)];
+ if (item->IsGroup()) {
+ Macro::RemoveGroup(item);
+ }
+ }
+
+ _mt->selectionModel()->clear();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+
+ Reset(_macros);
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::ExpandGroup(std::shared_ptr item)
+{
+ auto idx = GetItemModelIndex(item);
+ if (idx == -1 || !item->IsGroup() || !item->GroupSize() ||
+ !item->IsCollapsed()) {
+ return;
+ }
+
+ item->SetCollapsed(false);
+ Reset(_macros);
+
+ _mt->selectionModel()->clear();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::CollapseGroup(std::shared_ptr item)
+{
+ auto idx = GetItemModelIndex(item);
+ if (idx == -1 || !item->IsGroup() || !item->GroupSize() ||
+ item->IsCollapsed()) {
+ return;
+ }
+
+ item->SetCollapsed(true);
+ Reset(_macros);
+
+ _mt->selectionModel()->clear();
+
+ for (auto &m : _macros) {
+ m->ResolveMacroRef();
+ }
+}
+
+void MacroTreeModel::UpdateGroupState(bool update)
+{
+ bool nowHasGroups = false;
+ for (auto &item : _macros) {
+ if (item->IsGroup()) {
+ nowHasGroups = true;
+ break;
+ }
+ }
+
+ if (nowHasGroups != _hasGroups) {
+ _hasGroups = nowHasGroups;
+ if (update) {
+ _mt->UpdateWidgets(true);
+ }
+ }
+}
+
+void MacroTree::Reset(std::deque> ¯os,
+ bool highlight)
+{
+ _highlight = highlight;
+ MacroTreeModel *mtm = new MacroTreeModel(this, macros);
+ setModel(mtm);
+ GetModel()->Reset(macros);
+}
+
+void MacroTree::Add(std::shared_ptr item) const
+{
+ GetModel()->Add(item);
+}
+
+std::shared_ptr MacroTree::GetCurrentMacro() const
+{
+ return GetModel()->GetCurrentMacro();
+}
+
+MacroTree::MacroTree(QWidget *parent_) : QListView(parent_)
+{
+ setStyleSheet(QString(
+ "*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}"
+ "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}"
+ "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}"
+ "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}"
+ "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}"
+ "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}"
+ "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}"
+ "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));
+
+ setItemDelegate(new MacroTreeDelegate(this));
+}
+
+void MacroTree::ResetWidgets()
+{
+ MacroTreeModel *mtm = GetModel();
+ mtm->UpdateGroupState(false);
+ int modelIdx = 0;
+ for (int i = 0; i < (int)mtm->_macros.size(); i++) {
+ QModelIndex index = mtm->createIndex(modelIdx, 0, nullptr);
+ const auto ¯o = mtm->_macros[i];
+ setIndexWidget(index,
+ new MacroTreeItem(this, macro, _highlight));
+
+ // Skip items of collapsed groups
+ if (macro->IsGroup() && macro->IsCollapsed()) {
+ i += macro->GroupSize();
+ }
+ modelIdx++;
+ }
+}
+
+void MacroTree::UpdateWidget(const QModelIndex &idx,
+ std::shared_ptr item)
+{
+ setIndexWidget(idx, new MacroTreeItem(this, item, _highlight));
+}
+
+void MacroTree::UpdateWidgets(bool force)
+{
+ MacroTreeModel *mtm = GetModel();
+
+ for (int i = 0; i < (int)mtm->_macros.size(); i++) {
+ std::shared_ptr item = mtm->_macros[i];
+ MacroTreeItem *widget = GetItemWidget(i);
+
+ if (!widget) {
+ UpdateWidget(mtm->createIndex(i, 0, nullptr), item);
+ } else {
+ widget->Update(force);
+ }
+ // Skip items of collapsed groups
+ if (item->IsGroup() && item->IsCollapsed()) {
+ i += item->GroupSize();
+ }
+ }
+}
+
+Q_DECLARE_METATYPE(std::shared_ptr);
+
+static inline void MoveItem(std::deque> &items,
+ std::shared_ptr &item, int to)
+{
+ auto it = std::find(items.begin(), items.end(), item);
+ if (it == items.end()) {
+ blog(LOG_ERROR,
+ "something went wrong during drag & drop reordering");
+ return;
+ }
+
+ items.erase(it);
+ items.insert(std::next(items.begin(), to), item);
+}
+
+// Logic mostly based on OBS's source-tree dropEvent() with a few modifications
+// regarding group handling
+void MacroTree::dropEvent(QDropEvent *event)
+{
+ if (event->source() != this) {
+ QListView::dropEvent(event);
+ return;
+ }
+
+ MacroTreeModel *mtm = GetModel();
+ auto &items = mtm->_macros;
+ QModelIndexList indices = selectedIndexes();
+
+ DropIndicatorPosition indicator = dropIndicatorPosition();
+ int row = indexAt(
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ event->position().toPoint()
+#else
+ event->pos()
+#endif
+ )
+ .row();
+ bool emptyDrop = row == -1;
+
+ if (emptyDrop) {
+ if (items.empty()) {
+ QListView::dropEvent(event);
+ return;
+ }
+
+ row = mtm->rowCount(QModelIndex()) - 1;
+ indicator = QAbstractItemView::BelowItem;
+ }
+
+ // Store destination group if moving to a group
+ Macro *dropItem = items[ModelIndexToMacroIndex(row, items)]
+ .get(); // Item being dropped on
+ bool itemIsGroup = dropItem->IsGroup();
+ Macro *dropGroup = itemIsGroup ? dropItem : dropItem->Parent();
+
+ // Not a group if moving above the group
+ if (indicator == QAbstractItemView::AboveItem && itemIsGroup) {
+ dropGroup = nullptr;
+ }
+ if (emptyDrop) {
+ dropGroup = nullptr;
+ }
+
+ // Remember to remove list items if dropping on collapsed group
+ bool dropOnCollapsed = false;
+ if (dropGroup) {
+ dropOnCollapsed = dropGroup->IsCollapsed();
+ }
+
+ if (indicator == QAbstractItemView::BelowItem ||
+ indicator == QAbstractItemView::OnItem ||
+ indicator == QAbstractItemView::OnViewport)
+ row++;
+
+ if (row < 0 || row > (int)items.size()) {
+ QListView::dropEvent(event);
+ return;
+ }
+
+ // Determine if any base group is selected
+ bool hasGroups = false;
+ for (int i = 0; i < indices.size(); i++) {
+ std::shared_ptr item =
+ items[ModelIndexToMacroIndex(indices[i].row(), items)];
+ if (item->IsGroup()) {
+ hasGroups = true;
+ break;
+ }
+ }
+
+ // If dropping a group, detect if it's below another group
+ std::shared_ptr itemBelow;
+ if (row == (int)items.size()) {
+ itemBelow = nullptr;
+ } else {
+ itemBelow = items[MacroIndexToModelIndex(row, items)];
+ }
+
+ if (hasGroups) {
+ if (!itemBelow || itemBelow->Parent() != dropGroup) {
+ dropGroup = nullptr;
+ dropOnCollapsed = false;
+ }
+ }
+
+ // If dropping groups on other groups, disregard as invalid drag/drop
+ if (dropGroup && hasGroups) {
+ QListView::dropEvent(event);
+ return;
+ }
+
+ // If selection includes base group items, include all group sub-items and
+ // treat them all as one
+ //
+ // Also gather list of (sub)items to move in backend
+ std::vector> subItemsToMove;
+ QModelIndexList indicesWithoutSubitems = indices;
+
+ if (hasGroups) {
+ // Remove sub-items if selected
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ std::shared_ptr item =
+ items[ModelIndexToMacroIndex(indices[i].row(),
+ items)];
+ auto parent = item->Parent();
+ if (parent != nullptr) {
+ indices.removeAt(i);
+ }
+ }
+ indicesWithoutSubitems = indices;
+
+ // Add all sub-items of selected groups
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ std::shared_ptr item =
+ items[ModelIndexToMacroIndex(indices[i].row(),
+ items)];
+ if (!item->IsGroup()) {
+ continue;
+ }
+ bool collapsed = item->IsCollapsed();
+ for (int j = items.size() - 1; j >= 0; j--) {
+ std::shared_ptr subitem = items[j];
+ auto subitemGroup = subitem->Parent();
+
+ if (subitemGroup == item.get()) {
+ if (!collapsed) {
+ QModelIndex idx = mtm->createIndex(
+ MacroIndexToModelIndex(
+ j, items),
+ 0, nullptr);
+ indices.insert(i + 1, idx);
+ }
+ subItemsToMove.emplace_back(subitem);
+ }
+ }
+ }
+ }
+
+ // Build persistent indices
+ QList persistentIndices;
+ persistentIndices.reserve(indices.count());
+ for (QModelIndex &index : indices) {
+ persistentIndices.append(index);
+ }
+ std::sort(persistentIndices.begin(), persistentIndices.end());
+
+ // Prepare items to move in backend
+ std::sort(indicesWithoutSubitems.begin(), indicesWithoutSubitems.end());
+ std::vector> itemsToMove;
+ for (const auto &subitemsIdx : indicesWithoutSubitems) {
+ auto idx = ModelIndexToMacroIndex(subitemsIdx.row(), items);
+ itemsToMove.emplace_back(items[idx]);
+ }
+
+ // Move all items to destination index
+ int r = row;
+ for (auto &persistentIdx : persistentIndices) {
+ int from = persistentIdx.row();
+ int to = r;
+ int itemTo = to;
+
+ if (itemTo > from) {
+ itemTo--;
+ }
+
+ if (itemTo != from) {
+ mtm->beginMoveRows(QModelIndex(), from, from,
+ QModelIndex(), to);
+ mtm->endMoveRows();
+ }
+
+ r = persistentIdx.row() + 1;
+ }
+
+ std::sort(persistentIndices.begin(), persistentIndices.end());
+ int firstIdx = persistentIndices.front().row();
+ int lastIdx = persistentIndices.back().row();
+
+ // Remove items if dropped in to collapsed group
+ if (dropOnCollapsed) {
+ mtm->beginRemoveRows(QModelIndex(), firstIdx, lastIdx);
+ mtm->endRemoveRows();
+ }
+
+ // Move items in backend
+ int to = row;
+ try {
+ to = ModelIndexToMacroIndex(row, items);
+ } catch (std::out_of_range const &) {
+ to = items.size();
+ }
+ auto prev = *itemsToMove.rbegin();
+ auto curIdx = mtm->GetItemMacroIndex(prev);
+ int toIdx = to;
+ if (toIdx > curIdx) {
+ toIdx--;
+ }
+ Macro::PrepareMoveToGroup(dropGroup, prev);
+ MoveItem(items, prev, toIdx);
+ for (auto it = std::next(itemsToMove.rbegin(), 1);
+ it != itemsToMove.rend(); ++it) {
+ auto &item = *it;
+ auto curIdx = mtm->GetItemMacroIndex(item);
+ int toIdx = mtm->GetItemMacroIndex(prev);
+ if (toIdx > curIdx) {
+ toIdx--;
+ }
+ Macro::PrepareMoveToGroup(dropGroup, item);
+ MoveItem(items, item, toIdx);
+ prev = item;
+ }
+
+ // Move subitems of groups
+ for (const auto &i : subItemsToMove) {
+ auto removeIt = std::find(items.begin(), items.end(), i);
+ if (removeIt == items.end()) {
+ blog(LOG_ERROR, "Cannot move subitem '%s'",
+ i->Name().c_str());
+ continue;
+ }
+ items.erase(removeIt);
+ auto targetName = i->Parent()->Name();
+ auto GroupNameMatches = [targetName](std::shared_ptr m) {
+ return m->Name() == targetName;
+ };
+ auto it = std::find_if(items.begin(), items.end(),
+ GroupNameMatches);
+ items.insert(std::next(it, 1), i);
+ }
+
+ for (auto &m : items) {
+ m->ResolveMacroRef();
+ }
+
+ // Update widgets and accept event
+ UpdateWidgets(true);
+ event->accept();
+ event->setDropAction(Qt::CopyAction);
+
+ QListView::dropEvent(event);
+}
+
+bool MacroTree::GroupsSelected() const
+{
+ MacroTreeModel *mtm = GetModel();
+ QModelIndexList selectedIndices = selectedIndexes();
+
+ if (SelectionEmpty()) {
+ return false;
+ }
+
+ for (auto &idx : selectedIndices) {
+ std::shared_ptr item =
+ mtm->_macros[ModelIndexToMacroIndex(idx.row(),
+ mtm->_macros)];
+ if (item->IsGroup()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MacroTree::GroupedItemsSelected() const
+{
+ MacroTreeModel *mtm = GetModel();
+ QModelIndexList selectedIndices = selectedIndexes();
+
+ if (SelectionEmpty()) {
+ return false;
+ }
+
+ for (auto &idx : selectedIndices) {
+ std::shared_ptr item =
+ mtm->_macros[ModelIndexToMacroIndex(idx.row(),
+ mtm->_macros)];
+ auto parent = item->Parent();
+ if (parent) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MacroTree::SelectionEmpty() const
+{
+ return selectedIndexes().empty();
+}
+
+void MacroTree::MoveItemBefore(const std::shared_ptr &item,
+ const std::shared_ptr &after) const
+{
+ GetModel()->MoveItemBefore(item, after);
+}
+
+void MacroTree::MoveItemAfter(const std::shared_ptr &item,
+ const std::shared_ptr &after) const
+{
+ GetModel()->MoveItemAfter(item, after);
+}
+
+MacroTreeModel *MacroTree::GetModel() const
+{
+ return reinterpret_cast(model());
+}
+
+void MacroTree::Remove(std::shared_ptr item) const
+{
+ GetModel()->Remove(item);
+}
+
+void MacroTree::Up(std::shared_ptr item) const
+{
+ auto above = GetModel()->Neighbor(item, true);
+ if (!above) {
+ return;
+ }
+
+ if (item->IsSubitem()) {
+ // Nowhere to move to, when the item above is a group or
+ // regular entry
+ if (!above->IsSubitem()) {
+ return;
+ }
+
+ MoveItemBefore(item, above);
+ return;
+ }
+
+ if (above->IsSubitem()) {
+ above = GetModel()->FindEndOfGroup(above, true);
+ }
+
+ MoveItemBefore(item, above);
+}
+
+void MacroTree::Down(std::shared_ptr item) const
+{
+ auto below = GetModel()->Neighbor(item, false);
+ if (!below) {
+ return;
+ }
+
+ if (item->IsSubitem()) {
+ // Nowhere to move to, when the item below is a group or
+ // regular entry
+ if (!below->IsSubitem()) {
+ return;
+ }
+
+ MoveItemAfter(item, below);
+ return;
+ }
+
+ if (item->IsGroup()) {
+ if (below->IsSubitem()) {
+ below = GetModel()->FindEndOfGroup(below, false);
+
+ // Nowhere to move group to
+ if (GetModel()->IsLastItem(below)) {
+ return;
+ }
+
+ below = GetModel()->Neighbor(below, false);
+ }
+ MoveItemAfter(item, below);
+ return;
+ }
+
+ MoveItemAfter(item, below);
+}
+
+void MacroTree::GroupSelectedItems()
+{
+ QModelIndexList indices = selectedIndexes();
+ std::sort(indices.begin(), indices.end());
+ GetModel()->GroupSelectedItems(indices);
+}
+
+void MacroTree::UngroupSelectedGroups()
+{
+ QModelIndexList indices = selectedIndexes();
+ GetModel()->UngroupSelectedGroups(indices);
+}
+
+inline MacroTreeItem *MacroTree::GetItemWidget(int idx) const
+{
+ QWidget *widget = indexWidget(GetModel()->createIndex(idx, 0, nullptr));
+ return reinterpret_cast(widget);
+}
+
+void MacroTree::paintEvent(QPaintEvent *event)
+{
+ MacroTreeModel *mtm = GetModel();
+ if (mtm && mtm->_macros.empty()) {
+ QPainter painter(viewport());
+ const QRect rectangle =
+ QRect(0, 0, size().width(), size().height());
+ QRect boundingRect;
+ QString text(obs_module_text("AdvSceneSwitcher.macroTab.help"));
+ painter.drawText(rectangle, Qt::AlignCenter | Qt::TextWordWrap,
+ text, &boundingRect);
+ } else {
+ QListView::paintEvent(event);
+ }
+}
+
+MacroTreeDelegate::MacroTreeDelegate(QObject *parent)
+ : QStyledItemDelegate(parent)
+{
+}
+
+QSize MacroTreeDelegate::sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ MacroTree *tree = qobject_cast(parent());
+ QWidget *item = tree->indexWidget(index);
+
+ if (!item) {
+ return (QSize(0, 0));
+ }
+
+ return (QSize(option.widget->minimumWidth(), item->height()));
+}
diff --git a/src/macro-core/macro-tree.hpp b/src/macro-core/macro-tree.hpp
new file mode 100644
index 00000000..1eaa9967
--- /dev/null
+++ b/src/macro-core/macro-tree.hpp
@@ -0,0 +1,160 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+class Macro;
+
+class QLabel;
+class MacroTree;
+class QSpacerItem;
+class QHBoxLayout;
+
+class MacroTreeItem : public QFrame {
+ Q_OBJECT
+
+public:
+ explicit MacroTreeItem(MacroTree *tree, std::shared_ptr macro,
+ bool highlight);
+
+private slots:
+ void ExpandClicked(bool checked);
+ void EnableHighlight(bool enable);
+ void UpdatePaused();
+ void HighlightIfExecuted();
+ void MacroRenamed(const QString &, const QString &);
+
+private:
+ virtual void paintEvent(QPaintEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void Update(bool force);
+
+ enum class Type {
+ Unknown,
+ Item,
+ Group,
+ SubItem,
+ };
+
+ Type _type = Type::Unknown;
+
+ QSpacerItem *_spacer = nullptr;
+ QCheckBox *_expand = nullptr;
+ QLabel *_iconLabel = nullptr;
+ QCheckBox *_running = nullptr;
+ QHBoxLayout *_boxLayout = nullptr;
+ QLabel *_label = nullptr;
+ MacroTree *_tree;
+ bool _highlight;
+ QTimer _timer;
+ std::shared_ptr _macro;
+
+ friend class MacroTree;
+ friend class MacroTreeModel;
+};
+
+// Only used to enable applying "SourceTreeSubItemCheckBox" stylesheet
+class SourceTreeSubItemCheckBox : public QCheckBox {
+ Q_OBJECT
+};
+
+class MacroTreeModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ explicit MacroTreeModel(MacroTree *st,
+ std::deque> ¯os);
+ ~MacroTreeModel() = default;
+ virtual int rowCount(const QModelIndex &parent) const override;
+ virtual QVariant data(const QModelIndex &index,
+ int role) const override;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+ virtual Qt::DropActions supportedDropActions() const override;
+
+private:
+ void Reset(std::deque> &);
+ void MoveItemBefore(const std::shared_ptr &item,
+ const std::shared_ptr &before);
+ void MoveItemAfter(const std::shared_ptr &item,
+ const std::shared_ptr &after);
+ void Add(std::shared_ptr item);
+ void Remove(std::shared_ptr item);
+ std::shared_ptr Neighbor(const std::shared_ptr &m,
+ bool above) const;
+ std::shared_ptr FindEndOfGroup(const std::shared_ptr &m,
+ bool above) const;
+ std::shared_ptr GetCurrentMacro() const;
+ QString GetNewGroupName();
+ void GroupSelectedItems(QModelIndexList &indices);
+ void UngroupSelectedGroups(QModelIndexList &indices);
+ void ExpandGroup(std::shared_ptr item);
+ void CollapseGroup(std::shared_ptr item);
+ void UpdateGroupState(bool update);
+ int GetItemMacroIndex(const std::shared_ptr &item) const;
+ int GetItemModelIndex(const std::shared_ptr &item) const;
+ bool IsLastItem(std::shared_ptr item) const;
+
+ MacroTree *_mt;
+ std::deque> &_macros;
+ bool _hasGroups = false;
+
+ friend class MacroTree;
+ friend class MacroTreeItem;
+};
+
+class MacroTree : public QListView {
+ Q_OBJECT
+
+public:
+ explicit MacroTree(QWidget *parent = nullptr);
+ void Reset(std::deque> &, bool highlight);
+ void Add(std::shared_ptr item) const;
+ void Remove(std::shared_ptr item) const;
+ void Up(std::shared_ptr item) const;
+ void Down(std::shared_ptr item) const;
+ std::shared_ptr GetCurrentMacro() const;
+ bool GroupsSelected() const;
+ bool GroupedItemsSelected() const;
+ bool SelectionEmpty() const;
+
+public slots:
+ void GroupSelectedItems();
+ void UngroupSelectedGroups();
+
+protected:
+ virtual void dropEvent(QDropEvent *event) override;
+ virtual void paintEvent(QPaintEvent *event) override;
+
+private:
+ MacroTreeItem *GetItemWidget(int idx) const;
+ void ResetWidgets();
+ void UpdateWidget(const QModelIndex &idx, std::shared_ptr item);
+ void UpdateWidgets(bool force = false);
+ void MoveItemBefore(const std::shared_ptr &item,
+ const std::shared_ptr &after) const;
+ void MoveItemAfter(const std::shared_ptr &item,
+ const std::shared_ptr &after) const;
+ MacroTreeModel *GetModel() const;
+
+ bool _highlight = false;
+
+ friend class MacroTreeModel;
+ friend class MacroTreeItem;
+};
+
+class MacroTreeDelegate : public QStyledItemDelegate {
+ Q_OBJECT
+
+public:
+ MacroTreeDelegate(QObject *parent);
+ virtual QSize sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
diff --git a/src/macro-core/macro.cpp b/src/macro-core/macro.cpp
index 8c0d82a9..367fe2c1 100644
--- a/src/macro-core/macro.cpp
+++ b/src/macro-core/macro.cpp
@@ -28,8 +28,72 @@ Macro::~Macro()
ClearHotkeys();
}
+std::shared_ptr
+Macro::CreateGroup(const std::string &name,
+ std::vector> &children)
+{
+ auto group = std::make_shared(name, false);
+ for (auto &c : children) {
+ c->SetParent(group.get());
+ }
+ group->_isGroup = true;
+ group->_groupSize = children.size();
+ return group;
+}
+
+void Macro::RemoveGroup(std::shared_ptr group)
+{
+ auto it = std::find(switcher->macros.begin(), switcher->macros.end(),
+ group);
+ if (it == switcher->macros.end()) {
+ return;
+ }
+
+ auto size = group->GroupSize();
+ for (uint32_t i = 1; i <= size; i++) {
+ auto m = std::next(it, i);
+ (*m)->SetParent(nullptr);
+ }
+
+ switcher->macros.erase(it);
+}
+
+void Macro::PrepareMoveToGroup(Macro *group, std::shared_ptr item)
+{
+ for (auto &m : switcher->macros) {
+ if (m.get() == group) {
+ PrepareMoveToGroup(m, item);
+ return;
+ }
+ }
+ PrepareMoveToGroup(std::shared_ptr(), item);
+}
+
+void Macro::PrepareMoveToGroup(std::shared_ptr group,
+ std::shared_ptr item)
+{
+ if (!item) {
+ return;
+ }
+
+ // Potentially remove from old group
+ auto oldGroup = item->Parent();
+ if (oldGroup) {
+ oldGroup->_groupSize--;
+ }
+
+ item->SetParent(group.get());
+ if (group) {
+ group->_groupSize++;
+ }
+}
+
bool Macro::CeckMatch()
{
+ if (_isGroup) {
+ return false;
+ }
+
_matched = false;
for (auto &c : _conditions) {
if (_paused) {
@@ -140,6 +204,9 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
RunActions(ret, ignorePause);
}
_wasExecutedRecently = true;
+ if (_parent) {
+ _parent->_wasExecutedRecently = true;
+ }
return ret;
}
@@ -253,6 +320,16 @@ bool Macro::Save(obs_data_t *obj) const
obs_data_set_bool(obj, "parallel", _runInParallel);
obs_data_set_bool(obj, "onChange", _matchOnChange);
+ obs_data_set_bool(obj, "group", _isGroup);
+ if (_isGroup) {
+ auto groupData = obs_data_create();
+ obs_data_set_bool(groupData, "collapsed", _isCollapsed);
+ obs_data_set_int(groupData, "size", _groupSize);
+ obs_data_set_obj(obj, "groupData", groupData);
+ obs_data_release(groupData);
+ return true;
+ }
+
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey);
obs_data_set_array(obj, "pauseHotkey", pauseHotkey);
@@ -333,6 +410,15 @@ bool Macro::Load(obs_data_t *obj)
_runInParallel = obs_data_get_bool(obj, "parallel");
_matchOnChange = obs_data_get_bool(obj, "onChange");
+ _isGroup = obs_data_get_bool(obj, "group");
+ if (_isGroup) {
+ auto groupData = obs_data_get_obj(obj, "groupData");
+ _isCollapsed = obs_data_get_bool(groupData, "collapsed");
+ _groupSize = obs_data_get_int(groupData, "size");
+ obs_data_release(groupData);
+ return true;
+ }
+
obs_data_set_default_bool(obj, "registerHotkeys", true);
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
if (_registerHotkeys) {
@@ -610,8 +696,18 @@ void SwitcherData::loadMacros(obs_data_t *obj)
}
obs_data_array_release(macroArray);
+ int groupCount = 0;
+ Macro *group = nullptr;
for (auto &m : macros) {
m->ResolveMacroRef();
+ if (groupCount) {
+ m->SetParent(group);
+ groupCount--;
+ }
+ if (m->IsGroup()) {
+ groupCount = m->GroupSize();
+ group = m.get();
+ }
}
}
diff --git a/src/macro-core/macro.hpp b/src/macro-core/macro.hpp
index bdef3214..3fd18c87 100644
--- a/src/macro-core/macro.hpp
+++ b/src/macro-core/macro.hpp
@@ -18,7 +18,6 @@ class Macro {
public:
Macro(const std::string &name = "", const bool addHotkey = false);
virtual ~Macro();
-
bool CeckMatch();
bool PerformActions(bool forceParallel = false,
bool ignorePause = false);
@@ -41,9 +40,26 @@ public:
{
return _conditions;
}
+ std::deque> &Actions() { return _actions; }
void UpdateActionIndices();
void UpdateConditionIndices();
- std::deque> &Actions() { return _actions; }
+
+ // Group controls
+ static std::shared_ptr
+ CreateGroup(const std::string &name,
+ std::vector> &children);
+ static void RemoveGroup(std::shared_ptr);
+ static void PrepareMoveToGroup(Macro *group,
+ std::shared_ptr item);
+ static void PrepareMoveToGroup(std::shared_ptr group,
+ std::shared_ptr item);
+ bool IsGroup() { return _isGroup; }
+ uint32_t GroupSize() { return _groupSize; }
+ bool IsSubitem() { return !!_parent; }
+ void SetCollapsed(bool val) { _isCollapsed = val; }
+ bool IsCollapsed() { return _isCollapsed; }
+ void SetParent(Macro *m) { _parent = m; }
+ Macro *Parent() { return _parent; }
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -75,6 +91,10 @@ private:
std::string _name = "";
std::deque> _conditions;
std::deque> _actions;
+
+ Macro *_parent = nullptr;
+ uint32_t _groupSize = 0;
+
bool _runInParallel = false;
bool _matched = false;
bool _lastMatched = false;
@@ -87,6 +107,8 @@ private:
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
// UI helpers for the macro tab
+ bool _isGroup = false;
+ bool _isCollapsed = false;
bool _wasExecutedRecently = false;
bool _onChangeTriggered = false;