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