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