From a499058a6f2056e37f8e2c6eb61eae2095bdcd99 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 29 Nov 2022 10:30:11 +0100 Subject: [PATCH] Utils: Add push button with menu and default click action QPushButton has the functionality of setting a menu, but that replaces the normal "click" functionality, and always opens the menu on click. QToolButton can have a menu and the user can still click on it to just click. But, QToolButton looks very different, and in combination with other, normal push buttons very alien. Create a push button that only shows the menu when clicking on/near the menu indicator, and otherwise just sends the clicked() signal like a non-menu push button. Change-Id: Id6ba367e40c370275f67f0fbf77521c2e974b3b8 Reviewed-by: Reviewed-by: Marcus Tillmanns Reviewed-by: David Schulz --- src/libs/utils/CMakeLists.txt | 2 + src/libs/utils/optionpushbutton.cpp | 87 +++++++++++++++++++++++++++++ src/libs/utils/optionpushbutton.h | 31 ++++++++++ src/libs/utils/utils.qbs | 2 + 4 files changed, 122 insertions(+) create mode 100644 src/libs/utils/optionpushbutton.cpp create mode 100644 src/libs/utils/optionpushbutton.h diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 9ca7eb826bf..9d3fb078fb4 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -106,6 +106,8 @@ add_qtc_library(Utils namevaluevalidator.cpp namevaluevalidator.h navigationtreeview.cpp navigationtreeview.h networkaccessmanager.cpp networkaccessmanager.h + optionpushbutton.h + optionpushbutton.cpp osspecificaspects.h outputformat.h outputformatter.cpp outputformatter.h diff --git a/src/libs/utils/optionpushbutton.cpp b/src/libs/utils/optionpushbutton.cpp new file mode 100644 index 00000000000..66ab3e10e34 --- /dev/null +++ b/src/libs/utils/optionpushbutton.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "optionpushbutton.h" + +#include +#include +#include + +namespace Utils { + +/*! + \class Utils::OptionPushButton + + \brief The OptionPushButton class implements a QPushButton for which the menu is only opened + if the user presses the menu indicator. + + Use OptionPushButton::setOptionalMenu() to set the menu and its actions. + If the users clicks on the menu indicator of the push button, this menu is opened, and + its actions are triggered when the user selects them. + + If the user clicks anywhere else on the button, the QAbstractButton::clicked() signal is + sent, as if the button didn't have a menu. + + Note: You may not call QPushButton::setMenu(). Use OptionPushButton::setOptionalMenu() instead. +*/ + +/*! + Constructs an option push button with parent \a parent. +*/ +OptionPushButton::OptionPushButton(QWidget *parent) + : QPushButton(parent) +{} + +/*! + Constructs an option push button with text \a text and parent \a parent. +*/ +OptionPushButton::OptionPushButton(const QString &text, QWidget *parent) + : QPushButton(text, parent) +{} + +/*! + Associates the popup menu \a menu with this push button. + This menu is shown if the user clicks on the menu indicator that is shown. + If the user clicks anywhere else on the button, QAbstractButton::clicked() is sent instead. + + Ownership of the menu is not transferred to the push button. +*/ +void OptionPushButton::setOptionalMenu(QMenu *menu) +{ + setMenu(menu); + // hack away that QPushButton opens the menu on "pressed" + disconnect(this, SIGNAL(pressed()), this, SLOT(_q_popupPressed())); +} + +/*! + \internal +*/ +void OptionPushButton::mouseReleaseEvent(QMouseEvent *event) +{ + if (!menu() || event->button() != Qt::LeftButton) + return QPushButton::mouseReleaseEvent(event); + setDown(false); + const QRect r = rect(); + if (!r.contains(event->position().toPoint())) + return; + QStyleOptionButton option; + initStyleOption(&option); + // for Mac style + const QRect contentRect = style()->subElementRect(QStyle::SE_PushButtonContents, &option, this); + // for Common style + const int menuButtonSize = style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &option, this); + // see: newBtn.rect = QRect(ir.right() - mbi - 2, ir.height()/2 - mbi/2 + 3, mbi - 6, mbi - 6); + // in QCommonStyle::drawControl, CE_PushButtonBevel, QStyleOptionButton::HasMenu + static const int magicBorder = 4; + const int clickX = event->position().toPoint().x(); + if ((option.direction == Qt::LeftToRight + && clickX <= std::min(contentRect.right(), r.right() - menuButtonSize - magicBorder)) + || (option.direction == Qt::RightToLeft + && clickX >= std::max(contentRect.left(), r.left() + menuButtonSize + magicBorder))) { + click(); + } else { + showMenu(); + } +} + +} // namespace Utils diff --git a/src/libs/utils/optionpushbutton.h b/src/libs/utils/optionpushbutton.h new file mode 100644 index 00000000000..5015420de9b --- /dev/null +++ b/src/libs/utils/optionpushbutton.h @@ -0,0 +1,31 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include + +QT_BEGIN_NAMESPACE +class QMenu; +class QMouseEvent; +QT_END_NAMESPACE + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT OptionPushButton : public QPushButton +{ + Q_OBJECT + +public: + OptionPushButton(QWidget *parent = nullptr); + OptionPushButton(const QString &text, QWidget *parent = nullptr); + + void setOptionalMenu(QMenu *menu); + +protected: + void mouseReleaseEvent(QMouseEvent *event) override; +}; + +} // namespace Utils diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 9afc2590af8..5996f323e9e 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -208,6 +208,8 @@ Project { "navigationtreeview.h", "networkaccessmanager.cpp", "networkaccessmanager.h", + "optionpushbutton.h", + "optionpushbutton.cpp", "osspecificaspects.h", "outputformat.h", "outputformatter.cpp",