mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-10-25 10:02:41 -05:00
Support nested macro groups in undo / redo and import / export
This commit is contained in:
@@ -180,6 +180,114 @@ bool IsMacroInGroup(const Macro *macro, const Macro *group)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
GetOutermostMacros(const std::vector<std::shared_ptr<Macro>> ¯os)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> result;
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
const bool isGiven = std::find(macros.begin(), macros.end(),
|
||||
macro) != macros.end();
|
||||
const bool isInGivenGroup = std::any_of(
|
||||
macros.begin(), macros.end(),
|
||||
[¯o](const std::shared_ptr<Macro> &group) {
|
||||
return IsMacroInGroup(macro.get(), group.get());
|
||||
});
|
||||
if (isGiven && !isInGivenGroup) {
|
||||
result.emplace_back(macro);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
GetMacrosWithGroupContents(const std::vector<std::shared_ptr<Macro>> ¯os)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> result;
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
const bool include = std::any_of(
|
||||
macros.begin(), macros.end(),
|
||||
[¯o](const std::shared_ptr<Macro> &given) {
|
||||
return given == macro ||
|
||||
IsMacroInGroup(macro.get(), given.get());
|
||||
});
|
||||
if (include) {
|
||||
result.emplace_back(macro);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::deque<std::shared_ptr<Macro>>::iterator
|
||||
subtreeEnd(std::deque<std::shared_ptr<Macro>> ¯os,
|
||||
std::deque<std::shared_ptr<Macro>>::iterator it)
|
||||
{
|
||||
const auto remaining = std::distance(it, macros.end());
|
||||
const auto size =
|
||||
std::min<std::ptrdiff_t>(1 + (*it)->GroupSize(), remaining);
|
||||
return std::next(it, size);
|
||||
}
|
||||
|
||||
void InsertMacroSubtree(int index,
|
||||
const std::deque<std::shared_ptr<Macro>> &subtree,
|
||||
const std::shared_ptr<Macro> &parent)
|
||||
{
|
||||
if (subtree.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
Macro::PrepareMoveToGroup(parent, subtree.front());
|
||||
index = std::clamp(index, 0, (int)macros.size());
|
||||
macros.insert(macros.begin() + index, subtree.begin(), subtree.end());
|
||||
}
|
||||
|
||||
void RemoveMacroSubtree(const std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
auto it = std::find(macros.begin(), macros.end(), macro);
|
||||
if (it == macros.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto end = subtreeEnd(macros, it);
|
||||
Macro::PrepareMoveToGroup(nullptr, macro);
|
||||
macros.erase(it, end);
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro>
|
||||
InsertGroup(const std::string &name, int index,
|
||||
std::vector<std::shared_ptr<Macro>> &children,
|
||||
const std::shared_ptr<Macro> &parent)
|
||||
{
|
||||
auto group = Macro::CreateGroup(name, children);
|
||||
if (children.empty()) {
|
||||
Macro::PrepareMoveToGroup(parent, group);
|
||||
}
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
index = std::clamp(index, 0, (int)macros.size());
|
||||
macros.insert(macros.begin() + index, group);
|
||||
|
||||
auto previous = group;
|
||||
for (const auto &child : children) {
|
||||
auto start = std::find(macros.begin(), macros.end(), child);
|
||||
if (start == macros.end()) {
|
||||
continue;
|
||||
}
|
||||
const auto end = subtreeEnd(macros, start);
|
||||
const std::vector<std::shared_ptr<Macro>> subtree(start, end);
|
||||
macros.erase(start, end);
|
||||
|
||||
auto position =
|
||||
std::find(macros.begin(), macros.end(), previous);
|
||||
position = previous == group ? std::next(position)
|
||||
: subtreeEnd(macros, position);
|
||||
macros.insert(position, subtree.begin(), subtree.end());
|
||||
previous = child;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
std::condition_variable &GetMacroWaitCV()
|
||||
{
|
||||
static std::condition_variable cv;
|
||||
|
||||
@@ -7,11 +7,14 @@
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
struct obs_data;
|
||||
typedef struct obs_data obs_data_t;
|
||||
struct obs_data_array;
|
||||
typedef struct obs_data_array obs_data_array_t;
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -42,6 +45,27 @@ EXPORT std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *);
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupChildren(const Macro *group);
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupDescendants(const Macro *group);
|
||||
bool IsMacroInGroup(const Macro *macro, const Macro *group);
|
||||
// Returns the given macros without the ones contained in one of the given
|
||||
// groups, sorted by their position in the macro list
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
GetOutermostMacros(const std::vector<std::shared_ptr<Macro>> ¯os);
|
||||
// Returns the given macros and the contents of the given groups, sorted by
|
||||
// their position in the macro list
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
GetMacrosWithGroupContents(const std::vector<std::shared_ptr<Macro>> ¯os);
|
||||
// Inserts the macro and its group contents at the given position of the macro
|
||||
// list
|
||||
void InsertMacroSubtree(int index,
|
||||
const std::deque<std::shared_ptr<Macro>> &subtree,
|
||||
const std::shared_ptr<Macro> &parent);
|
||||
// Removes the macro and its group contents from the macro list
|
||||
void RemoveMacroSubtree(const std::shared_ptr<Macro> ¯o);
|
||||
// Creates a group at the given position of the macro list and moves the given
|
||||
// macros and their group contents into it
|
||||
std::shared_ptr<Macro>
|
||||
InsertGroup(const std::string &name, int index,
|
||||
std::vector<std::shared_ptr<Macro>> &children,
|
||||
const std::shared_ptr<Macro> &parent);
|
||||
|
||||
EXPORT std::condition_variable &GetMacroWaitCV();
|
||||
EXPORT std::condition_variable &GetMacroTransitionCV();
|
||||
@@ -85,6 +109,12 @@ void WaitForAllMacros();
|
||||
|
||||
EXPORT void LoadMacros(obs_data_t *obj);
|
||||
EXPORT void SaveMacros(obs_data_t *obj);
|
||||
// Appends the exported macros to the macro list.
|
||||
// Macros for which shouldImport returns false are skipped and their group
|
||||
// contents are moved to the parent group of the skipped group.
|
||||
std::vector<std::shared_ptr<Macro>> LoadImportedMacros(
|
||||
obs_data_array_t *macros,
|
||||
const std::function<bool(const std::shared_ptr<Macro> &)> &shouldImport);
|
||||
|
||||
EXPORT void InvalidateMacroTempVarValues();
|
||||
EXPORT void ResetMacroConditionTimers(Macro *);
|
||||
|
||||
@@ -152,29 +152,6 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
|
||||
RegisterMacroAddUndoRedo(name);
|
||||
}
|
||||
|
||||
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> ¯os,
|
||||
const std::shared_ptr<Macro> &group)
|
||||
{
|
||||
auto subitems = GetGroupDescendants(group.get());
|
||||
|
||||
// Remove subitems which were already selected to avoid duplicates
|
||||
for (const auto &subitem : subitems) {
|
||||
auto it = std::find(macros.begin(), macros.end(), subitem);
|
||||
if (it == macros.end()) {
|
||||
continue;
|
||||
}
|
||||
macros.erase(it);
|
||||
}
|
||||
|
||||
// Add group subitems
|
||||
auto it = std::find(macros.begin(), macros.end(), group);
|
||||
if (it == macros.end()) {
|
||||
return;
|
||||
}
|
||||
it = std::next(it);
|
||||
macros.insert(it, subitems.begin(), subitems.end());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
if (!macro) {
|
||||
@@ -196,14 +173,8 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> ¯o)
|
||||
RegisterMacroRemoveUndoRedo(macro.get());
|
||||
}
|
||||
|
||||
if (macro->IsGroup()) {
|
||||
std::vector<std::shared_ptr<Macro>> macros = {macro};
|
||||
addGroupSubitems(macros, macro);
|
||||
for (auto macro : macros) {
|
||||
ui->macroEdit->ClearSegmentWidgetCacheFor(macro.get());
|
||||
}
|
||||
} else {
|
||||
ui->macroEdit->ClearSegmentWidgetCacheFor(macro.get());
|
||||
for (const auto &m : GetMacrosWithGroupContents({macro})) {
|
||||
ui->macroEdit->ClearSegmentWidgetCacheFor(m.get());
|
||||
}
|
||||
|
||||
// Don't cache widgets for about to be deleted macros
|
||||
@@ -216,7 +187,7 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> ¯o)
|
||||
|
||||
void AdvSceneSwitcher::RemoveSelectedMacros()
|
||||
{
|
||||
auto macros = GetSelectedMacros();
|
||||
auto macros = GetOutermostMacros(GetSelectedMacros());
|
||||
if (macros.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -315,13 +286,7 @@ void AdvSceneSwitcher::RenameSelectedMacro()
|
||||
|
||||
void AdvSceneSwitcher::ExportMacros() const
|
||||
{
|
||||
auto selectedMacros = GetSelectedMacros();
|
||||
auto macros = selectedMacros;
|
||||
for (const auto ¯o : selectedMacros) {
|
||||
if (macro->IsGroup() && macro->GroupSize() > 0) {
|
||||
addGroupSubitems(macros, macro);
|
||||
}
|
||||
}
|
||||
const auto macros = GetMacrosWithGroupContents(GetSelectedMacros());
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
OBSDataArrayAutoRelease macroArray = obs_data_array_create();
|
||||
@@ -404,55 +369,20 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
}
|
||||
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros");
|
||||
size_t count = obs_data_array_count(array);
|
||||
|
||||
int groupSize = 0;
|
||||
std::shared_ptr<Macro> group;
|
||||
std::vector<std::shared_ptr<Macro>> importedMacros;
|
||||
auto &tempMacros = GetTemporaryMacros();
|
||||
|
||||
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
tempMacros.clear();
|
||||
|
||||
OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
|
||||
auto macro = std::make_shared<Macro>();
|
||||
tempMacros.emplace_back(macro);
|
||||
macro->Load(array_obj);
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
if (macroNameExists(macro->Name())) {
|
||||
lock.unlock();
|
||||
bool resolved = ResolveMacroImportNameConflict(macro);
|
||||
lock.lock();
|
||||
if (!resolved) {
|
||||
groupSize--;
|
||||
continue;
|
||||
const auto importedMacros = LoadImportedMacros(
|
||||
array, [this, &lock](const std::shared_ptr<Macro> ¯o) {
|
||||
if (!macroNameExists(macro->Name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
importedMacros.emplace_back(macro);
|
||||
GetTopLevelMacros().emplace_back(macro);
|
||||
if (groupSize > 0 && !macro->IsGroup()) {
|
||||
Macro::PrepareMoveToGroup(group, macro);
|
||||
groupSize--;
|
||||
}
|
||||
|
||||
if (macro->IsGroup()) {
|
||||
group = macro;
|
||||
groupSize = macro->GroupSize();
|
||||
// We are not sure if all elements will be added so we
|
||||
// have to reset the group size to zero and add elements
|
||||
// to the group as they come up.
|
||||
macro->ResetGroupSize();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto ¯o : importedMacros) {
|
||||
macro->PostLoad();
|
||||
}
|
||||
RunAndClearPostLoadSteps();
|
||||
tempMacros.clear();
|
||||
lock.unlock();
|
||||
auto renamedMacro = macro;
|
||||
const bool resolved =
|
||||
ResolveMacroImportNameConflict(renamedMacro);
|
||||
lock.lock();
|
||||
return resolved;
|
||||
});
|
||||
|
||||
ui->macros->Reset(GetTopLevelMacros(),
|
||||
GetGlobalMacroSettings()._highlightExecuted);
|
||||
|
||||
@@ -798,27 +798,7 @@ std::vector<std::shared_ptr<Macro>> MacroTree::GetCurrentMacros() const
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> MacroTree::GetSelectedSubtreeRoots() const
|
||||
{
|
||||
auto selected = GetCurrentMacros();
|
||||
std::vector<std::shared_ptr<Macro>> roots;
|
||||
for (const auto ¯o : selected) {
|
||||
const bool hasSelectedAncestor = std::any_of(
|
||||
selected.begin(), selected.end(),
|
||||
[¯o](const std::shared_ptr<Macro> &other) {
|
||||
return IsMacroInGroup(macro.get(), other.get());
|
||||
});
|
||||
if (!hasSelectedAncestor) {
|
||||
roots.emplace_back(macro);
|
||||
}
|
||||
}
|
||||
|
||||
auto treeModel = GetModel();
|
||||
std::sort(roots.begin(), roots.end(),
|
||||
[treeModel](const std::shared_ptr<Macro> &a,
|
||||
const std::shared_ptr<Macro> &b) {
|
||||
return treeModel->PositionOf(a.get()) <
|
||||
treeModel->PositionOf(b.get());
|
||||
});
|
||||
return roots;
|
||||
return GetOutermostMacros(GetCurrentMacros());
|
||||
}
|
||||
|
||||
bool MacroTree::GroupsSelected() const
|
||||
|
||||
@@ -143,11 +143,7 @@ static void deleteMacroByName(const char *jsonData)
|
||||
QEvent::DeferredDelete);
|
||||
} else {
|
||||
auto lock = LockContext();
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const auto it = std::find(macros.begin(), macros.end(), macro);
|
||||
if (it != macros.end()) {
|
||||
macros.erase(it);
|
||||
}
|
||||
RemoveMacroSubtree(macro);
|
||||
}
|
||||
|
||||
MacroSignalManager::Instance()->Remove(QString::fromStdString(name));
|
||||
@@ -178,17 +174,9 @@ static void restoreMacroFromData(const char *jsonData)
|
||||
macro->PostLoad();
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
if (!parentGroup.empty()) {
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str()).lock();
|
||||
if (parent) {
|
||||
Macro::PrepareMoveToGroup(parent, macro);
|
||||
}
|
||||
}
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const int insertIdx = std::min(index, (int)macros.size());
|
||||
macros.insert(macros.begin() + insertIdx, macro);
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str()).lock();
|
||||
InsertMacroSubtree(index, {macro}, parent);
|
||||
macroName = macro->Name();
|
||||
}
|
||||
|
||||
@@ -207,8 +195,9 @@ static void restoreMacroFromData(const char *jsonData)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Callback data format:
|
||||
// {"group_name": "...", "group_index": N, "group_data": {...},
|
||||
// "children": [{"data": {...}}, ...]}
|
||||
// {"group_name": "...", "group_index": N, "parent_group": "...",
|
||||
// "group_data": {...}, "children": [{"data": {...}}, ...]}
|
||||
// The children contain the contents of nested groups as well.
|
||||
static void deleteGroupWithChildrenFromData(const char *jsonData)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create_from_json(jsonData);
|
||||
@@ -222,7 +211,7 @@ static void deleteGroupWithChildrenFromData(const char *jsonData)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto children = GetGroupChildren(group.get());
|
||||
const auto children = GetGroupDescendants(group.get());
|
||||
|
||||
auto *const window = AdvSceneSwitcher::window;
|
||||
if (SettingsWindowIsOpened() && window) {
|
||||
@@ -238,11 +227,7 @@ static void deleteGroupWithChildrenFromData(const char *jsonData)
|
||||
QEvent::DeferredDelete);
|
||||
} else {
|
||||
auto lock = LockContext();
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const auto it = std::find(macros.begin(), macros.end(), group);
|
||||
if (it != macros.end()) {
|
||||
macros.erase(it, std::next(it, 1 + group->GroupSize()));
|
||||
}
|
||||
RemoveMacroSubtree(group);
|
||||
}
|
||||
|
||||
for (const auto &child : children) {
|
||||
@@ -261,21 +246,18 @@ static void restoreGroupWithChildrenFromData(const char *jsonData)
|
||||
}
|
||||
|
||||
const int groupIndex = (int)obs_data_get_int(data, "group_index");
|
||||
const std::string parentGroup =
|
||||
obs_data_get_string(data, "parent_group");
|
||||
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");
|
||||
}
|
||||
if (!groupData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto group = std::make_shared<Macro>();
|
||||
group->Load(groupData);
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> children;
|
||||
const size_t count = obs_data_array_count(childArray);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -294,22 +276,15 @@ static void restoreGroupWithChildrenFromData(const char *jsonData)
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
std::string restoredGroupName;
|
||||
const std::string restoredGroupName = group->Name();
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto group = Macro::CreateGroup(groupName, children);
|
||||
group->SetCollapsed(collapsed);
|
||||
restoredGroupName = group->Name();
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const int insertIdx = std::min(groupIndex, (int)macros.size());
|
||||
macros.insert(macros.begin() + insertIdx, group);
|
||||
int offset = 1;
|
||||
for (const auto &child : children) {
|
||||
macros.insert(macros.begin() + insertIdx + offset,
|
||||
child);
|
||||
offset++;
|
||||
}
|
||||
std::deque<std::shared_ptr<Macro>> subtree = {group};
|
||||
subtree.insert(subtree.end(), children.begin(), children.end());
|
||||
Macro::RestoreGroupHierarchy(subtree);
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str()).lock();
|
||||
InsertMacroSubtree(groupIndex, subtree, parent);
|
||||
}
|
||||
|
||||
auto *const window = AdvSceneSwitcher::window;
|
||||
@@ -332,7 +307,8 @@ static void restoreGroupWithChildrenFromData(const char *jsonData)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Callback data format for both group and ungroup:
|
||||
// {"group_name": "...", "group_index": N, "children": [{"name": "..."}, ...]}
|
||||
// {"group_name": "...", "group_index": N, "parent_group": "...",
|
||||
// "children": [{"name": "..."}, ...]}
|
||||
static void removeGroupFromData(const char *jsonData)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create_from_json(jsonData);
|
||||
@@ -371,6 +347,8 @@ static void restoreGroupFromData(const char *jsonData)
|
||||
|
||||
const std::string groupName = obs_data_get_string(data, "group_name");
|
||||
const int groupIndex = (int)obs_data_get_int(data, "group_index");
|
||||
const std::string parentGroup =
|
||||
obs_data_get_string(data, "parent_group");
|
||||
OBSDataArrayAutoRelease childArray =
|
||||
obs_data_get_array(data, "children");
|
||||
|
||||
@@ -387,23 +365,9 @@ static void restoreGroupFromData(const char *jsonData)
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto group = Macro::CreateGroup(groupName, children);
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const int insertIdx = std::min(groupIndex, (int)macros.size());
|
||||
macros.insert(macros.begin() + insertIdx, group);
|
||||
|
||||
int offset = 1;
|
||||
for (const auto &child : children) {
|
||||
const auto it =
|
||||
std::find(macros.begin(), macros.end(), child);
|
||||
if (it != macros.end()) {
|
||||
macros.erase(it);
|
||||
}
|
||||
macros.insert(macros.begin() + insertIdx + offset,
|
||||
child);
|
||||
offset++;
|
||||
}
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str()).lock();
|
||||
InsertGroup(groupName, groupIndex, children, parent);
|
||||
}
|
||||
|
||||
auto *const window = AdvSceneSwitcher::window;
|
||||
@@ -804,12 +768,7 @@ static void deleteMacrosBatch(const char *jsonData)
|
||||
nullptr, QEvent::DeferredDelete);
|
||||
} else {
|
||||
auto lock = LockContext();
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const auto it =
|
||||
std::find(macros.begin(), macros.end(), macro);
|
||||
if (it != macros.end()) {
|
||||
macros.erase(it);
|
||||
}
|
||||
RemoveMacroSubtree(macro);
|
||||
}
|
||||
|
||||
MacroSignalManager::Instance()->Remove(
|
||||
@@ -847,20 +806,13 @@ static void restoreMacrosBatch(const char *jsonData)
|
||||
macro->PostLoad();
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
if (!parentGroup.empty()) {
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str())
|
||||
.lock();
|
||||
if (parent) {
|
||||
Macro::PrepareMoveToGroup(parent,
|
||||
macro);
|
||||
}
|
||||
}
|
||||
// The group contents are restored by the following
|
||||
// entries, which will update the group size
|
||||
macro->ResetGroupSize();
|
||||
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
const int insertIdx =
|
||||
std::min(index, (int)macros.size());
|
||||
macros.insert(macros.begin() + insertIdx, macro);
|
||||
const auto parent =
|
||||
GetWeakMacroByName(parentGroup.c_str()).lock();
|
||||
InsertMacroSubtree(index, {macro}, parent);
|
||||
}
|
||||
|
||||
addedNames.push_back(macro->Name());
|
||||
@@ -1113,17 +1065,22 @@ void RegisterGroupCreateUndoRedo(const std::string &groupName)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto parent = group->Parent();
|
||||
const std::string parentGroup = parent ? parent->Name() : "";
|
||||
|
||||
const auto children = GetGroupChildren(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_string(undoData, "parent_group", parentGroup.c_str());
|
||||
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_string(redoData, "parent_group", parentGroup.c_str());
|
||||
obs_data_set_array(redoData, "children", childArray);
|
||||
|
||||
const std::string actionName =
|
||||
@@ -1157,17 +1114,22 @@ void RegisterGroupRemoveUndoRedo(const std::string &groupName)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto parent = group->Parent();
|
||||
const std::string parentGroup = parent ? parent->Name() : "";
|
||||
|
||||
const auto children = GetGroupChildren(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_string(undoData, "parent_group", parentGroup.c_str());
|
||||
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_string(redoData, "parent_group", parentGroup.c_str());
|
||||
obs_data_set_array(redoData, "children", childArray);
|
||||
|
||||
const std::string actionName =
|
||||
@@ -1202,10 +1164,13 @@ void RegisterGroupDeleteUndoRedo(Macro *macro)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto parent = macro->Parent();
|
||||
const std::string parentGroup = parent ? parent->Name() : "";
|
||||
|
||||
OBSDataAutoRelease groupData = obs_data_create();
|
||||
macro->Save(groupData);
|
||||
|
||||
const auto children = GetGroupChildren(macro);
|
||||
const auto children = GetGroupDescendants(macro);
|
||||
OBSDataArrayAutoRelease childArray = obs_data_array_create();
|
||||
for (const auto &child : children) {
|
||||
OBSDataAutoRelease childData = obs_data_create();
|
||||
@@ -1218,12 +1183,14 @@ void RegisterGroupDeleteUndoRedo(Macro *macro)
|
||||
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_string(undoData, "parent_group", parentGroup.c_str());
|
||||
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_string(redoData, "parent_group", parentGroup.c_str());
|
||||
obs_data_set_obj(redoData, "group_data", groupData);
|
||||
obs_data_set_array(redoData, "children", childArray);
|
||||
|
||||
|
||||
@@ -1290,17 +1290,13 @@ void Macro::SetHotkeysDesc() const
|
||||
|
||||
void SaveMacros(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *macroArray = obs_data_array_create();
|
||||
OBSDataArrayAutoRelease macroArray = obs_data_array_create();
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
OBSDataAutoRelease array_obj = obs_data_create();
|
||||
m->Save(array_obj);
|
||||
obs_data_array_push_back(macroArray, array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, "macros", macroArray);
|
||||
obs_data_array_release(macroArray);
|
||||
}
|
||||
|
||||
void LoadMacros(obs_data_t *obj)
|
||||
@@ -1308,16 +1304,15 @@ void LoadMacros(obs_data_t *obj)
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
macros.clear();
|
||||
|
||||
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
|
||||
OBSDataArrayAutoRelease macroArray = obs_data_get_array(obj, "macros");
|
||||
size_t count = obs_data_array_count(macroArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(macroArray, i);
|
||||
OBSDataAutoRelease array_obj =
|
||||
obs_data_array_item(macroArray, i);
|
||||
macros.emplace_back(std::make_shared<Macro>());
|
||||
macros.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(macroArray);
|
||||
|
||||
Macro::RestoreGroupHierarchy(macros);
|
||||
for (const auto &m : macros) {
|
||||
@@ -1325,6 +1320,48 @@ void LoadMacros(obs_data_t *obj)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> LoadImportedMacros(
|
||||
obs_data_array_t *array,
|
||||
const std::function<bool(const std::shared_ptr<Macro> &)> &shouldImport)
|
||||
{
|
||||
std::deque<std::shared_ptr<Macro>> loaded;
|
||||
auto &tempMacros = GetTemporaryMacros();
|
||||
const size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
tempMacros.clear();
|
||||
OBSDataAutoRelease obj = obs_data_array_item(array, i);
|
||||
auto macro = std::make_shared<Macro>();
|
||||
tempMacros.emplace_back(macro);
|
||||
macro->Load(obj);
|
||||
RunAndClearPostLoadSteps();
|
||||
loaded.emplace_back(macro);
|
||||
}
|
||||
tempMacros.clear();
|
||||
Macro::RestoreGroupHierarchy(loaded);
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> imported;
|
||||
for (const auto ¯o : loaded) {
|
||||
if (!shouldImport(macro)) {
|
||||
for (const auto &child : loaded) {
|
||||
if (child->Parent() == macro) {
|
||||
Macro::PrepareMoveToGroup(
|
||||
macro->Parent(), child);
|
||||
}
|
||||
}
|
||||
Macro::PrepareMoveToGroup(nullptr, macro);
|
||||
continue;
|
||||
}
|
||||
GetTopLevelMacros().emplace_back(macro);
|
||||
imported.emplace_back(macro);
|
||||
}
|
||||
|
||||
for (const auto ¯o : imported) {
|
||||
macro->PostLoad();
|
||||
}
|
||||
RunAndClearPostLoadSteps();
|
||||
return imported;
|
||||
}
|
||||
|
||||
bool CheckMacros()
|
||||
{
|
||||
bool matchFound = false;
|
||||
|
||||
@@ -261,3 +261,252 @@ TEST_CASE("Execution time is propagated to all parent groups", "[macro-group]")
|
||||
REQUIRE(GetMacro("G2")->WasExecutedSince(start));
|
||||
REQUIRE_FALSE(GetMacro("B")->WasExecutedSince(start));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void checkGroupSizes()
|
||||
{
|
||||
const auto ¯os = GetTopLevelMacros();
|
||||
for (const auto ¯o : macros) {
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
INFO("group " << macro->Name() << " in " << DescribeMacros());
|
||||
const auto descendants = std::count_if(
|
||||
macros.begin(), macros.end(),
|
||||
[¯o](const std::shared_ptr<Macro> &m) {
|
||||
return IsMacroInGroup(m.get(), macro.get());
|
||||
});
|
||||
REQUIRE(macro->GroupSize() == (uint32_t)descendants);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> macros(const Names &names)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> result;
|
||||
for (const auto &name : names) {
|
||||
result.emplace_back(GetMacro(name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int indexOf(const std::string &name)
|
||||
{
|
||||
const auto &all = GetTopLevelMacros();
|
||||
for (int i = 0; i < (int)all.size(); i++) {
|
||||
if (all[i]->Name() == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mirrors AdvSceneSwitcher::ExportMacros()
|
||||
OBSDataArrayAutoRelease exportMacros(const Names &selection)
|
||||
{
|
||||
OBSDataArrayAutoRelease array = obs_data_array_create();
|
||||
for (const auto ¯o :
|
||||
GetMacrosWithGroupContents(macros(selection))) {
|
||||
OBSDataAutoRelease obj = obs_data_create();
|
||||
macro->Save(obj);
|
||||
obs_data_array_push_back(array, obj);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Outermost macros", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
REQUIRE(names(GetOutermostMacros(macros({"C", "G2", "F", "A"}))) ==
|
||||
Names{"A", "G2", "F"});
|
||||
REQUIRE(names(GetOutermostMacros(macros({"C", "G2", "G1"}))) ==
|
||||
Names{"G1"});
|
||||
REQUIRE(GetOutermostMacros({}).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Macros with group contents", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
REQUIRE(names(GetMacrosWithGroupContents(macros({"F", "G2", "A"}))) ==
|
||||
Names{"A", "G2", "C", "D", "F"});
|
||||
REQUIRE(names(GetMacrosWithGroupContents(macros({"C", "G1"}))) ==
|
||||
Names{"G1", "B", "G2", "C", "D", "E"});
|
||||
}
|
||||
|
||||
TEST_CASE("Remove and restore macro subtrees", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
SECTION("Macro in nested group")
|
||||
{
|
||||
auto macro = GetMacro("C");
|
||||
const int index = indexOf("C");
|
||||
RemoveMacroSubtree(macro);
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[D], E], F");
|
||||
checkGroupSizes();
|
||||
|
||||
InsertMacroSubtree(index, {macro}, GetMacro("G2"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, D], E], F");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
SECTION("Nested group")
|
||||
{
|
||||
const std::deque<std::shared_ptr<Macro>> subtree = {
|
||||
GetMacro("G2"), GetMacro("C"), GetMacro("D")};
|
||||
const int index = indexOf("G2");
|
||||
RemoveMacroSubtree(GetMacro("G2"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, E], F");
|
||||
checkGroupSizes();
|
||||
|
||||
InsertMacroSubtree(index, subtree, GetMacro("G1"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, D], E], F");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
// Mirrors the group delete undo, which saves and loads the group and
|
||||
// its contents
|
||||
SECTION("Group from saved data")
|
||||
{
|
||||
auto group = GetMacro("G1");
|
||||
const int index = indexOf("G1");
|
||||
OBSDataArrayAutoRelease array = exportMacros({"G1"});
|
||||
RemoveMacroSubtree(group);
|
||||
REQUIRE(DescribeMacros() == "A, F");
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> subtree;
|
||||
for (size_t i = 0; i < obs_data_array_count(array); i++) {
|
||||
OBSDataAutoRelease obj = obs_data_array_item(array, i);
|
||||
auto macro = std::make_shared<Macro>();
|
||||
macro->Load(obj);
|
||||
subtree.emplace_back(macro);
|
||||
}
|
||||
Macro::RestoreGroupHierarchy(subtree);
|
||||
InsertMacroSubtree(index, subtree, nullptr);
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, D], E], F");
|
||||
checkGroupSizes();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Ungroup and regroup", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
|
||||
SECTION("Nested group")
|
||||
{
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
const int index = indexOf("G2");
|
||||
auto children = GetGroupChildren(GetMacro("G2").get());
|
||||
Macro::RemoveGroup(GetMacro("G2"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C, D, E], F");
|
||||
|
||||
InsertGroup("G2", index, children, GetMacro("G1"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, D], E], F");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
SECTION("Group containing groups")
|
||||
{
|
||||
SetupMacros("A, G1[B, G2[C, G3[D]], E], F");
|
||||
const int index = indexOf("G1");
|
||||
auto children = GetGroupChildren(GetMacro("G1").get());
|
||||
Macro::RemoveGroup(GetMacro("G1"));
|
||||
REQUIRE(DescribeMacros() == "A, B, G2[C, G3[D]], E, F");
|
||||
|
||||
InsertGroup("G1", index, children, nullptr);
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, G3[D]], E], F");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
SECTION("Empty nested group")
|
||||
{
|
||||
SetupMacros("A, G1[G2[]]");
|
||||
const int index = indexOf("G2");
|
||||
std::vector<std::shared_ptr<Macro>> children;
|
||||
Macro::RemoveGroup(GetMacro("G2"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[]");
|
||||
|
||||
InsertGroup("G2", index, children, GetMacro("G1"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[G2[]]");
|
||||
checkGroupSizes();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Export and import nested groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2{C, D}], E");
|
||||
OBSDataArrayAutoRelease array = exportMacros({"G1", "C"});
|
||||
const auto importAll = [](const std::shared_ptr<Macro> &) {
|
||||
return true;
|
||||
};
|
||||
|
||||
SECTION("Into empty macro list")
|
||||
{
|
||||
GetTopLevelMacros().clear();
|
||||
const auto imported = LoadImportedMacros(array, importAll);
|
||||
REQUIRE(names(imported) == Names{"G1", "B", "G2", "C", "D"});
|
||||
REQUIRE(DescribeMacros() == "G1[B, G2{C, D}]");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
// Mirrors AdvSceneSwitcher::ImportMacros() skipping macros for which
|
||||
// the name conflict was not resolved
|
||||
const auto renameOrSkip = [](const std::string &skip) {
|
||||
return [skip](const std::shared_ptr<Macro> ¯o) {
|
||||
if (macro->Name() == skip) {
|
||||
return false;
|
||||
}
|
||||
macro->SetName(macro->Name() + "x");
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
SECTION("Skip outer group")
|
||||
{
|
||||
LoadImportedMacros(array, renameOrSkip("G1"));
|
||||
REQUIRE(DescribeMacros() ==
|
||||
"A, G1[B, G2{C, D}], E, Bx, G2x{Cx, Dx}");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
SECTION("Skip inner group")
|
||||
{
|
||||
LoadImportedMacros(array, renameOrSkip("G2"));
|
||||
REQUIRE(DescribeMacros() ==
|
||||
"A, G1[B, G2{C, D}], E, G1x[Bx, Cx, Dx]");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
SECTION("Skip macro in nested group")
|
||||
{
|
||||
LoadImportedMacros(array, renameOrSkip("C"));
|
||||
REQUIRE(DescribeMacros() ==
|
||||
"A, G1[B, G2{C, D}], E, G1x[Bx, G2x{Dx}]");
|
||||
checkGroupSizes();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Import single level groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
OBSDataAutoRelease data = obs_data_create_from_json(R"({"macros": [
|
||||
{"name": "G1", "group": true,
|
||||
"groupData": {"size": 2, "collapsed": true}},
|
||||
{"name": "A"},
|
||||
{"name": "B"},
|
||||
{"name": "C"}
|
||||
]})");
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros");
|
||||
LoadImportedMacros(array,
|
||||
[](const std::shared_ptr<Macro> &) { return true; });
|
||||
REQUIRE(DescribeMacros() == "G1{A, B}, C");
|
||||
checkGroupSizes();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user