diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index 800788be7ec8aa..8fb3bea3a3816d 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -379,7 +379,70 @@ struct OBSStudioAPI : obs_frontend_callbacks { void *obs_frontend_add_dock(void *dock) override { - return (void *)main->AddDockWidget((QDockWidget *)dock); + QDockWidget *d = reinterpret_cast(dock); + + QString name = d->objectName(); + if (name.isEmpty() || main->IsDockObjectNameUsed(name)) { + blog(LOG_WARNING, + "The object name of the added dock is empty or already used," + " a temporary one will be set to avoid conflicts"); + + char *uuid = os_generate_uuid(); + name = QT_UTF8(uuid); + bfree(uuid); + name.append("_oldExtraDock"); + + d->setObjectName(name); + } + + return (void *)main->AddDockWidget(d); + } + + bool obs_frontend_add_dock_by_id(const char *id, const char *title, + void *widget) override + { + if (main->IsDockObjectNameUsed(QT_UTF8(id))) { + blog(LOG_WARNING, + "Dock id '%s' already used! " + "Duplicate library?", + id); + return false; + } + + OBSDock *dock = new OBSDock(main); + dock->setWidget((QWidget *)widget); + dock->setWindowTitle(QT_UTF8(title)); + dock->setObjectName(QT_UTF8(id)); + + main->AddDockWidget(dock, Qt::RightDockWidgetArea); + + dock->setFloating(true); + dock->setVisible(false); + + return true; + } + + void obs_frontend_remove_dock(const char *id) override + { + main->RemoveDockWidget(QT_UTF8(id)); + } + + bool obs_frontend_add_custom_qdock(const char *id, void *dock) override + { + if (main->IsDockObjectNameUsed(QT_UTF8(id))) { + blog(LOG_WARNING, + "Dock id '%s' already used! " + "Duplicate library?", + id); + return false; + } + + QDockWidget *d = reinterpret_cast(dock); + d->setObjectName(QT_UTF8(id)); + + main->AddCustomDockWidget(d); + + return true; } void obs_frontend_add_event_callback(obs_frontend_event_cb callback, diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 56b349da4a3fa4..34747fc07b5ab1 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -330,6 +330,26 @@ void *obs_frontend_add_dock(void *dock) return !!callbacks_valid() ? c->obs_frontend_add_dock(dock) : nullptr; } +bool obs_frontend_add_dock_by_id(const char *id, const char *title, + void *widget) +{ + return !!callbacks_valid() + ? c->obs_frontend_add_dock_by_id(id, title, widget) + : false; +} + +void obs_frontend_remove_dock(const char *id) +{ + if (callbacks_valid()) + c->obs_frontend_remove_dock(id); +} + +bool obs_frontend_add_custom_qdock(const char *id, void *dock) +{ + return !!callbacks_valid() ? c->obs_frontend_add_custom_qdock(id, dock) + : false; +} + void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) { diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index df00024c2dde00..a0913e683359f1 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -138,8 +138,18 @@ EXPORT void obs_frontend_add_tools_menu_item(const char *name, void *private_data); /* takes QDockWidget and returns QAction */ +OBS_DEPRECATED EXPORT void *obs_frontend_add_dock(void *dock); +/* takes QWidget for widget */ +EXPORT bool obs_frontend_add_dock_by_id(const char *id, const char *title, + void *widget); + +EXPORT void obs_frontend_remove_dock(const char *id); + +/* takes QDockWidget for dock */ +EXPORT bool obs_frontend_add_custom_qdock(const char *id, void *dock); + typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, void *private_data); diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index e0cd9a7b718692..90a0e06ea9dd5b 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -66,6 +66,13 @@ struct obs_frontend_callbacks { virtual void *obs_frontend_add_dock(void *dock) = 0; + virtual bool obs_frontend_add_dock_by_id(const char *id, + const char *title, + void *widget) = 0; + virtual void obs_frontend_remove_dock(const char *id) = 0; + virtual bool obs_frontend_add_custom_qdock(const char *id, + void *dock) = 0; + virtual void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) = 0; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index f25fc3251d6f18..d26d8951a65280 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -9296,15 +9296,18 @@ void OBSBasic::on_resetDocks_triggered(bool force) for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { if (!oldExtraDocks[i]) { oldExtraDocks.removeAt(i); + oldExtraDockNames.removeAt(i); } } #ifdef BROWSER_AVAILABLE if ((oldExtraDocks.size() || extraDocks.size() || - extraBrowserDocks.size()) && + extraCustomDocks.size() || extraBrowserDocks.size()) && !force) { #else - if ((oldExtraDocks.size() || extraDocks.size()) && !force) { + if ((oldExtraDocks.size() || extraDocks.size() || + extraCustomDocks.size()) && + !force) { #endif QMessageBox::StandardButton button = QMessageBox::question( this, QTStr("ResetUIWarning.Title"), @@ -9337,6 +9340,7 @@ void OBSBasic::on_resetDocks_triggered(bool force) } RESET_DOCKLIST(extraDocks) + RESET_DOCKLIST(extraCustomDocks) #ifdef BROWSER_AVAILABLE RESET_DOCKLIST(extraBrowserDocks) #endif @@ -9396,6 +9400,9 @@ void OBSBasic::on_lockDocks_toggled(bool lock) for (int i = extraDocks.size() - 1; i >= 0; i--) extraDocks[i]->setFeatures(features); + for (int i = extraCustomDocks.size() - 1; i >= 0; i--) + extraCustomDocks[i]->setFeatures(features); + #ifdef BROWSER_AVAILABLE for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) extraBrowserDocks[i]->setFeatures(features); @@ -9404,6 +9411,7 @@ void OBSBasic::on_lockDocks_toggled(bool lock) for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { if (!oldExtraDocks[i]) { oldExtraDocks.removeAt(i); + oldExtraDockNames.removeAt(i); } else { oldExtraDocks[i]->setFeatures(features); } @@ -10196,6 +10204,10 @@ void OBSBasic::ResizeOutputSizeOfSource() QAction *OBSBasic::AddDockWidget(QDockWidget *dock) { + // Prevent the object name from being changed + connect(dock, &QObject::objectNameChanged, this, + &OBSBasic::RepairOldExtraDockName); + #ifdef BROWSER_AVAILABLE QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); @@ -10210,6 +10222,7 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock) action->setCheckable(true); assignDockToggle(dock, action); oldExtraDocks.push_back(dock); + oldExtraDockNames.push_back(dock->objectName()); bool lock = ui->lockDocks->isChecked(); QDockWidget::DockWidgetFeatures features = @@ -10224,12 +10237,30 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock) for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { if (!oldExtraDocks[i]) { oldExtraDocks.removeAt(i); + oldExtraDockNames.removeAt(i); } } return action; } +void OBSBasic::RepairOldExtraDockName() +{ + QDockWidget *dock = reinterpret_cast(sender()); + int idx = oldExtraDocks.indexOf(dock); + QSignalBlocker block(dock); + + if (idx == -1) { + blog(LOG_WARNING, "A dock got its object name changed"); + return; + } + + blog(LOG_WARNING, "The dock '%s' got its object name restored", + QT_TO_UTF8(oldExtraDockNames[idx])); + + dock->setObjectName(oldExtraDockNames[idx]); +} + void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) { @@ -10271,13 +10302,70 @@ void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, void OBSBasic::RemoveDockWidget(const QString &name) { - if (!extraDockNames.contains(name)) + if (extraDockNames.contains(name)) { + int idx = extraDockNames.indexOf(name); + extraDockNames.removeAt(idx); + extraDocks[idx].clear(); + extraDocks.removeAt(idx); + } else if (extraCustomDockNames.contains(name)) { + int idx = extraCustomDockNames.indexOf(name); + extraCustomDockNames.removeAt(idx); + removeDockWidget(extraCustomDocks[idx]); + extraCustomDocks.removeAt(idx); + } +} + +bool OBSBasic::IsDockObjectNameUsed(const QString &name) +{ + QStringList list; + list << "scenesDock" + << "sourcesDock" + << "mixerDock" + << "transitionsDock" + << "controlsDock" + << "statsDock"; + list << oldExtraDockNames; + list << extraDockNames; + list << extraCustomDockNames; + + return list.contains(name); +} + +void OBSBasic::AddCustomDockWidget(QDockWidget *dock) +{ + // Prevent the object name from being changed + connect(dock, &QObject::objectNameChanged, this, + &OBSBasic::RepairCustomExtraDockName); + + bool lock = ui->lockDocks->isChecked(); + QDockWidget::DockWidgetFeatures features = + lock ? QDockWidget::NoDockWidgetFeatures + : (QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable); + + dock->setFeatures(features); + addDockWidget(Qt::RightDockWidgetArea, dock); + + extraCustomDockNames.push_back(dock->objectName()); + extraCustomDocks.push_back(dock); +} + +void OBSBasic::RepairCustomExtraDockName() +{ + QDockWidget *dock = reinterpret_cast(sender()); + int idx = extraCustomDocks.indexOf(dock); + QSignalBlocker block(dock); + + if (idx == -1) { + blog(LOG_WARNING, "A custom dock got its object name changed"); return; + } + + blog(LOG_WARNING, "The custom dock '%s' got its object name restored", + QT_TO_UTF8(extraCustomDockNames[idx])); - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].clear(); - extraDocks.removeAt(idx); + dock->setObjectName(extraCustomDockNames[idx]); } OBSBasic *OBSBasic::Get() diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 86509213df7ee3..5de1d39e4d23d4 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -229,6 +229,7 @@ class OBSBasic : public OBSMainWindow { std::vector signalHandlers; QList> oldExtraDocks; + QStringList oldExtraDockNames; bool loaded = false; long disableSaving = 1; @@ -556,6 +557,9 @@ class OBSBasic : public OBSMainWindow { QStringList extraDockNames; QList> extraDocks; + QStringList extraCustomDockNames; + QList> extraCustomDocks; + #ifdef BROWSER_AVAILABLE QPointer extraBrowserMenuDocksSeparator; @@ -970,6 +974,8 @@ private slots: void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); void RemoveDockWidget(const QString &name); + bool IsDockObjectNameUsed(const QString &name); + void AddCustomDockWidget(QDockWidget *dock); static OBSBasic *Get(); @@ -1201,6 +1207,9 @@ private slots: void ResizeOutputSizeOfSource(); + void RepairOldExtraDockName(); + void RepairCustomExtraDockName(); + public slots: void on_actionResetTransform_triggered(); diff --git a/docs/sphinx/reference-frontend-api.rst b/docs/sphinx/reference-frontend-api.rst index 4e5e89118e7c9d..a68db69ce4f527 100644 --- a/docs/sphinx/reference-frontend-api.rst +++ b/docs/sphinx/reference-frontend-api.rst @@ -447,6 +447,48 @@ Functions :param dock: QDockWidget to add/create :return: A pointer to the added QAction +.. deprecated:: 29.1 + Prefer :c:func:`obs_frontend_add_dock_by_id()` or + :c:func:`obs_frontend_add_custom_qdock()` instead. + +--------------------------------------- + +.. function:: bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) + + Adds a dock with the widget to the UI with a toggle in the Docks + menu. + + Note: Use :c:func:`obs_frontend_remove_dock` to remove the dock + and the id from the UI. + + :param id: Unique identifier of the dock + :param title: Window title of the dock + :param widget: QWidget to insert in the dock + :return: *true* if the dock was added, *false* if the id was already + used + +--------------------------------------- + +.. function:: void obs_frontend_remove_dock(const char *id) + + Removes the dock with this id from the UI. + + :param id: Unique identifier of the dock to remove. + +--------------------------------------- + +.. function:: bool obs_frontend_add_custom_qdock(const char *id, void *dock) + + Adds a custom dock to the UI with no toggle. + + Note: Use :c:func:`obs_frontend_remove_dock` to remove the dock + reference and id from the UI. + + :param id: Unique identifier of the dock + :param dock: QDockWidget to add + :return: *true* if the dock was added, *false* if the id was already + used + --------------------------------------- .. function:: void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data)