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