Select variables used by exported macros by default in export dialog

This commit is contained in:
WarmUpTill
2026-10-19 19:03:23 +02:00
committed by WarmUpTill
parent 1bfe56992b
commit 0924f14d20
8 changed files with 185 additions and 84 deletions

View File

@@ -233,6 +233,7 @@ AdvSceneSwitcher.macroTab.export.info="Paste the string below into the import di
AdvSceneSwitcher.macroTab.export.usePlainText="Use plain text"
AdvSceneSwitcher.macroTab.export.additionalContent="Export Additional Content"
AdvSceneSwitcher.macroTab.export.variables="Variables"
AdvSceneSwitcher.macroTab.export.used="(used by exported macros)"
AdvSceneSwitcher.macroTab.export.actionQueues="Action Queues"
AdvSceneSwitcher.macroTab.export.httpServers="HTTP Servers"
AdvSceneSwitcher.macroTab.export.macroScheduleEntries="Macro Schedule"

View File

@@ -1,6 +1,7 @@
#include "macro-export-extensions.hpp"
#include "action-queue.hpp"
#include "variable-usage.hpp"
#include "variable.hpp"
#include <obs.hpp>
@@ -45,6 +46,14 @@ static bool setup()
items.append({name, name});
}
return items;
},
[](const std::vector<std::shared_ptr<Macro>> &macros) {
QStringList ids;
for (const auto &[name, count] :
GetVariablesUsedByMacros(macros)) {
ids << QString::fromStdString(name);
}
return ids;
}});
// --- Action Queues ---

View File

@@ -3,6 +3,7 @@
#include "export-symbol-helper.hpp"
#include <functional>
#include <memory>
#include <obs-data.h>
#include <QList>
#include <QPair>
@@ -11,6 +12,8 @@
namespace advss {
class Macro;
struct MacroExportExtension {
const char *displayNameKey;
const char *jsonKey;
@@ -33,6 +36,11 @@ struct MacroExportExtension {
// checkbox in the export dialog.
std::function<QList<QPair<QString, QString>>()> getExportItems =
nullptr;
// Optional: item IDs to check by default for the exported macros.
std::function<QStringList(
const std::vector<std::shared_ptr<Macro>> &macros)>
getDefaultSelectedIds = nullptr;
};
EXPORT void AddMacroExportExtension(const MacroExportExtension &ext);

View File

@@ -100,6 +100,125 @@ public:
}
};
static QList<QPair<QString, QString>>
getSortedExportItems(const MacroExportExtension &ext)
{
auto items = ext.getExportItems();
std::sort(items.begin(), items.end(),
[](const QPair<QString, QString> &a,
const QPair<QString, QString> &b) {
return QString::localeAwareCompare(a.second,
b.second) < 0;
});
return items;
}
static QString getItemLabel(const QString &displayName, bool isDefault)
{
if (!isDefault) {
return displayName;
}
return displayName + " " +
obs_module_text("AdvSceneSwitcher.macroTab.export.used");
}
static void setInitialMainCheckState(QCheckBox *mainCheck, int checkedCount,
int itemCount)
{
if (checkedCount == 0) {
return;
}
if (checkedCount == itemCount) {
mainCheck->setChecked(true);
return;
}
mainCheck->setCheckState(Qt::PartiallyChecked);
mainCheck->setTristate(false);
}
void MacroExportImportDialog::AddExtensionCheckBox(
const MacroExportExtension &ext, int extIdx, QWidget *parent,
QLayout *layout)
{
ExtensionUI ui;
ui.mainCheck =
new QCheckBox(obs_module_text(ext.displayNameKey), parent);
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UpdateExportString);
layout->addWidget(ui.mainCheck);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
}
void MacroExportImportDialog::AddExtensionSection(
const MacroExportExtension &ext, int extIdx, QWidget *parent,
QLayout *layout)
{
const auto items = getSortedExportItems(ext);
if (items.isEmpty()) {
return;
}
ExtensionUI ui;
ui.mainCheck = new QCheckBox(obs_module_text(ext.displayNameKey));
auto itemsWidget = new QWidget();
auto itemsLayout = new QVBoxLayout(itemsWidget);
itemsLayout->setContentsMargins(4, 2, 4, 2);
itemsLayout->setSpacing(2);
const auto defaultIds = ext.getDefaultSelectedIds
? ext.getDefaultSelectedIds(_macros)
: QStringList();
int checkedCount = 0;
for (const auto &[id, displayName] : items) {
const bool isDefault = defaultIds.contains(id);
auto checkBox = new QCheckBox(
getItemLabel(displayName, isDefault), itemsWidget);
checkBox->setChecked(isDefault);
checkedCount += isDefault ? 1 : 0;
connect(checkBox, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UpdateExportString);
itemsLayout->addWidget(checkBox);
ui.itemChecks.append({id, checkBox});
}
setInitialMainCheckState(ui.mainCheck, checkedCount, items.size());
const int uiIdx = _extensionUIs.size();
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
[this, uiIdx](int state) {
SetExtensionItemsChecked(uiIdx, state);
});
auto section = new Section(200, parent);
section->AddHeaderWidget(ui.mainCheck);
section->SetContent(itemsWidget, true);
connect(section, &Section::AnimationFinished, this,
&MacroExportImportDialog::FitExtensionSection);
layout->addWidget(section);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
}
void MacroExportImportDialog::SetExtensionItemsChecked(int uiIdx, int state)
{
if (state == Qt::PartiallyChecked || uiIdx >= _extensionUIs.size()) {
return;
}
const bool checked = state == Qt::Checked;
for (auto &[id, checkBox] : _extensionUIs[uiIdx].itemChecks) {
QSignalBlocker blocker(checkBox);
checkBox->setChecked(checked);
}
UpdateExportString();
}
QWidget *MacroExportImportDialog::BuildExtensionWidget()
{
auto wrapper = new QWidget(this);
@@ -128,83 +247,13 @@ QWidget *MacroExportImportDialog::BuildExtensionWidget()
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));
_extensionOrder.push_back(extIdx);
continue;
if (ext.getExportItems) {
AddExtensionSection(ext, extIdx, outerContent,
outerLayout);
} else {
AddExtensionCheckBox(ext, extIdx, outerContent,
outerLayout);
}
// 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.
auto items = ext.getExportItems();
if (items.isEmpty()) {
continue;
}
std::sort(items.begin(), items.end(),
[](const QPair<QString, QString> &a,
const QPair<QString, QString> &b) {
return QString::localeAwareCompare(
a.second, b.second) < 0;
});
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, true);
connect(innerSection, &Section::AnimationFinished, this,
&MacroExportImportDialog::FitExtensionSection);
outerLayout->addWidget(innerSection);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
}
outerLayout->addStretch();
@@ -229,10 +278,12 @@ QWidget *MacroExportImportDialog::BuildExtensionWidget()
// Constructor
// ---------------------------------------------------------------------------
MacroExportImportDialog::MacroExportImportDialog(Type type,
const QString &baseJson)
MacroExportImportDialog::MacroExportImportDialog(
Type type, const QString &baseJson,
const std::vector<std::shared_ptr<Macro>> &macros)
: QDialog(nullptr),
_baseJson(baseJson),
_macros(macros),
_importExportString(new QPlainTextEdit(this)),
_usePlainText(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.export.usePlainText"))),
@@ -416,13 +467,15 @@ void MacroExportImportDialog::UsePlainTextChanged(int value)
// Static entry points
// ---------------------------------------------------------------------------
void MacroExportImportDialog::ExportMacros(obs_data_t *macroData)
void MacroExportImportDialog::ExportMacros(
obs_data_t *macroData,
const std::vector<std::shared_ptr<Macro>> &macros)
{
const char *rawJson = obs_data_get_json(macroData);
const QString baseJson = rawJson ? QString::fromUtf8(rawJson)
: QString{};
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson);
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson, macros);
dialog.adjustSize();
dialog.updateGeometry();
dialog.exec();

View File

@@ -8,20 +8,27 @@
#include <QPlainTextEdit>
#include <QPushButton>
#include <memory>
#include <obs-data.h>
#include <vector>
class QLayout;
class QScrollArea;
class QSplitter;
namespace advss {
class Macro;
struct MacroExportExtension;
class MacroExportImportDialog : public QDialog {
Q_OBJECT
public:
enum class Type { EXPORT_MACRO, IMPORT_MACRO };
static void ExportMacros(obs_data_t *macroData);
static void
ExportMacros(obs_data_t *macroData,
const std::vector<std::shared_ptr<Macro>> &macros);
static bool ImportMacros(QString &json);
private slots:
@@ -31,8 +38,9 @@ private slots:
void FitExtensionSection();
private:
explicit MacroExportImportDialog(Type type,
const QString &baseJson = {});
explicit MacroExportImportDialog(
Type type, const QString &baseJson = {},
const std::vector<std::shared_ptr<Macro>> &macros = {});
// Per-extension widgets
struct ExtensionUI {
@@ -41,11 +49,17 @@ private:
QList<QPair<QString, QCheckBox *>> itemChecks;
};
void AddExtensionCheckBox(const MacroExportExtension &ext, int extIdx,
QWidget *parent, QLayout *layout);
void AddExtensionSection(const MacroExportExtension &ext, int extIdx,
QWidget *parent, QLayout *layout);
void SetExtensionItemsChecked(int uiIdx, int state);
QWidget *BuildExtensionWidget();
QString BuildExportJson() const;
void RefreshExportText();
QString _baseJson;
std::vector<std::shared_ptr<Macro>> _macros;
ExportPromptsState _promptsState;
QPlainTextEdit *_importExportString;
QCheckBox *_usePlainText;

View File

@@ -333,7 +333,7 @@ void AdvSceneSwitcher::ExportMacros() const
obs_data_set_array(data, "macros", macroArray);
obs_data_set_string(data, "version", g_GIT_TAG);
MacroExportImportDialog::ExportMacros(data);
MacroExportImportDialog::ExportMacros(data, macros);
}
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(

View File

@@ -41,4 +41,18 @@ std::unordered_map<std::string, int> GetVariablesUsedByMacro(Macro *macro)
return collectReferences(macro);
}
std::unordered_map<std::string, int>
GetVariablesUsedByMacros(const std::vector<std::shared_ptr<Macro>> &macros)
{
std::unordered_map<std::string, int> result;
auto lock = LockContext();
for (const auto &macro : macros) {
for (const auto &[name, count] :
collectReferences(macro.get())) {
result[name] += count;
}
}
return result;
}
} // namespace advss

View File

@@ -15,5 +15,7 @@ struct VariableUsage {
std::unordered_map<std::string, VariableUsage> GetVariableUsage();
std::unordered_map<std::string, int> GetVariablesUsedByMacro(Macro *macro);
std::unordered_map<std::string, int>
GetVariablesUsedByMacros(const std::vector<std::shared_ptr<Macro>> &macros);
} // namespace advss