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