1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include "qquickdial_p.h" |
38 | #include "qquickdeferredexecute_p_p.h" |
39 | |
40 | #include <QtCore/qmath.h> |
41 | #include <QtQuick/private/qquickflickable_p.h> |
42 | #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> |
43 | |
44 | #include <cmath> |
45 | |
46 | QT_BEGIN_NAMESPACE |
47 | |
48 | /*! |
49 | \qmltype Dial |
50 | \inherits Control |
51 | //! \instantiates QQuickDial |
52 | \inqmlmodule QtQuick.Controls |
53 | \since 5.7 |
54 | \ingroup qtquickcontrols2-input |
55 | \brief Circular dial that is rotated to set a value. |
56 | |
57 | The Dial is similar to a traditional dial knob that is found on devices |
58 | such as stereos or industrial equipment. It allows the user to specify a |
59 | value within a range. |
60 | |
61 | \image qtquickcontrols2-dial-no-wrap.gif |
62 | |
63 | The value of the dial is set with the \l value property. The range is |
64 | set with the \l from and \l to properties. To enable or disable wrapping, |
65 | use the \l wrap property. |
66 | |
67 | The dial can be manipulated with a keyboard. It supports the following |
68 | actions: |
69 | |
70 | \table |
71 | \header \li \b {Action} \li \b {Key} |
72 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left |
73 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down |
74 | \row \li Set \l value to \l from \li \c Qt.Key_Home |
75 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right |
76 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up |
77 | \row \li Set \l value to \l to \li \c Qt.Key_End |
78 | \endtable |
79 | |
80 | \include qquickdial.qdocinc inputMode |
81 | |
82 | \sa {Customizing Dial}, {Input Controls} |
83 | */ |
84 | |
85 | /*! |
86 | \since QtQuick.Controls 2.2 (Qt 5.9) |
87 | \qmlsignal QtQuick.Controls::Dial::moved() |
88 | |
89 | This signal is emitted when the dial has been interactively moved |
90 | by the user by either touch, mouse, or keys. |
91 | */ |
92 | |
93 | static const qreal startAngleRadians = (M_PI * 2.0) * (4.0 / 6.0); |
94 | static const qreal startAngle = -140; |
95 | static const qreal endAngleRadians = (M_PI * 2.0) * (5.0 / 6.0); |
96 | static const qreal endAngle = 140; |
97 | |
98 | class QQuickDialPrivate : public QQuickControlPrivate |
99 | { |
100 | Q_DECLARE_PUBLIC(QQuickDial) |
101 | |
102 | public: |
103 | qreal valueAt(qreal position) const; |
104 | qreal snapPosition(qreal position) const; |
105 | qreal positionAt(const QPointF &point) const; |
106 | qreal circularPositionAt(const QPointF &point) const; |
107 | qreal linearPositionAt(const QPointF &point) const; |
108 | void setPosition(qreal position); |
109 | void updatePosition(); |
110 | bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; |
111 | bool isHorizontalOrVertical() const; |
112 | |
113 | void handlePress(const QPointF &point) override; |
114 | void handleMove(const QPointF &point) override; |
115 | void handleRelease(const QPointF &point) override; |
116 | void handleUngrab() override; |
117 | |
118 | void cancelHandle(); |
119 | void executeHandle(bool complete = false); |
120 | |
121 | void updateAllValuesAreInteger(); |
122 | |
123 | qreal from = 0; |
124 | qreal to = 1; |
125 | qreal value = 0; |
126 | qreal position = 0; |
127 | qreal angle = startAngle; |
128 | qreal stepSize = 0; |
129 | QPointF pressPoint; |
130 | qreal positionBeforePress = 0; |
131 | QQuickDial::SnapMode snapMode = QQuickDial::NoSnap; |
132 | QQuickDial::InputMode inputMode = QQuickDial::Circular; |
133 | QQuickDeferredPointer<QQuickItem> handle; |
134 | bool wrap = false; |
135 | bool live = true; |
136 | bool pressed = false; |
137 | bool allValuesAreInteger = false; |
138 | }; |
139 | |
140 | qreal QQuickDialPrivate::valueAt(qreal position) const |
141 | { |
142 | qreal value = from + (to - from) * position; |
143 | |
144 | /* play nice with users expecting that integer from, to and stepSize leads to |
145 | integer values - given that we are using floating point internally (and in |
146 | the API of value), this does not hold, but it is easy enough to handle |
147 | */ |
148 | if (allValuesAreInteger) |
149 | value = qRound(d: value); |
150 | |
151 | return value; |
152 | } |
153 | |
154 | qreal QQuickDialPrivate::snapPosition(qreal position) const |
155 | { |
156 | const qreal range = to - from; |
157 | if (qFuzzyIsNull(d: range)) |
158 | return position; |
159 | |
160 | const qreal effectiveStep = stepSize / range; |
161 | if (qFuzzyIsNull(d: effectiveStep)) |
162 | return position; |
163 | |
164 | return qRound(d: position / effectiveStep) * effectiveStep; |
165 | } |
166 | |
167 | qreal QQuickDialPrivate::positionAt(const QPointF &point) const |
168 | { |
169 | return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); |
170 | } |
171 | |
172 | qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const |
173 | { |
174 | qreal yy = height / 2.0 - point.y(); |
175 | qreal xx = point.x() - width / 2.0; |
176 | qreal angle = (xx || yy) ? std::atan2(y: yy, x: xx) : 0; |
177 | |
178 | if (angle < M_PI / -2) |
179 | angle = angle + M_PI * 2; |
180 | |
181 | qreal normalizedAngle = (startAngleRadians - angle) / endAngleRadians; |
182 | return normalizedAngle; |
183 | } |
184 | |
185 | qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const |
186 | { |
187 | // This value determines the range (either horizontal or vertical) |
188 | // within which the dial can be dragged. |
189 | // The larger this value is, the further the drag distance |
190 | // must be to go from a position of e.g. 0.0 to 1.0. |
191 | qreal dragArea = 0; |
192 | |
193 | // The linear input mode uses a "relative" input system, |
194 | // where the distance from the press point is used to calculate |
195 | // the change in position. Moving the mouse above the press |
196 | // point increases the position (when inputMode is Vertical), |
197 | // and vice versa. This prevents the dial from jumping when clicked. |
198 | qreal dragDistance = 0; |
199 | |
200 | if (inputMode == QQuickDial::Horizontal) { |
201 | dragArea = width * 2; |
202 | dragDistance = pressPoint.x() - point.x(); |
203 | } else { |
204 | dragArea = height * 2; |
205 | dragDistance = point.y() - pressPoint.y(); |
206 | } |
207 | const qreal normalisedDifference = dragDistance / dragArea; |
208 | return qBound(min: qreal(0), val: positionBeforePress - normalisedDifference, max: qreal(1)); |
209 | } |
210 | |
211 | void QQuickDialPrivate::setPosition(qreal pos) |
212 | { |
213 | Q_Q(QQuickDial); |
214 | pos = qBound<qreal>(min: qreal(0), val: pos, max: qreal(1)); |
215 | if (qFuzzyCompare(p1: position, p2: pos)) |
216 | return; |
217 | |
218 | position = pos; |
219 | |
220 | angle = startAngle + position * qAbs(t: endAngle - startAngle); |
221 | |
222 | emit q->positionChanged(); |
223 | emit q->angleChanged(); |
224 | } |
225 | |
226 | void QQuickDialPrivate::updatePosition() |
227 | { |
228 | qreal pos = 0; |
229 | if (!qFuzzyCompare(p1: from, p2: to)) |
230 | pos = (value - from) / (to - from); |
231 | setPosition(pos); |
232 | } |
233 | |
234 | bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPosition) const |
235 | { |
236 | return qAbs(t: proposedPosition - position) >= qreal(0.5) && eventPos.y() >= height / 2; |
237 | } |
238 | |
239 | bool QQuickDialPrivate::isHorizontalOrVertical() const |
240 | { |
241 | return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; |
242 | } |
243 | |
244 | void QQuickDialPrivate::handlePress(const QPointF &point) |
245 | { |
246 | Q_Q(QQuickDial); |
247 | QQuickControlPrivate::handlePress(point); |
248 | pressPoint = point; |
249 | positionBeforePress = position; |
250 | q->setPressed(true); |
251 | } |
252 | |
253 | void QQuickDialPrivate::handleMove(const QPointF &point) |
254 | { |
255 | Q_Q(QQuickDial); |
256 | QQuickControlPrivate::handleMove(point); |
257 | const qreal oldPos = position; |
258 | qreal pos = positionAt(point); |
259 | if (snapMode == QQuickDial::SnapAlways) |
260 | pos = snapPosition(position: pos); |
261 | |
262 | if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(eventPos: point, proposedPosition: pos)))) { |
263 | if (live) |
264 | q->setValue(valueAt(position: pos)); |
265 | else |
266 | setPosition(pos); |
267 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
268 | emit q->moved(); |
269 | } |
270 | } |
271 | |
272 | void QQuickDialPrivate::handleRelease(const QPointF &point) |
273 | { |
274 | Q_Q(QQuickDial); |
275 | QQuickControlPrivate::handleRelease(point); |
276 | if (q->keepMouseGrab() || q->keepTouchGrab()) { |
277 | const qreal oldPos = position; |
278 | qreal pos = positionAt(point); |
279 | if (snapMode != QQuickDial::NoSnap) |
280 | pos = snapPosition(position: pos); |
281 | |
282 | if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(eventPos: point, proposedPosition: pos)))) |
283 | q->setValue(valueAt(position: pos)); |
284 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
285 | emit q->moved(); |
286 | |
287 | q->setKeepMouseGrab(false); |
288 | q->setKeepTouchGrab(false); |
289 | } |
290 | |
291 | q->setPressed(false); |
292 | pressPoint = QPointF(); |
293 | positionBeforePress = 0; |
294 | } |
295 | |
296 | void QQuickDialPrivate::handleUngrab() |
297 | { |
298 | Q_Q(QQuickDial); |
299 | QQuickControlPrivate::handleUngrab(); |
300 | pressPoint = QPointF(); |
301 | positionBeforePress = 0; |
302 | q->setPressed(false); |
303 | } |
304 | |
305 | static inline QString handleName() { return QStringLiteral("handle" ); } |
306 | |
307 | void QQuickDialPrivate::cancelHandle() |
308 | { |
309 | Q_Q(QQuickDial); |
310 | quickCancelDeferred(object: q, property: handleName()); |
311 | } |
312 | |
313 | void QQuickDialPrivate::executeHandle(bool complete) |
314 | { |
315 | Q_Q(QQuickDial); |
316 | if (handle.wasExecuted()) |
317 | return; |
318 | |
319 | if (!handle || complete) |
320 | quickBeginDeferred(object: q, property: handleName(), delegate&: handle); |
321 | if (complete) |
322 | quickCompleteDeferred(object: q, property: handleName(), delegate&: handle); |
323 | } |
324 | |
325 | static bool areRepresentableAsInteger(qreal num1, qreal num2, qreal num3) { |
326 | auto check = [](qreal number) -> bool { return std::nearbyint(x: number) == number; }; |
327 | return check(num1) && check(num2) && check(num3); |
328 | } |
329 | |
330 | void QQuickDialPrivate::updateAllValuesAreInteger() |
331 | { |
332 | allValuesAreInteger = areRepresentableAsInteger(num1: to, num2: from, num3: stepSize) && stepSize != 0.0; |
333 | } |
334 | |
335 | QQuickDial::QQuickDial(QQuickItem *parent) |
336 | : QQuickControl(*(new QQuickDialPrivate), parent) |
337 | { |
338 | setActiveFocusOnTab(true); |
339 | setAcceptedMouseButtons(Qt::LeftButton); |
340 | #if QT_CONFIG(quicktemplates2_multitouch) |
341 | setAcceptTouchEvents(true); |
342 | #endif |
343 | #if QT_CONFIG(cursor) |
344 | setCursor(Qt::ArrowCursor); |
345 | #endif |
346 | } |
347 | |
348 | /*! |
349 | \qmlproperty real QtQuick.Controls::Dial::from |
350 | |
351 | This property holds the starting value for the range. The default value is \c 0.0. |
352 | |
353 | \sa to, value |
354 | */ |
355 | qreal QQuickDial::from() const |
356 | { |
357 | Q_D(const QQuickDial); |
358 | return d->from; |
359 | } |
360 | |
361 | void QQuickDial::setFrom(qreal from) |
362 | { |
363 | Q_D(QQuickDial); |
364 | if (qFuzzyCompare(p1: d->from, p2: from)) |
365 | return; |
366 | |
367 | d->from = from; |
368 | emit fromChanged(); |
369 | d->updateAllValuesAreInteger(); |
370 | if (isComponentComplete()) { |
371 | setValue(d->value); |
372 | d->updatePosition(); |
373 | } |
374 | } |
375 | |
376 | /*! |
377 | \qmlproperty real QtQuick.Controls::Dial::to |
378 | |
379 | This property holds the end value for the range. The default value is |
380 | \c 1.0. |
381 | |
382 | \sa from, value |
383 | */ |
384 | qreal QQuickDial::to() const |
385 | { |
386 | Q_D(const QQuickDial); |
387 | return d->to; |
388 | } |
389 | |
390 | void QQuickDial::setTo(qreal to) |
391 | { |
392 | Q_D(QQuickDial); |
393 | if (qFuzzyCompare(p1: d->to, p2: to)) |
394 | return; |
395 | |
396 | d->to = to; |
397 | d->updateAllValuesAreInteger(); |
398 | emit toChanged(); |
399 | if (isComponentComplete()) { |
400 | setValue(d->value); |
401 | d->updatePosition(); |
402 | } |
403 | } |
404 | |
405 | /*! |
406 | \qmlproperty real QtQuick.Controls::Dial::value |
407 | |
408 | This property holds the value in the range \c from - \c to. The default |
409 | value is \c 0.0. |
410 | |
411 | \sa position, live |
412 | */ |
413 | qreal QQuickDial::value() const |
414 | { |
415 | Q_D(const QQuickDial); |
416 | return d->value; |
417 | } |
418 | |
419 | void QQuickDial::setValue(qreal value) |
420 | { |
421 | Q_D(QQuickDial); |
422 | if (isComponentComplete()) |
423 | value = d->from > d->to ? qBound(min: d->to, val: value, max: d->from) : qBound(min: d->from, val: value, max: d->to); |
424 | |
425 | if (qFuzzyCompare(p1: d->value, p2: value)) |
426 | return; |
427 | |
428 | d->value = value; |
429 | d->updatePosition(); |
430 | emit valueChanged(); |
431 | } |
432 | |
433 | /*! |
434 | \qmlproperty real QtQuick.Controls::Dial::position |
435 | \readonly |
436 | |
437 | This property holds the logical position of the handle. |
438 | |
439 | The position is expressed as a fraction of the control's angle range (the |
440 | range within which the handle can be moved) in the range \c {0.0 - 1.0}. |
441 | |
442 | \sa value, angle |
443 | */ |
444 | qreal QQuickDial::position() const |
445 | { |
446 | Q_D(const QQuickDial); |
447 | return d->position; |
448 | } |
449 | |
450 | /*! |
451 | \qmlproperty real QtQuick.Controls::Dial::angle |
452 | \readonly |
453 | |
454 | This property holds the angle of the handle. |
455 | |
456 | The range is from \c -140 degrees to \c 140 degrees. |
457 | |
458 | \sa position |
459 | */ |
460 | qreal QQuickDial::angle() const |
461 | { |
462 | Q_D(const QQuickDial); |
463 | return d->angle; |
464 | } |
465 | |
466 | /*! |
467 | \qmlproperty real QtQuick.Controls::Dial::stepSize |
468 | |
469 | This property holds the step size. |
470 | |
471 | The step size determines the amount by which the dial's value |
472 | is increased and decreased when interacted with via the keyboard. |
473 | For example, a step size of \c 0.2, will result in the dial's |
474 | value increasing and decreasing in increments of \c 0.2. |
475 | |
476 | The step size is only respected for touch and mouse interaction |
477 | when \l snapMode is set to a value other than \c Dial.NoSnap. |
478 | |
479 | The default value is \c 0.0, which results in an effective step |
480 | size of \c 0.1 for keyboard interaction. |
481 | |
482 | \sa snapMode, increase(), decrease() |
483 | */ |
484 | qreal QQuickDial::stepSize() const |
485 | { |
486 | Q_D(const QQuickDial); |
487 | return d->stepSize; |
488 | } |
489 | |
490 | void QQuickDial::setStepSize(qreal step) |
491 | { |
492 | Q_D(QQuickDial); |
493 | if (qFuzzyCompare(p1: d->stepSize, p2: step)) |
494 | return; |
495 | |
496 | d->stepSize = step; |
497 | d->updateAllValuesAreInteger(); |
498 | emit stepSizeChanged(); |
499 | } |
500 | |
501 | /*! |
502 | \qmlproperty enumeration QtQuick.Controls::Dial::snapMode |
503 | |
504 | This property holds the snap mode. |
505 | |
506 | The snap mode works with the \l stepSize to allow the handle to snap to |
507 | certain points along the dial. |
508 | |
509 | Possible values: |
510 | \value Dial.NoSnap The dial does not snap (default). |
511 | \value Dial.SnapAlways The dial snaps while the handle is dragged. |
512 | \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released. |
513 | |
514 | \sa stepSize |
515 | */ |
516 | QQuickDial::SnapMode QQuickDial::snapMode() const |
517 | { |
518 | Q_D(const QQuickDial); |
519 | return d->snapMode; |
520 | } |
521 | |
522 | void QQuickDial::setSnapMode(SnapMode mode) |
523 | { |
524 | Q_D(QQuickDial); |
525 | if (d->snapMode == mode) |
526 | return; |
527 | |
528 | d->snapMode = mode; |
529 | emit snapModeChanged(); |
530 | } |
531 | |
532 | /*! |
533 | \since QtQuick.Controls 2.5 (Qt 5.12) |
534 | \qmlproperty enumeration QtQuick.Controls::Dial::inputMode |
535 | |
536 | This property holds the input mode. |
537 | |
538 | \include qquickdial.qdocinc inputMode |
539 | |
540 | The default value is \c Dial.Circular. |
541 | */ |
542 | QQuickDial::InputMode QQuickDial::inputMode() const |
543 | { |
544 | Q_D(const QQuickDial); |
545 | return d->inputMode; |
546 | } |
547 | |
548 | void QQuickDial::setInputMode(QQuickDial::InputMode mode) |
549 | { |
550 | Q_D(QQuickDial); |
551 | if (d->inputMode == mode) |
552 | return; |
553 | |
554 | d->inputMode = mode; |
555 | emit inputModeChanged(); |
556 | } |
557 | |
558 | /*! |
559 | \qmlproperty bool QtQuick.Controls::Dial::wrap |
560 | |
561 | This property holds whether the dial wraps when dragged. |
562 | |
563 | For example, when this property is set to \c true, dragging the dial past |
564 | the \l to position will result in the handle being positioned at the |
565 | \l from position, and vice versa: |
566 | |
567 | \image qtquickcontrols2-dial-wrap.gif |
568 | |
569 | When this property is \c false, it's not possible to drag the dial across |
570 | the from and to values. |
571 | |
572 | \image qtquickcontrols2-dial-no-wrap.gif |
573 | |
574 | The default value is \c false. |
575 | */ |
576 | bool QQuickDial::wrap() const |
577 | { |
578 | Q_D(const QQuickDial); |
579 | return d->wrap; |
580 | } |
581 | |
582 | void QQuickDial::setWrap(bool wrap) |
583 | { |
584 | Q_D(QQuickDial); |
585 | if (d->wrap == wrap) |
586 | return; |
587 | |
588 | d->wrap = wrap; |
589 | emit wrapChanged(); |
590 | } |
591 | |
592 | /*! |
593 | \qmlproperty bool QtQuick.Controls::Dial::pressed |
594 | |
595 | This property holds whether the dial is pressed. |
596 | |
597 | The dial will be pressed when either the mouse is pressed over it, or a key |
598 | such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial |
599 | be pressed upon key presses (due to styling reasons, for example), you can |
600 | use the \l {Keys}{Keys attached property}: |
601 | |
602 | \code |
603 | Dial { |
604 | Keys.onLeftPressed: {} |
605 | } |
606 | \endcode |
607 | |
608 | This will result in pressed only being \c true upon mouse presses. |
609 | */ |
610 | bool QQuickDial::isPressed() const |
611 | { |
612 | Q_D(const QQuickDial); |
613 | return d->pressed; |
614 | } |
615 | |
616 | void QQuickDial::setPressed(bool pressed) |
617 | { |
618 | Q_D(QQuickDial); |
619 | if (d->pressed == pressed) |
620 | return; |
621 | |
622 | d->pressed = pressed; |
623 | setAccessibleProperty(propertyName: "pressed" , value: pressed); |
624 | emit pressedChanged(); |
625 | } |
626 | |
627 | /*! |
628 | \qmlproperty Item QtQuick.Controls::Dial::handle |
629 | |
630 | This property holds the handle of the dial. |
631 | |
632 | The handle acts as a visual indicator of the position of the dial. |
633 | |
634 | \sa {Customizing Dial} |
635 | */ |
636 | QQuickItem *QQuickDial::handle() const |
637 | { |
638 | QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func()); |
639 | if (!d->handle) |
640 | d->executeHandle(); |
641 | return d->handle; |
642 | } |
643 | |
644 | void QQuickDial::setHandle(QQuickItem *handle) |
645 | { |
646 | Q_D(QQuickDial); |
647 | if (handle == d->handle) |
648 | return; |
649 | |
650 | if (!d->handle.isExecuting()) |
651 | d->cancelHandle(); |
652 | |
653 | QQuickControlPrivate::hideOldItem(item: d->handle); |
654 | d->handle = handle; |
655 | if (d->handle && !d->handle->parentItem()) |
656 | d->handle->setParentItem(this); |
657 | if (!d->handle.isExecuting()) |
658 | emit handleChanged(); |
659 | } |
660 | |
661 | /*! |
662 | \since QtQuick.Controls 2.2 (Qt 5.9) |
663 | \qmlproperty bool QtQuick.Controls::Dial::live |
664 | |
665 | This property holds whether the dial provides live updates for the \l value |
666 | property while the handle is dragged. |
667 | |
668 | The default value is \c true. |
669 | |
670 | \sa value |
671 | */ |
672 | bool QQuickDial::live() const |
673 | { |
674 | Q_D(const QQuickDial); |
675 | return d->live; |
676 | } |
677 | |
678 | void QQuickDial::setLive(bool live) |
679 | { |
680 | Q_D(QQuickDial); |
681 | if (d->live == live) |
682 | return; |
683 | |
684 | d->live = live; |
685 | emit liveChanged(); |
686 | } |
687 | |
688 | /*! |
689 | \qmlmethod void QtQuick.Controls::Dial::increase() |
690 | |
691 | Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
692 | |
693 | \sa stepSize |
694 | */ |
695 | void QQuickDial::increase() |
696 | { |
697 | Q_D(QQuickDial); |
698 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
699 | setValue(d->value + step); |
700 | } |
701 | |
702 | /*! |
703 | \qmlmethod void QtQuick.Controls::Dial::decrease() |
704 | |
705 | Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
706 | |
707 | \sa stepSize |
708 | */ |
709 | void QQuickDial::decrease() |
710 | { |
711 | Q_D(QQuickDial); |
712 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
713 | setValue(d->value - step); |
714 | } |
715 | |
716 | void QQuickDial::keyPressEvent(QKeyEvent *event) |
717 | { |
718 | Q_D(QQuickDial); |
719 | const qreal oldValue = d->value; |
720 | switch (event->key()) { |
721 | case Qt::Key_Left: |
722 | case Qt::Key_Down: |
723 | setPressed(true); |
724 | if (isMirrored()) |
725 | increase(); |
726 | else |
727 | decrease(); |
728 | break; |
729 | |
730 | case Qt::Key_Right: |
731 | case Qt::Key_Up: |
732 | setPressed(true); |
733 | if (isMirrored()) |
734 | decrease(); |
735 | else |
736 | increase(); |
737 | break; |
738 | |
739 | case Qt::Key_Home: |
740 | setPressed(true); |
741 | setValue(isMirrored() ? d->to : d->from); |
742 | break; |
743 | |
744 | case Qt::Key_End: |
745 | setPressed(true); |
746 | setValue(isMirrored() ? d->from : d->to); |
747 | break; |
748 | |
749 | default: |
750 | event->ignore(); |
751 | QQuickControl::keyPressEvent(event); |
752 | break; |
753 | } |
754 | if (!qFuzzyCompare(p1: d->value, p2: oldValue)) |
755 | emit moved(); |
756 | } |
757 | |
758 | void QQuickDial::keyReleaseEvent(QKeyEvent *event) |
759 | { |
760 | QQuickControl::keyReleaseEvent(event); |
761 | setPressed(false); |
762 | } |
763 | |
764 | void QQuickDial::mousePressEvent(QMouseEvent *event) |
765 | { |
766 | Q_D(QQuickDial); |
767 | QQuickControl::mousePressEvent(event); |
768 | d->handleMove(point: event->localPos()); |
769 | setKeepMouseGrab(true); |
770 | } |
771 | |
772 | #if QT_CONFIG(quicktemplates2_multitouch) |
773 | void QQuickDial::touchEvent(QTouchEvent *event) |
774 | { |
775 | Q_D(QQuickDial); |
776 | switch (event->type()) { |
777 | case QEvent::TouchUpdate: |
778 | for (const QTouchEvent::TouchPoint &point : event->touchPoints()) { |
779 | if (!d->acceptTouch(point)) |
780 | continue; |
781 | |
782 | switch (point.state()) { |
783 | case Qt::TouchPointMoved: |
784 | if (!keepTouchGrab()) { |
785 | bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.pos().x() - d->pressPoint.x(), axis: Qt::XAxis, tp: &point); |
786 | setKeepTouchGrab(overXDragThreshold); |
787 | |
788 | if (!overXDragThreshold) { |
789 | bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.pos().y() - d->pressPoint.y(), axis: Qt::YAxis, tp: &point); |
790 | setKeepTouchGrab(overYDragThreshold); |
791 | } |
792 | } |
793 | if (keepTouchGrab()) |
794 | d->handleMove(point: point.pos()); |
795 | break; |
796 | |
797 | default: |
798 | QQuickControl::touchEvent(event); |
799 | break; |
800 | } |
801 | } |
802 | break; |
803 | |
804 | default: |
805 | QQuickControl::touchEvent(event); |
806 | break; |
807 | } |
808 | } |
809 | #endif |
810 | |
811 | #if QT_CONFIG(wheelevent) |
812 | void QQuickDial::wheelEvent(QWheelEvent *event) |
813 | { |
814 | Q_D(QQuickDial); |
815 | QQuickControl::wheelEvent(event); |
816 | if (d->wheelEnabled) { |
817 | const qreal oldValue = d->value; |
818 | const QPointF angle = event->angleDelta(); |
819 | const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / QWheelEvent::DefaultDeltasPerStep; |
820 | const qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
821 | setValue(oldValue + step * delta); |
822 | event->setAccepted(!qFuzzyCompare(p1: d->value, p2: oldValue)); |
823 | } |
824 | } |
825 | #endif |
826 | |
827 | void QQuickDial::mirrorChange() |
828 | { |
829 | QQuickControl::mirrorChange(); |
830 | emit angleChanged(); |
831 | } |
832 | |
833 | void QQuickDial::componentComplete() |
834 | { |
835 | Q_D(QQuickDial); |
836 | d->executeHandle(complete: true); |
837 | QQuickControl::componentComplete(); |
838 | setValue(d->value); |
839 | d->updatePosition(); |
840 | } |
841 | |
842 | #if QT_CONFIG(accessibility) |
843 | void QQuickDial::accessibilityActiveChanged(bool active) |
844 | { |
845 | QQuickControl::accessibilityActiveChanged(active); |
846 | |
847 | Q_D(QQuickDial); |
848 | if (active) |
849 | setAccessibleProperty(propertyName: "pressed" , value: d->pressed); |
850 | } |
851 | |
852 | QAccessible::Role QQuickDial::accessibleRole() const |
853 | { |
854 | return QAccessible::Dial; |
855 | } |
856 | #endif |
857 | |
858 | QT_END_NAMESPACE |
859 | |