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