1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "private/qabstractbutton_p.h"
5
6#if QT_CONFIG(itemviews)
7#include "qabstractitemview.h"
8#endif
9#if QT_CONFIG(buttongroup)
10#include "qbuttongroup.h"
11#include "private/qbuttongroup_p.h"
12#endif
13#include "private/qapplication_p.h"
14#include "qabstractbutton_p.h"
15#include "qevent.h"
16#include "qpainter.h"
17#include "qapplication.h"
18#include "qstyle.h"
19#if QT_CONFIG(accessibility)
20#include "qaccessible.h"
21#endif
22#include <qpa/qplatformtheme.h>
23
24#include <algorithm>
25
26QT_BEGIN_NAMESPACE
27
28#define AUTO_REPEAT_DELAY 300
29#define AUTO_REPEAT_INTERVAL 100
30
31Q_WIDGETS_EXPORT extern bool qt_tab_all_widgets();
32
33/*!
34 \class QAbstractButton
35
36 \brief The QAbstractButton class is the abstract base class of
37 button widgets, providing functionality common to buttons.
38
39 \ingroup abstractwidgets
40 \inmodule QtWidgets
41
42 This class implements an \e abstract button.
43 Subclasses of this class handle user actions, and specify how the button
44 is drawn.
45
46 QAbstractButton provides support for both push buttons and checkable
47 (toggle) buttons. Checkable buttons are implemented in the QRadioButton
48 and QCheckBox classes. Push buttons are implemented in the
49 QPushButton and QToolButton classes; these also provide toggle
50 behavior if required.
51
52 Any button can display a label containing text and an icon. setText()
53 sets the text; setIcon() sets the icon. If a button is disabled, its label
54 is changed to give the button a "disabled" appearance.
55
56 If the button is a text button with a string containing an
57 ampersand ('&'), QAbstractButton automatically creates a shortcut
58 key. For example:
59
60 \snippet code/src_gui_widgets_qabstractbutton.cpp 0
61
62 The \uicontrol Alt+C shortcut is assigned to the button, i.e., when the
63 user presses \uicontrol Alt+C the button will call animateClick(). See
64 the \l {QShortcut#mnemonic}{QShortcut} documentation for details. To
65 display an actual ampersand, use '&&'.
66
67 You can also set a custom shortcut key using the setShortcut()
68 function. This is useful mostly for buttons that do not have any
69 text, and therefore can't have any automatic shortcut.
70
71 \snippet code/src_gui_widgets_qabstractbutton.cpp 1
72
73 All the buttons provided by Qt (QPushButton, QToolButton,
74 QCheckBox, and QRadioButton) can display both \l text and \l{icon}{icons}.
75
76 A button can be made the default button in a dialog by means of
77 QPushButton::setDefault() and QPushButton::setAutoDefault().
78
79 QAbstractButton provides most of the states used for buttons:
80
81 \list
82
83 \li isDown() indicates whether the button is \e pressed down.
84
85 \li isChecked() indicates whether the button is \e checked. Only
86 checkable buttons can be checked and unchecked (see below).
87
88 \li isEnabled() indicates whether the button can be pressed by the
89 user. \note As opposed to other widgets, buttons derived from
90 QAbstractButton accept mouse and context menu events
91 when disabled.
92
93 \li setAutoRepeat() sets whether the button will auto-repeat if the
94 user holds it down. \l autoRepeatDelay and \l autoRepeatInterval
95 define how auto-repetition is done.
96
97 \li setCheckable() sets whether the button is a toggle button or not.
98
99 \endlist
100
101 The difference between isDown() and isChecked() is as follows.
102 When the user clicks a toggle button to check it, the button is first
103 \e pressed then released into the \e checked state. When the user
104 clicks it again (to uncheck it), the button moves first to the
105 \e pressed state, then to the \e unchecked state (isChecked() and
106 isDown() are both false).
107
108 QAbstractButton provides four signals:
109
110 \list 1
111
112 \li pressed() is emitted when the left mouse button is pressed while
113 the mouse cursor is inside the button.
114
115 \li released() is emitted when the left mouse button is released.
116
117 \li clicked() is emitted when the button is first pressed and then
118 released, when the shortcut key is typed, or when click() or
119 animateClick() is called.
120
121 \li toggled() is emitted when the state of a toggle button changes.
122
123 \endlist
124
125 To subclass QAbstractButton, you must reimplement at least
126 paintEvent() to draw the button's outline and its text or pixmap. It
127 is generally advisable to reimplement sizeHint() as well, and
128 sometimes hitButton() (to determine whether a button press is within
129 the button). For buttons with more than two states (like tri-state
130 buttons), you will also have to reimplement checkStateSet() and
131 nextCheckState().
132
133 \sa QButtonGroup
134*/
135
136QAbstractButtonPrivate::QAbstractButtonPrivate(QSizePolicy::ControlType type)
137 :
138#ifndef QT_NO_SHORTCUT
139 shortcutId(0),
140#endif
141 checkable(false), checked(false), autoRepeat(false), autoExclusive(false),
142 down(false), blockRefresh(false), pressed(false),
143#if QT_CONFIG(buttongroup)
144 group(nullptr),
145#endif
146 autoRepeatDelay(AUTO_REPEAT_DELAY),
147 autoRepeatInterval(AUTO_REPEAT_INTERVAL),
148 controlType(type)
149{}
150
151QList<QAbstractButton *>QAbstractButtonPrivate::queryButtonList() const
152{
153#if QT_CONFIG(buttongroup)
154 if (group)
155 return group->d_func()->buttonList;
156#endif
157
158 if (!parent)
159 return {};
160 QList<QAbstractButton *> candidates = parent->findChildren<QAbstractButton *>();
161 if (autoExclusive) {
162 auto isNoMemberOfMyAutoExclusiveGroup = [](QAbstractButton *candidate) {
163 return !candidate->autoExclusive()
164#if QT_CONFIG(buttongroup)
165 || candidate->group()
166#endif
167 ;
168 };
169 candidates.removeIf(pred: isNoMemberOfMyAutoExclusiveGroup);
170 }
171 return candidates;
172}
173
174QAbstractButton *QAbstractButtonPrivate::queryCheckedButton() const
175{
176#if QT_CONFIG(buttongroup)
177 if (group)
178 return group->d_func()->checkedButton;
179#endif
180
181 Q_Q(const QAbstractButton);
182 QList<QAbstractButton *> buttonList = queryButtonList();
183 if (!autoExclusive || buttonList.size() == 1) // no group
184 return nullptr;
185
186 for (int i = 0; i < buttonList.size(); ++i) {
187 QAbstractButton *b = buttonList.at(i);
188 if (b->d_func()->checked && b != q)
189 return b;
190 }
191 return checked ? const_cast<QAbstractButton *>(q) : nullptr;
192}
193
194void QAbstractButtonPrivate::notifyChecked()
195{
196#if QT_CONFIG(buttongroup)
197 Q_Q(QAbstractButton);
198 if (group) {
199 QAbstractButton *previous = group->d_func()->checkedButton;
200 group->d_func()->checkedButton = q;
201 if (group->d_func()->exclusive && previous && previous != q)
202 previous->nextCheckState();
203 } else
204#endif
205 if (autoExclusive) {
206 if (QAbstractButton *b = queryCheckedButton())
207 b->setChecked(false);
208 }
209}
210
211void QAbstractButtonPrivate::moveFocus(int key)
212{
213 QList<QAbstractButton *> buttonList = queryButtonList();
214#if QT_CONFIG(buttongroup)
215 bool exclusive = group ? group->d_func()->exclusive : autoExclusive;
216#else
217 bool exclusive = autoExclusive;
218#endif
219 QWidget *f = QApplication::focusWidget();
220 QAbstractButton *fb = qobject_cast<QAbstractButton *>(object: f);
221 if (!fb || !buttonList.contains(t: fb))
222 return;
223
224 QAbstractButton *candidate = nullptr;
225 int bestScore = -1;
226 QRect target = f->rect().translated(p: f->mapToGlobal(QPoint(0,0)));
227 QPoint goal = target.center();
228 uint focus_flag = qt_tab_all_widgets() ? Qt::TabFocus : Qt::StrongFocus;
229
230 for (int i = 0; i < buttonList.size(); ++i) {
231 QAbstractButton *button = buttonList.at(i);
232 if (button != f && button->window() == f->window() && button->isEnabled() && !button->isHidden() &&
233 (exclusive || (button->focusPolicy() & focus_flag) == focus_flag)) {
234 QRect buttonRect = button->rect().translated(p: button->mapToGlobal(QPoint(0,0)));
235 QPoint p = buttonRect.center();
236
237 //Priority to widgets that overlap on the same coordinate.
238 //In that case, the distance in the direction will be used as significant score,
239 //take also in account orthogonal distance in case two widget are in the same distance.
240 int score;
241 if ((buttonRect.x() < target.right() && target.x() < buttonRect.right())
242 && (key == Qt::Key_Up || key == Qt::Key_Down)) {
243 //one item's is at the vertical of the other
244 score = (qAbs(t: p.y() - goal.y()) << 16) + qAbs(t: p.x() - goal.x());
245 } else if ((buttonRect.y() < target.bottom() && target.y() < buttonRect.bottom())
246 && (key == Qt::Key_Left || key == Qt::Key_Right) ) {
247 //one item's is at the horizontal of the other
248 score = (qAbs(t: p.x() - goal.x()) << 16) + qAbs(t: p.y() - goal.y());
249 } else {
250 score = (1 << 30) + (p.y() - goal.y()) * (p.y() - goal.y()) + (p.x() - goal.x()) * (p.x() - goal.x());
251 }
252
253 if (score > bestScore && candidate)
254 continue;
255
256 switch(key) {
257 case Qt::Key_Up:
258 if (p.y() < goal.y()) {
259 candidate = button;
260 bestScore = score;
261 }
262 break;
263 case Qt::Key_Down:
264 if (p.y() > goal.y()) {
265 candidate = button;
266 bestScore = score;
267 }
268 break;
269 case Qt::Key_Left:
270 if (p.x() < goal.x()) {
271 candidate = button;
272 bestScore = score;
273 }
274 break;
275 case Qt::Key_Right:
276 if (p.x() > goal.x()) {
277 candidate = button;
278 bestScore = score;
279 }
280 break;
281 }
282 }
283 }
284
285 if (exclusive
286#ifdef QT_KEYPAD_NAVIGATION
287 && !QApplicationPrivate::keypadNavigationEnabled()
288#endif
289 && candidate
290 && fb->d_func()->checked
291 && candidate->d_func()->checkable)
292 candidate->click();
293
294 if (candidate) {
295 if (key == Qt::Key_Up || key == Qt::Key_Left)
296 candidate->setFocus(Qt::BacktabFocusReason);
297 else
298 candidate->setFocus(Qt::TabFocusReason);
299 }
300}
301
302void QAbstractButtonPrivate::fixFocusPolicy()
303{
304 Q_Q(QAbstractButton);
305#if QT_CONFIG(buttongroup)
306 if (!group && !autoExclusive)
307#else
308 if (!autoExclusive)
309#endif
310 return;
311
312 QList<QAbstractButton *> buttonList = queryButtonList();
313 for (int i = 0; i < buttonList.size(); ++i) {
314 QAbstractButton *b = buttonList.at(i);
315 if (!b->isCheckable())
316 continue;
317 b->setFocusPolicy((Qt::FocusPolicy) ((b == q || !q->isCheckable())
318 ? (b->focusPolicy() | Qt::TabFocus)
319 : (b->focusPolicy() & ~Qt::TabFocus)));
320 }
321}
322
323void QAbstractButtonPrivate::init()
324{
325 Q_Q(QAbstractButton);
326
327 q->setFocusPolicy(Qt::FocusPolicy(q->style()->styleHint(stylehint: QStyle::SH_Button_FocusPolicy)));
328 q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, controlType));
329 q->setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
330 q->setForegroundRole(QPalette::ButtonText);
331 q->setBackgroundRole(QPalette::Button);
332}
333
334void QAbstractButtonPrivate::refresh()
335{
336 Q_Q(QAbstractButton);
337
338 if (blockRefresh)
339 return;
340 q->update();
341}
342
343void QAbstractButtonPrivate::click()
344{
345 Q_Q(QAbstractButton);
346
347 down = false;
348 blockRefresh = true;
349 bool changeState = true;
350 if (checked && queryCheckedButton() == q) {
351 // the checked button of an exclusive or autoexclusive group cannot be unchecked
352#if QT_CONFIG(buttongroup)
353 if (group ? group->d_func()->exclusive : autoExclusive)
354#else
355 if (autoExclusive)
356#endif
357 changeState = false;
358 }
359
360 QPointer<QAbstractButton> guard(q);
361 if (changeState) {
362 q->nextCheckState();
363 if (!guard)
364 return;
365 }
366 blockRefresh = false;
367 refresh();
368 q->repaint();
369 if (guard)
370 emitReleased();
371 if (guard)
372 emitClicked();
373}
374
375void QAbstractButtonPrivate::emitClicked()
376{
377 Q_Q(QAbstractButton);
378 QPointer<QAbstractButton> guard(q);
379 emit q->clicked(checked);
380#if QT_CONFIG(buttongroup)
381 if (guard && group) {
382 emit group->idClicked(group->id(button: q));
383 if (guard && group)
384 emit group->buttonClicked(q);
385 }
386#endif
387}
388
389void QAbstractButtonPrivate::emitPressed()
390{
391 Q_Q(QAbstractButton);
392 QPointer<QAbstractButton> guard(q);
393 emit q->pressed();
394#if QT_CONFIG(buttongroup)
395 if (guard && group) {
396 emit group->idPressed(group->id(button: q));
397 if (guard && group)
398 emit group->buttonPressed(q);
399 }
400#endif
401}
402
403void QAbstractButtonPrivate::emitReleased()
404{
405 Q_Q(QAbstractButton);
406 QPointer<QAbstractButton> guard(q);
407 emit q->released();
408#if QT_CONFIG(buttongroup)
409 if (guard && group) {
410 emit group->idReleased(group->id(button: q));
411 if (guard && group)
412 emit group->buttonReleased(q);
413 }
414#endif
415}
416
417void QAbstractButtonPrivate::emitToggled(bool checked)
418{
419 Q_Q(QAbstractButton);
420 QPointer<QAbstractButton> guard(q);
421 emit q->toggled(checked);
422#if QT_CONFIG(buttongroup)
423 if (guard && group) {
424 emit group->idToggled(group->id(button: q), checked);
425 if (guard && group)
426 emit group->buttonToggled(q, checked);
427 }
428#endif
429}
430
431/*!
432 Constructs an abstract button with a \a parent.
433*/
434QAbstractButton::QAbstractButton(QWidget *parent)
435 : QWidget(*new QAbstractButtonPrivate, parent, { })
436{
437 Q_D(QAbstractButton);
438 d->init();
439}
440
441/*!
442 Destroys the button.
443 */
444 QAbstractButton::~QAbstractButton()
445{
446#if QT_CONFIG(buttongroup)
447 Q_D(QAbstractButton);
448 if (d->group)
449 d->group->removeButton(this);
450#endif
451}
452
453
454/*! \internal
455 */
456QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
457 : QWidget(dd, parent, { })
458{
459 Q_D(QAbstractButton);
460 d->init();
461}
462
463/*!
464\property QAbstractButton::text
465\brief the text shown on the button
466
467If the button has no text, the text() function will return an empty
468string.
469
470If the text contains an ampersand character ('&'), a shortcut is
471automatically created for it. The character that follows the '&' will
472be used as the shortcut key. Any previous shortcut will be
473overwritten or cleared if no shortcut is defined by the text. See the
474\l {QShortcut#mnemonic}{QShortcut} documentation for details. To
475display an actual ampersand, use '&&'.
476
477There is no default text.
478*/
479
480void QAbstractButton::setText(const QString &text)
481{
482 Q_D(QAbstractButton);
483 if (d->text == text)
484 return;
485 d->text = text;
486#ifndef QT_NO_SHORTCUT
487 QKeySequence newMnemonic = QKeySequence::mnemonic(text);
488 setShortcut(newMnemonic);
489#endif
490 d->sizeHint = QSize();
491 update();
492 updateGeometry();
493#if QT_CONFIG(accessibility)
494 QAccessibleEvent event(this, QAccessible::NameChanged);
495 QAccessible::updateAccessibility(event: &event);
496#endif
497}
498
499QString QAbstractButton::text() const
500{
501 Q_D(const QAbstractButton);
502 return d->text;
503}
504
505
506/*!
507 \property QAbstractButton::icon
508 \brief the icon shown on the button
509
510 The icon's default size is defined by the GUI style, but can be
511 adjusted by setting the \l iconSize property.
512*/
513void QAbstractButton::setIcon(const QIcon &icon)
514{
515 Q_D(QAbstractButton);
516 d->icon = icon;
517 d->sizeHint = QSize();
518 update();
519 updateGeometry();
520}
521
522QIcon QAbstractButton::icon() const
523{
524 Q_D(const QAbstractButton);
525 return d->icon;
526}
527
528#ifndef QT_NO_SHORTCUT
529/*!
530\property QAbstractButton::shortcut
531\brief the mnemonic associated with the button
532*/
533
534void QAbstractButton::setShortcut(const QKeySequence &key)
535{
536 Q_D(QAbstractButton);
537 if (d->shortcutId != 0)
538 releaseShortcut(id: d->shortcutId);
539 d->shortcut = key;
540 d->shortcutId = grabShortcut(key);
541}
542
543QKeySequence QAbstractButton::shortcut() const
544{
545 Q_D(const QAbstractButton);
546 return d->shortcut;
547}
548#endif // QT_NO_SHORTCUT
549
550/*!
551\property QAbstractButton::checkable
552\brief whether the button is checkable
553
554By default, the button is not checkable.
555
556\sa checked
557*/
558void QAbstractButton::setCheckable(bool checkable)
559{
560 Q_D(QAbstractButton);
561 if (d->checkable == checkable)
562 return;
563
564 d->checkable = checkable;
565 d->checked = false;
566}
567
568bool QAbstractButton::isCheckable() const
569{
570 Q_D(const QAbstractButton);
571 return d->checkable;
572}
573
574/*!
575\property QAbstractButton::checked
576\brief whether the button is checked
577
578Only checkable buttons can be checked. By default, the button is unchecked.
579
580\sa checkable
581*/
582void QAbstractButton::setChecked(bool checked)
583{
584 Q_D(QAbstractButton);
585 if (!d->checkable || d->checked == checked) {
586 if (!d->blockRefresh)
587 checkStateSet();
588 return;
589 }
590
591 if (!checked && d->queryCheckedButton() == this) {
592 // the checked button of an exclusive or autoexclusive group cannot be unchecked
593#if QT_CONFIG(buttongroup)
594 if (d->group ? d->group->d_func()->exclusive : d->autoExclusive)
595 return;
596 if (d->group)
597 d->group->d_func()->detectCheckedButton();
598#else
599 if (d->autoExclusive)
600 return;
601#endif
602 }
603
604 QPointer<QAbstractButton> guard(this);
605
606 d->checked = checked;
607 if (!d->blockRefresh)
608 checkStateSet();
609 d->refresh();
610
611 if (guard && checked)
612 d->notifyChecked();
613 if (guard)
614 d->emitToggled(checked);
615
616#if QT_CONFIG(accessibility)
617 if (guard) {
618 QAccessible::State s;
619 s.checked = true;
620 QAccessibleStateChangeEvent event(this, s);
621 QAccessible::updateAccessibility(event: &event);
622 }
623#endif
624}
625
626bool QAbstractButton::isChecked() const
627{
628 Q_D(const QAbstractButton);
629 return d->checked;
630}
631
632/*!
633 \property QAbstractButton::down
634 \brief whether the button is pressed down
635
636 If this property is \c true, the button is pressed down. The signals
637 pressed() and clicked() are not emitted if you set this property
638 to true. The default is false.
639*/
640
641void QAbstractButton::setDown(bool down)
642{
643 Q_D(QAbstractButton);
644 if (d->down == down)
645 return;
646 d->down = down;
647 d->refresh();
648 if (d->autoRepeat && d->down)
649 d->repeatTimer.start(msec: d->autoRepeatDelay, obj: this);
650 else
651 d->repeatTimer.stop();
652}
653
654bool QAbstractButton::isDown() const
655{
656 Q_D(const QAbstractButton);
657 return d->down;
658}
659
660/*!
661\property QAbstractButton::autoRepeat
662\brief whether autoRepeat is enabled
663
664If autoRepeat is enabled, then the pressed(), released(), and clicked() signals are emitted at
665regular intervals when the button is down. autoRepeat is off by default.
666The initial delay and the repetition interval are defined in milliseconds by \l
667autoRepeatDelay and \l autoRepeatInterval.
668
669Note: If a button is pressed down by a shortcut key, then auto-repeat is enabled and timed by the
670system and not by this class. The pressed(), released(), and clicked() signals will be emitted
671like in the normal case.
672*/
673
674void QAbstractButton::setAutoRepeat(bool autoRepeat)
675{
676 Q_D(QAbstractButton);
677 if (d->autoRepeat == autoRepeat)
678 return;
679 d->autoRepeat = autoRepeat;
680 if (d->autoRepeat && d->down)
681 d->repeatTimer.start(msec: d->autoRepeatDelay, obj: this);
682 else
683 d->repeatTimer.stop();
684}
685
686bool QAbstractButton::autoRepeat() const
687{
688 Q_D(const QAbstractButton);
689 return d->autoRepeat;
690}
691
692/*!
693 \property QAbstractButton::autoRepeatDelay
694 \brief the initial delay of auto-repetition
695 \since 4.2
696
697 If \l autoRepeat is enabled, then autoRepeatDelay defines the initial
698 delay in milliseconds before auto-repetition kicks in.
699
700 \sa autoRepeat, autoRepeatInterval
701*/
702
703void QAbstractButton::setAutoRepeatDelay(int autoRepeatDelay)
704{
705 Q_D(QAbstractButton);
706 d->autoRepeatDelay = autoRepeatDelay;
707}
708
709int QAbstractButton::autoRepeatDelay() const
710{
711 Q_D(const QAbstractButton);
712 return d->autoRepeatDelay;
713}
714
715/*!
716 \property QAbstractButton::autoRepeatInterval
717 \brief the interval of auto-repetition
718 \since 4.2
719
720 If \l autoRepeat is enabled, then autoRepeatInterval defines the
721 length of the auto-repetition interval in millisecons.
722
723 \sa autoRepeat, autoRepeatDelay
724*/
725
726void QAbstractButton::setAutoRepeatInterval(int autoRepeatInterval)
727{
728 Q_D(QAbstractButton);
729 d->autoRepeatInterval = autoRepeatInterval;
730}
731
732int QAbstractButton::autoRepeatInterval() const
733{
734 Q_D(const QAbstractButton);
735 return d->autoRepeatInterval;
736}
737
738
739
740/*!
741\property QAbstractButton::autoExclusive
742\brief whether auto-exclusivity is enabled
743
744If auto-exclusivity is enabled, checkable buttons that belong to the
745same parent widget behave as if they were part of the same
746exclusive button group. In an exclusive button group, only one button
747can be checked at any time; checking another button automatically
748unchecks the previously checked one.
749
750The property has no effect on buttons that belong to a button
751group.
752
753autoExclusive is off by default, except for radio buttons.
754
755\sa QRadioButton
756*/
757void QAbstractButton::setAutoExclusive(bool autoExclusive)
758{
759 Q_D(QAbstractButton);
760 d->autoExclusive = autoExclusive;
761}
762
763bool QAbstractButton::autoExclusive() const
764{
765 Q_D(const QAbstractButton);
766 return d->autoExclusive;
767}
768
769#if QT_CONFIG(buttongroup)
770/*!
771 Returns the group that this button belongs to.
772
773 If the button is not a member of any QButtonGroup, this function
774 returns \nullptr.
775
776 \sa QButtonGroup
777*/
778QButtonGroup *QAbstractButton::group() const
779{
780 Q_D(const QAbstractButton);
781 return d->group;
782}
783#endif // QT_CONFIG(buttongroup)
784
785/*!
786Performs an animated click: the button is pressed immediately, and
787released 100ms later.
788
789Calling this function again before the button is released resets
790the release timer.
791
792All signals associated with a click are emitted as appropriate.
793
794This function does nothing if the button is \l{setEnabled()}{disabled.}
795
796\sa click()
797*/
798void QAbstractButton::animateClick()
799{
800 if (!isEnabled())
801 return;
802 Q_D(QAbstractButton);
803 if (d->checkable && focusPolicy() & Qt::ClickFocus)
804 setFocus();
805 setDown(true);
806 repaint();
807 if (!d->animateTimer.isActive())
808 d->emitPressed();
809 d->animateTimer.start(msec: 100, obj: this);
810}
811
812/*!
813Performs a click.
814
815All the usual signals associated with a click are emitted as
816appropriate. If the button is checkable, the state of the button is
817toggled.
818
819This function does nothing if the button is \l{setEnabled()}{disabled.}
820
821\sa animateClick()
822 */
823void QAbstractButton::click()
824{
825 if (!isEnabled())
826 return;
827 Q_D(QAbstractButton);
828 QPointer<QAbstractButton> guard(this);
829 d->down = true;
830 d->emitPressed();
831 if (guard) {
832 d->down = false;
833 nextCheckState();
834 if (guard)
835 d->emitReleased();
836 if (guard)
837 d->emitClicked();
838 }
839}
840
841/*! \fn void QAbstractButton::toggle()
842
843 Toggles the state of a checkable button.
844
845 \sa checked
846*/
847void QAbstractButton::toggle()
848{
849 Q_D(QAbstractButton);
850 setChecked(!d->checked);
851}
852
853
854/*! This virtual handler is called when setChecked() is used,
855unless it is called from within nextCheckState(). It allows
856subclasses to reset their intermediate button states.
857
858\sa nextCheckState()
859 */
860void QAbstractButton::checkStateSet()
861{
862}
863
864/*! This virtual handler is called when a button is clicked. The
865default implementation calls setChecked(!isChecked()) if the button
866isCheckable(). It allows subclasses to implement intermediate button
867states.
868
869\sa checkStateSet()
870*/
871void QAbstractButton::nextCheckState()
872{
873 if (isCheckable())
874 setChecked(!isChecked());
875}
876
877/*!
878Returns \c true if \a pos is inside the clickable button rectangle;
879otherwise returns \c false.
880
881By default, the clickable area is the entire widget. Subclasses
882may reimplement this function to provide support for clickable
883areas of different shapes and sizes.
884*/
885bool QAbstractButton::hitButton(const QPoint &pos) const
886{
887 return rect().contains(p: pos);
888}
889
890/*! \reimp */
891bool QAbstractButton::event(QEvent *e)
892{
893 // as opposed to other widgets, disabled buttons accept mouse
894 // events. This avoids surprising click-through scenarios
895 if (!isEnabled()) {
896 switch(e->type()) {
897 case QEvent::TabletPress:
898 case QEvent::TabletRelease:
899 case QEvent::TabletMove:
900 case QEvent::MouseButtonPress:
901 case QEvent::MouseButtonRelease:
902 case QEvent::MouseButtonDblClick:
903 case QEvent::MouseMove:
904 case QEvent::HoverMove:
905 case QEvent::HoverEnter:
906 case QEvent::HoverLeave:
907 case QEvent::ContextMenu:
908 return true;
909 default:
910 break;
911 }
912 }
913
914#ifndef QT_NO_SHORTCUT
915 if (e->type() == QEvent::Shortcut) {
916 Q_D(QAbstractButton);
917 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
918 if (d->shortcutId != se->shortcutId())
919 return false;
920 if (!se->isAmbiguous()) {
921 if (!d->animateTimer.isActive())
922 animateClick();
923 } else {
924 if (focusPolicy() != Qt::NoFocus)
925 setFocus(Qt::ShortcutFocusReason);
926 window()->setAttribute(Qt::WA_KeyboardFocusChange);
927 }
928 return true;
929 }
930#endif
931 return QWidget::event(event: e);
932}
933
934/*! \reimp */
935void QAbstractButton::mousePressEvent(QMouseEvent *e)
936{
937 Q_D(QAbstractButton);
938 if (e->button() != Qt::LeftButton) {
939 e->ignore();
940 return;
941 }
942 if (hitButton(pos: e->position().toPoint())) {
943 setDown(true);
944 d->pressed = true;
945 repaint();
946 d->emitPressed();
947 e->accept();
948 } else {
949 e->ignore();
950 }
951}
952
953/*! \reimp */
954void QAbstractButton::mouseReleaseEvent(QMouseEvent *e)
955{
956 Q_D(QAbstractButton);
957
958 if (e->button() != Qt::LeftButton) {
959 e->ignore();
960 return;
961 }
962
963 d->pressed = false;
964
965 if (!d->down) {
966 // refresh is required by QMacStyle to resume the default button animation
967 d->refresh();
968 e->ignore();
969 return;
970 }
971
972 if (hitButton(pos: e->position().toPoint())) {
973 d->repeatTimer.stop();
974 d->click();
975 e->accept();
976 } else {
977 setDown(false);
978 e->ignore();
979 }
980}
981
982/*! \reimp */
983void QAbstractButton::mouseMoveEvent(QMouseEvent *e)
984{
985 Q_D(QAbstractButton);
986 if (!(e->buttons() & Qt::LeftButton) || !d->pressed) {
987 e->ignore();
988 return;
989 }
990
991 if (hitButton(pos: e->position().toPoint()) != d->down) {
992 setDown(!d->down);
993 repaint();
994 if (d->down)
995 d->emitPressed();
996 else
997 d->emitReleased();
998 e->accept();
999 } else if (!hitButton(pos: e->position().toPoint())) {
1000 e->ignore();
1001 }
1002}
1003
1004/*! \reimp */
1005void QAbstractButton::keyPressEvent(QKeyEvent *e)
1006{
1007 Q_D(QAbstractButton);
1008 bool next = true;
1009
1010 const auto key = e->key();
1011 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()
1012 ->themeHint(hint: QPlatformTheme::ButtonPressKeys)
1013 .value<QList<Qt::Key>>();
1014 if (buttonPressKeys.contains(t: key) && !e->isAutoRepeat()) {
1015 setDown(true);
1016 repaint();
1017 d->emitPressed();
1018 return;
1019 }
1020
1021 switch (key) {
1022 case Qt::Key_Up:
1023 next = false;
1024 Q_FALLTHROUGH();
1025 case Qt::Key_Left:
1026 case Qt::Key_Right:
1027 case Qt::Key_Down: {
1028#ifdef QT_KEYPAD_NAVIGATION
1029 if ((QApplicationPrivate::keypadNavigationEnabled()
1030 && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right))
1031 || (!QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional
1032 || (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down))) {
1033 e->ignore();
1034 return;
1035 }
1036#endif
1037 QWidget *pw = parentWidget();
1038 if (d->autoExclusive
1039#if QT_CONFIG(buttongroup)
1040 || d->group
1041#endif
1042#if QT_CONFIG(itemviews)
1043 || (pw && qobject_cast<QAbstractItemView *>(object: pw->parentWidget()))
1044#endif
1045 ) {
1046 // ### Using qobject_cast to check if the parent is a viewport of
1047 // QAbstractItemView is a crude hack, and should be revisited and
1048 // cleaned up when fixing task 194373. It's here to ensure that we
1049 // keep compatibility outside QAbstractItemView.
1050 d->moveFocus(key: e->key());
1051 if (hasFocus()) // nothing happened, propagate
1052 e->ignore();
1053 } else {
1054 // Prefer parent widget, use this if parent is absent
1055 QWidget *w = pw ? pw : this;
1056 bool reverse = (w->layoutDirection() == Qt::RightToLeft);
1057 if ((e->key() == Qt::Key_Left && !reverse)
1058 || (e->key() == Qt::Key_Right && reverse)) {
1059 next = false;
1060 }
1061 focusNextPrevChild(next);
1062 }
1063 break;
1064 }
1065 default:
1066#ifndef QT_NO_SHORTCUT
1067 if (e->matches(key: QKeySequence::Cancel) && d->down) {
1068 setDown(false);
1069 repaint();
1070 d->emitReleased();
1071 return;
1072 }
1073#endif
1074 e->ignore();
1075 }
1076}
1077
1078/*! \reimp */
1079void QAbstractButton::keyReleaseEvent(QKeyEvent *e)
1080{
1081 Q_D(QAbstractButton);
1082
1083 if (!e->isAutoRepeat())
1084 d->repeatTimer.stop();
1085
1086 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()
1087 ->themeHint(hint: QPlatformTheme::ButtonPressKeys)
1088 .value<QList<Qt::Key>>();
1089 if (buttonPressKeys.contains(t: e->key()) && !e->isAutoRepeat() && d->down) {
1090 d->click();
1091 return;
1092 }
1093
1094 e->ignore();
1095}
1096
1097/*!\reimp
1098 */
1099void QAbstractButton::timerEvent(QTimerEvent *e)
1100{
1101 Q_D(QAbstractButton);
1102 if (e->timerId() == d->repeatTimer.timerId()) {
1103 d->repeatTimer.start(msec: d->autoRepeatInterval, obj: this);
1104 if (d->down) {
1105 QPointer<QAbstractButton> guard(this);
1106 nextCheckState();
1107 if (guard)
1108 d->emitReleased();
1109 if (guard)
1110 d->emitClicked();
1111 if (guard)
1112 d->emitPressed();
1113 }
1114 } else if (e->timerId() == d->animateTimer.timerId()) {
1115 d->animateTimer.stop();
1116 d->click();
1117 }
1118}
1119
1120/*! \reimp */
1121void QAbstractButton::focusInEvent(QFocusEvent *e)
1122{
1123 Q_D(QAbstractButton);
1124#ifdef QT_KEYPAD_NAVIGATION
1125 if (!QApplicationPrivate::keypadNavigationEnabled())
1126#endif
1127 d->fixFocusPolicy();
1128 QWidget::focusInEvent(event: e);
1129}
1130
1131/*! \reimp */
1132void QAbstractButton::focusOutEvent(QFocusEvent *e)
1133{
1134 Q_D(QAbstractButton);
1135 if (e->reason() != Qt::PopupFocusReason && d->down) {
1136 d->down = false;
1137 d->emitReleased();
1138 }
1139 QWidget::focusOutEvent(event: e);
1140}
1141
1142/*! \reimp */
1143void QAbstractButton::changeEvent(QEvent *e)
1144{
1145 Q_D(QAbstractButton);
1146 switch (e->type()) {
1147 case QEvent::EnabledChange:
1148 if (!isEnabled() && d->down) {
1149 d->down = false;
1150 d->emitReleased();
1151 }
1152 break;
1153 default:
1154 d->sizeHint = QSize();
1155 break;
1156 }
1157 QWidget::changeEvent(e);
1158}
1159
1160/*!
1161 \fn void QAbstractButton::paintEvent(QPaintEvent *e)
1162 \reimp
1163*/
1164
1165/*!
1166 \fn void QAbstractButton::pressed()
1167
1168 This signal is emitted when the button is pressed down.
1169
1170 \sa released(), clicked()
1171*/
1172
1173/*!
1174 \fn void QAbstractButton::released()
1175
1176 This signal is emitted when the button is released.
1177
1178 \sa pressed(), clicked(), toggled()
1179*/
1180
1181/*!
1182\fn void QAbstractButton::clicked(bool checked)
1183
1184This signal is emitted when the button is activated (i.e., pressed down
1185then released while the mouse cursor is inside the button), when the
1186shortcut key is typed, or when click() or animateClick() is called.
1187Notably, this signal is \e not emitted if you call setDown(),
1188setChecked() or toggle().
1189
1190If the button is checkable, \a checked is true if the button is
1191checked, or false if the button is unchecked.
1192
1193\sa pressed(), released(), toggled()
1194*/
1195
1196/*!
1197\fn void QAbstractButton::toggled(bool checked)
1198
1199This signal is emitted whenever a checkable button changes its state.
1200\a checked is true if the button is checked, or false if the button is
1201unchecked.
1202
1203This may be the result of a user action, click() slot activation,
1204or because setChecked() is called.
1205
1206The states of buttons in exclusive button groups are updated before this
1207signal is emitted. This means that slots can act on either the "off"
1208signal or the "on" signal emitted by the buttons in the group whose
1209states have changed.
1210
1211For example, a slot that reacts to signals emitted by newly checked
1212buttons but which ignores signals from buttons that have been unchecked
1213can be implemented using the following pattern:
1214
1215\snippet code/src_gui_widgets_qabstractbutton.cpp 2
1216
1217Button groups can be created using the QButtonGroup class, and
1218updates to the button states monitored with the
1219\l{QButtonGroup::buttonClicked()} signal.
1220
1221\sa checked, clicked()
1222*/
1223
1224/*!
1225 \property QAbstractButton::iconSize
1226 \brief the icon size used for this button.
1227
1228 The default size is defined by the GUI style. This is a maximum
1229 size for the icons. Smaller icons will not be scaled up.
1230*/
1231
1232QSize QAbstractButton::iconSize() const
1233{
1234 Q_D(const QAbstractButton);
1235 if (d->iconSize.isValid())
1236 return d->iconSize;
1237 int e = style()->pixelMetric(metric: QStyle::PM_ButtonIconSize, option: nullptr, widget: this);
1238 return QSize(e, e);
1239}
1240
1241void QAbstractButton::setIconSize(const QSize &size)
1242{
1243 Q_D(QAbstractButton);
1244 if (d->iconSize == size)
1245 return;
1246
1247 d->iconSize = size;
1248 d->sizeHint = QSize();
1249 updateGeometry();
1250 if (isVisible()) {
1251 update();
1252 }
1253}
1254
1255
1256
1257QT_END_NAMESPACE
1258
1259#include "moc_qabstractbutton.cpp"
1260

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