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