Split first run wizard into multiple files

This commit is contained in:
WarmUpTill 2026-06-16 22:44:34 +02:00
parent 78499bcf5f
commit b3a69fdb3f
7 changed files with 496 additions and 422 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -0,0 +1,85 @@
#pragma once
#include "log-helper.hpp"
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include <obs-data.h>
#include <QFrame>
#include <QLabel>
#include <string>
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

View File

@ -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 <obs.hpp>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QRegularExpression>
#include <QVBoxLayout>
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<Macro> &macro)
: 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": "<user input>",
// "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": "<scene>",
// "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<Macro>(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

View File

@ -0,0 +1,76 @@
#pragma once
#include "first-run-wizard.hpp"
#include <QComboBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>
#include <memory>
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<advss::Macro> &macro);
void initializePage() override;
bool validatePage() override;
int nextId() const override { return PAGE_DONE; }
private:
QLabel *_summary;
std::shared_ptr<advss::Macro> &_macro;
};
} // namespace wiz
} // namespace advss

View File

@ -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 <obs-frontend-api.h>
#include <obs-data.h>
#include <util/config-file.h>
#include <QFrame>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QRegularExpression>
#include <QVBoxLayout>
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<Macro> &macro)
: 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": "<user input>",
// "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": "<scene>",
// "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<Macro> FirstRunWizard::ShowWizard(QWidget *parent,
return wizard->_macro;
}
// static
bool FirstRunWizard::CreateMacro(std::shared_ptr<Macro> &macro,
const std::string &macroName,
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<Macro>(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

View File

@ -1,31 +1,27 @@
#pragma once
#include <obs-data.h>
#include <QComboBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>
#include <QRadioButton>
#include <QWizard>
#include <QWizardPage>
#include <string>
#include <memory>
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<Macro> &macro);
void initializePage() override;
bool validatePage() override;
int nextId() const override { return PAGE_DONE; }
private:
QLabel *_summary;
std::shared_ptr<Macro> &_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<Macro> ShowWizard(QWidget *parent,
bool *wasSkipped = nullptr);
static bool
CreateMacro(std::shared_ptr<Macro> &macro, const std::string &macroName,
const std::string &conditionId, obs_data_t *conditionData,
const std::string &actionId, obs_data_t *actionData);
static std::shared_ptr<advss::Macro>
ShowWizard(QWidget *parent, bool *wasSkipped = nullptr);
private:
void markFirstRunComplete();
std::shared_ptr<Macro> _macro;
std::shared_ptr<advss::Macro> _macro;
};
} // namespace wiz
} // namespace advss