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