Add export / import functionality to macro tab

This enables the easy sharing of single / multiple macros across scene
collections.
Previously either all settings had to be copied via the export / import
functionality of the General tab or none at all.
This commit is contained in:
WarmUpTill 2023-07-30 00:53:17 +02:00 committed by WarmUpTill
parent dcae0e8e8b
commit 4b0a631987
9 changed files with 307 additions and 29 deletions

View File

@ -263,6 +263,8 @@ target_sources(
src/utils/filter-combo-box.hpp
src/utils/filter-selection.cpp
src/utils/filter-selection.hpp
src/utils/macro-export-import-dialog.cpp
src/utils/macro-export-import-dialog.hpp
src/utils/macro-list.cpp
src/utils/macro-list.hpp
src/utils/macro-segment-selection.cpp

View File

@ -92,6 +92,12 @@ AdvSceneSwitcher.macroTab.group="Group Selected Macros"
AdvSceneSwitcher.macroTab.ungroup="Ungroup Selected Groups"
AdvSceneSwitcher.macroTab.rename="Rename"
AdvSceneSwitcher.macroTab.remove="Remove"
AdvSceneSwitcher.macroTab.export="Export"
AdvSceneSwitcher.macroTab.export.info="Paste the string below into the import dialog to import the selected macros:"
AdvSceneSwitcher.macroTab.import="Import"
AdvSceneSwitcher.macroTab.import.info="Paste the export string into the below text box to import macros:"
AdvSceneSwitcher.macroTab.import.invalid="Invalid import data provided!"
AdvSceneSwitcher.macroTab.import.nameConflict="Cannot continue importing macro \"%1\" as a macro with the same name already exists!\nDo you want to continue with the import of \"%2\" and choose a new name? (Will be skipped otherwise)"
AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"

View File

@ -112,8 +112,7 @@ public slots:
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void MacroSelectionChanged(const QItemSelection &,
const QItemSelection &);
void MacroSelectionChanged();
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
void DeleteMacroSegementHotkey();
@ -122,6 +121,8 @@ public slots:
void ShowMacroConditionsContextMenu(const QPoint &);
void CopyMacro();
void RenameCurrentMacro();
void ExportMacros();
void ImportMacros();
void ExpandAllActions();
void ExpandAllConditions();
void CollapseAllActions();
@ -165,6 +166,7 @@ signals:
void VariableRemoved(const QString &);
private:
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
bool MacroTabIsInFocus();
MacroSegmentList *conditionsList = nullptr;

View File

@ -6,7 +6,9 @@
#include "switcher-data.hpp"
#include "name-dialog.hpp"
#include "macro-properties.hpp"
#include "macro-export-import-dialog.hpp"
#include "utility.hpp"
#include "version.h"
#include <QColor>
#include <QMenu>
@ -18,7 +20,7 @@ namespace advss {
static QMetaObject::Connection addPulse;
static QTimer onChangeHighlightTimer;
static bool macroNameExists(std::string name)
static bool macroNameExists(const std::string &name)
{
return !!GetMacroByName(name.c_str());
}
@ -60,12 +62,8 @@ bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr<Macro> &res,
return false;
}
{
auto lock = LockContext();
res = std::make_shared<Macro>(
name,
switcher->macroProperties._newMacroRegisterHotkeys);
}
res = std::make_shared<Macro>(
name, switcher->macroProperties._newMacroRegisterHotkeys);
return true;
}
@ -77,11 +75,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
return;
}
{
auto lock = LockContext();
ui->macros->Add(newMacro);
}
ui->macros->Add(newMacro);
ui->macroAdd->disconnect(addPulse);
emit MacroAdded(QString::fromStdString(name));
}
@ -101,11 +95,7 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
}
}
{
auto lock = LockContext();
ui->macros->Remove(macro);
}
ui->macros->Remove(macro);
emit MacroRemoved(name);
}
@ -152,7 +142,6 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
void AdvSceneSwitcher::on_macroUp_clicked()
{
auto lock = LockContext();
auto macro = GetSelectedMacro();
if (!macro) {
return;
@ -162,7 +151,6 @@ void AdvSceneSwitcher::on_macroUp_clicked()
void AdvSceneSwitcher::on_macroDown_clicked()
{
auto lock = LockContext();
auto macro = GetSelectedMacro();
if (!macro) {
return;
@ -202,6 +190,179 @@ void AdvSceneSwitcher::RenameCurrentMacro()
ui->macroName->setText(QString::fromStdString(name));
}
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
const std::shared_ptr<Macro> &group)
{
std::vector<std::shared_ptr<Macro>> subitems;
subitems.reserve(group->GroupSize());
// Find all subitems
for (auto it = switcher->macros.begin(); it < switcher->macros.end();
it++) {
if ((*it)->Name() == group->Name()) {
for (uint32_t i = 1; i <= group->GroupSize(); i++) {
subitems.emplace_back(*std::next(it, i));
}
break;
}
}
// 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::ExportMacros()
{
auto selectedMacros = GetSelectedMacros();
auto macros = selectedMacros;
for (const auto &macro : selectedMacros) {
if (macro->IsGroup() && macro->GroupSize() > 0) {
addGroupSubitems(macros, macro);
}
}
auto data = obs_data_create();
auto macroArray = obs_data_array_create();
for (const auto &macro : macros) {
obs_data_t *obj = obs_data_create();
macro->Save(obj);
obs_data_array_push_back(macroArray, obj);
obs_data_release(obj);
}
obs_data_set_array(data, "macros", macroArray);
obs_data_array_release(macroArray);
obs_data_set_string(data, "version", g_GIT_TAG);
auto json = obs_data_get_json(data);
QString exportString(json);
obs_data_release(data);
MacroExportImportDialog::ExportMacros(exportString);
}
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
std::shared_ptr<Macro> &macro)
{
QString errorMesg = obs_module_text(
"AdvSceneSwitcher.macroTab.import.nameConflict");
errorMesg = errorMesg.arg(QString::fromStdString(macro->Name()),
QString::fromStdString(macro->Name()));
bool continueResolve = DisplayMessage(errorMesg, true);
if (!continueResolve) {
return false;
}
QString format = QString::fromStdString(macro->Name()) + " %1";
int i = 2;
QString placeHolderText = format.arg(i);
while ((macroNameExists(placeHolderText.toUtf8().constData()))) {
placeHolderText = format.arg(++i);
}
std::string newName;
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.macroTab.add"),
obs_module_text("AdvSceneSwitcher.macroTab.name"), newName,
placeHolderText);
if (!accepted) {
return false;
}
if (newName.empty()) {
return false;
}
if (macroNameExists(newName)) {
DisplayMessage(
obs_module_text("AdvSceneSwitcher.macroTab.exists"));
return ResolveMacroImportNameConflict(macro);
}
macro->SetName(newName);
return true;
}
void AdvSceneSwitcher::ImportMacros()
{
QString json;
if (!MacroExportImportDialog::ImportMacros(json)) {
return;
}
auto data = obs_data_create_from_json(json.toStdString().c_str());
if (!data) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.macroTab.import.invalid"));
ImportMacros();
return;
}
auto version = obs_data_get_string(data, "version");
if (strcmp(version, g_GIT_TAG) != 0) {
blog(LOG_WARNING,
"importing macros from non matching plugin version \"%s\"",
version);
}
auto array = obs_data_get_array(data, "macros");
size_t count = obs_data_array_count(array);
int groupSize = 0;
std::shared_ptr<Macro> group;
auto lock = LockContext();
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>();
macro->Load(array_obj);
macro->PostLoad();
if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) {
obs_data_release(array_obj);
groupSize--;
continue;
}
switcher->macros.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();
}
obs_data_release(array_obj);
}
obs_data_array_release(array);
obs_data_release(data);
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
}
void AdvSceneSwitcher::on_macroName_editingFinished()
{
auto macro = GetSelectedMacro();
@ -372,8 +533,7 @@ std::vector<std::shared_ptr<Macro>> AdvSceneSwitcher::GetSelectedMacros()
return ui->macros->GetCurrentMacros();
}
void AdvSceneSwitcher::MacroSelectionChanged(const QItemSelection &,
const QItemSelection &)
void AdvSceneSwitcher::MacroSelectionChanged()
{
if (loading) {
return;
@ -445,12 +605,8 @@ void AdvSceneSwitcher::SetupMacroTab()
}
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
connect(ui->macros->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection &,
const QItemSelection &)),
this,
SLOT(MacroSelectionChanged(const QItemSelection &,
const QItemSelection &)));
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged()));
delete conditionsList;
conditionsList = new MacroSegmentList(this);
@ -569,6 +725,15 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
obs_module_text("AdvSceneSwitcher.macroTab.remove"), this,
&AdvSceneSwitcher::on_macroRemove_clicked);
remove->setDisabled(ui->macros->SelectionEmpty());
menu.addSeparator();
auto exportAction = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.export"), this,
&AdvSceneSwitcher::ExportMacros);
exportAction->setDisabled(ui->macros->SelectionEmpty());
auto import = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.import"), this,
&AdvSceneSwitcher::ImportMacros);
menu.exec(globalPos);
}

View File

@ -395,6 +395,7 @@ CountItemsVisibleInModel(const std::deque<std::shared_ptr<Macro>> &macros)
void MacroTreeModel::Add(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto idx = CountItemsVisibleInModel(_macros);
beginInsertRows(QModelIndex(), idx, idx);
_macros.emplace_back(item);
@ -407,6 +408,7 @@ void MacroTreeModel::Add(std::shared_ptr<Macro> item)
void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto uiStartIdx = GetItemModelIndex(item);
if (uiStartIdx == -1) {
return;
@ -759,6 +761,12 @@ void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> &macros,
MacroTreeModel *mtm = new MacroTreeModel(this, macros);
setModel(mtm);
GetModel()->Reset(macros);
connect(selectionModel(),
SIGNAL(selectionChanged(const QItemSelection &,
const QItemSelection &)),
this,
SLOT(SelectionChangedHelper(const QItemSelection &,
const QItemSelection &)));
}
void MacroTree::Add(std::shared_ptr<Macro> item,
@ -1173,6 +1181,7 @@ void MacroTree::Remove(std::shared_ptr<Macro> item) const
void MacroTree::Up(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto above = GetModel()->Neighbor(item, true);
if (!above) {
return;
@ -1198,6 +1207,7 @@ void MacroTree::Up(std::shared_ptr<Macro> item) const
void MacroTree::Down(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto below = GetModel()->Neighbor(item, false);
if (!below) {
return;
@ -1247,6 +1257,12 @@ void MacroTree::UngroupSelectedGroups()
assert(GetModel()->IsInValidState());
}
void MacroTree::SelectionChangedHelper(const QItemSelection &,
const QItemSelection &)
{
emit MacroSelectionChanged();
}
inline MacroTreeItem *MacroTree::GetItemWidget(int idx) const
{
QWidget *widget = indexWidget(GetModel()->createIndex(idx, 0, nullptr));

View File

@ -138,6 +138,11 @@ public:
public slots:
void GroupSelectedItems();
void UngroupSelectedGroups();
void SelectionChangedHelper(const QItemSelection &,
const QItemSelection &);
signals:
void MacroSelectionChanged();
protected:
virtual void dropEvent(QDropEvent *event) override;

View File

@ -60,6 +60,7 @@ public:
std::shared_ptr<Macro> item);
bool IsGroup() const { return _isGroup; }
uint32_t GroupSize() const { return _groupSize; }
void ResetGroupSize() { _groupSize = 0; }
bool IsSubitem() const { return !_parent.expired(); }
void SetCollapsed(bool val) { _isCollapsed = val; }
bool IsCollapsed() const { return _isCollapsed; }

View File

@ -0,0 +1,61 @@
#include "macro-export-import-dialog.hpp"
#include "obs-module-helper.hpp"
#include <QLayout>
#include <QLabel>
#include <QDialogButtonBox>
namespace advss {
MacroExportImportDialog::MacroExportImportDialog(Type type)
: QDialog(nullptr), _importExportString(new QPlainTextEdit(this))
{
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
auto label = new QLabel(obs_module_text(
type == Type::EXPORT_MACRO
? "AdvSceneSwitcher.macroTab.export.info"
: "AdvSceneSwitcher.macroTab.import.info"));
label->setWordWrap(true);
QDialogButtonBox *buttons = nullptr;
if (type == Type::EXPORT_MACRO) {
buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
} else {
buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
}
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
buttons->setCenterButtons(true);
auto layout = new QVBoxLayout;
layout->addWidget(label);
layout->addWidget(_importExportString);
layout->addWidget(buttons);
setLayout(layout);
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
}
void MacroExportImportDialog::ExportMacros(const QString &json)
{
MacroExportImportDialog dialog(
MacroExportImportDialog::Type::EXPORT_MACRO);
dialog._importExportString->setPlainText(json);
dialog.adjustSize();
dialog.updateGeometry();
dialog.exec();
}
bool MacroExportImportDialog::ImportMacros(QString &json)
{
MacroExportImportDialog dialog(
MacroExportImportDialog::Type::IMPORT_MACRO);
if (dialog.exec() == QDialog::Accepted) {
json = dialog._importExportString->toPlainText();
return true;
}
return false;
}
} // namespace advss

View File

@ -0,0 +1,20 @@
#pragma once
#include <QDialog>
#include <QPlainTextEdit>
namespace advss {
class MacroExportImportDialog : public QDialog {
Q_OBJECT
public:
enum class Type { EXPORT_MACRO, IMPORT_MACRO };
MacroExportImportDialog(Type type);
static void ExportMacros(const QString &json);
static bool ImportMacros(QString &json);
private:
QPlainTextEdit *_importExportString;
};
} // namespace advss