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 "qapplication.h"
5#include "qbitmap.h"
6#if QT_CONFIG(dialog)
7#include <private/qdialog_p.h>
8#endif
9#include "qdrawutil.h"
10#include "qevent.h"
11#include "qfontmetrics.h"
12#include "qstylepainter.h"
13#include "qpixmap.h"
14#include "qpointer.h"
15#include "qpushbutton.h"
16#include "qstyle.h"
17#include "qstyleoption.h"
18#if QT_CONFIG(toolbar)
19#include "qtoolbar.h"
20#endif
21#include "qdebug.h"
22#include "qlayoutitem.h"
23#if QT_CONFIG(dialogbuttonbox)
24#include "qdialogbuttonbox.h"
25#endif
26
27#if QT_CONFIG(accessibility)
28#include "qaccessible.h"
29#endif
30
31#if QT_CONFIG(menu)
32#include "qmenu.h"
33#include "private/qmenu_p.h"
34#endif
35#include "private/qpushbutton_p.h"
36
37QT_BEGIN_NAMESPACE
38
39
40/*!
41 \class QPushButton
42 \brief The QPushButton widget provides a command button.
43
44 \ingroup basicwidgets
45 \inmodule QtWidgets
46
47 \image windows-pushbutton.png
48
49 The push button, or command button, is perhaps the most commonly
50 used widget in any graphical user interface. Push (click) a button
51 to command the computer to perform some action, or to answer a
52 question. Typical buttons are OK, Apply, Cancel, Close, Yes, No
53 and Help.
54
55 A command button is rectangular and typically displays a text
56 label describing its action. A shortcut key can be specified by
57 preceding the preferred character with an ampersand in the
58 text. For example:
59
60 \snippet code/src_gui_widgets_qpushbutton.cpp 0
61
62 In this example the shortcut is \e{Alt+D}. See the \l
63 {QShortcut#mnemonic}{QShortcut} documentation for details (to
64 display an actual ampersand, use '&&').
65
66 Push buttons display a textual label, and optionally a small
67 icon. These can be set using the constructors and changed later
68 using setText() and setIcon(). If the button is disabled, the
69 appearance of the text and icon will be manipulated with respect
70 to the GUI style to make the button look "disabled".
71
72 A push button emits the signal clicked() when it is activated by
73 the mouse, the Spacebar or by a keyboard shortcut. Connect to
74 this signal to perform the button's action. Push buttons also
75 provide less commonly used signals, for example pressed() and
76 released().
77
78 Command buttons in dialogs are by default auto-default buttons,
79 i.e., they become the default push button automatically when they
80 receive the keyboard input focus. A default button is a push
81 button that is activated when the user presses the Enter or Return
82 key in a dialog. You can change this with setAutoDefault(). Note
83 that auto-default buttons reserve a little extra space which is
84 necessary to draw a default-button indicator. If you do not want
85 this space around your buttons, call setAutoDefault(false).
86
87 Being so central, the button widget has grown to accommodate a
88 great many variations in the past decade. The Microsoft style
89 guide now shows about ten different states of Windows push buttons
90 and the text implies that there are dozens more when all the
91 combinations of features are taken into consideration.
92
93 The most important modes or states are:
94 \list
95 \li Available or not (grayed out, disabled).
96 \li Standard push button, toggling push button or menu button.
97 \li On or off (only for toggling push buttons).
98 \li Default or normal. The default button in a dialog can generally
99 be "clicked" using the Enter or Return key.
100 \li Auto-repeat or not.
101 \li Pressed down or not.
102 \endlist
103
104 As a general rule, use a push button when the application or
105 dialog window performs an action when the user clicks on it (such
106 as Apply, Cancel, Close and Help) \e and when the widget is
107 supposed to have a wide, rectangular shape with a text label.
108 Small, typically square buttons that change the state of the
109 window rather than performing an action (such as the buttons in
110 the top-right corner of the QFileDialog) are not command buttons,
111 but tool buttons. Qt provides a special class (QToolButton) for
112 these buttons.
113
114 If you need toggle behavior (see setCheckable()) or a button
115 that auto-repeats the activation signal when being pushed down
116 like the arrows in a scroll bar (see setAutoRepeat()), a command
117 button is probably not what you want. When in doubt, use a tool
118 button.
119
120 \note On \macos when a push button's width becomes smaller than 50 or
121 its height becomes smaller than 30, the button's corners are
122 changed from round to square. Use the setMinimumSize()
123 function to prevent this behavior.
124
125 A variation of a command button is a menu button. These provide
126 not just one command, but several, since when they are clicked
127 they pop up a menu of options. Use the method setMenu() to
128 associate a popup menu with a push button.
129
130 Other classes of buttons are option buttons (see QRadioButton) and
131 check boxes (see QCheckBox).
132
133
134 In Qt, the QAbstractButton base class provides most of the modes
135 and other API, and QPushButton provides GUI logic.
136 See QAbstractButton for more information about the API.
137
138 \sa QToolButton, QRadioButton, QCheckBox
139*/
140
141/*!
142 \property QPushButton::autoDefault
143 \brief whether the push button is an auto default button
144
145 If this property is set to true then the push button is an auto
146 default button.
147
148 In some GUI styles a default button is drawn with an extra frame
149 around it, up to 3 pixels or more. Qt automatically keeps this
150 space free around auto-default buttons, i.e., auto-default buttons
151 may have a slightly larger size hint.
152
153 This property's default is true for buttons that have a QDialog
154 parent; otherwise it defaults to false.
155
156 See the \l default property for details of how \l default and
157 auto-default interact.
158*/
159
160/*!
161 \property QPushButton::default
162 \brief whether the push button is the default button
163
164 Default and autodefault buttons decide what happens when the user
165 presses enter in a dialog.
166
167 A button with this property set to true (i.e., the dialog's
168 \e default button,) will automatically be pressed when the user presses enter,
169 with one exception: if an \a autoDefault button currently has focus, the autoDefault
170 button is pressed. When the dialog has \l autoDefault buttons but no default button,
171 pressing enter will press either the \l autoDefault button that currently has focus, or if no
172 button has focus, the next \l autoDefault button in the focus chain.
173
174 In a dialog, only one push button at a time can be the default
175 button. This button is then displayed with an additional frame
176 (depending on the GUI style).
177
178 The default button behavior is provided only in dialogs. Buttons
179 can always be clicked from the keyboard by pressing Spacebar when
180 the button has focus.
181
182 If the default property is set to false on the current default button
183 while the dialog is visible, a new default will automatically be
184 assigned the next time a push button in the dialog receives focus.
185
186 This property's default is false.
187*/
188
189/*!
190 \property QPushButton::flat
191 \brief whether the button border is raised
192
193 This property's default is false. If this property is set, most
194 styles will not paint the button background unless the button is
195 being pressed. setAutoFillBackground() can be used to ensure that
196 the background is filled using the QPalette::Button brush.
197*/
198
199/*!
200 Constructs a push button with no text and a \a parent.
201*/
202
203QPushButton::QPushButton(QWidget *parent)
204 : QAbstractButton(*new QPushButtonPrivate, parent)
205{
206 Q_D(QPushButton);
207 d->init();
208}
209
210/*!
211 Constructs a push button with the parent \a parent and the text \a
212 text.
213*/
214
215QPushButton::QPushButton(const QString &text, QWidget *parent)
216 : QPushButton(parent)
217{
218 setText(text);
219}
220
221
222/*!
223 Constructs a push button with an \a icon and a \a text, and a \a parent.
224
225 Note that you can also pass a QPixmap object as an icon (thanks to
226 the implicit type conversion provided by C++).
227
228*/
229QPushButton::QPushButton(const QIcon& icon, const QString &text, QWidget *parent)
230 : QPushButton(*new QPushButtonPrivate, parent)
231{
232 setText(text);
233 setIcon(icon);
234}
235
236/*! \internal
237 */
238QPushButton::QPushButton(QPushButtonPrivate &dd, QWidget *parent)
239 : QAbstractButton(dd, parent)
240{
241 Q_D(QPushButton);
242 d->init();
243}
244
245/*!
246 Destroys the push button.
247*/
248QPushButton::~QPushButton()
249{
250}
251
252#if QT_CONFIG(dialog)
253QDialog *QPushButtonPrivate::dialogParent() const
254{
255 Q_Q(const QPushButton);
256 const QWidget *p = q;
257 while (p && !p->isWindow()) {
258 p = p->parentWidget();
259 if (const QDialog *dialog = qobject_cast<const QDialog *>(object: p))
260 return const_cast<QDialog *>(dialog);
261 }
262 return nullptr;
263}
264#endif
265
266/*!
267 Initialize \a option with the values from this QPushButton. This method is useful
268 for subclasses when they need a QStyleOptionButton, but don't want to fill
269 in all the information themselves.
270
271 \sa QStyleOption::initFrom()
272*/
273void QPushButton::initStyleOption(QStyleOptionButton *option) const
274{
275 if (!option)
276 return;
277
278 Q_D(const QPushButton);
279 option->initFrom(w: this);
280 option->features = QStyleOptionButton::None;
281 if (d->flat)
282 option->features |= QStyleOptionButton::Flat;
283#if QT_CONFIG(menu)
284 if (d->menu)
285 option->features |= QStyleOptionButton::HasMenu;
286#endif
287 if (autoDefault())
288 option->features |= QStyleOptionButton::AutoDefaultButton;
289 if (d->defaultButton)
290 option->features |= QStyleOptionButton::DefaultButton;
291 if (d->down || d->menuOpen)
292 option->state |= QStyle::State_Sunken;
293 if (d->checked)
294 option->state |= QStyle::State_On;
295 if (!d->flat && !d->down)
296 option->state |= QStyle::State_Raised;
297 if (underMouse() && hasMouseTracking())
298 option->state.setFlag(flag: QStyle::State_MouseOver, on: d->hovering);
299 option->text = d->text;
300 option->icon = d->icon;
301 option->iconSize = iconSize();
302}
303
304void QPushButton::setAutoDefault(bool enable)
305{
306 Q_D(QPushButton);
307 uint state = enable ? QPushButtonPrivate::On : QPushButtonPrivate::Off;
308 if (d->autoDefault != QPushButtonPrivate::Auto && d->autoDefault == state)
309 return;
310 d->autoDefault = state;
311 d->sizeHint = QSize();
312 update();
313 updateGeometry();
314}
315
316bool QPushButton::autoDefault() const
317{
318 Q_D(const QPushButton);
319 if (d->autoDefault == QPushButtonPrivate::Auto)
320 return ( d->dialogParent() != nullptr );
321 return d->autoDefault;
322}
323
324void QPushButton::setDefault(bool enable)
325{
326 Q_D(QPushButton);
327 if (d->defaultButton == enable)
328 return;
329 d->defaultButton = enable;
330#if QT_CONFIG(dialog)
331 if (d->defaultButton) {
332 if (QDialog *dlg = d->dialogParent())
333 dlg->d_func()->setMainDefault(this);
334 }
335#endif
336 update();
337#if QT_CONFIG(accessibility)
338 QAccessible::State s;
339 s.defaultButton = true;
340 QAccessibleStateChangeEvent event(this, s);
341 QAccessible::updateAccessibility(event: &event);
342#endif
343}
344
345bool QPushButton::isDefault() const
346{
347 Q_D(const QPushButton);
348 return d->defaultButton;
349}
350
351/*!
352 \reimp
353*/
354QSize QPushButton::sizeHint() const
355{
356 Q_D(const QPushButton);
357 if (d->sizeHint.isValid() && d->lastAutoDefault == autoDefault())
358 return d->sizeHint;
359 d->lastAutoDefault = autoDefault();
360 ensurePolished();
361
362 int w = 0, h = 0;
363
364 QStyleOptionButton opt;
365 initStyleOption(option: &opt);
366
367 // calculate contents size...
368#if !defined(QT_NO_ICON) && QT_CONFIG(dialogbuttonbox)
369 bool showButtonBoxIcons = qobject_cast<QDialogButtonBox*>(object: parentWidget())
370 && style()->styleHint(stylehint: QStyle::SH_DialogButtonBox_ButtonsHaveIcons);
371
372 if (!icon().isNull() || showButtonBoxIcons) {
373 int ih = opt.iconSize.height();
374 int iw = opt.iconSize.width() + 4;
375 w += iw;
376 h = qMax(a: h, b: ih);
377 }
378#endif
379 QString s(text());
380 bool empty = s.isEmpty();
381 if (empty)
382 s = QStringLiteral("XXXX");
383 QFontMetrics fm = fontMetrics();
384 QSize sz = fm.size(flags: Qt::TextShowMnemonic, str: s);
385 if (!empty || !w)
386 w += sz.width();
387 if (!empty || !h)
388 h = qMax(a: h, b: sz.height());
389 opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
390#if QT_CONFIG(menu)
391 if (menu())
392 w += style()->pixelMetric(metric: QStyle::PM_MenuButtonIndicator, option: &opt, widget: this);
393#endif
394 d->sizeHint = style()->sizeFromContents(ct: QStyle::CT_PushButton, opt: &opt, contentsSize: QSize(w, h), w: this);
395 return d->sizeHint;
396}
397
398/*!
399 \reimp
400 */
401QSize QPushButton::minimumSizeHint() const
402{
403 return sizeHint();
404}
405
406
407/*!\reimp
408*/
409void QPushButton::paintEvent(QPaintEvent *)
410{
411 QStylePainter p(this);
412 QStyleOptionButton option;
413 initStyleOption(option: &option);
414 p.drawControl(ce: QStyle::CE_PushButton, opt: option);
415}
416
417
418/*! \reimp */
419void QPushButton::keyPressEvent(QKeyEvent *e)
420{
421 Q_D(QPushButton);
422 switch (e->key()) {
423 case Qt::Key_Enter:
424 case Qt::Key_Return:
425 if (autoDefault() || d->defaultButton) {
426 click();
427 break;
428 }
429 Q_FALLTHROUGH();
430 default:
431 QAbstractButton::keyPressEvent(e);
432 }
433}
434
435/*!
436 \reimp
437*/
438void QPushButton::focusInEvent(QFocusEvent *e)
439{
440 Q_D(QPushButton);
441 if (e->reason() != Qt::PopupFocusReason && autoDefault() && !d->defaultButton) {
442 d->defaultButton = true;
443#if QT_CONFIG(dialog)
444 QDialog *dlg = qobject_cast<QDialog*>(object: window());
445 if (dlg)
446 dlg->d_func()->setDefault(this);
447#endif
448 }
449 QAbstractButton::focusInEvent(e);
450}
451
452/*!
453 \reimp
454*/
455void QPushButton::focusOutEvent(QFocusEvent *e)
456{
457 Q_D(QPushButton);
458 if (e->reason() != Qt::PopupFocusReason && autoDefault() && d->defaultButton) {
459#if QT_CONFIG(dialog)
460 QDialog *dlg = qobject_cast<QDialog*>(object: window());
461 if (dlg)
462 dlg->d_func()->setDefault(nullptr);
463 else
464 d->defaultButton = false;
465#endif
466 }
467
468 QAbstractButton::focusOutEvent(e);
469#if QT_CONFIG(menu)
470 if (d->menu && d->menu->isVisible()) // restore pressed status
471 setDown(true);
472#endif
473}
474
475/*!
476 \reimp
477*/
478void QPushButton::mouseMoveEvent(QMouseEvent *e)
479{
480 Q_D(QPushButton);
481
482 if (testAttribute(attribute: Qt::WA_Hover)) {
483 bool hit = false;
484 if (underMouse())
485 hit = hitButton(pos: e->position().toPoint());
486
487 if (hit != d->hovering) {
488 update(rect());
489 d->hovering = hit;
490 }
491 }
492
493 QAbstractButton::mouseMoveEvent(e);
494}
495
496/*!
497 \reimp
498*/
499bool QPushButton::hitButton(const QPoint &pos) const
500{
501 QStyleOptionButton option;
502 initStyleOption(option: &option);
503 const QRect bevel = style()->subElementRect(subElement: QStyle::SE_PushButtonBevel, option: &option, widget: this);
504 return bevel.contains(p: pos);
505}
506
507#if QT_CONFIG(menu)
508/*!
509 Associates the popup menu \a menu with this push button. This
510 turns the button into a menu button, which in some styles will
511 produce a small triangle to the right of the button's text.
512
513 Ownership of the menu is \e not transferred to the push button.
514
515 \image fusion-pushbutton-menu.png Screenshot of a Fusion style push button with popup menu.
516 A push button with popup menus shown in the \l{Qt Widget Gallery}
517 {Fusion widget style}.
518
519 \sa menu()
520*/
521void QPushButton::setMenu(QMenu* menu)
522{
523 Q_D(QPushButton);
524 if (menu == d->menu)
525 return;
526
527 if (menu && !d->menu) {
528 connect(sender: this, SIGNAL(pressed()), receiver: this, SLOT(_q_popupPressed()), Qt::UniqueConnection);
529 }
530 if (d->menu)
531 removeAction(action: d->menu->menuAction());
532 d->menu = menu;
533 if (d->menu)
534 addAction(action: d->menu->menuAction());
535
536 d->resetLayoutItemMargins();
537 d->sizeHint = QSize();
538 update();
539 updateGeometry();
540}
541
542/*!
543 Returns the button's associated popup menu or \nullptr if no popup
544 menu has been set.
545
546 \sa setMenu()
547*/
548QMenu* QPushButton::menu() const
549{
550 Q_D(const QPushButton);
551 return d->menu;
552}
553
554/*!
555 Shows (pops up) the associated popup menu. If there is no such
556 menu, this function does nothing. This function does not return
557 until the popup menu has been closed by the user.
558*/
559void QPushButton::showMenu()
560{
561 Q_D(QPushButton);
562 if (!d || !d->menu)
563 return;
564 setDown(true);
565 d->_q_popupPressed();
566}
567
568void QPushButtonPrivate::_q_popupPressed()
569{
570 Q_Q(QPushButton);
571 if (!down || !menu)
572 return;
573
574 menu->setNoReplayFor(q);
575
576 QPoint menuPos = adjustedMenuPosition();
577
578 QMenuPrivate::get(m: menu)->causedPopup.widget = q;
579
580 //Because of a delay in menu effects, we must keep track of the
581 //menu visibility to avoid flicker on button release
582 menuOpen = true;
583 QObject::connect(sender: menu, signal: &QMenu::aboutToHide,
584 context: q, slot: [q]{ q->setDown(false); }, type: Qt::SingleShotConnection);
585 menu->popup(pos: menuPos);
586}
587
588QPoint QPushButtonPrivate::adjustedMenuPosition()
589{
590 Q_Q(QPushButton);
591
592 bool horizontal = true;
593#if QT_CONFIG(toolbar)
594 QToolBar *tb = qobject_cast<QToolBar*>(object: parent);
595 if (tb && tb->orientation() == Qt::Vertical)
596 horizontal = false;
597#endif
598
599 QWidgetItem item(q);
600 QRect rect = item.geometry();
601 rect.setRect(ax: rect.x() - q->x(), ay: rect.y() - q->y(), aw: rect.width(), ah: rect.height());
602
603 QSize menuSize = menu->sizeHint();
604 QPoint globalPos = q->mapToGlobal(rect.topLeft());
605 int x = globalPos.x();
606 int y = globalPos.y();
607 const QRect availableGeometry = QWidgetPrivate::availableScreenGeometry(widget: q, globalPosition: q->mapToGlobal(rect.center()));
608 if (horizontal) {
609 if (globalPos.y() + rect.height() + menuSize.height() <= availableGeometry.bottom()) {
610 y += rect.height();
611 } else if (globalPos.y() - menuSize.height() >= availableGeometry.y()) {
612 y -= menuSize.height();
613 }
614 if (q->layoutDirection() == Qt::RightToLeft)
615 x += rect.width() - menuSize.width();
616 } else {
617 if (globalPos.x() + rect.width() + menu->sizeHint().width() <= availableGeometry.right()) {
618 x += rect.width();
619 } else if (globalPos.x() - menuSize.width() >= availableGeometry.x()) {
620 x -= menuSize.width();
621 }
622 }
623
624 return QPoint(x,y);
625}
626
627#endif // QT_CONFIG(menu)
628
629void QPushButtonPrivate::init()
630{
631 Q_Q(QPushButton);
632 q->setAttribute(Qt::WA_MacShowFocusRect);
633 resetLayoutItemMargins();
634}
635
636void QPushButtonPrivate::resetLayoutItemMargins()
637{
638 Q_Q(QPushButton);
639 QStyleOptionButton opt;
640 q->initStyleOption(option: &opt);
641 setLayoutItemMargins(element: QStyle::SE_PushButtonLayoutItem, opt: &opt);
642}
643
644void QPushButton::setFlat(bool flat)
645{
646 Q_D(QPushButton);
647 if (d->flat == flat)
648 return;
649 d->flat = flat;
650 d->resetLayoutItemMargins();
651 d->sizeHint = QSize();
652 update();
653 updateGeometry();
654}
655
656bool QPushButton::isFlat() const
657{
658 Q_D(const QPushButton);
659 return d->flat;
660}
661
662/*! \reimp */
663bool QPushButton::event(QEvent *e)
664{
665 Q_D(QPushButton);
666 if (e->type() == QEvent::ParentChange) {
667#if QT_CONFIG(dialog)
668 if (QDialog *dialog = d->dialogParent()) {
669 if (d->defaultButton)
670 dialog->d_func()->setMainDefault(this);
671 }
672#endif
673 } else if (e->type() == QEvent::StyleChange
674#ifdef Q_OS_MAC
675 || e->type() == QEvent::MacSizeChange
676#endif
677 ) {
678 d->resetLayoutItemMargins();
679 updateGeometry();
680 } else if (e->type() == QEvent::PolishRequest) {
681 updateGeometry();
682 }
683 return QAbstractButton::event(e);
684}
685
686QT_END_NAMESPACE
687
688#include "moc_qpushbutton.cpp"
689

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