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 "qquickrangeslider_p.h"
38#include "qquickcontrol_p_p.h"
39#include "qquickdeferredexecute_p_p.h"
40
41#include <QtCore/qscopedpointer.h>
42#include <QtQuick/private/qquickwindow_p.h>
43
44QT_BEGIN_NAMESPACE
45
46/*!
47 \qmltype RangeSlider
48 \inherits Control
49//! \instantiates QQuickRangeSlider
50 \inqmlmodule QtQuick.Controls
51 \since 5.7
52 \ingroup qtquickcontrols2-input
53 \ingroup qtquickcontrols2-focusscopes
54 \brief Used to select a range of values by sliding two handles along a track.
55
56 \image qtquickcontrols2-rangeslider.gif
57
58 RangeSlider is used to select a range specified by two values, by sliding
59 each handle along a track.
60
61 In the example below, custom \l from and \l to values are set, and the
62 initial positions of the \l first and \l second handles are set:
63
64 \code
65 RangeSlider {
66 from: 1
67 to: 100
68 first.value: 25
69 second.value: 75
70 }
71 \endcode
72
73 In order to perform an action when the value for a particular handle changes,
74 use the following syntax:
75
76 \code
77 first.onMoved: console.log("first.value changed to " + first.value)
78 \endcode
79
80 The \l {first.position} and \l {second.position} properties are expressed as
81 fractions of the control's size, in the range \c {0.0 - 1.0}.
82 The \l {first.visualPosition} and \l {second.visualPosition} properties are
83 the same, except that they are reversed in a
84 \l {Right-to-left User Interfaces}{right-to-left} application.
85 The \c visualPosition is useful for positioning the handles when styling
86 RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24
87 in a left-to-right application, and \c 0.76 in a right-to-left application.
88
89 For a slider that allows the user to select a single value, see \l Slider.
90
91 \sa {Customizing RangeSlider}, {Input Controls},
92 {Focus Management in Qt Quick Controls}
93*/
94
95class QQuickRangeSliderNodePrivate : public QObjectPrivate
96{
97 Q_DECLARE_PUBLIC(QQuickRangeSliderNode)
98public:
99 QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider)
100 : value(value),
101 slider(slider)
102 {
103 }
104
105 bool isFirst() const;
106
107 void setPosition(qreal position, bool ignoreOtherPosition = false);
108 void updatePosition(bool ignoreOtherPosition = false);
109
110 void cancelHandle();
111 void executeHandle(bool complete = false);
112
113 static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node);
114
115 qreal value = 0;
116 bool isPendingValue = false;
117 qreal pendingValue = 0;
118 qreal position = 0;
119 QQuickDeferredPointer<QQuickItem> handle;
120 QQuickRangeSlider *slider = nullptr;
121 bool pressed = false;
122 bool hovered = false;
123 int touchId = -1;
124};
125
126bool QQuickRangeSliderNodePrivate::isFirst() const
127{
128 return this == get(node: slider->first());
129}
130
131void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition)
132{
133 Q_Q(QQuickRangeSliderNode);
134
135 const qreal min = isFirst() || ignoreOtherPosition ? 0.0 : qMax<qreal>(a: 0.0, b: slider->first()->position());
136 const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin<qreal>(a: 1.0, b: slider->second()->position());
137 position = qBound(min, val: position, max);
138 if (!qFuzzyCompare(p1: this->position, p2: position)) {
139 this->position = position;
140 emit q->positionChanged();
141 emit q->visualPositionChanged();
142 }
143}
144
145void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition)
146{
147 qreal pos = 0;
148 if (!qFuzzyCompare(p1: slider->from(), p2: slider->to()))
149 pos = (value - slider->from()) / (slider->to() - slider->from());
150 setPosition(position: pos, ignoreOtherPosition);
151}
152
153static inline QString handleName() { return QStringLiteral("handle"); }
154
155void QQuickRangeSliderNodePrivate::cancelHandle()
156{
157 Q_Q(QQuickRangeSliderNode);
158 quickCancelDeferred(object: q, property: handleName());
159}
160
161void QQuickRangeSliderNodePrivate::executeHandle(bool complete)
162{
163 Q_Q(QQuickRangeSliderNode);
164 if (handle.wasExecuted())
165 return;
166
167 if (!handle || complete)
168 quickBeginDeferred(object: q, property: handleName(), delegate&: handle);
169 if (complete)
170 quickCompleteDeferred(object: q, property: handleName(), delegate&: handle);
171}
172
173QQuickRangeSliderNodePrivate *QQuickRangeSliderNodePrivate::get(QQuickRangeSliderNode *node)
174{
175 return node->d_func();
176}
177
178QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider)
179 : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider)
180{
181}
182
183QQuickRangeSliderNode::~QQuickRangeSliderNode()
184{
185}
186
187qreal QQuickRangeSliderNode::value() const
188{
189 Q_D(const QQuickRangeSliderNode);
190 return d->value;
191}
192
193void QQuickRangeSliderNode::setValue(qreal value)
194{
195 Q_D(QQuickRangeSliderNode);
196 if (!d->slider->isComponentComplete()) {
197 d->pendingValue = value;
198 d->isPendingValue = true;
199 return;
200 }
201
202 // First, restrict the first value to be within to and from.
203 const qreal smaller = qMin(a: d->slider->to(), b: d->slider->from());
204 const qreal larger = qMax(a: d->slider->to(), b: d->slider->from());
205 value = qBound(min: smaller, val: value, max: larger);
206
207 // Then, ensure that it doesn't go past the other value,
208 // a check that depends on whether or not the range is inverted.
209 const bool invertedRange = d->slider->from() > d->slider->to();
210 if (d->isFirst()) {
211 if (invertedRange) {
212 if (value < d->slider->second()->value())
213 value = d->slider->second()->value();
214 } else {
215 if (value > d->slider->second()->value())
216 value = d->slider->second()->value();
217 }
218 } else {
219 if (invertedRange) {
220 if (value > d->slider->first()->value())
221 value = d->slider->first()->value();
222 } else {
223 if (value < d->slider->first()->value())
224 value = d->slider->first()->value();
225 }
226 }
227
228 if (!qFuzzyCompare(p1: d->value, p2: value)) {
229 d->value = value;
230 d->updatePosition();
231 emit valueChanged();
232 }
233}
234
235qreal QQuickRangeSliderNode::position() const
236{
237 Q_D(const QQuickRangeSliderNode);
238 return d->position;
239}
240
241qreal QQuickRangeSliderNode::visualPosition() const
242{
243 Q_D(const QQuickRangeSliderNode);
244 if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored())
245 return 1.0 - d->position;
246 return d->position;
247}
248
249QQuickItem *QQuickRangeSliderNode::handle() const
250{
251 QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func());
252 if (!d->handle)
253 d->executeHandle();
254 return d->handle;
255}
256
257void QQuickRangeSliderNode::setHandle(QQuickItem *handle)
258{
259 Q_D(QQuickRangeSliderNode);
260 if (d->handle == handle)
261 return;
262
263 if (!d->handle.isExecuting())
264 d->cancelHandle();
265
266 const qreal oldImplicitHandleWidth = implicitHandleWidth();
267 const qreal oldImplicitHandleHeight = implicitHandleHeight();
268
269 QQuickControlPrivate::get(control: d->slider)->removeImplicitSizeListener(item: d->handle);
270 QQuickControlPrivate::hideOldItem(item: d->handle);
271 d->handle = handle;
272
273 if (handle) {
274 if (!handle->parentItem())
275 handle->setParentItem(d->slider);
276
277 QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->first())->handle;
278 QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->second())->handle;
279 if (firstHandle && secondHandle) {
280 // The order of property assignments in QML is undefined,
281 // but we need the first handle to be before the second due
282 // to focus order constraints, so check for that here.
283 const QList<QQuickItem *> childItems = d->slider->childItems();
284 const int firstIndex = childItems.indexOf(t: firstHandle);
285 const int secondIndex = childItems.indexOf(t: secondHandle);
286 if (firstIndex != -1 && secondIndex != -1 && firstIndex > secondIndex) {
287 firstHandle->stackBefore(secondHandle);
288 // Ensure we have some way of knowing which handle is above
289 // the other when it comes to mouse presses, and also that
290 // they are rendered in the correct order.
291 secondHandle->setZ(secondHandle->z() + 1);
292 }
293 }
294
295 handle->setActiveFocusOnTab(true);
296 QQuickControlPrivate::get(control: d->slider)->addImplicitSizeListener(item: handle);
297 }
298
299 if (!qFuzzyCompare(p1: oldImplicitHandleWidth, p2: implicitHandleWidth()))
300 emit implicitHandleWidthChanged();
301 if (!qFuzzyCompare(p1: oldImplicitHandleHeight, p2: implicitHandleHeight()))
302 emit implicitHandleHeightChanged();
303 if (!d->handle.isExecuting())
304 emit handleChanged();
305}
306
307bool QQuickRangeSliderNode::isPressed() const
308{
309 Q_D(const QQuickRangeSliderNode);
310 return d->pressed;
311}
312
313void QQuickRangeSliderNode::setPressed(bool pressed)
314{
315 Q_D(QQuickRangeSliderNode);
316 if (d->pressed == pressed)
317 return;
318
319 d->pressed = pressed;
320 d->slider->setAccessibleProperty(propertyName: "pressed", value: pressed || d->slider->second()->isPressed());
321 emit pressedChanged();
322}
323
324bool QQuickRangeSliderNode::isHovered() const
325{
326 Q_D(const QQuickRangeSliderNode);
327 return d->hovered;
328}
329
330void QQuickRangeSliderNode::setHovered(bool hovered)
331{
332 Q_D(QQuickRangeSliderNode);
333 if (d->hovered == hovered)
334 return;
335
336 d->hovered = hovered;
337 emit hoveredChanged();
338}
339
340qreal QQuickRangeSliderNode::implicitHandleWidth() const
341{
342 Q_D(const QQuickRangeSliderNode);
343 if (!d->handle)
344 return 0;
345 return d->handle->implicitWidth();
346}
347
348qreal QQuickRangeSliderNode::implicitHandleHeight() const
349{
350 Q_D(const QQuickRangeSliderNode);
351 if (!d->handle)
352 return 0;
353 return d->handle->implicitHeight();
354}
355
356void QQuickRangeSliderNode::increase()
357{
358 Q_D(QQuickRangeSliderNode);
359 qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
360 setValue(d->value + step);
361}
362
363void QQuickRangeSliderNode::decrease()
364{
365 Q_D(QQuickRangeSliderNode);
366 qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
367 setValue(d->value - step);
368}
369
370static const qreal defaultFrom = 0.0;
371static const qreal defaultTo = 1.0;
372
373class QQuickRangeSliderPrivate : public QQuickControlPrivate
374{
375 Q_DECLARE_PUBLIC(QQuickRangeSlider)
376
377public:
378 QQuickRangeSliderNode *pressedNode(int touchId = -1) const;
379
380#if QT_CONFIG(quicktemplates2_multitouch)
381 bool acceptTouch(const QTouchEvent::TouchPoint &point) override;
382#endif
383 void handlePress(const QPointF &point) override;
384 void handleMove(const QPointF &point) override;
385 void handleRelease(const QPointF &point) override;
386 void handleUngrab() override;
387
388 void updateHover(const QPointF &pos);
389
390 void itemImplicitWidthChanged(QQuickItem *item) override;
391 void itemImplicitHeightChanged(QQuickItem *item) override;
392
393 bool live = true;
394 qreal from = defaultFrom;
395 qreal to = defaultTo;
396 qreal stepSize = 0;
397 qreal touchDragThreshold = -1;
398 QQuickRangeSliderNode *first = nullptr;
399 QQuickRangeSliderNode *second = nullptr;
400 QPointF pressPoint;
401 Qt::Orientation orientation = Qt::Horizontal;
402 QQuickRangeSlider::SnapMode snapMode = QQuickRangeSlider::NoSnap;
403};
404
405static qreal valueAt(const QQuickRangeSlider *slider, qreal position)
406{
407 return slider->from() + (slider->to() - slider->from()) * position;
408}
409
410static qreal snapPosition(const QQuickRangeSlider *slider, qreal position)
411{
412 const qreal range = slider->to() - slider->from();
413 if (qFuzzyIsNull(d: range))
414 return position;
415
416 const qreal effectiveStep = slider->stepSize() / range;
417 if (qFuzzyIsNull(d: effectiveStep))
418 return position;
419
420 return qRound(d: position / effectiveStep) * effectiveStep;
421}
422
423static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point)
424{
425 if (slider->orientation() == Qt::Horizontal) {
426 const qreal hw = handle ? handle->width() : 0;
427 const qreal offset = hw / 2;
428 const qreal extent = slider->availableWidth() - hw;
429 if (!qFuzzyIsNull(d: extent)) {
430 if (slider->isMirrored())
431 return (slider->width() - point.x() - slider->rightPadding() - offset) / extent;
432 return (point.x() - slider->leftPadding() - offset) / extent;
433 }
434 } else {
435 const qreal hh = handle ? handle->height() : 0;
436 const qreal offset = hh / 2;
437 const qreal extent = slider->availableHeight() - hh;
438 if (!qFuzzyIsNull(d: extent))
439 return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent;
440 }
441 return 0;
442}
443
444QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const
445{
446 if (touchId == -1)
447 return first->isPressed() ? first : (second->isPressed() ? second : nullptr);
448 if (QQuickRangeSliderNodePrivate::get(node: first)->touchId == touchId)
449 return first;
450 if (QQuickRangeSliderNodePrivate::get(node: second)->touchId == touchId)
451 return second;
452 return nullptr;
453}
454
455#if QT_CONFIG(quicktemplates2_multitouch)
456bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point)
457{
458 int firstId = QQuickRangeSliderNodePrivate::get(node: first)->touchId;
459 int secondId = QQuickRangeSliderNodePrivate::get(node: second)->touchId;
460
461 if (((firstId == -1 || secondId == -1) && point.state() == Qt::TouchPointPressed) || point.id() == firstId || point.id() == secondId) {
462 touchId = point.id();
463 return true;
464 }
465
466 return false;
467}
468#endif
469
470void QQuickRangeSliderPrivate::handlePress(const QPointF &point)
471{
472 Q_Q(QQuickRangeSlider);
473 QQuickControlPrivate::handlePress(point);
474 pressPoint = point;
475
476 QQuickItem *firstHandle = first->handle();
477 QQuickItem *secondHandle = second->handle();
478 const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(point: q->mapToItem(item: firstHandle, point));
479 const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(point: q->mapToItem(item: secondHandle, point));
480 QQuickRangeSliderNode *hitNode = nullptr;
481 QQuickRangeSliderNode *otherNode = nullptr;
482
483 if (firstHit && secondHit) {
484 // choose highest
485 hitNode = firstHandle->z() > secondHandle->z() ? first : second;
486 otherNode = firstHandle->z() > secondHandle->z() ? second : first;
487 } else if (firstHit) {
488 hitNode = first;
489 otherNode = second;
490 } else if (secondHit) {
491 hitNode = second;
492 otherNode = first;
493 } else {
494 // find the nearest
495 const qreal firstPos = positionAt(slider: q, handle: firstHandle, point);
496 const qreal secondPos = positionAt(slider: q, handle: secondHandle, point);
497 const qreal firstDistance = qAbs(t: firstPos - first->position());
498 const qreal secondDistance = qAbs(t: secondPos - second->position());
499
500 if (qFuzzyCompare(p1: firstDistance, p2: secondDistance)) {
501 // same distance => choose the one that can be moved towards the press position
502 const bool inverted = from > to;
503 if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) {
504 hitNode = first;
505 otherNode = second;
506 } else {
507 hitNode = second;
508 otherNode = first;
509 }
510 } else if (firstDistance < secondDistance) {
511 hitNode = first;
512 otherNode = second;
513 } else {
514 hitNode = second;
515 otherNode = first;
516 }
517 }
518
519 if (hitNode) {
520 hitNode->setPressed(true);
521 if (QQuickItem *handle = hitNode->handle())
522 handle->setZ(1);
523 QQuickRangeSliderNodePrivate::get(node: hitNode)->touchId = touchId;
524 }
525 if (otherNode) {
526 if (QQuickItem *handle = otherNode->handle())
527 handle->setZ(0);
528 }
529}
530
531void QQuickRangeSliderPrivate::handleMove(const QPointF &point)
532{
533 Q_Q(QQuickRangeSlider);
534 QQuickControlPrivate::handleMove(point);
535 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
536 if (pressedNode) {
537 const qreal oldPos = pressedNode->position();
538 qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point);
539 if (snapMode == QQuickRangeSlider::SnapAlways)
540 pos = snapPosition(slider: q, position: pos);
541 if (live)
542 pressedNode->setValue(valueAt(slider: q, position: pos));
543 else
544 QQuickRangeSliderNodePrivate::get(node: pressedNode)->setPosition(position: pos);
545
546 if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos))
547 emit pressedNode->moved();
548 }
549}
550
551void QQuickRangeSliderPrivate::handleRelease(const QPointF &point)
552{
553 Q_Q(QQuickRangeSlider);
554 QQuickControlPrivate::handleRelease(point);
555 pressPoint = QPointF();
556
557 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
558 if (!pressedNode)
559 return;
560 QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(node: pressedNode);
561
562 if (q->keepMouseGrab() || q->keepTouchGrab()) {
563 const qreal oldPos = pressedNode->position();
564 qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point);
565 if (snapMode != QQuickRangeSlider::NoSnap)
566 pos = snapPosition(slider: q, position: pos);
567 qreal val = valueAt(slider: q, position: pos);
568 if (!qFuzzyCompare(p1: val, p2: pressedNode->value()))
569 pressedNode->setValue(val);
570 else if (snapMode != QQuickRangeSlider::NoSnap)
571 pressedNodePrivate->setPosition(position: pos);
572 q->setKeepMouseGrab(false);
573 q->setKeepTouchGrab(false);
574
575 if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos))
576 emit pressedNode->moved();
577 }
578 pressedNode->setPressed(false);
579 pressedNodePrivate->touchId = -1;
580}
581
582void QQuickRangeSliderPrivate::handleUngrab()
583{
584 QQuickControlPrivate::handleUngrab();
585 pressPoint = QPointF();
586 first->setPressed(false);
587 second->setPressed(false);
588 QQuickRangeSliderNodePrivate::get(node: first)->touchId = -1;
589 QQuickRangeSliderNodePrivate::get(node: second)->touchId = -1;
590}
591
592void QQuickRangeSliderPrivate::updateHover(const QPointF &pos)
593{
594 Q_Q(QQuickRangeSlider);
595 QQuickItem *firstHandle = first->handle();
596 QQuickItem *secondHandle = second->handle();
597 first->setHovered(firstHandle && firstHandle->isEnabled() && firstHandle->contains(point: q->mapToItem(item: firstHandle, point: pos)));
598 second->setHovered(secondHandle && secondHandle->isEnabled() && secondHandle->contains(point: q->mapToItem(item: secondHandle, point: pos)));
599}
600
601void QQuickRangeSliderPrivate::itemImplicitWidthChanged(QQuickItem *item)
602{
603 QQuickControlPrivate::itemImplicitWidthChanged(item);
604 if (item == first->handle())
605 emit first->implicitHandleWidthChanged();
606 else if (item == second->handle())
607 emit second->implicitHandleWidthChanged();
608}
609
610void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item)
611{
612 QQuickControlPrivate::itemImplicitHeightChanged(item);
613 if (item == first->handle())
614 emit first->implicitHandleHeightChanged();
615 else if (item == second->handle())
616 emit second->implicitHandleHeightChanged();
617}
618
619QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent)
620 : QQuickControl(*(new QQuickRangeSliderPrivate), parent)
621{
622 Q_D(QQuickRangeSlider);
623 d->first = new QQuickRangeSliderNode(0.0, this);
624 d->second = new QQuickRangeSliderNode(1.0, this);
625
626 setFlag(flag: QQuickItem::ItemIsFocusScope);
627 setAcceptedMouseButtons(Qt::LeftButton);
628#if QT_CONFIG(quicktemplates2_multitouch)
629 setAcceptTouchEvents(true);
630#endif
631#if QT_CONFIG(cursor)
632 setCursor(Qt::ArrowCursor);
633#endif
634}
635
636QQuickRangeSlider::~QQuickRangeSlider()
637{
638 Q_D(QQuickRangeSlider);
639 d->removeImplicitSizeListener(item: d->first->handle());
640 d->removeImplicitSizeListener(item: d->second->handle());
641}
642
643/*!
644 \qmlproperty real QtQuick.Controls::RangeSlider::from
645
646 This property holds the starting value for the range. The default value is \c 0.0.
647
648 \sa to, first.value, second.value
649*/
650qreal QQuickRangeSlider::from() const
651{
652 Q_D(const QQuickRangeSlider);
653 return d->from;
654}
655
656void QQuickRangeSlider::setFrom(qreal from)
657{
658 Q_D(QQuickRangeSlider);
659 if (qFuzzyCompare(p1: d->from, p2: from))
660 return;
661
662 d->from = from;
663 emit fromChanged();
664
665 if (isComponentComplete()) {
666 d->first->setValue(d->first->value());
667 d->second->setValue(d->second->value());
668 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
669 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
670 firstPrivate->updatePosition(ignoreOtherPosition: true);
671 secondPrivate->updatePosition();
672 }
673}
674
675/*!
676 \qmlproperty real QtQuick.Controls::RangeSlider::to
677
678 This property holds the end value for the range. The default value is \c 1.0.
679
680 \sa from, first.value, second.value
681*/
682qreal QQuickRangeSlider::to() const
683{
684 Q_D(const QQuickRangeSlider);
685 return d->to;
686}
687
688void QQuickRangeSlider::setTo(qreal to)
689{
690 Q_D(QQuickRangeSlider);
691 if (qFuzzyCompare(p1: d->to, p2: to))
692 return;
693
694 d->to = to;
695 emit toChanged();
696
697 if (isComponentComplete()) {
698 d->first->setValue(d->first->value());
699 d->second->setValue(d->second->value());
700 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
701 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
702 firstPrivate->updatePosition(ignoreOtherPosition: true);
703 secondPrivate->updatePosition();
704 }
705}
706
707/*!
708 \since QtQuick.Controls 2.5 (Qt 5.12)
709 \qmlproperty qreal QtQuick.Controls::RangeSlider::touchDragThreshold
710
711 This property holds the threshold (in logical pixels) at which a touch drag event will be initiated.
712 The mouse drag threshold won't be affected.
713 The default value is \c Qt.styleHints.startDragDistance.
714
715 \sa QStyleHints
716
717*/
718qreal QQuickRangeSlider::touchDragThreshold() const
719{
720 Q_D(const QQuickRangeSlider);
721 return d->touchDragThreshold;
722}
723
724void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold)
725{
726 Q_D(QQuickRangeSlider);
727 if (d->touchDragThreshold == touchDragThreshold)
728 return;
729
730 d->touchDragThreshold = touchDragThreshold;
731 emit touchDragThresholdChanged();
732}
733
734void QQuickRangeSlider::resetTouchDragThreshold()
735{
736 setTouchDragThreshold(-1);
737}
738
739/*!
740 \since QtQuick.Controls 2.5 (Qt 5.12)
741 \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position)
742
743 Returns the value for the given \a position.
744
745 \sa first.value, second.value, first.position, second.position, live
746*/
747qreal QQuickRangeSlider::valueAt(qreal position) const
748{
749 Q_D(const QQuickRangeSlider);
750 const qreal value = (d->to - d->from) * position;
751 if (qFuzzyIsNull(d: d->stepSize))
752 return d->from + value;
753 return d->from + qRound(d: value / d->stepSize) * d->stepSize;
754}
755
756/*!
757 \qmlproperty real QtQuick.Controls::RangeSlider::first.value
758 \qmlproperty real QtQuick.Controls::RangeSlider::first.position
759 \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
760 \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle
761 \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed
762 \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered
763 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth
764 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight
765
766 \table
767 \header
768 \li Property
769 \li Description
770 \row
771 \li value
772 \li This property holds the value of the first handle in the range
773 \c from - \c to.
774
775 If \l from is greater than \l to, the value of the first handle
776 must be greater than the second, and vice versa.
777
778 The default value is \c 0.0.
779 \row
780 \li handle
781 \li This property holds the first handle item.
782 \row
783 \li visualPosition
784 \li This property holds the visual position of the first handle.
785
786 The position is expressed as a fraction of the control's size, in the range
787 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
788 value is equal to \c {1.0 - position}. This makes the value suitable for
789 visualizing the slider, taking right-to-left support into account.
790 \row
791 \li position
792 \li This property holds the logical position of the first handle.
793
794 The position is expressed as a fraction of the control's size, in the range
795 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
796 \l {first.visualPosition}{visualPosition} should be used instead.
797 \row
798 \li pressed
799 \li This property holds whether the first handle is pressed by either touch,
800 mouse, or keys.
801 \row
802 \li hovered
803 \li This property holds whether the first handle is hovered.
804 This property was introduced in QtQuick.Controls 2.1.
805 \row
806 \li implicitHandleWidth
807 \li This property holds the implicit width of the first handle.
808 This property was introduced in QtQuick.Controls 2.5.
809 \row
810 \li implicitHandleHeight
811 \li This property holds the implicit height of the first handle.
812 This property was introduced in QtQuick.Controls 2.5.
813 \endtable
814
815 \sa first.moved(), first.increase(), first.decrease()
816*/
817QQuickRangeSliderNode *QQuickRangeSlider::first() const
818{
819 Q_D(const QQuickRangeSlider);
820 return d->first;
821}
822
823/*!
824 \qmlsignal void QtQuick.Controls::RangeSlider::first.moved()
825 \qmlsignal void QtQuick.Controls::RangeSlider::second.moved()
826 \since QtQuick.Controls 2.5
827
828 This signal is emitted when either the first or second handle has been
829 interactively moved by the user by either touch, mouse, or keys.
830
831 \sa first, second
832*/
833
834/*!
835 \qmlproperty real QtQuick.Controls::RangeSlider::second.value
836 \qmlproperty real QtQuick.Controls::RangeSlider::second.position
837 \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition
838 \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle
839 \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed
840 \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered
841 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth
842 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight
843
844 \table
845 \header
846 \li Property
847 \li Description
848 \row
849 \li value
850 \li This property holds the value of the second handle in the range
851 \c from - \c to.
852
853 If \l from is greater than \l to, the value of the first handle
854 must be greater than the second, and vice versa.
855
856 The default value is \c 0.0.
857 \row
858 \li handle
859 \li This property holds the second handle item.
860 \row
861 \li visualPosition
862 \li This property holds the visual position of the second handle.
863
864 The position is expressed as a fraction of the control's size, in the range
865 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
866 value is equal to \c {1.0 - position}. This makes the value suitable for
867 visualizing the slider, taking right-to-left support into account.
868 \row
869 \li position
870 \li This property holds the logical position of the second handle.
871
872 The position is expressed as a fraction of the control's size, in the range
873 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
874 \l {second.visualPosition}{visualPosition} should be used instead.
875 \row
876 \li pressed
877 \li This property holds whether the second handle is pressed by either touch,
878 mouse, or keys.
879 \row
880 \li hovered
881 \li This property holds whether the second handle is hovered.
882 This property was introduced in QtQuick.Controls 2.1.
883 \row
884 \li implicitHandleWidth
885 \li This property holds the implicit width of the second handle.
886 This property was introduced in QtQuick.Controls 2.5.
887 \row
888 \li implicitHandleHeight
889 \li This property holds the implicit height of the second handle.
890 This property was introduced in QtQuick.Controls 2.5.
891 \endtable
892
893 \sa second.moved(), second.increase(), second.decrease()
894*/
895QQuickRangeSliderNode *QQuickRangeSlider::second() const
896{
897 Q_D(const QQuickRangeSlider);
898 return d->second;
899}
900
901/*!
902 \qmlproperty real QtQuick.Controls::RangeSlider::stepSize
903
904 This property holds the step size. The default value is \c 0.0.
905
906 \sa snapMode, first.increase(), first.decrease()
907*/
908qreal QQuickRangeSlider::stepSize() const
909{
910 Q_D(const QQuickRangeSlider);
911 return d->stepSize;
912}
913
914void QQuickRangeSlider::setStepSize(qreal step)
915{
916 Q_D(QQuickRangeSlider);
917 if (qFuzzyCompare(p1: d->stepSize, p2: step))
918 return;
919
920 d->stepSize = step;
921 emit stepSizeChanged();
922}
923
924/*!
925 \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode
926
927 This property holds the snap mode.
928
929 The snap mode determines how the slider handles behave with
930 regards to the \l stepSize.
931
932 Possible values:
933 \value RangeSlider.NoSnap The slider does not snap (default).
934 \value RangeSlider.SnapAlways The slider snaps while the handle is dragged.
935 \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released.
936
937 For visual explanations of the various modes, see the
938 \l {Slider::}{snapMode} documentation of \l Slider.
939
940 \sa stepSize
941*/
942QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const
943{
944 Q_D(const QQuickRangeSlider);
945 return d->snapMode;
946}
947
948void QQuickRangeSlider::setSnapMode(SnapMode mode)
949{
950 Q_D(QQuickRangeSlider);
951 if (d->snapMode == mode)
952 return;
953
954 d->snapMode = mode;
955 emit snapModeChanged();
956}
957
958/*!
959 \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation
960
961 This property holds the orientation.
962
963 Possible values:
964 \value Qt.Horizontal Horizontal (default)
965 \value Qt.Vertical Vertical
966
967 \sa horizontal, vertical
968*/
969Qt::Orientation QQuickRangeSlider::orientation() const
970{
971 Q_D(const QQuickRangeSlider);
972 return d->orientation;
973}
974
975void QQuickRangeSlider::setOrientation(Qt::Orientation orientation)
976{
977 Q_D(QQuickRangeSlider);
978 if (d->orientation == orientation)
979 return;
980
981 d->orientation = orientation;
982 emit orientationChanged();
983}
984
985/*!
986 \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue)
987
988 Sets \l first.value and \l second.value with the given arguments.
989
990 If \l to is larger than \l from and \a firstValue is larger than
991 \a secondValue, firstValue will be clamped to secondValue.
992
993 If \l from is larger than \l to and secondValue is larger than
994 firstValue, secondValue will be clamped to firstValue.
995
996 This function may be necessary to set the first and second values
997 after the control has been completed, as there is a circular
998 dependency between firstValue and secondValue which can cause
999 assigned values to be clamped to each other.
1000
1001 \sa stepSize
1002*/
1003void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue)
1004{
1005 Q_D(QQuickRangeSlider);
1006 // Restrict the values to be within to and from.
1007 const qreal smaller = qMin(a: d->to, b: d->from);
1008 const qreal larger = qMax(a: d->to, b: d->from);
1009 firstValue = qBound(min: smaller, val: firstValue, max: larger);
1010 secondValue = qBound(min: smaller, val: secondValue, max: larger);
1011
1012 if (d->from > d->to) {
1013 // If the from and to values are reversed, the secondValue
1014 // might be less than the first value, which is not allowed.
1015 if (secondValue > firstValue)
1016 secondValue = firstValue;
1017 } else {
1018 // Otherwise, clamp first to second if it's too large.
1019 if (firstValue > secondValue)
1020 firstValue = secondValue;
1021 }
1022
1023 // Then set both values. If they didn't change, no change signal will be emitted.
1024 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
1025 if (firstValue != firstPrivate->value) {
1026 firstPrivate->value = firstValue;
1027 emit d->first->valueChanged();
1028 }
1029
1030 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
1031 if (secondValue != secondPrivate->value) {
1032 secondPrivate->value = secondValue;
1033 emit d->second->valueChanged();
1034 }
1035
1036 // After we've set both values, then we can update the positions.
1037 // If we don't do this last, the positions may be incorrect.
1038 firstPrivate->updatePosition(ignoreOtherPosition: true);
1039 secondPrivate->updatePosition();
1040}
1041
1042/*!
1043 \since QtQuick.Controls 2.2 (Qt 5.9)
1044 \qmlproperty bool QtQuick.Controls::RangeSlider::live
1045
1046 This property holds whether the slider provides live updates for the \l first.value
1047 and \l second.value properties while the respective handles are dragged.
1048
1049 The default value is \c true.
1050
1051 \sa first.value, second.value
1052*/
1053bool QQuickRangeSlider::live() const
1054{
1055 Q_D(const QQuickRangeSlider);
1056 return d->live;
1057}
1058
1059void QQuickRangeSlider::setLive(bool live)
1060{
1061 Q_D(QQuickRangeSlider);
1062 if (d->live == live)
1063 return;
1064
1065 d->live = live;
1066 emit liveChanged();
1067}
1068
1069/*!
1070 \since QtQuick.Controls 2.3 (Qt 5.10)
1071 \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal
1072 \readonly
1073
1074 This property holds whether the slider is horizontal.
1075
1076 \sa orientation
1077*/
1078bool QQuickRangeSlider::isHorizontal() const
1079{
1080 Q_D(const QQuickRangeSlider);
1081 return d->orientation == Qt::Horizontal;
1082}
1083
1084/*!
1085 \since QtQuick.Controls 2.3 (Qt 5.10)
1086 \qmlproperty bool QtQuick.Controls::RangeSlider::vertical
1087 \readonly
1088
1089 This property holds whether the slider is vertical.
1090
1091 \sa orientation
1092*/
1093bool QQuickRangeSlider::isVertical() const
1094{
1095 Q_D(const QQuickRangeSlider);
1096 return d->orientation == Qt::Vertical;
1097}
1098
1099void QQuickRangeSlider::focusInEvent(QFocusEvent *event)
1100{
1101 Q_D(QQuickRangeSlider);
1102 QQuickControl::focusInEvent(event);
1103
1104 // The active focus ends up to RangeSlider when using forceActiveFocus()
1105 // or QML KeyNavigation. We must forward the focus to one of the handles,
1106 // because RangeSlider handles key events for the focused handle. If
1107 // neither handle has active focus, RangeSlider doesn't do anything.
1108 QQuickItem *handle = nextItemInFocusChain();
1109 // QQuickItem::nextItemInFocusChain() only works as desired with
1110 // Qt::TabFocusAllControls. otherwise pick the first handle
1111 if (!handle || handle == this)
1112 handle = d->first->handle();
1113 if (handle)
1114 handle->forceActiveFocus(reason: event->reason());
1115}
1116
1117void QQuickRangeSlider::keyPressEvent(QKeyEvent *event)
1118{
1119 Q_D(QQuickRangeSlider);
1120 QQuickControl::keyPressEvent(event);
1121
1122 QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus()
1123 ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr);
1124 if (!focusNode)
1125 return;
1126
1127 const qreal oldValue = focusNode->value();
1128 if (d->orientation == Qt::Horizontal) {
1129 if (event->key() == Qt::Key_Left) {
1130 focusNode->setPressed(true);
1131 if (isMirrored())
1132 focusNode->increase();
1133 else
1134 focusNode->decrease();
1135 event->accept();
1136 } else if (event->key() == Qt::Key_Right) {
1137 focusNode->setPressed(true);
1138 if (isMirrored())
1139 focusNode->decrease();
1140 else
1141 focusNode->increase();
1142 event->accept();
1143 }
1144 } else {
1145 if (event->key() == Qt::Key_Up) {
1146 focusNode->setPressed(true);
1147 focusNode->increase();
1148 event->accept();
1149 } else if (event->key() == Qt::Key_Down) {
1150 focusNode->setPressed(true);
1151 focusNode->decrease();
1152 event->accept();
1153 }
1154 }
1155 if (!qFuzzyCompare(p1: focusNode->value(), p2: oldValue))
1156 emit focusNode->moved();
1157}
1158
1159void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event)
1160{
1161 Q_D(QQuickRangeSlider);
1162 QQuickControl::hoverEnterEvent(event);
1163 d->updateHover(pos: event->posF());
1164}
1165
1166void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event)
1167{
1168 Q_D(QQuickRangeSlider);
1169 QQuickControl::hoverMoveEvent(event);
1170 d->updateHover(pos: event->posF());
1171}
1172
1173void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event)
1174{
1175 Q_D(QQuickRangeSlider);
1176 QQuickControl::hoverLeaveEvent(event);
1177 d->first->setHovered(false);
1178 d->second->setHovered(false);
1179}
1180
1181void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event)
1182{
1183 Q_D(QQuickRangeSlider);
1184 QQuickControl::keyReleaseEvent(event);
1185 d->first->setPressed(false);
1186 d->second->setPressed(false);
1187}
1188
1189void QQuickRangeSlider::mousePressEvent(QMouseEvent *event)
1190{
1191 Q_D(QQuickRangeSlider);
1192 QQuickControl::mousePressEvent(event);
1193 d->handleMove(point: event->localPos());
1194 setKeepMouseGrab(true);
1195}
1196
1197#if QT_CONFIG(quicktemplates2_multitouch)
1198void QQuickRangeSlider::touchEvent(QTouchEvent *event)
1199{
1200 Q_D(QQuickRangeSlider);
1201 switch (event->type()) {
1202 case QEvent::TouchUpdate:
1203 for (const QTouchEvent::TouchPoint &point : event->touchPoints()) {
1204 if (!d->acceptTouch(point))
1205 continue;
1206
1207 switch (point.state()) {
1208 case Qt::TouchPointPressed:
1209 d->handlePress(point: point.pos());
1210 break;
1211 case Qt::TouchPointMoved:
1212 if (!keepTouchGrab()) {
1213 if (d->orientation == Qt::Horizontal)
1214 setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.pos().x() - point.startPos().x(), axis: Qt::XAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold)));
1215 else
1216 setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.pos().y() - point.startPos().y(), axis: Qt::YAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold)));
1217 }
1218 if (keepTouchGrab())
1219 d->handleMove(point: point.pos());
1220 break;
1221 case Qt::TouchPointReleased:
1222 d->handleRelease(point: point.pos());
1223 break;
1224 default:
1225 break;
1226 }
1227 }
1228 break;
1229
1230 default:
1231 QQuickControl::touchEvent(event);
1232 break;
1233 }
1234}
1235#endif
1236
1237void QQuickRangeSlider::mirrorChange()
1238{
1239 Q_D(QQuickRangeSlider);
1240 QQuickControl::mirrorChange();
1241 emit d->first->visualPositionChanged();
1242 emit d->second->visualPositionChanged();
1243}
1244
1245void QQuickRangeSlider::classBegin()
1246{
1247 Q_D(QQuickRangeSlider);
1248 QQuickControl::classBegin();
1249
1250 QQmlContext *context = qmlContext(this);
1251 if (context) {
1252 QQmlEngine::setContextForObject(d->first, context);
1253 QQmlEngine::setContextForObject(d->second, context);
1254 }
1255}
1256
1257void QQuickRangeSlider::componentComplete()
1258{
1259 Q_D(QQuickRangeSlider);
1260 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first);
1261 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second);
1262 firstPrivate->executeHandle(complete: true);
1263 secondPrivate->executeHandle(complete: true);
1264
1265 QQuickControl::componentComplete();
1266
1267 if (firstPrivate->isPendingValue || secondPrivate->isPendingValue
1268 || !qFuzzyCompare(p1: d->from, p2: defaultFrom) || !qFuzzyCompare(p1: d->to, p2: defaultTo)) {
1269 // Properties were set while we were loading. To avoid clamping issues that occur when setting the
1270 // values of first and second overriding values set by the user, set them all at once at the end.
1271 // Another reason that we must set these values here is that the from and to values might have made the old range invalid.
1272 setValues(firstValue: firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value,
1273 secondValue: secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value);
1274
1275 firstPrivate->pendingValue = 0;
1276 firstPrivate->isPendingValue = false;
1277 secondPrivate->pendingValue = 0;
1278 secondPrivate->isPendingValue = false;
1279 } else {
1280 // If there was no pending data, we must still update the positions,
1281 // as first.setValue()/second.setValue() won't be called as part of default construction.
1282 // Don't need to ignore the second position when updating the first position here,
1283 // as our default values are guaranteed to be valid.
1284 firstPrivate->updatePosition();
1285 secondPrivate->updatePosition();
1286 }
1287}
1288
1289/*!
1290 \qmlmethod void QtQuick.Controls::RangeSlider::first.increase()
1291
1292 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1293
1294 \sa first
1295*/
1296
1297/*!
1298 \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease()
1299
1300 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1301
1302 \sa first
1303*/
1304
1305/*!
1306 \qmlmethod void QtQuick.Controls::RangeSlider::second.increase()
1307
1308 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1309
1310 \sa second
1311*/
1312
1313/*!
1314 \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease()
1315
1316 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1317
1318 \sa second
1319*/
1320
1321#if QT_CONFIG(accessibility)
1322QAccessible::Role QQuickRangeSlider::accessibleRole() const
1323{
1324 return QAccessible::Slider;
1325}
1326#endif
1327
1328QT_END_NAMESPACE
1329

source code of qtquickcontrols2/src/quicktemplates2/qquickrangeslider.cpp