Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickrangeslider.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8
9#include <QtCore/qscopedpointer.h>
10#include <QtQuick/private/qquickwindow_p.h>
11#include <QtQuickTemplates2/private/qtquicktemplates2math_p.h>
12
14
15/*!
16 \qmltype RangeSlider
17 \inherits Control
18//! \nativetype QQuickRangeSlider
19 \inqmlmodule QtQuick.Controls
20 \since 5.7
21 \ingroup qtquickcontrols-input
22 \ingroup qtquickcontrols-focusscopes
23 \brief Used to select a range of values by sliding two handles along a track.
24
25 \image qtquickcontrols-rangeslider.gif
26 {Range slider with two handles defining range}
27
28 RangeSlider is used to select a range specified by two values, by sliding
29 each handle along a track.
30
31 In the example below, custom \l from and \l to values are set, and the
32 initial positions of the \l first and \l second handles are set:
33
34 \code
35 RangeSlider {
36 from: 1
37 to: 100
38 first.value: 25
39 second.value: 75
40 }
41 \endcode
42
43 In order to perform an action when the value for a particular handle changes,
44 use the following syntax:
45
46 \code
47 first.onMoved: console.log("first.value changed to " + first.value)
48 \endcode
49
50 The \l {first.position} and \l {second.position} properties are expressed as
51 fractions of the control's size, in the range \c {0.0 - 1.0}.
52 The \l {first.visualPosition} and \l {second.visualPosition} properties are
53 the same, except that they are reversed in a
54 \l {Right-to-left User Interfaces}{right-to-left} application.
55 The \c visualPosition is useful for positioning the handles when styling
56 RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24
57 in a left-to-right application, and \c 0.76 in a right-to-left application.
58
59 For a slider that allows the user to select a single value, see \l Slider.
60
61 \sa {Customizing RangeSlider}, {Input Controls},
62 {Focus Management in Qt Quick Controls}
63*/
64
66{
67 Q_DECLARE_PUBLIC(QQuickRangeSliderNode)
68public:
74
75 bool isFirst() const;
76
77 void setPosition(qreal position, bool ignoreOtherPosition = false);
78 void updatePosition(bool ignoreOtherPosition = false);
79
81 void executeHandle(bool complete = false);
82
83 static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node);
84
86 bool isPendingValue = false;
91 bool pressed = false;
92 bool hovered = false;
93 int touchId = -1;
94};
95
96bool QQuickRangeSliderNodePrivate::isFirst() const
97{
98 return this == get(slider->first());
99}
100
101void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition)
102{
103 Q_Q(QQuickRangeSliderNode);
104
105 qreal min = 0.0;
106 qreal max = 1.0;
107
108 // Only apply constraints if crossing is disabled
109 if (!slider->isCrossingEnabled() && !ignoreOtherPosition) {
110 min = isFirst() ? 0.0 : qMax<qreal>(0.0, slider->first()->position());
111 max = !isFirst() ? 1.0 : qMin<qreal>(1.0, slider->second()->position());
112 }
113
114 position = qBound(min, position, max);
115 if (!qFuzzyCompare(this->position, position)) {
116 this->position = position;
117 emit q->positionChanged();
118 emit q->visualPositionChanged();
119 }
120
121 // Check for crossing after position update
122 if (!ignoreOtherPosition)
123 q->updateHandleCrossing();
124}
125
126void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition)
127{
128 qreal pos = 0;
129 if (!qFuzzyCompare(slider->from(), slider->to()))
130 pos = (value - slider->from()) / (slider->to() - slider->from());
131 setPosition(pos, ignoreOtherPosition);
132}
133
135{
136 Q_Q(QQuickRangeSliderNode);
137 quickCancelDeferred(q, handleName());
138}
139
141{
142 Q_Q(QQuickRangeSliderNode);
143 if (handle.wasExecuted())
144 return;
145
146 if (!handle || complete)
147 quickBeginDeferred(q, handleName(), handle);
148 if (complete)
149 quickCompleteDeferred(q, handleName(), handle);
150}
151
153{
154 return node->d_func();
155}
156
157QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider)
158 : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider)
159{
160}
161
162QQuickRangeSliderNode::~QQuickRangeSliderNode()
163{
164}
165
166qreal QQuickRangeSliderNode::value() const
167{
168 Q_D(const QQuickRangeSliderNode);
169 return d->value;
170}
171
172void QQuickRangeSliderNode::setValue(qreal value)
173{
174 Q_D(QQuickRangeSliderNode);
175 if (!d->slider->isComponentComplete()) {
176 d->pendingValue = value;
177 d->isPendingValue = true;
178 return;
179 }
180
181 // First, restrict the first value to be within to and from.
182 const qreal smaller = qMin(d->slider->to(), d->slider->from());
183 const qreal larger = qMax(d->slider->to(), d->slider->from());
184 value = qBound(smaller, value, larger);
185
186 // Then, ensure that it doesn't go past the other value,
187 // a check that depends on whether or not the range is inverted.
188 // Only apply this constraint if crossing is disabled.
189 if (!d->slider->isCrossingEnabled()) {
190 const bool invertedRange = d->slider->from() > d->slider->to();
191 if (d->isFirst()) {
192 if (invertedRange) {
193 if (value < d->slider->second()->value())
194 value = d->slider->second()->value();
195 } else {
196 if (value > d->slider->second()->value())
197 value = d->slider->second()->value();
198 }
199 } else {
200 if (invertedRange) {
201 if (value > d->slider->first()->value())
202 value = d->slider->first()->value();
203 } else {
204 if (value < d->slider->first()->value())
205 value = d->slider->first()->value();
206 }
207 }
208 }
209
210 if (!qFuzzyCompare(d->value, value)) {
211 d->value = value;
212 d->updatePosition();
213 emit valueChanged();
214 d->slider->effectiveValueChange(this);
215 d->slider->updateFocusOrder();
216 }
217
218 // Check for crossing after value update
219 updateHandleCrossing();
220}
221
222qreal QQuickRangeSliderNode::position() const
223{
224 Q_D(const QQuickRangeSliderNode);
225 return d->position;
226}
227
228qreal QQuickRangeSliderNode::visualPosition() const
229{
230 Q_D(const QQuickRangeSliderNode);
231 if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored())
232 return 1.0 - d->position;
233 return d->position;
234}
235
236QQuickItem *QQuickRangeSliderNode::handle() const
237{
238 QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func());
239 if (!d->handle)
240 d->executeHandle();
241 return d->handle;
242}
243
244void QQuickRangeSliderNode::setHandle(QQuickItem *handle)
245{
246 Q_D(QQuickRangeSliderNode);
247 if (d->handle == handle)
248 return;
249
250 QQuickControlPrivate::warnIfCustomizationNotSupported(d->slider, handle, QStringLiteral("handle"));
251
252 if (!d->handle.isExecuting())
253 d->cancelHandle();
254
255 const qreal oldImplicitHandleWidth = implicitHandleWidth();
256 const qreal oldImplicitHandleHeight = implicitHandleHeight();
257
258 QQuickControlPrivate::get(d->slider)->removeImplicitSizeListener(d->handle);
259 QQuickControlPrivate::hideOldItem(d->handle);
260 d->handle = handle;
261
262 if (handle) {
263 if (!handle->parentItem())
264 handle->setParentItem(d->slider);
265
266 d->slider->updateFocusOrder();
267
268 handle->setActiveFocusOnTab(true);
269 QQuickControlPrivate::get(d->slider)->addImplicitSizeListener(handle);
270 }
271
272 if (!qFuzzyCompare(oldImplicitHandleWidth, implicitHandleWidth()))
273 emit implicitHandleWidthChanged();
274 if (!qFuzzyCompare(oldImplicitHandleHeight, implicitHandleHeight()))
275 emit implicitHandleHeightChanged();
276 if (!d->handle.isExecuting())
277 emit handleChanged();
278}
279
280bool QQuickRangeSliderNode::isPressed() const
281{
282 Q_D(const QQuickRangeSliderNode);
283 return d->pressed;
284}
285
286void QQuickRangeSliderNode::setPressed(bool pressed)
287{
288 Q_D(QQuickRangeSliderNode);
289 if (d->pressed == pressed)
290 return;
291
292 d->pressed = pressed;
293 d->slider->setAccessibleProperty("pressed", pressed || d->slider->second()->isPressed());
294 emit pressedChanged();
295}
296
297bool QQuickRangeSliderNode::isHovered() const
298{
299 Q_D(const QQuickRangeSliderNode);
300 return d->hovered;
301}
302
303void QQuickRangeSliderNode::setHovered(bool hovered)
304{
305 Q_D(QQuickRangeSliderNode);
306 if (d->hovered == hovered)
307 return;
308
309 d->hovered = hovered;
310 emit hoveredChanged();
311}
312
313qreal QQuickRangeSliderNode::implicitHandleWidth() const
314{
315 Q_D(const QQuickRangeSliderNode);
316 if (!d->handle)
317 return 0;
318 return d->handle->implicitWidth();
319}
320
321qreal QQuickRangeSliderNode::implicitHandleHeight() const
322{
323 Q_D(const QQuickRangeSliderNode);
324 if (!d->handle)
325 return 0;
326 return d->handle->implicitHeight();
327}
328
329void QQuickRangeSliderNode::increase()
330{
331 Q_D(QQuickRangeSliderNode);
332 qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
333 setValue(d->value + step);
334}
335
336void QQuickRangeSliderNode::decrease()
337{
338 Q_D(QQuickRangeSliderNode);
339 qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
340 setValue(d->value - step);
341}
342
343void QQuickRangeSliderNode::updateHandleCrossing()
344{
345 Q_D(QQuickRangeSliderNode);
346 d->slider->updateHandleCrossing();
347}
348
349static const qreal defaultFrom = 0.0;
350static const qreal defaultTo = 1.0;
351
353{
354 Q_DECLARE_PUBLIC(QQuickRangeSlider)
355
356public:
358
359 QQuickRangeSliderNode *pressedNode(int touchId = -1) const;
360
361#if QT_CONFIG(quicktemplates2_multitouch)
363#endif
364 bool handlePress(const QPointF &point, ulong timestamp) override;
365 bool handleMove(const QPointF &point, ulong timestamp) override;
366 bool handleRelease(const QPointF &point, ulong timestamp) override;
368
369 void updateHover(const QPointF &pos);
370
371 void itemImplicitWidthChanged(QQuickItem *item) override;
372 void itemImplicitHeightChanged(QQuickItem *item) override;
373 void itemDestroyed(QQuickItem *item) override;
374
376
377 // Crossing detection and swap helpers
378 bool shouldHandlesCross() const;
381
386 QQuickRangeSliderNode *first = nullptr;
387 QQuickRangeSliderNode *second = nullptr;
391 bool live = true;
393 bool crossingEnabled = false;
394 bool handlesCrossed = false;
395};
396
397static qreal valueAt(const QQuickRangeSlider *slider, qreal position)
398{
399 qreal value = slider->from() + (slider->to() - slider->from()) * position;
400
401 // See the comment in QQuickDialPrivate::valueAt for why we do this.
402 if (QQuickRangeSliderPrivate::get(slider)->allValuesAreInteger)
403 value = qRound(value);
404
405 return value;
406}
407
408static qreal snapPosition(const QQuickRangeSlider *slider, qreal position)
409{
410 const qreal range = slider->to() - slider->from();
411 if (qFuzzyIsNull(range))
412 return position;
413
414 const qreal effectiveStep = slider->stepSize() / range;
415 if (qFuzzyIsNull(effectiveStep))
416 return position;
417
418 return qRound(position / effectiveStep) * effectiveStep;
419}
420
421static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point)
422{
423 if (slider->orientation() == Qt::Horizontal) {
424 const qreal hw = handle ? handle->width() : 0;
425 const qreal offset = hw / 2;
426 const qreal extent = slider->availableWidth() - hw;
427 if (!qFuzzyIsNull(extent)) {
428 if (slider->isMirrored())
429 return (slider->width() - point.x() - slider->rightPadding() - offset) / extent;
430 return (point.x() - slider->leftPadding() - offset) / extent;
431 }
432 } else {
433 const qreal hh = handle ? handle->height() : 0;
434 const qreal offset = hh / 2;
435 const qreal extent = slider->availableHeight() - hh;
436 if (!qFuzzyIsNull(extent))
437 return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent;
438 }
439 return 0;
440}
441
442const QQuickRangeSliderPrivate *QQuickRangeSliderPrivate::get(const QQuickRangeSlider *slider)
443{
444 return slider->d_func();
445}
446
447QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const
448{
449 if (touchId == -1)
450 return first->isPressed() ? first : (second->isPressed() ? second : nullptr);
452 return first;
454 return second;
455 return nullptr;
456}
457
459{
460 const bool invertedRange = from > to;
461 const qreal firstValue = first->value();
462 const qreal secondValue = second->value();
463
464 if (invertedRange) {
465 return firstValue < secondValue;
466 } else {
467 return firstValue > secondValue;
468 }
469}
470
472{
473 if (!crossingEnabled)
474 return;
475
476 const bool shouldCross = shouldHandlesCross();
477
478 if (shouldCross != handlesCrossed)
480}
481
483{
484 Q_Q(QQuickRangeSlider);
485
487 emit q->handlesCrossedChanged();
488
489 // When crossing state changes, effective values swap
490 emit q->effectiveFirstValueChanged();
491 emit q->effectiveSecondValueChanged();
492}
493
494#if QT_CONFIG(quicktemplates2_multitouch)
495bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point)
496{
497 int firstId = QQuickRangeSliderNodePrivate::get(first)->touchId;
498 int secondId = QQuickRangeSliderNodePrivate::get(second)->touchId;
499
500 if (((firstId == -1 || secondId == -1) && point.state() == QEventPoint::Pressed) || point.id() == firstId || point.id() == secondId) {
501 touchId = point.id();
502 return true;
503 }
504
505 return false;
506}
507#endif
508
509bool QQuickRangeSliderPrivate::handlePress(const QPointF &point, ulong timestamp)
510{
511 Q_Q(QQuickRangeSlider);
512 QQuickControlPrivate::handlePress(point, timestamp);
513 pressPoint = point;
514
515 QQuickItem *firstHandle = first->handle();
516 QQuickItem *secondHandle = second->handle();
517 const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(q->mapToItem(firstHandle, point));
518 const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(q->mapToItem(secondHandle, point));
519 QQuickRangeSliderNode *hitNode = nullptr;
520 QQuickRangeSliderNode *otherNode = nullptr;
521
522 if (firstHit && secondHit) {
523 // choose highest
524 hitNode = firstHandle->z() > secondHandle->z() ? first : second;
525 otherNode = firstHandle->z() > secondHandle->z() ? second : first;
526 } else if (firstHit) {
527 hitNode = first;
528 otherNode = second;
529 } else if (secondHit) {
530 hitNode = second;
531 otherNode = first;
532 } else {
533 // find the nearest
534 const qreal firstPos = positionAt(q, firstHandle, point);
535 const qreal secondPos = positionAt(q, secondHandle, point);
536 const qreal firstDistance = qAbs(firstPos - first->position());
537 const qreal secondDistance = qAbs(secondPos - second->position());
538
539 if (qFuzzyCompare(firstDistance, secondDistance)) {
540 // same distance => choose the one that can be moved towards the press position
541 const bool inverted = from > to;
542 if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) {
543 hitNode = first;
544 otherNode = second;
545 } else {
546 hitNode = second;
547 otherNode = first;
548 }
549 } else if (firstDistance < secondDistance) {
550 hitNode = first;
551 otherNode = second;
552 } else {
553 hitNode = second;
554 otherNode = first;
555 }
556 }
557
558 if (hitNode) {
559 hitNode->setPressed(true);
560 if (QQuickItem *handle = hitNode->handle()) {
561 handle->setZ(1);
562
563 // A specific handle was hit, so it should get focus, rather than the default
564 // (first handle) that gets focus whenever the RangeSlider itself does - see focusInEvent().
565 if (focusPolicy & Qt::ClickFocus)
566 handle->forceActiveFocus(Qt::MouseFocusReason);
567 }
568 QQuickRangeSliderNodePrivate::get(hitNode)->touchId = touchId;
569 }
570 if (otherNode) {
571 if (QQuickItem *handle = otherNode->handle())
572 handle->setZ(0);
573 }
574 return true;
575}
576
577bool QQuickRangeSliderPrivate::handleMove(const QPointF &point, ulong timestamp)
578{
579 Q_Q(QQuickRangeSlider);
580 QQuickControlPrivate::handleMove(point, timestamp);
581 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
582 if (pressedNode) {
583 const qreal oldPos = pressedNode->position();
584 qreal pos = positionAt(q, pressedNode->handle(), point);
585 if (snapMode == QQuickRangeSlider::SnapAlways)
586 pos = snapPosition(q, pos);
587 if (live)
588 pressedNode->setValue(valueAt(q, pos));
589 else
590 QQuickRangeSliderNodePrivate::get(pressedNode)->setPosition(pos);
591
592 if (!qFuzzyCompare(pressedNode->position(), oldPos))
593 emit pressedNode->moved();
594 }
595 return true;
596}
597
598bool QQuickRangeSliderPrivate::handleRelease(const QPointF &point, ulong timestamp)
599{
600 Q_Q(QQuickRangeSlider);
601 QQuickControlPrivate::handleRelease(point, timestamp);
602 pressPoint = QPointF();
603
604 QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
605 if (!pressedNode)
606 return true;
608
609 const qreal oldPos = pressedNode->position();
610 qreal pos = positionAt(q, pressedNode->handle(), point);
611 if (snapMode != QQuickRangeSlider::NoSnap)
612 pos = snapPosition(q, pos);
613 qreal val = valueAt(q, pos);
614 if (!qFuzzyCompare(val, pressedNode->value()))
615 pressedNode->setValue(val);
616 else if (snapMode != QQuickRangeSlider::NoSnap)
617 pressedNodePrivate->setPosition(pos);
618 q->setKeepMouseGrab(false);
619 q->setKeepTouchGrab(false);
620
621 if (!qFuzzyCompare(pressedNode->position(), oldPos))
622 emit pressedNode->moved();
623
624 pressedNode->setPressed(false);
625 pressedNodePrivate->touchId = -1;
626 return true;
627}
628
630{
631 QQuickControlPrivate::handleUngrab();
632 pressPoint = QPointF();
633 first->setPressed(false);
634 second->setPressed(false);
637}
638
639void QQuickRangeSliderPrivate::updateHover(const QPointF &pos)
640{
641 Q_Q(QQuickRangeSlider);
642 QQuickItem *firstHandle = first->handle();
643 QQuickItem *secondHandle = second->handle();
644 bool firstHandleHovered = firstHandle && firstHandle->isEnabled()
645 && firstHandle->contains(q->mapToItem(firstHandle, pos));
646 bool secondHandleHovered = secondHandle && secondHandle->isEnabled()
647 && secondHandle->contains(q->mapToItem(secondHandle, pos));
648
649 if (firstHandleHovered && secondHandleHovered) {
650 // Only hover the handle with the higher Z value.
651 const bool firstHandleHasHigherZ = firstHandle->z() > secondHandle->z();
652 firstHandleHovered = firstHandleHasHigherZ;
653 secondHandleHovered = !firstHandleHasHigherZ;
654 }
655 first->setHovered(firstHandleHovered);
656 second->setHovered(secondHandleHovered);
657}
658
660{
661 QQuickControlPrivate::itemImplicitWidthChanged(item);
662 if (item == first->handle())
663 emit first->implicitHandleWidthChanged();
664 else if (item == second->handle())
665 emit second->implicitHandleWidthChanged();
666}
667
669{
670 QQuickControlPrivate::itemImplicitHeightChanged(item);
671 if (item == first->handle())
672 emit first->implicitHandleHeightChanged();
673 else if (item == second->handle())
674 emit second->implicitHandleHeightChanged();
675}
676
678{
679 QQuickControlPrivate::itemDestroyed(item);
680 if (item == first->handle())
681 first->setHandle(nullptr);
682 else if (item == second->handle())
683 second->setHandle(nullptr);
684}
685
687{
688 allValuesAreInteger = areRepresentableAsInteger(to, from, stepSize) && stepSize != 0.0;
689}
690
691QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent)
692 : QQuickControl(*(new QQuickRangeSliderPrivate), parent)
693{
694 Q_D(QQuickRangeSlider);
695 d->first = new QQuickRangeSliderNode(0.0, this);
696 d->second = new QQuickRangeSliderNode(1.0, this);
697 d->setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Fixed);
698
699 setFlag(QQuickItem::ItemIsFocusScope);
700#ifdef Q_OS_MACOS
701 setFocusPolicy(Qt::TabFocus);
702#else
703 setFocusPolicy(Qt::StrongFocus);
704#endif
705 setAcceptedMouseButtons(Qt::LeftButton);
706#if QT_CONFIG(quicktemplates2_multitouch)
707 setAcceptTouchEvents(true);
708#endif
709#if QT_CONFIG(cursor)
710 setCursor(Qt::ArrowCursor);
711#endif
712}
713
714QQuickRangeSlider::~QQuickRangeSlider()
715{
716 Q_D(QQuickRangeSlider);
717 d->removeImplicitSizeListener(d->first->handle());
718 d->removeImplicitSizeListener(d->second->handle());
719}
720
721/*!
722 \qmlproperty real QtQuick.Controls::RangeSlider::from
723
724 This property holds the starting value for the range. The default value is \c 0.0.
725
726 \sa to, first.value, second.value
727*/
728qreal QQuickRangeSlider::from() const
729{
730 Q_D(const QQuickRangeSlider);
731 return d->from;
732}
733
734void QQuickRangeSlider::setFrom(qreal from)
735{
736 Q_D(QQuickRangeSlider);
737 if (qFuzzyCompare(d->from, from))
738 return;
739
740 d->from = from;
741 emit fromChanged();
742 d->updateAllValuesAreInteger();
743
744 if (isComponentComplete()) {
745 d->first->setValue(d->first->value());
746 d->second->setValue(d->second->value());
747 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
748 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
749 firstPrivate->updatePosition(true);
750 secondPrivate->updatePosition();
751 }
752}
753
754/*!
755 \qmlproperty real QtQuick.Controls::RangeSlider::to
756
757 This property holds the end value for the range. The default value is \c 1.0.
758
759 \sa from, first.value, second.value
760*/
761qreal QQuickRangeSlider::to() const
762{
763 Q_D(const QQuickRangeSlider);
764 return d->to;
765}
766
767void QQuickRangeSlider::setTo(qreal to)
768{
769 Q_D(QQuickRangeSlider);
770 if (qFuzzyCompare(d->to, to))
771 return;
772
773 d->to = to;
774 emit toChanged();
775 d->updateAllValuesAreInteger();
776
777 if (isComponentComplete()) {
778 d->first->setValue(d->first->value());
779 d->second->setValue(d->second->value());
780 auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
781 auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
782 firstPrivate->updatePosition(true);
783 secondPrivate->updatePosition();
784 }
785}
786
787/*!
788 \since QtQuick.Controls 2.5 (Qt 5.12)
789 \qmlproperty real QtQuick.Controls::RangeSlider::touchDragThreshold
790
791 This property holds the threshold (in logical pixels) at which a touch drag event will be initiated.
792 The mouse drag threshold won't be affected.
793 The default value is \c Application.styleHints.startDragDistance.
794
795 \sa QStyleHints
796
797*/
798qreal QQuickRangeSlider::touchDragThreshold() const
799{
800 Q_D(const QQuickRangeSlider);
801 return d->touchDragThreshold;
802}
803
804void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold)
805{
806 Q_D(QQuickRangeSlider);
807 if (d->touchDragThreshold == touchDragThreshold)
808 return;
809
810 d->touchDragThreshold = touchDragThreshold;
811 emit touchDragThresholdChanged();
812}
813
814void QQuickRangeSlider::resetTouchDragThreshold()
815{
816 setTouchDragThreshold(-1);
817}
818
819/*!
820 \since QtQuick.Controls 2.5 (Qt 5.12)
821 \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position)
822
823 Returns the value for the given \a position.
824
825 \sa first.value, second.value, first.position, second.position, live
826*/
827qreal QQuickRangeSlider::valueAt(qreal position) const
828{
829 Q_D(const QQuickRangeSlider);
830 const qreal value = (d->to - d->from) * position;
831 if (qFuzzyIsNull(d->stepSize))
832 return d->from + value;
833 return d->from + qRound(value / d->stepSize) * d->stepSize;
834}
835
836/*!
837 \since QtQuick.Controls 6.12
838 \qmlproperty bool QtQuick.Controls::RangeSlider::crossingEnabled
839
840 This property determines whether the slider handles can cross each other.
841
842 When \c false (the default), the first handle cannot be moved past the
843 second handle, and vice versa. The values are constrained such that
844 \c {first.value} <= \c {second.value} for normal ranges, or
845 \c {first.value} >= \c {second.value} for inverted ranges.
846
847 Use the \l handlesCrossed property to detect when handles have swapped.
848
849 The default value is \c false.
850
851 When crossing is disabled, if handles were crossed then handles values will be exchanged.
852
853 \sa handlesCrossed, first.value, second.value
854*/
855bool QQuickRangeSlider::isCrossingEnabled() const
856{
857 Q_D(const QQuickRangeSlider);
858 return d->crossingEnabled;
859}
860
861void QQuickRangeSlider::setCrossingEnabled(bool enabled)
862{
863 Q_D(QQuickRangeSlider);
864 if (d->crossingEnabled == enabled)
865 return;
866
867 d->crossingEnabled = enabled;
868
869 // If disabling crossing while handles are crossed, uncross them
870 if (!enabled && d->handlesCrossed) {
871 if (d->shouldHandlesCross()) {
872 qreal temp = d->first->value();
873 d->first->setValue(d->second->value());
874 d->second->setValue(temp);
875 }
876
877 d->swapHandles();
878 }
879
880 emit crossingEnabledChanged();
881}
882
883/*!
884 \since QtQuick.Controls 6.12
885 \qmlproperty bool QtQuick.Controls::RangeSlider::handlesCrossed
886 \readonly
887
888 This property holds whether the handles have crossed each other.
889
890 This property is only relevant when \l crossingEnabled is \c true.
891 When handles cross, their internal roles swap to maintain the semantic
892 meaning of first and second as lower and upper bounds respectively.
893
894 This property can be used to provide visual feedback when handles are
895 in a crossed state.
896
897 \sa crossingEnabled, handlesCrossedChanged()
898*/
899bool QQuickRangeSlider::handlesCrossed() const
900{
901 Q_D(const QQuickRangeSlider);
902 return d->handlesCrossed;
903}
904
905void QQuickRangeSlider::updateHandleCrossing()
906{
907 Q_D(QQuickRangeSlider);
908 if (d->crossingEnabled)
909 d->updateHandleCrossing();
910}
911
912void QQuickRangeSlider::effectiveValueChange(QQuickRangeSliderNode* node)
913{
914 Q_D(QQuickRangeSlider);
915 if (node == d->first) {
916 // First node's value affects effectiveFirstValue when not crossed,
917 // or effectiveSecondValue when crossed
918 if (d->handlesCrossed)
919 emit effectiveSecondValueChanged();
920 else
921 emit effectiveFirstValueChanged();
922 } else if (node == d->second) {
923 // Second node's value affects effectiveSecondValue when not crossed,
924 // or effectiveFirstValue when crossed
925 if (d->handlesCrossed)
926 emit effectiveFirstValueChanged();
927 else
928 emit effectiveSecondValueChanged();
929 }
930}
931
932void QQuickRangeSlider::updateFocusOrder()
933{
934 Q_D(QQuickRangeSlider);
935 QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(d->first)->handle;
936 QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(d->second)->handle;
937 if (firstHandle && secondHandle) {
938 // The order of property assignments in QML is undefined,
939 // but we need the first handle to be before the second due
940 // to focus order constraints, so check for that here.
941 const QList<QQuickItem *> childItems = this->childItems();
942 const int firstIndex = childItems.indexOf(firstHandle);
943 const int secondIndex = childItems.indexOf(secondHandle);
944 if (firstIndex != -1 && secondIndex != -1) {
945 if (!d->handlesCrossed && firstIndex > secondIndex) {
946 firstHandle->stackBefore(secondHandle);
947 // Ensure we have some way of knowing which handle is above
948 // the other when it comes to mouse presses, and also that
949 // they are rendered in the correct order.
950 secondHandle->setZ(firstHandle->z() + 1);
951 } else if (d->handlesCrossed && firstIndex < secondIndex) {
952 secondHandle->stackBefore(firstHandle);
953 // Ensure we have some way of knowing which handle is above
954 // the other when it comes to mouse presses, and also that
955 // they are rendered in the correct order.
956 firstHandle->setZ(secondHandle->z() + 1);
957 }
958 }
959 }
960}
961
962/*!
963 \since QtQuick.Controls 6.12
964 \qmlproperty real QtQuick.Controls::RangeSlider::effectiveFirstValue
965 \readonly
966
967 This property holds the value of the handle that was originally created as
968 the "first" handle, regardless of whether handles have crossed.
969
970 When \l crossingEnabled is \c false or handles haven't crossed, this is
971 equal to \c first.value. When handles have crossed, this represents the
972 value of the handle in the visual "first" position (leftmost in horizontal,
973 bottommost in vertical orientation).
974
975 \sa effectiveSecondValue, first.value, handlesCrossed, crossingEnabled
976*/
977qreal QQuickRangeSlider::effectiveFirstValue() const
978{
979 Q_D(const QQuickRangeSlider);
980 // Return the value of the leftmost/bottommost handle visually
981 // When crossed, the "second" handle is visually on the left/bottom
982 return d->handlesCrossed ? (d->second ? d->second->value() : 1.0)
983 : (d->first ? d->first->value() : 0.0);
984}
985
986/*!
987 \since QtQuick.Controls 6.12
988 \qmlproperty real QtQuick.Controls::RangeSlider::effectiveSecondValue
989 \readonly
990
991 This property holds the value of the handle in the visual "second" position
992 (rightmost in horizontal, topmost in vertical orientation).
993
994 When \l crossingEnabled is \c false or handles haven't crossed, this is
995 equal to \c second.value. When handles have crossed, this represents the
996 value of the handle in the visual "second" position (rightmost in horizontal,
997 topmost in vertical orientation).
998
999 \sa effectiveFirstValue, second.value, handlesCrossed, crossingEnabled
1000*/
1001qreal QQuickRangeSlider::effectiveSecondValue() const
1002{
1003 Q_D(const QQuickRangeSlider);
1004 // Return the value of the rightmost/topmost handle visually
1005 // When crossed, the "first" handle is visually on the right/top
1006 return d->handlesCrossed ? (d->first ? d->first->value() : 0.0)
1007 : (d->second ? d->second->value() : 1.0);
1008}
1009
1010/*!
1011 \qmlproperty real QtQuick.Controls::RangeSlider::first.value
1012 \qmlproperty real QtQuick.Controls::RangeSlider::first.position
1013 \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
1014 \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle
1015 \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed
1016 \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered
1017 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth
1018 \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight
1019
1020 \table
1021 \header
1022 \li Property
1023 \li Description
1024 \row
1025 \li value
1026 \li This property holds the value of the first handle in the range
1027 \c from - \c to.
1028
1029 If \l from is greater than \l to, the value of the first handle
1030 must be greater than the second, and vice versa.
1031
1032 The default value is \c 0.0.
1033 \row
1034 \li handle
1035 \li This property holds the first handle item.
1036 \row
1037 \li visualPosition
1038 \li This property holds the visual position of the first handle.
1039
1040 The position is expressed as a fraction of the control's size, in the range
1041 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
1042 value is equal to \c {1.0 - position}. This makes the value suitable for
1043 visualizing the slider, taking right-to-left support into account.
1044 \row
1045 \li position
1046 \li This property holds the logical position of the first handle.
1047
1048 The position is expressed as a fraction of the control's size, in the range
1049 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
1050 \l {first.visualPosition}{visualPosition} should be used instead.
1051 \row
1052 \li pressed
1053 \li This property holds whether the first handle is pressed by either touch,
1054 mouse, or keys.
1055 \row
1056 \li hovered
1057 \li This property holds whether the first handle is hovered.
1058 This property was introduced in \l{QtQuick.Controls} 2.1.
1059 \row
1060 \li implicitHandleWidth
1061 \li This property holds the implicit width of the first handle.
1062 This property was introduced in \l{QtQuick.Controls} 2.5.
1063 \row
1064 \li implicitHandleHeight
1065 \li This property holds the implicit height of the first handle.
1066 This property was introduced in \l{QtQuick.Controls} 2.5.
1067 \endtable
1068
1069 \sa first.moved(), first.increase(), first.decrease()
1070*/
1071QQuickRangeSliderNode *QQuickRangeSlider::first() const
1072{
1073 Q_D(const QQuickRangeSlider);
1074 return d->first;
1075}
1076
1077/*!
1078 \qmlsignal void QtQuick.Controls::RangeSlider::first.moved()
1079 \qmlsignal void QtQuick.Controls::RangeSlider::second.moved()
1080 \since QtQuick.Controls 2.5
1081
1082 This signal is emitted when either the first or second handle has been
1083 interactively moved by the user by either touch, mouse, or keys.
1084
1085 \sa first, second
1086*/
1087
1088/*!
1089 \qmlsignal void QtQuick.Controls::RangeSlider::handlesCrossedChanged()
1090 \since QtQuick.Controls 6.12
1091
1092 This signal is emitted when the handles cross each other, changing
1093 the \l handlesCrossed property.
1094
1095 \sa crossingEnabled
1096*/
1097
1098/*!
1099 \qmlproperty real QtQuick.Controls::RangeSlider::second.value
1100 \qmlproperty real QtQuick.Controls::RangeSlider::second.position
1101 \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition
1102 \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle
1103 \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed
1104 \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered
1105 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth
1106 \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight
1107
1108 \table
1109 \header
1110 \li Property
1111 \li Description
1112 \row
1113 \li value
1114 \li This property holds the value of the second handle in the range
1115 \c from - \c to.
1116
1117 If \l from is greater than \l to, the value of the first handle
1118 must be greater than the second, and vice versa.
1119
1120 The default value is \c 0.0.
1121 \row
1122 \li handle
1123 \li This property holds the second handle item.
1124 \row
1125 \li visualPosition
1126 \li This property holds the visual position of the second handle.
1127
1128 The position is expressed as a fraction of the control's size, in the range
1129 \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
1130 value is equal to \c {1.0 - position}. This makes the value suitable for
1131 visualizing the slider, taking right-to-left support into account.
1132 \row
1133 \li position
1134 \li This property holds the logical position of the second handle.
1135
1136 The position is expressed as a fraction of the control's size, in the range
1137 \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
1138 \l {second.visualPosition}{visualPosition} should be used instead.
1139 \row
1140 \li pressed
1141 \li This property holds whether the second handle is pressed by either touch,
1142 mouse, or keys.
1143 \row
1144 \li hovered
1145 \li This property holds whether the second handle is hovered.
1146 This property was introduced in \l{QtQuick.Controls} 2.1.
1147 \row
1148 \li implicitHandleWidth
1149 \li This property holds the implicit width of the second handle.
1150 This property was introduced in \l{QtQuick.Controls} 2.5.
1151 \row
1152 \li implicitHandleHeight
1153 \li This property holds the implicit height of the second handle.
1154 This property was introduced in \l{QtQuick.Controls} 2.5.
1155 \endtable
1156
1157 \sa second.moved(), second.increase(), second.decrease()
1158*/
1159QQuickRangeSliderNode *QQuickRangeSlider::second() const
1160{
1161 Q_D(const QQuickRangeSlider);
1162 return d->second;
1163}
1164
1165/*!
1166 \qmlproperty real QtQuick.Controls::RangeSlider::stepSize
1167
1168 This property holds the step size. The default value is \c 0.0.
1169
1170 \sa snapMode, first.increase(), first.decrease()
1171*/
1172qreal QQuickRangeSlider::stepSize() const
1173{
1174 Q_D(const QQuickRangeSlider);
1175 return d->stepSize;
1176}
1177
1178void QQuickRangeSlider::setStepSize(qreal step)
1179{
1180 Q_D(QQuickRangeSlider);
1181 if (qFuzzyCompare(d->stepSize, step))
1182 return;
1183
1184 d->stepSize = step;
1185 emit stepSizeChanged();
1186 d->updateAllValuesAreInteger();
1187}
1188
1189/*!
1190 \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode
1191
1192 This property holds the snap mode.
1193
1194 The snap mode determines how the slider handles behave with
1195 regards to the \l stepSize.
1196
1197 Possible values:
1198 \value RangeSlider.NoSnap The slider does not snap (default).
1199 \value RangeSlider.SnapAlways The slider snaps while the handle is dragged.
1200 \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released.
1201
1202 For visual explanations of the various modes, see the
1203 \l {Slider::}{snapMode} documentation of \l Slider.
1204
1205 \sa stepSize
1206*/
1207QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const
1208{
1209 Q_D(const QQuickRangeSlider);
1210 return d->snapMode;
1211}
1212
1213void QQuickRangeSlider::setSnapMode(SnapMode mode)
1214{
1215 Q_D(QQuickRangeSlider);
1216 if (d->snapMode == mode)
1217 return;
1218
1219 d->snapMode = mode;
1220 emit snapModeChanged();
1221}
1222
1223/*!
1224 \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation
1225
1226 This property holds the orientation.
1227
1228 Possible values:
1229 \value Qt.Horizontal Horizontal (default)
1230 \value Qt.Vertical Vertical
1231
1232 \sa horizontal, vertical
1233*/
1234Qt::Orientation QQuickRangeSlider::orientation() const
1235{
1236 Q_D(const QQuickRangeSlider);
1237 return d->orientation;
1238}
1239
1240void QQuickRangeSlider::setOrientation(Qt::Orientation orientation)
1241{
1242 Q_D(QQuickRangeSlider);
1243 if (d->orientation == orientation)
1244 return;
1245
1246 if (orientation == Qt::Horizontal)
1247 d->setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Fixed);
1248 else
1249 d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Expanding);
1250
1251 d->orientation = orientation;
1252 emit orientationChanged();
1253}
1254
1255/*!
1256 \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue)
1257
1258 Sets \l first.value and \l second.value with the given arguments.
1259
1260 If \l to is larger than \l from and \a firstValue is larger than
1261 \a secondValue, firstValue will be clamped to secondValue.
1262
1263 If \l from is larger than \l to and secondValue is larger than
1264 firstValue, secondValue will be clamped to firstValue.
1265
1266 This function may be necessary to set the first and second values
1267 after the control has been completed, as there is a circular
1268 dependency between firstValue and secondValue which can cause
1269 assigned values to be clamped to each other.
1270
1271 \sa stepSize
1272*/
1273void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue)
1274{
1275 Q_D(QQuickRangeSlider);
1276 // Restrict the values to be within to and from.
1277 const qreal smaller = qMin(d->to, d->from);
1278 const qreal larger = qMax(d->to, d->from);
1279 firstValue = qBound(smaller, firstValue, larger);
1280 secondValue = qBound(smaller, secondValue, larger);
1281
1282 // Only apply crossing prevention if crossing is disabled
1283 if (!d->crossingEnabled) {
1284 if (d->from > d->to) {
1285 // If the from and to values are reversed, the secondValue
1286 // might be less than the first value, which is not allowed.
1287 if (secondValue > firstValue)
1288 secondValue = firstValue;
1289 } else {
1290 // Otherwise, clamp first to second if it's too large.
1291 if (firstValue > secondValue)
1292 firstValue = secondValue;
1293 }
1294 }
1295
1296 // Then set both values. If they didn't change, no change signal will be emitted.
1297 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
1298 if (firstValue != firstPrivate->value) {
1299 firstPrivate->value = firstValue;
1300 emit d->first->valueChanged();
1301 }
1302
1303 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
1304 if (secondValue != secondPrivate->value) {
1305 secondPrivate->value = secondValue;
1306 emit d->second->valueChanged();
1307 }
1308
1309 // After we've set both values, then we can update the positions.
1310 // If we don't do this last, the positions may be incorrect.
1311 firstPrivate->updatePosition(true);
1312 secondPrivate->updatePosition();
1313
1314 // Check for crossing after values are set
1315 if (d->crossingEnabled)
1316 d->updateHandleCrossing();
1317}
1318
1319/*!
1320 \since QtQuick.Controls 2.2 (Qt 5.9)
1321 \qmlproperty bool QtQuick.Controls::RangeSlider::live
1322
1323 This property holds whether the slider provides live updates for the \l first.value
1324 and \l second.value properties while the respective handles are dragged.
1325
1326 The default value is \c true.
1327
1328 \sa first.value, second.value
1329*/
1330bool QQuickRangeSlider::live() const
1331{
1332 Q_D(const QQuickRangeSlider);
1333 return d->live;
1334}
1335
1336void QQuickRangeSlider::setLive(bool live)
1337{
1338 Q_D(QQuickRangeSlider);
1339 if (d->live == live)
1340 return;
1341
1342 d->live = live;
1343 emit liveChanged();
1344}
1345
1346/*!
1347 \since QtQuick.Controls 2.3 (Qt 5.10)
1348 \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal
1349 \readonly
1350
1351 This property holds whether the slider is horizontal.
1352
1353 \sa orientation
1354*/
1355bool QQuickRangeSlider::isHorizontal() const
1356{
1357 Q_D(const QQuickRangeSlider);
1358 return d->orientation == Qt::Horizontal;
1359}
1360
1361/*!
1362 \since QtQuick.Controls 2.3 (Qt 5.10)
1363 \qmlproperty bool QtQuick.Controls::RangeSlider::vertical
1364 \readonly
1365
1366 This property holds whether the slider is vertical.
1367
1368 \sa orientation
1369*/
1370bool QQuickRangeSlider::isVertical() const
1371{
1372 Q_D(const QQuickRangeSlider);
1373 return d->orientation == Qt::Vertical;
1374}
1375
1376void QQuickRangeSlider::focusInEvent(QFocusEvent *event)
1377{
1378 Q_D(QQuickRangeSlider);
1379 QQuickControl::focusInEvent(event);
1380
1381 // The active focus ends up to RangeSlider when using forceActiveFocus()
1382 // or QML KeyNavigation. We must forward the focus to one of the handles,
1383 // because RangeSlider handles key events for the focused handle. If
1384 // neither handle has active focus, RangeSlider doesn't do anything.
1385 QQuickItem *handle = nextItemInFocusChain();
1386 // QQuickItem::nextItemInFocusChain() only works as desired with
1387 // Qt::TabFocusAllControls. otherwise pick the first handle
1388 if (!handle || handle == this)
1389 handle = !d->handlesCrossed ? d->first->handle() : d->second->handle();
1390 if (handle)
1391 handle->forceActiveFocus(event->reason());
1392}
1393
1394void QQuickRangeSlider::keyPressEvent(QKeyEvent *event)
1395{
1396 Q_D(QQuickRangeSlider);
1397 QQuickControl::keyPressEvent(event);
1398
1399 QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus()
1400 ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr);
1401 if (!focusNode)
1402 return;
1403
1404 const qreal oldValue = focusNode->value();
1405 if (d->orientation == Qt::Horizontal) {
1406 if (event->key() == Qt::Key_Left) {
1407 focusNode->setPressed(true);
1408 if (isMirrored())
1409 focusNode->increase();
1410 else
1411 focusNode->decrease();
1412 event->accept();
1413 } else if (event->key() == Qt::Key_Right) {
1414 focusNode->setPressed(true);
1415 if (isMirrored())
1416 focusNode->decrease();
1417 else
1418 focusNode->increase();
1419 event->accept();
1420 }
1421 } else {
1422 if (event->key() == Qt::Key_Up) {
1423 focusNode->setPressed(true);
1424 focusNode->increase();
1425 event->accept();
1426 } else if (event->key() == Qt::Key_Down) {
1427 focusNode->setPressed(true);
1428 focusNode->decrease();
1429 event->accept();
1430 }
1431 }
1432 if (!qFuzzyCompare(focusNode->value(), oldValue))
1433 emit focusNode->moved();
1434}
1435
1436void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event)
1437{
1438 Q_D(QQuickRangeSlider);
1439 QQuickControl::hoverEnterEvent(event);
1440 d->updateHover(event->position());
1441 event->ignore();
1442}
1443
1444void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event)
1445{
1446 Q_D(QQuickRangeSlider);
1447 QQuickControl::hoverMoveEvent(event);
1448 d->updateHover(event->position());
1449 event->ignore();
1450}
1451
1452void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event)
1453{
1454 Q_D(QQuickRangeSlider);
1455 QQuickControl::hoverLeaveEvent(event);
1456 d->first->setHovered(false);
1457 d->second->setHovered(false);
1458 event->ignore();
1459}
1460
1461void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event)
1462{
1463 Q_D(QQuickRangeSlider);
1464 QQuickControl::keyReleaseEvent(event);
1465 d->first->setPressed(false);
1466 d->second->setPressed(false);
1467}
1468
1469void QQuickRangeSlider::mousePressEvent(QMouseEvent *event)
1470{
1471 Q_D(QQuickRangeSlider);
1472 QQuickControl::mousePressEvent(event);
1473 d->handleMove(event->position(), event->timestamp());
1474 setKeepMouseGrab(true);
1475}
1476
1477#if QT_CONFIG(quicktemplates2_multitouch)
1478void QQuickRangeSlider::touchEvent(QTouchEvent *event)
1479{
1480 Q_D(QQuickRangeSlider);
1481 switch (event->type()) {
1482 case QEvent::TouchUpdate:
1483 for (const QTouchEvent::TouchPoint &point : event->points()) {
1484 if (!d->acceptTouch(point))
1485 continue;
1486
1487 switch (point.state()) {
1488 case QEventPoint::Pressed:
1489 d->handlePress(point.position(), event->timestamp());
1490 break;
1491 case QEventPoint::Updated:
1492 if (!keepTouchGrab()) {
1493 if (d->orientation == Qt::Horizontal) {
1494 setKeepTouchGrab(QQuickDeliveryAgentPrivate::dragOverThreshold(point.position().x() - point.pressPosition().x(),
1495 Qt::XAxis, point, qRound(d->touchDragThreshold)));
1496 } else {
1497 setKeepTouchGrab(QQuickDeliveryAgentPrivate::dragOverThreshold(point.position().y() - point.pressPosition().y(),
1498 Qt::YAxis, point, qRound(d->touchDragThreshold)));
1499 }
1500 }
1501 if (keepTouchGrab())
1502 d->handleMove(point.position(), event->timestamp());
1503 break;
1504 case QEventPoint::Released:
1505 d->handleRelease(point.position(), event->timestamp());
1506 break;
1507 default:
1508 break;
1509 }
1510 }
1511 break;
1512
1513 default:
1514 QQuickControl::touchEvent(event);
1515 break;
1516 }
1517}
1518#endif
1519
1520void QQuickRangeSlider::mirrorChange()
1521{
1522 Q_D(QQuickRangeSlider);
1523 QQuickControl::mirrorChange();
1524 emit d->first->visualPositionChanged();
1525 emit d->second->visualPositionChanged();
1526}
1527
1528void QQuickRangeSlider::classBegin()
1529{
1530 Q_D(QQuickRangeSlider);
1531 QQuickControl::classBegin();
1532
1533 QQmlContext *context = qmlContext(this);
1534 if (context) {
1535 QQmlEngine::setContextForObject(d->first, context);
1536 QQmlEngine::setContextForObject(d->second, context);
1537 }
1538}
1539
1540void QQuickRangeSlider::componentComplete()
1541{
1542 Q_D(QQuickRangeSlider);
1543 QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
1544 QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
1545 firstPrivate->executeHandle(true);
1546 secondPrivate->executeHandle(true);
1547
1548 QQuickControl::componentComplete();
1549
1550 if (firstPrivate->isPendingValue || secondPrivate->isPendingValue
1551 || !qFuzzyCompare(d->from, defaultFrom) || !qFuzzyCompare(d->to, defaultTo)) {
1552 // Properties were set while we were loading. To avoid clamping issues that occur when setting the
1553 // values of first and second overriding values set by the user, set them all at once at the end.
1554 // Another reason that we must set these values here is that the from and to values might have made the old range invalid.
1555 setValues(firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value,
1556 secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value);
1557
1558 firstPrivate->pendingValue = 0;
1559 firstPrivate->isPendingValue = false;
1560 secondPrivate->pendingValue = 0;
1561 secondPrivate->isPendingValue = false;
1562 } else {
1563 // If there was no pending data, we must still update the positions,
1564 // as first.setValue()/second.setValue() won't be called as part of default construction.
1565 // Don't need to ignore the second position when updating the first position here,
1566 // as our default values are guaranteed to be valid.
1567 firstPrivate->updatePosition();
1568 secondPrivate->updatePosition();
1569 }
1570
1571 d->updateAllValuesAreInteger();
1572}
1573
1574/*!
1575 \qmlmethod void QtQuick.Controls::RangeSlider::first.increase()
1576
1577 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1578
1579 \sa first
1580*/
1581
1582/*!
1583 \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease()
1584
1585 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1586
1587 \sa first
1588*/
1589
1590/*!
1591 \qmlmethod void QtQuick.Controls::RangeSlider::second.increase()
1592
1593 Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1594
1595 \sa second
1596*/
1597
1598/*!
1599 \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease()
1600
1601 Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
1602
1603 \sa second
1604*/
1605
1606#if QT_CONFIG(accessibility)
1607QAccessible::Role QQuickRangeSlider::accessibleRole() const
1608{
1609 return QAccessible::Slider;
1610}
1611#endif
1612
1613QT_END_NAMESPACE
1614
1615#include "moc_qquickrangeslider_p.cpp"
Used to select a range of values by sliding two handles along a track.
void setPosition(qreal position, bool ignoreOtherPosition=false)
void executeHandle(bool complete=false)
static QQuickRangeSliderNodePrivate * get(QQuickRangeSliderNode *node)
QQuickDeferredPointer< QQuickItem > handle
void updatePosition(bool ignoreOtherPosition=false)
bool handleMove(const QPointF &point, ulong timestamp) override
void itemDestroyed(QQuickItem *item) override
bool handleRelease(const QPointF &point, ulong timestamp) override
QQuickRangeSliderNode * pressedNode(int touchId=-1) const
QQuickRangeSliderNode * first
void itemImplicitHeightChanged(QQuickItem *item) override
bool handlePress(const QPointF &point, ulong timestamp) override
void updateHover(const QPointF &pos)
QQuickRangeSliderNode * second
void itemImplicitWidthChanged(QQuickItem *item) override
Combined button and popup list for selecting options.
static const qreal defaultFrom
static const qreal defaultTo
static qreal valueAt(const QQuickRangeSlider *slider, qreal position)
static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point)
static qreal snapPosition(const QQuickRangeSlider *slider, qreal position)