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