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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/quicktemplates/qquickrangeslider.cpp