1// Copyright (C) 2017 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 "qquickabstractbutton_p.h"
5#include "qquickabstractbutton_p_p.h"
6#include "qquickactiongroup_p.h"
7#include "qquickbuttongroup_p.h"
8#include "qquickaction_p.h"
9#include "qquickaction_p_p.h"
10#include "qquickshortcutcontext_p_p.h"
11#include "qquickdeferredexecute_p_p.h"
12
13#include <QtGui/qstylehints.h>
14#include <QtGui/qguiapplication.h>
15#if QT_CONFIG(shortcut)
16# include <QtGui/private/qshortcutmap_p.h>
17#endif
18#if QT_CONFIG(accessibility)
19#include <QtGui/private/qaccessiblehelper_p.h>
20#endif
21#include <QtGui/private/qguiapplication_p.h>
22#include <QtGui/qpa/qplatformtheme.h>
23#include <QtQuick/private/qquickevents_p_p.h>
24#include <QtQuick/private/qquickevents_p_p.h>
25#include <QtQuick/private/qquickaccessibleattached_p.h>
26#include <QtQml/qqmllist.h>
27
28QT_BEGIN_NAMESPACE
29
30/*!
31 \qmltype AbstractButton
32 \inherits Control
33//! \nativetype QQuickAbstractButton
34 \inqmlmodule QtQuick.Controls
35 \since 5.7
36 \ingroup qtquickcontrols-buttons
37 \brief Abstract base type providing functionality common to buttons.
38
39 AbstractButton provides the interface for controls with button-like
40 behavior; for example, push buttons and checkable controls like
41 radio buttons and check boxes. As an abstract control, it has no delegate
42 implementations, leaving them to the types that derive from it.
43
44 \sa ButtonGroup, {Button Controls}
45*/
46
47/*!
48 \qmlsignal QtQuick.Controls::AbstractButton::pressed()
49
50 This signal is emitted when the button is interactively pressed by the user via touch, mouse, or keyboard.
51*/
52
53/*!
54 \qmlsignal QtQuick.Controls::AbstractButton::released()
55
56 This signal is emitted when the button is interactively released by the user via touch, mouse, or keyboard.
57*/
58
59/*!
60 \qmlsignal QtQuick.Controls::AbstractButton::canceled()
61
62 This signal is emitted when the button loses mouse grab
63 while being pressed, or when it would emit the \l released
64 signal but the mouse cursor is not inside the button.
65*/
66
67/*!
68 \qmlsignal QtQuick.Controls::AbstractButton::clicked()
69
70 This signal is emitted when the button is interactively clicked by the user via touch, mouse, or keyboard.
71
72 \sa click(), animateClick(), {Call a C++ function from QML when a Button is clicked}
73*/
74
75/*!
76 \since QtQuick.Controls 2.2 (Qt 5.9)
77 \qmlsignal QtQuick.Controls::AbstractButton::toggled()
78
79 This signal is emitted when a checkable button is interactively toggled by the user via touch, mouse, or keyboard.
80*/
81
82/*!
83 \qmlsignal QtQuick.Controls::AbstractButton::pressAndHold()
84
85 This signal is emitted when the button is interactively pressed and held down by the user via touch or mouse.
86 It is not emitted when \l autoRepeat is enabled.
87*/
88
89/*!
90 \qmlsignal QtQuick.Controls::AbstractButton::doubleClicked()
91
92 This signal is emitted when the button is interactively double clicked by the user via touch or mouse.
93*/
94
95void QQuickAbstractButtonPrivate::init()
96{
97 Q_Q(QQuickAbstractButton);
98 q->setActiveFocusOnTab(true);
99#ifdef Q_OS_MACOS
100 q->setFocusPolicy(Qt::TabFocus);
101#else
102 q->setFocusPolicy(Qt::StrongFocus);
103#endif
104 q->setAcceptedMouseButtons(Qt::LeftButton);
105#if QT_CONFIG(quicktemplates2_multitouch)
106 q->setAcceptTouchEvents(true);
107#endif
108#if QT_CONFIG(cursor)
109 q->setCursor(Qt::ArrowCursor);
110#endif
111 setSizePolicy(horizontalPolicy: QLayoutPolicy::Preferred, verticalPolicy: QLayoutPolicy::Fixed);
112}
113
114QPointF QQuickAbstractButtonPrivate::centerPressPoint() const
115{
116 return QPointF(qRound(d: width / 2), qRound(d: height / 2));
117}
118
119void QQuickAbstractButtonPrivate::setPressPoint(const QPointF &point)
120{
121 pressPoint = point;
122 setMovePoint(point);
123}
124
125void QQuickAbstractButtonPrivate::setMovePoint(const QPointF &point)
126{
127 Q_Q(QQuickAbstractButton);
128 bool xChange = !qFuzzyCompare(p1: point.x(), p2: movePoint.x());
129 bool yChange = !qFuzzyCompare(p1: point.y(), p2: movePoint.y());
130 movePoint = point;
131 if (xChange)
132 emit q->pressXChanged();
133 if (yChange)
134 emit q->pressYChanged();
135}
136
137bool QQuickAbstractButtonPrivate::handlePress(const QPointF &point, ulong timestamp)
138{
139 Q_Q(QQuickAbstractButton);
140 if (pressed)
141 return true;
142 QQuickControlPrivate::handlePress(point, timestamp);
143 setPressPoint(point);
144 q->setPressed(true);
145
146 emit q->pressed();
147
148 if (autoRepeat)
149 startRepeatDelay();
150 else if (touchId != -1 || Qt::LeftButton == (pressButtons & Qt::LeftButton))
151 startPressAndHold();
152 else
153 stopPressAndHold();
154 return true;
155}
156
157bool QQuickAbstractButtonPrivate::handleMove(const QPointF &point, ulong timestamp)
158{
159 Q_Q(QQuickAbstractButton);
160 QQuickControlPrivate::handleMove(point, timestamp);
161 setMovePoint(point);
162 q->setPressed(keepPressed || q->contains(point));
163
164 if (!pressed && autoRepeat)
165 stopPressRepeat();
166 else if (holdTimer > 0 && (!pressed || QLineF(pressPoint, point).length() > QGuiApplication::styleHints()->startDragDistance()))
167 stopPressAndHold();
168 return true;
169}
170
171bool QQuickAbstractButtonPrivate::handleRelease(const QPointF &point, ulong timestamp)
172{
173 Q_Q(QQuickAbstractButton);
174 // Store this here since the base class' handleRelease clears it.
175 const int pressTouchId = touchId;
176
177 QQuickControlPrivate::handleRelease(point, timestamp);
178 bool wasPressed = pressed;
179 setPressPoint(point);
180 q->setPressed(false);
181 pressButtons = Qt::NoButton;
182
183 const bool touchDoubleClick = pressTouchId != -1 && lastTouchReleaseTimestamp != 0
184 && QQuickDeliveryAgentPrivate::isWithinDoubleClickInterval(timeInterval: timestamp - lastTouchReleaseTimestamp)
185 && isDoubleClickConnected();
186
187 if (!wasHeld && (keepPressed || q->contains(point)))
188 q->nextCheckState();
189
190 if (wasPressed) {
191 emit q->released();
192 if (!wasHeld && !wasDoubleClick)
193 trigger(doubleClick: touchDoubleClick);
194 } else {
195 emit q->canceled();
196 }
197
198 if (autoRepeat)
199 stopPressRepeat();
200 else
201 stopPressAndHold();
202
203 if (!touchDoubleClick) {
204 // This is not a double click yet, but it is potentially the
205 // first release before a double click.
206 if (pressTouchId != -1) {
207 // The corresponding press for this release was a touch press.
208 // Keep track of the timestamp of the release so that we can
209 // emit doubleClicked() if another one comes afterwards.
210 lastTouchReleaseTimestamp = timestamp;
211 }
212 } else {
213 // We just did a double click, so clear the release timestamp
214 // to prepare for any possible future double clicks.
215 lastTouchReleaseTimestamp = 0;
216 }
217
218 wasDoubleClick = false;
219 return true;
220}
221
222void QQuickAbstractButtonPrivate::handleUngrab()
223{
224 Q_Q(QQuickAbstractButton);
225 QQuickControlPrivate::handleUngrab();
226 pressButtons = Qt::NoButton;
227 if (!pressed)
228 return;
229
230 q->setPressed(false);
231 stopPressRepeat();
232 stopPressAndHold();
233 wasDoubleClick = false;
234 lastTouchReleaseTimestamp = 0;
235 emit q->canceled();
236}
237
238bool QQuickAbstractButtonPrivate::acceptKeyClick(Qt::Key key) const
239{
240 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::ButtonPressKeys).value<QList<Qt::Key>>();
241 return buttonPressKeys.contains(t: key);
242}
243
244bool QQuickAbstractButtonPrivate::isPressAndHoldConnected()
245{
246 Q_Q(QQuickAbstractButton);
247 static const QMetaMethod method = [&]() {
248 const auto signal = &QQuickAbstractButton::pressAndHold;
249 return QMetaMethod::fromSignal(signal);
250 }();
251 return q->isSignalConnected(signal: method);
252}
253
254bool QQuickAbstractButtonPrivate::isDoubleClickConnected()
255{
256 Q_Q(QQuickAbstractButton);
257 static const QMetaMethod method = [&]() {
258 const auto signal = &QQuickAbstractButton::doubleClicked;
259 return QMetaMethod::fromSignal(signal);
260 }();
261 return q->isSignalConnected(signal: method);
262}
263
264void QQuickAbstractButtonPrivate::startPressAndHold()
265{
266 Q_Q(QQuickAbstractButton);
267 wasHeld = false;
268 stopPressAndHold();
269 if (isPressAndHoldConnected())
270 holdTimer = q->startTimer(interval: QGuiApplication::styleHints()->mousePressAndHoldInterval());
271}
272
273void QQuickAbstractButtonPrivate::stopPressAndHold()
274{
275 Q_Q(QQuickAbstractButton);
276 if (holdTimer > 0) {
277 q->killTimer(id: holdTimer);
278 holdTimer = 0;
279 }
280}
281
282void QQuickAbstractButtonPrivate::startRepeatDelay()
283{
284 Q_Q(QQuickAbstractButton);
285 stopPressRepeat();
286 delayTimer = q->startTimer(interval: repeatDelay);
287}
288
289void QQuickAbstractButtonPrivate::startPressRepeat()
290{
291 Q_Q(QQuickAbstractButton);
292 stopPressRepeat();
293 repeatTimer = q->startTimer(interval: repeatInterval);
294}
295
296void QQuickAbstractButtonPrivate::stopPressRepeat()
297{
298 Q_Q(QQuickAbstractButton);
299 if (delayTimer > 0) {
300 q->killTimer(id: delayTimer);
301 delayTimer = 0;
302 }
303 if (repeatTimer > 0) {
304 q->killTimer(id: repeatTimer);
305 repeatTimer = 0;
306 }
307}
308
309#if QT_CONFIG(shortcut)
310void QQuickAbstractButtonPrivate::grabShortcut()
311{
312 Q_Q(QQuickAbstractButton);
313 if (shortcut.isEmpty())
314 return;
315
316 shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(owner: q, key: shortcut, context: Qt::WindowShortcut, matcher: QQuickShortcutContext::matcher);
317
318 if (!q->isEnabled())
319 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable: false, id: shortcutId, owner: q);
320}
321
322void QQuickAbstractButtonPrivate::ungrabShortcut()
323{
324 Q_Q(QQuickAbstractButton);
325 if (!shortcutId)
326 return;
327
328 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: shortcutId, owner: q);
329 shortcutId = 0;
330}
331#endif
332
333void QQuickAbstractButtonPrivate::actionTextChange()
334{
335 Q_Q(QQuickAbstractButton);
336 if (explicitText)
337 return;
338
339 q->buttonChange(change: QQuickAbstractButton::ButtonTextChange);
340}
341
342void QQuickAbstractButtonPrivate::setText(const QString &newText, QQml::PropertyUtils::State propertyState)
343{
344 Q_Q(QQuickAbstractButton);
345 const QString oldText = q->text();
346 explicitText = isExplicitlySet(propertyState);
347 text = newText;
348 if (oldText == q->text())
349 return;
350
351 q->buttonChange(change: QQuickAbstractButton::ButtonTextChange);
352}
353
354void QQuickAbstractButtonPrivate::updateEffectiveIcon()
355{
356 Q_Q(QQuickAbstractButton);
357 // We store effectiveIcon because we need to be able to tell if the icon has actually changed.
358 // If we only stored our icon and the action's icon, and resolved in the getter, we'd have
359 // no way of knowing what the old value was here. As an added benefit, we only resolve when
360 // something has changed, as opposed to doing it unconditionally in the icon() getter.
361 const QQuickIcon newEffectiveIcon = action ? icon.resolve(other: action->icon()) : icon;
362 if (newEffectiveIcon == effectiveIcon)
363 return;
364
365 effectiveIcon = newEffectiveIcon;
366 emit q->iconChanged();
367}
368
369void QQuickAbstractButtonPrivate::click()
370{
371 Q_Q(QQuickAbstractButton);
372 if (effectiveEnable)
373 emit q->clicked();
374}
375
376void QQuickAbstractButtonPrivate::accessiblePressAction()
377{
378 trigger();
379}
380
381void QQuickAbstractButtonPrivate::trigger(bool doubleClick)
382{
383 Q_Q(QQuickAbstractButton);
384 const bool wasEnabled = effectiveEnable;
385 if (action && action->isEnabled())
386 QQuickActionPrivate::get(action)->trigger(q, doToggle: false);
387 if (wasEnabled && (!action || !action->isEnabled())) {
388 if (!doubleClick)
389 emit q->clicked();
390 else
391 emit q->doubleClicked();
392 }
393}
394
395void QQuickAbstractButtonPrivate::toggle(bool value)
396{
397 Q_Q(QQuickAbstractButton);
398 const bool wasChecked = checked;
399 q->setChecked(value);
400 if (wasChecked != checked)
401 emit q->toggled();
402}
403
404void QQuickAbstractButtonPrivate::cancelIndicator()
405{
406 Q_Q(QQuickAbstractButton);
407 quickCancelDeferred(object: q, property: indicatorName());
408}
409
410void QQuickAbstractButtonPrivate::executeIndicator(bool complete)
411{
412 Q_Q(QQuickAbstractButton);
413 if (indicator.wasExecuted())
414 return;
415
416 if (!indicator || complete)
417 quickBeginDeferred(object: q, property: indicatorName(), delegate&: indicator);
418 if (complete)
419 quickCompleteDeferred(object: q, property: indicatorName(), delegate&: indicator);
420}
421
422void QQuickAbstractButtonPrivate::itemImplicitWidthChanged(QQuickItem *item)
423{
424 Q_Q(QQuickAbstractButton);
425 QQuickControlPrivate::itemImplicitWidthChanged(item);
426 if (item == indicator)
427 emit q->implicitIndicatorWidthChanged();
428}
429
430void QQuickAbstractButtonPrivate::itemImplicitHeightChanged(QQuickItem *item)
431{
432 Q_Q(QQuickAbstractButton);
433 QQuickControlPrivate::itemImplicitHeightChanged(item);
434 if (item == indicator)
435 emit q->implicitIndicatorHeightChanged();
436}
437
438void QQuickAbstractButtonPrivate::itemDestroyed(QQuickItem *item)
439{
440 Q_Q(QQuickAbstractButton);
441 QQuickControlPrivate::itemDestroyed(item);
442 if (item == indicator) {
443 indicator = nullptr;
444 emit q->implicitIndicatorWidthChanged();
445 emit q->implicitIndicatorHeightChanged();
446 }
447}
448
449QQuickAbstractButton *QQuickAbstractButtonPrivate::findCheckedButton() const
450{
451 Q_Q(const QQuickAbstractButton);
452 if (group)
453 return group->checkedButton();
454
455 const QList<QQuickAbstractButton *> buttons = findExclusiveButtons();
456 // TODO: A singular QRadioButton can be unchecked, which seems logical,
457 // because there's nothing to be exclusive with. However, a RadioButton
458 // from QtQuick.Controls 1.x can never be unchecked, which is the behavior
459 // that QQuickRadioButton adopted. Uncommenting the following count check
460 // gives the QRadioButton behavior. Notice that tst_radiobutton.qml needs
461 // to be updated.
462 if (!autoExclusive /*|| buttons.count() == 1*/)
463 return nullptr;
464
465 for (QQuickAbstractButton *button : buttons) {
466 if (button->isChecked() && button != q)
467 return button;
468 }
469 return checked ? const_cast<QQuickAbstractButton *>(q) : nullptr;
470}
471
472QList<QQuickAbstractButton *> QQuickAbstractButtonPrivate::findExclusiveButtons() const
473{
474 QList<QQuickAbstractButton *> buttons;
475 if (group) {
476 QQmlListProperty<QQuickAbstractButton> groupButtons = group->buttons();
477 int count = groupButtons.count(&groupButtons);
478 for (int i = 0; i < count; ++i) {
479 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: groupButtons.at(&groupButtons, i));
480 if (button)
481 buttons += button;
482 }
483 } else if (parentItem) {
484 const auto childItems = parentItem->childItems();
485 for (QQuickItem *child : childItems) {
486 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: child);
487 if (button && button->autoExclusive() && !QQuickAbstractButtonPrivate::get(button)->group)
488 buttons += button;
489 }
490 }
491 return buttons;
492}
493
494QQuickAbstractButton::QQuickAbstractButton(QQuickItem *parent)
495 : QQuickControl(*(new QQuickAbstractButtonPrivate), parent)
496{
497 Q_D(QQuickAbstractButton);
498 d->init();
499}
500
501QQuickAbstractButton::QQuickAbstractButton(QQuickAbstractButtonPrivate &dd, QQuickItem *parent)
502 : QQuickControl(dd, parent)
503{
504 Q_D(QQuickAbstractButton);
505 d->init();
506}
507
508QQuickAbstractButton::~QQuickAbstractButton()
509{
510 Q_D(QQuickAbstractButton);
511 d->removeImplicitSizeListener(item: d->indicator);
512 if (d->group) {
513 auto *attached = qobject_cast<QQuickButtonGroupAttached *>(
514 object: qmlAttachedPropertiesObject<QQuickButtonGroup>(obj: this, create: false));
515 if (attached)
516 attached->setGroup(nullptr);
517 else
518 d->group->removeButton(button: this);
519 }
520#if QT_CONFIG(shortcut)
521 d->ungrabShortcut();
522#endif
523}
524
525/*!
526 \qmlproperty string QtQuick.Controls::AbstractButton::text
527
528 This property holds a textual description of the button.
529
530 \note The text is used for accessibility purposes, so it makes sense to
531 set a textual description even if the content item is an image.
532
533 \sa icon, display, {Control::contentItem}{contentItem}
534*/
535QString QQuickAbstractButton::text() const
536{
537 Q_D(const QQuickAbstractButton);
538 return d->explicitText || !d->action ? d->text : d->action->text();
539}
540
541void QQuickAbstractButton::setText(const QString &text)
542{
543 Q_D(QQuickAbstractButton);
544 d->setText(newText: text, propertyState: QQml::PropertyUtils::State::ExplicitlySet);
545}
546
547void QQuickAbstractButton::resetText()
548{
549 Q_D(QQuickAbstractButton);
550 d->setText(newText: QString(), propertyState: QQml::PropertyUtils::State::ImplicitlySet);
551}
552
553/*!
554 \qmlproperty bool QtQuick.Controls::AbstractButton::down
555
556 This property holds whether the button is visually down.
557
558 Unless explicitly set, this property follows the value of \l pressed. To
559 return to the default value, set this property to \c undefined.
560
561 \sa pressed
562*/
563bool QQuickAbstractButton::isDown() const
564{
565 Q_D(const QQuickAbstractButton);
566 return d->down;
567}
568
569void QQuickAbstractButton::setDown(bool down)
570{
571 Q_D(QQuickAbstractButton);
572 d->explicitDown = true;
573
574 if (d->down == down)
575 return;
576
577 d->down = down;
578 emit downChanged();
579}
580
581void QQuickAbstractButton::resetDown()
582{
583 Q_D(QQuickAbstractButton);
584 if (!d->explicitDown)
585 return;
586
587 setDown(d->pressed);
588 d->explicitDown = false;
589}
590
591/*!
592 \qmlproperty bool QtQuick.Controls::AbstractButton::pressed
593 \readonly
594
595 This property holds whether the button is physically pressed. A button can
596 be pressed by either touch or key events.
597
598 \sa down
599*/
600bool QQuickAbstractButton::isPressed() const
601{
602 Q_D(const QQuickAbstractButton);
603 return d->pressed;
604}
605
606void QQuickAbstractButton::setPressed(bool isPressed)
607{
608 Q_D(QQuickAbstractButton);
609 if (d->pressed == isPressed)
610 return;
611
612 d->pressed = isPressed;
613 setAccessibleProperty(propertyName: "pressed", value: isPressed);
614 emit pressedChanged();
615 buttonChange(change: ButtonPressedChanged);
616
617 if (!d->explicitDown) {
618 setDown(d->pressed);
619 d->explicitDown = false;
620 }
621}
622
623/*!
624 \qmlproperty bool QtQuick.Controls::AbstractButton::checked
625
626 This property holds whether the button is checked.
627
628 Since Qt 6.2, setting this property no longer affects the
629 \l {AbstractButton::}{checkable} property. Explicitly set the
630 \c checkable property if needed.
631
632 \sa checkable
633*/
634bool QQuickAbstractButton::isChecked() const
635{
636 Q_D(const QQuickAbstractButton);
637 return d->checked;
638}
639
640void QQuickAbstractButton::setChecked(bool checked)
641{
642 Q_D(QQuickAbstractButton);
643 if (d->checked == checked)
644 return;
645
646 d->checked = checked;
647 if (d->action)
648 d->action->setChecked(checked);
649 setAccessibleProperty(propertyName: "checked", value: checked);
650 buttonChange(change: ButtonCheckedChange);
651 emit checkedChanged();
652}
653
654/*!
655 \qmlproperty bool QtQuick.Controls::AbstractButton::checkable
656
657 This property holds whether the button is checkable.
658
659 A checkable button toggles between checked (on) and unchecked (off) when
660 the user clicks on it or presses the space bar while the button has active
661 focus.
662
663 The default value is \c false.
664
665 \sa checked
666*/
667bool QQuickAbstractButton::isCheckable() const
668{
669 Q_D(const QQuickAbstractButton);
670 return d->checkable;
671}
672
673void QQuickAbstractButton::setCheckable(bool checkable)
674{
675 Q_D(QQuickAbstractButton);
676 if (d->checkable == checkable)
677 return;
678
679 d->checkable = checkable;
680 if (d->action)
681 d->action->setCheckable(checkable);
682 setAccessibleProperty(propertyName: "checkable", value: checkable);
683 buttonChange(change: ButtonCheckableChange);
684 emit checkableChanged();
685}
686
687/*!
688 \qmlproperty bool QtQuick.Controls::AbstractButton::autoExclusive
689
690 This property holds whether auto-exclusivity is enabled.
691
692 If auto-exclusivity is enabled, checkable buttons that belong to the same
693 parent item behave as if they were part of the same ButtonGroup. Only
694 one button can be checked at any time; checking another button automatically
695 unchecks the previously checked one.
696
697 \note The property has no effect on buttons that belong to a ButtonGroup.
698
699 RadioButton and TabButton are auto-exclusive by default.
700*/
701bool QQuickAbstractButton::autoExclusive() const
702{
703 Q_D(const QQuickAbstractButton);
704 return d->autoExclusive;
705}
706
707void QQuickAbstractButton::setAutoExclusive(bool exclusive)
708{
709 Q_D(QQuickAbstractButton);
710 if (d->autoExclusive == exclusive)
711 return;
712
713 d->autoExclusive = exclusive;
714 emit autoExclusiveChanged();
715}
716
717/*!
718 \qmlproperty bool QtQuick.Controls::AbstractButton::autoRepeat
719
720 This property holds whether the button repeats \l pressed(), \l released()
721 and \l clicked() signals while the button is pressed and held down.
722
723 If this property is set to \c true, the \l pressAndHold() signal will not
724 be emitted.
725
726 The default value is \c false.
727
728 The initial delay and the repetition interval are defined in milliseconds
729 by \l autoRepeatDelay and \l autoRepeatInterval.
730*/
731bool QQuickAbstractButton::autoRepeat() const
732{
733 Q_D(const QQuickAbstractButton);
734 return d->autoRepeat;
735}
736
737void QQuickAbstractButton::setAutoRepeat(bool repeat)
738{
739 Q_D(QQuickAbstractButton);
740 if (d->autoRepeat == repeat)
741 return;
742
743 d->stopPressRepeat();
744 d->autoRepeat = repeat;
745 emit autoRepeatChanged();
746}
747
748/*!
749 \qmlproperty Item QtQuick.Controls::AbstractButton::indicator
750
751 This property holds the indicator item.
752*/
753QQuickItem *QQuickAbstractButton::indicator() const
754{
755 QQuickAbstractButtonPrivate *d = const_cast<QQuickAbstractButtonPrivate *>(d_func());
756 if (!d->indicator)
757 d->executeIndicator();
758 return d->indicator;
759}
760
761void QQuickAbstractButton::setIndicator(QQuickItem *indicator)
762{
763 Q_D(QQuickAbstractButton);
764 if (d->indicator == indicator)
765 return;
766
767 QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: indicator, QStringLiteral("indicator"));
768
769 if (!d->indicator.isExecuting())
770 d->cancelIndicator();
771
772 const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth();
773 const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight();
774
775 d->removeImplicitSizeListener(item: d->indicator);
776 QQuickControlPrivate::hideOldItem(item: d->indicator);
777 d->indicator = indicator;
778
779 if (indicator) {
780 if (!indicator->parentItem())
781 indicator->setParentItem(this);
782 indicator->setAcceptedMouseButtons(Qt::LeftButton);
783 d->addImplicitSizeListener(item: indicator);
784 }
785
786 if (!qFuzzyCompare(p1: oldImplicitIndicatorWidth, p2: implicitIndicatorWidth()))
787 emit implicitIndicatorWidthChanged();
788 if (!qFuzzyCompare(p1: oldImplicitIndicatorHeight, p2: implicitIndicatorHeight()))
789 emit implicitIndicatorHeightChanged();
790 if (!d->indicator.isExecuting())
791 emit indicatorChanged();
792}
793
794/*!
795 \qmlproperty string QtQuick.Controls::AbstractButton::icon.name
796 \qmlproperty url QtQuick.Controls::AbstractButton::icon.source
797 \qmlproperty int QtQuick.Controls::AbstractButton::icon.width
798 \qmlproperty int QtQuick.Controls::AbstractButton::icon.height
799 \qmlproperty color QtQuick.Controls::AbstractButton::icon.color
800 \qmlproperty bool QtQuick.Controls::AbstractButton::icon.cache
801
802 This property group was added in QtQuick.Controls 2.3.
803
804 \include qquickicon.qdocinc grouped-properties
805
806 \sa text, display, {Icons in Qt Quick Controls}
807*/
808
809QQuickIcon QQuickAbstractButton::icon() const
810{
811 Q_D(const QQuickAbstractButton);
812 return d->effectiveIcon;
813}
814
815void QQuickAbstractButton::setIcon(const QQuickIcon &icon)
816{
817 Q_D(QQuickAbstractButton);
818 d->icon = icon;
819 d->icon.ensureRelativeSourceResolved(owner: this);
820 d->updateEffectiveIcon();
821}
822
823/*!
824 \since QtQuick.Controls 2.3 (Qt 5.10)
825 \qmlproperty enumeration QtQuick.Controls::AbstractButton::display
826
827 This property determines how the \l icon and \l text are displayed within
828 the button.
829
830 \table
831 \header \li Display \li Result
832 \row \li \c AbstractButton.IconOnly \li \image qtquickcontrols-button-icononly.png
833 \row \li \c AbstractButton.TextOnly \li \image qtquickcontrols-button-textonly.png
834 \row \li \c AbstractButton.TextBesideIcon (default) \li \image qtquickcontrols-button-textbesideicon.png
835 \row \li \c AbstractButton.TextUnderIcon \li \image qtquickcontrols-button-textundericon.png
836 \endtable
837
838 \sa {Control::}{spacing}, {Control::}{padding}
839*/
840QQuickAbstractButton::Display QQuickAbstractButton::display() const
841{
842 Q_D(const QQuickAbstractButton);
843 return d->display;
844}
845
846void QQuickAbstractButton::setDisplay(Display display)
847{
848 Q_D(QQuickAbstractButton);
849 if (display == d->display)
850 return;
851
852 d->display = display;
853 emit displayChanged();
854}
855
856/*!
857 \since QtQuick.Controls 2.3 (Qt 5.10)
858 \qmlproperty Action QtQuick.Controls::AbstractButton::action
859
860 This property holds the button action.
861
862 \sa Action
863*/
864QQuickAction *QQuickAbstractButton::action() const
865{
866 Q_D(const QQuickAbstractButton);
867 return d->action;
868}
869
870void QQuickAbstractButton::setAction(QQuickAction *action)
871{
872 Q_D(QQuickAbstractButton);
873 if (d->action == action)
874 return;
875
876 const QString oldText = text();
877
878 if (QQuickAction *oldAction = d->action.data()) {
879 QQuickActionPrivate::get(action: oldAction)->unregisterItem(item: this);
880 QObjectPrivate::disconnect(sender: oldAction, signal: &QQuickAction::triggered, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::click);
881 QObjectPrivate::disconnect(sender: oldAction, signal: &QQuickAction::textChanged, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::actionTextChange);
882
883 QObjectPrivate::disconnect(sender: oldAction, signal: &QQuickAction::iconChanged, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::updateEffectiveIcon);
884 disconnect(sender: oldAction, signal: &QQuickAction::checkedChanged, receiver: this, slot: &QQuickAbstractButton::setChecked);
885 disconnect(sender: oldAction, signal: &QQuickAction::checkableChanged, receiver: this, slot: &QQuickAbstractButton::setCheckable);
886 disconnect(sender: oldAction, signal: &QQuickAction::enabledChanged, receiver: this, slot: &QQuickItem::setEnabled);
887 }
888
889 if (action) {
890 QQuickActionPrivate::get(action)->registerItem(item: this);
891 QObjectPrivate::connect(sender: action, signal: &QQuickAction::triggered, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::click);
892 QObjectPrivate::connect(sender: action, signal: &QQuickAction::textChanged, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::actionTextChange);
893
894 QObjectPrivate::connect(sender: action, signal: &QQuickAction::iconChanged, receiverPrivate: d, slot: &QQuickAbstractButtonPrivate::updateEffectiveIcon);
895 connect(sender: action, signal: &QQuickAction::checkedChanged, context: this, slot: &QQuickAbstractButton::setChecked);
896 connect(sender: action, signal: &QQuickAction::checkableChanged, context: this, slot: &QQuickAbstractButton::setCheckable);
897 connect(sender: action, signal: &QQuickAction::enabledChanged, context: this, slot: &QQuickItem::setEnabled);
898
899 setChecked(action->isChecked());
900 setCheckable(action->isCheckable());
901 setEnabled(action->isEnabled());
902 }
903
904#if QT_CONFIG(accessibility)
905 auto attached = qobject_cast<QQuickAccessibleAttached*>(object: qmlAttachedPropertiesObject<QQuickAccessibleAttached>(obj: this, create: true));
906 Q_ASSERT(attached);
907 attached->setProxying(qobject_cast<QQuickAccessibleAttached*>(object: qmlAttachedPropertiesObject<QQuickAccessibleAttached>(obj: action, create: true)));
908#endif
909
910 d->action = action;
911
912 if (oldText != text())
913 buttonChange(change: ButtonTextChange);
914
915 d->updateEffectiveIcon();
916
917 emit actionChanged();
918}
919
920/*!
921 \since QtQuick.Controls 2.4 (Qt 5.11)
922 \qmlproperty int QtQuick.Controls::AbstractButton::autoRepeatDelay
923
924 This property holds the initial delay of auto-repetition in milliseconds.
925 The default value is \c 300 ms.
926
927 \sa autoRepeat, autoRepeatInterval
928*/
929int QQuickAbstractButton::autoRepeatDelay() const
930{
931 Q_D(const QQuickAbstractButton);
932 return d->repeatDelay;
933}
934
935void QQuickAbstractButton::setAutoRepeatDelay(int delay)
936{
937 Q_D(QQuickAbstractButton);
938 if (d->repeatDelay == delay)
939 return;
940
941 d->repeatDelay = delay;
942 emit autoRepeatDelayChanged();
943}
944
945/*!
946 \since QtQuick.Controls 2.4 (Qt 5.11)
947 \qmlproperty int QtQuick.Controls::AbstractButton::autoRepeatInterval
948
949 This property holds the interval of auto-repetition in milliseconds.
950 The default value is \c 100 ms.
951
952 \sa autoRepeat, autoRepeatDelay
953*/
954int QQuickAbstractButton::autoRepeatInterval() const
955{
956 Q_D(const QQuickAbstractButton);
957 return d->repeatInterval;
958}
959
960void QQuickAbstractButton::setAutoRepeatInterval(int interval)
961{
962 Q_D(QQuickAbstractButton);
963 if (d->repeatInterval == interval)
964 return;
965
966 d->repeatInterval = interval;
967 emit autoRepeatIntervalChanged();
968}
969
970#if QT_CONFIG(shortcut)
971QKeySequence QQuickAbstractButton::shortcut() const
972{
973 Q_D(const QQuickAbstractButton);
974 return d->shortcut;
975}
976
977void QQuickAbstractButton::setShortcut(const QKeySequence &shortcut)
978{
979 Q_D(QQuickAbstractButton);
980 if (d->shortcut == shortcut)
981 return;
982
983 d->ungrabShortcut();
984 d->shortcut = shortcut;
985 if (isVisible())
986 d->grabShortcut();
987}
988#endif
989
990/*!
991 \readonly
992 \since QtQuick.Controls 2.4 (Qt 5.11)
993 \qmlproperty real QtQuick.Controls::AbstractButton::pressX
994
995 This property holds the x-coordinate of the last press.
996
997 \note The value is updated on touch moves, but left intact after touch release.
998
999 \sa pressY
1000*/
1001qreal QQuickAbstractButton::pressX() const
1002{
1003 Q_D(const QQuickAbstractButton);
1004 return d->movePoint.x();
1005}
1006
1007/*!
1008 \readonly
1009 \since QtQuick.Controls 2.4 (Qt 5.11)
1010 \qmlproperty real QtQuick.Controls::AbstractButton::pressY
1011
1012 This property holds the y-coordinate of the last press.
1013
1014 \note The value is updated on touch moves, but left intact after touch release.
1015
1016 \sa pressX
1017*/
1018qreal QQuickAbstractButton::pressY() const
1019{
1020 Q_D(const QQuickAbstractButton);
1021 return d->movePoint.y();
1022}
1023
1024/*!
1025 \since QtQuick.Controls 2.5 (Qt 5.12)
1026 \qmlproperty real QtQuick.Controls::AbstractButton::implicitIndicatorWidth
1027 \readonly
1028
1029 This property holds the implicit indicator width.
1030
1031 The value is equal to \c {indicator ? indicator.implicitWidth : 0}.
1032
1033 This is typically used, together with \l {Control::}{implicitContentWidth} and
1034 \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}.
1035
1036 \sa implicitIndicatorHeight
1037*/
1038qreal QQuickAbstractButton::implicitIndicatorWidth() const
1039{
1040 Q_D(const QQuickAbstractButton);
1041 if (!d->indicator)
1042 return 0;
1043 return d->indicator->implicitWidth();
1044}
1045
1046/*!
1047 \since QtQuick.Controls 2.5 (Qt 5.12)
1048 \qmlproperty real QtQuick.Controls::AbstractButton::implicitIndicatorHeight
1049 \readonly
1050
1051 This property holds the implicit indicator height.
1052
1053 The value is equal to \c {indicator ? indicator.implicitHeight : 0}.
1054
1055 This is typically used, together with \l {Control::}{implicitContentHeight} and
1056 \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}.
1057
1058 \sa implicitIndicatorWidth
1059*/
1060qreal QQuickAbstractButton::implicitIndicatorHeight() const
1061{
1062 Q_D(const QQuickAbstractButton);
1063 if (!d->indicator)
1064 return 0;
1065 return d->indicator->implicitHeight();
1066}
1067
1068/*!
1069 \qmlmethod void QtQuick.Controls::AbstractButton::toggle()
1070
1071 Toggles the checked state of the button.
1072
1073 \sa click(), animateClick()
1074*/
1075void QQuickAbstractButton::toggle()
1076{
1077 Q_D(QQuickAbstractButton);
1078 setChecked(!d->checked);
1079}
1080
1081/*!
1082 \since Qt 6.8
1083 \qmlmethod void QtQuick.Controls::AbstractButton::click()
1084
1085 Simulates the button being clicked with no delay between press and release.
1086
1087 All signals associated with a click are emitted as appropriate.
1088
1089 If the \l [QML] {Item::} {focusPolicy} includes \c Qt.ClickFocus,
1090 \l [QML] {Item::} {activeFocus} will become \c true.
1091
1092 This function does nothing if the button is
1093 \l [QML] {Item::enabled} {disabled}.
1094
1095 Calling this function again before the button is released resets
1096 the release timer.
1097
1098 \sa animateClick(), pressed(), released(), clicked()
1099*/
1100void QQuickAbstractButton::click()
1101{
1102 Q_D(QQuickAbstractButton);
1103 if (!isEnabled())
1104 return;
1105
1106 // QQuickItemPrivate::deliverPointerEvent calls setFocusIfNeeded on real clicks,
1107 // so we need to do it ourselves.
1108 const bool setFocusOnPress = !QGuiApplication::styleHints()->setFocusOnTouchRelease();
1109 if (setFocusOnPress && focusPolicy() & Qt::ClickFocus)
1110 forceActiveFocus(reason: Qt::MouseFocusReason);
1111
1112 const QPointF eventPos(d->width / 2, d->height / 2);
1113 d->handlePress(point: eventPos, timestamp: 0);
1114 d->handleRelease(point: eventPos, timestamp: 0);
1115}
1116
1117/*!
1118 \since Qt 6.8
1119 \qmlmethod void QtQuick.Controls::AbstractButton::animateClick()
1120
1121 Simulates the button being clicked, with a 100 millisecond delay
1122 between press and release, animating its visual state in the
1123 process.
1124
1125 All signals associated with a click are emitted as appropriate.
1126
1127 If the \l [QML] {Item::} {focusPolicy} includes \c Qt.ClickFocus,
1128 \l [QML] {Item::}{activeFocus} will become \c true.
1129
1130 This function does nothing if the button is
1131 \l [QML] {Item::enabled} {disabled}.
1132
1133 Calling this function again before the button is released resets
1134 the release timer.
1135
1136 \sa click(), pressed(), released(), clicked()
1137*/
1138void QQuickAbstractButton::animateClick()
1139{
1140 Q_D(QQuickAbstractButton);
1141 if (!isEnabled())
1142 return;
1143
1144 // See comment in click() for why we do this.
1145 const bool setFocusOnPress = !QGuiApplication::styleHints()->setFocusOnTouchRelease();
1146 if (setFocusOnPress && focusPolicy() & Qt::ClickFocus)
1147 forceActiveFocus(reason: Qt::MouseFocusReason);
1148
1149 // If the timer was already running, kill it so we can restart it.
1150 if (d->animateTimer != 0) {
1151 killTimer(id: d->animateTimer);
1152 d->animateTimer = 0;
1153 } else {
1154 d->handlePress(point: QPointF(d->width / 2, d->height / 2), timestamp: 0);
1155 }
1156
1157 d->animateTimer = startTimer(interval: 100);
1158}
1159
1160void QQuickAbstractButton::componentComplete()
1161{
1162 Q_D(QQuickAbstractButton);
1163 d->executeIndicator(complete: true);
1164 QQuickControl::componentComplete();
1165}
1166
1167bool QQuickAbstractButton::event(QEvent *event)
1168{
1169#if QT_CONFIG(shortcut)
1170 Q_D(QQuickAbstractButton);
1171 if (event->type() == QEvent::Shortcut) {
1172 QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
1173 if (se->shortcutId() == d->shortcutId) {
1174 d->trigger();
1175 return true;
1176 }
1177 }
1178#endif
1179 return QQuickControl::event(event);
1180}
1181
1182void QQuickAbstractButton::focusOutEvent(QFocusEvent *event)
1183{
1184 Q_D(QQuickAbstractButton);
1185 QQuickControl::focusOutEvent(event);
1186 if (d->touchId == -1) // don't ungrab on multi-touch if another control gets focused
1187 d->handleUngrab();
1188}
1189
1190void QQuickAbstractButton::keyPressEvent(QKeyEvent *event)
1191{
1192 Q_D(QQuickAbstractButton);
1193 QQuickControl::keyPressEvent(event);
1194 if (d->acceptKeyClick(key: static_cast<Qt::Key>(event->key()))) {
1195 d->setPressPoint(d->centerPressPoint());
1196 setPressed(true);
1197
1198 if (d->autoRepeat)
1199 d->startRepeatDelay();
1200
1201 emit pressed();
1202 event->accept();
1203 }
1204}
1205
1206void QQuickAbstractButton::keyReleaseEvent(QKeyEvent *event)
1207{
1208 Q_D(QQuickAbstractButton);
1209 QQuickControl::keyReleaseEvent(event);
1210 if (d->pressed && d->acceptKeyClick(key: static_cast<Qt::Key>(event->key()))) {
1211 setPressed(false);
1212
1213 nextCheckState();
1214 emit released();
1215 d->trigger();
1216
1217 if (d->autoRepeat)
1218 d->stopPressRepeat();
1219 event->accept();
1220 }
1221}
1222
1223void QQuickAbstractButton::mousePressEvent(QMouseEvent *event)
1224{
1225 if (!(event->buttons() & Qt::LeftButton)) {
1226 event->ignore();
1227 return;
1228 }
1229
1230 Q_D(QQuickAbstractButton);
1231 d->pressButtons = event->buttons();
1232 QQuickControl::mousePressEvent(event);
1233}
1234
1235void QQuickAbstractButton::mouseDoubleClickEvent(QMouseEvent *event)
1236{
1237 Q_UNUSED(event);
1238 Q_D(QQuickAbstractButton);
1239 if (d->isDoubleClickConnected()) {
1240 // don't call QQuickItem::mouseDoubleClickEvent(): it would ignore()
1241 emit doubleClicked();
1242 d->wasDoubleClick = true;
1243 }
1244}
1245
1246void QQuickAbstractButton::timerEvent(QTimerEvent *event)
1247{
1248 Q_D(QQuickAbstractButton);
1249 QQuickControl::timerEvent(event);
1250 if (event->timerId() == d->holdTimer) {
1251 d->stopPressAndHold();
1252 d->wasHeld = true;
1253 emit pressAndHold();
1254 } else if (event->timerId() == d->delayTimer) {
1255 d->startPressRepeat();
1256 } else if (event->timerId() == d->repeatTimer) {
1257 emit released();
1258 d->trigger();
1259 emit pressed();
1260 } else if (event->timerId() == d->animateTimer) {
1261 const bool setFocusOnRelease = QGuiApplication::styleHints()->setFocusOnTouchRelease();
1262 if (setFocusOnRelease && focusPolicy() & Qt::ClickFocus)
1263 forceActiveFocus(reason: Qt::MouseFocusReason);
1264 d->handleRelease(point: QPointF(d->width / 2, d->height / 2), timestamp: 0);
1265 killTimer(id: d->animateTimer);
1266 d->animateTimer = 0;
1267 }
1268}
1269
1270void QQuickAbstractButton::itemChange(ItemChange change, const ItemChangeData &value)
1271{
1272 QQuickControl::itemChange(change, value);
1273#if QT_CONFIG(shortcut)
1274 Q_D(QQuickAbstractButton);
1275 if (change == ItemVisibleHasChanged) {
1276 if (value.boolValue)
1277 d->grabShortcut();
1278 else
1279 d->ungrabShortcut();
1280 }
1281#endif
1282}
1283
1284void QQuickAbstractButton::buttonChange(ButtonChange change)
1285{
1286 Q_D(QQuickAbstractButton);
1287 switch (change) {
1288 case ButtonCheckedChange:
1289 if (d->checked) {
1290 QQuickAbstractButton *button = d->findCheckedButton();
1291 if (button && button != this)
1292 button->setChecked(false);
1293 }
1294 break;
1295 case ButtonTextChange: {
1296 const QString txt = text();
1297#if QT_CONFIG(accessibility)
1298 maybeSetAccessibleName(name: qt_accStripAmp(text: txt));
1299#endif
1300#if QT_CONFIG(shortcut)
1301 setShortcut(QKeySequence::mnemonic(text: txt));
1302#endif
1303 emit textChanged();
1304 break;
1305 }
1306 default:
1307 break;
1308 }
1309}
1310
1311void QQuickAbstractButton::nextCheckState()
1312{
1313 Q_D(QQuickAbstractButton);
1314 if (!d->checkable)
1315 return;
1316
1317 if (d->checked) {
1318 if (d->findCheckedButton() == this)
1319 return;
1320 if (d->action) {
1321 // For non-exclusive groups checkedAction is null
1322 if (const auto group = QQuickActionPrivate::get(action: d->action)->group)
1323 if (group->checkedAction() == d->action)
1324 return;
1325 }
1326 }
1327
1328 d->toggle(value: !d->checked);
1329}
1330
1331#if QT_CONFIG(accessibility)
1332void QQuickAbstractButton::accessibilityActiveChanged(bool active)
1333{
1334 QQuickControl::accessibilityActiveChanged(active);
1335
1336 Q_D(QQuickAbstractButton);
1337 if (active) {
1338 maybeSetAccessibleName(name: qt_accStripAmp(text: text()));
1339 setAccessibleProperty(propertyName: "pressed", value: d->pressed);
1340 setAccessibleProperty(propertyName: "checked", value: d->checked);
1341 setAccessibleProperty(propertyName: "checkable", value: d->checkable);
1342 }
1343}
1344
1345QAccessible::Role QQuickAbstractButton::accessibleRole() const
1346{
1347 Q_D(const QQuickAbstractButton);
1348 if (d->checkable) {
1349 return QAccessible::CheckBox;
1350 }
1351 return QAccessible::Button;
1352}
1353
1354void QQuickAbstractButton::accessiblePressAction()
1355{
1356 Q_D(QQuickAbstractButton);
1357 d->accessiblePressAction();
1358}
1359#endif
1360
1361QT_END_NAMESPACE
1362
1363#include "moc_qquickabstractbutton_p.cpp"
1364

source code of qtdeclarative/src/quicktemplates/qquickabstractbutton.cpp