diff --git a/CMakeLists.txt b/CMakeLists.txt index 887b53ae..3ef43b9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,9 @@ target_sources( lib/utils/filter-combo-box.hpp lib/utils/first-run-wizard.cpp lib/utils/first-run-wizard.hpp + lib/utils/first-run-wizard-helpers.hpp + lib/utils/first-run-wizard-window.cpp + lib/utils/first-run-wizard-window.hpp lib/utils/help-icon.hpp lib/utils/help-icon.cpp lib/utils/item-selection-helpers.cpp diff --git a/lib/general.cpp b/lib/general.cpp index e872f425..a93f5169 100644 --- a/lib/general.cpp +++ b/lib/general.cpp @@ -452,7 +452,7 @@ void AdvSceneSwitcher::CheckFirstTimeSetup() } bool wasSkipped = false; - auto macro = FirstRunWizard::ShowWizard(this, &wasSkipped); + auto macro = wiz::FirstRunWizard::ShowWizard(this, &wasSkipped); if (macro) { renameMacroIfNecessary(macro); QTimer::singleShot(0, this, @@ -466,7 +466,7 @@ void AdvSceneSwitcher::CheckFirstTimeSetup() void AdvSceneSwitcher::on_openSetupWizard_clicked() { - auto macro = FirstRunWizard::ShowWizard(this); + auto macro = wiz::FirstRunWizard::ShowWizard(this); if (!macro) { return; } diff --git a/lib/utils/first-run-wizard-helpers.hpp b/lib/utils/first-run-wizard-helpers.hpp new file mode 100644 index 00000000..d1c0d108 --- /dev/null +++ b/lib/utils/first-run-wizard-helpers.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "log-helper.hpp" +#include "macro.hpp" +#include "macro-action-factory.hpp" +#include "macro-condition-factory.hpp" + +#include + +#include +#include + +#include + +namespace advss::wiz::detail { + +static bool addCondition(advss::Macro *macro, const std::string &id, + obs_data_t *data) +{ + auto cond = MacroConditionFactory::Create(id, macro); + if (!cond) { + blog(LOG_WARNING, + "FirstRunWizard: condition factory returned null for '%s'", + id.c_str()); + return false; + } + if (!cond->Load(data)) { + blog(LOG_WARNING, + "FirstRunWizard: condition Load() failed for '%s'", + id.c_str()); + return false; + } + macro->Conditions().emplace_back(cond); + return true; +} + +static bool addAction(advss::Macro *macro, const std::string &id, + obs_data_t *data) +{ + auto action = MacroActionFactory::Create(id, macro); + if (!action) { + blog(LOG_WARNING, + "FirstRunWizard: action factory returned null for '%s'", + id.c_str()); + return false; + } + if (!action->Load(data)) { + blog(LOG_WARNING, + "FirstRunWizard: action Load() failed for '%s'", + id.c_str()); + return false; + } + macro->Actions().emplace_back(action); + return true; +} + +static bool addElseAction(advss::Macro *macro, const std::string &id, + obs_data_t *data) +{ + auto action = MacroActionFactory::Create(id, macro); + if (!action) { + blog(LOG_WARNING, + "FirstRunWizard: else-action factory returned null for '%s'", + id.c_str()); + return false; + } + if (!action->Load(data)) { + blog(LOG_WARNING, + "FirstRunWizard: else-action Load() failed for '%s'", + id.c_str()); + return false; + } + macro->ElseActions().emplace_back(action); + return true; +} + +static void setupSummaryLabel(QLabel *label) +{ + label->setWordWrap(true); + label->setTextFormat(Qt::RichText); + label->setFrameShape(QFrame::StyledPanel); + label->setContentsMargins(12, 12, 12, 12); +} + +} // namespace advss::wiz::detail diff --git a/lib/utils/first-run-wizard-window.cpp b/lib/utils/first-run-wizard-window.cpp new file mode 100644 index 00000000..76ebdeeb --- /dev/null +++ b/lib/utils/first-run-wizard-window.cpp @@ -0,0 +1,308 @@ +#include "first-run-wizard-window.hpp" +#include "first-run-wizard-helpers.hpp" + +#include "macro-settings.hpp" +#include "obs-module-helper.hpp" +#include "platform-funcs.hpp" +#include "selection-helpers.hpp" + +#include + +#include +#include +#include +#include + +namespace advss { + +namespace wiz { + +static constexpr char kConditionIdWindow[] = "window"; +static constexpr char kActionIdSceneSwitch[] = "scene_switch"; + +static QString DetectFocusedWindow() +{ + return QString::fromStdString(GetCurrentWindowTitle()); +} + +static QString EscapeForRegex(const QString &input) +{ + return QRegularExpression::escape(input); +} + +// =========================================================================== +// WindowSceneSelectionPage +// =========================================================================== + +WindowSceneSelectionPage::WindowSceneSelectionPage(QWidget *parent) + : QWizardPage(parent) +{ + setTitle(obs_module_text("FirstRunWizard.scene.title")); + setSubTitle(obs_module_text("FirstRunWizard.scene.subtitle")); + + auto label = + new QLabel(obs_module_text("FirstRunWizard.scene.label"), this); + _sceneCombo = new QComboBox(this); + _sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // registerField with * suffix means the field is mandatory for Next + registerField("targetScene*", _sceneCombo, "currentText", + SIGNAL(currentTextChanged(QString))); + + connect(_sceneCombo, &QComboBox::currentTextChanged, this, + &QWizardPage::completeChanged); + + auto row = new QHBoxLayout; + row->addWidget(label); + row->addWidget(_sceneCombo, 1); + + auto layout = new QVBoxLayout(this); + layout->addLayout(row); + layout->addStretch(); +} + +void WindowSceneSelectionPage::initializePage() +{ + _sceneCombo->clear(); + for (const QString &name : GetSceneNames()) { + _sceneCombo->addItem(name); + } +} + +bool WindowSceneSelectionPage::isComplete() const +{ + return _sceneCombo->count() > 0 && + !_sceneCombo->currentText().isEmpty(); +} + +// =========================================================================== +// WindowConditionPage +// =========================================================================== + +WindowConditionPage::WindowConditionPage(QWidget *parent) + : QWizardPage(parent), + _detectTimer(new QTimer(this)) +{ + setTitle(obs_module_text("FirstRunWizard.window.title")); + setSubTitle(obs_module_text("FirstRunWizard.window.subtitle")); + + auto label = new QLabel(obs_module_text("FirstRunWizard.window.label"), + this); + _windowEdit = new QLineEdit(this); + _windowEdit->setPlaceholderText( + obs_module_text("FirstRunWizard.window.placeholder")); + + _autoDetect = new QPushButton( + obs_module_text("FirstRunWizard.window.autoDetect"), this); + _autoDetect->setToolTip( + obs_module_text("FirstRunWizard.window.autoDetectTooltip")); + + registerField("windowTitle*", _windowEdit); + + connect(_windowEdit, &QLineEdit::textChanged, this, + &QWizardPage::completeChanged); + connect(_autoDetect, &QPushButton::clicked, this, + &WindowConditionPage::onAutoDetectClicked); + connect(_detectTimer, &QTimer::timeout, this, + &WindowConditionPage::onCountdownTick); + + auto row = new QHBoxLayout; + row->addWidget(label); + row->addWidget(_windowEdit, 1); + + auto hint = + new QLabel(obs_module_text("FirstRunWizard.window.hint"), this); + hint->setTextFormat(Qt::RichText); + hint->setWordWrap(true); + + auto layout = new QVBoxLayout(this); + layout->addLayout(row); + layout->addWidget(_autoDetect, 0, Qt::AlignLeft); + layout->addWidget(hint); + layout->addStretch(); +} + +void WindowConditionPage::initializePage() +{ + if (_windowEdit->text().isEmpty()) { + QString detected = DetectFocusedWindow(); + if (!detected.isEmpty()) { + _windowEdit->setText(detected); + } + } +} + +bool WindowConditionPage::isComplete() const +{ + return !_windowEdit->text().trimmed().isEmpty(); +} + +void WindowConditionPage::onAutoDetectClicked() +{ + _countdown = 3; + _autoDetect->setEnabled(false); + _autoDetect->setText( + QString(obs_module_text( + "FirstRunWizard.window.autoDetectCountdown")) + .arg(_countdown)); + _detectTimer->start(1000); +} + +void WindowConditionPage::onCountdownTick() +{ + --_countdown; + if (_countdown > 0) { + _autoDetect->setText( + QString(obs_module_text( + "FirstRunWizard.window.autoDetectCountdown")) + .arg(_countdown)); + return; + } + + _detectTimer->stop(); + QString title = DetectFocusedWindow(); + if (!title.isEmpty()) { + _windowEdit->setText(title); + } + + _autoDetect->setEnabled(true); + _autoDetect->setText( + obs_module_text("FirstRunWizard.window.autoDetect")); +} + +// =========================================================================== +// WindowReviewPage +// =========================================================================== + +WindowReviewPage::WindowReviewPage(QWidget *parent, + std::shared_ptr ¯o) + : QWizardPage(parent), + _macro(macro) +{ + setTitle(obs_module_text("FirstRunWizard.review.title")); + setSubTitle(obs_module_text("FirstRunWizard.review.subtitle")); + + _summary = new QLabel(this); + detail::setupSummaryLabel(_summary); + + auto layout = new QVBoxLayout(this); + layout->addWidget(_summary); + layout->addStretch(); +} + +void WindowReviewPage::initializePage() +{ + const QString scene = field("targetScene").toString(); + const QString window = field("windowTitle").toString(); + + _summary->setText( + QString(obs_module_text("FirstRunWizard.review.summary")) + .arg(scene.toHtmlEscaped(), window.toHtmlEscaped())); +} + +bool WindowReviewPage::validatePage() +{ + const QString scene = field("targetScene").toString(); + const QString window = EscapeForRegex(field("windowTitle").toString()); + const std::string name = ("Window -> " + scene).toStdString(); + + // Build condition data blob + // --------------------------------------------------------------- + // Condition blob — mirrors MacroConditionWindow::Save() output: + // + // { + // "segmentSettings": { "enabled": true, "version": 1 }, + // "id": "window", + // "checkTitle": true, + // "window": "", + // "windowRegexConfig": { + // "enable": true, // use regex-style partial matching + // "partial": true, // match anywhere in the title + // "options": 3 // case-insensitive (QRegularExpression flags) + // }, + // "focus": true, // only trigger when window is focused + // "version": 1 + // } + // --------------------------------------------------------------- + OBSDataAutoRelease condSegment = obs_data_create(); + obs_data_set_bool(condSegment, "enabled", true); + obs_data_set_int(condSegment, "version", 1); + + OBSDataAutoRelease condRegex = obs_data_create(); + obs_data_set_bool(condRegex, "enable", true); + obs_data_set_bool(condRegex, "partial", true); + obs_data_set_int(condRegex, "options", 3); // CaseInsensitiveOption + + OBSDataAutoRelease condData = obs_data_create(); + obs_data_set_obj(condData, "segmentSettings", condSegment); + obs_data_set_string(condData, "id", "window"); + obs_data_set_bool(condData, "checkTitle", true); + obs_data_set_string(condData, "window", window.toUtf8().constData()); + obs_data_set_obj(condData, "windowRegexConfig", condRegex); + obs_data_set_bool(condData, "focus", true); + obs_data_set_int(condData, "version", 1); + + // Build action data blob + // --------------------------------------------------------------- + // Action blob — mirrors MacroActionSwitchScene::Save() output: + // + // { + // "segmentSettings": { "enabled": true, "version": 1 }, + // "id": "scene_switch", + // "action": 0, // 0 = switch scene + // "sceneSelection": { + // "type": 0, // 0 = scene by name + // "name": "", + // "canvasSelection": "Main" + // }, + // "transitionType": 1, // 1 = use scene's default transition + // "blockUntilTransitionDone": false, + // "sceneType": 0 + // } + // --------------------------------------------------------------- + OBSDataAutoRelease actionSegment = obs_data_create(); + obs_data_set_bool(actionSegment, "enabled", true); + obs_data_set_int(actionSegment, "version", 1); + + OBSDataAutoRelease sceneSelection = obs_data_create(); + obs_data_set_int(sceneSelection, "type", 0); + obs_data_set_string(sceneSelection, "name", scene.toUtf8().constData()); + obs_data_set_string(sceneSelection, "canvasSelection", "Main"); + + OBSDataAutoRelease actionData = obs_data_create(); + obs_data_set_obj(actionData, "segmentSettings", actionSegment); + obs_data_set_string(actionData, "id", "scene_switch"); + obs_data_set_int(actionData, "action", 0); + obs_data_set_obj(actionData, "sceneSelection", sceneSelection); + obs_data_set_int(actionData, "transitionType", 1); + obs_data_set_bool(actionData, "blockUntilTransitionDone", false); + obs_data_set_int(actionData, "sceneType", 0); + + _macro = std::make_shared(name, GetGlobalMacroSettings()); + if (!_macro) { + blog(LOG_WARNING, + "FirstRunWizard: window macro allocation failed"); + return true; + } + + if (!detail::addCondition(_macro.get(), kConditionIdWindow, condData) || + !detail::addAction(_macro.get(), kActionIdSceneSwitch, + actionData)) { + QMessageBox::warning( + this, + obs_module_text("FirstRunWizard.review.errorTitle"), + QString(obs_module_text( + "FirstRunWizard.review.errorBody")) + .arg(window, scene)); + _macro.reset(); + // Still advance so the user is not stuck. + return true; + } + + blog(LOG_INFO, "FirstRunWizard: created macro '%s'", name.c_str()); + return true; +} + +} // namespace wiz + +} // namespace advss diff --git a/lib/utils/first-run-wizard-window.hpp b/lib/utils/first-run-wizard-window.hpp new file mode 100644 index 00000000..28d3d245 --- /dev/null +++ b/lib/utils/first-run-wizard-window.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "first-run-wizard.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace advss { + +namespace wiz { + +// --------------------------------------------------------------------------- +// WindowSceneSelectionPage +// Registers wizard field "targetScene" (QString). +// --------------------------------------------------------------------------- +class WindowSceneSelectionPage : public QWizardPage { + Q_OBJECT +public: + explicit WindowSceneSelectionPage(QWidget *parent = nullptr); + void initializePage() override; + bool isComplete() const override; + int nextId() const override { return PAGE_WINDOW_CONDITION; } + +private: + QComboBox *_sceneCombo; +}; + +// --------------------------------------------------------------------------- +// WindowConditionPage +// Registers wizard field "windowTitle" (QString). +// Auto-detect button samples the focused window after a countdown. +// --------------------------------------------------------------------------- +class WindowConditionPage : public QWizardPage { + Q_OBJECT +public: + explicit WindowConditionPage(QWidget *parent = nullptr); + void initializePage() override; + bool isComplete() const override; + int nextId() const override { return PAGE_WINDOW_REVIEW; } + +private slots: + void onAutoDetectClicked(); + void onCountdownTick(); + +private: + QLineEdit *_windowEdit; + QPushButton *_autoDetect; + QTimer *_detectTimer; + int _countdown = 3; +}; + +// --------------------------------------------------------------------------- +// WindowReviewPage +// Displays a summary and builds the macro from wizard fields on Finish. +// --------------------------------------------------------------------------- +class WindowReviewPage : public QWizardPage { + Q_OBJECT +public: + explicit WindowReviewPage(QWidget *parent, + std::shared_ptr ¯o); + void initializePage() override; + bool validatePage() override; + int nextId() const override { return PAGE_DONE; } + +private: + QLabel *_summary; + std::shared_ptr &_macro; +}; + +} // namespace wiz +} // namespace advss diff --git a/lib/utils/first-run-wizard.cpp b/lib/utils/first-run-wizard.cpp index 30773d08..3386cde6 100644 --- a/lib/utils/first-run-wizard.cpp +++ b/lib/utils/first-run-wizard.cpp @@ -1,28 +1,15 @@ #include "first-run-wizard.hpp" +#include "first-run-wizard-window.hpp" -#include "log-helper.hpp" #include "macro.hpp" -#include "macro-action-factory.hpp" -#include "macro-condition-factory.hpp" -#include "macro-settings.hpp" -#include "platform-funcs.hpp" -#include "selection-helpers.hpp" #include -#include #include -#include -#include -#include -#include #include namespace advss { -static constexpr char kConditionIdWindow[] = "window"; -static constexpr char kActionIdSceneSwitch[] = "scene_switch"; - // --------------------------------------------------------------------------- // OBS global config helpers // --------------------------------------------------------------------------- @@ -53,10 +40,10 @@ static void WriteFirstRun(bool value) config_save_safe(cfg, "tmp", nullptr); } -static QString DetectFocusedWindow() -{ - return QString::fromStdString(GetCurrentWindowTitle()); -} +} // namespace advss + +namespace advss { +namespace wiz { // =========================================================================== // WelcomePage @@ -77,274 +64,6 @@ WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent) layout->addStretch(); } -// =========================================================================== -// SceneSelectionPage -// =========================================================================== - -SceneSelectionPage::SceneSelectionPage(QWidget *parent) : QWizardPage(parent) -{ - setTitle(obs_module_text("FirstRunWizard.scene.title")); - setSubTitle(obs_module_text("FirstRunWizard.scene.subtitle")); - - auto label = - new QLabel(obs_module_text("FirstRunWizard.scene.label"), this); - _sceneCombo = new QComboBox(this); - _sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // registerField with * suffix means the field is mandatory for Next - registerField("targetScene*", _sceneCombo, "currentText", - SIGNAL(currentTextChanged(QString))); - - connect(_sceneCombo, &QComboBox::currentTextChanged, this, - &QWizardPage::completeChanged); - - auto row = new QHBoxLayout; - row->addWidget(label); - row->addWidget(_sceneCombo, 1); - - auto layout = new QVBoxLayout(this); - layout->addLayout(row); - layout->addStretch(); -} - -void SceneSelectionPage::initializePage() -{ - _sceneCombo->clear(); - for (const QString &name : GetSceneNames()) - _sceneCombo->addItem(name); -} - -bool SceneSelectionPage::isComplete() const -{ - return _sceneCombo->count() > 0 && - !_sceneCombo->currentText().isEmpty(); -} - -// =========================================================================== -// WindowConditionPage -// =========================================================================== - -WindowConditionPage::WindowConditionPage(QWidget *parent) - : QWizardPage(parent), - _detectTimer(new QTimer(this)) -{ - setTitle(obs_module_text("FirstRunWizard.window.title")); - setSubTitle(obs_module_text("FirstRunWizard.window.subtitle")); - - auto label = new QLabel(obs_module_text("FirstRunWizard.window.label"), - this); - _windowEdit = new QLineEdit(this); - _windowEdit->setPlaceholderText( - obs_module_text("FirstRunWizard.window.placeholder")); - - _autoDetect = new QPushButton( - obs_module_text("FirstRunWizard.window.autoDetect"), this); - _autoDetect->setToolTip( - obs_module_text("FirstRunWizard.window.autoDetectTooltip")); - - registerField("windowTitle*", _windowEdit); - - connect(_windowEdit, &QLineEdit::textChanged, this, - &QWizardPage::completeChanged); - connect(_autoDetect, &QPushButton::clicked, this, - &WindowConditionPage::onAutoDetectClicked); - connect(_detectTimer, &QTimer::timeout, this, - &WindowConditionPage::onCountdownTick); - - auto row = new QHBoxLayout; - row->addWidget(label); - row->addWidget(_windowEdit, 1); - - auto hint = - new QLabel(obs_module_text("FirstRunWizard.window.hint"), this); - hint->setTextFormat(Qt::RichText); - hint->setWordWrap(true); - - auto layout = new QVBoxLayout(this); - layout->addLayout(row); - layout->addWidget(_autoDetect, 0, Qt::AlignLeft); - layout->addWidget(hint); - layout->addStretch(); -} - -void WindowConditionPage::initializePage() -{ - if (_windowEdit->text().isEmpty()) { - QString detected = DetectFocusedWindow(); - if (!detected.isEmpty()) { - _windowEdit->setText(detected); - } - } -} - -bool WindowConditionPage::isComplete() const -{ - return !_windowEdit->text().trimmed().isEmpty(); -} - -void WindowConditionPage::onAutoDetectClicked() -{ - _countdown = 3; - _autoDetect->setEnabled(false); - _autoDetect->setText( - QString(obs_module_text( - "FirstRunWizard.window.autoDetectCountdown")) - .arg(_countdown)); - _detectTimer->start(1000); -} - -void WindowConditionPage::onCountdownTick() -{ - --_countdown; - if (_countdown > 0) { - _autoDetect->setText( - QString(obs_module_text( - "FirstRunWizard.window.autoDetectCountdown")) - .arg(_countdown)); - return; - } - - _detectTimer->stop(); - QString title = DetectFocusedWindow(); - if (!title.isEmpty()) { - _windowEdit->setText(title); - } - - _autoDetect->setEnabled(true); - _autoDetect->setText( - obs_module_text("FirstRunWizard.window.autoDetect")); -} - -// =========================================================================== -// ReviewPage -// =========================================================================== - -ReviewPage::ReviewPage(QWidget *parent, std::shared_ptr ¯o) - : QWizardPage(parent), - _macro(macro) -{ - setTitle(obs_module_text("FirstRunWizard.review.title")); - setSubTitle(obs_module_text("FirstRunWizard.review.subtitle")); - - _summary = new QLabel(this); - _summary->setWordWrap(true); - _summary->setTextFormat(Qt::RichText); - _summary->setFrameShape(QFrame::StyledPanel); - _summary->setContentsMargins(12, 12, 12, 12); - - auto layout = new QVBoxLayout(this); - layout->addWidget(_summary); - layout->addStretch(); -} - -void ReviewPage::initializePage() -{ - const QString scene = field("targetScene").toString(); - const QString window = field("windowTitle").toString(); - - _summary->setText( - QString(obs_module_text("FirstRunWizard.review.summary")) - .arg(scene.toHtmlEscaped(), window.toHtmlEscaped())); -} - -static QString escapeForRegex(const QString &input) -{ - return QRegularExpression::escape(input); -} - -bool ReviewPage::validatePage() -{ - const QString scene = field("targetScene").toString(); - const QString window = escapeForRegex(field("windowTitle").toString()); - const std::string name = ("Window -> " + scene).toStdString(); - - // Build condition data blob - // --------------------------------------------------------------- - // Condition blob — mirrors MacroConditionWindow::Save() output: - // - // { - // "segmentSettings": { "enabled": true, "version": 1 }, - // "id": "window", - // "checkTitle": true, - // "window": "", - // "windowRegexConfig": { - // "enable": true, // use regex-style partial matching - // "partial": true, // match anywhere in the title - // "options": 3 // case-insensitive (QRegularExpression flags) - // }, - // "focus": true, // only trigger when window is focused - // "version": 1 - // } - // --------------------------------------------------------------- - OBSDataAutoRelease condSegment = obs_data_create(); - obs_data_set_bool(condSegment, "enabled", true); - obs_data_set_int(condSegment, "version", 1); - - OBSDataAutoRelease condRegex = obs_data_create(); - obs_data_set_bool(condRegex, "enable", true); - obs_data_set_bool(condRegex, "partial", true); - obs_data_set_int(condRegex, "options", 3); // CaseInsensitiveOption - - OBSDataAutoRelease condData = obs_data_create(); - obs_data_set_obj(condData, "segmentSettings", condSegment); - obs_data_set_string(condData, "id", "window"); - obs_data_set_bool(condData, "checkTitle", true); - obs_data_set_string(condData, "window", window.toUtf8().constData()); - obs_data_set_obj(condData, "windowRegexConfig", condRegex); - obs_data_set_bool(condData, "focus", true); - obs_data_set_int(condData, "version", 1); - - // Build action data blob - // --------------------------------------------------------------- - // Action blob — mirrors MacroActionSwitchScene::Save() output: - // - // { - // "segmentSettings": { "enabled": true, "version": 1 }, - // "id": "scene_switch", - // "action": 0, // 0 = switch scene - // "sceneSelection": { - // "type": 0, // 0 = scene by name - // "name": "", - // "canvasSelection": "Main" - // }, - // "transitionType": 1, // 1 = use scene's default transition - // "blockUntilTransitionDone": false, - // "sceneType": 0 - // } - // --------------------------------------------------------------- - OBSDataAutoRelease actionSegment = obs_data_create(); - obs_data_set_bool(actionSegment, "enabled", true); - obs_data_set_int(actionSegment, "version", 1); - - OBSDataAutoRelease sceneSelection = obs_data_create(); - obs_data_set_int(sceneSelection, "type", 0); - obs_data_set_string(sceneSelection, "name", scene.toUtf8().constData()); - obs_data_set_string(sceneSelection, "canvasSelection", "Main"); - - OBSDataAutoRelease actionData = obs_data_create(); - obs_data_set_obj(actionData, "segmentSettings", actionSegment); - obs_data_set_string(actionData, "id", "scene_switch"); - obs_data_set_int(actionData, "action", 0); - obs_data_set_obj(actionData, "sceneSelection", sceneSelection); - obs_data_set_int(actionData, "transitionType", 1); - obs_data_set_bool(actionData, "blockUntilTransitionDone", false); - obs_data_set_int(actionData, "sceneType", 0); - - if (!FirstRunWizard::CreateMacro(_macro, name, kConditionIdWindow, - condData, kActionIdSceneSwitch, - actionData)) { - QMessageBox::warning( - this, - obs_module_text("FirstRunWizard.review.errorTitle"), - QString(obs_module_text( - "FirstRunWizard.review.errorBody")) - .arg(window, scene)); - _macro.reset(); - // Still advance so the user is not stuck. - } - return true; -} - // =========================================================================== // DonePage // =========================================================================== @@ -376,9 +95,9 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QWizard(parent) setMinimumSize(540, 420); setPage(PAGE_WELCOME, new WelcomePage(this)); - setPage(PAGE_SCENE, new SceneSelectionPage(this)); - setPage(PAGE_WINDOW, new WindowConditionPage(this)); - setPage(PAGE_REVIEW, new ReviewPage(this, _macro)); + setPage(PAGE_WINDOW_SCENE, new WindowSceneSelectionPage(this)); + setPage(PAGE_WINDOW_CONDITION, new WindowConditionPage(this)); + setPage(PAGE_WINDOW_REVIEW, new WindowReviewPage(this, _macro)); setPage(PAGE_DONE, new DonePage(this)); setStartId(PAGE_WELCOME); @@ -410,58 +129,5 @@ std::shared_ptr FirstRunWizard::ShowWizard(QWidget *parent, return wizard->_macro; } -// static -bool FirstRunWizard::CreateMacro(std::shared_ptr ¯o, - const std::string ¯oName, - const std::string &conditionId, - obs_data_t *conditionData, - const std::string &actionId, - obs_data_t *actionData) -{ - // 1. Create and register the Macro - macro = std::make_shared(macroName, GetGlobalMacroSettings()); - if (!macro) { - blog(LOG_WARNING, "FirstRunWizard: Macro allocation failed"); - return false; - } - - // 2. Instantiate condition via factory, then hydrate via Load() - auto condition = - MacroConditionFactory::Create(conditionId, macro.get()); - if (!condition) { - blog(LOG_WARNING, - "FirstRunWizard: condition factory returned null " - "for id '%s' — is the base plugin loaded?", - conditionId.c_str()); - return false; - } - if (!condition->Load(conditionData)) { - blog(LOG_WARNING, - "FirstRunWizard: condition Load() failed for id '%s'", - conditionId.c_str()); - return false; - } - macro->Conditions().emplace_back(condition); - - // 3. Instantiate action via factory, then hydrate via Load() - auto action = MacroActionFactory::Create(actionId, macro.get()); - if (!action) { - blog(LOG_WARNING, - "FirstRunWizard: action factory returned null " - "for id '%s' — is the base plugin loaded?", - actionId.c_str()); - return false; - } - if (!action->Load(actionData)) { - blog(LOG_WARNING, - "FirstRunWizard: action Load() failed for id '%s'", - actionId.c_str()); - return false; - } - macro->Actions().emplace_back(action); - - blog(LOG_INFO, "FirstRunWizard: created macro '%s'", macroName.c_str()); - return true; -} - +} // namespace wiz } // namespace advss diff --git a/lib/utils/first-run-wizard.hpp b/lib/utils/first-run-wizard.hpp index 842d9b58..457d33fc 100644 --- a/lib/utils/first-run-wizard.hpp +++ b/lib/utils/first-run-wizard.hpp @@ -1,31 +1,27 @@ #pragma once -#include - -#include -#include -#include -#include -#include +#include #include #include -#include +#include namespace advss { +bool IsFirstRun(); + class Macro; -bool IsFirstRun(); +namespace wiz { // --------------------------------------------------------------------------- // Page IDs // --------------------------------------------------------------------------- enum WizardPageId { PAGE_WELCOME = 0, - PAGE_SCENE, - PAGE_WINDOW, - PAGE_REVIEW, + PAGE_WINDOW_SCENE, + PAGE_WINDOW_CONDITION, + PAGE_WINDOW_REVIEW, PAGE_DONE, }; @@ -36,64 +32,7 @@ class WelcomePage : public QWizardPage { Q_OBJECT public: explicit WelcomePage(QWidget *parent = nullptr); - int nextId() const override { return PAGE_SCENE; } -}; - -// --------------------------------------------------------------------------- -// SceneSelectionPage -// Registers wizard field "targetScene" (QString). -// --------------------------------------------------------------------------- -class SceneSelectionPage : public QWizardPage { - Q_OBJECT -public: - explicit SceneSelectionPage(QWidget *parent = nullptr); - void initializePage() override; - bool isComplete() const override; - int nextId() const override { return PAGE_WINDOW; } - -private: - QComboBox *_sceneCombo; -}; - -// --------------------------------------------------------------------------- -// WindowConditionPage -// Registers wizard field "windowTitle" (QString). -// Auto-detect button samples the focused window after a countdown. -// --------------------------------------------------------------------------- -class WindowConditionPage : public QWizardPage { - Q_OBJECT -public: - explicit WindowConditionPage(QWidget *parent = nullptr); - void initializePage() override; - bool isComplete() const override; - int nextId() const override { return PAGE_REVIEW; } - -private slots: - void onAutoDetectClicked(); - void onCountdownTick(); - -private: - QLineEdit *_windowEdit; - QPushButton *_autoDetect; - QTimer *_detectTimer; - int _countdown = 3; -}; - -// --------------------------------------------------------------------------- -// ReviewPage -// Displays a summary and calls FirstRunWizard::CreateMacro() on Finish. -// --------------------------------------------------------------------------- -class ReviewPage : public QWizardPage { - Q_OBJECT -public: - explicit ReviewPage(QWidget *parent, std::shared_ptr ¯o); - void initializePage() override; - bool validatePage() override; - int nextId() const override { return PAGE_DONE; } - -private: - QLabel *_summary; - std::shared_ptr &_macro; + int nextId() const override { return PAGE_WINDOW_SCENE; } }; // --------------------------------------------------------------------------- @@ -113,17 +52,14 @@ class FirstRunWizard : public QWizard { Q_OBJECT public: explicit FirstRunWizard(QWidget *parent = nullptr); - static std::shared_ptr ShowWizard(QWidget *parent, - bool *wasSkipped = nullptr); - static bool - CreateMacro(std::shared_ptr ¯o, const std::string ¯oName, - const std::string &conditionId, obs_data_t *conditionData, - const std::string &actionId, obs_data_t *actionData); + static std::shared_ptr + ShowWizard(QWidget *parent, bool *wasSkipped = nullptr); private: void markFirstRunComplete(); - std::shared_ptr _macro; + std::shared_ptr _macro; }; +} // namespace wiz } // namespace advss