diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 76b72857..61f721bf 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -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" diff --git a/lib/macro/macro-export-builtin-extensions.cpp b/lib/macro/macro-export-builtin-extensions.cpp index d0291099..d9de4c65 100644 --- a/lib/macro/macro-export-builtin-extensions.cpp +++ b/lib/macro/macro-export-builtin-extensions.cpp @@ -1,6 +1,7 @@ #include "macro-export-extensions.hpp" #include "action-queue.hpp" +#include "variable-usage.hpp" #include "variable.hpp" #include @@ -45,6 +46,14 @@ static bool setup() items.append({name, name}); } return items; + }, + [](const std::vector> ¯os) { + QStringList ids; + for (const auto &[name, count] : + GetVariablesUsedByMacros(macros)) { + ids << QString::fromStdString(name); + } + return ids; }}); // --- Action Queues --- diff --git a/lib/macro/macro-export-extensions.hpp b/lib/macro/macro-export-extensions.hpp index f994bb43..ca5084d8 100644 --- a/lib/macro/macro-export-extensions.hpp +++ b/lib/macro/macro-export-extensions.hpp @@ -3,6 +3,7 @@ #include "export-symbol-helper.hpp" #include +#include #include #include #include @@ -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>()> getExportItems = nullptr; + + // Optional: item IDs to check by default for the exported macros. + std::function> ¯os)> + getDefaultSelectedIds = nullptr; }; EXPORT void AddMacroExportExtension(const MacroExportExtension &ext); diff --git a/lib/macro/macro-export-import-dialog.cpp b/lib/macro/macro-export-import-dialog.cpp index 5ae5ea47..2aa60158 100644 --- a/lib/macro/macro-export-import-dialog.cpp +++ b/lib/macro/macro-export-import-dialog.cpp @@ -100,6 +100,125 @@ public: } }; +static QList> +getSortedExportItems(const MacroExportExtension &ext) +{ + auto items = ext.getExportItems(); + std::sort(items.begin(), items.end(), + [](const QPair &a, + const QPair &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 &a, - const QPair &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> ¯os) : 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> ¯os) { 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(); diff --git a/lib/macro/macro-export-import-dialog.hpp b/lib/macro/macro-export-import-dialog.hpp index 94e23931..bc71152e 100644 --- a/lib/macro/macro-export-import-dialog.hpp +++ b/lib/macro/macro-export-import-dialog.hpp @@ -8,20 +8,27 @@ #include #include +#include #include #include +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> ¯os); 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> ¯os = {}); // Per-extension widgets struct ExtensionUI { @@ -41,11 +49,17 @@ private: QList> 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> _macros; ExportPromptsState _promptsState; QPlainTextEdit *_importExportString; QCheckBox *_usePlainText; diff --git a/lib/macro/macro-tab.cpp b/lib/macro/macro-tab.cpp index a7710bfe..353dfe40 100644 --- a/lib/macro/macro-tab.cpp +++ b/lib/macro/macro-tab.cpp @@ -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( diff --git a/lib/variables/variable-usage.cpp b/lib/variables/variable-usage.cpp index 468ef999..9fc85e47 100644 --- a/lib/variables/variable-usage.cpp +++ b/lib/variables/variable-usage.cpp @@ -41,4 +41,18 @@ std::unordered_map GetVariablesUsedByMacro(Macro *macro) return collectReferences(macro); } +std::unordered_map +GetVariablesUsedByMacros(const std::vector> ¯os) +{ + std::unordered_map result; + auto lock = LockContext(); + for (const auto ¯o : macros) { + for (const auto &[name, count] : + collectReferences(macro.get())) { + result[name] += count; + } + } + return result; +} + } // namespace advss diff --git a/lib/variables/variable-usage.hpp b/lib/variables/variable-usage.hpp index 574168d1..f687a5ac 100644 --- a/lib/variables/variable-usage.hpp +++ b/lib/variables/variable-usage.hpp @@ -15,5 +15,7 @@ struct VariableUsage { std::unordered_map GetVariableUsage(); std::unordered_map GetVariablesUsedByMacro(Macro *macro); +std::unordered_map +GetVariablesUsedByMacros(const std::vector> ¯os); } // namespace advss