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

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