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