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