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 | |
13 | QT_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 |
77 | constexpr qreal toUserAngleDeg(qreal logicAngleRad) { |
78 | // minus to turn clockwise, add 90 deg clockwise |
79 | return -logicAngleRad / M_PI * 180. + 90; |
80 | } |
81 | |
82 | static const qreal defaultStartAngle = -140; |
83 | static const qreal defaultEndAngle = 140; |
84 | |
85 | class QQuickDialPrivate : public QQuickControlPrivate |
86 | { |
87 | Q_DECLARE_PUBLIC(QQuickDial) |
88 | |
89 | public: |
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 | |
131 | qreal 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 | |
145 | qreal 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 | |
158 | qreal QQuickDialPrivate::positionAt(const QPointF &point) const |
159 | { |
160 | return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); |
161 | } |
162 | |
163 | qreal 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 | |
197 | qreal 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 | |
223 | void 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 | |
239 | void 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 | |
247 | bool 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 | |
254 | bool QQuickDialPrivate::isHorizontalOrVertical() const |
255 | { |
256 | return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; |
257 | } |
258 | |
259 | bool 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 | |
269 | bool 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 | |
291 | bool 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 | |
318 | void QQuickDialPrivate::handleUngrab() |
319 | { |
320 | Q_Q(QQuickDial); |
321 | QQuickControlPrivate::handleUngrab(); |
322 | pressPoint = QPointF(); |
323 | positionBeforePress = 0; |
324 | q->setPressed(false); |
325 | } |
326 | |
327 | void QQuickDialPrivate::cancelHandle() |
328 | { |
329 | Q_Q(QQuickDial); |
330 | quickCancelDeferred(object: q, property: handleName()); |
331 | } |
332 | |
333 | void 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 | |
345 | template<typename ...Real> |
346 | static bool areRepresentableAsInteger(Real... numbers) { |
347 | auto check = [](qreal number) -> bool { return std::nearbyint(x: number) == number; }; |
348 | return (... && check(numbers)); |
349 | } |
350 | |
351 | void QQuickDialPrivate::updateAllValuesAreInteger() |
352 | { |
353 | allValuesAreInteger = areRepresentableAsInteger(numbers: to, numbers: from, numbers: stepSize) && stepSize != 0.0; |
354 | } |
355 | |
356 | void 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 | |
364 | QQuickDial::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 | */ |
384 | qreal QQuickDial::from() const |
385 | { |
386 | Q_D(const QQuickDial); |
387 | return d->from; |
388 | } |
389 | |
390 | void 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 | */ |
413 | qreal QQuickDial::to() const |
414 | { |
415 | Q_D(const QQuickDial); |
416 | return d->to; |
417 | } |
418 | |
419 | void 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 | */ |
442 | qreal QQuickDial::value() const |
443 | { |
444 | Q_D(const QQuickDial); |
445 | return d->value; |
446 | } |
447 | |
448 | void 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 | */ |
473 | qreal 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 | */ |
490 | qreal 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 | */ |
514 | qreal QQuickDial::stepSize() const |
515 | { |
516 | Q_D(const QQuickDial); |
517 | return d->stepSize; |
518 | } |
519 | |
520 | void 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 | */ |
544 | qreal QQuickDial::startAngle() const |
545 | { |
546 | Q_D(const QQuickDial); |
547 | return d->startAngle; |
548 | } |
549 | |
550 | void 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 | */ |
605 | qreal QQuickDial::endAngle() const |
606 | { |
607 | Q_D(const QQuickDial); |
608 | return d->endAngle; |
609 | |
610 | } |
611 | |
612 | void 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 | */ |
669 | QQuickDial::SnapMode QQuickDial::snapMode() const |
670 | { |
671 | Q_D(const QQuickDial); |
672 | return d->snapMode; |
673 | } |
674 | |
675 | void 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 | */ |
695 | QQuickDial::InputMode QQuickDial::inputMode() const |
696 | { |
697 | Q_D(const QQuickDial); |
698 | return d->inputMode; |
699 | } |
700 | |
701 | void 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 | */ |
729 | bool QQuickDial::wrap() const |
730 | { |
731 | Q_D(const QQuickDial); |
732 | return d->wrap; |
733 | } |
734 | |
735 | void 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 | */ |
763 | bool QQuickDial::isPressed() const |
764 | { |
765 | Q_D(const QQuickDial); |
766 | return d->pressed; |
767 | } |
768 | |
769 | void 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 | */ |
789 | QQuickItem *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 | |
797 | void 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 | */ |
827 | bool QQuickDial::live() const |
828 | { |
829 | Q_D(const QQuickDial); |
830 | return d->live; |
831 | } |
832 | |
833 | void 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 | */ |
850 | void 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 | */ |
864 | void 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 | |
871 | void 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 | |
913 | void QQuickDial::keyReleaseEvent(QKeyEvent *event) |
914 | { |
915 | QQuickControl::keyReleaseEvent(event); |
916 | setPressed(false); |
917 | } |
918 | |
919 | void 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) |
928 | void 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) |
967 | void 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 | |
982 | void QQuickDial::mirrorChange() |
983 | { |
984 | QQuickControl::mirrorChange(); |
985 | emit angleChanged(); |
986 | } |
987 | |
988 | void 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) |
1013 | void 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 | |
1022 | QAccessible::Role QQuickDial::accessibleRole() const |
1023 | { |
1024 | return QAccessible::Dial; |
1025 | } |
1026 | #endif |
1027 | |
1028 | QT_END_NAMESPACE |
1029 | |
1030 | #include "moc_qquickdial_p.cpp" |
1031 | |