diff --git a/CMakeLists.txt b/CMakeLists.txt index 35c449b7..ba2972ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,7 +204,6 @@ target_sources( lib/utils/name-dialog.hpp lib/utils/non-modal-dialog.cpp lib/utils/non-modal-dialog.hpp - lib/utils/obs-dock.hpp lib/utils/obs-module-helper.cpp lib/utils/obs-module-helper.hpp lib/utils/path-helpers.cpp diff --git a/lib/macro/macro-dock.cpp b/lib/macro/macro-dock.cpp index 61395338..ad8cc338 100644 --- a/lib/macro/macro-dock.cpp +++ b/lib/macro/macro-dock.cpp @@ -6,14 +6,14 @@ namespace advss { -MacroDock::MacroDock(std::weak_ptr m, QWidget *parent, +MacroDock::MacroDock(std::weak_ptr m, const StringVariable &runButtonText, const StringVariable &pauseButtonText, const StringVariable &unpauseButtonText, const StringVariable &conditionsTrueText, const StringVariable &conditionsFalseText, bool enableHighlight) - : OBSDock(parent), + : QFrame(), _runButtonText(runButtonText), _pauseButtonText(pauseButtonText), _unpauseButtonText(unpauseButtonText), @@ -25,19 +25,16 @@ MacroDock::MacroDock(std::weak_ptr m, QWidget *parent, _statusText(new QLabel(conditionsFalseText.c_str())), _macro(m) { + setFrameShape(QFrame::StyledPanel); + setFrameShadow(QFrame::Sunken); + auto macro = _macro.lock(); if (macro) { - setWindowTitle(QString::fromStdString(macro->Name())); _run->setVisible(macro->DockHasRunButton()); _pauseToggle->setVisible(macro->DockHasPauseButton()); _statusText->setVisible(macro->DockHasStatusLabel()); - } else { - setWindowTitle(""); } - setFeatures(DockWidgetClosable | DockWidgetMovable | - DockWidgetFloatable); - QWidget::connect(_run, SIGNAL(clicked()), this, SLOT(RunClicked())); QWidget::connect(_pauseToggle, SIGNAL(clicked()), this, SLOT(PauseToggleClicked())); @@ -52,21 +49,7 @@ MacroDock::MacroDock(std::weak_ptr m, QWidget *parent, QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight())); _timer.start(500); - // QFrame wrapper is necessary to avoid dock being partially - // transparent - QFrame *wrapper = new QFrame; - wrapper->setFrameShape(QFrame::StyledPanel); - wrapper->setFrameShadow(QFrame::Sunken); - wrapper->setLayout(layout); - setWidget(wrapper); - - setFloating(true); - hide(); -} - -void MacroDock::SetName(const QString &name) -{ - setWindowTitle(name); + setLayout(layout); } void MacroDock::ShowRunButton(bool value) diff --git a/lib/macro/macro-dock.hpp b/lib/macro/macro-dock.hpp index d1cf7eac..4442f058 100644 --- a/lib/macro/macro-dock.hpp +++ b/lib/macro/macro-dock.hpp @@ -1,10 +1,9 @@ #pragma once -#include "obs-dock.hpp" #include "variable-string.hpp" +#include #include #include -#include #include #include @@ -12,18 +11,16 @@ namespace advss { class Macro; -class MacroDock : public OBSDock { +class MacroDock : public QFrame { Q_OBJECT public: - MacroDock(std::weak_ptr, QWidget *parent, - const StringVariable &runButtonText, + MacroDock(std::weak_ptr, const StringVariable &runButtonText, const StringVariable &pauseButtonText, const StringVariable &unpauseButtonText, const StringVariable &conditionsTrueText, const StringVariable &conditionsFalseText, bool enableHighlight); - void SetName(const QString &); void ShowRunButton(bool); void SetRunButtonText(const StringVariable &); void ShowPauseButton(bool); diff --git a/lib/macro/macro-tab.cpp b/lib/macro/macro-tab.cpp index 1edae7cf..7c064f82 100644 --- a/lib/macro/macro-tab.cpp +++ b/lib/macro/macro-tab.cpp @@ -1165,7 +1165,7 @@ void AdvSceneSwitcher::CopyMacro() } OBSDataAutoRelease data = obs_data_create(); - macro->Save(data); + macro->Save(data, true); newMacro->Load(data); newMacro->PostLoad(); newMacro->SetName(name); diff --git a/lib/macro/macro.cpp b/lib/macro/macro.cpp index ecbe3451..c5542b06 100644 --- a/lib/macro/macro.cpp +++ b/lib/macro/macro.cpp @@ -14,6 +14,63 @@ #include #include #include +#include + +#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0) +#include + +namespace { + +struct DockMapEntry { + QAction *action = nullptr; + QWidget *dock = nullptr; +}; + +} // namespace + +static std::unordered_map dockIds; +static std::mutex dockMutex; + +static bool obs_frontend_add_dock_by_id(const char *id, const char *title, + QWidget *widget) +{ + std::lock_guard lock(dockMutex); + if (dockIds.count(id) > 0) { + return false; + } + + widget->setObjectName(id); + + auto dock = new QDockWidget(); + dock->setWindowTitle(title); + dock->setWidget(widget); + dock->setFloating(true); + dock->setVisible(false); + dock->setFeatures(QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable); + + auto action = static_cast(obs_frontend_add_dock(dock)); + if (!action) { + return false; + } + dockIds[id] = {action, dock}; + return true; +} + +static void obs_frontend_remove_dock(const char *id) +{ + std::lock_guard lock(dockMutex); + auto it = dockIds.find(id); + if (it == dockIds.end()) { + return; + } + it->second.action->deleteLater(); + it->second.dock->deleteLater(); + dockIds.erase(it); +} + +#endif namespace advss { @@ -258,9 +315,15 @@ int64_t Macro::MsSinceLastCheck() const void Macro::SetName(const std::string &name) { + const bool nameChanged = _name == name; _name = name; + SetHotkeysDesc(); - SetDockWidgetName(); + + if (nameChanged) { + _dockId = GenerateDockId(); + } + EnableDock(_registerDock); } void Macro::ResetTimers() @@ -315,11 +378,6 @@ bool Macro::RunElseActions(bool ignorePause) return RunActionsHelper(_elseActions, ignorePause); } -bool Macro::DockIsVisible() const -{ - return _dock && _dockAction && _dock->isVisible(); -} - bool Macro::WasPausedSince( const std::chrono::high_resolution_clock::time_point &time) const { @@ -550,9 +608,11 @@ std::shared_ptr Macro::Parent() const return _parent.lock(); } -bool Macro::Save(obs_data_t *obj) const +bool Macro::Save(obs_data_t *obj, bool saveForCopy) const { - obs_data_set_string(obj, "name", _name.c_str()); + if (!saveForCopy) { + obs_data_set_string(obj, "name", _name.c_str()); + } obs_data_set_bool(obj, "pause", _paused); obs_data_set_bool(obj, "parallel", _runInParallel); obs_data_set_bool(obj, "onChange", _performActionsOnChange); @@ -568,7 +628,7 @@ bool Macro::Save(obs_data_t *obj) const return true; } - SaveDockSettings(obj); + SaveDockSettings(obj, saveForCopy); SaveSplitterPos(_actionConditionSplitterPosition, obj, "macroActionConditionSplitterPosition"); @@ -813,14 +873,10 @@ bool Macro::PauseHotkeysEnabled() const return _registerHotkeys; } -void Macro::SaveDockSettings(obs_data_t *obj) const +void Macro::SaveDockSettings(obs_data_t *obj, bool saveForCopy) const { auto dockSettings = obs_data_create(); obs_data_set_bool(dockSettings, "register", _registerDock); - // The object name is used to restore the position of the dock - if (_registerDock) { - SetDockWidgetName(); - } obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton); obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton); obs_data_set_bool(dockSettings, "hasStatusLabel", _dockHasStatusLabel); @@ -833,18 +889,14 @@ void Macro::SaveDockSettings(obs_data_t *obj) const "conditionsTrueStatusText"); _conditionsFalseStatusText.Save(dockSettings, "conditionsFalseStatusText"); - if (_dock) { - auto window = static_cast( - obs_frontend_get_main_window()); - obs_data_set_bool(dockSettings, "isFloating", - _dock->isFloating()); - obs_data_set_bool(dockSettings, "isVisible", DockIsVisible()); - obs_data_set_int(dockSettings, "area", - window->dockWidgetArea(_dock)); - obs_data_set_string( - dockSettings, "geometry", - _dock->saveGeometry().toBase64().constData()); + if (saveForCopy) { + auto uuid = GenerateDockId(); + obs_data_set_string(dockSettings, "dockId", uuid.c_str()); + + } else { + obs_data_set_string(dockSettings, "dockId", _dockId.c_str()); } + obs_data_set_int(dockSettings, "version", 1); obs_data_set_obj(obj, "dockSettings", dockSettings); obs_data_release(dockSettings); } @@ -861,8 +913,13 @@ void Macro::LoadDockSettings(obs_data_t *obj) return; } + if (!obs_data_has_user_value(dockSettings, "version")) { + _dockId = std::string("ADVSS-") + _name; + } else { + _dockId = obs_data_get_string(dockSettings, "dockId"); + } + const bool dockEnabled = obs_data_get_bool(dockSettings, "register"); - _dockIsVisible = obs_data_get_bool(dockSettings, "isVisible"); // TODO: remove these default settings in a future version obs_data_set_default_string( @@ -890,15 +947,6 @@ void Macro::LoadDockSettings(obs_data_t *obj) obs_data_get_bool(dockSettings, "hasStatusLabel"); _dockHighlight = obs_data_get_bool(dockSettings, "highlightIfConditionsTrue"); - _dockIsFloating = obs_data_get_bool(dockSettings, "isFloating"); - _dockArea = static_cast( - obs_data_get_int(dockSettings, "area")); - auto geometryStr = - obs_data_get_string(dockSettings, "geometry"); - if (geometryStr && strlen(geometryStr)) { - _dockGeo = - QByteArray::fromBase64(QByteArray(geometryStr)); - } } EnableDock(dockEnabled); obs_data_release(dockSettings); @@ -906,47 +954,29 @@ void Macro::LoadDockSettings(obs_data_t *obj) void Macro::EnableDock(bool value) { - if (_registerDock == value) { - return; - } - // Reset dock regardless RemoveDock(); - // Unregister dock - if (_registerDock) { - _dockIsFloating = true; - _dockGeo = QByteArray(); + if (!value) { _registerDock = value; return; } - // Create new dock widget - auto window = - static_cast(obs_frontend_get_main_window()); - _dock = new MacroDock(GetWeakMacroByName(_name.c_str()), window, - _runButtonText, _pauseButtonText, - _unpauseButtonText, _conditionsTrueStatusText, + _dock = new MacroDock(GetWeakMacroByName(_name.c_str()), _runButtonText, + _pauseButtonText, _unpauseButtonText, + _conditionsTrueStatusText, _conditionsFalseStatusText, _dockHighlight); - SetDockWidgetName(); // Used by OBS to restore position - // Register new dock - _dockAction = static_cast(obs_frontend_add_dock(_dock)); - - // Note that OBSBasic::OBSInit() has precedence over the visibility and - // geometry set here. - // The function calls here are only intended to attempt to restore the - // dock status when switching scene collections. - if (InitialLoadIsComplete()) { - _dock->setVisible(_dockIsVisible); - if (window->dockWidgetArea(_dock) != _dockArea) { - window->addDockWidget(_dockArea, _dock); - } - if (_dock->isFloating() != _dockIsFloating) { - _dock->setFloating(_dockIsFloating); - } - _dock->restoreGeometry(_dockGeo); + if (!obs_frontend_add_dock_by_id(_dockId.c_str(), _name.c_str(), + _dock)) { + blog(LOG_INFO, "failed to add macro dock for macro %s", + _name.c_str()); + _dock->deleteLater(); + _dock = nullptr; + _registerDock = false; + return; } + _registerDock = value; } @@ -1043,28 +1073,22 @@ StringVariable Macro::ConditionsFalseStatusText() const void Macro::RemoveDock() { - if (_dock) { - _dock->close(); - _dock->deleteLater(); - _dockAction->deleteLater(); - _dock = nullptr; - _dockAction = nullptr; - } + obs_frontend_remove_dock(_dockId.c_str()); + _dock = nullptr; } -void Macro::SetDockWidgetName() const +std::string Macro::GenerateDockId() { - if (!_dock) { - return; - } - // Set prefix to avoid dock name conflict - _dock->setObjectName("ADVSS-" + QString::fromStdString(_name)); - _dock->SetName(QString::fromStdString(_name)); +#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(30, 0, 0) + auto uuid = os_generate_uuid(); + auto id = std::string("advss-macro-dock-") + std::string(uuid); + bfree(uuid); + return id; - if (!_dockAction) { - return; - } - _dockAction->setText(QString::fromStdString(_name)); +#else + static std::atomic_int16_t idCounter = 0; + return std::to_string(++idCounter); +#endif } static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) diff --git a/lib/macro/macro.hpp b/lib/macro/macro.hpp index bf7f015f..a73ec917 100644 --- a/lib/macro/macro.hpp +++ b/lib/macro/macro.hpp @@ -98,7 +98,7 @@ public: std::shared_ptr Parent() const; // Saving and loading - bool Save(obs_data_t *obj) const; + bool Save(obs_data_t *obj, bool saveForCopy = false) const; bool Load(obs_data_t *obj); // Some macros can refer to other macros, which are not yet loaded. // Use this function to set these references after loading is complete. @@ -155,11 +155,10 @@ private: bool RunActions(bool ignorePause); bool RunElseActions(bool ignorePause); - bool DockIsVisible() const; - void SetDockWidgetName() const; - void SaveDockSettings(obs_data_t *obj) const; + void SaveDockSettings(obs_data_t *obj, bool saveForCopy) const; void LoadDockSettings(obs_data_t *obj); void RemoveDock(); + static std::string GenerateDockId(); std::string _name = ""; bool _die = false; @@ -217,12 +216,8 @@ private: obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true"); StringVariable _conditionsFalseStatusText = obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false"); - bool _dockIsFloating = true; - bool _dockIsVisible = false; - Qt::DockWidgetArea _dockArea; - QByteArray _dockGeo; MacroDock *_dock = nullptr; - QAction *_dockAction = nullptr; + std::string _dockId = GenerateDockId(); }; void LoadMacros(obs_data_t *obj); diff --git a/lib/utils/obs-dock.hpp b/lib/utils/obs-dock.hpp deleted file mode 100644 index 8ec81887..00000000 --- a/lib/utils/obs-dock.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include - -// QDockWidget wrapper enable applying "OBSDock" stylesheet -class OBSDock : public QDockWidget { - Q_OBJECT - -public: - inline OBSDock(QWidget *parent = nullptr) : QDockWidget(parent) {} -}; diff --git a/lib/utils/status-control.cpp b/lib/utils/status-control.cpp index 41ef00c3..9203b5c9 100644 --- a/lib/utils/status-control.cpp +++ b/lib/utils/status-control.cpp @@ -1,4 +1,5 @@ #include "status-control.hpp" +#include "log-helper.hpp" #include "obs-module-helper.hpp" #include "path-helpers.hpp" #include "plugin-state-helpers.hpp" @@ -11,10 +12,30 @@ #include #include +#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0) +#include + +static bool obs_frontend_add_dock_by_id(const char *id, const char *title, + QWidget *widget) +{ + widget->setObjectName(id); + + auto dock = new QDockWidget(); + dock->setWindowTitle(title); + dock->setWidget(widget); + dock->setFloating(true); + dock->setVisible(false); + dock->setFeatures(QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable); + + return !!obs_frontend_add_dock(dock); +} +#endif + namespace advss { void OpenSettingsWindow(); -StatusDock *dock = nullptr; static QString colorToString(const QColor &color) { @@ -168,16 +189,12 @@ void StatusControl::SetStatusStyleSheet(bool stopped) const _status->setStyleSheet(style); } -StatusDock::StatusDock(QWidget *parent) : OBSDock(parent) +StatusDockWidget::StatusDockWidget(QWidget *parent) : QFrame(parent) { - setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); - setFeatures(DockWidgetClosable | DockWidgetMovable | - DockWidgetFloatable); - // Setting a fixed object name is crucial for OBS to be able to restore - // the docks position, if the dock is not floating - setObjectName("Adv-ss-dock"); + setFrameShape(QFrame::StyledPanel); + setFrameShadow(QFrame::Sunken); - QAction *action = new QAction; + auto action = new QAction; action->setProperty("themeID", QVariant(QString::fromUtf8("cogsIcon"))); action->connect(action, &QAction::triggered, OpenSettingsWindow); const auto path = QString::fromStdString(GetDataFilePath( @@ -195,29 +212,21 @@ StatusDock::StatusDock(QWidget *parent) : OBSDock(parent) statusControl->ButtonLayout()->setStretchFactor(statusControl->Button(), 100); - QVBoxLayout *layout = new QVBoxLayout; + auto layout = new QVBoxLayout; layout->addWidget(statusControl); layout->setContentsMargins(0, 0, 0, 0); - - // QFrame wrapper is necessary to avoid dock being partially - // transparent - QFrame *wrapper = new QFrame; - wrapper->setFrameShape(QFrame::StyledPanel); - wrapper->setFrameShadow(QFrame::Sunken); - wrapper->setLayout(layout); - setWidget(wrapper); - - setFloating(true); - hide(); + setLayout(layout); } void SetupDock() { - dock = new StatusDock( - static_cast(obs_frontend_get_main_window())); - // Added for cosmetic reasons to avoid brief flash of dock window on startup - dock->setVisible(false); - obs_frontend_add_dock(dock); + auto dock = new StatusDockWidget(); + if (!obs_frontend_add_dock_by_id( + "advss-status-dock", + obs_module_text("AdvSceneSwitcher.windowTitle"), dock)) { + blog(LOG_INFO, "failed to register status dock!"); + dock->deleteLater(); + } } } // namespace advss diff --git a/lib/utils/status-control.hpp b/lib/utils/status-control.hpp index 586b827e..df88d94c 100644 --- a/lib/utils/status-control.hpp +++ b/lib/utils/status-control.hpp @@ -1,6 +1,4 @@ #pragma once -#include "obs-dock.hpp" - #include #include #include @@ -38,11 +36,11 @@ private: bool _setToStopped = true; }; -class StatusDock : public OBSDock { +class StatusDockWidget : public QFrame { Q_OBJECT public: - StatusDock(QWidget *parent = 0); + StatusDockWidget(QWidget *parent = 0); }; void SetupDock();