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