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//! \nativetype 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 Q_D(QQuickDial);
376 d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Preferred, verticalPolicy: QLayoutPolicy::Preferred);
377}
378
379/*!
380 \qmlproperty real QtQuick.Controls::Dial::from
381
382 This property holds the starting value for the range. The default value is \c 0.0.
383
384 \sa to, value
385*/
386qreal QQuickDial::from() const
387{
388 Q_D(const QQuickDial);
389 return d->from;
390}
391
392void QQuickDial::setFrom(qreal from)
393{
394 Q_D(QQuickDial);
395 if (qFuzzyCompare(p1: d->from, p2: from))
396 return;
397
398 d->from = from;
399 emit fromChanged();
400 d->updateAllValuesAreInteger();
401 if (isComponentComplete()) {
402 setValue(d->value);
403 d->updatePosition();
404 }
405}
406
407/*!
408 \qmlproperty real QtQuick.Controls::Dial::to
409
410 This property holds the end value for the range. The default value is
411 \c 1.0.
412
413 \sa from, value
414*/
415qreal QQuickDial::to() const
416{
417 Q_D(const QQuickDial);
418 return d->to;
419}
420
421void QQuickDial::setTo(qreal to)
422{
423 Q_D(QQuickDial);
424 if (qFuzzyCompare(p1: d->to, p2: to))
425 return;
426
427 d->to = to;
428 d->updateAllValuesAreInteger();
429 emit toChanged();
430 if (isComponentComplete()) {
431 setValue(d->value);
432 d->updatePosition();
433 }
434}
435
436/*!
437 \qmlproperty real QtQuick.Controls::Dial::value
438
439 This property holds the value in the range \c from - \c to. The default
440 value is \c 0.0.
441
442 \sa position, live
443*/
444qreal QQuickDial::value() const
445{
446 Q_D(const QQuickDial);
447 return d->value;
448}
449
450void QQuickDial::setValue(qreal value)
451{
452 Q_D(QQuickDial);
453 if (isComponentComplete())
454 value = d->from > d->to ? qBound(min: d->to, val: value, max: d->from) : qBound(min: d->from, val: value, max: d->to);
455
456 if (qFuzzyCompare(p1: d->value, p2: value))
457 return;
458
459 d->value = value;
460 d->updatePosition();
461 emit valueChanged();
462}
463
464/*!
465 \qmlproperty real QtQuick.Controls::Dial::position
466 \readonly
467
468 This property holds the logical position of the handle.
469
470 The position is expressed as a fraction of the control's angle range (the
471 range within which the handle can be moved) in the range \c {0.0 - 1.0}.
472
473 \sa value, angle
474*/
475qreal QQuickDial::position() const
476{
477 Q_D(const QQuickDial);
478 return d->position;
479}
480
481/*!
482 \qmlproperty real QtQuick.Controls::Dial::angle
483 \readonly
484
485 This property holds the clockwise angle of the handle in degrees.
486
487 The angle is zero at the 12 o'clock position and the range is from
488 \l startAngle to \c endAngle.
489
490 \sa position, startAngle, endAngle
491*/
492qreal QQuickDial::angle() const
493{
494 Q_D(const QQuickDial);
495 return d->angle;
496}
497
498/*!
499 \qmlproperty real QtQuick.Controls::Dial::stepSize
500
501 This property holds the step size.
502
503 The step size determines the amount by which the dial's value
504 is increased and decreased when interacted with via the keyboard.
505 For example, a step size of \c 0.2, will result in the dial's
506 value increasing and decreasing in increments of \c 0.2.
507
508 The step size is only respected for touch and mouse interaction
509 when \l snapMode is set to a value other than \c Dial.NoSnap.
510
511 The default value is \c 0.0, which results in an effective step
512 size of \c 0.1 for keyboard interaction.
513
514 \sa snapMode, increase(), decrease()
515*/
516qreal QQuickDial::stepSize() const
517{
518 Q_D(const QQuickDial);
519 return d->stepSize;
520}
521
522void QQuickDial::setStepSize(qreal step)
523{
524 Q_D(QQuickDial);
525 if (qFuzzyCompare(p1: d->stepSize, p2: step))
526 return;
527
528 d->stepSize = step;
529 d->updateAllValuesAreInteger();
530 emit stepSizeChanged();
531}
532
533
534/*!
535 \qmlproperty real QtQuick.Controls::Dial::startAngle
536 \since 6.6
537
538 This property holds the starting angle of the dial in degrees.
539
540 This is the \l angle the dial will have for its minimum value, i.e. \l from.
541 The \l startAngle has to be smaller than the \l endAngle, larger than -360
542 and larger or equal to the \l endAngle - 360 degrees.
543
544 \sa endAngle, angle
545*/
546qreal QQuickDial::startAngle() const
547{
548 Q_D(const QQuickDial);
549 return d->startAngle;
550}
551
552void QQuickDial::setStartAngle(qreal startAngle)
553{
554 Q_D(QQuickDial);
555 if (!d->componentComplete) {
556 // Binding evaluation order can cause warnings with certain combinations
557 // of start and end angles, so delay the actual setting until after component completion.
558 // Store the requested value in the existing member to avoid the need for an extra one.
559 d->startAngle = startAngle;
560 return;
561 }
562
563 if (qFuzzyCompare(p1: d->startAngle, p2: startAngle))
564 return;
565
566 // do not allow to change direction
567 if (startAngle >= d->endAngle) {
568 qmlWarning(me: this) << "startAngle (" << startAngle
569 << ") cannot be greater than or equal to endAngle (" << d->endAngle << ")";
570 return;
571 }
572
573 // Keep the interval around 0
574 if (startAngle <= -360.) {
575 qmlWarning(me: this) << "startAngle (" << startAngle << ") cannot be less than or equal to -360";
576 return;
577 }
578
579 // keep the interval [startAngle, endAngle] unique
580 if (startAngle < d->endAngle - 360.) {
581 qmlWarning(me: this) << "Difference between startAngle (" << startAngle
582 << ") and endAngle (" << d->endAngle << ") cannot be greater than 360."
583 << " Changing endAngle to avoid overlaps.";
584 d->endAngle = startAngle + 360.;
585 emit endAngleChanged();
586 }
587
588 d->startAngle = startAngle;
589 // changing the startAngle will change the angle
590 // if the value is kept constant
591 d->updatePosition();
592 emit startAngleChanged();
593}
594
595/*!
596 \qmlproperty real QtQuick.Controls::Dial::endAngle
597 \since 6.6
598
599 This property holds the end angle of the dial in degrees.
600
601 This is the \l angle the dial will have for its maximum value, i.e. \l to.
602 The \l endAngle has to be bigger than the \l startAngle, smaller than 720
603 and smaller or equal than the \l startAngle + 360 degrees.
604
605 \sa endAngle, angle
606*/
607qreal QQuickDial::endAngle() const
608{
609 Q_D(const QQuickDial);
610 return d->endAngle;
611
612}
613
614void QQuickDial::setEndAngle(qreal endAngle)
615{
616 Q_D(QQuickDial);
617 if (!d->componentComplete) {
618 // Binding evaluation order can cause warnings with certain combinations
619 // of start and end angles, so delay the actual setting until after component completion.
620 // Store the requested value in the existing member to avoid the need for an extra one.
621 d->endAngle = endAngle;
622 return;
623 }
624
625 if (qFuzzyCompare(p1: d->endAngle, p2: endAngle))
626 return;
627
628 if (endAngle <= d->startAngle) {
629 qmlWarning(me: this) << "endAngle (" << endAngle
630 << ") cannot be less than or equal to startAngle (" << d->startAngle << ")";
631 return;
632 }
633
634 // Keep the interval around 0
635 if (endAngle >= 720.) {
636 qmlWarning(me: this) << "endAngle (" << endAngle << ") cannot be greater than or equal to 720";
637 return;
638 }
639
640 // keep the interval [startAngle, endAngle] unique
641 if (endAngle > d->startAngle + 360.) {
642 qmlWarning(me: this) << "Difference between startAngle (" << d->startAngle
643 << ") and endAngle (" << endAngle << ") cannot be greater than 360."
644 << " Changing startAngle to avoid overlaps.";
645 d->startAngle = endAngle - 360.;
646 emit startAngleChanged();
647 }
648
649 d->endAngle = endAngle;
650 // changing the startAngle will change the angle
651 // if the value is kept constant
652 d->updatePosition();
653 emit endAngleChanged();
654}
655
656/*!
657 \qmlproperty enumeration QtQuick.Controls::Dial::snapMode
658
659 This property holds the snap mode.
660
661 The snap mode works with the \l stepSize to allow the handle to snap to
662 certain points along the dial.
663
664 Possible values:
665 \value Dial.NoSnap The dial does not snap (default).
666 \value Dial.SnapAlways The dial snaps while the handle is dragged.
667 \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released.
668
669 \sa stepSize
670*/
671QQuickDial::SnapMode QQuickDial::snapMode() const
672{
673 Q_D(const QQuickDial);
674 return d->snapMode;
675}
676
677void QQuickDial::setSnapMode(SnapMode mode)
678{
679 Q_D(QQuickDial);
680 if (d->snapMode == mode)
681 return;
682
683 d->snapMode = mode;
684 emit snapModeChanged();
685}
686
687/*!
688 \since QtQuick.Controls 2.5 (Qt 5.12)
689 \qmlproperty enumeration QtQuick.Controls::Dial::inputMode
690
691 This property holds the input mode.
692
693 \include qquickdial.qdocinc inputMode
694
695 The default value is \c Dial.Circular.
696*/
697QQuickDial::InputMode QQuickDial::inputMode() const
698{
699 Q_D(const QQuickDial);
700 return d->inputMode;
701}
702
703void QQuickDial::setInputMode(QQuickDial::InputMode mode)
704{
705 Q_D(QQuickDial);
706 if (d->inputMode == mode)
707 return;
708
709 d->inputMode = mode;
710 emit inputModeChanged();
711}
712
713/*!
714 \qmlproperty bool QtQuick.Controls::Dial::wrap
715
716 This property holds whether the dial wraps when dragged.
717
718 For example, when this property is set to \c true, dragging the dial past
719 the \l to position will result in the handle being positioned at the
720 \l from position, and vice versa:
721
722 \image qtquickcontrols-dial-wrap.gif
723
724 When this property is \c false, it's not possible to drag the dial across
725 the from and to values.
726
727 \image qtquickcontrols-dial-no-wrap.gif
728
729 The default value is \c false.
730*/
731bool QQuickDial::wrap() const
732{
733 Q_D(const QQuickDial);
734 return d->wrap;
735}
736
737void QQuickDial::setWrap(bool wrap)
738{
739 Q_D(QQuickDial);
740 if (d->wrap == wrap)
741 return;
742
743 d->wrap = wrap;
744 emit wrapChanged();
745}
746
747/*!
748 \qmlproperty bool QtQuick.Controls::Dial::pressed
749
750 This property holds whether the dial is pressed.
751
752 The dial will be pressed when either the mouse is pressed over it, or a key
753 such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial
754 be pressed upon key presses (due to styling reasons, for example), you can
755 use the \l {Keys}{Keys attached property}:
756
757 \code
758 Dial {
759 Keys.onLeftPressed: {}
760 }
761 \endcode
762
763 This will result in pressed only being \c true upon mouse presses.
764*/
765bool QQuickDial::isPressed() const
766{
767 Q_D(const QQuickDial);
768 return d->pressed;
769}
770
771void QQuickDial::setPressed(bool pressed)
772{
773 Q_D(QQuickDial);
774 if (d->pressed == pressed)
775 return;
776
777 d->pressed = pressed;
778 setAccessibleProperty(propertyName: "pressed", value: pressed);
779 emit pressedChanged();
780}
781
782/*!
783 \qmlproperty Item QtQuick.Controls::Dial::handle
784
785 This property holds the handle of the dial.
786
787 The handle acts as a visual indicator of the position of the dial.
788
789 \sa {Customizing Dial}
790*/
791QQuickItem *QQuickDial::handle() const
792{
793 QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func());
794 if (!d->handle)
795 d->executeHandle();
796 return d->handle;
797}
798
799void QQuickDial::setHandle(QQuickItem *handle)
800{
801 Q_D(QQuickDial);
802 if (handle == d->handle)
803 return;
804
805 QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: handle, QStringLiteral("handle"));
806
807 if (!d->handle.isExecuting())
808 d->cancelHandle();
809
810 QQuickControlPrivate::hideOldItem(item: d->handle);
811 d->handle = handle;
812 if (d->handle && !d->handle->parentItem())
813 d->handle->setParentItem(this);
814 if (!d->handle.isExecuting())
815 emit handleChanged();
816}
817
818/*!
819 \since QtQuick.Controls 2.2 (Qt 5.9)
820 \qmlproperty bool QtQuick.Controls::Dial::live
821
822 This property holds whether the dial provides live updates for the \l value
823 property while the handle is dragged.
824
825 The default value is \c true.
826
827 \sa value
828*/
829bool QQuickDial::live() const
830{
831 Q_D(const QQuickDial);
832 return d->live;
833}
834
835void QQuickDial::setLive(bool live)
836{
837 Q_D(QQuickDial);
838 if (d->live == live)
839 return;
840
841 d->live = live;
842 emit liveChanged();
843}
844
845/*!
846 \qmlmethod void QtQuick.Controls::Dial::increase()
847
848 Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined.
849
850 \sa stepSize
851*/
852void QQuickDial::increase()
853{
854 Q_D(QQuickDial);
855 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
856 setValue(d->value + step);
857}
858
859/*!
860 \qmlmethod void QtQuick.Controls::Dial::decrease()
861
862 Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined.
863
864 \sa stepSize
865*/
866void QQuickDial::decrease()
867{
868 Q_D(QQuickDial);
869 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
870 setValue(d->value - step);
871}
872
873void QQuickDial::keyPressEvent(QKeyEvent *event)
874{
875 Q_D(QQuickDial);
876 const qreal oldValue = d->value;
877 switch (event->key()) {
878 case Qt::Key_Left:
879 case Qt::Key_Down:
880 setPressed(true);
881 if (isMirrored())
882 increase();
883 else
884 decrease();
885 break;
886
887 case Qt::Key_Right:
888 case Qt::Key_Up:
889 setPressed(true);
890 if (isMirrored())
891 decrease();
892 else
893 increase();
894 break;
895
896 case Qt::Key_Home:
897 setPressed(true);
898 setValue(isMirrored() ? d->to : d->from);
899 break;
900
901 case Qt::Key_End:
902 setPressed(true);
903 setValue(isMirrored() ? d->from : d->to);
904 break;
905
906 default:
907 event->ignore();
908 QQuickControl::keyPressEvent(event);
909 break;
910 }
911 if (!qFuzzyCompare(p1: d->value, p2: oldValue))
912 emit moved();
913}
914
915void QQuickDial::keyReleaseEvent(QKeyEvent *event)
916{
917 QQuickControl::keyReleaseEvent(event);
918 setPressed(false);
919}
920
921void QQuickDial::mousePressEvent(QMouseEvent *event)
922{
923 Q_D(QQuickDial);
924 QQuickControl::mousePressEvent(event);
925 d->handleMove(point: event->position(), timestamp: event->timestamp());
926 setKeepMouseGrab(true);
927}
928
929#if QT_CONFIG(quicktemplates2_multitouch)
930void QQuickDial::touchEvent(QTouchEvent *event)
931{
932 Q_D(QQuickDial);
933 switch (event->type()) {
934 case QEvent::TouchUpdate:
935 for (const QTouchEvent::TouchPoint &point : event->points()) {
936 if (!d->acceptTouch(point))
937 continue;
938
939 switch (point.state()) {
940 case QEventPoint::Updated:
941 if (!keepTouchGrab()) {
942 bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().x() - d->pressPoint.x(), axis: Qt::XAxis, tp: &point);
943 setKeepTouchGrab(overXDragThreshold);
944
945 if (!overXDragThreshold) {
946 bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.position().y() - d->pressPoint.y(), axis: Qt::YAxis, tp: &point);
947 setKeepTouchGrab(overYDragThreshold);
948 }
949 }
950 if (keepTouchGrab())
951 d->handleMove(point: point.position(), timestamp: event->timestamp());
952 break;
953
954 default:
955 QQuickControl::touchEvent(event);
956 break;
957 }
958 }
959 break;
960
961 default:
962 QQuickControl::touchEvent(event);
963 break;
964 }
965}
966#endif
967
968#if QT_CONFIG(wheelevent)
969void QQuickDial::wheelEvent(QWheelEvent *event)
970{
971 Q_D(QQuickDial);
972 QQuickControl::wheelEvent(event);
973 if (d->wheelEnabled) {
974 const qreal oldValue = d->value;
975 const QPointF angle = event->angleDelta();
976 const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / int(QWheelEvent::DefaultDeltasPerStep);
977 const qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
978 setValue(oldValue + step * delta);
979 event->setAccepted(!qFuzzyCompare(p1: d->value, p2: oldValue));
980 }
981}
982#endif
983
984void QQuickDial::mirrorChange()
985{
986 QQuickControl::mirrorChange();
987 emit angleChanged();
988}
989
990void QQuickDial::componentComplete()
991{
992 Q_D(QQuickDial);
993 d->executeHandle(complete: true);
994 QQuickControl::componentComplete();
995
996 // Set the (delayed) start and end angles, if necessary (see the setters for more info).
997 if (!qFuzzyCompare(p1: d->startAngle, p2: defaultStartAngle)) {
998 const qreal startAngle = d->startAngle;
999 // Temporarily set it to something else so that it sees that it has changed.
1000 d->startAngle = defaultStartAngle;
1001 setStartAngle(startAngle);
1002 }
1003
1004 if (!qFuzzyCompare(p1: d->endAngle, p2: defaultEndAngle)) {
1005 const qreal endAngle = d->endAngle;
1006 d->endAngle = defaultEndAngle;
1007 setEndAngle(endAngle);
1008 }
1009
1010 setValue(d->value);
1011 d->updatePosition();
1012}
1013
1014#if QT_CONFIG(accessibility)
1015void QQuickDial::accessibilityActiveChanged(bool active)
1016{
1017 QQuickControl::accessibilityActiveChanged(active);
1018
1019 Q_D(QQuickDial);
1020 if (active)
1021 setAccessibleProperty(propertyName: "pressed", value: d->pressed);
1022}
1023
1024QAccessible::Role QQuickDial::accessibleRole() const
1025{
1026 return QAccessible::Dial;
1027}
1028#endif
1029
1030QT_END_NAMESPACE
1031
1032#include "moc_qquickdial_p.cpp"
1033

Provided by KDAB

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

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