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 <qdrawutil.h>
44#include <qevent.h>
45#include <qicon.h>
46#include <qpainter.h>
47#include <qpointer.h>
48#include <qstyle.h>
49#include <qstyleoption.h>
50#if QT_CONFIG(tooltip)
51#include <qtooltip.h>
52#endif
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
68QT_BEGIN_NAMESPACE
69
70class QToolButtonPrivate : public QAbstractButtonPrivate
71{
72 Q_DECLARE_PUBLIC(QToolButton)
73public:
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)
107bool 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*/
191QToolButton::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
202void 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*/
240void 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
326QToolButton::~QToolButton()
327{
328}
329
330/*!
331 \reimp
332*/
333QSize 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 return d->sizeHint;
374}
375
376/*!
377 \reimp
378 */
379QSize QToolButton::minimumSizeHint() const
380{
381 return sizeHint();
382}
383
384/*!
385 \property QToolButton::toolButtonStyle
386 \brief whether the tool button displays an icon only, text only,
387 or text beside/below the icon.
388
389 The default is Qt::ToolButtonIconOnly.
390
391 To have the style of toolbuttons follow the system settings, set this property to Qt::ToolButtonFollowStyle.
392 On Unix, the user settings from the desktop environment will be used.
393 On other platforms, Qt::ToolButtonFollowStyle means icon only.
394
395 QToolButton automatically connects this slot to the relevant
396 signal in the QMainWindow in which is resides.
397*/
398
399/*!
400 \property QToolButton::arrowType
401 \brief whether the button displays an arrow instead of a normal icon
402
403 This displays an arrow as the icon for the QToolButton.
404
405 By default, this property is set to Qt::NoArrow.
406*/
407
408Qt::ToolButtonStyle QToolButton::toolButtonStyle() const
409{
410 Q_D(const QToolButton);
411 return d->toolButtonStyle;
412}
413
414Qt::ArrowType QToolButton::arrowType() const
415{
416 Q_D(const QToolButton);
417 return d->arrowType;
418}
419
420
421void QToolButton::setToolButtonStyle(Qt::ToolButtonStyle style)
422{
423 Q_D(QToolButton);
424 if (d->toolButtonStyle == style)
425 return;
426
427 d->toolButtonStyle = style;
428 d->sizeHint = QSize();
429 updateGeometry();
430 if (isVisible()) {
431 update();
432 }
433}
434
435void QToolButton::setArrowType(Qt::ArrowType type)
436{
437 Q_D(QToolButton);
438 if (d->arrowType == type)
439 return;
440
441 d->arrowType = type;
442 d->sizeHint = QSize();
443 updateGeometry();
444 if (isVisible()) {
445 update();
446 }
447}
448
449/*!
450 \fn void QToolButton::paintEvent(QPaintEvent *event)
451
452 Paints the button in response to the paint \a event.
453*/
454void QToolButton::paintEvent(QPaintEvent *)
455{
456 QStylePainter p(this);
457 QStyleOptionToolButton opt;
458 initStyleOption(&opt);
459 p.drawComplexControl(QStyle::CC_ToolButton, opt);
460}
461
462/*!
463 \reimp
464 */
465void QToolButton::actionEvent(QActionEvent *event)
466{
467 Q_D(QToolButton);
468 auto action = static_cast<QAction *>(event->action());
469 switch (event->type()) {
470 case QEvent::ActionChanged:
471 if (action == d->defaultAction)
472 setDefaultAction(action); // update button state
473 break;
474 case QEvent::ActionAdded:
475 connect(action, SIGNAL(triggered()), this, SLOT(_q_actionTriggered()));
476 break;
477 case QEvent::ActionRemoved:
478 if (d->defaultAction == action)
479 d->defaultAction = nullptr;
480#if QT_CONFIG(menu)
481 if (action == d->menuAction)
482 d->menuAction = nullptr;
483#endif
484 action->disconnect(this);
485 break;
486 default:
487 ;
488 }
489 QAbstractButton::actionEvent(event);
490}
491
492QStyle::SubControl QToolButtonPrivate::newHoverControl(const QPoint &pos)
493{
494 Q_Q(QToolButton);
495 QStyleOptionToolButton opt;
496 q->initStyleOption(&opt);
497 opt.subControls = QStyle::SC_All;
498 hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ToolButton, &opt, pos, q);
499 if (hoverControl == QStyle::SC_None)
500 hoverRect = QRect();
501 else
502 hoverRect = q->style()->subControlRect(QStyle::CC_ToolButton, &opt, hoverControl, q);
503 return hoverControl;
504}
505
506bool QToolButtonPrivate::updateHoverControl(const QPoint &pos)
507{
508 Q_Q(QToolButton);
509 QRect lastHoverRect = hoverRect;
510 QStyle::SubControl lastHoverControl = hoverControl;
511 bool doesHover = q->testAttribute(Qt::WA_Hover);
512 if (lastHoverControl != newHoverControl(pos) && doesHover) {
513 q->update(lastHoverRect);
514 q->update(hoverRect);
515 return true;
516 }
517 return !doesHover;
518}
519
520void QToolButtonPrivate::_q_actionTriggered()
521{
522 Q_Q(QToolButton);
523 if (QAction *action = qobject_cast<QAction *>(q->sender()))
524 emit q->triggered(action);
525}
526
527/*!
528 \reimp
529 */
530void QToolButton::enterEvent(QEnterEvent * e)
531{
532 Q_D(QToolButton);
533 if (d->autoRaise)
534 update();
535 if (d->defaultAction)
536 d->defaultAction->hover();
537 QAbstractButton::enterEvent(e);
538}
539
540
541/*!
542 \reimp
543 */
544void QToolButton::leaveEvent(QEvent * e)
545{
546 Q_D(QToolButton);
547 if (d->autoRaise)
548 update();
549
550 QAbstractButton::leaveEvent(e);
551}
552
553
554/*!
555 \reimp
556 */
557void QToolButton::timerEvent(QTimerEvent *e)
558{
559#if QT_CONFIG(menu)
560 Q_D(QToolButton);
561 if (e->timerId() == d->popupTimer.timerId()) {
562 d->popupTimerDone();
563 return;
564 }
565#endif
566 QAbstractButton::timerEvent(e);
567}
568
569
570/*!
571 \reimp
572*/
573void QToolButton::changeEvent(QEvent *e)
574{
575#if QT_CONFIG(toolbar)
576 Q_D(QToolButton);
577 if (e->type() == QEvent::ParentChange) {
578 if (qobject_cast<QToolBar*>(parentWidget()))
579 d->autoRaise = true;
580 } else if (e->type() == QEvent::StyleChange
581#ifdef Q_OS_MAC
582 || e->type() == QEvent::MacSizeChange
583#endif
584 ) {
585 d->delay = style()->styleHint(QStyle::SH_ToolButton_PopupDelay, nullptr, this);
586 d->setLayoutItemMargins(QStyle::SE_ToolButtonLayoutItem);
587 }
588#endif
589 QAbstractButton::changeEvent(e);
590}
591
592/*!
593 \reimp
594*/
595void QToolButton::mousePressEvent(QMouseEvent *e)
596{
597 Q_D(QToolButton);
598#if QT_CONFIG(menu)
599 QStyleOptionToolButton opt;
600 initStyleOption(&opt);
601 if (e->button() == Qt::LeftButton && (d->popupMode == MenuButtonPopup)) {
602 QRect popupr = style()->subControlRect(QStyle::CC_ToolButton, &opt,
603 QStyle::SC_ToolButtonMenu, this);
604 if (popupr.isValid() && popupr.contains(e->position().toPoint())) {
605 d->buttonPressed = QToolButtonPrivate::MenuButtonPressed;
606 showMenu();
607 return;
608 }
609 }
610#endif
611 d->buttonPressed = QToolButtonPrivate::ToolButtonPressed;
612 QAbstractButton::mousePressEvent(e);
613}
614
615/*!
616 \reimp
617*/
618void QToolButton::mouseReleaseEvent(QMouseEvent *e)
619{
620 Q_D(QToolButton);
621 QAbstractButton::mouseReleaseEvent(e);
622 d->buttonPressed = QToolButtonPrivate::NoButtonPressed;
623}
624
625/*!
626 \reimp
627*/
628bool QToolButton::hitButton(const QPoint &pos) const
629{
630 Q_D(const QToolButton);
631 if(QAbstractButton::hitButton(pos))
632 return (d->buttonPressed != QToolButtonPrivate::MenuButtonPressed);
633 return false;
634}
635
636
637#if QT_CONFIG(menu)
638/*!
639 Associates the given \a menu with this tool button.
640
641 The menu will be shown according to the button's \l popupMode.
642
643 Ownership of the menu is not transferred to the tool button.
644
645 \sa menu()
646*/
647void QToolButton::setMenu(QMenu* menu)
648{
649 Q_D(QToolButton);
650
651 if (d->menuAction == (menu ? menu->menuAction() : nullptr))
652 return;
653
654 if (d->menuAction)
655 removeAction(d->menuAction);
656
657 if (menu) {
658 d->menuAction = menu->menuAction();
659 addAction(d->menuAction);
660 } else {
661 d->menuAction = nullptr;
662 }
663
664 // changing the menu set may change the size hint, so reset it
665 d->sizeHint = QSize();
666 updateGeometry();
667 update();
668}
669
670/*!
671 Returns the associated menu, or \nullptr if no menu has been
672 defined.
673
674 \sa setMenu()
675*/
676QMenu* QToolButton::menu() const
677{
678 Q_D(const QToolButton);
679 if (d->menuAction)
680 return d->menuAction->menu();
681 return nullptr;
682}
683
684/*!
685 Shows (pops up) the associated popup menu. If there is no such
686 menu, this function does nothing. This function does not return
687 until the popup menu has been closed by the user.
688*/
689void QToolButton::showMenu()
690{
691 Q_D(QToolButton);
692 if (!d->hasMenu()) {
693 d->menuButtonDown = false;
694 return; // no menu to show
695 }
696 // prevent recursions spinning another event loop
697 if (d->menuButtonDown)
698 return;
699
700
701 d->menuButtonDown = true;
702 repaint();
703 d->popupTimer.stop();
704 d->popupTimerDone();
705}
706
707void QToolButtonPrivate::_q_buttonPressed()
708{
709 Q_Q(QToolButton);
710 if (!hasMenu())
711 return; // no menu to show
712 if (popupMode == QToolButton::MenuButtonPopup)
713 return;
714 else if (delay > 0 && popupMode == QToolButton::DelayedPopup)
715 popupTimer.start(delay, q);
716 else if (delay == 0 || popupMode == QToolButton::InstantPopup)
717 q->showMenu();
718}
719
720void QToolButtonPrivate::_q_buttonReleased()
721{
722 popupTimer.stop();
723}
724
725static QPoint positionMenu(const QToolButton *q, bool horizontal,
726 const QSize &sh)
727{
728 QPoint p;
729 const QRect rect = q->rect(); // Find screen via point in case of QGraphicsProxyWidget.
730 const QRect screen = QWidgetPrivate::availableScreenGeometry(q);
731 if (horizontal) {
732 if (q->isRightToLeft()) {
733 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
734 p = q->mapToGlobal(rect.bottomRight());
735 } else {
736 p = q->mapToGlobal(rect.topRight() - QPoint(0, sh.height()));
737 }
738 p.rx() -= sh.width();
739 } else {
740 if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.bottom()) {
741 p = q->mapToGlobal(rect.bottomLeft());
742 } else {
743 p = q->mapToGlobal(rect.topLeft() - QPoint(0, sh.height()));
744 }
745 }
746 } else {
747 if (q->isRightToLeft()) {
748 if (q->mapToGlobal(QPoint(rect.left(), 0)).x() - sh.width() <= screen.x()) {
749 p = q->mapToGlobal(rect.topRight());
750 } else {
751 p = q->mapToGlobal(rect.topLeft());
752 p.rx() -= sh.width();
753 }
754 } else {
755 if (q->mapToGlobal(QPoint(rect.right(), 0)).x() + sh.width() <= screen.right()) {
756 p = q->mapToGlobal(rect.topRight());
757 } else {
758 p = q->mapToGlobal(rect.topLeft() - QPoint(sh.width(), 0));
759 }
760 }
761 }
762 p.rx() = qMax(screen.left(), qMin(p.x(), screen.right() - sh.width()));
763 p.ry() += 1;
764 return p;
765}
766
767void QToolButtonPrivate::popupTimerDone()
768{
769 Q_Q(QToolButton);
770 popupTimer.stop();
771 if (!menuButtonDown && !down)
772 return;
773
774 menuButtonDown = true;
775 QPointer<QMenu> actualMenu;
776 bool mustDeleteActualMenu = false;
777 if (menuAction) {
778 actualMenu = menuAction->menu();
779 } else if (defaultAction && defaultAction->menu()) {
780 actualMenu = defaultAction->menu();
781 } else {
782 actualMenu = new QMenu(q);
783 mustDeleteActualMenu = true;
784 for (int i = 0; i < actions.size(); i++)
785 actualMenu->addAction(actions.at(i));
786 }
787 repeat = q->autoRepeat();
788 q->setAutoRepeat(false);
789 bool horizontal = true;
790#if QT_CONFIG(toolbar)
791 QToolBar *tb = qobject_cast<QToolBar*>(parent);
792 if (tb && tb->orientation() == Qt::Vertical)
793 horizontal = false;
794#endif
795 QPointer<QToolButton> that = q;
796 actualMenu->setNoReplayFor(q);
797 if (!mustDeleteActualMenu) //only if action are not in this widget
798 QObject::connect(actualMenu, SIGNAL(triggered(QAction*)), q, SLOT(_q_menuTriggered(QAction*)));
799 QObject::connect(actualMenu, SIGNAL(aboutToHide()), q, SLOT(_q_updateButtonDown()));
800 actualMenu->d_func()->causedPopup.widget = q;
801 actualMenu->d_func()->causedPopup.action = defaultAction;
802 actionsCopy = q->actions(); //(the list of action may be modified in slots)
803
804 // QTBUG-78966, Delay positioning until after aboutToShow().
805 auto positionFunction = [q, horizontal](const QSize &sizeHint) {
806 return positionMenu(q, horizontal, sizeHint); };
807 const auto initialPos = positionFunction(actualMenu->sizeHint());
808 actualMenu->d_func()->exec(initialPos, nullptr, positionFunction);
809
810 if (!that)
811 return;
812
813 QObject::disconnect(actualMenu, SIGNAL(aboutToHide()), q, SLOT(_q_updateButtonDown()));
814 if (mustDeleteActualMenu)
815 delete actualMenu;
816 else
817 QObject::disconnect(actualMenu, SIGNAL(triggered(QAction*)), q, SLOT(_q_menuTriggered(QAction*)));
818
819 actionsCopy.clear();
820
821 if (repeat)
822 q->setAutoRepeat(true);
823}
824
825void QToolButtonPrivate::_q_updateButtonDown()
826{
827 Q_Q(QToolButton);
828 menuButtonDown = false;
829 if (q->isDown())
830 q->setDown(false);
831 else
832 q->repaint();
833}
834
835void QToolButtonPrivate::_q_menuTriggered(QAction *action)
836{
837 Q_Q(QToolButton);
838 if (action && !actionsCopy.contains(action))
839 emit q->triggered(action);
840}
841
842/*! \enum QToolButton::ToolButtonPopupMode
843
844 Describes how a menu should be popped up for tool buttons that has
845 a menu set or contains a list of actions.
846
847 \value DelayedPopup After pressing and holding the tool button
848 down for a certain amount of time (the timeout is style dependent,
849 see QStyle::SH_ToolButton_PopupDelay), the menu is displayed. A
850 typical application example is the "back" button in some web
851 browsers's tool bars. If the user clicks it, the browser simply
852 browses back to the previous page. If the user presses and holds
853 the button down for a while, the tool button shows a menu
854 containing the current history list
855
856 \value MenuButtonPopup In this mode the tool button displays a
857 special arrow to indicate that a menu is present. The menu is
858 displayed when the arrow part of the button is pressed.
859
860 \value InstantPopup The menu is displayed, without delay, when
861 the tool button is pressed. In this mode, the button's own action
862 is not triggered.
863*/
864
865/*!
866 \property QToolButton::popupMode
867 \brief describes the way that popup menus are used with tool buttons
868
869 By default, this property is set to \l DelayedPopup.
870*/
871
872void QToolButton::setPopupMode(ToolButtonPopupMode mode)
873{
874 Q_D(QToolButton);
875 d->popupMode = mode;
876}
877
878QToolButton::ToolButtonPopupMode QToolButton::popupMode() const
879{
880 Q_D(const QToolButton);
881 return d->popupMode;
882}
883#endif
884
885/*!
886 \property QToolButton::autoRaise
887 \brief whether auto-raising is enabled or not.
888
889 The default is disabled (i.e. false).
890
891 This property is currently ignored on \macos when using QMacStyle.
892*/
893void QToolButton::setAutoRaise(bool enable)
894{
895 Q_D(QToolButton);
896 d->autoRaise = enable;
897
898 update();
899}
900
901bool QToolButton::autoRaise() const
902{
903 Q_D(const QToolButton);
904 return d->autoRaise;
905}
906
907/*!
908 Sets the default action to \a action.
909
910 If a tool button has a default action, the action defines the
911 following properties of the button:
912
913 \list
914 \li \l {QAbstractButton::}{checkable}
915 \li \l {QAbstractButton::}{checked}
916 \li \l {QWidget::}{enabled}
917 \li \l {QWidget::}{font}
918 \li \l {QAbstractButton::}{icon}
919 \li \l {QToolButton::}{popupMode} (assuming the action has a menu)
920 \li \l {QWidget::}{statusTip}
921 \li \l {QAbstractButton::}{text}
922 \li \l {QWidget::}{toolTip}
923 \li \l {QWidget::}{whatsThis}
924 \endlist
925
926 Other properties, such as \l autoRepeat, are not affected
927 by actions.
928 */
929void QToolButton::setDefaultAction(QAction *action)
930{
931 Q_D(QToolButton);
932#if QT_CONFIG(menu)
933 bool hadMenu = false;
934 hadMenu = d->hasMenu();
935#endif
936 d->defaultAction = action;
937 if (!action)
938 return;
939 if (!actions().contains(action))
940 addAction(action);
941 QString buttonText = action->iconText();
942 // If iconText() is generated from text(), we need to escape any '&'s so they
943 // don't turn into shortcuts
944 if (QActionPrivate::get(action)->iconText.isEmpty())
945 buttonText.replace(QLatin1String("&"), QLatin1String("&&"));
946 setText(buttonText);
947 setIcon(action->icon());
948#if QT_CONFIG(tooltip)
949 setToolTip(action->toolTip());
950#endif
951#if QT_CONFIG(statustip)
952 setStatusTip(action->statusTip());
953#endif
954#if QT_CONFIG(whatsthis)
955 setWhatsThis(action->whatsThis());
956#endif
957#if QT_CONFIG(menu)
958 if (action->menu() && !hadMenu) {
959 // new 'default' popup mode defined introduced by tool bar. We
960 // should have changed QToolButton's default instead. Do that
961 // in 4.2.
962 setPopupMode(QToolButton::MenuButtonPopup);
963 }
964#endif
965 setCheckable(action->isCheckable());
966 setChecked(action->isChecked());
967 setEnabled(action->isEnabled());
968 if (action->d_func()->fontSet)
969 setFont(action->font());
970}
971
972
973/*!
974 Returns the default action.
975
976 \sa setDefaultAction()
977 */
978QAction *QToolButton::defaultAction() const
979{
980 Q_D(const QToolButton);
981 return d->defaultAction;
982}
983
984
985
986/*!
987 \reimp
988 */
989void QToolButton::nextCheckState()
990{
991 Q_D(QToolButton);
992 if (!d->defaultAction)
993 QAbstractButton::nextCheckState();
994 else
995 d->defaultAction->trigger();
996}
997
998/*! \reimp */
999bool QToolButton::event(QEvent *event)
1000{
1001 switch(event->type()) {
1002 case QEvent::HoverEnter:
1003 case QEvent::HoverLeave:
1004 case QEvent::HoverMove:
1005 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
1006 d_func()->updateHoverControl(he->position().toPoint());
1007 break;
1008 default:
1009 break;
1010 }
1011 return QAbstractButton::event(event);
1012}
1013
1014QT_END_NAMESPACE
1015
1016#include "moc_qtoolbutton.cpp"
1017