mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-05-09 04:32:13 -05:00
Add API to expand export / import dialog
This commit is contained in:
parent
ec7800597f
commit
c0d27cc0db
|
|
@ -134,6 +134,9 @@ target_sources(
|
|||
lib/macro/macro-dock-window.hpp
|
||||
lib/macro/macro-edit.cpp
|
||||
lib/macro/macro-edit.hpp
|
||||
lib/macro/macro-export-builtin-extensions.cpp
|
||||
lib/macro/macro-export-extensions.cpp
|
||||
lib/macro/macro-export-extensions.hpp
|
||||
lib/macro/macro-export-import-dialog.cpp
|
||||
lib/macro/macro-export-import-dialog.hpp
|
||||
lib/macro/macro-helpers.cpp
|
||||
|
|
|
|||
|
|
@ -202,6 +202,9 @@ 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.export.usePlainText="Use plain text"
|
||||
AdvSceneSwitcher.macroTab.export.additionalContent="Export Additional Content"
|
||||
AdvSceneSwitcher.macroTab.export.variables="Variables"
|
||||
AdvSceneSwitcher.macroTab.export.actionQueues="Action Queues"
|
||||
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!"
|
||||
|
|
|
|||
89
lib/macro/macro-export-builtin-extensions.cpp
Normal file
89
lib/macro/macro-export-builtin-extensions.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include "macro-export-extensions.hpp"
|
||||
|
||||
#include "action-queue.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool setup();
|
||||
static bool setupDone = setup();
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
// --- Variables ---
|
||||
AddMacroExportExtension(
|
||||
{"AdvSceneSwitcher.macroTab.export.variables", "variables",
|
||||
[](obs_data_t *data, const QStringList &selectedIds) {
|
||||
if (selectedIds.isEmpty()) {
|
||||
SaveVariables(data);
|
||||
return;
|
||||
}
|
||||
OBSDataArrayAutoRelease array =
|
||||
obs_data_array_create();
|
||||
for (const auto &v : GetVariables()) {
|
||||
const QString name =
|
||||
QString::fromStdString(v->Name());
|
||||
if (!selectedIds.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
v->Save(item);
|
||||
obs_data_array_push_back(array, item);
|
||||
}
|
||||
obs_data_set_array(data, "variables", array);
|
||||
},
|
||||
[](obs_data_t *data, const QStringList &) {
|
||||
ImportVariables(data);
|
||||
},
|
||||
[]() -> QList<QPair<QString, QString>> {
|
||||
QList<QPair<QString, QString>> items;
|
||||
for (const auto &v : GetVariables()) {
|
||||
const QString name =
|
||||
QString::fromStdString(v->Name());
|
||||
items.append({name, name});
|
||||
}
|
||||
return items;
|
||||
}});
|
||||
|
||||
// --- Action Queues ---
|
||||
AddMacroExportExtension(
|
||||
{"AdvSceneSwitcher.macroTab.export.actionQueues",
|
||||
"actionQueues",
|
||||
[](obs_data_t *data, const QStringList &selectedIds) {
|
||||
if (selectedIds.isEmpty()) {
|
||||
SaveActionQueues(data);
|
||||
return;
|
||||
}
|
||||
OBSDataArrayAutoRelease array =
|
||||
obs_data_array_create();
|
||||
for (const auto &q : GetActionQueues()) {
|
||||
const QString name =
|
||||
QString::fromStdString(q->Name());
|
||||
if (!selectedIds.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
q->Save(item);
|
||||
obs_data_array_push_back(array, item);
|
||||
}
|
||||
obs_data_set_array(data, "actionQueues", array);
|
||||
},
|
||||
[](obs_data_t *data, const QStringList &) {
|
||||
ImportQueues(data);
|
||||
},
|
||||
[]() -> QList<QPair<QString, QString>> {
|
||||
QList<QPair<QString, QString>> items;
|
||||
for (const auto &q : GetActionQueues()) {
|
||||
const QString name =
|
||||
QString::fromStdString(q->Name());
|
||||
items.append({name, name});
|
||||
}
|
||||
return items;
|
||||
}});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
17
lib/macro/macro-export-extensions.cpp
Normal file
17
lib/macro/macro-export-extensions.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include "macro-export-extensions.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::vector<MacroExportExtension> extensions;
|
||||
|
||||
void AddMacroExportExtension(const MacroExportExtension &ext)
|
||||
{
|
||||
extensions.emplace_back(ext);
|
||||
}
|
||||
|
||||
const std::vector<MacroExportExtension> &GetMacroExportExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
41
lib/macro/macro-export-extensions.hpp
Normal file
41
lib/macro/macro-export-extensions.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "export-symbol-helper.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <obs-data.h>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct MacroExportExtension {
|
||||
const char *displayNameKey;
|
||||
const char *jsonKey;
|
||||
|
||||
// Export callback.
|
||||
// Write content into 'data'. 'selectedIds' contains the item IDs the
|
||||
// user explicitly checked; if empty (no per-item widget provided) save
|
||||
// everything.
|
||||
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
|
||||
save;
|
||||
|
||||
// Import callback.
|
||||
// Read content from 'data'. 'selectedIds' contains the item IDs the
|
||||
// user explicitly checked; if empty import every item found in 'data'.
|
||||
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
|
||||
load;
|
||||
|
||||
// Optional: enumerate items available for export as (id, displayName)
|
||||
// pairs. When nullptr the extension appears as a single all-or-nothing
|
||||
// checkbox in the export dialog.
|
||||
std::function<QList<QPair<QString, QString>>()> getExportItems =
|
||||
nullptr;
|
||||
};
|
||||
|
||||
EXPORT void AddMacroExportExtension(const MacroExportExtension &ext);
|
||||
const std::vector<MacroExportExtension> &GetMacroExportExtensions();
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -1,22 +1,160 @@
|
|||
#include "macro-export-import-dialog.hpp"
|
||||
#include "macro-export-extensions.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <QLayout>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool usePlainText = false;
|
||||
|
||||
MacroExportImportDialog::MacroExportImportDialog(Type type)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compress / decompress helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QString compressMacroString(const QString &input)
|
||||
{
|
||||
QByteArray inputData = input.toUtf8();
|
||||
auto compressedData = qCompress(inputData);
|
||||
QByteArray encodedData = compressedData.toBase64();
|
||||
return QString::fromUtf8(encodedData);
|
||||
}
|
||||
|
||||
static QString decompressMacroString(const QString &input)
|
||||
{
|
||||
QByteArray encodedData = input.toUtf8();
|
||||
QByteArray compressedData = QByteArray::fromBase64(encodedData);
|
||||
auto outputData = qUncompress(compressedData);
|
||||
return QString::fromUtf8(outputData);
|
||||
}
|
||||
|
||||
static bool isValidData(const QString &json)
|
||||
{
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(json.toStdString().c_str());
|
||||
return !!data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extension section
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
{
|
||||
auto outerSection = new Section(300, this);
|
||||
outerSection->AddHeaderWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.export.additionalContent")));
|
||||
|
||||
auto outerContent = new QWidget();
|
||||
auto outerLayout = new QVBoxLayout(outerContent);
|
||||
outerLayout->setContentsMargins(4, 4, 4, 4);
|
||||
outerLayout->setSpacing(4);
|
||||
|
||||
const auto &extensions = GetMacroExportExtensions();
|
||||
|
||||
// Build a sorted index list (alphabetical by translated display name).
|
||||
std::vector<int> order(extensions.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
std::sort(order.begin(), order.end(), [&](int a, int b) {
|
||||
return QString::localeAwareCompare(
|
||||
obs_module_text(extensions[a].displayNameKey),
|
||||
obs_module_text(extensions[b].displayNameKey)) <
|
||||
0;
|
||||
});
|
||||
|
||||
for (int extIdx : order) {
|
||||
const auto &ext = extensions[extIdx];
|
||||
ExtensionUI ui;
|
||||
|
||||
// Simple all-or-nothing checkbox (no sub-items).
|
||||
if (!ext.getExportItems) {
|
||||
ui.mainCheck = new QCheckBox(
|
||||
obs_module_text(ext.displayNameKey),
|
||||
outerContent);
|
||||
ui.mainCheck->setChecked(false);
|
||||
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
|
||||
&MacroExportImportDialog::UpdateExportString);
|
||||
outerLayout->addWidget(ui.mainCheck);
|
||||
_extensionUIs.append(std::move(ui));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extensions with per-item selection get their own inner
|
||||
// Section: the main checkbox sits in the header (next to the
|
||||
// toggle arrow) and the per-item checkboxes live in the
|
||||
// collapsible content area.
|
||||
const auto items = ext.getExportItems();
|
||||
if (items.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto innerSection = new Section(200, outerContent);
|
||||
|
||||
ui.mainCheck =
|
||||
new QCheckBox(obs_module_text(ext.displayNameKey));
|
||||
ui.mainCheck->setChecked(false);
|
||||
innerSection->AddHeaderWidget(ui.mainCheck);
|
||||
|
||||
auto subWidget = new QWidget();
|
||||
auto subLayout = new QVBoxLayout(subWidget);
|
||||
subLayout->setContentsMargins(4, 2, 4, 2);
|
||||
subLayout->setSpacing(2);
|
||||
|
||||
for (const auto &[id, displayName] : items) {
|
||||
auto cb = new QCheckBox(displayName, subWidget);
|
||||
cb->setChecked(false);
|
||||
subLayout->addWidget(cb);
|
||||
ui.itemChecks.append({id, cb});
|
||||
connect(cb, &QCheckBox::stateChanged, this,
|
||||
&MacroExportImportDialog::UpdateExportString);
|
||||
}
|
||||
|
||||
// Main checkbox toggles all sub-items.
|
||||
// Capture uiIdx (position in _extensionUIs after append).
|
||||
const int uiIdx = _extensionUIs.size();
|
||||
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
|
||||
[this, uiIdx](int state) {
|
||||
if (state == Qt::PartiallyChecked)
|
||||
return;
|
||||
const bool checked = (state == Qt::Checked);
|
||||
if (uiIdx < _extensionUIs.size()) {
|
||||
for (auto &[id, cb] :
|
||||
_extensionUIs[uiIdx].itemChecks) {
|
||||
QSignalBlocker b(cb);
|
||||
cb->setChecked(checked);
|
||||
}
|
||||
}
|
||||
UpdateExportString();
|
||||
});
|
||||
|
||||
innerSection->SetContent(subWidget, false);
|
||||
outerLayout->addWidget(innerSection);
|
||||
_extensionUIs.append(std::move(ui));
|
||||
}
|
||||
|
||||
outerSection->SetContent(outerContent, true);
|
||||
return outerSection;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
MacroExportImportDialog::MacroExportImportDialog(Type type,
|
||||
const QString &baseJson)
|
||||
: QDialog(nullptr),
|
||||
_baseJson(baseJson),
|
||||
_importExportString(new QPlainTextEdit(this)),
|
||||
_usePlainText(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.export.usePlainText")))
|
||||
{
|
||||
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
|
||||
|
||||
auto label = new QLabel(obs_module_text(
|
||||
type == Type::EXPORT_MACRO
|
||||
? "AdvSceneSwitcher.macroTab.export.info"
|
||||
|
|
@ -39,7 +177,12 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
|
|||
connect(_usePlainText, &QCheckBox::stateChanged, this,
|
||||
&MacroExportImportDialog::UsePlainTextChanged);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
|
||||
layout->addWidget(BuildExtensionSection());
|
||||
}
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(_importExportString);
|
||||
layout->addWidget(_usePlainText);
|
||||
|
|
@ -47,32 +190,57 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
|
|||
setLayout(layout);
|
||||
|
||||
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
|
||||
if (type == Type::EXPORT_MACRO) {
|
||||
RefreshExportText();
|
||||
}
|
||||
}
|
||||
|
||||
QString compressMacroString(const QString &input)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export string helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
QString MacroExportImportDialog::BuildExportJson() const
|
||||
{
|
||||
QByteArray inputData = input.toUtf8();
|
||||
auto compressedData = qCompress(inputData);
|
||||
QByteArray encodedData = compressedData.toBase64();
|
||||
return QString::fromUtf8(encodedData);
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(_baseJson.toStdString().c_str());
|
||||
if (!data) {
|
||||
return _baseJson;
|
||||
}
|
||||
|
||||
const auto &extensions = GetMacroExportExtensions();
|
||||
for (int i = 0;
|
||||
i < (int)extensions.size() && i < (int)_extensionUIs.size(); ++i) {
|
||||
if (!_extensionUIs[i].mainCheck->isChecked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList selectedIds;
|
||||
for (const auto &[id, cb] : _extensionUIs[i].itemChecks) {
|
||||
if (cb->isChecked()) {
|
||||
selectedIds << id;
|
||||
}
|
||||
}
|
||||
extensions[i].save(data, selectedIds);
|
||||
}
|
||||
|
||||
const char *json = obs_data_get_json(data);
|
||||
return json ? QString::fromUtf8(json) : QString{};
|
||||
}
|
||||
|
||||
QString decompressMacroString(const QString &input)
|
||||
void MacroExportImportDialog::RefreshExportText()
|
||||
{
|
||||
QByteArray encodedData = input.toUtf8();
|
||||
QByteArray compressedData = QByteArray::fromBase64(encodedData);
|
||||
auto outputData = qUncompress(compressedData);
|
||||
return QString::fromUtf8(outputData);
|
||||
const QString json = BuildExportJson();
|
||||
if (usePlainText) {
|
||||
_importExportString->setPlainText(json);
|
||||
} else {
|
||||
_importExportString->setPlainText(compressMacroString(json));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroExportImportDialog::ExportMacros(const QString &json)
|
||||
void MacroExportImportDialog::UpdateExportString()
|
||||
{
|
||||
MacroExportImportDialog dialog(
|
||||
MacroExportImportDialog::Type::EXPORT_MACRO);
|
||||
dialog._importExportString->setPlainText(compressMacroString(json));
|
||||
dialog.adjustSize();
|
||||
dialog.updateGeometry();
|
||||
dialog.exec();
|
||||
RefreshExportText();
|
||||
}
|
||||
|
||||
void MacroExportImportDialog::UsePlainTextChanged(int value)
|
||||
|
|
@ -90,26 +258,45 @@ void MacroExportImportDialog::UsePlainTextChanged(int value)
|
|||
usePlainText = value;
|
||||
}
|
||||
|
||||
static bool isValidData(const QString &json)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static entry points
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void MacroExportImportDialog::ExportMacros(obs_data_t *macroData)
|
||||
{
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(json.toStdString().c_str());
|
||||
return !!data;
|
||||
const char *rawJson = obs_data_get_json(macroData);
|
||||
const QString baseJson = rawJson ? QString::fromUtf8(rawJson)
|
||||
: QString{};
|
||||
|
||||
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson);
|
||||
dialog.adjustSize();
|
||||
dialog.updateGeometry();
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
bool MacroExportImportDialog::ImportMacros(QString &json)
|
||||
{
|
||||
MacroExportImportDialog dialog(
|
||||
MacroExportImportDialog::Type::IMPORT_MACRO);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
json = decompressMacroString(
|
||||
dialog._importExportString->toPlainText());
|
||||
if (!isValidData(json)) { // Fallback to support raw json format
|
||||
json = dialog._importExportString->toPlainText();
|
||||
}
|
||||
return true;
|
||||
MacroExportImportDialog dialog(Type::IMPORT_MACRO);
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
json = decompressMacroString(dialog._importExportString->toPlainText());
|
||||
if (!isValidData(json)) {
|
||||
// Fallback: support raw (uncompressed) JSON pasted directly.
|
||||
json = dialog._importExportString->toPlainText();
|
||||
}
|
||||
|
||||
// Invoke all extension load callbacks.
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(json.toStdString().c_str());
|
||||
if (data) {
|
||||
for (const auto &ext : GetMacroExportExtensions()) {
|
||||
ext.load(data, {});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -1,25 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "section.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
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 void ExportMacros(obs_data_t *macroData);
|
||||
static bool ImportMacros(QString &json);
|
||||
|
||||
private slots:
|
||||
void UsePlainTextChanged(int);
|
||||
void UpdateExportString();
|
||||
|
||||
private:
|
||||
explicit MacroExportImportDialog(Type type,
|
||||
const QString &baseJson = {});
|
||||
|
||||
// Per-extension widgets
|
||||
struct ExtensionUI {
|
||||
QCheckBox *mainCheck = nullptr;
|
||||
// (itemId, checkbox) pairs - empty when no per-item selection.
|
||||
QList<QPair<QString, QCheckBox *>> itemChecks;
|
||||
};
|
||||
|
||||
Section *BuildExtensionSection();
|
||||
QString BuildExportJson() const;
|
||||
void RefreshExportText();
|
||||
|
||||
QString _baseJson;
|
||||
QPlainTextEdit *_importExportString;
|
||||
QCheckBox *_usePlainText;
|
||||
QList<ExtensionUI> _extensionUIs;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#include "advanced-scene-switcher.hpp"
|
||||
#include "action-queue.hpp"
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "macro-export-import-dialog.hpp"
|
||||
|
|
@ -316,13 +315,9 @@ void AdvSceneSwitcher::ExportMacros() const
|
|||
obs_data_array_push_back(macroArray, obj);
|
||||
}
|
||||
obs_data_set_array(data, "macros", macroArray);
|
||||
SaveVariables(data);
|
||||
SaveActionQueues(data);
|
||||
obs_data_set_string(data, "version", g_GIT_TAG);
|
||||
auto json = obs_data_get_json(data);
|
||||
QString exportString(json);
|
||||
|
||||
MacroExportImportDialog::ExportMacros(exportString);
|
||||
MacroExportImportDialog::ExportMacros(data);
|
||||
}
|
||||
|
||||
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
|
||||
|
|
@ -384,8 +379,6 @@ void AdvSceneSwitcher::ImportMacros()
|
|||
ImportMacros();
|
||||
return;
|
||||
}
|
||||
ImportVariables(data);
|
||||
ImportQueues(data);
|
||||
|
||||
auto version = obs_data_get_string(data, "version");
|
||||
if (strcmp(version, g_GIT_TAG) != 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user