diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb5059bb..e5c3fac3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Minor: Added the `--incognito/--no-incognito` options to the `/openurl` command, allowing you to override the "Open links in incognito/private mode" setting. (#5149) - Minor: Added support for the `{input.text}` placeholder in the **Split** -> **Run a command** hotkey. (#5130) - Minor: Add a new Channel API for experimental plugins feature. (#5141) +- Minor: Added the ability to change the top-most status of a window regardless of the _Always on top_ setting (right click the notebook). (#5135) - Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840) - Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848) - Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834) diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index 592f2b97e..20bb911f1 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -1,6 +1,7 @@ #include "widgets/BaseWindow.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" @@ -207,37 +208,52 @@ void BaseWindow::init() // } #endif -#ifdef USEWINSDK - // fourtf: don't ask me why we need to delay this - if (!this->flags_.has(TopMost)) - { - QTimer::singleShot(1, this, [this] { - getSettings()->windowTopMost.connect( - [this](bool topMost, auto) { - ::SetWindowPos(HWND(this->winId()), - topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, - 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); - }, - this->connections_); - }); - } -#else // TopMost flag overrides setting if (!this->flags_.has(TopMost)) { getSettings()->windowTopMost.connect( - [this](bool topMost, auto) { - auto isVisible = this->isVisible(); - this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost); - if (isVisible) - { - this->show(); - } + [this](bool topMost) { + this->setTopMost(topMost); }, this->connections_); } +} + +void BaseWindow::setTopMost(bool topMost) +{ + if (this->flags_.has(TopMost)) + { + qCWarning(chatterinoWidget) + << "Called setTopMost on a window with the `TopMost` flag set."; + return; + } + + if (this->isTopMost_ == topMost) + { + return; + } + +#ifdef USEWINSDK + ::SetWindowPos(reinterpret_cast(this->winId()), + topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + +#else + auto isVisible = this->isVisible(); + this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost); + if (isVisible) + { + this->show(); + } #endif + + this->isTopMost_ = topMost; + this->topMostChanged(this->isTopMost_); +} + +bool BaseWindow::isTopMost() const +{ + return this->isTopMost_ || this->flags_.has(TopMost); } void BaseWindow::setActionOnFocusLoss(ActionOnFocusLoss value) @@ -559,6 +575,15 @@ void BaseWindow::showEvent(QShowEvent *) { this->moveTo(this->pos(), widgets::BoundsChecking::CursorPosition); } + + if (!this->flags_.has(TopMost)) + { + QTimer::singleShot(1, this, [this] { + ::SetWindowPos(reinterpret_cast(this->winId()), + this->isTopMost_ ? HWND_TOPMOST : HWND_NOTOPMOST, 0, + 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + }); + } #endif } diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index d55b5bd6d..aafb3555e 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -68,10 +68,20 @@ public: float scale() const override; float qtFontScale() const; + /// @returns true if the window is the top-most window. + /// Either #setTopMost was called or the `TopMost` flag is set which overrides this + bool isTopMost() const; + /// Updates the window's top-most status + /// If the `TopMost` flag is set, this is a no-op + void setTopMost(bool topMost); + pajlada::Signals::NoArgSignal closing; static bool supportsCustomWindowFrame(); +signals: + void topMostChanged(bool topMost); + protected: #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) bool nativeEvent(const QByteArray &eventType, void *message, @@ -131,6 +141,7 @@ private: FlagsEnum flags_; float nativeScale_ = 1; bool isResizeFixing_ = false; + bool isTopMost_ = false; struct { QLayout *windowLayout = nullptr; diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 3b9536199..4ae8b22f3 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -59,6 +59,28 @@ Notebook::Notebook(QWidget *parent) }); this->updateTabVisibilityMenuAction(); + this->toggleTopMostAction_ = new QAction("Top most window", this); + this->toggleTopMostAction_->setCheckable(true); + auto *window = dynamic_cast(this->window()); + if (window) + { + auto updateTopMost = [this, window] { + this->toggleTopMostAction_->setChecked(window->isTopMost()); + }; + updateTopMost(); + QObject::connect(this->toggleTopMostAction_, &QAction::triggered, + window, [window] { + window->setTopMost(!window->isTopMost()); + }); + QObject::connect(window, &BaseWindow::topMostChanged, this, + updateTopMost); + } + else + { + qCWarning(chatterinoApp) + << "Notebook must be created within a BaseWindow"; + } + this->addNotebookActionsToMenu(&this->menu_); // Manually resize the add button so the initial paint uses the correct @@ -1181,6 +1203,8 @@ void Notebook::addNotebookActionsToMenu(QMenu *menu) menu->addAction(this->showTabsAction_); menu->addAction(this->lockNotebookLayoutAction_); + + menu->addAction(this->toggleTopMostAction_); } NotebookButton *Notebook::getAddButton() diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 3dcabf82d..9aa694c66 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -196,6 +196,7 @@ private: NotebookTabLocation tabLocation_ = NotebookTabLocation::Top; QAction *lockNotebookLayoutAction_; QAction *showTabsAction_; + QAction *toggleTopMostAction_; // This filter, if set, is used to figure out the visibility of // the tabs in this notebook. @@ -224,7 +225,6 @@ private: // Main window on Windows has basically a duplicate of this in Window NotebookButton *streamerModeIcon_{}; - void updateStreamerModeIcon(); };