浏览代码

UI: Refactor integration and browser docks

Use the QAction provided by QDockWidget with new Qt connection rather
than creating a new one for each dock.

Separate extra browser docks from extra docks, the latter is meant for
plugin/integration docks.
tytan652 3 年之前
父节点
当前提交
e6873d3278
共有 9 个文件被更改,包括 222 次插入108 次删除
  1. 28 15
      UI/auth-restream.cpp
  2. 1 8
      UI/auth-restream.hpp
  3. 45 31
      UI/auth-twitch.cpp
  4. 1 8
      UI/auth-twitch.hpp
  5. 19 5
      UI/auth-youtube.cpp
  6. 2 2
      UI/auth-youtube.hpp
  7. 110 21
      UI/window-basic-main.cpp
  8. 9 2
      UI/window-basic-main.hpp
  9. 7 16
      UI/window-extra-browsers.cpp

+ 28 - 15
UI/auth-restream.cpp

@@ -24,12 +24,28 @@ using namespace json11;
 #define RESTREAM_STREAMKEY_URL "https://api.restream.io/v2/user/streamKey"
 #define RESTREAM_SCOPE_VERSION 1
 
+#define RESTREAM_CHAT_DOCK_NAME "restreamChat"
+#define RESTREAM_INFO_DOCK_NAME "restreamInfo"
+#define RESTREAM_CHANNELS_DOCK_NAME "restreamChannel"
+
 static Auth::Def restreamDef = {"Restream", Auth::Type::OAuth_StreamKey};
 
 /* ------------------------------------------------------------------------- */
 
 RestreamAuth::RestreamAuth(const Def &d) : OAuthStreamKey(d) {}
 
+RestreamAuth::~RestreamAuth()
+{
+	if (!uiLoaded)
+		return;
+
+	OBSBasic *main = OBSBasic::Get();
+
+	main->RemoveDockWidget(RESTREAM_CHAT_DOCK_NAME);
+	main->RemoveDockWidget(RESTREAM_INFO_DOCK_NAME);
+	main->RemoveDockWidget(RESTREAM_CHANNELS_DOCK_NAME);
+}
+
 bool RestreamAuth::GetChannelInfo()
 try {
 	std::string client_id = RESTREAM_CLIENTID;
@@ -134,52 +150,49 @@ void RestreamAuth::LoadUI()
 	QSize size = main->frameSize();
 	QPoint pos = main->pos();
 
-	chat.reset(new BrowserDock());
-	chat->setObjectName("restreamChat");
+	BrowserDock *chat = new BrowserDock();
+	chat->setObjectName(RESTREAM_CHAT_DOCK_NAME);
 	chat->resize(420, 600);
 	chat->setMinimumSize(200, 300);
 	chat->setWindowTitle(QTStr("Auth.Chat"));
 	chat->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(chat.data(), url, panel_cookies);
+	browser = cef->create_widget(chat, url, panel_cookies);
 	chat->SetWidget(browser);
 
-	main->addDockWidget(Qt::RightDockWidgetArea, chat.data());
-	chatMenu.reset(main->AddDockWidget(chat.data()));
+	main->AddDockWidget(chat, Qt::RightDockWidgetArea);
 
 	/* ----------------------------------- */
 
 	url = "https://restream.io/titles/embed";
 
-	info.reset(new BrowserDock());
-	info->setObjectName("restreamInfo");
+	BrowserDock *info = new BrowserDock();
+	info->setObjectName(RESTREAM_INFO_DOCK_NAME);
 	info->resize(410, 600);
 	info->setMinimumSize(200, 150);
 	info->setWindowTitle(QTStr("Auth.StreamInfo"));
 	info->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(info.data(), url, panel_cookies);
+	browser = cef->create_widget(info, url, panel_cookies);
 	info->SetWidget(browser);
 
-	main->addDockWidget(Qt::LeftDockWidgetArea, info.data());
-	infoMenu.reset(main->AddDockWidget(info.data()));
+	main->AddDockWidget(info, Qt::LeftDockWidgetArea);
 
 	/* ----------------------------------- */
 
 	url = "https://restream.io/channel/embed";
 
-	channels.reset(new BrowserDock());
-	channels->setObjectName("restreamChannel");
+	BrowserDock *channels = new BrowserDock();
+	channels->setObjectName(RESTREAM_CHANNELS_DOCK_NAME);
 	channels->resize(410, 600);
 	channels->setMinimumSize(410, 300);
 	channels->setWindowTitle(QTStr("RestreamAuth.Channels"));
 	channels->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(channels.data(), url, panel_cookies);
+	browser = cef->create_widget(channels, url, panel_cookies);
 	channels->SetWidget(browser);
 
-	main->addDockWidget(Qt::LeftDockWidgetArea, channels.data());
-	channelMenu.reset(main->AddDockWidget(channels.data()));
+	main->AddDockWidget(channels, Qt::LeftDockWidgetArea);
 
 	/* ----------------------------------- */
 

+ 1 - 8
UI/auth-restream.hpp

@@ -7,14 +7,6 @@ class BrowserDock;
 class RestreamAuth : public OAuthStreamKey {
 	Q_OBJECT
 
-	QSharedPointer<BrowserDock> chat;
-	QSharedPointer<BrowserDock> info;
-	QSharedPointer<BrowserDock> channels;
-
-	QSharedPointer<QAction> chatMenu;
-	QSharedPointer<QAction> infoMenu;
-	QSharedPointer<QAction> channelMenu;
-
 	bool uiLoaded = false;
 
 	virtual bool RetryLogin() override;
@@ -28,6 +20,7 @@ class RestreamAuth : public OAuthStreamKey {
 
 public:
 	RestreamAuth(const Def &d);
+	~RestreamAuth();
 
 	static std::shared_ptr<Auth> Login(QWidget *parent,
 					   const std::string &service_name);

+ 45 - 31
UI/auth-twitch.cpp

@@ -27,6 +27,11 @@ using namespace json11;
 
 #define TWITCH_SCOPE_VERSION 1
 
+#define TWITCH_CHAT_DOCK_NAME "twitchChat"
+#define TWITCH_INFO_DOCK_NAME "twitchInfo"
+#define TWITCH_STATS_DOCK_NAME "twitchStats"
+#define TWITCH_FEED_DOCK_NAME "twitchFeed"
+
 static Auth::Def twitchDef = {"Twitch", Auth::Type::OAuth_StreamKey};
 
 /* ------------------------------------------------------------------------- */
@@ -49,6 +54,19 @@ TwitchAuth::TwitchAuth(const Def &d) : OAuthStreamKey(d)
 		&TwitchAuth::TryLoadSecondaryUIPanes);
 }
 
+TwitchAuth::~TwitchAuth()
+{
+	if (!uiLoaded)
+		return;
+
+	OBSBasic *main = OBSBasic::Get();
+
+	main->RemoveDockWidget(TWITCH_CHAT_DOCK_NAME);
+	main->RemoveDockWidget(TWITCH_INFO_DOCK_NAME);
+	main->RemoveDockWidget(TWITCH_STATS_DOCK_NAME);
+	main->RemoveDockWidget(TWITCH_FEED_DOCK_NAME);
+}
+
 bool TwitchAuth::MakeApiRequest(const char *path, Json &json_out)
 {
 	std::string client_id = TWITCH_CLIENTID;
@@ -233,16 +251,16 @@ void TwitchAuth::LoadUI()
 	QSize size = main->frameSize();
 	QPoint pos = main->pos();
 
-	chat.reset(new BrowserDock());
-	chat->setObjectName("twitchChat");
+	BrowserDock *chat = new BrowserDock();
+	chat->setObjectName(TWITCH_CHAT_DOCK_NAME);
 	chat->resize(300, 600);
 	chat->setMinimumSize(200, 300);
 	chat->setWindowTitle(QTStr("Auth.Chat"));
 	chat->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(chat.data(), url, panel_cookies);
+	browser = cef->create_widget(chat, url, panel_cookies);
 	chat->SetWidget(browser);
-	cef->add_force_popup_url(moderation_tools_url, chat.data());
+	cef->add_force_popup_url(moderation_tools_url, chat);
 
 	if (App()->IsThemeDark()) {
 		script = "localStorage.setItem('twilight.theme', 1);";
@@ -261,8 +279,7 @@ void TwitchAuth::LoadUI()
 
 	browser->setStartupScript(script);
 
-	main->addDockWidget(Qt::RightDockWidgetArea, chat.data());
-	chatMenu.reset(main->AddDockWidget(chat.data()));
+	main->AddDockWidget(chat, Qt::RightDockWidgetArea);
 
 	/* ----------------------------------- */
 
@@ -323,19 +340,18 @@ void TwitchAuth::LoadSecondaryUIPanes()
 	url += name;
 	url += "/stream-manager/edit-stream-info";
 
-	info.reset(new BrowserDock());
-	info->setObjectName("twitchInfo");
+	BrowserDock *info = new BrowserDock();
+	info->setObjectName(TWITCH_INFO_DOCK_NAME);
 	info->resize(300, 650);
 	info->setMinimumSize(200, 300);
 	info->setWindowTitle(QTStr("Auth.StreamInfo"));
 	info->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(info.data(), url, panel_cookies);
+	browser = cef->create_widget(info, url, panel_cookies);
 	info->SetWidget(browser);
 	browser->setStartupScript(script);
 
-	main->addDockWidget(Qt::RightDockWidgetArea, info.data());
-	infoMenu.reset(main->AddDockWidget(info.data()));
+	main->AddDockWidget(info, Qt::RightDockWidgetArea);
 
 	/* ----------------------------------- */
 
@@ -343,19 +359,18 @@ void TwitchAuth::LoadSecondaryUIPanes()
 	url += name;
 	url += "/dashboard/live/stats";
 
-	stat.reset(new BrowserDock());
-	stat->setObjectName("twitchStats");
-	stat->resize(200, 250);
-	stat->setMinimumSize(200, 150);
-	stat->setWindowTitle(QTStr("TwitchAuth.Stats"));
-	stat->setAllowedAreas(Qt::AllDockWidgetAreas);
+	BrowserDock *stats = new BrowserDock();
+	stats->setObjectName(TWITCH_STATS_DOCK_NAME);
+	stats->resize(200, 250);
+	stats->setMinimumSize(200, 150);
+	stats->setWindowTitle(QTStr("TwitchAuth.Stats"));
+	stats->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(stat.data(), url, panel_cookies);
-	stat->SetWidget(browser);
+	browser = cef->create_widget(stats, url, panel_cookies);
+	stats->SetWidget(browser);
 	browser->setStartupScript(script);
 
-	main->addDockWidget(Qt::RightDockWidgetArea, stat.data());
-	statMenu.reset(main->AddDockWidget(stat.data()));
+	main->AddDockWidget(stats, Qt::RightDockWidgetArea);
 
 	/* ----------------------------------- */
 
@@ -364,36 +379,35 @@ void TwitchAuth::LoadSecondaryUIPanes()
 	url += "/stream-manager/activity-feed";
 	url += "?uuid=" + uuid;
 
-	feed.reset(new BrowserDock());
-	feed->setObjectName("twitchFeed");
+	BrowserDock *feed = new BrowserDock();
+	feed->setObjectName(TWITCH_FEED_DOCK_NAME);
 	feed->resize(300, 650);
 	feed->setMinimumSize(200, 300);
 	feed->setWindowTitle(QTStr("TwitchAuth.Feed"));
 	feed->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(feed.data(), url, panel_cookies);
+	browser = cef->create_widget(feed, url, panel_cookies);
 	feed->SetWidget(browser);
 	browser->setStartupScript(script);
 
-	main->addDockWidget(Qt::RightDockWidgetArea, feed.data());
-	feedMenu.reset(main->AddDockWidget(feed.data()));
+	main->AddDockWidget(feed, Qt::RightDockWidgetArea);
 
 	/* ----------------------------------- */
 
 	info->setFloating(true);
-	stat->setFloating(true);
+	stats->setFloating(true);
 	feed->setFloating(true);
 
-	QSize statSize = stat->frameSize();
+	QSize statSize = stats->frameSize();
 
 	info->move(pos.x() + 50, pos.y() + 50);
-	stat->move(pos.x() + size.width() / 2 - statSize.width() / 2,
-		   pos.y() + size.height() / 2 - statSize.height() / 2);
+	stats->move(pos.x() + size.width() / 2 - statSize.width() / 2,
+		    pos.y() + size.height() / 2 - statSize.height() / 2);
 	feed->move(pos.x() + 100, pos.y() + 100);
 
 	if (firstLoad) {
 		info->setVisible(true);
-		stat->setVisible(false);
+		stats->setVisible(false);
 		feed->setVisible(false);
 	} else {
 		uint32_t lastVersion = config_get_int(App()->GlobalConfig(),

+ 1 - 8
UI/auth-twitch.hpp

@@ -15,14 +15,6 @@ class TwitchAuth : public OAuthStreamKey {
 
 	friend class TwitchLogin;
 
-	QSharedPointer<BrowserDock> chat;
-	QSharedPointer<BrowserDock> info;
-	QSharedPointer<BrowserDock> stat;
-	QSharedPointer<BrowserDock> feed;
-	QSharedPointer<QAction> chatMenu;
-	QSharedPointer<QAction> infoMenu;
-	QSharedPointer<QAction> statMenu;
-	QSharedPointer<QAction> feedMenu;
 	bool uiLoaded = false;
 
 	std::string name;
@@ -40,6 +32,7 @@ class TwitchAuth : public OAuthStreamKey {
 
 public:
 	TwitchAuth(const Def &d);
+	~TwitchAuth();
 
 	static std::shared_ptr<Auth> Login(QWidget *parent,
 					   const std::string &service_name);

+ 19 - 5
UI/auth-youtube.cpp

@@ -42,6 +42,8 @@ using namespace json11;
 #define YOUTUBE_CHAT_POPOUT_URL \
 	"https://www.youtube.com/live_chat?is_popout=1&dark_theme=1&v=%1"
 
+#define YOUTUBE_CHAT_DOCK_NAME "ytChat"
+
 static const char allowedChars[] =
 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 static const int allowedCount = static_cast<int>(sizeof(allowedChars) - 1);
@@ -71,6 +73,19 @@ YoutubeAuth::YoutubeAuth(const Def &d)
 {
 }
 
+YoutubeAuth::~YoutubeAuth()
+{
+	if (!uiLoaded)
+		return;
+
+#ifdef BROWSER_AVAILABLE
+	OBSBasic *main = OBSBasic::Get();
+
+	main->RemoveDockWidget(YOUTUBE_CHAT_DOCK_NAME);
+	chat = nullptr;
+#endif
+}
+
 bool YoutubeAuth::RetryLogin()
 {
 	return true;
@@ -139,20 +154,19 @@ void YoutubeAuth::LoadUI()
 	QSize size = main->frameSize();
 	QPoint pos = main->pos();
 
-	chat.reset(new YoutubeChatDock());
-	chat->setObjectName("ytChat");
+	chat = new YoutubeChatDock();
+	chat->setObjectName(YOUTUBE_CHAT_DOCK_NAME);
 	chat->resize(300, 600);
 	chat->setMinimumSize(200, 300);
 	chat->setWindowTitle(QTStr("Auth.Chat"));
 	chat->setAllowedAreas(Qt::AllDockWidgetAreas);
 
-	browser = cef->create_widget(chat.data(), YOUTUBE_CHAT_PLACEHOLDER_URL,
+	browser = cef->create_widget(chat, YOUTUBE_CHAT_PLACEHOLDER_URL,
 				     panel_cookies);
 	browser->setStartupScript(ytchat_script);
 
 	chat->SetWidget(browser);
-	main->addDockWidget(Qt::RightDockWidgetArea, chat.data());
-	chatMenu.reset(main->AddDockWidget(chat.data()));
+	main->AddDockWidget(chat, Qt::RightDockWidgetArea);
 
 	chat->setFloating(true);
 	chat->move(pos.x() + size.width() - chat->width() - 50, pos.y() + 50);

+ 2 - 2
UI/auth-youtube.hpp

@@ -43,8 +43,7 @@ class YoutubeAuth : public OAuthStreamKey {
 	std::string section;
 
 #ifdef BROWSER_AVAILABLE
-	QSharedPointer<YoutubeChatDock> chat;
-	QSharedPointer<QAction> chatMenu;
+	YoutubeChatDock *chat;
 #endif
 
 	virtual bool RetryLogin() override;
@@ -56,6 +55,7 @@ class YoutubeAuth : public OAuthStreamKey {
 
 public:
 	YoutubeAuth(const Def &d);
+	~YoutubeAuth();
 
 	void SetChatId(const QString &chat_id, const std::string &api_chat_id);
 	void ResetChat();

+ 110 - 21
UI/window-basic-main.cpp

@@ -9076,13 +9076,19 @@ int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const
 void OBSBasic::on_resetDocks_triggered(bool force)
 {
 	/* prune deleted extra docks */
-	for (int i = extraDocks.size() - 1; i >= 0; i--) {
-		if (!extraDocks[i]) {
-			extraDocks.removeAt(i);
+	for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
+		if (!oldExtraDocks[i]) {
+			oldExtraDocks.removeAt(i);
 		}
 	}
 
-	if (extraDocks.size() && !force) {
+#ifdef BROWSER_AVAILABLE
+	if ((oldExtraDocks.size() || extraDocks.size() ||
+	     extraBrowserDocks.size()) &&
+	    !force) {
+#else
+	if ((oldExtraDocks.size() || extraDocks.size()) && !force) {
+#endif
 		QMessageBox::StandardButton button = QMessageBox::question(
 			this, QTStr("ResetUIWarning.Title"),
 			QTStr("ResetUIWarning.Text"));
@@ -9092,17 +9098,33 @@ void OBSBasic::on_resetDocks_triggered(bool force)
 	}
 
 	/* undock/hide/center extra docks */
-	for (int i = extraDocks.size() - 1; i >= 0; i--) {
-		if (extraDocks[i]) {
-			extraDocks[i]->setVisible(true);
-			extraDocks[i]->setFloating(true);
-			extraDocks[i]->move(frameGeometry().topLeft() +
-					    rect().center() -
-					    extraDocks[i]->rect().center());
-			extraDocks[i]->setVisible(false);
+	for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
+		if (oldExtraDocks[i]) {
+			oldExtraDocks[i]->setVisible(true);
+			oldExtraDocks[i]->setFloating(true);
+			oldExtraDocks[i]->move(
+				frameGeometry().topLeft() + rect().center() -
+				oldExtraDocks[i]->rect().center());
+			oldExtraDocks[i]->setVisible(false);
 		}
 	}
 
+#define RESET_DOCKLIST(dockList)                                 \
+	for (int i = dockList.size() - 1; i >= 0; i--) {         \
+		dockList[i]->setVisible(true);                   \
+		dockList[i]->setFloating(true);                  \
+		dockList[i]->move(frameGeometry().topLeft() +    \
+				  rect().center() -              \
+				  dockList[i]->rect().center()); \
+		dockList[i]->setVisible(false);                  \
+	}
+
+	RESET_DOCKLIST(extraDocks)
+#ifdef BROWSER_AVAILABLE
+	RESET_DOCKLIST(extraBrowserDocks)
+#endif
+#undef RESET_DOCKLIST
+
 	restoreState(startingDockLayout);
 
 	int cx = width();
@@ -9154,11 +9176,19 @@ void OBSBasic::on_lockDocks_toggled(bool lock)
 	ui->controlsDock->setFeatures(mainFeatures);
 	statsDock->setFeatures(features);
 
-	for (int i = extraDocks.size() - 1; i >= 0; i--) {
-		if (!extraDocks[i]) {
-			extraDocks.removeAt(i);
+	for (int i = extraDocks.size() - 1; i >= 0; i--)
+		extraDocks[i]->setFeatures(features);
+
+#ifdef BROWSER_AVAILABLE
+	for (int i = extraBrowserDocks.size() - 1; i >= 0; i--)
+		extraBrowserDocks[i]->setFeatures(features);
+#endif
+
+	for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
+		if (!oldExtraDocks[i]) {
+			oldExtraDocks.removeAt(i);
 		} else {
-			extraDocks[i]->setFeatures(features);
+			oldExtraDocks[i]->setFeatures(features);
 		}
 	}
 }
@@ -9947,11 +9977,20 @@ void OBSBasic::ResizeOutputSizeOfSource()
 
 QAction *OBSBasic::AddDockWidget(QDockWidget *dock)
 {
+#ifdef BROWSER_AVAILABLE
+	QAction *action = new QAction(dock->windowTitle(), ui->menuDocks);
+
+	if (!extraBrowserMenuDocksSeparator.isNull())
+		ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator,
+					    action);
+	else
+		ui->menuDocks->addAction(action);
+#else
 	QAction *action = ui->menuDocks->addAction(dock->windowTitle());
-	action->setProperty("uuid", dock->property("uuid").toString());
+#endif
 	action->setCheckable(true);
 	assignDockToggle(dock, action);
-	extraDocks.push_back(dock);
+	oldExtraDocks.push_back(dock);
 
 	bool lock = ui->lockDocks->isChecked();
 	QDockWidget::DockWidgetFeatures features =
@@ -9963,15 +10002,65 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock)
 	dock->setFeatures(features);
 
 	/* prune deleted docks */
-	for (int i = extraDocks.size() - 1; i >= 0; i--) {
-		if (!extraDocks[i]) {
-			extraDocks.removeAt(i);
+	for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
+		if (!oldExtraDocks[i]) {
+			oldExtraDocks.removeAt(i);
 		}
 	}
 
 	return action;
 }
 
+void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area,
+			     bool extraBrowser)
+{
+	if (dock->objectName().isEmpty())
+		return;
+
+	bool lock = ui->lockDocks->isChecked();
+	QDockWidget::DockWidgetFeatures features =
+		lock ? QDockWidget::NoDockWidgetFeatures
+		     : (QDockWidget::DockWidgetClosable |
+			QDockWidget::DockWidgetMovable |
+			QDockWidget::DockWidgetFloatable);
+
+	setupDockAction(dock);
+	dock->setFeatures(features);
+	addDockWidget(area, dock);
+
+#ifdef BROWSER_AVAILABLE
+	if (extraBrowser && extraBrowserMenuDocksSeparator.isNull())
+		extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator();
+
+	if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull())
+		ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator,
+					    dock->toggleViewAction());
+	else
+		ui->menuDocks->addAction(dock->toggleViewAction());
+
+	if (extraBrowser)
+		return;
+#else
+	UNUSED_PARAMETER(extraBrowser);
+
+	ui->menuDocks->addAction(dock->toggleViewAction());
+#endif
+
+	extraDockNames.push_back(dock->objectName());
+	extraDocks.push_back(QSharedPointer<QDockWidget>(dock));
+}
+
+void OBSBasic::RemoveDockWidget(const QString &name)
+{
+	if (!extraDockNames.contains(name))
+		return;
+
+	int idx = extraDockNames.indexOf(name);
+	extraDockNames.removeAt(idx);
+	extraDocks[idx].clear();
+	extraDocks.removeAt(idx);
+}
+
 OBSBasic *OBSBasic::Get()
 {
 	return reinterpret_cast<OBSBasic *>(App()->GetMainWindow());

+ 9 - 2
UI/window-basic-main.hpp

@@ -228,7 +228,7 @@ private:
 
 	std::vector<OBSSignal> signalHandlers;
 
-	QList<QPointer<QDockWidget>> extraDocks;
+	QList<QPointer<QDockWidget>> oldExtraDocks;
 
 	bool loaded = false;
 	long disableSaving = 1;
@@ -551,9 +551,13 @@ private:
 
 	void UpdatePreviewProgramIndicators();
 
+	QStringList extraDockNames;
+	QList<QSharedPointer<QDockWidget>> extraDocks;
+
 #ifdef BROWSER_AVAILABLE
+	QPointer<QAction> extraBrowserMenuDocksSeparator;
+
 	QList<QSharedPointer<QDockWidget>> extraBrowserDocks;
-	QList<QSharedPointer<QAction>> extraBrowserDockActions;
 	QStringList extraBrowserDockTargets;
 
 	void ClearExtraBrowserDocks();
@@ -957,6 +961,9 @@ public:
 	void CreateEditTransformWindow(obs_sceneitem_t *item);
 
 	QAction *AddDockWidget(QDockWidget *dock);
+	void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area,
+			   bool extraBrowser = false);
+	void RemoveDockWidget(const QString &name);
 
 	static OBSBasic *Get();
 

+ 7 - 16
UI/window-extra-browsers.cpp

@@ -179,7 +179,6 @@ void ExtraBrowsersModel::UpdateItem(Item &item)
 		main->extraBrowserDocks[idx].data());
 	dock->setWindowTitle(item.title);
 	dock->setObjectName(item.title + OBJ_NAME_SUFFIX);
-	main->extraBrowserDockActions[idx]->setText(item.title);
 
 	if (main->extraBrowserDockTargets[idx] != item.url) {
 		dock->cefWidget->setURL(QT_TO_UTF8(item.url));
@@ -234,11 +233,13 @@ void ExtraBrowsersModel::Apply()
 
 	for (int i = deleted.size() - 1; i >= 0; i--) {
 		int idx = deleted[i];
-		main->extraBrowserDockActions.removeAt(idx);
 		main->extraBrowserDockTargets.removeAt(idx);
 		main->extraBrowserDocks.removeAt(idx);
 	}
 
+	if (main->extraBrowserDocks.empty())
+		main->extraBrowserMenuDocksSeparator.clear();
+
 	deleted.clear();
 
 	Reset();
@@ -459,7 +460,6 @@ void OBSExtraBrowsers::on_apply_clicked()
 void OBSBasic::ClearExtraBrowserDocks()
 {
 	extraBrowserDockTargets.clear();
-	extraBrowserDockActions.clear();
 	extraBrowserDocks.clear();
 }
 
@@ -475,7 +475,7 @@ void OBSBasic::LoadExtraBrowserDocks()
 
 	Json::array array = json.array_items();
 	if (!array.empty())
-		ui->menuDocks->addSeparator();
+		extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator();
 
 	for (Json &item : array) {
 		std::string title = item["title"].string_value();
@@ -562,7 +562,9 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url,
 		}
 	}
 
-	addDockWidget(Qt::RightDockWidgetArea, dock);
+	AddDockWidget(dock, Qt::RightDockWidgetArea, true);
+	extraBrowserDocks.push_back(QSharedPointer<QDockWidget>(dock));
+	extraBrowserDockTargets.push_back(url);
 
 	if (firstCreate) {
 		dock->setFloating(true);
@@ -577,15 +579,4 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url,
 		dock->move(curPos);
 		dock->setVisible(true);
 	}
-
-	QAction *action = AddDockWidget(dock);
-	if (firstCreate) {
-		action->blockSignals(true);
-		action->setChecked(true);
-		action->blockSignals(false);
-	}
-
-	extraBrowserDocks.push_back(QSharedPointer<QDockWidget>(dock));
-	extraBrowserDockActions.push_back(QSharedPointer<QAction>(action));
-	extraBrowserDockTargets.push_back(url);
 }