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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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