Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtoolbutton.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qtoolbutton.h"
6
7#include <qapplication.h>
8#include <qdrawutil.h>
9#include <qevent.h>
10#include <qicon.h>
11#include <qpainter.h>
12#include <qpointer.h>
13#include <qstyle.h>
14#include <qstyleoption.h>
15#if QT_CONFIG(tooltip)
16#include <qtooltip.h>
17#endif
18#if QT_CONFIG(mainwindow)
19#include <qmainwindow.h>
20#endif
21#if QT_CONFIG(toolbar)
22#include <qtoolbar.h>
23#endif
24#include <qvariant.h>
25#include <qstylepainter.h>
26#include <private/qabstractbutton_p.h>
27#include <private/qaction_p.h>
28#if QT_CONFIG(menu)
29#include <qeventloop.h>
30#include <qmenu.h>
31#include <private/qmenu_p.h>
32#endif
33
35
36using namespace Qt::StringLiterals;
37
39{
40 Q_DECLARE_PUBLIC(QToolButton)
41public:
42 void init();
43#if QT_CONFIG(menu)
44 void onButtonPressed();
45 void onButtonReleased();
46 QMenu *showMenu();
47 void updateButtonDown();
50#endif
51 bool updateHoverControl(const QPoint &pos);
53 QStyle::State styleButtonState(QStyle::State state) const override;
56 QRect hoverRect;
57 QPointer<QAction> menuAction; //the menu set by the user (setMenu)
59 int delay;
63 uint popupModeSetByUser : 1; // true if popupMode was set through setPopupMode
69 QAction *defaultAction;
70#if QT_CONFIG(menu)
71 bool hasMenu() const;
72 //workaround for task 177850
74#endif
75};
76
77#if QT_CONFIG(menu)
78bool QToolButtonPrivate::hasMenu() const
79{
80 return ((defaultAction && defaultAction->menu())
81 || (menuAction && menuAction->menu())
82 || actions.size() > (defaultAction ? 1 : 0));
83}
84#endif
85
86/*!
87 \class QToolButton
88 \brief The QToolButton class provides a quick-access button to
89 commands or options, usually used inside a QToolBar.
90
91 \ingroup basicwidgets
92 \inmodule QtWidgets
93
94 A tool button is a special button that provides quick-access to
95 specific commands or options. As opposed to a normal command
96 button, a tool button usually doesn't show a text label, but shows
97 an icon instead.
98
99 Tool buttons are normally created when new QAction instances are
100 created with QToolBar::addAction() or existing actions are added
101 to a toolbar with QToolBar::addAction(). It is also possible to
102 construct tool buttons in the same way as any other widget, and
103 arrange them alongside other widgets in layouts.
104
105 One classic use of a tool button is to select tools; for example,
106 the "pen" tool in a drawing program. This would be implemented
107 by using a QToolButton as a toggle button (see setCheckable()).
108
109 QToolButton supports auto-raising. In auto-raise mode, the button
110 draws a 3D frame only when the mouse points at it. The feature is
111 automatically turned on when a button is used inside a QToolBar.
112 Change it with setAutoRaise().
113
114 A tool button's icon is set as QIcon. This makes it possible to
115 specify different pixmaps for the disabled and active state. The
116 disabled pixmap is used when the button's functionality is not
117 available. The active pixmap is displayed when the button is
118 auto-raised because the mouse pointer is hovering over it.
119
120 The button's look and dimension is adjustable with
121 setToolButtonStyle() and setIconSize(). When used inside a
122 QToolBar in a QMainWindow, the button automatically adjusts to
123 QMainWindow's settings (see QMainWindow::setToolButtonStyle() and
124 QMainWindow::setIconSize()). Instead of an icon, a tool button can
125 also display an arrow symbol, specified with
126 \l{QToolButton::arrowType} {arrowType}.
127
128 A tool button can offer additional choices in a popup menu. The
129 popup menu can be set using setMenu(). Use setPopupMode() to
130 configure the different modes available for tool buttons with a
131 menu set. The default mode is DelayedPopupMode which is sometimes
132 used with the "Back" button in a web browser. After pressing and
133 holding the button down for a while, a menu pops up showing a list
134 of possible pages to jump to. The timeout is style dependent,
135 see QStyle::SH_ToolButton_PopupDelay.
136
137 \image assistant-toolbar.png {Qt Assistant's toolbar with tool buttons}
138 \caption Qt Assistant's toolbar contains tool buttons that are associated
139 with actions used in other parts of the main window.
140
141
142 \sa QPushButton, QToolBar, QMainWindow, QAction
143*/
144
145/*!
146 \fn void QToolButton::triggered(QAction *action)
147
148 This signal is emitted when the given \a action is triggered.
149
150 The action may also be associated with other parts of the user interface,
151 such as menu items and keyboard shortcuts. Sharing actions in this
152 way helps make the user interface more consistent and is often less work
153 to implement.
154*/
155
156/*!
157 Constructs an empty tool button with parent \a
158 parent.
159*/
160QToolButton::QToolButton(QWidget * parent)
161 : QAbstractButton(*new QToolButtonPrivate, parent)
162{
163 Q_D(QToolButton);
164 d->init();
165}
166
167
168
169/* Set-up code common to all the constructors */
170
172{
173 Q_Q(QToolButton);
174 defaultAction = nullptr;
175#if QT_CONFIG(toolbar)
176 if (qobject_cast<QToolBar*>(parent))
177 autoRaise = true;
178 else
179#endif
180 autoRaise = false;
181 arrowType = Qt::NoArrow;
182 menuButtonDown = false;
183 popupMode = QToolButton::DelayedPopup;
184 popupModeSetByUser = false;
185 buttonPressed = QToolButtonPrivate::NoButtonPressed;
186
187 toolButtonStyle = Qt::ToolButtonIconOnly;
188 hoverControl = QStyle::SC_None;
189
190 q->setFocusPolicy(Qt::TabFocus);
191 q->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed,
192 QSizePolicy::ToolButton));
193
194#if QT_CONFIG(menu)
195 QObjectPrivate::connect(q, &QAbstractButton::pressed,
196 this, &QToolButtonPrivate::onButtonPressed);
197 QObjectPrivate::connect(q, &QAbstractButton::released,
198 this, &QToolButtonPrivate::onButtonReleased);
199#endif
200
201 setLayoutItemMargins(QStyle::SE_ToolButtonLayoutItem);
202 delay = q->style()->styleHint(QStyle::SH_ToolButton_PopupDelay, nullptr, q);
203}
204
205/*!
206 Initialize \a option with the values from this QToolButton. This method
207 is useful for subclasses when they need a QStyleOptionToolButton, but don't want
208 to fill in all the information themselves.
209
210 \sa QStyleOption::initFrom()
211*/
212void QToolButton::initStyleOption(QStyleOptionToolButton *option) const
213{
214 if (!option)
215 return;
216
217 Q_D(const QToolButton);
218 option->initFrom(this);
219 option->iconSize = iconSize(); //default value
220
221#if QT_CONFIG(toolbar)
222 if (parentWidget()) {
223 if (QToolBar *toolBar = qobject_cast<QToolBar *>(parentWidget())) {
224 option->iconSize = toolBar->iconSize();
225 }
226 }
227#endif // QT_CONFIG(toolbar)
228
229 option->text = d->text;
230 option->icon = d->icon;
231 option->arrowType = d->arrowType;
232 option->state = d->styleButtonState(option->state);
233 option->subControls = QStyle::SC_ToolButton;
234 option->activeSubControls = QStyle::SC_None;
235
236 option->features = QStyleOptionToolButton::None;
237 if (d->popupMode == QToolButton::MenuButtonPopup) {
238 option->subControls |= QStyle::SC_ToolButtonMenu;
239 option->features |= QStyleOptionToolButton::MenuButtonPopup;
240 }
241 if (option->state & QStyle::State_MouseOver) {
242 option->activeSubControls = d->hoverControl;
243 }
244 if (d->menuButtonDown) {
245 option->state |= QStyle::State_Sunken;
246 option->activeSubControls |= QStyle::SC_ToolButtonMenu;
247 }
248 if (d->down) {
249 option->state |= QStyle::State_Sunken;
250 option->activeSubControls |= QStyle::SC_ToolButton;
251 }
252
253
254 if (d->arrowType != Qt::NoArrow)
255 option->features |= QStyleOptionToolButton::Arrow;
256 if (d->popupMode == QToolButton::DelayedPopup)
257 option->features |= QStyleOptionToolButton::PopupDelay;
258#if QT_CONFIG(menu)
259 if (d->hasMenu())
260 option->features |= QStyleOptionToolButton::HasMenu;
261#endif
262 if (d->toolButtonStyle == Qt::ToolButtonFollowStyle) {
263 option->toolButtonStyle = Qt::ToolButtonStyle(style()->styleHint(QStyle::SH_ToolButtonStyle, option, this));
264 } else
265 option->toolButtonStyle = d->toolButtonStyle;
266
267 if (option->toolButtonStyle == Qt::ToolButtonTextBesideIcon) {
268 // If the action is not prioritized, remove the text label to save space
269 if (d->defaultAction && d->defaultAction->priority() < QAction::NormalPriority)
270 option->toolButtonStyle = Qt::ToolButtonIconOnly;
271 }
272
273 if (d->icon.isNull() && d->arrowType == Qt::NoArrow) {
274 if (!d->text.isEmpty())
275 option->toolButtonStyle = Qt::ToolButtonTextOnly;
276 else if (option->toolButtonStyle != Qt::ToolButtonTextOnly)
277 option->toolButtonStyle = Qt::ToolButtonIconOnly;
278 }
279
280 option->pos = pos();
281 option->font = font();
282}
283
284/*!
285 Destroys the object and frees any allocated resources.
286*/
287
288QToolButton::~QToolButton()
289{
290}
291
292/*!
293 \reimp
294*/
295QSize QToolButton::sizeHint() const
296{
297 Q_D(const QToolButton);
298 if (d->sizeHint.isValid())
299 return d->sizeHint;
300 ensurePolished();
301
302 int w = 0, h = 0;
303 QStyleOptionToolButton opt;
304 initStyleOption(&opt);
305
306 QFontMetrics fm = fontMetrics();
307 if (opt.toolButtonStyle != Qt::ToolButtonTextOnly) {
308 QSize icon = opt.iconSize;
309 w = icon.width();
310 h = icon.height();
311 }
312
313 if (opt.toolButtonStyle != Qt::ToolButtonIconOnly) {
314 QSize textSize = fm.size(Qt::TextShowMnemonic, text());
315 textSize.setWidth(textSize.width() + fm.horizontalAdvance(u' ') * 2);
316 if (opt.toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
317 h += 4 + textSize.height();
318 if (textSize.width() > w)
319 w = textSize.width();
320 } else if (opt.toolButtonStyle == Qt::ToolButtonTextBesideIcon) {
321 w += 4 + textSize.width();
322 if (textSize.height() > h)
323 h = textSize.height();
324 } else { // TextOnly
325 w = textSize.width();
326 h = textSize.height();
327 }
328 }
329
330 opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
331 if (d->popupMode == MenuButtonPopup)
332 w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
333
334 d->sizeHint = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);
335 return d->sizeHint;
336}
337
338/*!
339 \reimp
340 */
341QSize QToolButton::minimumSizeHint() const
342{
343 return sizeHint();
344}
345
346/*!
347 \property QToolButton::toolButtonStyle
348 \brief whether the tool button displays an icon only, text only,
349 or text beside/below the icon.
350
351 The default is Qt::ToolButtonIconOnly.
352
353 To have the style of toolbuttons follow the system settings, set this property to Qt::ToolButtonFollowStyle.
354 On Unix, the user settings from the desktop environment will be used.
355 On other platforms, Qt::ToolButtonFollowStyle means icon only.
356
357 QToolButton automatically connects this slot to the relevant
358 signal in the QMainWindow in which is resides.
359*/
360
361/*!
362 \property QToolButton::arrowType
363 \brief whether the button displays an arrow instead of a normal icon
364
365 This displays an arrow as the icon for the QToolButton.
366
367 By default, this property is set to Qt::NoArrow.
368*/
369
370Qt::ToolButtonStyle QToolButton::toolButtonStyle() const
371{
372 Q_D(const QToolButton);
373 return d->toolButtonStyle;
374}
375
376Qt::ArrowType QToolButton::arrowType() const
377{
378 Q_D(const QToolButton);
379 return d->arrowType;
380}
381
382
383void QToolButton::setToolButtonStyle(Qt::ToolButtonStyle style)
384{
385 Q_D(QToolButton);
386 if (d->toolButtonStyle == style)
387 return;
388
389 d->toolButtonStyle = style;
390 d->sizeHint = QSize();
391 updateGeometry();
392 if (isVisible()) {
393 update();
394 }
395}
396
397void QToolButton::setArrowType(Qt::ArrowType type)
398{
399 Q_D(QToolButton);
400 if (d->arrowType == type)
401 return;
402
403 d->arrowType = type;
404 d->sizeHint = QSize();
405 updateGeometry();
406 if (isVisible()) {
407 update();
408 }
409}
410
411/*!
412 \fn void QToolButton::paintEvent(QPaintEvent *event)
413
414 Paints the button in response to the paint \a event.
415*/
416void QToolButton::paintEvent(QPaintEvent *)
417{
418 QStylePainter p(this);
419 QStyleOptionToolButton opt;
420 initStyleOption(&opt);
421 p.drawComplexControl(QStyle::CC_ToolButton, opt);
422}
423
424/*!
425 \reimp
426 */
427void QToolButton::actionEvent(QActionEvent *event)
428{
429 Q_D(QToolButton);
430 auto action = static_cast<QAction *>(event->action());
431 switch (event->type()) {
432 case QEvent::ActionChanged:
433 if (action == d->defaultAction)
434 setDefaultAction(action); // update button state
435 break;
436 case QEvent::ActionAdded:
437 QObjectPrivate::connect(action, &QAction::triggered, d,
438 &QToolButtonPrivate::onActionTriggered);
439 break;
440 case QEvent::ActionRemoved:
441 if (d->defaultAction == action)
442 d->defaultAction = nullptr;
443#if QT_CONFIG(menu)
444 if (action == d->menuAction)
445 d->menuAction = nullptr;
446#endif
447 action->disconnect(this);
448 break;
449 default:
450 ;
451 }
452 QAbstractButton::actionEvent(event);
453}
454
456{
457 Q_Q(QToolButton);
458 QStyleOptionToolButton opt;
459 q->initStyleOption(&opt);
460 opt.subControls = QStyle::SC_All;
461 hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ToolButton, &opt, pos, q);
462 if (hoverControl == QStyle::SC_None)
463 hoverRect = QRect();
464 else
465 hoverRect = q->style()->subControlRect(QStyle::CC_ToolButton, &opt, hoverControl, q);
466 return hoverControl;
467}
468
470{
471 Q_Q(QToolButton);
472 QRect lastHoverRect = hoverRect;
473 QStyle::SubControl lastHoverControl = hoverControl;
474 bool doesHover = q->testAttribute(Qt::WA_Hover);
475 if (lastHoverControl != newHoverControl(pos) && doesHover) {
476 q->update(lastHoverRect);
477 q->update(hoverRect);
478 return true;
479 }
480 return !doesHover;
481}
482
484{
485 Q_Q(QToolButton);
486 if (QAction *action = qobject_cast<QAction *>(q->sender()))
487 emit q->triggered(action);
488}
489
491{
492 state = QAbstractButtonPrivate::styleButtonState(state);
493 if (checked)
494 state |= QStyle::State_On;
495 if (autoRaise)
496 state |= QStyle::State_AutoRaise;
497 if (!checked && !down)
498 state |= QStyle::State_Raised;
499 if (menuButtonDown)
500 state |= QStyle::State_Sunken;
501 return state;
502}
503
504/*!
505 \reimp
506 */
507void QToolButton::enterEvent(QEnterEvent * e)
508{
509 Q_D(QToolButton);
510 if (d->autoRaise)
511 update();
512 if (d->defaultAction)
513 d->defaultAction->hover();
514 QAbstractButton::enterEvent(e);
515}
516
517
518/*!
519 \reimp
520 */
521void QToolButton::leaveEvent(QEvent * e)
522{
523 Q_D(QToolButton);
524 if (d->autoRaise)
525 update();
526
527 QAbstractButton::leaveEvent(e);
528}
529
530
531/*!
532 \reimp
533 */
534void QToolButton::timerEvent(QTimerEvent *e)
535{
536#if QT_CONFIG(menu)
537 Q_D(QToolButton);
538 if (e->timerId() == d->popupTimer.timerId()) {
539 d->popupTimer.stop();
540 if (d->menuButtonDown || d->down)
541 d->showMenu();
542 return;
543 }
544#endif
545 QAbstractButton::timerEvent(e);
546}
547
548
549/*!
550 \reimp
551*/
552void QToolButton::changeEvent(QEvent *e)
553{
554#if QT_CONFIG(toolbar)
555 Q_D(QToolButton);
556 if (e->type() == QEvent::ParentChange) {
557 if (qobject_cast<QToolBar*>(parentWidget()))
558 d->autoRaise = true;
559 } else if (e->type() == QEvent::StyleChange
560#ifdef Q_OS_MACOS
561 || e->type() == QEvent::MacSizeChange
562#endif
563 ) {
564 d->delay = style()->styleHint(QStyle::SH_ToolButton_PopupDelay, nullptr, this);
565 d->setLayoutItemMargins(QStyle::SE_ToolButtonLayoutItem);
566 }
567#endif
568 QAbstractButton::changeEvent(e);
569}
570
571/*!
572 \reimp
573*/
574void QToolButton::mousePressEvent(QMouseEvent *e)
575{
576 Q_D(QToolButton);
577#if QT_CONFIG(menu)
578 QStyleOptionToolButton opt;
579 initStyleOption(&opt);
580 if (e->button() == Qt::LeftButton && (d->popupMode == MenuButtonPopup)) {
581 QRect popupr = style()->subControlRect(QStyle::CC_ToolButton, &opt,
582 QStyle::SC_ToolButtonMenu, this);
583 if (popupr.isValid() && popupr.contains(e->position().toPoint())) {
584 d->buttonPressed = QToolButtonPrivate::MenuButtonPressed;
585 d->showMenu();
586 return;
587 }
588 }
589#endif
590 d->buttonPressed = QToolButtonPrivate::ToolButtonPressed;
591 QAbstractButton::mousePressEvent(e);
592}
593
594/*!
595 \reimp
596*/
597void QToolButton::mouseReleaseEvent(QMouseEvent *e)
598{
599 Q_D(QToolButton);
600 QPointer<QAbstractButton> guard(this);
601 QAbstractButton::mouseReleaseEvent(e);
602 if (guard)
603 d->buttonPressed = QToolButtonPrivate::NoButtonPressed;
604}
605
606/*!
607 \reimp
608*/
609bool QToolButton::hitButton(const QPoint &pos) const
610{
611 Q_D(const QToolButton);
612 if (QAbstractButton::hitButton(pos))
613 return (d->buttonPressed != QToolButtonPrivate::MenuButtonPressed);
614 return false;
615}
616
617
618#if QT_CONFIG(menu)
619/*!
620 Associates the given \a menu with this tool button.
621
622 The menu will be shown according to the button's \l popupMode.
623
624 Ownership of the menu is not transferred to the tool button.
625
626 \sa menu()
627*/
628void QToolButton::setMenu(QMenu* menu)
629{
630 Q_D(QToolButton);
631
632 if (d->menuAction == (menu ? menu->menuAction() : nullptr))
633 return;
634
635 if (d->menuAction)
636 removeAction(d->menuAction);
637
638 if (menu) {
639 d->menuAction = menu->menuAction();
640 addAction(d->menuAction);
641 } else {
642 d->menuAction = nullptr;
643 }
644
645 // changing the menu set may change the size hint, so reset it
646 d->sizeHint = QSize();
647 updateGeometry();
648 update();
649}
650
651/*!
652 Returns the associated menu, or \nullptr if no menu has been
653 defined.
654
655 \sa setMenu()
656*/
657QMenu* QToolButton::menu() const
658{
659 Q_D(const QToolButton);
660 if (d->menuAction)
661 return d->menuAction->menu();
662 return nullptr;
663}
664
665/*!
666 Shows (pops up) the associated popup menu. If there is no such
667 menu, this function does nothing. This function does not return
668 until the popup menu has been closed by the user.
669*/
670void QToolButton::showMenu()
671{
672 Q_D(QToolButton);
673 QPointer<QMenu> menu = d->showMenu();
674 if (!menu)
675 return;
676
677 // Block until the menu closes; quit when the menu is closed or destroyed
678 QEventLoop loop;
679 QObject::connect(menu, &QMenu::aboutToHide, &loop, &QEventLoop::quit);
680 QObject::connect(menu, &QObject::destroyed, &loop, &QEventLoop::quit);
681 loop.exec();
682}
683
684void QToolButtonPrivate::onButtonPressed()
685{
686 Q_Q(QToolButton);
687 if (!hasMenu())
688 return; // no menu to show
689 if (popupMode == QToolButton::MenuButtonPopup)
690 return;
691 else if (delay > 0 && popupMode == QToolButton::DelayedPopup)
692 popupTimer.start(delay, q);
693 else if (delay == 0 || popupMode == QToolButton::InstantPopup)
694 showMenu();
695}
696
697void QToolButtonPrivate::onButtonReleased()
698{
699 popupTimer.stop();
700}
701
702static QPoint positionMenu(const QToolButton *q, bool horizontal,
703 const QSize &sh)
704{
705 QPoint p;
706 const QRect rect = q->rect(); // Find screen via point in case of QGraphicsProxyWidget.
707 const QRect screen = QWidgetPrivate::availableScreenGeometry(q, q->mapToGlobal(rect.center()));
708 if (horizontal) {
709 if (q->isRightToLeft()) {
710 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
711 p = q->mapToGlobal(rect.bottomRight());
712 } else {
713 p = q->mapToGlobal(rect.topRight() - QPoint(0, sh.height()));
714 }
715 p.rx() -= sh.width();
716 } else {
717 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
718 p = q->mapToGlobal(rect.bottomLeft());
719 } else {
720 p = q->mapToGlobal(rect.topLeft() - QPoint(0, sh.height()));
721 }
722 }
723 } else {
724 if (q->isRightToLeft()) {
725 if (q->mapToGlobal(QPoint(rect.left(), 0)).x() - sh.width() <= screen.x()) {
726 p = q->mapToGlobal(rect.topRight());
727 } else {
728 p = q->mapToGlobal(rect.topLeft());
729 p.rx() -= sh.width();
730 }
731 } else {
732 if (q->mapToGlobal(QPoint(rect.right(), 0)).x() + sh.width() <= screen.right()) {
733 p = q->mapToGlobal(rect.topRight());
734 } else {
735 p = q->mapToGlobal(rect.topLeft() - QPoint(sh.width(), 0));
736 }
737 }
738 }
739
740 // QTBUG-118695 Force point inside the current screen. If the returned point
741 // is not found inside any screen, QMenu's positioning logic kicks in without
742 // taking the QToolButton's screen into account. This can cause the menu to
743 // end up on primary monitor, even if the QToolButton is on a non-primary monitor.
744 p.rx() = qMax(screen.left(), qMin(p.x(), screen.right() - sh.width()));
745 p.ry() = qMax(screen.top(), qMin(p.y() + 1, screen.bottom()));
746 return p;
747}
748
749// Shows the tool button's menu. Returns immediately and does not block or
750// spin a nested event loop.
751QMenu *QToolButtonPrivate::showMenu()
752{
753 Q_Q(QToolButton);
754
755 if (!hasMenu()) {
756 menuButtonDown = false;
757 return nullptr; // no menu to show
758 }
759 // prevent recursions from showing the menu again
760 if (menuButtonDown)
761 return nullptr;
762
763 menuButtonDown = true;
764 q->repaint();
765 popupTimer.stop();
766
767 QPointer<QMenu> actualMenu;
768 bool mustDeleteActualMenu = false;
769 if (menuAction) {
770 actualMenu = menuAction->menu();
771 } else if (defaultAction && defaultAction->menu()) {
772 actualMenu = defaultAction->menu();
773 } else {
774 actualMenu = new QMenu(q);
775 mustDeleteActualMenu = true;
776 for (int i = 0; i < actions.size(); i++)
777 actualMenu->addAction(actions.at(i));
778 }
779 repeat = q->autoRepeat();
780 q->setAutoRepeat(false);
781 bool horizontal = true;
782#if QT_CONFIG(toolbar)
783 QToolBar *tb = qobject_cast<QToolBar*>(parent);
784 if (tb && tb->orientation() == Qt::Vertical)
785 horizontal = false;
786#endif
787 QPointer<QToolButton> that = q;
788 actualMenu->setNoReplayFor(q);
789 if (!mustDeleteActualMenu) { //only if action are not in this widget
790 QObjectPrivate::connect(actualMenu, &QMenu::triggered,
791 this, &QToolButtonPrivate::onMenuTriggered);
792 }
793 QObjectPrivate::connect(actualMenu, &QMenu::aboutToHide,
794 this, &QToolButtonPrivate::updateButtonDown);
795 actualMenu->d_func()->causedPopup.widget = q;
796 actualMenu->d_func()->causedPopup.action = defaultAction;
797 actionsCopy = q->actions(); //(the list of action may be modified in slots)
798
799 // QTBUG-78966, Delay positioning until after aboutToShow().
800 auto positionFunction = [q, horizontal](const QSize &sizeHint) {
801 return positionMenu(q, horizontal, sizeHint); };
802 const auto initialPos = positionFunction(actualMenu->sizeHint());
803
804 auto cleanup = [that, mustDeleteActualMenu, actualMenu] {
805 if (!that)
806 return;
807 QToolButtonPrivate *d = that->d_func();
808
809 if (d->menuButtonDown) {
810 // The menu was empty, it didn't actually show up, so it was never hidden either
811 d->updateButtonDown();
812 }
813
814 if (mustDeleteActualMenu) {
815 actualMenu->deleteLater();
816 } else if (actualMenu) {
817 QObjectPrivate::disconnect(actualMenu, &QMenu::aboutToHide,
818 d, &QToolButtonPrivate::updateButtonDown);
819 QObjectPrivate::disconnect(actualMenu, &QMenu::triggered,
820 d, &QToolButtonPrivate::onMenuTriggered);
821 }
822
823 d->actionsCopy.clear();
824 if (d->repeat)
825 that->setAutoRepeat(true);
826 };
827
828 // Run cleanup when the menu is done. There are two relevant signals in order
829 // 1. aboutToHide
830 // 2. triggered [optional]
831 // But since aboutToHide is first and triggered is optional there is no
832 // one single place to clean up. (cleanup disconnects from the triggered signal)
833 auto runCleanupWhenDone = [actualMenu, cleanup] {
834 if (actualMenu && actualMenu->d_func()->actionAboutToTrigger) {
835 QObject::connect(actualMenu, &QMenu::triggered, actualMenu, cleanup,
836 Qt::SingleShotConnection);
837 } else {
838 cleanup();
839 }
840 };
841
842 QMetaObject::Connection hideConnection = QObject::connect(
843 actualMenu, &QMenu::aboutToHide, q, runCleanupWhenDone,
844 Qt::SingleShotConnection);
845 actualMenu->d_func()->popup(initialPos, nullptr, positionFunction);
846
847 if (!actualMenu || !actualMenu->isVisible()) {
848 // Nothing was shown (e.g. an empty menu), so aboutToHide will never
849 // fire. Run the cleanup now instead.
850 QObject::disconnect(hideConnection);
851 cleanup();
852 return nullptr;
853 }
854 return actualMenu;
855}
856
857void QToolButtonPrivate::updateButtonDown()
858{
859 Q_Q(QToolButton);
860 menuButtonDown = false;
861 if (q->isDown())
862 q->setDown(false);
863 else
864 q->repaint();
865}
866
867void QToolButtonPrivate::onMenuTriggered(QAction *action)
868{
869 Q_Q(QToolButton);
870 if (action && !actionsCopy.contains(action))
871 emit q->triggered(action);
872}
873
874void QToolButtonPrivate::onDefaultActionChanged()
875{
876 Q_Q(QToolButton);
877 if (defaultAction && defaultAction->menu() && !popupModeSetByUser)
878 q->setPopupMode(QToolButton::MenuButtonPopup);
879}
880
881/*! \enum QToolButton::ToolButtonPopupMode
882
883 Describes how a menu should be popped up for tool buttons that has
884 a menu set or contains a list of actions.
885
886 \value DelayedPopup After pressing and holding the tool button
887 down for a certain amount of time (the timeout is style dependent,
888 see QStyle::SH_ToolButton_PopupDelay), the menu is displayed. A
889 typical application example is the "back" button in some web
890 browsers's tool bars. If the user clicks it, the browser simply
891 browses back to the previous page. If the user presses and holds
892 the button down for a while, the tool button shows a menu
893 containing the current history list
894
895 \value MenuButtonPopup In this mode the tool button displays a
896 special arrow to indicate that a menu is present. The menu is
897 displayed when the arrow part of the button is pressed.
898
899 \value InstantPopup The menu is displayed, without delay, when
900 the tool button is pressed. In this mode, the button's own action
901 is not triggered.
902*/
903
904/*!
905 \property QToolButton::popupMode
906 \brief describes the way that popup menus are used with tool buttons
907
908 By default, this property is set to \l DelayedPopup.
909*/
910
911void QToolButton::setPopupMode(ToolButtonPopupMode mode)
912{
913 Q_D(QToolButton);
914 d->popupModeSetByUser = true;
915 d->popupMode = mode;
916}
917
918QToolButton::ToolButtonPopupMode QToolButton::popupMode() const
919{
920 Q_D(const QToolButton);
921 return d->popupMode;
922}
923#endif
924
925/*!
926 \property QToolButton::autoRaise
927 \brief whether auto-raising is enabled or not.
928
929 The default is disabled (i.e. false).
930
931 This property is currently ignored on \macos when using QMacStyle.
932*/
933void QToolButton::setAutoRaise(bool enable)
934{
935 Q_D(QToolButton);
936 d->autoRaise = enable;
937
938 update();
939}
940
941bool QToolButton::autoRaise() const
942{
943 Q_D(const QToolButton);
944 return d->autoRaise;
945}
946
947/*!
948 Sets the default action to \a action.
949
950 If a tool button has a default action, the action defines the
951 following properties of the button:
952
953 \list
954 \li \l {QAbstractButton::}{checkable}
955 \li \l {QAbstractButton::}{checked}
956 \li \l {QWidget::}{enabled}
957 \li \l {QWidget::}{font}
958 \li \l {QAbstractButton::}{icon}
959 \li \l {QToolButton::}{popupMode} (assuming the action has a menu)
960 \li \l {QWidget::}{statusTip}
961 \li \l {QAbstractButton::}{text}
962 \li \l {QWidget::}{toolTip}
963 \li \l {QWidget::}{whatsThis}
964 \endlist
965
966 Other properties, such as \l autoRepeat, are not affected
967 by actions.
968 */
969void QToolButton::setDefaultAction(QAction *action)
970{
971 Q_D(QToolButton);
972#if QT_CONFIG(menu)
973 if (d->defaultAction && d->defaultAction != action) {
974 QObjectPrivate::disconnect(d->defaultAction, &QAction::changed, d,
975 &QToolButtonPrivate::onDefaultActionChanged);
976 }
977 const bool hadMenu = d->hasMenu();
978#endif
979 d->defaultAction = action;
980 if (!action)
981 return;
982 if (!actions().contains(action))
983 addAction(action);
984 QString buttonText = action->iconText();
985 // If iconText() is generated from text(), we need to escape any '&'s so they
986 // don't turn into shortcuts
987 if (QActionPrivate::get(action)->iconText.isEmpty())
988 buttonText.replace("&"_L1, "&&"_L1);
989 setText(buttonText);
990 setIcon(action->icon());
991#if QT_CONFIG(tooltip)
992 setToolTip(action->toolTip());
993#endif
994#if QT_CONFIG(statustip)
995 setStatusTip(action->statusTip());
996#endif
997#if QT_CONFIG(whatsthis)
998 setWhatsThis(action->whatsThis());
999#endif
1000#if QT_CONFIG(menu)
1001 if (!hadMenu && !d->popupModeSetByUser) {
1002 // ### Qt7 Fixme
1003 // new 'default' popup mode defined introduced by tool bar. We
1004 // should have changed QToolButton's default instead. Do that
1005 // in 4.2.
1006 if (action->menu()) {
1007 setPopupMode(QToolButton::MenuButtonPopup);
1008 } else {
1009 QObjectPrivate::connect(d->defaultAction, &QAction::changed, d,
1010 &QToolButtonPrivate::onDefaultActionChanged);
1011 }
1012 }
1013#endif
1014 setCheckable(action->isCheckable());
1015 setChecked(action->isChecked());
1016 setEnabled(action->isEnabled());
1017 if (action->d_func()->fontSet)
1018 setFont(action->font());
1019}
1020
1021
1022/*!
1023 Returns the default action.
1024
1025 \sa setDefaultAction()
1026 */
1027QAction *QToolButton::defaultAction() const
1028{
1029 Q_D(const QToolButton);
1030 return d->defaultAction;
1031}
1032
1033/*!
1034 \reimp
1035 */
1036void QToolButton::checkStateSet()
1037{
1038 Q_D(QToolButton);
1039 if (d->defaultAction && d->defaultAction->isCheckable())
1040 d->defaultAction->setChecked(isChecked());
1041}
1042
1043/*!
1044 \reimp
1045 */
1046void QToolButton::nextCheckState()
1047{
1048 Q_D(QToolButton);
1049 if (!d->defaultAction)
1050 QAbstractButton::nextCheckState();
1051 else
1052 d->defaultAction->trigger();
1053}
1054
1055/*! \reimp */
1056bool QToolButton::event(QEvent *event)
1057{
1058 switch(event->type()) {
1059 case QEvent::HoverEnter:
1060 case QEvent::HoverLeave:
1061 case QEvent::HoverMove:
1062 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
1063 d_func()->updateHoverControl(he->position().toPoint());
1064 break;
1065 default:
1066 break;
1067 }
1068 return QAbstractButton::event(event);
1069}
1070
1071QT_END_NAMESPACE
1072
1073#include "moc_qtoolbutton.cpp"
\inmodule QtCore\reentrant
Definition qpoint.h:30
QAction * defaultAction
bool updateHoverControl(const QPoint &pos)
Qt::ArrowType arrowType
QStyle::SubControl hoverControl
QPointer< QAction > menuAction
Qt::ToolButtonStyle toolButtonStyle
QStyle::State styleButtonState(QStyle::State state) const override
QStyle::SubControl newHoverControl(const QPoint &pos)
QBasicTimer popupTimer
Combined button and popup list for selecting options.