feat: add option to change the top-most status of a window (#5135)

* feat: add option to pin a popup

* chore: add changelog entry

* chore: change changelog entry
This commit is contained in:
nerix 2024-02-04 13:33:37 +01:00 committed by GitHub
parent 8e9aa87a08
commit af8eba0323
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 85 additions and 24 deletions

View file

@ -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 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: 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: 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 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: 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) - 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)

View file

@ -1,6 +1,7 @@
#include "widgets/BaseWindow.hpp" #include "widgets/BaseWindow.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/QLogging.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp" #include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
@ -207,37 +208,52 @@ void BaseWindow::init()
// } // }
#endif #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 // TopMost flag overrides setting
if (!this->flags_.has(TopMost)) if (!this->flags_.has(TopMost))
{ {
getSettings()->windowTopMost.connect( getSettings()->windowTopMost.connect(
[this](bool topMost, auto) { [this](bool topMost) {
auto isVisible = this->isVisible(); this->setTopMost(topMost);
this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost);
if (isVisible)
{
this->show();
}
}, },
this->connections_); 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<HWND>(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 #endif
this->isTopMost_ = topMost;
this->topMostChanged(this->isTopMost_);
}
bool BaseWindow::isTopMost() const
{
return this->isTopMost_ || this->flags_.has(TopMost);
} }
void BaseWindow::setActionOnFocusLoss(ActionOnFocusLoss value) void BaseWindow::setActionOnFocusLoss(ActionOnFocusLoss value)
@ -559,6 +575,15 @@ void BaseWindow::showEvent(QShowEvent *)
{ {
this->moveTo(this->pos(), widgets::BoundsChecking::CursorPosition); this->moveTo(this->pos(), widgets::BoundsChecking::CursorPosition);
} }
if (!this->flags_.has(TopMost))
{
QTimer::singleShot(1, this, [this] {
::SetWindowPos(reinterpret_cast<HWND>(this->winId()),
this->isTopMost_ ? HWND_TOPMOST : HWND_NOTOPMOST, 0,
0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
});
}
#endif #endif
} }

View file

@ -68,10 +68,20 @@ public:
float scale() const override; float scale() const override;
float qtFontScale() const; 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; pajlada::Signals::NoArgSignal closing;
static bool supportsCustomWindowFrame(); static bool supportsCustomWindowFrame();
signals:
void topMostChanged(bool topMost);
protected: protected:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool nativeEvent(const QByteArray &eventType, void *message, bool nativeEvent(const QByteArray &eventType, void *message,
@ -131,6 +141,7 @@ private:
FlagsEnum<Flags> flags_; FlagsEnum<Flags> flags_;
float nativeScale_ = 1; float nativeScale_ = 1;
bool isResizeFixing_ = false; bool isResizeFixing_ = false;
bool isTopMost_ = false;
struct { struct {
QLayout *windowLayout = nullptr; QLayout *windowLayout = nullptr;

View file

@ -59,6 +59,28 @@ Notebook::Notebook(QWidget *parent)
}); });
this->updateTabVisibilityMenuAction(); this->updateTabVisibilityMenuAction();
this->toggleTopMostAction_ = new QAction("Top most window", this);
this->toggleTopMostAction_->setCheckable(true);
auto *window = dynamic_cast<BaseWindow *>(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_); this->addNotebookActionsToMenu(&this->menu_);
// Manually resize the add button so the initial paint uses the correct // 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->showTabsAction_);
menu->addAction(this->lockNotebookLayoutAction_); menu->addAction(this->lockNotebookLayoutAction_);
menu->addAction(this->toggleTopMostAction_);
} }
NotebookButton *Notebook::getAddButton() NotebookButton *Notebook::getAddButton()

View file

@ -196,6 +196,7 @@ private:
NotebookTabLocation tabLocation_ = NotebookTabLocation::Top; NotebookTabLocation tabLocation_ = NotebookTabLocation::Top;
QAction *lockNotebookLayoutAction_; QAction *lockNotebookLayoutAction_;
QAction *showTabsAction_; QAction *showTabsAction_;
QAction *toggleTopMostAction_;
// This filter, if set, is used to figure out the visibility of // This filter, if set, is used to figure out the visibility of
// the tabs in this notebook. // the tabs in this notebook.
@ -224,7 +225,6 @@ private:
// Main window on Windows has basically a duplicate of this in Window // Main window on Windows has basically a duplicate of this in Window
NotebookButton *streamerModeIcon_{}; NotebookButton *streamerModeIcon_{};
void updateStreamerModeIcon(); void updateStreamerModeIcon();
}; };