mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
Add FirstRunWizard
This commit is contained in:
parent
047fef4f6e
commit
dd59191bfa
|
|
@ -204,6 +204,8 @@ target_sources(
|
|||
lib/utils/file-selection.hpp
|
||||
lib/utils/filter-combo-box.cpp
|
||||
lib/utils/filter-combo-box.hpp
|
||||
lib/utils/first-run-wizard.cpp
|
||||
lib/utils/first-run-wizard.hpp
|
||||
lib/utils/help-icon.hpp
|
||||
lib/utils/help-icon.cpp
|
||||
lib/utils/item-selection-helpers.cpp
|
||||
|
|
|
|||
|
|
@ -835,8 +835,6 @@ AdvSceneSwitcher.status.inactive="Inaktiv"
|
|||
AdvSceneSwitcher.running="Plugin läuft"
|
||||
AdvSceneSwitcher.stopped="Plugin gestoppt"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Dies scheint das erste Mal zu sein, dass der Erweiterte Szenenwechsler gestartet wurde.<br>Bitte schaue ins <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> für eine Liste von Anleitungen und Beispielen.<br>Nicht zögern und Fragen im <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">Thread</span></a> des Plugins im OBS-Forum stellen!!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Die Entwicklung für dieses Tab wurde gestoppt!\nBitte stattdessen zur Verwendung des Makros-Tab übergehen.\nDieser Hinweis kann im Allgemein-Tab deaktiviert werden."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="Millisekunden"
|
||||
|
|
|
|||
|
|
@ -2438,8 +2438,6 @@ AdvSceneSwitcher.status.inactive="Inactive"
|
|||
AdvSceneSwitcher.running="Plugin running"
|
||||
AdvSceneSwitcher.stopped="Plugin stopped"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>This seems to be the first time the Advanced Scene Switcher was started.<br>Please have a look at the <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> for a list of guides and examples.<br>Do not hesitate to ask questions in the plugin's <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a> on the OBS forums!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Development for this tab stopped!\nPlease consider transitioning to using Macros instead.\nThis hint can be disabled on the General tab."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milliseconds"
|
||||
|
|
@ -2479,6 +2477,37 @@ AdvSceneSwitcher.day.sunday="Sunday"
|
|||
AdvSceneSwitcher.day.none="No day"
|
||||
AdvSceneSwitcher.day.any="Any day"
|
||||
|
||||
# First run wizard
|
||||
FirstRunWizard.windowTitle="Advanced Scene Switcher - Setup Wizard"
|
||||
|
||||
FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher"
|
||||
FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps."
|
||||
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard creates a macro that <b>switches to a chosen OBS scene whenever a specific window comes into focus</b>.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
||||
|
||||
FirstRunWizard.scene.title="Choose a Target Scene"
|
||||
FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?"
|
||||
FirstRunWizard.scene.label="Switch to scene:"
|
||||
|
||||
FirstRunWizard.window.title="Choose a Trigger Window"
|
||||
FirstRunWizard.window.subtitle="Enter the title of the window that should trigger the scene switch. Partial matches are fine - \"Firefox\" will match any Firefox window."
|
||||
FirstRunWizard.window.label="Window title contains:"
|
||||
FirstRunWizard.window.placeholder="e.g. \"Firefox\", \"Visual Studio Code\""
|
||||
FirstRunWizard.window.autoDetect="Auto-detect focused window"
|
||||
FirstRunWizard.window.autoDetectTooltip="Click, then switch to the target window. The title will be captured automatically after 3 seconds."
|
||||
FirstRunWizard.window.autoDetectCountdown="Detecting in %1 s - focus your window now..."
|
||||
FirstRunWizard.window.hint="<small>Tip: a short partial title is more robust than the full title, which often changes when you switch tabs.</small>"
|
||||
|
||||
FirstRunWizard.review.title="Review Your Macro"
|
||||
FirstRunWizard.review.subtitle="Click Back to make changes, or Finish to create the macro."
|
||||
FirstRunWizard.review.summary="<b>Macro name:</b> Window -> %1<br><br><b>Condition:</b> Focused window title contains <i>\"%2\"</i> (case-insensitive)<br><br><b>Action:</b> Switch to scene <i>\"%1\"</i>"
|
||||
FirstRunWizard.review.errorTitle="Macro Creation Failed"
|
||||
FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\""
|
||||
|
||||
FirstRunWizard.done.title="You're all set!"
|
||||
FirstRunWizard.done.subtitle="Your first macro has been created and is now active."
|
||||
FirstRunWizard.done.body="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
|
||||
|
||||
FirstRunWizard.openButton="Open Setup Wizard..."
|
||||
# This secion is copied from the OBS locale files
|
||||
|
||||
# OBS commonly shared locale
|
||||
|
|
|
|||
|
|
@ -681,8 +681,6 @@ AdvSceneSwitcher.status.inactive="Inactivo"
|
|||
AdvSceneSwitcher.running="Iniciar complemento"
|
||||
AdvSceneSwitcher.stopped="Complemento de detención"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la primera vez que se inicia el conmutador de escena avanzado.<br>Por favor, eche un vistazo a <a href=\"https:/ /github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para obtener una lista de guías y ejemplos.<br>No dude en hacer preguntas en el complemento <a href=\"https://obsproject.com /forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">hilo</span></a> en los foros de OBS!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="¡Se detuvo el desarrollo de esta pestaña!\nConsidere cambiar a macros en su lugar.\nEsta sugerencia se puede desactivar en la pestaña General".
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milisegundos"
|
||||
|
|
|
|||
|
|
@ -1123,8 +1123,6 @@ AdvSceneSwitcher.status.inactive="Inactif"
|
|||
AdvSceneSwitcher.running="Plugin en cours d'exécution"
|
||||
AdvSceneSwitcher.stopped="Plugin arrêté"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Il semble que ce soit la première fois que l'Advanced Scene Switcher est démarré.<br>Veuillez consulter le <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> pour obtenir une liste de guides et d'exemples.<br>N'hésitez pas à poser des questions dans le <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">fil de discussion</span></a> du plugin sur les forums OBS !</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Le développement de cet onglet est terminé !\nVeuillez envisager de passer à l'utilisation des Macros à la place.\nCette astuce peut être désactivée dans l'onglet Général."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="millisecondes"
|
||||
|
|
|
|||
|
|
@ -2252,8 +2252,6 @@ AdvSceneSwitcher.status.inactive="停止中"
|
|||
AdvSceneSwitcher.running="プラグイン実行中"
|
||||
AdvSceneSwitcher.stopped="プラグイン停止しました"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>高機能シーンスイッチャーが初めて起動したようです。<br>ガイドと使用例については <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> をご覧ください。<br>質問は <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">OBSフォーラムのプラグインのスレッド</span></a> で遠慮なくどうぞ!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="このタブの開発は停止しました!\n代わりにマクロの使用に移行することを検討してください。\nこのヒントは [全般] タブで無効にすることができます。"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="ミリ秒"
|
||||
|
|
|
|||
|
|
@ -1872,8 +1872,6 @@ AdvSceneSwitcher.status.inactive="Inativo"
|
|||
AdvSceneSwitcher.running="Plugin em execução"
|
||||
AdvSceneSwitcher.stopped="Plugin parado"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Parece que esta é a primeira vez que o Advanced Scene Switcher está sendo iniciado.<br>Por favor, consulte o <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para uma lista de guias e exemplos.<br>Não hesite em fazer perguntas no <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">tópico</span></a> do plugin nos fóruns do OBS!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Desenvolvimento para esta aba interrompido!\nPor favor, considere a transição para o uso de Macros em vez disso.\nEsta dica pode ser desativada na aba Geral."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milissegundos"
|
||||
|
|
|
|||
|
|
@ -587,8 +587,6 @@ AdvSceneSwitcher.status.inactive="İnaktif"
|
|||
AdvSceneSwitcher.running="Eklenti çalışıyor"
|
||||
AdvSceneSwitcher.stopped="Eklenti durdu"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Gelişmiş Sahne Değiştirici ilk kez başlatılıyor gibi görünüyor.<br>Lütfen <a href=\"https://github.com/ adresine bir göz atın. WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> için kılavuzlar ve örnekler listesi.<br>Yapmayın. eklentinin <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:# sayfasında soru sormaktan çekinmeyin OBS forumlarında 268bd2;\">konu</span></a>!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="millisaniye"
|
||||
AdvSceneSwitcher.unit.seconds="saniye"
|
||||
AdvSceneSwitcher.unit.minutes="dakika"
|
||||
|
|
|
|||
|
|
@ -2116,8 +2116,6 @@ AdvSceneSwitcher.status.inactive="已停止"
|
|||
AdvSceneSwitcher.running="插件正在运行"
|
||||
AdvSceneSwitcher.stopped="插件已停止"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>这似乎是您第一次启动高级场景切换器.<br>请看一下 <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> 查看指南和示例列表.<br>如果有问题,在在OBS论坛插件帖子内提问 <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a></p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="此选项卡的开发已停止!请考虑转换为使用宏来代替。\n可以在“常规”选项卡上禁用此提示."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="毫秒"
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>962</width>
|
||||
<height>1160</height>
|
||||
<height>1190</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
|
|
@ -565,6 +565,13 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openSetupWizard">
|
||||
<property name="text">
|
||||
<string>FirstRunWizard.openButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ public slots:
|
|||
void on_priorityUp_clicked();
|
||||
void on_priorityDown_clicked();
|
||||
void on_threadPriority_currentTextChanged(const QString &text);
|
||||
void on_openSetupWizard_clicked();
|
||||
|
||||
/* --- End of legacy tab section --- */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
#include "advanced-scene-switcher.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "first-run-wizard.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "macro-search.hpp"
|
||||
#include "macro-settings.hpp"
|
||||
#include "path-helpers.hpp"
|
||||
#include "selection-helpers.hpp"
|
||||
#include "source-helpers.hpp"
|
||||
#include "splitter-helpers.hpp"
|
||||
#include "status-control.hpp"
|
||||
#include "switcher-data.hpp"
|
||||
#include "tab-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "version.h"
|
||||
|
||||
|
|
@ -361,14 +360,46 @@ void AdvSceneSwitcher::RestoreWindowGeo()
|
|||
}
|
||||
}
|
||||
|
||||
static void renameMacroIfNecessary(const std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
if (!GetMacroByName(macro->Name().c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = macro->Name();
|
||||
int i = 2;
|
||||
while (GetMacroByName((name + " " + std::to_string(i)).c_str())) {
|
||||
i++;
|
||||
}
|
||||
|
||||
macro->SetName(name + " " + std::to_string(i));
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CheckFirstTimeSetup()
|
||||
{
|
||||
if (switcher->firstBoot && !switcher->disableHints) {
|
||||
switcher->firstBoot = false;
|
||||
DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
|
||||
switcher->Start();
|
||||
if (!IsFirstRun() || !GetTopLevelMacros().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = FirstRunWizard::ShowWizard(this);
|
||||
if (macro) {
|
||||
renameMacroIfNecessary(macro);
|
||||
QTimer::singleShot(0, this,
|
||||
[this, macro]() { ui->macros->Add(macro); });
|
||||
}
|
||||
switcher->Start();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_openSetupWizard_clicked()
|
||||
{
|
||||
auto macro = FirstRunWizard::ShowWizard(this);
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
renameMacroIfNecessary(macro);
|
||||
ui->macros->Add(macro);
|
||||
ui->tabWidget->setCurrentWidget(ui->macroTab);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ bool SwitcherData::VersionChanged(obs_data_t *obj, std::string currentVersion)
|
|||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
return false;
|
||||
}
|
||||
switcher->firstBoot = false;
|
||||
|
||||
std::string previousVersion = obs_data_get_string(obj, "version");
|
||||
return previousVersion != currentVersion;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ public:
|
|||
bool stop = false;
|
||||
std::condition_variable cv;
|
||||
|
||||
bool firstBoot = true;
|
||||
bool transitionActive = false;
|
||||
bool sceneCollectionStop = false;
|
||||
bool obsIsShuttingDown = false;
|
||||
|
|
|
|||
465
lib/utils/first-run-wizard.cpp
Normal file
465
lib/utils/first-run-wizard.cpp
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
#include "first-run-wizard.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
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr char kConfigSection[] = "AdvancedSceneSwitcher";
|
||||
static constexpr char kFirstRunKey[] = "firstRun";
|
||||
|
||||
bool IsFirstRun()
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
|
||||
config_t *cfg = obs_frontend_get_user_config();
|
||||
#else
|
||||
config_t *cfg = obs_frontend_get_global_config();
|
||||
#endif
|
||||
if (!config_has_user_value(cfg, kConfigSection, kFirstRunKey)) {
|
||||
return true;
|
||||
}
|
||||
return config_get_bool(cfg, kConfigSection, kFirstRunKey);
|
||||
}
|
||||
|
||||
static void WriteFirstRun(bool value)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
|
||||
config_t *cfg = obs_frontend_get_user_config();
|
||||
#else
|
||||
config_t *cfg = obs_frontend_get_global_config();
|
||||
#endif
|
||||
config_set_bool(cfg, kConfigSection, kFirstRunKey, value);
|
||||
config_save_safe(cfg, "tmp", nullptr);
|
||||
}
|
||||
|
||||
static QString DetectFocusedWindow()
|
||||
{
|
||||
std::string title;
|
||||
GetCurrentWindowTitle(title);
|
||||
return QString::fromStdString(title);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// WelcomePage
|
||||
// ===========================================================================
|
||||
|
||||
WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.welcome.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.welcome.subtitle"));
|
||||
|
||||
auto body = new QLabel(obs_module_text("FirstRunWizard.welcome.body"),
|
||||
this);
|
||||
body->setWordWrap(true);
|
||||
body->setTextFormat(Qt::RichText);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(body);
|
||||
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> ¯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": "<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
|
||||
// ===========================================================================
|
||||
|
||||
DonePage::DonePage(QWidget *parent) : QWizardPage(parent)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.done.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.done.subtitle"));
|
||||
|
||||
auto body =
|
||||
new QLabel(obs_module_text("FirstRunWizard.done.body"), this);
|
||||
body->setWordWrap(true);
|
||||
body->setTextFormat(Qt::RichText);
|
||||
body->setOpenExternalLinks(true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(body);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// FirstRunWizard
|
||||
// ===========================================================================
|
||||
|
||||
FirstRunWizard::FirstRunWizard(QWidget *parent) : QWizard(parent)
|
||||
{
|
||||
setWindowTitle(obs_module_text("FirstRunWizard.windowTitle"));
|
||||
setWizardStyle(QWizard::ModernStyle);
|
||||
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_DONE, new DonePage(this));
|
||||
|
||||
setStartId(PAGE_WELCOME);
|
||||
setOption(QWizard::NoBackButtonOnLastPage, true);
|
||||
setOption(QWizard::NoCancelButtonOnLastPage, true);
|
||||
|
||||
// Mark done on both Accept (Finish) and Reject (Cancel / close)
|
||||
connect(this, &QWizard::accepted, this,
|
||||
&FirstRunWizard::markFirstRunComplete);
|
||||
connect(this, &QWizard::rejected, this,
|
||||
&FirstRunWizard::markFirstRunComplete);
|
||||
}
|
||||
|
||||
void FirstRunWizard::markFirstRunComplete()
|
||||
{
|
||||
WriteFirstRun(false);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<Macro> FirstRunWizard::ShowWizard(QWidget *parent)
|
||||
{
|
||||
auto wizard = new FirstRunWizard(parent);
|
||||
wizard->exec();
|
||||
wizard->deleteLater();
|
||||
return wizard->_macro;
|
||||
}
|
||||
|
||||
// static
|
||||
bool FirstRunWizard::CreateMacro(std::shared_ptr<Macro> ¯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<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 advss
|
||||
128
lib/utils/first-run-wizard.hpp
Normal file
128
lib/utils/first-run-wizard.hpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QWizard>
|
||||
#include <QWizardPage>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
|
||||
bool IsFirstRun();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Page IDs
|
||||
// ---------------------------------------------------------------------------
|
||||
enum WizardPageId {
|
||||
PAGE_WELCOME = 0,
|
||||
PAGE_SCENE,
|
||||
PAGE_WINDOW,
|
||||
PAGE_REVIEW,
|
||||
PAGE_DONE,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WelcomePage
|
||||
// ---------------------------------------------------------------------------
|
||||
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> ¯o);
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
int nextId() const override { return PAGE_DONE; }
|
||||
|
||||
private:
|
||||
QLabel *_summary;
|
||||
std::shared_ptr<Macro> &_macro;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DonePage
|
||||
// ---------------------------------------------------------------------------
|
||||
class DonePage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DonePage(QWidget *parent = nullptr);
|
||||
int nextId() const override { return -1; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FirstRunWizard
|
||||
// ---------------------------------------------------------------------------
|
||||
class FirstRunWizard : public QWizard {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FirstRunWizard(QWidget *parent = nullptr);
|
||||
static std::shared_ptr<Macro> ShowWizard(QWidget *parent);
|
||||
static bool
|
||||
CreateMacro(std::shared_ptr<Macro> ¯o, const std::string ¯oName,
|
||||
const std::string &conditionId, obs_data_t *conditionData,
|
||||
const std::string &actionId, obs_data_t *actionData);
|
||||
|
||||
private:
|
||||
void markFirstRunComplete();
|
||||
|
||||
std::shared_ptr<Macro> _macro;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
Loading…
Reference in New Issue
Block a user