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 | |
26 | QT_BEGIN_NAMESPACE |
27 | |
28 | #define AUTO_REPEAT_DELAY 300 |
29 | #define AUTO_REPEAT_INTERVAL 100 |
30 | |
31 | Q_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 | |
136 | QAbstractButtonPrivate::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 | |
151 | QList<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 | |
174 | QAbstractButton *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 | |
194 | void 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 | |
211 | void 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 | |
302 | void 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 | |
323 | void 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 | |
334 | void QAbstractButtonPrivate::refresh() |
335 | { |
336 | Q_Q(QAbstractButton); |
337 | |
338 | if (blockRefresh) |
339 | return; |
340 | q->update(); |
341 | } |
342 | |
343 | void 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 | |
375 | void 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 | |
389 | void 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 | |
403 | void 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 | |
417 | void 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 | */ |
434 | QAbstractButton::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 | */ |
456 | QAbstractButton::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 | |
467 | If the button has no text, the text() function will return an empty |
468 | string. |
469 | |
470 | If the text contains an ampersand character ('&'), a shortcut is |
471 | automatically created for it. The character that follows the '&' will |
472 | be used as the shortcut key. Any previous shortcut will be |
473 | overwritten or cleared if no shortcut is defined by the text. See the |
474 | \l {QShortcut#mnemonic}{QShortcut} documentation for details. To |
475 | display an actual ampersand, use '&&'. |
476 | |
477 | There is no default text. |
478 | */ |
479 | |
480 | void 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 | |
499 | QString 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 | */ |
513 | void QAbstractButton::setIcon(const QIcon &icon) |
514 | { |
515 | Q_D(QAbstractButton); |
516 | d->icon = icon; |
517 | d->sizeHint = QSize(); |
518 | update(); |
519 | updateGeometry(); |
520 | } |
521 | |
522 | QIcon 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 | |
534 | void 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 | |
543 | QKeySequence 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 | |
554 | By default, the button is not checkable. |
555 | |
556 | \sa checked |
557 | */ |
558 | void 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 | |
568 | bool 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 | |
578 | Only checkable buttons can be checked. By default, the button is unchecked. |
579 | |
580 | \sa checkable |
581 | */ |
582 | void 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 | |
626 | bool 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 | |
641 | void 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 | |
654 | bool 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 | |
664 | If autoRepeat is enabled, then the pressed(), released(), and clicked() signals are emitted at |
665 | regular intervals when the button is down. autoRepeat is off by default. |
666 | The initial delay and the repetition interval are defined in milliseconds by \l |
667 | autoRepeatDelay and \l autoRepeatInterval. |
668 | |
669 | Note: If a button is pressed down by a shortcut key, then auto-repeat is enabled and timed by the |
670 | system and not by this class. The pressed(), released(), and clicked() signals will be emitted |
671 | like in the normal case. |
672 | */ |
673 | |
674 | void 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 | |
686 | bool 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 | |
703 | void QAbstractButton::setAutoRepeatDelay(int autoRepeatDelay) |
704 | { |
705 | Q_D(QAbstractButton); |
706 | d->autoRepeatDelay = autoRepeatDelay; |
707 | } |
708 | |
709 | int 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 | |
726 | void QAbstractButton::setAutoRepeatInterval(int autoRepeatInterval) |
727 | { |
728 | Q_D(QAbstractButton); |
729 | d->autoRepeatInterval = autoRepeatInterval; |
730 | } |
731 | |
732 | int 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 | |
744 | If auto-exclusivity is enabled, checkable buttons that belong to the |
745 | same parent widget behave as if they were part of the same |
746 | exclusive button group. In an exclusive button group, only one button |
747 | can be checked at any time; checking another button automatically |
748 | unchecks the previously checked one. |
749 | |
750 | The property has no effect on buttons that belong to a button |
751 | group. |
752 | |
753 | autoExclusive is off by default, except for radio buttons. |
754 | |
755 | \sa QRadioButton |
756 | */ |
757 | void QAbstractButton::setAutoExclusive(bool autoExclusive) |
758 | { |
759 | Q_D(QAbstractButton); |
760 | d->autoExclusive = autoExclusive; |
761 | } |
762 | |
763 | bool 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 | */ |
778 | QButtonGroup *QAbstractButton::group() const |
779 | { |
780 | Q_D(const QAbstractButton); |
781 | return d->group; |
782 | } |
783 | #endif // QT_CONFIG(buttongroup) |
784 | |
785 | /*! |
786 | Performs an animated click: the button is pressed immediately, and |
787 | released 100ms later. |
788 | |
789 | Calling this function again before the button is released resets |
790 | the release timer. |
791 | |
792 | All signals associated with a click are emitted as appropriate. |
793 | |
794 | This function does nothing if the button is \l{setEnabled()}{disabled.} |
795 | |
796 | \sa click() |
797 | */ |
798 | void 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 | /*! |
813 | Performs a click. |
814 | |
815 | All the usual signals associated with a click are emitted as |
816 | appropriate. If the button is checkable, the state of the button is |
817 | toggled. |
818 | |
819 | This function does nothing if the button is \l{setEnabled()}{disabled.} |
820 | |
821 | \sa animateClick() |
822 | */ |
823 | void 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 | */ |
847 | void QAbstractButton::toggle() |
848 | { |
849 | Q_D(QAbstractButton); |
850 | setChecked(!d->checked); |
851 | } |
852 | |
853 | |
854 | /*! This virtual handler is called when setChecked() is used, |
855 | unless it is called from within nextCheckState(). It allows |
856 | subclasses to reset their intermediate button states. |
857 | |
858 | \sa nextCheckState() |
859 | */ |
860 | void QAbstractButton::checkStateSet() |
861 | { |
862 | } |
863 | |
864 | /*! This virtual handler is called when a button is clicked. The |
865 | default implementation calls setChecked(!isChecked()) if the button |
866 | isCheckable(). It allows subclasses to implement intermediate button |
867 | states. |
868 | |
869 | \sa checkStateSet() |
870 | */ |
871 | void QAbstractButton::nextCheckState() |
872 | { |
873 | if (isCheckable()) |
874 | setChecked(!isChecked()); |
875 | } |
876 | |
877 | /*! |
878 | Returns \c true if \a pos is inside the clickable button rectangle; |
879 | otherwise returns \c false. |
880 | |
881 | By default, the clickable area is the entire widget. Subclasses |
882 | may reimplement this function to provide support for clickable |
883 | areas of different shapes and sizes. |
884 | */ |
885 | bool QAbstractButton::hitButton(const QPoint &pos) const |
886 | { |
887 | return rect().contains(p: pos); |
888 | } |
889 | |
890 | /*! \reimp */ |
891 | bool 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 */ |
935 | void 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 */ |
954 | void 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 */ |
983 | void 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 */ |
1005 | void 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 */ |
1079 | void 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 | */ |
1099 | void 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 */ |
1121 | void 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 */ |
1132 | void 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 */ |
1143 | void 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 | |
1184 | This signal is emitted when the button is activated (i.e., pressed down |
1185 | then released while the mouse cursor is inside the button), when the |
1186 | shortcut key is typed, or when click() or animateClick() is called. |
1187 | Notably, this signal is \e not emitted if you call setDown(), |
1188 | setChecked() or toggle(). |
1189 | |
1190 | If the button is checkable, \a checked is true if the button is |
1191 | checked, or false if the button is unchecked. |
1192 | |
1193 | \sa pressed(), released(), toggled() |
1194 | */ |
1195 | |
1196 | /*! |
1197 | \fn void QAbstractButton::toggled(bool checked) |
1198 | |
1199 | This 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 |
1201 | unchecked. |
1202 | |
1203 | This may be the result of a user action, click() slot activation, |
1204 | or because setChecked() is called. |
1205 | |
1206 | The states of buttons in exclusive button groups are updated before this |
1207 | signal is emitted. This means that slots can act on either the "off" |
1208 | signal or the "on" signal emitted by the buttons in the group whose |
1209 | states have changed. |
1210 | |
1211 | For example, a slot that reacts to signals emitted by newly checked |
1212 | buttons but which ignores signals from buttons that have been unchecked |
1213 | can be implemented using the following pattern: |
1214 | |
1215 | \snippet code/src_gui_widgets_qabstractbutton.cpp 2 |
1216 | |
1217 | Button groups can be created using the QButtonGroup class, and |
1218 | updates 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 | |
1232 | QSize 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 | |
1241 | void 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 | |
1257 | QT_END_NAMESPACE |
1258 | |
1259 | #include "moc_qabstractbutton.cpp" |
1260 | |