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 onButtonPressed();
43 void onButtonReleased();
44 void popupTimerDone();
45 void updateButtonDown();
46 void onMenuTriggered(QAction *);
47#endif
48 bool updateHoverControl(const QPoint &pos);
49 void onActionTriggered();
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 QObjectPrivate::connect(sender: q, signal: &QAbstractButton::pressed,
191 receiverPrivate: this, slot: &QToolButtonPrivate::onButtonPressed);
192 QObjectPrivate::connect(sender: q, signal: &QAbstractButton::released,
193 receiverPrivate: this, slot: &QToolButtonPrivate::onButtonReleased);
194#endif
195
196 setLayoutItemMargins(element: QStyle::SE_ToolButtonLayoutItem);
197 delay = q->style()->styleHint(stylehint: QStyle::SH_ToolButton_PopupDelay, opt: nullptr, widget: q);
198}
199
200/*!
201 Initialize \a option with the values from this QToolButton. This method
202 is useful for subclasses when they need a QStyleOptionToolButton, but don't want
203 to fill in all the information themselves.
204
205 \sa QStyleOption::initFrom()
206*/
207void QToolButton::initStyleOption(QStyleOptionToolButton *option) const
208{
209 if (!option)
210 return;
211
212 Q_D(const QToolButton);
213 option->initFrom(w: this);
214 option->iconSize = iconSize(); //default value
215
216#if QT_CONFIG(toolbar)
217 if (parentWidget()) {
218 if (QToolBar *toolBar = qobject_cast<QToolBar *>(object: parentWidget())) {
219 option->iconSize = toolBar->iconSize();
220 }
221 }
222#endif // QT_CONFIG(toolbar)
223
224 option->text = d->text;
225 option->icon = d->icon;
226 option->arrowType = d->arrowType;
227 if (d->down)
228 option->state |= QStyle::State_Sunken;
229 if (d->checked)
230 option->state |= QStyle::State_On;
231 if (d->autoRaise)
232 option->state |= QStyle::State_AutoRaise;
233 if (!d->checked && !d->down)
234 option->state |= QStyle::State_Raised;
235
236 option->subControls = QStyle::SC_ToolButton;
237 option->activeSubControls = QStyle::SC_None;
238
239 option->features = QStyleOptionToolButton::None;
240 if (d->popupMode == QToolButton::MenuButtonPopup) {
241 option->subControls |= QStyle::SC_ToolButtonMenu;
242 option->features |= QStyleOptionToolButton::MenuButtonPopup;
243 }
244 if (option->state & QStyle::State_MouseOver) {
245 option->activeSubControls = d->hoverControl;
246 }
247 if (d->menuButtonDown) {
248 option->state |= QStyle::State_Sunken;
249 option->activeSubControls |= QStyle::SC_ToolButtonMenu;
250 }
251 if (d->down) {
252 option->state |= QStyle::State_Sunken;
253 option->activeSubControls |= QStyle::SC_ToolButton;
254 }
255
256
257 if (d->arrowType != Qt::NoArrow)
258 option->features |= QStyleOptionToolButton::Arrow;
259 if (d->popupMode == QToolButton::DelayedPopup)
260 option->features |= QStyleOptionToolButton::PopupDelay;
261#if QT_CONFIG(menu)
262 if (d->hasMenu())
263 option->features |= QStyleOptionToolButton::HasMenu;
264#endif
265 if (d->toolButtonStyle == Qt::ToolButtonFollowStyle) {
266 option->toolButtonStyle = Qt::ToolButtonStyle(style()->styleHint(stylehint: QStyle::SH_ToolButtonStyle, opt: option, widget: this));
267 } else
268 option->toolButtonStyle = d->toolButtonStyle;
269
270 if (option->toolButtonStyle == Qt::ToolButtonTextBesideIcon) {
271 // If the action is not prioritized, remove the text label to save space
272 if (d->defaultAction && d->defaultAction->priority() < QAction::NormalPriority)
273 option->toolButtonStyle = Qt::ToolButtonIconOnly;
274 }
275
276 if (d->icon.isNull() && d->arrowType == Qt::NoArrow) {
277 if (!d->text.isEmpty())
278 option->toolButtonStyle = Qt::ToolButtonTextOnly;
279 else if (option->toolButtonStyle != Qt::ToolButtonTextOnly)
280 option->toolButtonStyle = Qt::ToolButtonIconOnly;
281 }
282
283 option->pos = pos();
284 option->font = font();
285}
286
287/*!
288 Destroys the object and frees any allocated resources.
289*/
290
291QToolButton::~QToolButton()
292{
293}
294
295/*!
296 \reimp
297*/
298QSize QToolButton::sizeHint() const
299{
300 Q_D(const QToolButton);
301 if (d->sizeHint.isValid())
302 return d->sizeHint;
303 ensurePolished();
304
305 int w = 0, h = 0;
306 QStyleOptionToolButton opt;
307 initStyleOption(option: &opt);
308
309 QFontMetrics fm = fontMetrics();
310 if (opt.toolButtonStyle != Qt::ToolButtonTextOnly) {
311 QSize icon = opt.iconSize;
312 w = icon.width();
313 h = icon.height();
314 }
315
316 if (opt.toolButtonStyle != Qt::ToolButtonIconOnly) {
317 QSize textSize = fm.size(flags: Qt::TextShowMnemonic, str: text());
318 textSize.setWidth(textSize.width() + fm.horizontalAdvance(u' ') * 2);
319 if (opt.toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
320 h += 4 + textSize.height();
321 if (textSize.width() > w)
322 w = textSize.width();
323 } else if (opt.toolButtonStyle == Qt::ToolButtonTextBesideIcon) {
324 w += 4 + textSize.width();
325 if (textSize.height() > h)
326 h = textSize.height();
327 } else { // TextOnly
328 w = textSize.width();
329 h = textSize.height();
330 }
331 }
332
333 opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
334 if (d->popupMode == MenuButtonPopup)
335 w += style()->pixelMetric(metric: QStyle::PM_MenuButtonIndicator, option: &opt, widget: this);
336
337 d->sizeHint = style()->sizeFromContents(ct: QStyle::CT_ToolButton, opt: &opt, contentsSize: QSize(w, h), w: this);
338 return d->sizeHint;
339}
340
341/*!
342 \reimp
343 */
344QSize QToolButton::minimumSizeHint() const
345{
346 return sizeHint();
347}
348
349/*!
350 \property QToolButton::toolButtonStyle
351 \brief whether the tool button displays an icon only, text only,
352 or text beside/below the icon.
353
354 The default is Qt::ToolButtonIconOnly.
355
356 To have the style of toolbuttons follow the system settings, set this property to Qt::ToolButtonFollowStyle.
357 On Unix, the user settings from the desktop environment will be used.
358 On other platforms, Qt::ToolButtonFollowStyle means icon only.
359
360 QToolButton automatically connects this slot to the relevant
361 signal in the QMainWindow in which is resides.
362*/
363
364/*!
365 \property QToolButton::arrowType
366 \brief whether the button displays an arrow instead of a normal icon
367
368 This displays an arrow as the icon for the QToolButton.
369
370 By default, this property is set to Qt::NoArrow.
371*/
372
373Qt::ToolButtonStyle QToolButton::toolButtonStyle() const
374{
375 Q_D(const QToolButton);
376 return d->toolButtonStyle;
377}
378
379Qt::ArrowType QToolButton::arrowType() const
380{
381 Q_D(const QToolButton);
382 return d->arrowType;
383}
384
385
386void QToolButton::setToolButtonStyle(Qt::ToolButtonStyle style)
387{
388 Q_D(QToolButton);
389 if (d->toolButtonStyle == style)
390 return;
391
392 d->toolButtonStyle = style;
393 d->sizeHint = QSize();
394 updateGeometry();
395 if (isVisible()) {
396 update();
397 }
398}
399
400void QToolButton::setArrowType(Qt::ArrowType type)
401{
402 Q_D(QToolButton);
403 if (d->arrowType == type)
404 return;
405
406 d->arrowType = type;
407 d->sizeHint = QSize();
408 updateGeometry();
409 if (isVisible()) {
410 update();
411 }
412}
413
414/*!
415 \fn void QToolButton::paintEvent(QPaintEvent *event)
416
417 Paints the button in response to the paint \a event.
418*/
419void QToolButton::paintEvent(QPaintEvent *)
420{
421 QStylePainter p(this);
422 QStyleOptionToolButton opt;
423 initStyleOption(option: &opt);
424 p.drawComplexControl(cc: QStyle::CC_ToolButton, opt);
425}
426
427/*!
428 \reimp
429 */
430void QToolButton::actionEvent(QActionEvent *event)
431{
432 Q_D(QToolButton);
433 auto action = static_cast<QAction *>(event->action());
434 switch (event->type()) {
435 case QEvent::ActionChanged:
436 if (action == d->defaultAction)
437 setDefaultAction(action); // update button state
438 break;
439 case QEvent::ActionAdded:
440 QObjectPrivate::connect(sender: action, signal: &QAction::triggered, receiverPrivate: d,
441 slot: &QToolButtonPrivate::onActionTriggered);
442 break;
443 case QEvent::ActionRemoved:
444 if (d->defaultAction == action)
445 d->defaultAction = nullptr;
446#if QT_CONFIG(menu)
447 if (action == d->menuAction)
448 d->menuAction = nullptr;
449#endif
450 action->disconnect(receiver: this);
451 break;
452 default:
453 ;
454 }
455 QAbstractButton::actionEvent(event);
456}
457
458QStyle::SubControl QToolButtonPrivate::newHoverControl(const QPoint &pos)
459{
460 Q_Q(QToolButton);
461 QStyleOptionToolButton opt;
462 q->initStyleOption(option: &opt);
463 opt.subControls = QStyle::SC_All;
464 hoverControl = q->style()->hitTestComplexControl(cc: QStyle::CC_ToolButton, opt: &opt, pt: pos, widget: q);
465 if (hoverControl == QStyle::SC_None)
466 hoverRect = QRect();
467 else
468 hoverRect = q->style()->subControlRect(cc: QStyle::CC_ToolButton, opt: &opt, sc: hoverControl, widget: q);
469 return hoverControl;
470}
471
472bool QToolButtonPrivate::updateHoverControl(const QPoint &pos)
473{
474 Q_Q(QToolButton);
475 QRect lastHoverRect = hoverRect;
476 QStyle::SubControl lastHoverControl = hoverControl;
477 bool doesHover = q->testAttribute(attribute: Qt::WA_Hover);
478 if (lastHoverControl != newHoverControl(pos) && doesHover) {
479 q->update(lastHoverRect);
480 q->update(hoverRect);
481 return true;
482 }
483 return !doesHover;
484}
485
486void QToolButtonPrivate::onActionTriggered()
487{
488 Q_Q(QToolButton);
489 if (QAction *action = qobject_cast<QAction *>(object: q->sender()))
490 emit q->triggered(action);
491}
492
493/*!
494 \reimp
495 */
496void QToolButton::enterEvent(QEnterEvent * e)
497{
498 Q_D(QToolButton);
499 if (d->autoRaise)
500 update();
501 if (d->defaultAction)
502 d->defaultAction->hover();
503 QAbstractButton::enterEvent(event: e);
504}
505
506
507/*!
508 \reimp
509 */
510void QToolButton::leaveEvent(QEvent * e)
511{
512 Q_D(QToolButton);
513 if (d->autoRaise)
514 update();
515
516 QAbstractButton::leaveEvent(event: e);
517}
518
519
520/*!
521 \reimp
522 */
523void QToolButton::timerEvent(QTimerEvent *e)
524{
525#if QT_CONFIG(menu)
526 Q_D(QToolButton);
527 if (e->timerId() == d->popupTimer.timerId()) {
528 d->popupTimerDone();
529 return;
530 }
531#endif
532 QAbstractButton::timerEvent(e);
533}
534
535
536/*!
537 \reimp
538*/
539void QToolButton::changeEvent(QEvent *e)
540{
541#if QT_CONFIG(toolbar)
542 Q_D(QToolButton);
543 if (e->type() == QEvent::ParentChange) {
544 if (qobject_cast<QToolBar*>(object: parentWidget()))
545 d->autoRaise = true;
546 } else if (e->type() == QEvent::StyleChange
547#ifdef Q_OS_MAC
548 || e->type() == QEvent::MacSizeChange
549#endif
550 ) {
551 d->delay = style()->styleHint(stylehint: QStyle::SH_ToolButton_PopupDelay, opt: nullptr, widget: this);
552 d->setLayoutItemMargins(element: QStyle::SE_ToolButtonLayoutItem);
553 }
554#endif
555 QAbstractButton::changeEvent(e);
556}
557
558/*!
559 \reimp
560*/
561void QToolButton::mousePressEvent(QMouseEvent *e)
562{
563 Q_D(QToolButton);
564#if QT_CONFIG(menu)
565 QStyleOptionToolButton opt;
566 initStyleOption(option: &opt);
567 if (e->button() == Qt::LeftButton && (d->popupMode == MenuButtonPopup)) {
568 QRect popupr = style()->subControlRect(cc: QStyle::CC_ToolButton, opt: &opt,
569 sc: QStyle::SC_ToolButtonMenu, widget: this);
570 if (popupr.isValid() && popupr.contains(p: e->position().toPoint())) {
571 d->buttonPressed = QToolButtonPrivate::MenuButtonPressed;
572 showMenu();
573 return;
574 }
575 }
576#endif
577 d->buttonPressed = QToolButtonPrivate::ToolButtonPressed;
578 QAbstractButton::mousePressEvent(e);
579}
580
581/*!
582 \reimp
583*/
584void QToolButton::mouseReleaseEvent(QMouseEvent *e)
585{
586 Q_D(QToolButton);
587 QPointer<QAbstractButton> guard(this);
588 QAbstractButton::mouseReleaseEvent(e);
589 if (guard)
590 d->buttonPressed = QToolButtonPrivate::NoButtonPressed;
591}
592
593/*!
594 \reimp
595*/
596bool QToolButton::hitButton(const QPoint &pos) const
597{
598 Q_D(const QToolButton);
599 if (QAbstractButton::hitButton(pos))
600 return (d->buttonPressed != QToolButtonPrivate::MenuButtonPressed);
601 return false;
602}
603
604
605#if QT_CONFIG(menu)
606/*!
607 Associates the given \a menu with this tool button.
608
609 The menu will be shown according to the button's \l popupMode.
610
611 Ownership of the menu is not transferred to the tool button.
612
613 \sa menu()
614*/
615void QToolButton::setMenu(QMenu* menu)
616{
617 Q_D(QToolButton);
618
619 if (d->menuAction == (menu ? menu->menuAction() : nullptr))
620 return;
621
622 if (d->menuAction)
623 removeAction(action: d->menuAction);
624
625 if (menu) {
626 d->menuAction = menu->menuAction();
627 addAction(action: d->menuAction);
628 } else {
629 d->menuAction = nullptr;
630 }
631
632 // changing the menu set may change the size hint, so reset it
633 d->sizeHint = QSize();
634 updateGeometry();
635 update();
636}
637
638/*!
639 Returns the associated menu, or \nullptr if no menu has been
640 defined.
641
642 \sa setMenu()
643*/
644QMenu* QToolButton::menu() const
645{
646 Q_D(const QToolButton);
647 if (d->menuAction)
648 return d->menuAction->menu();
649 return nullptr;
650}
651
652/*!
653 Shows (pops up) the associated popup menu. If there is no such
654 menu, this function does nothing. This function does not return
655 until the popup menu has been closed by the user.
656*/
657void QToolButton::showMenu()
658{
659 Q_D(QToolButton);
660 if (!d->hasMenu()) {
661 d->menuButtonDown = false;
662 return; // no menu to show
663 }
664 // prevent recursions spinning another event loop
665 if (d->menuButtonDown)
666 return;
667
668
669 d->menuButtonDown = true;
670 repaint();
671 d->popupTimer.stop();
672 d->popupTimerDone();
673}
674
675void QToolButtonPrivate::onButtonPressed()
676{
677 Q_Q(QToolButton);
678 if (!hasMenu())
679 return; // no menu to show
680 if (popupMode == QToolButton::MenuButtonPopup)
681 return;
682 else if (delay > 0 && popupMode == QToolButton::DelayedPopup)
683 popupTimer.start(msec: delay, obj: q);
684 else if (delay == 0 || popupMode == QToolButton::InstantPopup)
685 q->showMenu();
686}
687
688void QToolButtonPrivate::onButtonReleased()
689{
690 popupTimer.stop();
691}
692
693static QPoint positionMenu(const QToolButton *q, bool horizontal,
694 const QSize &sh)
695{
696 QPoint p;
697 const QRect rect = q->rect(); // Find screen via point in case of QGraphicsProxyWidget.
698 const QRect screen = QWidgetPrivate::availableScreenGeometry(widget: q, globalPosition: q->mapToGlobal(rect.center()));
699 if (horizontal) {
700 if (q->isRightToLeft()) {
701 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
702 p = q->mapToGlobal(rect.bottomRight());
703 } else {
704 p = q->mapToGlobal(rect.topRight() - QPoint(0, sh.height()));
705 }
706 p.rx() -= sh.width();
707 } else {
708 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
709 p = q->mapToGlobal(rect.bottomLeft());
710 } else {
711 p = q->mapToGlobal(rect.topLeft() - QPoint(0, sh.height()));
712 }
713 }
714 } else {
715 if (q->isRightToLeft()) {
716 if (q->mapToGlobal(QPoint(rect.left(), 0)).x() - sh.width() <= screen.x()) {
717 p = q->mapToGlobal(rect.topRight());
718 } else {
719 p = q->mapToGlobal(rect.topLeft());
720 p.rx() -= sh.width();
721 }
722 } else {
723 if (q->mapToGlobal(QPoint(rect.right(), 0)).x() + sh.width() <= screen.right()) {
724 p = q->mapToGlobal(rect.topRight());
725 } else {
726 p = q->mapToGlobal(rect.topLeft() - QPoint(sh.width(), 0));
727 }
728 }
729 }
730
731 // QTBUG-118695 Force point inside the current screen. If the returned point
732 // is not found inside any screen, QMenu's positioning logic kicks in without
733 // taking the QToolButton's screen into account. This can cause the menu to
734 // end up on primary monitor, even if the QToolButton is on a non-primary monitor.
735 p.rx() = qMax(a: screen.left(), b: qMin(a: p.x(), b: screen.right() - sh.width()));
736 p.ry() = qMax(a: screen.top(), b: qMin(a: p.y() + 1, b: screen.bottom()));
737 return p;
738}
739
740void QToolButtonPrivate::popupTimerDone()
741{
742 Q_Q(QToolButton);
743 popupTimer.stop();
744 if (!menuButtonDown && !down)
745 return;
746
747 menuButtonDown = true;
748 QPointer<QMenu> actualMenu;
749 bool mustDeleteActualMenu = false;
750 if (menuAction) {
751 actualMenu = menuAction->menu();
752 } else if (defaultAction && defaultAction->menu()) {
753 actualMenu = defaultAction->menu();
754 } else {
755 actualMenu = new QMenu(q);
756 mustDeleteActualMenu = true;
757 for (int i = 0; i < actions.size(); i++)
758 actualMenu->addAction(action: actions.at(i));
759 }
760 repeat = q->autoRepeat();
761 q->setAutoRepeat(false);
762 bool horizontal = true;
763#if QT_CONFIG(toolbar)
764 QToolBar *tb = qobject_cast<QToolBar*>(object: parent);
765 if (tb && tb->orientation() == Qt::Vertical)
766 horizontal = false;
767#endif
768 QPointer<QToolButton> that = q;
769 actualMenu->setNoReplayFor(q);
770 if (!mustDeleteActualMenu) { //only if action are not in this widget
771 QObjectPrivate::connect(sender: actualMenu, signal: &QMenu::triggered,
772 receiverPrivate: this, slot: &QToolButtonPrivate::onMenuTriggered);
773 }
774 QObjectPrivate::connect(sender: actualMenu, signal: &QMenu::aboutToHide,
775 receiverPrivate: this, slot: &QToolButtonPrivate::updateButtonDown);
776 actualMenu->d_func()->causedPopup.widget = q;
777 actualMenu->d_func()->causedPopup.action = defaultAction;
778 actionsCopy = q->actions(); //(the list of action may be modified in slots)
779
780 // QTBUG-78966, Delay positioning until after aboutToShow().
781 auto positionFunction = [q, horizontal](const QSize &sizeHint) {
782 return positionMenu(q, horizontal, sh: sizeHint); };
783 const auto initialPos = positionFunction(actualMenu->sizeHint());
784 actualMenu->d_func()->exec(p: initialPos, action: nullptr, positionFunction);
785
786 if (!that)
787 return;
788
789 QObjectPrivate::disconnect(sender: actualMenu, signal: &QMenu::aboutToHide,
790 receiverPrivate: this, slot: &QToolButtonPrivate::updateButtonDown);
791 if (menuButtonDown) {
792 // The menu was empty, it didn't actually show up, so it was never hidden either
793 updateButtonDown();
794 }
795
796 if (mustDeleteActualMenu) {
797 delete actualMenu;
798 } else {
799 QObjectPrivate::disconnect(sender: actualMenu, signal: &QMenu::triggered,
800 receiverPrivate: this, slot: &QToolButtonPrivate::onMenuTriggered);
801 }
802
803 actionsCopy.clear();
804
805 if (repeat)
806 q->setAutoRepeat(true);
807}
808
809void QToolButtonPrivate::updateButtonDown()
810{
811 Q_Q(QToolButton);
812 menuButtonDown = false;
813 if (q->isDown())
814 q->setDown(false);
815 else
816 q->repaint();
817}
818
819void QToolButtonPrivate::onMenuTriggered(QAction *action)
820{
821 Q_Q(QToolButton);
822 if (action && !actionsCopy.contains(t: action))
823 emit q->triggered(action);
824}
825
826/*! \enum QToolButton::ToolButtonPopupMode
827
828 Describes how a menu should be popped up for tool buttons that has
829 a menu set or contains a list of actions.
830
831 \value DelayedPopup After pressing and holding the tool button
832 down for a certain amount of time (the timeout is style dependent,
833 see QStyle::SH_ToolButton_PopupDelay), the menu is displayed. A
834 typical application example is the "back" button in some web
835 browsers's tool bars. If the user clicks it, the browser simply
836 browses back to the previous page. If the user presses and holds
837 the button down for a while, the tool button shows a menu
838 containing the current history list
839
840 \value MenuButtonPopup In this mode the tool button displays a
841 special arrow to indicate that a menu is present. The menu is
842 displayed when the arrow part of the button is pressed.
843
844 \value InstantPopup The menu is displayed, without delay, when
845 the tool button is pressed. In this mode, the button's own action
846 is not triggered.
847*/
848
849/*!
850 \property QToolButton::popupMode
851 \brief describes the way that popup menus are used with tool buttons
852
853 By default, this property is set to \l DelayedPopup.
854*/
855
856void QToolButton::setPopupMode(ToolButtonPopupMode mode)
857{
858 Q_D(QToolButton);
859 d->popupMode = mode;
860}
861
862QToolButton::ToolButtonPopupMode QToolButton::popupMode() const
863{
864 Q_D(const QToolButton);
865 return d->popupMode;
866}
867#endif
868
869/*!
870 \property QToolButton::autoRaise
871 \brief whether auto-raising is enabled or not.
872
873 The default is disabled (i.e. false).
874
875 This property is currently ignored on \macos when using QMacStyle.
876*/
877void QToolButton::setAutoRaise(bool enable)
878{
879 Q_D(QToolButton);
880 d->autoRaise = enable;
881
882 update();
883}
884
885bool QToolButton::autoRaise() const
886{
887 Q_D(const QToolButton);
888 return d->autoRaise;
889}
890
891/*!
892 Sets the default action to \a action.
893
894 If a tool button has a default action, the action defines the
895 following properties of the button:
896
897 \list
898 \li \l {QAbstractButton::}{checkable}
899 \li \l {QAbstractButton::}{checked}
900 \li \l {QWidget::}{enabled}
901 \li \l {QWidget::}{font}
902 \li \l {QAbstractButton::}{icon}
903 \li \l {QToolButton::}{popupMode} (assuming the action has a menu)
904 \li \l {QWidget::}{statusTip}
905 \li \l {QAbstractButton::}{text}
906 \li \l {QWidget::}{toolTip}
907 \li \l {QWidget::}{whatsThis}
908 \endlist
909
910 Other properties, such as \l autoRepeat, are not affected
911 by actions.
912 */
913void QToolButton::setDefaultAction(QAction *action)
914{
915 Q_D(QToolButton);
916#if QT_CONFIG(menu)
917 bool hadMenu = false;
918 hadMenu = d->hasMenu();
919#endif
920 d->defaultAction = action;
921 if (!action)
922 return;
923 if (!actions().contains(t: action))
924 addAction(action);
925 QString buttonText = action->iconText();
926 // If iconText() is generated from text(), we need to escape any '&'s so they
927 // don't turn into shortcuts
928 if (QActionPrivate::get(q: action)->iconText.isEmpty())
929 buttonText.replace(before: "&"_L1, after: "&&"_L1);
930 setText(buttonText);
931 setIcon(action->icon());
932#if QT_CONFIG(tooltip)
933 setToolTip(action->toolTip());
934#endif
935#if QT_CONFIG(statustip)
936 setStatusTip(action->statusTip());
937#endif
938#if QT_CONFIG(whatsthis)
939 setWhatsThis(action->whatsThis());
940#endif
941#if QT_CONFIG(menu)
942 if (action->menu() && !hadMenu) {
943 // new 'default' popup mode defined introduced by tool bar. We
944 // should have changed QToolButton's default instead. Do that
945 // in 4.2.
946 setPopupMode(QToolButton::MenuButtonPopup);
947 }
948#endif
949 setCheckable(action->isCheckable());
950 setChecked(action->isChecked());
951 setEnabled(action->isEnabled());
952 if (action->d_func()->fontSet)
953 setFont(action->font());
954}
955
956
957/*!
958 Returns the default action.
959
960 \sa setDefaultAction()
961 */
962QAction *QToolButton::defaultAction() const
963{
964 Q_D(const QToolButton);
965 return d->defaultAction;
966}
967
968/*!
969 \reimp
970 */
971void QToolButton::checkStateSet()
972{
973 Q_D(QToolButton);
974 if (d->defaultAction && d->defaultAction->isCheckable())
975 d->defaultAction->setChecked(isChecked());
976}
977
978/*!
979 \reimp
980 */
981void QToolButton::nextCheckState()
982{
983 Q_D(QToolButton);
984 if (!d->defaultAction)
985 QAbstractButton::nextCheckState();
986 else
987 d->defaultAction->trigger();
988}
989
990/*! \reimp */
991bool QToolButton::event(QEvent *event)
992{
993 switch(event->type()) {
994 case QEvent::HoverEnter:
995 case QEvent::HoverLeave:
996 case QEvent::HoverMove:
997 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
998 d_func()->updateHoverControl(pos: he->position().toPoint());
999 break;
1000 default:
1001 break;
1002 }
1003 return QAbstractButton::event(e: event);
1004}
1005
1006QT_END_NAMESPACE
1007
1008#include "moc_qtoolbutton.cpp"
1009

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