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 option->state = d->styleButtonState(option->state);
294 option->text = d->text;
295 option->icon = d->icon;
296 option->iconSize = iconSize();
297}
298
299void QPushButton::setAutoDefault(bool enable)
300{
301 Q_D(QPushButton);
302 uint state = enable ? QPushButtonPrivate::On : QPushButtonPrivate::Off;
303 if (d->autoDefault != QPushButtonPrivate::Auto && d->autoDefault == state)
304 return;
305 d->autoDefault = state;
306 d->sizeHint = QSize();
307 update();
308 updateGeometry();
309}
310
311bool QPushButton::autoDefault() const
312{
313 Q_D(const QPushButton);
314 if (d->autoDefault == QPushButtonPrivate::Auto)
315 return ( d->dialogParent() != nullptr );
316 return d->autoDefault;
317}
318
319void QPushButton::setDefault(bool enable)
320{
321 Q_D(QPushButton);
322 if (d->defaultButton == enable)
323 return;
324 d->defaultButton = enable;
325#if QT_CONFIG(dialog)
326 if (d->defaultButton) {
327 if (QDialog *dlg = d->dialogParent())
328 dlg->d_func()->setMainDefault(this);
329 }
330#endif
331 update();
332#if QT_CONFIG(accessibility)
333 QAccessible::State s;
334 s.defaultButton = true;
335 QAccessibleStateChangeEvent event(this, s);
336 QAccessible::updateAccessibility(&event);
337#endif
338}
339
340bool QPushButton::isDefault() const
341{
342 Q_D(const QPushButton);
343 return d->defaultButton;
344}
345
346/*!
347 \reimp
348*/
349QSize QPushButton::sizeHint() const
350{
351 Q_D(const QPushButton);
352 if (d->sizeHint.isValid() && d->lastAutoDefault == autoDefault())
353 return d->sizeHint;
354 d->lastAutoDefault = autoDefault();
355 ensurePolished();
356
357 int w = 0, h = 0;
358
359 QStyleOptionButton opt;
360 initStyleOption(&opt);
361
362 // calculate contents size...
363#if !defined(QT_NO_ICON) && QT_CONFIG(dialogbuttonbox)
364 bool showButtonBoxIcons = qobject_cast<QDialogButtonBox*>(parentWidget())
365 && style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons, nullptr, this);
366
367 if (!icon().isNull() || showButtonBoxIcons) {
368 int ih = opt.iconSize.height();
369 int iw = opt.iconSize.width() + 4;
370 w += iw;
371 h = qMax(h, ih);
372 }
373#endif
374 QString s(text());
375 bool empty = s.isEmpty();
376 if (empty)
377 s = QStringLiteral("XXXX");
378 QFontMetrics fm = fontMetrics();
379 QSize sz = fm.size(Qt::TextShowMnemonic, s);
380 if (!empty || !w)
381 w += sz.width();
382 if (!empty || !h)
383 h = qMax(h, sz.height());
384 opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
385#if QT_CONFIG(menu)
386 if (menu())
387 w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
388#endif
389 d->sizeHint = style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this);
390 return d->sizeHint;
391}
392
393/*!
394 \reimp
395 */
396QSize QPushButton::minimumSizeHint() const
397{
398 return sizeHint();
399}
400
401
402/*!\reimp
403*/
404void QPushButton::paintEvent(QPaintEvent *)
405{
406 QStylePainter p(this);
407 QStyleOptionButton option;
408 initStyleOption(&option);
409 p.drawControl(QStyle::CE_PushButton, option);
410}
411
412
413/*! \reimp */
414void QPushButton::keyPressEvent(QKeyEvent *e)
415{
416 Q_D(QPushButton);
417 switch (e->key()) {
418 case Qt::Key_Enter:
419 case Qt::Key_Return:
420 if (autoDefault() || d->defaultButton) {
421 click();
422 break;
423 }
424 Q_FALLTHROUGH();
425 default:
426 QAbstractButton::keyPressEvent(e);
427 }
428}
429
430/*!
431 \reimp
432*/
433void QPushButton::focusInEvent(QFocusEvent *e)
434{
435 Q_D(QPushButton);
436 if (e->reason() != Qt::PopupFocusReason && autoDefault() && !d->defaultButton) {
437 d->defaultButton = true;
438#if QT_CONFIG(dialog)
439 QDialog *dlg = qobject_cast<QDialog*>(window());
440 if (dlg)
441 dlg->d_func()->setDefault(this);
442#endif
443 }
444 QAbstractButton::focusInEvent(e);
445}
446
447/*!
448 \reimp
449*/
450void QPushButton::focusOutEvent(QFocusEvent *e)
451{
452 Q_D(QPushButton);
453 if (e->reason() != Qt::PopupFocusReason && autoDefault() && d->defaultButton) {
454#if QT_CONFIG(dialog)
455 QDialog *dlg = qobject_cast<QDialog*>(window());
456 if (dlg)
457 dlg->d_func()->setDefault(nullptr);
458 else
459 d->defaultButton = false;
460#endif
461 }
462
463 QAbstractButton::focusOutEvent(e);
464#if QT_CONFIG(menu)
465 if (d->menu && d->menu->isVisible()) // restore pressed status
466 setDown(true);
467#endif
468}
469
470/*!
471 \reimp
472*/
473void QPushButton::mouseMoveEvent(QMouseEvent *e)
474{
475 Q_D(QPushButton);
476
477 if (testAttribute(Qt::WA_Hover)) {
478 bool hit = false;
479 if (underMouse())
480 hit = hitButton(e->position().toPoint());
481
482 if (hit != d->hovering) {
483 update(rect());
484 d->hovering = hit;
485 }
486 }
487
488 QAbstractButton::mouseMoveEvent(e);
489}
490
491/*!
492 \reimp
493*/
494bool QPushButton::hitButton(const QPoint &pos) const
495{
496 QStyleOptionButton option;
497 initStyleOption(&option);
498 const QRect bevel = style()->subElementRect(QStyle::SE_PushButtonBevel, &option, this);
499 return bevel.contains(pos);
500}
501
502#if QT_CONFIG(menu)
503/*!
504 Associates the popup menu \a menu with this push button. This
505 turns the button into a menu button, which in some styles will
506 produce a small triangle to the right of the button's text.
507
508 Ownership of the menu is \e not transferred to the push button.
509
510 \image fusion-pushbutton-menu.png
511 {Push button with popup menu}
512 A push button with popup menus shown in the \l{Qt Widget Gallery}
513 {Fusion widget style}.
514
515 \sa menu()
516*/
517void QPushButton::setMenu(QMenu* menu)
518{
519 Q_D(QPushButton);
520 if (menu == d->menu)
521 return;
522
523 if (menu && !d->menu) {
524 QObjectPrivate::connect(this, &QPushButton::pressed,
525 d, &QPushButtonPrivate::popupPressed, Qt::UniqueConnection);
526 }
527 if (d->menu)
528 removeAction(d->menu->menuAction());
529 d->menu = menu;
530 if (d->menu)
531 addAction(d->menu->menuAction());
532
533 d->resetLayoutItemMargins();
534 d->sizeHint = QSize();
535 update();
536 updateGeometry();
537}
538
539/*!
540 Returns the button's associated popup menu or \nullptr if no popup
541 menu has been set.
542
543 \sa setMenu()
544*/
545QMenu* QPushButton::menu() const
546{
547 Q_D(const QPushButton);
548 return d->menu;
549}
550
551/*!
552 Shows (pops up) the associated popup menu. If there is no such
553 menu, this function does nothing. This function does not return
554 until the popup menu has been closed by the user.
555*/
556void QPushButton::showMenu()
557{
558 Q_D(QPushButton);
559 if (!d || !d->menu)
560 return;
561 setDown(true);
562 d->popupPressed();
563}
564
565void QPushButtonPrivate::popupPressed()
566{
567 Q_Q(QPushButton);
568 if (!down || !menu)
569 return;
570
571 menu->setNoReplayFor(q);
572
573 QPoint menuPos = adjustedMenuPosition();
574
575 QMenuPrivate::get(menu)->causedPopup.widget = q;
576
577 //Because of a delay in menu effects, we must keep track of the
578 //menu visibility to avoid flicker on button release
579 menuOpen = true;
580 QObject::connect(menu, &QMenu::aboutToHide,
581 q, [q, this]{ menuOpen = false; q->setDown(false); }, Qt::SingleShotConnection);
582 menu->popup(menuPos);
583}
584
585QPoint QPushButtonPrivate::adjustedMenuPosition()
586{
587 Q_Q(QPushButton);
588
589 bool horizontal = true;
590#if QT_CONFIG(toolbar)
591 QToolBar *tb = qobject_cast<QToolBar*>(parent);
592 if (tb && tb->orientation() == Qt::Vertical)
593 horizontal = false;
594#endif
595
596 QWidgetItem item(q);
597 QRect rect = item.geometry();
598 rect.setRect(rect.x() - q->x(), rect.y() - q->y(), rect.width(), rect.height());
599
600 QSize menuSize = menu->sizeHint();
601 QPoint globalPos = q->mapToGlobal(rect.topLeft());
602 int x = globalPos.x();
603 int y = globalPos.y();
604 const QRect availableGeometry = QWidgetPrivate::availableScreenGeometry(q, q->mapToGlobal(rect.center()));
605 if (horizontal) {
606 if (globalPos.y() + rect.height() + menuSize.height() <= availableGeometry.bottom()) {
607 y += rect.height();
608 } else if (globalPos.y() - menuSize.height() >= availableGeometry.y()) {
609 y -= menuSize.height();
610 }
611 if (q->layoutDirection() == Qt::RightToLeft)
612 x += rect.width() - menuSize.width();
613 } else {
614 if (globalPos.x() + rect.width() + menu->sizeHint().width() <= availableGeometry.right()) {
615 x += rect.width();
616 } else if (globalPos.x() - menuSize.width() >= availableGeometry.x()) {
617 x -= menuSize.width();
618 }
619 }
620
621 return QPoint(x,y);
622}
623
624#endif // QT_CONFIG(menu)
625
626void QPushButtonPrivate::init()
627{
628 Q_Q(QPushButton);
629 q->setAttribute(Qt::WA_MacShowFocusRect);
630 resetLayoutItemMargins();
631}
632
633void QPushButtonPrivate::resetLayoutItemMargins()
634{
635 Q_Q(QPushButton);
636 QStyleOptionButton opt;
637 q->initStyleOption(&opt);
638 setLayoutItemMargins(QStyle::SE_PushButtonLayoutItem, &opt);
639}
640
641QStyle::State QPushButtonPrivate::styleButtonState(QStyle::State state) const
642{
643 Q_Q(const QPushButton);
644 state = QAbstractButtonPrivate::styleButtonState(state);
645 if (menuOpen)
646 state |= QStyle::State_Sunken;
647 if (checked)
648 state |= QStyle::State_On;
649 if (!flat && !down)
650 state |= QStyle::State_Raised;
651 if (q->underMouse() && q->hasMouseTracking())
652 state.setFlag(QStyle::State_MouseOver, hovering);
653 return state;
654}
655
656void QPushButton::setFlat(bool flat)
657{
658 Q_D(QPushButton);
659 if (d->flat == flat)
660 return;
661 d->flat = flat;
662 d->resetLayoutItemMargins();
663 d->sizeHint = QSize();
664 update();
665 updateGeometry();
666}
667
668bool QPushButton::isFlat() const
669{
670 Q_D(const QPushButton);
671 return d->flat;
672}
673
674/*! \reimp */
675bool QPushButton::event(QEvent *e)
676{
677 Q_D(QPushButton);
678 if (e->type() == QEvent::ParentChange) {
679#if QT_CONFIG(dialog)
680 if (QDialog *dialog = d->dialogParent()) {
681 if (d->defaultButton)
682 dialog->d_func()->setMainDefault(this);
683 }
684#endif
685 } else if (e->type() == QEvent::StyleChange
686#ifdef Q_OS_MAC
687 || e->type() == QEvent::MacSizeChange
688#endif
689 ) {
690 d->resetLayoutItemMargins();
691 updateGeometry();
692 } else if (e->type() == QEvent::PolishRequest) {
693 updateGeometry();
694 }
695 return QAbstractButton::event(e);
696}
697
698QT_END_NAMESPACE
699
700#include "moc_qpushbutton.cpp"