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