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 "qquickdial_p.h"
5#include "qquickdeferredexecute_p_p.h"
6
7#include <QtCore/qmath.h>
8#include <QtQuick/private/qquickflickable_p.h>
9#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
10
11#include <cmath>
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype Dial
17 \inherits Control
18//! \instantiates QQuickDial
19 \inqmlmodule QtQuick.Controls
20 \since 5.7
21 \ingroup qtquickcontrols-input
22 \brief Circular dial that is rotated to set a value.
23
24 The Dial is similar to a traditional dial knob that is found on devices
25 such as stereos or industrial equipment. It allows the user to specify a
26 value within a range.
27
28 \image qtquickcontrols-dial-no-wrap.gif
29
30 The value of the dial is set with the \l value property. The range is
31 set with the \l from and \l to properties. To enable or disable wrapping,
32 use the \l wrap property.
33
34 The dial can be manipulated with a keyboard. It supports the following
35 actions:
36
37 \table
38 \header \li \b {Action} \li \b {Key}
39 \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left
40 \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down
41 \row \li Set \l value to \l from \li \c Qt.Key_Home
42 \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right
43 \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up
44 \row \li Set \l value to \l to \li \c Qt.Key_End
45 \endtable
46
47 \include qquickdial.qdocinc inputMode
48
49 \sa {Customizing Dial}, {Input Controls}
50*/
51
52/*!
53 \since QtQuick.Controls 2.2 (Qt 5.9)
54 \qmlsignal QtQuick.Controls::Dial::moved()
55
56 This signal is emitted when the dial has been interactively moved
57 by the user by either touch, mouse, or keys.
58*/
59
60/*!
61 \qmlsignal QtQuick.Controls::Dial::wrapped(Dial.WrapDirection direction)
62 \since 6.6
63
64 This signal is emitted when the dial wraps around, i.e. goes beyond its
65 maximum value to its minimum value, or vice versa. It is only emitted when
66 \l wrap is \c true.
67 The \a direction argument specifies the direction of the full rotation and
68 will be one of the following arguments:
69
70 \value Dial.Clockwise The dial wrapped in clockwise direction.
71 \value Dial.CounterClockwise The dial wrapped in counterclockwise direction.
72*/
73
74// The user angle is the clockwise angle between the position and the vertical
75// y-axis (12 o clock position).
76// Using radians for logic (atan2(...)) and degree for user interface
77constexpr qreal toUserAngleDeg(qreal logicAngleRad) {
78 // minus to turn clockwise, add 90 deg clockwise
79 return -logicAngleRad / M_PI * 180. + 90;
80}
81
82static const qreal defaultStartAngle = -140;
83static const qreal defaultEndAngle = 140;
84
85class QQuickDialPrivate : public QQuickControlPrivate
86{
87 Q_DECLARE_PUBLIC(QQuickDial)
88
89public:
90 qreal valueAt(qreal position) const;
91 qreal snapPosition(qreal position) const;
92 qreal positionAt(const QPointF &point) const;
93 qreal circularPositionAt(const QPointF &point) const;
94 qreal linearPositionAt(const QPointF &point) const;
95 void setPosition(qreal position);
96 void updatePosition();
97 bool isLargeChange(qreal proposedPosition) const;
98 bool isHorizontalOrVertical() const;
99
100 bool handlePress(const QPointF &point, ulong timestamp) override;
101 bool handleMove(const QPointF &point, ulong timestamp) override;
102 bool handleRelease(const QPointF &point, ulong timestamp) override;
103 void handleUngrab() override;
104
105 void cancelHandle();
106 void executeHandle(bool complete = false);
107
108 void updateAllValuesAreInteger();
109
110 void maybeEmitWrapAround(qreal pos);
111
112 qreal from = 0;
113 qreal to = 1;
114 qreal value = 0;
115 qreal position = 0;
116 qreal startAngle = defaultStartAngle;
117 qreal endAngle = defaultEndAngle;
118 qreal angle = startAngle;
119 qreal stepSize = 0;
120 QPointF pressPoint;
121 qreal positionBeforePress = 0;
122 QQuickDial::SnapMode snapMode = QQuickDial::NoSnap;
123 QQuickDial::InputMode inputMode = QQuickDial::Circular;
124 QQuickDeferredPointer<QQuickItem> handle;
125 bool wrap = false;
126 bool live = true;
127 bool pressed = false;
128 bool allValuesAreInteger = false;
129};
130
131qreal QQuickDialPrivate::valueAt(qreal position) const
132{
133 qreal value = from + (to - from) * position;
134
135 /* play nice with users expecting that integer from, to and stepSize leads to
136 integer values - given that we are using floating point internally (and in
137 the API of value), this does not hold, but it is easy enough to handle
138 */
139 if (allValuesAreInteger)
140 value = qRound(d: value);
141
142 return value;
143}
144
145qreal QQuickDialPrivate::snapPosition(qreal position) const
146{
147 const qreal range = to - from;
148 if (qFuzzyIsNull(d: range))
149 return position;
150
151 const qreal effectiveStep = stepSize / range;
152 if (qFuzzyIsNull(d: effectiveStep))
153 return position;
154
155 return qRound(d: position / effectiveStep) * effectiveStep;
156}
157
158qreal QQuickDialPrivate::positionAt(const QPointF &point) const
159{
160 return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point);
161}
162
163qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const
164{
165 qreal yy = height / 2.0 - point.y();
166 qreal xx = point.x() - width / 2.0;
167 qreal alpha = (xx || yy) ? toUserAngleDeg(logicAngleRad: std::atan2(y: yy, x: xx)) : 0;
168
169 // Move around the circle to reach the interval.
170 if (alpha < startAngle && alpha + 360. < endAngle)
171 alpha += 360.;
172 else if (alpha >= endAngle && alpha - 360. >= startAngle)
173 alpha -= 360.;
174
175 // If wrap is on and we are out of the interval [startAngle, endAngle],
176 // we want to jump to the closest border to make it feel nice and responsive
177 if ((alpha < startAngle || alpha > endAngle) && wrap) {
178 if (abs(x: alpha - startAngle) > abs(x: endAngle - alpha - 360.))
179 alpha += 360.;
180 else if (abs(x: alpha - startAngle - 360.) < abs(x: endAngle - alpha))
181 alpha -= 360.;
182 }
183
184 // If wrap is off,
185 // we want to stay as close as possible to the current angle.
186 // This is important to allow easy setting of boundary values (0,1)
187 if (!wrap) {
188 if (abs(x: angle - alpha) > abs(x: angle - (alpha + 360.)))
189 alpha += 360.;
190 if (abs(x: angle - alpha) > abs(x: angle - (alpha - 360.)))
191 alpha -= 360.;
192 }
193
194 return (alpha - startAngle) / (endAngle - startAngle);
195}
196
197qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const
198{
199 // This value determines the range (either horizontal or vertical)
200 // within which the dial can be dragged.
201 // The larger this value is, the further the drag distance
202 // must be to go from a position of e.g. 0.0 to 1.0.
203 qreal dragArea = 0;
204
205 // The linear input mode uses a "relative" input system,
206 // where the distance from the press point is used to calculate
207 // the change in position. Moving the mouse above the press
208 // point increases the position (when inputMode is Vertical),
209 // and vice versa. This prevents the dial from jumping when clicked.
210 qreal dragDistance = 0;
211
212 if (inputMode == QQuickDial::Horizontal) {
213 dragArea = width * 2;
214 dragDistance = pressPoint.x() - point.x();
215 } else {
216 dragArea = height * 2;
217 dragDistance = point.y() - pressPoint.y();
218 }
219 const qreal normalisedDifference = dragDistance / dragArea;
220 return qBound(min: qreal(0), val: positionBeforePress - normalisedDifference, max: qreal(1));
221}
222
223void QQuickDialPrivate::setPosition(qreal pos)
224{
225 Q_Q(QQuickDial);
226 pos = qBound<qreal>(min: qreal(0), val: pos, max: qreal(1));
227 const qreal alpha = startAngle + pos * qAbs(t: endAngle - startAngle);
228 if (qFuzzyCompare(p1: position, p2: pos) && qFuzzyCompare(p1: angle, p2: alpha))
229 return;
230
231 angle = alpha;
232 position = pos;
233
234
235 emit q->positionChanged();
236 emit q->angleChanged();
237}
238
239void QQuickDialPrivate::updatePosition()
240{
241 qreal pos = 0;
242 if (!qFuzzyCompare(p1: from, p2: to))
243 pos = (value - from) / (to - from);
244 setPosition(pos);
245}
246
247bool QQuickDialPrivate::isLargeChange(qreal proposedPosition) const
248{
249 if (endAngle - startAngle < 180.0)
250 return false;
251 return qAbs(t: proposedPosition - position) > qreal(0.5);
252}
253
254bool QQuickDialPrivate::isHorizontalOrVertical() const
255{
256 return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical;
257}
258
259bool QQuickDialPrivate::handlePress(const QPointF &point, ulong timestamp)
260{
261 Q_Q(QQuickDial);
262 QQuickControlPrivate::handlePress(point, timestamp);
263 pressPoint = point;
264 positionBeforePress = position;
265 q->setPressed(true);
266 return true;
267}
268
269bool QQuickDialPrivate::handleMove(const QPointF &point, ulong timestamp)
270{
271 Q_Q(QQuickDial);
272 QQuickControlPrivate::handleMove(point, timestamp);
273 const qreal oldPos = position;
274 qreal pos = qBound(min: 0.0, val: positionAt(point), max: 1.0);
275 if (snapMode == QQuickDial::SnapAlways)
276 pos = snapPosition(position: pos);
277
278 maybeEmitWrapAround(pos);
279
280 if (wrap || isHorizontalOrVertical() || !isLargeChange(proposedPosition: pos)) {
281 if (live)
282 q->setValue(valueAt(position: pos));
283 else
284 setPosition(pos);
285 if (!qFuzzyCompare(p1: pos, p2: oldPos))
286 emit q->moved();
287 }
288 return true;
289}
290
291bool QQuickDialPrivate::handleRelease(const QPointF &point, ulong timestamp)
292{
293 Q_Q(QQuickDial);
294 QQuickControlPrivate::handleRelease(point, timestamp);
295 if (q->keepMouseGrab() || q->keepTouchGrab()) {
296 const qreal oldPos = position;
297 qreal pos = positionAt(point);
298 if (snapMode != QQuickDial::NoSnap)
299 pos = snapPosition(position: pos);
300
301 maybeEmitWrapAround(pos);
302
303 if (wrap || isHorizontalOrVertical() || !isLargeChange(proposedPosition: pos))
304 q->setValue(valueAt(position: pos));
305 if (!qFuzzyCompare(p1: pos, p2: oldPos))
306 emit q->moved();
307
308 q->setKeepMouseGrab(false);
309 q->setKeepTouchGrab(false);
310 }
311
312 q->setPressed(false);
313 pressPoint = QPointF();
314 positionBeforePress = 0;
315 return true;
316}
317
318void QQuickDialPrivate::handleUngrab()
319{
320 Q_Q(QQuickDial);
321 QQuickControlPrivate::handleUngrab();
322 pressPoint = QPointF();
323 positionBeforePress = 0;
324 q->setPressed(false);
325}
326
327void QQuickDialPrivate::cancelHandle()
328{
329 Q_Q(QQuickDial);
330 quickCancelDeferred(object: q, property: handleName());
331}
332
333void QQuickDialPrivate::executeHandle(bool complete)
334{
335 Q_Q(QQuickDial);
336 if (handle.wasExecuted())
337 return;
338
339 if (!handle || complete)
340 quickBeginDeferred(object: q, property: handleName(), delegate&: handle);
341 if (complete)
342 quickCompleteDeferred(object: q, property: handleName(), delegate&: handle);
343}
344
345template<typename ...Real>
346static bool areRepresentableAsInteger(Real... numbers) {
347 auto check = [](qreal number) -> bool { return std::nearbyint(x: number) == number; };
348 return (... && check(numbers));
349}
350
351void QQuickDialPrivate::updateAllValuesAreInteger()
352{
353 allValuesAreInteger = areRepresentableAsInteger(numbers: to, numbers: from, numbers: stepSize) && stepSize != 0.0;
354}
355
356void QQuickDialPrivate::maybeEmitWrapAround(qreal pos)
357{
358 Q_Q(QQuickDial);
359
360 if (wrap && isLargeChange(proposedPosition: pos))
361 emit q->wrapped((pos < q->position()) ? QQuickDial::Clockwise : QQuickDial::CounterClockwise);
362}
363
364QQuickDial::QQuickDial(QQuickItem *parent)
365 : QQuickControl(*(new QQuickDialPrivate), parent)
366{
367 setActiveFocusOnTab(true);
368 setAcceptedMouseButtons(Qt::LeftButton);
369#if QT_CONFIG(quicktemplates2_multitouch)
370 setAcceptTouchEvents(true);
371#endif
372#if QT_CONFIG(cursor)
373 setCursor(Qt::ArrowCursor);
374#endif
375}
376
377/*!
378 \qmlproperty real QtQuick.Controls::Dial::from
379
380 This property holds the starting value for the range. The default value is \c 0.0.
381
382 \sa to, value
383*/
384qreal QQuickDial::from() const
385{
386 Q_D(const QQuickDial);
387 return d->from;
388}
389
390void QQuickDial::setFrom(qreal from)
391{
392 Q_D(QQuickDial);
393 if (qFuzzyCompare(p1: d->from, p2: from))
394 return;
395
396 d->from = from;
397 emit fromChanged();
398 d->updateAllValuesAreInteger();
399 if (isComponentComplete()) {
400 setValue(d->value);
401 d->updatePosition();
402 }
403}
404
405/*!
406 \qmlproperty real QtQuick.Controls::Dial::to
407
408 This property holds the end value for the range. The default value is
409 \c 1.0.
410
411 \sa from, value
412*/
413qreal QQuickDial::to() const
414{
415 Q_D(const QQuickDial);
416 return d->to;
417}
418
419void QQuickDial::setTo(qreal to)
420{
421 Q_D(QQuickDial);
422 if (qFuzzyCompare(p1: d->to, p2: to))
423 return;
424
425 d->to = to;
426 d->updateAllValuesAreInteger();
427 emit toChanged();
428 if (isComponentComplete()) {
429 setValue(d->value);
430 d->updatePosition();
431 }
432}
433
434/*!
435 \qmlproperty real QtQuick.Controls::Dial::value
436
437 This property holds the value in the range \c from - \c to. The default
438 value is \c 0.0.
439
440 \sa position, live
441*/
442qreal QQuickDial::value() const
443{
444 Q_D(const QQuickDial);
445 return d->value;
446}
447
448void QQuickDial::setValue(qreal value)
449{
450 Q_D(QQuickDial);
451 if (isComponentComplete())
452 value = d->from > d->to ? qBound(min: d->to, val: value, max: d->from) : qBound(min: d->from, val: value, max: d->to);
453
454 if (qFuzzyCompare(p1: d->value, p2: value))
455 return;
456
457 d->value = value;
458 d->updatePosition();
459 emit valueChanged();
460}
461
462/*!
463 \qmlproperty real QtQuick.Controls::Dial::position
464 \readonly
465
466 This property holds the logical position of the handle.
467
468 The position is expressed as a fraction of the control's angle range (the
469 range within which the handle can be moved) in the range \c {0.0 - 1.0}.
470
471 \sa value, angle
472*/
473qreal QQuickDial::position() const
474{
475 Q_D(const QQuickDial);
476 return d->position;
477}
478
479/*!
480 \qmlproperty real QtQuick.Controls::Dial::angle
481 \readonly
482
483 This property holds the clockwise angle of the handle in degrees.
484
485 The angle is zero at the 12 o'clock position and the range is from
486 \l startAngle to \c endAngle.
487
488 \sa position, startAngle, endAngle
489*/
490qreal QQuickDial::angle() const
491{
492 Q_D(const QQuickDial);
493 return d->angle;
494}
495
496/*!
497 \qmlproperty real QtQuick.Controls::Dial::stepSize
498
499 This property holds the step size.
500
501 The step size determines the amount by which the dial's value
502 is increased and decreased when interacted with via the keyboard.
503 For example, a step size of \c 0.2, will result in the dial's
504 value increasing and decreasing in increments of \c 0.2.
505
506 The step size is only respected for touch and mouse interaction
507 when \l snapMode is set to a value other than \c Dial.NoSnap.
508
509 The default value is \c 0.0, which results in an effective step
510 size of \c 0.1 for keyboard interaction.
511
512 \sa snapMode, increase(), decrease()
513*/
514qreal QQuickDial::stepSize() const
515{
516 Q_D(const QQuickDial);
517 return d->stepSize;
518}
519
520void QQuickDial::setStepSize(qreal step)
521{
522 Q_D(QQuickDial);
523 if (qFuzzyCompare(p1: d->stepSize, p2: step))
524 return;
525
526 d->stepSize = step;
527 d->updateAllValuesAreInteger();
528 emit stepSizeChanged();
529}
530
531
532/*!
533 \qmlproperty real QtQuick.Controls::Dial::startAngle
534 \since 6.6
535
536 This property holds the starting angle of the dial in degrees.
537
538 This is the \l angle the dial will have for its minimum value, i.e. \l from.
539 The \l startAngle has to be smaller than the \l endAngle, larger than -360
540 and larger or equal to the \l endAngle - 360 degrees.
541
542 \sa endAngle, angle
543*/
544qreal QQuickDial::startAngle() const
545{
546 Q_D(const QQuickDial);
547 return d->startAngle;
548}
549
550void QQuickDial::setStartAngle(qreal startAngle)
551{
552 Q_D(QQuickDial);
553 if (!d->componentComplete) {
554 // Binding evaluation order can cause warnings with certain combinations
555 // of start and end angles, so delay the actual setting until after component completion.
556 // Store the requested value in the existing member to avoid the need for an extra one.
557 d->startAngle = startAngle;
558 return;
559 }
560
561 if (qFuzzyCompare(p1: d->startAngle, p2: startAngle))
562 return;
563
564 // do not allow to change direction
565 if (startAngle >= d->endAngle) {
566 qmlWarning(me: this) << "startAngle (" << startAngle
567 << ") cannot be greater than or equal to endAngle (" << d->endAngle << ")";
568 return;
569 }
570
571 // Keep the interval around 0
572 if (startAngle <= -360.) {
573 qmlWarning(me: this) << "startAngle (" << startAngle << ") cannot be less than or equal to -360";
574 return;
575 }
576
577 // keep the interval [startAngle, endAngle] unique
578 if (startAngle < d->endAngle - 360.) {
579 qmlWarning(me: this) << "Difference between startAngle (" << startAngle
580 << ") and endAngle (" << d->endAngle << ") cannot be greater than 360."
581 << " Changing endAngle to avoid overlaps.";
582 d->endAngle = startAngle + 360.;
583 emit endAngleChanged();
584 }
585
586 d->startAngle = startAngle;
587 // changing the startAngle will change the angle
588 // if the value is kept constant
589 d->updatePosition();
590 emit startAngleChanged();
591}
592
593/*!
594 \qmlproperty real QtQuick.Controls::Dial::endAngle
595 \since 6.6
596
597 This property holds the end angle of the dial in degrees.
598
599 This is the \l angle the dial will have for its maximum value, i.e. \l to.
600 The \l endAngle has to be bigger than the \l startAngle, smaller than 720
601 and smaller or equal than the \l startAngle + 360 degrees.
602
603 \sa endAngle, angle
604*/
605qreal QQuickDial::endAngle() const
606{
607 Q_D(const QQuickDial);
608 return d->endAngle;
609
610}
611
612void QQuickDial::setEndAngle(qreal endAngle)
613{
614 Q_D(QQuickDial);
615 if (!d->componentComplete) {
616 // Binding evaluation order can cause warnings with certain combinations
617 // of start and end angles, so delay the actual setting until after component completion.
618 // Store the requested value in the existing member to avoid the need for an extra one.
619 d->endAngle = endAngle;
620 return;
621 }
622
623 if (qFuzzyCompare(p1: d->endAngle, p2: endAngle))
624 return;
625
626 if (endAngle <= d->startAngle) {
627 qmlWarning(me: this) << "endAngle (" << endAngle
628 << ") cannot be less than or equal to startAngle (" << d->startAngle << ")";
629 return;
630 }
631
632 // Keep the interval around 0
633 if (endAngle >= 720.) {
634 qmlWarning(me: this) << "endAngle (" << endAngle << ") cannot be greater than or equal to 720";
635 return;
636 }
637
638 // keep the interval [startAngle, endAngle] unique
639 if (endAngle > d->startAngle + 360.) {
640 qmlWarning(me: this) << "Difference between startAngle (" << d->startAngle
641 << ") and endAngle (" << endAngle << ") cannot be greater than 360."
642 << " Changing startAngle to avoid overlaps.";
643 d->startAngle = endAngle - 360.;
644 emit startAngleChanged();
645 }
646
647 d->endAngle = endAngle;
648 // changing the startAngle will change the angle
649 // if the value is kept constant
650 d->updatePosition();
651 emit endAngleChanged();
652}
653
654/*!
655 \qmlproperty enumeration QtQuick.Controls::Dial::snapMode
656
657 This property holds the snap mode.
658
659 The snap mode works with the \l stepSize to allow the handle to snap to
660 certain points along the dial.
661
662 Possible values:
663 \value Dial.NoSnap The dial does not snap (default).
664 \value Dial.SnapAlways The dial snaps while the handle is dragged.
665 \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released.
666
667 \sa stepSize
668*/
669QQuickDial::SnapMode QQuickDial::snapMode() const
670{
671 Q_D(const QQuickDial);
672 return d->snapMode;
673}
674
675void QQuickDial::setSnapMode(SnapMode mode)
676{
677 Q_D(QQuickDial);
678 if (d->snapMode == mode)
679 return;
680
681 d->snapMode = mode;
682 emit snapModeChanged();
683}
684
685/*!
686 \since QtQuick.Controls 2.5 (Qt 5.12)
687 \qmlproperty enumeration QtQuick.Controls::Dial::inputMode
688
689 This property holds the input mode.
690
691 \include qquickdial.qdocinc inputMode
692
693 The default value is \c Dial.Circular.
694*/
695QQuickDial::InputMode QQuickDial::inputMode() const
696{
697 Q_D(const QQuickDial);
698 return d->inputMode;
699}
700
701void QQuickDial::setInputMode(QQuickDial::InputMode mode)
702{
703 Q_D(QQuickDial);
704 if (d->inputMode == mode)
705 return;
706
707 d->inputMode = mode;
708 emit inputModeChanged();
709}
710
711/*!
712 \qmlproperty bool QtQuick.Controls::Dial::wrap
713
714 This property holds whether the dial wraps when dragged.
715
716 For example, when this property is set to \c true, dragging the dial past
717 the \l to position will result in the handle being positioned at the
718 \l from position, and vice versa:
719
720 \image qtquickcontrols-dial-wrap.gif
721
722 When this property is \c false, it's not possible to drag the dial across
723 the from and to values.
724
725 \image qtquickcontrols-dial-no-wrap.gif
726
727 The default value is \c false.
728*/
729bool QQuickDial::wrap() const
730{
731 Q_D(const QQuickDial);
732 return d->wrap;
733}
734
735void QQuickDial::setWrap(bool wrap)
736{
737 Q_D(QQuickDial);
738 if (d->wrap == wrap)
739 return;
740
741 d->wrap = wrap;
742 emit wrapChanged();
743}
744
745/*!
746 \qmlproperty bool QtQuick.Controls::Dial::pressed
747
748 This property holds whether the dial is pressed.
749
750 The dial will be pressed when either the mouse is pressed over it, or a key
751 such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial
752 be pressed upon key presses (due to styling reasons, for example), you can
753 use the \l {Keys}{Keys attached property}:
754
755 \code
756 Dial {
757 Keys.onLeftPressed: {}
758 }
759 \endcode
760
761 This will result in pressed only being \c true upon mouse presses.
762*/
763bool QQuickDial::isPressed() const
764{
765 Q_D(const QQuickDial);
766 return d->pressed;
767}
768
769void QQuickDial::setPressed(bool pressed)
770{
771 Q_D(QQuickDial);
772 if (d->pressed == pressed)
773 return;
774
775 d->pressed = pressed;
776 setAccessibleProperty(propertyName: "pressed", value: pressed);
777 emit pressedChanged();
778}
779
780/*!
781 \qmlproperty Item QtQuick.Controls::Dial::handle
782
783 This property holds the handle of the dial.
784
785 The handle acts as a visual indicator of the position of the dial.
786
787 \sa {Customizing Dial}
788*/
789QQuickItem *QQuickDial::handle() const
790{
791 QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func());
792 if (!d->handle)
793 d->executeHandle();
794 return d->handle;
795}
796
797void QQuickDial::setHandle(QQuickItem *handle)
798{
799 Q_D(QQuickDial);
800 if (handle == d->handle)
801 return;
802
803 QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: handle, QStringLiteral("handle"));
804
805 if (!d->handle.isExecuting())
806 d->cancelHandle();
807
808 QQuickControlPrivate::hideOldItem(item: d->handle);
809 d->handle = handle;
810 if (d->handle && !d->handle->parentItem())
811 d->handle->setParentItem(this);
812 if (!d->handle.isExecuting())
813 emit handleChanged();
814}
815
816/*!
817 \since QtQuick.Controls 2.2 (Qt 5.9)
818 \qmlproperty bool QtQuick.Controls::Dial::live
819
820 This property holds whether the dial provides live updates for the \l value
821 property while the handle is dragged.
822
823 The default value is \c true.
824
825 \sa value
826*/
827bool QQuickDial::live() const
828{
829 Q_D(const QQuickDial);
830 return d->live;
831}
832
833void QQuickDial::setLive(bool live)
834{
835 Q_D(QQuickDial);
836 if (d->live == live)
837 return;
838
839 d->live = live;
840 emit liveChanged();
841}
842
843/*!
844 \qmlmethod void QtQuick.Controls::Dial::increase()
845
846 Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined.
847
848 \sa stepSize
849*/
850void QQuickDial::increase()
851{
852 Q_D(QQuickDial);
853 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
854 setValue(d->value + step);
855}
856
857/*!
858 \qmlmethod void QtQuick.Controls::Dial::decrease()
859
860 Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined.
861
862 \sa stepSize
863*/
864void QQuickDial::decrease()
865{
866 Q_D(QQuickDial);
867 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
868 setValue(d->value - step);
869}
870
871void QQuickDial::keyPressEvent(QKeyEvent *event)
872{
873 Q_D(QQuickDial);
874 const qreal oldValue = d->value;
875 switch (event->key()) {
876 case Qt::Key_Left:
877 case Qt::Key_Down:
878 setPressed(true);
879 if (isMirrored())
880 increase();
881 else
882 decrease();
883 break;
884
885 case Qt::Key_Right:
886 case Qt::Key_Up:
887 setPressed(true);
888 if (isMirrored())
889 decrease();
890 else
891 increase();
892 break;
893
894 case Qt::Key_Home:
895 setPressed(true);
896 setValue(isMirrored() ? d->to : d->from);
897 break;
898
899 case Qt::Key_End:
900 setPressed(true);
901 setValue(isMirrored() ? d->from : d->to);
902 break;
903
904 default:
905 event->ignore();
906 QQuickControl::keyPressEvent(event);
907 break;
908 }
909 if (!qFuzzyCompare(p1: d->value, p2: oldValue))
910 emit moved();
911}
912
913void QQuickDial::keyReleaseEvent(QKeyEvent *event)
914{
915 QQuickControl::keyReleaseEvent(event);
916 setPressed(false);
917}
918
919void QQuickDial::mousePressEvent(QMouseEvent *event)
920{
921 Q_D(QQuickDial);
922 QQuickControl::mousePressEvent(event);
923 d->handleMove(point: event->position(), timestamp: event->timestamp());
924 setKeepMouseGrab(true);
925}
926
927#if QT_CONFIG(quicktemplates2_multitouch)
928void QQuickDial::touchEvent(QTouchEvent *event)
929{
930 Q_D(QQuickDial);
931 switch (event->type()) {
932 case QEvent::TouchUpdate:
933 for (const QTouchEvent::TouchPoint &point : event->points()) {
934 if (!d->acceptTouch(point))
935 continue;
936
937 switch (point.state()) {
938 case QEventPoint::Updated:
939 if (!keepTouchGrab()) {
940 bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().x() - d->pressPoint.x(), axis: Qt::XAxis, tp: &point);
941 setKeepTouchGrab(overXDragThreshold);
942
943 if (!overXDragThreshold) {
944 bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().y() - d->pressPoint.y(), axis: Qt::YAxis, tp: &point);
945 setKeepTouchGrab(overYDragThreshold);
946 }
947 }
948 if (keepTouchGrab())
949 d->handleMove(point: point.position(), timestamp: event->timestamp());
950 break;
951
952 default:
953 QQuickControl::touchEvent(event);
954 break;
955 }
956 }
957 break;
958
959 default:
960 QQuickControl::touchEvent(event);
961 break;
962 }
963}
964#endif
965
966#if QT_CONFIG(wheelevent)
967void QQuickDial::wheelEvent(QWheelEvent *event)
968{
969 Q_D(QQuickDial);
970 QQuickControl::wheelEvent(event);
971 if (d->wheelEnabled) {
972 const qreal oldValue = d->value;
973 const QPointF angle = event->angleDelta();
974 const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / int(QWheelEvent::DefaultDeltasPerStep);
975 const qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
976 setValue(oldValue + step * delta);
977 event->setAccepted(!qFuzzyCompare(p1: d->value, p2: oldValue));
978 }
979}
980#endif
981
982void QQuickDial::mirrorChange()
983{
984 QQuickControl::mirrorChange();
985 emit angleChanged();
986}
987
988void QQuickDial::componentComplete()
989{
990 Q_D(QQuickDial);
991 d->executeHandle(complete: true);
992 QQuickControl::componentComplete();
993
994 // Set the (delayed) start and end angles, if necessary (see the setters for more info).
995 if (!qFuzzyCompare(p1: d->startAngle, p2: defaultStartAngle)) {
996 const qreal startAngle = d->startAngle;
997 // Temporarily set it to something else so that it sees that it has changed.
998 d->startAngle = defaultStartAngle;
999 setStartAngle(startAngle);
1000 }
1001
1002 if (!qFuzzyCompare(p1: d->endAngle, p2: defaultEndAngle)) {
1003 const qreal endAngle = d->endAngle;
1004 d->endAngle = defaultEndAngle;
1005 setEndAngle(endAngle);
1006 }
1007
1008 setValue(d->value);
1009 d->updatePosition();
1010}
1011
1012#if QT_CONFIG(accessibility)
1013void QQuickDial::accessibilityActiveChanged(bool active)
1014{
1015 QQuickControl::accessibilityActiveChanged(active);
1016
1017 Q_D(QQuickDial);
1018 if (active)
1019 setAccessibleProperty(propertyName: "pressed", value: d->pressed);
1020}
1021
1022QAccessible::Role QQuickDial::accessibleRole() const
1023{
1024 return QAccessible::Dial;
1025}
1026#endif
1027
1028QT_END_NAMESPACE
1029
1030#include "moc_qquickdial_p.cpp"
1031

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