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

source code of qtbase/src/widgets/widgets/qtoolbutton.cpp