Add option to prompt user for input on macro import

This commit is contained in:
WarmUpTill
2026-10-09 19:48:34 +02:00
committed by WarmUpTill
parent 2683f6bb56
commit 16ff4c5679
12 changed files with 863 additions and 8 deletions

View File

@@ -144,8 +144,14 @@ target_sources(
lib/macro/macro-export-extensions.hpp
lib/macro/macro-export-import-dialog.cpp
lib/macro/macro-export-import-dialog.hpp
lib/macro/macro-export-prompts-dialog.cpp
lib/macro/macro-export-prompts-dialog.hpp
lib/macro/macro-helpers.cpp
lib/macro/macro-helpers.hpp
lib/macro/macro-import-prompts.cpp
lib/macro/macro-import-prompts.hpp
lib/macro/macro-import-prompts-dialog.cpp
lib/macro/macro-import-prompts-dialog.hpp
lib/macro/macro-input.cpp
lib/macro/macro-input.hpp
lib/macro/macro-list.cpp

View File

@@ -233,10 +233,26 @@ AdvSceneSwitcher.macroTab.export.httpServers="HTTP Servers"
AdvSceneSwitcher.macroTab.export.macroScheduleEntries="Macro Schedule"
AdvSceneSwitcher.macroTab.export.twitchConnections="Twitch Connections"
AdvSceneSwitcher.macroTab.export.websocketConnections="Websocket Connections"
AdvSceneSwitcher.macroTab.export.importPrompts.edit="Define import prompts..."
AdvSceneSwitcher.macroTab.export.importPrompts.info="Mark values below as \"replaceable\": whoever imports this macro will be prompted to supply their own value for each one you add (e.g. a scene, a source, or a file path that only exists on your setup)."
AdvSceneSwitcher.macroTab.export.importPrompts.scope="Apply to:"
AdvSceneSwitcher.macroTab.export.importPrompts.scope.all="All exported macros"
AdvSceneSwitcher.macroTab.export.importPrompts.values="Values found in the selected macro(s)"
AdvSceneSwitcher.macroTab.export.importPrompts.value="Value"
AdvSceneSwitcher.macroTab.export.importPrompts.occurrences="Occurrences"
AdvSceneSwitcher.macroTab.export.importPrompts.add="Mark as replaceable..."
AdvSceneSwitcher.macroTab.export.importPrompts.defined="Prompts defined for this export"
AdvSceneSwitcher.macroTab.export.importPrompts.remove="Remove"
AdvSceneSwitcher.macroTab.export.importPrompts.label="Prompt shown to the recipient"
AdvSceneSwitcher.macroTab.export.importPrompts.type="Type of value"
AdvSceneSwitcher.macroTab.export.importPrompts.defaultValue="Default value"
AdvSceneSwitcher.macroTab.export.importPrompts.options="Options to choose from:"
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 with import of \"%1\" as the name is already in use!\nDo want to choose a new name for the imported \"%2\"? (It will be skipped otherwise)"
AdvSceneSwitcher.macroTab.import.prompts.header="Provide values for the imported macros"
AdvSceneSwitcher.macroTab.import.prompts.info="This import contains values the macro's author marked as needing your input.\nDefaults provided by the author are already filled in.\nFields left empty are imported as empty values."
AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"

View File

@@ -1,10 +1,13 @@
#include "macro-export-import-dialog.hpp"
#include "json-helpers.hpp"
#include "macro-export-extensions.hpp"
#include "macro-import-prompts-dialog.hpp"
#include "obs-module-helper.hpp"
#include "section.hpp"
#include <obs.hpp>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayout>
#include <QScrollArea>
@@ -43,10 +46,60 @@ static bool isValidData(const QString &json)
return !!data;
}
static bool runImportPrompts(obs_data_t *data)
{
auto prompts = LoadImportPrompts(data);
if (prompts.empty()) {
return true;
}
MacroImportPromptsDialog dialog(nullptr, prompts);
if (dialog.exec() != QDialog::Accepted) {
return false;
}
auto replacements = dialog.GetReplacements();
if (replacements.empty()) {
return true;
}
auto macrosJson =
GetJsonField(obs_data_get_json(data), "macros").value_or("[]");
for (auto &[placeholder, value] : replacements) {
macrosJson =
ReplaceJsonStringValue(macrosJson, placeholder, value);
}
std::string wrapped = "{\"macros\":" + macrosJson + "}";
OBSDataAutoRelease wrapper = obs_data_create_from_json(wrapped.c_str());
if (!wrapper) {
return true;
}
OBSDataArrayAutoRelease macros = obs_data_get_array(wrapper, "macros");
obs_data_set_array(data, "macros", macros);
return true;
}
// ---------------------------------------------------------------------------
// Extension section
// ---------------------------------------------------------------------------
class ContentSizedScrollArea : public QScrollArea {
public:
using QScrollArea::QScrollArea;
// Skip scrollbars and so on to avoid gap in layout
QSize minimumSizeHint() const override
{
auto hint = QScrollArea::minimumSizeHint();
if (widget()) {
hint.setHeight(std::min(hint.height(),
widget()->sizeHint().height()));
}
return hint;
}
};
QWidget *MacroExportImportDialog::BuildExtensionWidget()
{
auto wrapper = new QWidget(this);
@@ -147,6 +200,8 @@ QWidget *MacroExportImportDialog::BuildExtensionWidget()
});
innerSection->SetContent(subWidget, true);
connect(innerSection, &Section::AnimationFinished, this,
&MacroExportImportDialog::FitExtensionSection);
outerLayout->addWidget(innerSection);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
@@ -154,13 +209,19 @@ QWidget *MacroExportImportDialog::BuildExtensionWidget()
outerLayout->addStretch();
auto scrollArea = new QScrollArea(wrapper);
auto scrollArea = new ContentSizedScrollArea(wrapper);
_extensionScrollArea = scrollArea;
scrollArea->setWidget(outerContent);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setFrameShape(QFrame::NoFrame);
wrapperLayout->addWidget(scrollArea);
auto promptsLayout = new QHBoxLayout();
promptsLayout->addWidget(_editImportPrompts);
promptsLayout->addStretch();
wrapperLayout->addLayout(promptsLayout);
return wrapper;
}
@@ -174,8 +235,18 @@ MacroExportImportDialog::MacroExportImportDialog(Type type,
_baseJson(baseJson),
_importExportString(new QPlainTextEdit(this)),
_usePlainText(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.export.usePlainText")))
"AdvSceneSwitcher.macroTab.export.usePlainText"))),
_editImportPrompts(new QPushButton(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.edit")))
{
if (type == Type::EXPORT_MACRO) {
OBSDataAutoRelease data = obs_data_create_from_json(
_baseJson.toStdString().c_str());
if (data) {
_promptsState.Load(data);
}
}
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
auto label = new QLabel(obs_module_text(
@@ -200,12 +271,16 @@ MacroExportImportDialog::MacroExportImportDialog(Type type,
connect(_usePlainText, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UsePlainTextChanged);
_editImportPrompts->setVisible(type == Type::EXPORT_MACRO);
connect(_editImportPrompts, &QPushButton::clicked, this,
&MacroExportImportDialog::EditImportPrompts);
auto layout = new QVBoxLayout(this);
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
auto splitter = new QSplitter(Qt::Vertical, this);
splitter->setChildrenCollapsible(false);
splitter->addWidget(BuildExtensionWidget());
_splitter = splitter;
auto textWidget = new QWidget(splitter);
auto textLayout = new QVBoxLayout(textWidget);
@@ -214,12 +289,16 @@ MacroExportImportDialog::MacroExportImportDialog(Type type,
textLayout->addWidget(_importExportString);
textLayout->addWidget(_usePlainText);
splitter->addWidget(textWidget);
splitter->addWidget(BuildExtensionWidget());
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 0);
layout->addWidget(splitter);
} else {
layout->addWidget(label);
layout->addWidget(_importExportString);
layout->addWidget(_usePlainText);
layout->addWidget(_editImportPrompts);
}
layout->addWidget(buttons);
@@ -244,6 +323,8 @@ QString MacroExportImportDialog::BuildExportJson() const
return _baseJson;
}
_promptsState.Apply(data);
const auto &extensions = GetMacroExportExtensions();
for (int i = 0; i < (int)_extensionUIs.size(); ++i) {
const auto &ui = _extensionUIs[i];
@@ -286,6 +367,36 @@ void MacroExportImportDialog::UpdateExportString()
RefreshExportText();
}
void MacroExportImportDialog::FitExtensionSection()
{
if (!_splitter || !_extensionScrollArea ||
!_extensionScrollArea->widget()) {
return;
}
const int delta = _extensionScrollArea->widget()->sizeHint().height() -
_extensionScrollArea->viewport()->height();
if (delta == 0) {
return;
}
auto sizes = _splitter->sizes();
const int minTextHeight =
_splitter->widget(0)->minimumSizeHint().height();
const int change = std::min(delta, sizes[0] - minTextHeight);
sizes[0] -= change;
sizes[1] += change;
_splitter->setSizes(sizes);
}
void MacroExportImportDialog::EditImportPrompts()
{
if (!MacroExportPromptsDialog::Edit(this, _promptsState)) {
return;
}
RefreshExportText();
}
void MacroExportImportDialog::UsePlainTextChanged(int value)
{
if (usePlainText == (!!value)) {
@@ -330,13 +441,19 @@ bool MacroExportImportDialog::ImportMacros(QString &json)
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, {});
}
if (!data) {
return true;
}
if (!runImportPrompts(data)) {
return false;
}
json = QString::fromUtf8(obs_data_get_json(data));
for (const auto &ext : GetMacroExportExtensions()) {
ext.load(data, {});
}
return true;

View File

@@ -1,13 +1,19 @@
#pragma once
#include "macro-export-prompts-dialog.hpp"
#include <QCheckBox>
#include <QDialog>
#include <QList>
#include <QPlainTextEdit>
#include <QPushButton>
#include <obs-data.h>
#include <vector>
class QScrollArea;
class QSplitter;
namespace advss {
class MacroExportImportDialog : public QDialog {
@@ -21,6 +27,8 @@ public:
private slots:
void UsePlainTextChanged(int);
void UpdateExportString();
void EditImportPrompts();
void FitExtensionSection();
private:
explicit MacroExportImportDialog(Type type,
@@ -38,8 +46,12 @@ private:
void RefreshExportText();
QString _baseJson;
ExportPromptsState _promptsState;
QPlainTextEdit *_importExportString;
QCheckBox *_usePlainText;
QPushButton *_editImportPrompts;
QSplitter *_splitter = nullptr;
QScrollArea *_extensionScrollArea = nullptr;
QList<ExtensionUI> _extensionUIs;
std::vector<int> _extensionOrder;
};

View File

@@ -0,0 +1,358 @@
#include "macro-export-prompts-dialog.hpp"
#include "json-helpers.hpp"
#include "obs-module-helper.hpp"
#include "string-list.hpp"
#include <nlohmann/json.hpp>
#include <obs.hpp>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>
namespace advss {
static std::string getArrayElementJson(const std::string &arrayJson, int index)
{
try {
auto j = nlohmann::json::parse(arrayJson);
if (!j.is_array() || index < 0 || index >= (int)j.size()) {
return "{}";
}
return j.at(index).dump();
} catch (const nlohmann::json::exception &) {
return "{}";
}
}
static std::string setArrayElementJson(const std::string &arrayJson, int index,
const std::string &elementJson)
{
try {
auto j = nlohmann::json::parse(arrayJson);
if (j.is_array() && index >= 0 && index < (int)j.size()) {
j[index] = nlohmann::json::parse(elementJson);
}
return j.dump();
} catch (const nlohmann::json::exception &) {
return arrayJson;
}
}
static int arraySize(const std::string &arrayJson)
{
try {
auto j = nlohmann::json::parse(arrayJson);
return j.is_array() ? (int)j.size() : 0;
} catch (const nlohmann::json::exception &) {
return 0;
}
}
void ExportPromptsState::Load(obs_data_t *data)
{
OBSDataArrayAutoRelease macros = obs_data_get_array(data, "macros");
OBSDataAutoRelease wrapper = obs_data_create();
obs_data_set_array(wrapper, "macros", macros);
macrosArrayJson = GetJsonField(obs_data_get_json(wrapper), "macros")
.value_or("[]");
entries.clear();
}
void ExportPromptsState::Apply(obs_data_t *data) const
{
if (entries.empty()) {
return;
}
std::string wrapped = "{\"macros\":" + macrosArrayJson + "}";
OBSDataAutoRelease wrapper = obs_data_create_from_json(wrapped.c_str());
if (!wrapper) {
return;
}
OBSDataArrayAutoRelease macros = obs_data_get_array(wrapper, "macros");
obs_data_set_array(data, "macros", macros);
std::vector<ImportPrompt> prompts;
for (auto &entry : entries) {
prompts.push_back(entry.prompt);
}
SaveImportPrompts(data, prompts);
}
MacroExportPromptsDialog::MacroExportPromptsDialog(
QWidget *parent, const ExportPromptsState &state)
: QDialog(parent),
_state(state)
{
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
auto layout = new QVBoxLayout(this);
auto info = new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.info"));
info->setWordWrap(true);
layout->addWidget(info);
auto scopeLayout = new QHBoxLayout();
scopeLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.scope")));
_scope = new QComboBox(this);
_scope->addItem(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.scope.all"));
int count = arraySize(_state.macrosArrayJson);
for (int i = 0; i < count; i++) {
auto name = GetJsonField(getArrayElementJson(
_state.macrosArrayJson, i),
"name")
.value_or("");
_scope->addItem(QString::fromStdString(name));
}
connect(_scope, qOverload<int>(&QComboBox::currentIndexChanged), this,
&MacroExportPromptsDialog::ScopeChanged);
scopeLayout->addWidget(_scope, 1);
layout->addLayout(scopeLayout);
auto contentLayout = new QHBoxLayout();
auto valuesLayout = new QVBoxLayout();
valuesLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.values")));
_values = new QTableWidget(0, 2, this);
_values->setHorizontalHeaderLabels(
{obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.value"),
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.occurrences")});
_values->horizontalHeader()->setStretchLastSection(false);
_values->horizontalHeader()->setSectionResizeMode(0,
QHeaderView::Stretch);
_values->setEditTriggers(QAbstractItemView::NoEditTriggers);
_values->setSelectionBehavior(QAbstractItemView::SelectRows);
_values->setSelectionMode(QAbstractItemView::SingleSelection);
valuesLayout->addWidget(_values);
auto addButton = new QPushButton(
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.add"),
this);
connect(addButton, &QPushButton::clicked, this,
&MacroExportPromptsDialog::AddPrompt);
valuesLayout->addWidget(addButton);
contentLayout->addLayout(valuesLayout, 1);
auto promptsLayout = new QVBoxLayout();
promptsLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.defined")));
_promptsList = new QListWidget(this);
promptsLayout->addWidget(_promptsList);
auto removeButton = new QPushButton(
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.remove"),
this);
connect(removeButton, &QPushButton::clicked, this,
&MacroExportPromptsDialog::RemovePrompt);
promptsLayout->addWidget(removeButton);
contentLayout->addLayout(promptsLayout, 1);
layout->addLayout(contentLayout);
auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
buttons->setCenterButtons(true);
layout->addWidget(buttons);
resize(600, 400);
RefreshValuesTable();
RefreshPromptsList();
}
std::string MacroExportPromptsDialog::CurrentScopeJson() const
{
int idx = _scope->currentIndex();
if (idx <= 0) {
return _state.macrosArrayJson;
}
return getArrayElementJson(_state.macrosArrayJson, idx - 1);
}
void MacroExportPromptsDialog::ApplyCurrentScopeJson(const std::string &json)
{
int idx = _scope->currentIndex();
if (idx <= 0) {
_state.macrosArrayJson = json;
return;
}
_state.macrosArrayJson =
setArrayElementJson(_state.macrosArrayJson, idx - 1, json);
}
void MacroExportPromptsDialog::RefreshValuesTable()
{
auto values = CollectDistinctJsonStringValues(CurrentScopeJson());
_values->setRowCount(0);
for (auto &[value, occurrences] : values) {
int row = _values->rowCount();
_values->insertRow(row);
_values->setItem(
row, 0,
new QTableWidgetItem(QString::fromStdString(value)));
_values->setItem(
row, 1,
new QTableWidgetItem(QString::number(occurrences)));
}
}
void MacroExportPromptsDialog::RefreshPromptsList()
{
_promptsList->clear();
for (auto &entry : _state.entries) {
_promptsList->addItem(QString("%1 (%2)").arg(
QString::fromStdString(entry.prompt.label),
QString::fromStdString(entry.scope)));
}
}
void MacroExportPromptsDialog::ScopeChanged(int)
{
RefreshValuesTable();
}
void MacroExportPromptsDialog::AddPrompt()
{
int row = _values->currentRow();
if (row < 0) {
return;
}
auto value = _values->item(row, 0)->text();
QDialog dialog(this);
dialog.setWindowTitle(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.add"));
auto layout = new QVBoxLayout(&dialog);
auto form = new QFormLayout();
auto labelEdit = new QLineEdit(&dialog);
form->addRow(
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.label"),
labelEdit);
auto typeCombo = new QComboBox(&dialog);
PopulateValueInputTypeSelection(typeCombo);
typeCombo->setCurrentIndex(
typeCombo->findData(static_cast<int>(ValueInputType::String)));
form->addRow(
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.type"),
typeCombo);
auto defaultValueEdit = new QLineEdit(value, &dialog);
form->addRow(
obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.defaultValue"),
defaultValueEdit);
auto optionsWidget = new QWidget(&dialog);
auto optionsLayout = new QVBoxLayout(optionsWidget);
optionsLayout->setContentsMargins(0, 0, 0, 0);
optionsLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.importPrompts.options")));
auto optionsEdit = new StringListEdit(optionsWidget);
optionsEdit->SetStringList({value.toStdString()});
optionsLayout->addWidget(optionsEdit);
optionsWidget->hide();
connect(typeCombo, qOverload<int>(&QComboBox::currentIndexChanged),
&dialog, [&dialog, typeCombo, optionsWidget](int) {
optionsWidget->setVisible(
typeCombo->currentData().toInt() ==
static_cast<int>(ValueInputType::Selection));
dialog.adjustSize();
});
layout->addLayout(form);
layout->addWidget(optionsWidget);
auto buttons = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
connect(buttons, &QDialogButtonBox::accepted, &dialog,
&QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog,
&QDialog::reject);
buttons->setCenterButtons(true);
layout->addWidget(buttons);
if (dialog.exec() != QDialog::Accepted) {
return;
}
auto label = labelEdit->text().toStdString();
if (label.empty()) {
return;
}
auto type =
static_cast<ValueInputType>(typeCombo->currentData().toInt());
ExportPromptsState::Entry entry;
entry.prompt.label = label;
entry.prompt.type = type;
entry.prompt.defaultValue = defaultValueEdit->text().toStdString();
if (type == ValueInputType::Selection) {
for (const auto &option : optionsEdit->GetStringList()) {
entry.prompt.options.push_back(
option.UnresolvedValue());
}
}
entry.scope = _scope->currentText().toStdString();
auto placeholder = GenerateImportPlaceholder();
ApplyCurrentScopeJson(ReplaceJsonStringValue(
CurrentScopeJson(), value.toStdString(), placeholder));
entry.prompt.placeholders.push_back(placeholder);
entry.originalValues.push_back(value.toStdString());
_state.entries.emplace_back(std::move(entry));
RefreshValuesTable();
RefreshPromptsList();
}
void MacroExportPromptsDialog::RemovePrompt()
{
int idx = _promptsList->currentRow();
if (idx < 0 || idx >= (int)_state.entries.size()) {
return;
}
auto &entry = _state.entries[idx];
for (size_t i = 0; i < entry.prompt.placeholders.size(); i++) {
_state.macrosArrayJson = ReplaceJsonStringValue(
_state.macrosArrayJson, entry.prompt.placeholders[i],
entry.originalValues[i]);
}
_state.entries.erase(_state.entries.begin() + idx);
RefreshValuesTable();
RefreshPromptsList();
}
bool MacroExportPromptsDialog::Edit(QWidget *parent, ExportPromptsState &state)
{
MacroExportPromptsDialog dialog(parent, state);
if (dialog.exec() != QDialog::Accepted) {
return false;
}
state = dialog._state;
return true;
}
} // namespace advss

View File

@@ -0,0 +1,57 @@
#pragma once
#include "macro-import-prompts.hpp"
#include <obs-data.h>
#include <QDialog>
#include <string>
#include <vector>
class QComboBox;
class QTableWidget;
class QListWidget;
namespace advss {
struct ExportPromptsState {
struct Entry {
ImportPrompt prompt;
std::vector<std::string> originalValues;
std::string scope;
};
void Load(obs_data_t *data);
void Apply(obs_data_t *data) const;
std::string macrosArrayJson;
std::vector<Entry> entries;
};
class MacroExportPromptsDialog : public QDialog {
Q_OBJECT
public:
static bool Edit(QWidget *parent, ExportPromptsState &state);
private slots:
void ScopeChanged(int);
void AddPrompt();
void RemovePrompt();
private:
MacroExportPromptsDialog(QWidget *parent,
const ExportPromptsState &state);
void RefreshValuesTable();
void RefreshPromptsList();
std::string CurrentScopeJson() const;
void ApplyCurrentScopeJson(const std::string &json);
ExportPromptsState _state;
QComboBox *_scope;
QTableWidget *_values;
QListWidget *_promptsList;
};
} // namespace advss

View File

@@ -0,0 +1,64 @@
#include "macro-import-prompts-dialog.hpp"
#include "help-icon.hpp"
#include "obs-module-helper.hpp"
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>
namespace advss {
MacroImportPromptsDialog::MacroImportPromptsDialog(
QWidget *parent, const std::vector<ImportPrompt> &prompts)
: QDialog(parent)
{
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
auto layout = new QVBoxLayout(this);
auto headerLayout = new QHBoxLayout();
headerLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.import.prompts.header")));
headerLayout->addWidget(new HelpIcon(
obs_module_text("AdvSceneSwitcher.macroTab.import.prompts.info"),
this));
headerLayout->addStretch();
layout->addLayout(headerLayout);
auto form = new QFormLayout();
for (const auto &prompt : prompts) {
QStringList options;
for (const auto &option : prompt.options) {
options << QString::fromStdString(option);
}
auto input = new ValueInputWidget(
prompt.type, prompt.defaultValue, options, this);
form->addRow(QString::fromStdString(prompt.label), input);
_rows.push_back({prompt, input});
}
layout->addLayout(form);
auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
buttons->setCenterButtons(true);
layout->addWidget(buttons);
}
std::unordered_map<std::string, std::string>
MacroImportPromptsDialog::GetReplacements() const
{
std::unordered_map<std::string, std::string> result;
for (const auto &row : _rows) {
auto value = row.input->Value();
for (const auto &placeholder : row.prompt.placeholders) {
result[placeholder] = value;
}
}
return result;
}
} // namespace advss

View File

@@ -0,0 +1,30 @@
#pragma once
#include "macro-import-prompts.hpp"
#include <QDialog>
#include <string>
#include <unordered_map>
#include <vector>
namespace advss {
class MacroImportPromptsDialog : public QDialog {
Q_OBJECT
public:
MacroImportPromptsDialog(QWidget *parent,
const std::vector<ImportPrompt> &prompts);
std::unordered_map<std::string, std::string> GetReplacements() const;
private:
struct Row {
ImportPrompt prompt;
ValueInputWidget *input;
};
std::vector<Row> _rows;
};
} // namespace advss

View File

@@ -0,0 +1,74 @@
#include "macro-import-prompts.hpp"
namespace advss {
static void saveStrings(obs_data_t *obj, const char *name,
const std::vector<std::string> &strings)
{
obs_data_array_t *array = obs_data_array_create();
for (auto &string : strings) {
obs_data_t *entry = obs_data_create();
obs_data_set_string(entry, "value", string.c_str());
obs_data_array_push_back(array, entry);
obs_data_release(entry);
}
obs_data_set_array(obj, name, array);
obs_data_array_release(array);
}
static std::vector<std::string> loadStrings(obs_data_t *obj, const char *name)
{
std::vector<std::string> result;
obs_data_array_t *array = obs_data_get_array(obj, name);
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
obs_data_t *entry = obs_data_array_item(array, i);
result.emplace_back(obs_data_get_string(entry, "value"));
obs_data_release(entry);
}
obs_data_array_release(array);
return result;
}
void SaveImportPrompts(obs_data_t *obj,
const std::vector<ImportPrompt> &prompts)
{
obs_data_array_t *array = obs_data_array_create();
for (auto &prompt : prompts) {
obs_data_t *entry = obs_data_create();
obs_data_set_string(entry, "label", prompt.label.c_str());
obs_data_set_int(entry, "type", static_cast<int>(prompt.type));
saveStrings(entry, "placeholders", prompt.placeholders);
obs_data_set_string(entry, "defaultValue",
prompt.defaultValue.c_str());
saveStrings(entry, "options", prompt.options);
obs_data_array_push_back(array, entry);
obs_data_release(entry);
}
obs_data_set_array(obj, "importPrompts", array);
obs_data_array_release(array);
}
std::vector<ImportPrompt> LoadImportPrompts(obs_data_t *obj)
{
std::vector<ImportPrompt> result;
obs_data_array_t *array = obs_data_get_array(obj, "importPrompts");
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
obs_data_t *entry = obs_data_array_item(array, i);
ImportPrompt prompt;
prompt.label = obs_data_get_string(entry, "label");
prompt.type = static_cast<ValueInputType>(
obs_data_get_int(entry, "type"));
prompt.placeholders = loadStrings(entry, "placeholders");
prompt.defaultValue =
obs_data_get_string(entry, "defaultValue");
prompt.options = loadStrings(entry, "options");
result.emplace_back(std::move(prompt));
obs_data_release(entry);
}
obs_data_array_release(array);
return result;
}
} // namespace advss

View File

@@ -0,0 +1,24 @@
#pragma once
#include "export-symbol-helper.hpp"
#include "value-input.hpp"
#include <obs-data.h>
#include <string>
#include <vector>
namespace advss {
struct ImportPrompt {
std::string label;
ValueInputType type = ValueInputType::String;
std::vector<std::string> placeholders;
std::string defaultValue;
std::vector<std::string> options;
};
EXPORT void SaveImportPrompts(obs_data_t *obj,
const std::vector<ImportPrompt> &prompts);
EXPORT std::vector<ImportPrompt> LoadImportPrompts(obs_data_t *obj);
} // namespace advss

View File

@@ -6,6 +6,9 @@
#endif
#include <nlohmann/json.hpp>
#include <QJsonDocument>
#include <unordered_map>
#include <util/bmem.h>
#include <util/platform.h>
namespace advss {
@@ -118,4 +121,89 @@ ExtractSingleJsonArrayElement(const std::string &jsonStr)
return {};
}
static void collectStrings(const nlohmann::json &json,
std::vector<std::pair<std::string, int>> &result,
std::unordered_map<std::string, size_t> &index)
{
if (json.is_string()) {
auto value = json.get<std::string>();
auto it = index.find(value);
if (it == index.end()) {
index[value] = result.size();
result.emplace_back(value, 1);
} else {
result[it->second].second++;
}
return;
}
if (json.is_object()) {
for (const auto &item : json.items()) {
collectStrings(item.value(), result, index);
}
return;
}
if (json.is_array()) {
for (const auto &value : json) {
collectStrings(value, result, index);
}
}
}
std::vector<std::pair<std::string, int>>
CollectDistinctJsonStringValues(const std::string &jsonStr)
{
std::vector<std::pair<std::string, int>> result;
try {
nlohmann::json json = nlohmann::json::parse(jsonStr);
std::unordered_map<std::string, size_t> index;
collectStrings(json, result, index);
} catch (const nlohmann::json::exception &) {
}
return result;
}
static void replaceStrings(nlohmann::json &json, const std::string &oldValue,
const std::string &newValue)
{
if (json.is_string()) {
if (json.get<std::string>() == oldValue) {
json = newValue;
}
return;
}
if (json.is_object()) {
for (auto &item : json.items()) {
replaceStrings(item.value(), oldValue, newValue);
}
return;
}
if (json.is_array()) {
for (auto &value : json) {
replaceStrings(value, oldValue, newValue);
}
}
}
std::string ReplaceJsonStringValue(const std::string &jsonStr,
const std::string &oldValue,
const std::string &newValue)
{
try {
nlohmann::json json = nlohmann::json::parse(jsonStr);
replaceStrings(json, oldValue, newValue);
return json.dump();
} catch (const nlohmann::json::exception &) {
return jsonStr;
}
}
std::string GenerateImportPlaceholder()
{
char *uuid = os_generate_uuid();
std::string result =
"ADVSS_IMPORT_PLACEHOLDER_" + std::string(uuid ? uuid : "");
bfree(uuid);
return result;
}
} // namespace advss

View File

@@ -4,6 +4,8 @@
#include <QString>
#include <string>
#include <utility>
#include <vector>
namespace advss {
@@ -20,4 +22,11 @@ EXPORT std::optional<std::string> AccessJsonArrayIndex(const std::string &json,
EXPORT std::optional<std::string>
ExtractSingleJsonArrayElement(const std::string &json);
EXPORT std::vector<std::pair<std::string, int>>
CollectDistinctJsonStringValues(const std::string &json);
EXPORT std::string ReplaceJsonStringValue(const std::string &json,
const std::string &oldValue,
const std::string &newValue);
EXPORT std::string GenerateImportPlaceholder();
} // namespace advss