mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-10-17 14:08:55 -05:00
Add option to prompt user for input on macro import
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
358
lib/macro/macro-export-prompts-dialog.cpp
Normal file
358
lib/macro/macro-export-prompts-dialog.cpp
Normal 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
|
||||
57
lib/macro/macro-export-prompts-dialog.hpp
Normal file
57
lib/macro/macro-export-prompts-dialog.hpp
Normal 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
|
||||
64
lib/macro/macro-import-prompts-dialog.cpp
Normal file
64
lib/macro/macro-import-prompts-dialog.cpp
Normal 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
|
||||
30
lib/macro/macro-import-prompts-dialog.hpp
Normal file
30
lib/macro/macro-import-prompts-dialog.hpp
Normal 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
|
||||
74
lib/macro/macro-import-prompts.cpp
Normal file
74
lib/macro/macro-import-prompts.cpp
Normal 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
|
||||
24
lib/macro/macro-import-prompts.hpp
Normal file
24
lib/macro/macro-import-prompts.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user