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 auto cleanup = [that = QPointer(q), mustDeleteActualMenu, actualMenu] {
788 if (!that)
789 return;
790 QToolButtonPrivate *d = that->d_func();
791
792 if (d->menuButtonDown) {
793 // The menu was empty, it didn't actually show up, so it was never hidden either
794 d->updateButtonDown();
795 }
796
797 if (mustDeleteActualMenu) {
798 actualMenu->deleteLater();
799 } else if (actualMenu) {
800 QObjectPrivate::disconnect(actualMenu, &QMenu::aboutToHide,
801 d, &QToolButtonPrivate::updateButtonDown);
802 QObjectPrivate::disconnect(actualMenu, &QMenu::triggered,
803 d, &QToolButtonPrivate::onMenuTriggered);
804 }
805
806 d->actionsCopy.clear();
807 if (d->repeat)
808 that->setAutoRepeat(true);
809 };
810
811 actualMenu->setNoReplayFor(q);
812 if (!mustDeleteActualMenu) { //only if action are not in this widget
813 QObjectPrivate::connect(actualMenu, &QMenu::triggered,
814 this, &QToolButtonPrivate::onMenuTriggered);
815 }
816 QObjectPrivate::connect(actualMenu, &QMenu::aboutToHide,
817 this, &QToolButtonPrivate::updateButtonDown);
818 actualMenu->d_func()->causedPopup.widget = q;
819 actualMenu->d_func()->causedPopup.action = defaultAction;
820 actionsCopy = q->actions(); //(the list of action may be modified in slots)
821
822 // QTBUG-78966, Delay positioning until after aboutToShow().
823 auto positionFunction = [q, horizontal](const QSize &sizeHint) {
824 return positionMenu(q, horizontal, sizeHint); };
825 const auto initialPos = positionFunction(actualMenu->sizeHint());
826
827 // Run cleanup when the menu is done. There are two relevant signals in order
828 // 1. aboutToHide
829 // 2. triggered [optional]
830 // But since aboutToHide is first and triggered is optional there is no
831 // one single place to clean up. (cleanup disconnects from the triggered signal)
832 auto runCleanupWhenDone = [actualMenu, cleanup] {
833 if (actualMenu && actualMenu->d_func()->actionAboutToTrigger) {
834 QObject::connect(actualMenu, &QMenu::triggered, actualMenu, cleanup,
835 Qt::SingleShotConnection);
836 } else {
837 cleanup();
838 }
839 };
840
841 QMetaObject::Connection hideConnection = QObject::connect(
842 actualMenu, &QMenu::aboutToHide, q, runCleanupWhenDone,
843 Qt::SingleShotConnection);
844 actualMenu->d_func()->popup(initialPos, nullptr, positionFunction);
845
846 if (!actualMenu || !actualMenu->isVisible()) {
847 // Nothing was shown (e.g. an empty menu), so aboutToHide will never
848 // fire. Run the cleanup now instead.
849 QObject::disconnect(hideConnection);
850 cleanup();
851 return nullptr;
852 }
853 return actualMenu;
854}
855
856void QToolButtonPrivate::updateButtonDown()
857{
858 Q_Q(QToolButton);
859 menuButtonDown = false;
860 if (q->isDown())
861 q->setDown(false);
862 else
863 q->repaint();
864}
865
866void QToolButtonPrivate::onMenuTriggered(QAction *action)
867{
868 Q_Q(QToolButton);
869 if (action && !actionsCopy.contains(action))
870 emit q->triggered(action);
871}
872
873void QToolButtonPrivate::onDefaultActionChanged()
874{
875 Q_Q(QToolButton);
876 if (defaultAction && defaultAction->menu() && !popupModeSetByUser)
877 q->setPopupMode(QToolButton::MenuButtonPopup);
878}
879
880/*! \enum QToolButton::ToolButtonPopupMode
881
882 Describes how a menu should be popped up for tool buttons that has
883 a menu set or contains a list of actions.
884
885 \value DelayedPopup After pressing and holding the tool button
886 down for a certain amount of time (the timeout is style dependent,
887 see QStyle::SH_ToolButton_PopupDelay), the menu is displayed. A
888 typical application example is the "back" button in some web
889 browsers's tool bars. If the user clicks it, the browser simply
890 browses back to the previous page. If the user presses and holds
891 the button down for a while, the tool button shows a menu
892 containing the current history list
893
894 \value MenuButtonPopup In this mode the tool button displays a
895 special arrow to indicate that a menu is present. The menu is
896 displayed when the arrow part of the button is pressed.
897
898 \value InstantPopup The menu is displayed, without delay, when
899 the tool button is pressed. In this mode, the button's own action
900 is not triggered.
901*/
902
903/*!
904 \property QToolButton::popupMode
905 \brief describes the way that popup menus are used with tool buttons
906
907 By default, this property is set to \l DelayedPopup.
908*/
909
910void QToolButton::setPopupMode(ToolButtonPopupMode mode)
911{
912 Q_D(QToolButton);
913 d->popupModeSetByUser = true;
914 d->popupMode = mode;
915}
916
917QToolButton::ToolButtonPopupMode QToolButton::popupMode() const
918{
919 Q_D(const QToolButton);
920 return d->popupMode;
921}
922#endif
923
924/*!
925 \property QToolButton::autoRaise
926 \brief whether auto-raising is enabled or not.
927
928 The default is disabled (i.e. false).
929
930 This property is currently ignored on \macos when using QMacStyle.
931*/
932void QToolButton::setAutoRaise(bool enable)
933{
934 Q_D(QToolButton);
935 d->autoRaise = enable;
936
937 update();
938}
939
940bool QToolButton::autoRaise() const
941{
942 Q_D(const QToolButton);
943 return d->autoRaise;
944}
945
946/*!
947 Sets the default action to \a action.
948
949 If a tool button has a default action, the action defines the
950 following properties of the button:
951
952 \list
953 \li \l {QAbstractButton::}{checkable}
954 \li \l {QAbstractButton::}{checked}
955 \li \l {QWidget::}{enabled}
956 \li \l {QWidget::}{font}
957 \li \l {QAbstractButton::}{icon}
958 \li \l {QToolButton::}{popupMode} (assuming the action has a menu)
959 \li \l {QWidget::}{statusTip}
960 \li \l {QAbstractButton::}{text}
961 \li \l {QWidget::}{toolTip}
962 \li \l {QWidget::}{whatsThis}
963 \endlist
964
965 Other properties, such as \l autoRepeat, are not affected
966 by actions.
967 */
968void QToolButton::setDefaultAction(QAction *action)
969{
970 Q_D(QToolButton);
971#if QT_CONFIG(menu)
972 if (d->defaultAction && d->defaultAction != action) {
973 QObjectPrivate::disconnect(d->defaultAction, &QAction::changed, d,
974 &QToolButtonPrivate::onDefaultActionChanged);
975 }
976 const bool hadMenu = d->hasMenu();
977#endif
978 d->defaultAction = action;
979 if (!action)
980 return;
981 if (!actions().contains(action))
982 addAction(action);
983 QString buttonText = action->iconText();
984 // If iconText() is generated from text(), we need to escape any '&'s so they
985 // don't turn into shortcuts
986 if (QActionPrivate::get(action)->iconText.isEmpty())
987 buttonText.replace("&"_L1, "&&"_L1);
988 setText(buttonText);
989 setIcon(action->icon());
990#if QT_CONFIG(tooltip)
991 setToolTip(action->toolTip());
992#endif
993#if QT_CONFIG(statustip)
994 setStatusTip(action->statusTip());
995#endif
996#if QT_CONFIG(whatsthis)
997 setWhatsThis(action->whatsThis());
998#endif
999#if QT_CONFIG(menu)
1000 if (!hadMenu && !d->popupModeSetByUser) {
1001 // ### Qt7 Fixme
1002 // new 'default' popup mode defined introduced by tool bar. We
1003 // should have changed QToolButton's default instead. Do that
1004 // in 4.2.
1005 if (action->menu()) {
1006 setPopupMode(QToolButton::MenuButtonPopup);
1007 } else {
1008 QObjectPrivate::connect(d->defaultAction, &QAction::changed, d,
1009 &QToolButtonPrivate::onDefaultActionChanged,
1010 Qt::UniqueConnection);
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.