From 6ee3cfbb2da4a2bb6d6f3b5ac2b4bd6a2fff8eeb Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Mon, 20 Apr 2020 19:54:03 +0100 Subject: [PATCH] Introduce "Maximize/restore" title bar button support As happens with other docking frameworks: - Maximized/restore button only visible when window is floating - floating button is never visible Available by setting Flag_TitleBarHasMaximizeButton. Added example too. Fixes: #37 --- examples/dockwidgets/main.cpp | 6 ++++ src/Config.h | 1 + src/private/FloatingWindow_p.h | 2 ++ src/private/TitleBar.cpp | 38 +++++++++++++++++--- src/private/TitleBar_p.h | 9 ++++- src/private/widgets/FloatingWindowWidget.cpp | 9 +++++ src/private/widgets/FloatingWindowWidget_p.h | 1 + src/private/widgets/TitleBarWidget.cpp | 19 +++++++++- src/private/widgets/TitleBarWidget_p.h | 2 ++ 9 files changed, 81 insertions(+), 6 deletions(-) diff --git a/examples/dockwidgets/main.cpp b/examples/dockwidgets/main.cpp index ffbc406b..72003f75 100644 --- a/examples/dockwidgets/main.cpp +++ b/examples/dockwidgets/main.cpp @@ -79,6 +79,9 @@ int main(int argc, char **argv) QCommandLineOption nonDockable("d", QCoreApplication::translate("main", "DockWidget #9 will be non-dockable")); parser.addOption(nonDockable); + QCommandLineOption maximizeButton("b", QCoreApplication::translate("main", "DockWidgets have maximize/restore buttons instead of float/dock button")); + parser.addOption(maximizeButton); + #if defined(DOCKS_DEVELOPER_MODE) QCommandLineOption noCentralFrame("c", QCoreApplication::translate("main", "No central frame")); parser.addOption(noCentralFrame); @@ -106,6 +109,9 @@ int main(int argc, char **argv) if (parser.isSet(reorderTabsOption)) flags |= KDDockWidgets::Config::Flag_AllowReorderTabs; + if (parser.isSet(maximizeButton)) + flags |= KDDockWidgets::Config::Flag_TitleBarHasMaximizeButton; + if (parser.isSet(lazyResizeOption)) flags |= KDDockWidgets::Config::Flag_LazyResize; diff --git a/src/Config.h b/src/Config.h index 6720d5d8..ee0702a5 100644 --- a/src/Config.h +++ b/src/Config.h @@ -69,6 +69,7 @@ public: Flag_LazyResize = 32, /// The dock widgets are resized in a lazy manner. The actual resize only happens when you release the mouse button. Flag_TabsHaveCloseButton = 64, /// Tabs will have a close button. Equivalent to QTabWidget::setTabsClosable(true). Flag_DoubleClickMaximizes = 128, /// Double clicking the titlebar will maximize a floating window instead of re-docking it + Flag_TitleBarHasMaximizeButton = 256, /// The title bar will have a maximize/restore button when floating. This is mutually-exclusive with the floating button (since many apps behave that way). Flag_Default = Flag_AeroSnapWithClientDecos ///> The defaults }; Q_DECLARE_FLAGS(Flags, Flag) diff --git a/src/private/FloatingWindow_p.h b/src/private/FloatingWindow_p.h index 2afce0df..4cd9a0d8 100644 --- a/src/private/FloatingWindow_p.h +++ b/src/private/FloatingWindow_p.h @@ -29,6 +29,7 @@ QT_BEGIN_NAMESPACE class QAbstractNativeEventFilter; +class QWindowStateChangeEvent; QT_END_NAMESPACE namespace KDDockWidgets { @@ -114,6 +115,7 @@ public: Q_SIGNALS: void numFramesChanged(); + void windowStateChanged(QWindowStateChangeEvent *); protected: #ifdef Q_OS_WIN bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; diff --git a/src/private/TitleBar.cpp b/src/private/TitleBar.cpp index 82d8f823..4785940e 100644 --- a/src/private/TitleBar.cpp +++ b/src/private/TitleBar.cpp @@ -51,6 +51,8 @@ TitleBar::TitleBar(FloatingWindow *parent) { connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateCloseButton); connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateFloatButton); + connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateMaximizeButton); + connect(m_floatingWindow, &FloatingWindow::windowStateChanged, this, &TitleBar::updateMaximizeButton); init(); } @@ -68,10 +70,7 @@ bool TitleBar::onDoubleClicked() { if ((Config::self().flags() & Config::Flag_DoubleClickMaximizes) && m_floatingWindow) { // Not using isFloating(), as that can be a dock widget nested in a floating window. By convention it's floating, but it's not the title bar of the top-level window. - if (m_floatingWindow->isMaximized()) - m_floatingWindow->showNormal(); - else - m_floatingWindow->showMaximized(); + toggleMaximized(); return true; } else if (supportsFloatingButton()) { onFloatClicked(); @@ -81,6 +80,17 @@ bool TitleBar::onDoubleClicked() return false; } +void TitleBar::toggleMaximized() +{ + if (!m_floatingWindow) + return; + + if (m_floatingWindow->isMaximized()) + m_floatingWindow->showNormal(); + else + m_floatingWindow->showMaximized(); +} + void TitleBar::setTitle(const QString &title) { if (title != m_title) { @@ -145,11 +155,26 @@ std::unique_ptr TitleBar::makeWindow() bool TitleBar::supportsFloatingButton() const { + if (Config::self().flags() & Config::Flag_TitleBarHasMaximizeButton) { + // Apps having a maximize/restore button traditionally don't have a floating one, + // QDockWidget style only has floating and no maximize/restore. + // We can add an option later if we need them to co-exist + return false; + } + // If we have a floating window with nested dock widgets we can't re-attach, because we don't // know where to return !m_floatingWindow || m_floatingWindow->hasSingleFrame(); } +bool TitleBar::supportsMaximizeButton() const +{ + if (!(Config::self().flags() & Config::Flag_TitleBarHasMaximizeButton)) + return false; + + return m_floatingWindow != nullptr; +} + bool TitleBar::hasIcon() const { return !m_icon.isNull(); @@ -229,3 +254,8 @@ void TitleBar::onFloatClicked() makeWindow(); } } + +void TitleBar::onMaximizeClicked() +{ + toggleMaximized(); +} diff --git a/src/private/TitleBar_p.h b/src/private/TitleBar_p.h index d17a8432..a849296e 100644 --- a/src/private/TitleBar_p.h +++ b/src/private/TitleBar_p.h @@ -67,9 +67,12 @@ public: /// There should always be at least 1. If more than 1 then they are tabbed. DockWidgetBase::List dockWidgets() const; - ///@brief returns whether this title bar supports a floating/unfloating button + ///@brief returns whether this title bar supports a floating/docking button bool supportsFloatingButton() const; + ///@brief returns whether this title bar supports a maximize/restore button + bool supportsMaximizeButton() const; + ///@brief returns whether this title bar has an icon bool hasIcon() const; @@ -94,7 +97,11 @@ Q_SIGNALS: protected: void onCloseClicked(); void onFloatClicked(); + void onMaximizeClicked(); + void toggleMaximized(); + virtual void updateFloatButton() {} + virtual void updateMaximizeButton() {} // The following are needed for the unit-tests virtual bool isCloseButtonVisible() const { return true; } diff --git a/src/private/widgets/FloatingWindowWidget.cpp b/src/private/widgets/FloatingWindowWidget.cpp index ad0c10b9..4003f857 100644 --- a/src/private/widgets/FloatingWindowWidget.cpp +++ b/src/private/widgets/FloatingWindowWidget.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace KDDockWidgets; @@ -51,6 +52,14 @@ void FloatingWindowWidget::paintEvent(QPaintEvent *) p.drawRect(rect().adjusted(0, 0, -1, -1)); } +bool FloatingWindowWidget::event(QEvent *ev) +{ + if (ev->type() == QEvent::WindowStateChange) + Q_EMIT windowStateChanged(static_cast(ev)); + + return FloatingWindow::event(ev); +} + void FloatingWindowWidget::init() { m_vlayout->setSpacing(0); diff --git a/src/private/widgets/FloatingWindowWidget_p.h b/src/private/widgets/FloatingWindowWidget_p.h index 14ee171b..c3766abd 100644 --- a/src/private/widgets/FloatingWindowWidget_p.h +++ b/src/private/widgets/FloatingWindowWidget_p.h @@ -38,6 +38,7 @@ public: protected: void paintEvent(QPaintEvent *) override; + bool event(QEvent *ev) override; private: void init(); Q_DISABLE_COPY(FloatingWindowWidget) diff --git a/src/private/widgets/TitleBarWidget.cpp b/src/private/widgets/TitleBarWidget.cpp index 78a3ff63..fd66f3e0 100644 --- a/src/private/widgets/TitleBarWidget.cpp +++ b/src/private/widgets/TitleBarWidget.cpp @@ -58,15 +58,20 @@ void TitleBarWidget::init() m_layout->setContentsMargins(2, 2, 2, 2); m_layout->setSpacing(2); + m_maximizeButton = TitleBarWidget::createButton(this, style()->standardIcon(QStyle::SP_TitleBarMaxButton)); m_floatButton = TitleBarWidget::createButton(this, style()->standardIcon(QStyle::SP_TitleBarNormalButton)); m_closeButton = TitleBarWidget::createButton(this, style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + m_layout->addWidget(m_maximizeButton); m_layout->addWidget(m_floatButton); m_layout->addWidget(m_closeButton); connect(m_floatButton, &QAbstractButton::clicked, this, &TitleBarWidget::onFloatClicked); connect(m_closeButton, &QAbstractButton::clicked, this, &TitleBarWidget::onCloseClicked); + connect(m_maximizeButton, &QAbstractButton::clicked, this, &TitleBarWidget::onMaximizeClicked); updateCloseButton(); + updateFloatButton(); + updateMaximizeButton(); connect(this, &TitleBar::titleChanged, this, [this] { update(); @@ -103,7 +108,7 @@ int TitleBarWidget::buttonAreaWidth() const TitleBarWidget::~TitleBarWidget() { // To avoid a crash - for (auto button : { m_floatButton , m_closeButton }) { + for (auto button : { m_floatButton, m_maximizeButton, m_closeButton }) { button->setParent(nullptr); button->deleteLater(); } @@ -147,6 +152,18 @@ void TitleBarWidget::updateCloseButton() m_closeButton->setEnabled(!anyNonClosable); } +void TitleBarWidget::updateMaximizeButton() +{ + if (auto fw = floatingWindow()) { + m_maximizeButton->setIcon(style()->standardIcon(fw->isMaximized() ? QStyle::SP_TitleBarNormalButton + : QStyle::SP_TitleBarMaxButton)); + + m_maximizeButton->setVisible(supportsMaximizeButton()); + } else { + m_maximizeButton->setVisible(false); + } +} + bool TitleBarWidget::isCloseButtonVisible() const { return m_closeButton->isVisible(); diff --git a/src/private/widgets/TitleBarWidget_p.h b/src/private/widgets/TitleBarWidget_p.h index 827dec19..4ba05e81 100644 --- a/src/private/widgets/TitleBarWidget_p.h +++ b/src/private/widgets/TitleBarWidget_p.h @@ -59,6 +59,7 @@ protected: void mouseDoubleClickEvent(QMouseEvent *) override; void updateFloatButton() override; void updateCloseButton() override; + void updateMaximizeButton() override; // The following are needed for the unit-tests bool isCloseButtonVisible() const override; @@ -75,6 +76,7 @@ private: QHBoxLayout *const m_layout; QAbstractButton *m_closeButton = nullptr; QAbstractButton *m_floatButton = nullptr; + QAbstractButton *m_maximizeButton = nullptr; QLabel *m_dockWidgetIcon = nullptr; };