aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktemplates/qquickrangeslider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quicktemplates/qquickrangeslider.cpp')
-rw-r--r--src/quicktemplates/qquickrangeslider.cpp1340
1 files changed, 1340 insertions, 0 deletions
diff --git a/src/quicktemplates/qquickrangeslider.cpp b/src/quicktemplates/qquickrangeslider.cpp
new file mode 100644
index 0000000000..d97dfdff90
--- /dev/null
+++ b/src/quicktemplates/qquickrangeslider.cpp
@@ -0,0 +1,1340 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickrangeslider_p.h"
+#include "qquickcontrol_p_p.h"
+#include "qquickdeferredexecute_p_p.h"
+
+#include <QtCore/qscopedpointer.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype RangeSlider
+ \inherits Control
+//! \instantiates QQuickRangeSlider
+ \inqmlmodule QtQuick.Controls
+ \since 5.7
+ \ingroup qtquickcontrols-input
+ \ingroup qtquickcontrols-focusscopes
+ \brief Used to select a range of values by sliding two handles along a track.
+
+ \image qtquickcontrols-rangeslider.gif
+
+ RangeSlider is used to select a range specified by two values, by sliding
+ each handle along a track.
+
+ In the example below, custom \l from and \l to values are set, and the
+ initial positions of the \l first and \l second handles are set:
+
+ \code
+ RangeSlider {
+ from: 1
+ to: 100
+ first.value: 25
+ second.value: 75
+ }
+ \endcode
+
+ In order to perform an action when the value for a particular handle changes,
+ use the following syntax:
+
+ \code
+ first.onMoved: console.log("first.value changed to " + first.value)
+ \endcode
+
+ The \l {first.position} and \l {second.position} properties are expressed as
+ fractions of the control's size, in the range \c {0.0 - 1.0}.
+ The \l {first.visualPosition} and \l {second.visualPosition} properties are
+ the same, except that they are reversed in a
+ \l {Right-to-left User Interfaces}{right-to-left} application.
+ The \c visualPosition is useful for positioning the handles when styling
+ RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24
+ in a left-to-right application, and \c 0.76 in a right-to-left application.
+
+ For a slider that allows the user to select a single value, see \l Slider.
+
+ \sa {Customizing RangeSlider}, {Input Controls},
+ {Focus Management in Qt Quick Controls}
+*/
+
+class QQuickRangeSliderNodePrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickRangeSliderNode)
+public:
+ QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider)
+ : value(value),
+ slider(slider)
+ {
+ }
+
+ bool isFirst() const;
+
+ void setPosition(qreal position, bool ignoreOtherPosition = false);
+ void updatePosition(bool ignoreOtherPosition = false);
+
+ void cancelHandle();
+ void executeHandle(bool complete = false);
+
+ static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node);
+
+ qreal value = 0;
+ bool isPendingValue = false;
+ qreal pendingValue = 0;
+ qreal position = 0;
+ QQuickDeferredPointer<QQuickItem> handle;
+ QQuickRangeSlider *slider = nullptr;
+ bool pressed = false;
+ bool hovered = false;
+ int touchId = -1;
+};
+
+bool QQuickRangeSliderNodePrivate::isFirst() const
+{
+ return this == get(slider->first());
+}
+
+void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition)
+{
+ Q_Q(QQuickRangeSliderNode);
+
+ const qreal min = isFirst() || ignoreOtherPosition ? 0.0 : qMax<qreal>(0.0, slider->first()->position());
+ const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin<qreal>(1.0, slider->second()->position());
+ position = qBound(min, position, max);
+ if (!qFuzzyCompare(this->position, position)) {
+ this->position = position;
+ emit q->positionChanged();
+ emit q->visualPositionChanged();
+ }
+}
+
+void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition)
+{
+ qreal pos = 0;
+ if (!qFuzzyCompare(slider->from(), slider->to()))
+ pos = (value - slider->from()) / (slider->to() - slider->from());
+ setPosition(pos, ignoreOtherPosition);
+}
+
+void QQuickRangeSliderNodePrivate::cancelHandle()
+{
+ Q_Q(QQuickRangeSliderNode);
+ quickCancelDeferred(q, handleName());
+}
+
+void QQuickRangeSliderNodePrivate::executeHandle(bool complete)
+{
+ Q_Q(QQuickRangeSliderNode);
+ if (handle.wasExecuted())
+ return;
+
+ if (!handle || complete)
+ quickBeginDeferred(q, handleName(), handle);
+ if (complete)
+ quickCompleteDeferred(q, handleName(), handle);
+}
+
+QQuickRangeSliderNodePrivate *QQuickRangeSliderNodePrivate::get(QQuickRangeSliderNode *node)
+{
+ return node->d_func();
+}
+
+QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider)
+ : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider)
+{
+}
+
+QQuickRangeSliderNode::~QQuickRangeSliderNode()
+{
+}
+
+qreal QQuickRangeSliderNode::value() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ return d->value;
+}
+
+void QQuickRangeSliderNode::setValue(qreal value)
+{
+ Q_D(QQuickRangeSliderNode);
+ if (!d->slider->isComponentComplete()) {
+ d->pendingValue = value;
+ d->isPendingValue = true;
+ return;
+ }
+
+ // First, restrict the first value to be within to and from.
+ const qreal smaller = qMin(d->slider->to(), d->slider->from());
+ const qreal larger = qMax(d->slider->to(), d->slider->from());
+ value = qBound(smaller, value, larger);
+
+ // Then, ensure that it doesn't go past the other value,
+ // a check that depends on whether or not the range is inverted.
+ const bool invertedRange = d->slider->from() > d->slider->to();
+ if (d->isFirst()) {
+ if (invertedRange) {
+ if (value < d->slider->second()->value())
+ value = d->slider->second()->value();
+ } else {
+ if (value > d->slider->second()->value())
+ value = d->slider->second()->value();
+ }
+ } else {
+ if (invertedRange) {
+ if (value > d->slider->first()->value())
+ value = d->slider->first()->value();
+ } else {
+ if (value < d->slider->first()->value())
+ value = d->slider->first()->value();
+ }
+ }
+
+ if (!qFuzzyCompare(d->value, value)) {
+ d->value = value;
+ d->updatePosition();
+ emit valueChanged();
+ }
+}
+
+qreal QQuickRangeSliderNode::position() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ return d->position;
+}
+
+qreal QQuickRangeSliderNode::visualPosition() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored())
+ return 1.0 - d->position;
+ return d->position;
+}
+
+QQuickItem *QQuickRangeSliderNode::handle() const
+{
+ QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func());
+ if (!d->handle)
+ d->executeHandle();
+ return d->handle;
+}
+
+void QQuickRangeSliderNode::setHandle(QQuickItem *handle)
+{
+ Q_D(QQuickRangeSliderNode);
+ if (d->handle == handle)
+ return;
+
+ QQuickControlPrivate::warnIfCustomizationNotSupported(d->slider, handle, QStringLiteral("handle"));
+
+ if (!d->handle.isExecuting())
+ d->cancelHandle();
+
+ const qreal oldImplicitHandleWidth = implicitHandleWidth();
+ const qreal oldImplicitHandleHeight = implicitHandleHeight();
+
+ QQuickControlPrivate::get(d->slider)->removeImplicitSizeListener(d->handle);
+ QQuickControlPrivate::hideOldItem(d->handle);
+ d->handle = handle;
+
+ if (handle) {
+ if (!handle->parentItem())
+ handle->setParentItem(d->slider);
+
+ QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(d->slider->first())->handle;
+ QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(d->slider->second())->handle;
+ if (firstHandle && secondHandle) {
+ // The order of property assignments in QML is undefined,
+ // but we need the first handle to be before the second due
+ // to focus order constraints, so check for that here.
+ const QList<QQuickItem *> childItems = d->slider->childItems();
+ const int firstIndex = childItems.indexOf(firstHandle);
+ const int secondIndex = childItems.indexOf(secondHandle);
+ if (firstIndex != -1 && secondIndex != -1 && firstIndex > secondIndex) {
+ firstHandle->stackBefore(secondHandle);
+ // Ensure we have some way of knowing which handle is above
+ // the other when it comes to mouse presses, and also that
+ // they are rendered in the correct order.
+ secondHandle->setZ(secondHandle->z() + 1);
+ }
+ }
+
+ handle->setActiveFocusOnTab(true);
+ QQuickControlPrivate::get(d->slider)->addImplicitSizeListener(handle);
+ }
+
+ if (!qFuzzyCompare(oldImplicitHandleWidth, implicitHandleWidth()))
+ emit implicitHandleWidthChanged();
+ if (!qFuzzyCompare(oldImplicitHandleHeight, implicitHandleHeight()))
+ emit implicitHandleHeightChanged();
+ if (!d->handle.isExecuting())
+ emit handleChanged();
+}
+
+bool QQuickRangeSliderNode::isPressed() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ return d->pressed;
+}
+
+void QQuickRangeSliderNode::setPressed(bool pressed)
+{
+ Q_D(QQuickRangeSliderNode);
+ if (d->pressed == pressed)
+ return;
+
+ d->pressed = pressed;
+ d->slider->setAccessibleProperty("pressed", pressed || d->slider->second()->isPressed());
+ emit pressedChanged();
+}
+
+bool QQuickRangeSliderNode::isHovered() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ return d->hovered;
+}
+
+void QQuickRangeSliderNode::setHovered(bool hovered)
+{
+ Q_D(QQuickRangeSliderNode);
+ if (d->hovered == hovered)
+ return;
+
+ d->hovered = hovered;
+ emit hoveredChanged();
+}
+
+qreal QQuickRangeSliderNode::implicitHandleWidth() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ if (!d->handle)
+ return 0;
+ return d->handle->implicitWidth();
+}
+
+qreal QQuickRangeSliderNode::implicitHandleHeight() const
+{
+ Q_D(const QQuickRangeSliderNode);
+ if (!d->handle)
+ return 0;
+ return d->handle->implicitHeight();
+}
+
+void QQuickRangeSliderNode::increase()
+{
+ Q_D(QQuickRangeSliderNode);
+ qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
+ setValue(d->value + step);
+}
+
+void QQuickRangeSliderNode::decrease()
+{
+ Q_D(QQuickRangeSliderNode);
+ qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize();
+ setValue(d->value - step);
+}
+
+static const qreal defaultFrom = 0.0;
+static const qreal defaultTo = 1.0;
+
+class QQuickRangeSliderPrivate : public QQuickControlPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickRangeSlider)
+
+public:
+ QQuickRangeSliderNode *pressedNode(int touchId = -1) const;
+
+#if QT_CONFIG(quicktemplates2_multitouch)
+ bool acceptTouch(const QTouchEvent::TouchPoint &point) override;
+#endif
+ bool handlePress(const QPointF &point, ulong timestamp) override;
+ bool handleMove(const QPointF &point, ulong timestamp) override;
+ bool handleRelease(const QPointF &point, ulong timestamp) override;
+ void handleUngrab() override;
+
+ void updateHover(const QPointF &pos);
+
+ void itemImplicitWidthChanged(QQuickItem *item) override;
+ void itemImplicitHeightChanged(QQuickItem *item) override;
+ void itemDestroyed(QQuickItem *item) override;
+
+ bool live = true;
+ qreal from = defaultFrom;
+ qreal to = defaultTo;
+ qreal stepSize = 0;
+ qreal touchDragThreshold = -1;
+ QQuickRangeSliderNode *first = nullptr;
+ QQuickRangeSliderNode *second = nullptr;
+ QPointF pressPoint;
+ Qt::Orientation orientation = Qt::Horizontal;
+ QQuickRangeSlider::SnapMode snapMode = QQuickRangeSlider::NoSnap;
+};
+
+static qreal valueAt(const QQuickRangeSlider *slider, qreal position)
+{
+ return slider->from() + (slider->to() - slider->from()) * position;
+}
+
+static qreal snapPosition(const QQuickRangeSlider *slider, qreal position)
+{
+ const qreal range = slider->to() - slider->from();
+ if (qFuzzyIsNull(range))
+ return position;
+
+ const qreal effectiveStep = slider->stepSize() / range;
+ if (qFuzzyIsNull(effectiveStep))
+ return position;
+
+ return qRound(position / effectiveStep) * effectiveStep;
+}
+
+static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point)
+{
+ if (slider->orientation() == Qt::Horizontal) {
+ const qreal hw = handle ? handle->width() : 0;
+ const qreal offset = hw / 2;
+ const qreal extent = slider->availableWidth() - hw;
+ if (!qFuzzyIsNull(extent)) {
+ if (slider->isMirrored())
+ return (slider->width() - point.x() - slider->rightPadding() - offset) / extent;
+ return (point.x() - slider->leftPadding() - offset) / extent;
+ }
+ } else {
+ const qreal hh = handle ? handle->height() : 0;
+ const qreal offset = hh / 2;
+ const qreal extent = slider->availableHeight() - hh;
+ if (!qFuzzyIsNull(extent))
+ return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent;
+ }
+ return 0;
+}
+
+QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const
+{
+ if (touchId == -1)
+ return first->isPressed() ? first : (second->isPressed() ? second : nullptr);
+ if (QQuickRangeSliderNodePrivate::get(first)->touchId == touchId)
+ return first;
+ if (QQuickRangeSliderNodePrivate::get(second)->touchId == touchId)
+ return second;
+ return nullptr;
+}
+
+#if QT_CONFIG(quicktemplates2_multitouch)
+bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point)
+{
+ int firstId = QQuickRangeSliderNodePrivate::get(first)->touchId;
+ int secondId = QQuickRangeSliderNodePrivate::get(second)->touchId;
+
+ if (((firstId == -1 || secondId == -1) && point.state() == QEventPoint::Pressed) || point.id() == firstId || point.id() == secondId) {
+ touchId = point.id();
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+bool QQuickRangeSliderPrivate::handlePress(const QPointF &point, ulong timestamp)
+{
+ Q_Q(QQuickRangeSlider);
+ QQuickControlPrivate::handlePress(point, timestamp);
+ pressPoint = point;
+
+ QQuickItem *firstHandle = first->handle();
+ QQuickItem *secondHandle = second->handle();
+ const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(q->mapToItem(firstHandle, point));
+ const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(q->mapToItem(secondHandle, point));
+ QQuickRangeSliderNode *hitNode = nullptr;
+ QQuickRangeSliderNode *otherNode = nullptr;
+
+ if (firstHit && secondHit) {
+ // choose highest
+ hitNode = firstHandle->z() > secondHandle->z() ? first : second;
+ otherNode = firstHandle->z() > secondHandle->z() ? second : first;
+ } else if (firstHit) {
+ hitNode = first;
+ otherNode = second;
+ } else if (secondHit) {
+ hitNode = second;
+ otherNode = first;
+ } else {
+ // find the nearest
+ const qreal firstPos = positionAt(q, firstHandle, point);
+ const qreal secondPos = positionAt(q, secondHandle, point);
+ const qreal firstDistance = qAbs(firstPos - first->position());
+ const qreal secondDistance = qAbs(secondPos - second->position());
+
+ if (qFuzzyCompare(firstDistance, secondDistance)) {
+ // same distance => choose the one that can be moved towards the press position
+ const bool inverted = from > to;
+ if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) {
+ hitNode = first;
+ otherNode = second;
+ } else {
+ hitNode = second;
+ otherNode = first;
+ }
+ } else if (firstDistance < secondDistance) {
+ hitNode = first;
+ otherNode = second;
+ } else {
+ hitNode = second;
+ otherNode = first;
+ }
+ }
+
+ if (hitNode) {
+ hitNode->setPressed(true);
+ if (QQuickItem *handle = hitNode->handle()) {
+ handle->setZ(1);
+
+ // A specific handle was hit, so it should get focus, rather than the default
+ // (first handle) that gets focus whenever the RangeSlider itself does - see focusInEvent().
+ if (focusPolicy & Qt::ClickFocus)
+ handle->forceActiveFocus(Qt::MouseFocusReason);
+ }
+ QQuickRangeSliderNodePrivate::get(hitNode)->touchId = touchId;
+ }
+ if (otherNode) {
+ if (QQuickItem *handle = otherNode->handle())
+ handle->setZ(0);
+ }
+ return true;
+}
+
+bool QQuickRangeSliderPrivate::handleMove(const QPointF &point, ulong timestamp)
+{
+ Q_Q(QQuickRangeSlider);
+ QQuickControlPrivate::handleMove(point, timestamp);
+ QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
+ if (pressedNode) {
+ const qreal oldPos = pressedNode->position();
+ qreal pos = positionAt(q, pressedNode->handle(), point);
+ if (snapMode == QQuickRangeSlider::SnapAlways)
+ pos = snapPosition(q, pos);
+ if (live)
+ pressedNode->setValue(valueAt(q, pos));
+ else
+ QQuickRangeSliderNodePrivate::get(pressedNode)->setPosition(pos);
+
+ if (!qFuzzyCompare(pressedNode->position(), oldPos))
+ emit pressedNode->moved();
+ }
+ return true;
+}
+
+bool QQuickRangeSliderPrivate::handleRelease(const QPointF &point, ulong timestamp)
+{
+ Q_Q(QQuickRangeSlider);
+ QQuickControlPrivate::handleRelease(point, timestamp);
+ pressPoint = QPointF();
+
+ QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId);
+ if (!pressedNode)
+ return true;
+ QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(pressedNode);
+
+ const qreal oldPos = pressedNode->position();
+ qreal pos = positionAt(q, pressedNode->handle(), point);
+ if (snapMode != QQuickRangeSlider::NoSnap)
+ pos = snapPosition(q, pos);
+ qreal val = valueAt(q, pos);
+ if (!qFuzzyCompare(val, pressedNode->value()))
+ pressedNode->setValue(val);
+ else if (snapMode != QQuickRangeSlider::NoSnap)
+ pressedNodePrivate->setPosition(pos);
+ q->setKeepMouseGrab(false);
+ q->setKeepTouchGrab(false);
+
+ if (!qFuzzyCompare(pressedNode->position(), oldPos))
+ emit pressedNode->moved();
+
+ pressedNode->setPressed(false);
+ pressedNodePrivate->touchId = -1;
+ return true;
+}
+
+void QQuickRangeSliderPrivate::handleUngrab()
+{
+ QQuickControlPrivate::handleUngrab();
+ pressPoint = QPointF();
+ first->setPressed(false);
+ second->setPressed(false);
+ QQuickRangeSliderNodePrivate::get(first)->touchId = -1;
+ QQuickRangeSliderNodePrivate::get(second)->touchId = -1;
+}
+
+void QQuickRangeSliderPrivate::updateHover(const QPointF &pos)
+{
+ Q_Q(QQuickRangeSlider);
+ QQuickItem *firstHandle = first->handle();
+ QQuickItem *secondHandle = second->handle();
+ bool firstHandleHovered = firstHandle && firstHandle->isEnabled()
+ && firstHandle->contains(q->mapToItem(firstHandle, pos));
+ bool secondHandleHovered = secondHandle && secondHandle->isEnabled()
+ && secondHandle->contains(q->mapToItem(secondHandle, pos));
+
+ if (firstHandleHovered && secondHandleHovered) {
+ // Only hover the handle with the higher Z value.
+ const bool firstHandleHasHigherZ = firstHandle->z() > secondHandle->z();
+ firstHandleHovered = firstHandleHasHigherZ;
+ secondHandleHovered = !firstHandleHasHigherZ;
+ }
+ first->setHovered(firstHandleHovered);
+ second->setHovered(secondHandleHovered);
+}
+
+void QQuickRangeSliderPrivate::itemImplicitWidthChanged(QQuickItem *item)
+{
+ QQuickControlPrivate::itemImplicitWidthChanged(item);
+ if (item == first->handle())
+ emit first->implicitHandleWidthChanged();
+ else if (item == second->handle())
+ emit second->implicitHandleWidthChanged();
+}
+
+void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item)
+{
+ QQuickControlPrivate::itemImplicitHeightChanged(item);
+ if (item == first->handle())
+ emit first->implicitHandleHeightChanged();
+ else if (item == second->handle())
+ emit second->implicitHandleHeightChanged();
+}
+
+void QQuickRangeSliderPrivate::itemDestroyed(QQuickItem *item)
+{
+ QQuickControlPrivate::itemDestroyed(item);
+ if (item == first->handle())
+ first->setHandle(nullptr);
+ else if (item == second->handle())
+ second->setHandle(nullptr);
+}
+
+QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent)
+ : QQuickControl(*(new QQuickRangeSliderPrivate), parent)
+{
+ Q_D(QQuickRangeSlider);
+ d->first = new QQuickRangeSliderNode(0.0, this);
+ d->second = new QQuickRangeSliderNode(1.0, this);
+ d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
+
+ setFlag(QQuickItem::ItemIsFocusScope);
+#ifdef Q_OS_MACOS
+ setFocusPolicy(Qt::TabFocus);
+#else
+ setFocusPolicy(Qt::StrongFocus);
+#endif
+ setAcceptedMouseButtons(Qt::LeftButton);
+#if QT_CONFIG(quicktemplates2_multitouch)
+ setAcceptTouchEvents(true);
+#endif
+#if QT_CONFIG(cursor)
+ setCursor(Qt::ArrowCursor);
+#endif
+}
+
+QQuickRangeSlider::~QQuickRangeSlider()
+{
+ Q_D(QQuickRangeSlider);
+ d->removeImplicitSizeListener(d->first->handle());
+ d->removeImplicitSizeListener(d->second->handle());
+}
+
+/*!
+ \qmlproperty real QtQuick.Controls::RangeSlider::from
+
+ This property holds the starting value for the range. The default value is \c 0.0.
+
+ \sa to, first.value, second.value
+*/
+qreal QQuickRangeSlider::from() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->from;
+}
+
+void QQuickRangeSlider::setFrom(qreal from)
+{
+ Q_D(QQuickRangeSlider);
+ if (qFuzzyCompare(d->from, from))
+ return;
+
+ d->from = from;
+ emit fromChanged();
+
+ if (isComponentComplete()) {
+ d->first->setValue(d->first->value());
+ d->second->setValue(d->second->value());
+ auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ firstPrivate->updatePosition(true);
+ secondPrivate->updatePosition();
+ }
+}
+
+/*!
+ \qmlproperty real QtQuick.Controls::RangeSlider::to
+
+ This property holds the end value for the range. The default value is \c 1.0.
+
+ \sa from, first.value, second.value
+*/
+qreal QQuickRangeSlider::to() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->to;
+}
+
+void QQuickRangeSlider::setTo(qreal to)
+{
+ Q_D(QQuickRangeSlider);
+ if (qFuzzyCompare(d->to, to))
+ return;
+
+ d->to = to;
+ emit toChanged();
+
+ if (isComponentComplete()) {
+ d->first->setValue(d->first->value());
+ d->second->setValue(d->second->value());
+ auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ firstPrivate->updatePosition(true);
+ secondPrivate->updatePosition();
+ }
+}
+
+/*!
+ \since QtQuick.Controls 2.5 (Qt 5.12)
+ \qmlproperty qreal QtQuick.Controls::RangeSlider::touchDragThreshold
+
+ This property holds the threshold (in logical pixels) at which a touch drag event will be initiated.
+ The mouse drag threshold won't be affected.
+ The default value is \c Qt.styleHints.startDragDistance.
+
+ \sa QStyleHints
+
+*/
+qreal QQuickRangeSlider::touchDragThreshold() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->touchDragThreshold;
+}
+
+void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold)
+{
+ Q_D(QQuickRangeSlider);
+ if (d->touchDragThreshold == touchDragThreshold)
+ return;
+
+ d->touchDragThreshold = touchDragThreshold;
+ emit touchDragThresholdChanged();
+}
+
+void QQuickRangeSlider::resetTouchDragThreshold()
+{
+ setTouchDragThreshold(-1);
+}
+
+/*!
+ \since QtQuick.Controls 2.5 (Qt 5.12)
+ \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position)
+
+ Returns the value for the given \a position.
+
+ \sa first.value, second.value, first.position, second.position, live
+*/
+qreal QQuickRangeSlider::valueAt(qreal position) const
+{
+ Q_D(const QQuickRangeSlider);
+ const qreal value = (d->to - d->from) * position;
+ if (qFuzzyIsNull(d->stepSize))
+ return d->from + value;
+ return d->from + qRound(value / d->stepSize) * d->stepSize;
+}
+
+/*!
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.value
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.position
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
+ \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle
+ \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed
+ \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight
+
+ \table
+ \header
+ \li Property
+ \li Description
+ \row
+ \li value
+ \li This property holds the value of the first handle in the range
+ \c from - \c to.
+
+ If \l from is greater than \l to, the value of the first handle
+ must be greater than the second, and vice versa.
+
+ The default value is \c 0.0.
+ \row
+ \li handle
+ \li This property holds the first handle item.
+ \row
+ \li visualPosition
+ \li This property holds the visual position of the first handle.
+
+ The position is expressed as a fraction of the control's size, in the range
+ \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
+ value is equal to \c {1.0 - position}. This makes the value suitable for
+ visualizing the slider, taking right-to-left support into account.
+ \row
+ \li position
+ \li This property holds the logical position of the first handle.
+
+ The position is expressed as a fraction of the control's size, in the range
+ \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
+ \l {first.visualPosition}{visualPosition} should be used instead.
+ \row
+ \li pressed
+ \li This property holds whether the first handle is pressed by either touch,
+ mouse, or keys.
+ \row
+ \li hovered
+ \li This property holds whether the first handle is hovered.
+ This property was introduced in QtQuick.Controls 2.1.
+ \row
+ \li implicitHandleWidth
+ \li This property holds the implicit width of the first handle.
+ This property was introduced in QtQuick.Controls 2.5.
+ \row
+ \li implicitHandleHeight
+ \li This property holds the implicit height of the first handle.
+ This property was introduced in QtQuick.Controls 2.5.
+ \endtable
+
+ \sa first.moved(), first.increase(), first.decrease()
+*/
+QQuickRangeSliderNode *QQuickRangeSlider::first() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->first;
+}
+
+/*!
+ \qmlsignal void QtQuick.Controls::RangeSlider::first.moved()
+ \qmlsignal void QtQuick.Controls::RangeSlider::second.moved()
+ \since QtQuick.Controls 2.5
+
+ This signal is emitted when either the first or second handle has been
+ interactively moved by the user by either touch, mouse, or keys.
+
+ \sa first, second
+*/
+
+/*!
+ \qmlproperty real QtQuick.Controls::RangeSlider::second.value
+ \qmlproperty real QtQuick.Controls::RangeSlider::second.position
+ \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition
+ \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle
+ \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed
+ \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered
+ \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth
+ \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight
+
+ \table
+ \header
+ \li Property
+ \li Description
+ \row
+ \li value
+ \li This property holds the value of the second handle in the range
+ \c from - \c to.
+
+ If \l from is greater than \l to, the value of the first handle
+ must be greater than the second, and vice versa.
+
+ The default value is \c 0.0.
+ \row
+ \li handle
+ \li This property holds the second handle item.
+ \row
+ \li visualPosition
+ \li This property holds the visual position of the second handle.
+
+ The position is expressed as a fraction of the control's size, in the range
+ \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the
+ value is equal to \c {1.0 - position}. This makes the value suitable for
+ visualizing the slider, taking right-to-left support into account.
+ \row
+ \li position
+ \li This property holds the logical position of the second handle.
+
+ The position is expressed as a fraction of the control's size, in the range
+ \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware
+ \l {second.visualPosition}{visualPosition} should be used instead.
+ \row
+ \li pressed
+ \li This property holds whether the second handle is pressed by either touch,
+ mouse, or keys.
+ \row
+ \li hovered
+ \li This property holds whether the second handle is hovered.
+ This property was introduced in QtQuick.Controls 2.1.
+ \row
+ \li implicitHandleWidth
+ \li This property holds the implicit width of the second handle.
+ This property was introduced in QtQuick.Controls 2.5.
+ \row
+ \li implicitHandleHeight
+ \li This property holds the implicit height of the second handle.
+ This property was introduced in QtQuick.Controls 2.5.
+ \endtable
+
+ \sa second.moved(), second.increase(), second.decrease()
+*/
+QQuickRangeSliderNode *QQuickRangeSlider::second() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->second;
+}
+
+/*!
+ \qmlproperty real QtQuick.Controls::RangeSlider::stepSize
+
+ This property holds the step size. The default value is \c 0.0.
+
+ \sa snapMode, first.increase(), first.decrease()
+*/
+qreal QQuickRangeSlider::stepSize() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->stepSize;
+}
+
+void QQuickRangeSlider::setStepSize(qreal step)
+{
+ Q_D(QQuickRangeSlider);
+ if (qFuzzyCompare(d->stepSize, step))
+ return;
+
+ d->stepSize = step;
+ emit stepSizeChanged();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode
+
+ This property holds the snap mode.
+
+ The snap mode determines how the slider handles behave with
+ regards to the \l stepSize.
+
+ Possible values:
+ \value RangeSlider.NoSnap The slider does not snap (default).
+ \value RangeSlider.SnapAlways The slider snaps while the handle is dragged.
+ \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released.
+
+ For visual explanations of the various modes, see the
+ \l {Slider::}{snapMode} documentation of \l Slider.
+
+ \sa stepSize
+*/
+QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->snapMode;
+}
+
+void QQuickRangeSlider::setSnapMode(SnapMode mode)
+{
+ Q_D(QQuickRangeSlider);
+ if (d->snapMode == mode)
+ return;
+
+ d->snapMode = mode;
+ emit snapModeChanged();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation
+
+ This property holds the orientation.
+
+ Possible values:
+ \value Qt.Horizontal Horizontal (default)
+ \value Qt.Vertical Vertical
+
+ \sa horizontal, vertical
+*/
+Qt::Orientation QQuickRangeSlider::orientation() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->orientation;
+}
+
+void QQuickRangeSlider::setOrientation(Qt::Orientation orientation)
+{
+ Q_D(QQuickRangeSlider);
+ if (d->orientation == orientation)
+ return;
+
+ if (orientation == Qt::Horizontal)
+ d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
+ else
+ d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred);
+
+ d->orientation = orientation;
+ emit orientationChanged();
+}
+
+/*!
+ \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue)
+
+ Sets \l first.value and \l second.value with the given arguments.
+
+ If \l to is larger than \l from and \a firstValue is larger than
+ \a secondValue, firstValue will be clamped to secondValue.
+
+ If \l from is larger than \l to and secondValue is larger than
+ firstValue, secondValue will be clamped to firstValue.
+
+ This function may be necessary to set the first and second values
+ after the control has been completed, as there is a circular
+ dependency between firstValue and secondValue which can cause
+ assigned values to be clamped to each other.
+
+ \sa stepSize
+*/
+void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue)
+{
+ Q_D(QQuickRangeSlider);
+ // Restrict the values to be within to and from.
+ const qreal smaller = qMin(d->to, d->from);
+ const qreal larger = qMax(d->to, d->from);
+ firstValue = qBound(smaller, firstValue, larger);
+ secondValue = qBound(smaller, secondValue, larger);
+
+ if (d->from > d->to) {
+ // If the from and to values are reversed, the secondValue
+ // might be less than the first value, which is not allowed.
+ if (secondValue > firstValue)
+ secondValue = firstValue;
+ } else {
+ // Otherwise, clamp first to second if it's too large.
+ if (firstValue > secondValue)
+ firstValue = secondValue;
+ }
+
+ // Then set both values. If they didn't change, no change signal will be emitted.
+ QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ if (firstValue != firstPrivate->value) {
+ firstPrivate->value = firstValue;
+ emit d->first->valueChanged();
+ }
+
+ QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ if (secondValue != secondPrivate->value) {
+ secondPrivate->value = secondValue;
+ emit d->second->valueChanged();
+ }
+
+ // After we've set both values, then we can update the positions.
+ // If we don't do this last, the positions may be incorrect.
+ firstPrivate->updatePosition(true);
+ secondPrivate->updatePosition();
+}
+
+/*!
+ \since QtQuick.Controls 2.2 (Qt 5.9)
+ \qmlproperty bool QtQuick.Controls::RangeSlider::live
+
+ This property holds whether the slider provides live updates for the \l first.value
+ and \l second.value properties while the respective handles are dragged.
+
+ The default value is \c true.
+
+ \sa first.value, second.value
+*/
+bool QQuickRangeSlider::live() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->live;
+}
+
+void QQuickRangeSlider::setLive(bool live)
+{
+ Q_D(QQuickRangeSlider);
+ if (d->live == live)
+ return;
+
+ d->live = live;
+ emit liveChanged();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal
+ \readonly
+
+ This property holds whether the slider is horizontal.
+
+ \sa orientation
+*/
+bool QQuickRangeSlider::isHorizontal() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->orientation == Qt::Horizontal;
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty bool QtQuick.Controls::RangeSlider::vertical
+ \readonly
+
+ This property holds whether the slider is vertical.
+
+ \sa orientation
+*/
+bool QQuickRangeSlider::isVertical() const
+{
+ Q_D(const QQuickRangeSlider);
+ return d->orientation == Qt::Vertical;
+}
+
+void QQuickRangeSlider::focusInEvent(QFocusEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::focusInEvent(event);
+
+ // The active focus ends up to RangeSlider when using forceActiveFocus()
+ // or QML KeyNavigation. We must forward the focus to one of the handles,
+ // because RangeSlider handles key events for the focused handle. If
+ // neither handle has active focus, RangeSlider doesn't do anything.
+ QQuickItem *handle = nextItemInFocusChain();
+ // QQuickItem::nextItemInFocusChain() only works as desired with
+ // Qt::TabFocusAllControls. otherwise pick the first handle
+ if (!handle || handle == this)
+ handle = d->first->handle();
+ if (handle)
+ handle->forceActiveFocus(event->reason());
+}
+
+void QQuickRangeSlider::keyPressEvent(QKeyEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::keyPressEvent(event);
+
+ QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus()
+ ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr);
+ if (!focusNode)
+ return;
+
+ const qreal oldValue = focusNode->value();
+ if (d->orientation == Qt::Horizontal) {
+ if (event->key() == Qt::Key_Left) {
+ focusNode->setPressed(true);
+ if (isMirrored())
+ focusNode->increase();
+ else
+ focusNode->decrease();
+ event->accept();
+ } else if (event->key() == Qt::Key_Right) {
+ focusNode->setPressed(true);
+ if (isMirrored())
+ focusNode->decrease();
+ else
+ focusNode->increase();
+ event->accept();
+ }
+ } else {
+ if (event->key() == Qt::Key_Up) {
+ focusNode->setPressed(true);
+ focusNode->increase();
+ event->accept();
+ } else if (event->key() == Qt::Key_Down) {
+ focusNode->setPressed(true);
+ focusNode->decrease();
+ event->accept();
+ }
+ }
+ if (!qFuzzyCompare(focusNode->value(), oldValue))
+ emit focusNode->moved();
+}
+
+void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::hoverEnterEvent(event);
+ d->updateHover(event->position());
+ event->ignore();
+}
+
+void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::hoverMoveEvent(event);
+ d->updateHover(event->position());
+ event->ignore();
+}
+
+void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::hoverLeaveEvent(event);
+ d->first->setHovered(false);
+ d->second->setHovered(false);
+ event->ignore();
+}
+
+void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::keyReleaseEvent(event);
+ d->first->setPressed(false);
+ d->second->setPressed(false);
+}
+
+void QQuickRangeSlider::mousePressEvent(QMouseEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::mousePressEvent(event);
+ d->handleMove(event->position(), event->timestamp());
+ setKeepMouseGrab(true);
+}
+
+#if QT_CONFIG(quicktemplates2_multitouch)
+void QQuickRangeSlider::touchEvent(QTouchEvent *event)
+{
+ Q_D(QQuickRangeSlider);
+ switch (event->type()) {
+ case QEvent::TouchUpdate:
+ for (const QTouchEvent::TouchPoint &point : event->points()) {
+ if (!d->acceptTouch(point))
+ continue;
+
+ switch (point.state()) {
+ case QEventPoint::Pressed:
+ d->handlePress(point.position(), event->timestamp());
+ break;
+ case QEventPoint::Updated:
+ if (!keepTouchGrab()) {
+ if (d->orientation == Qt::Horizontal)
+ setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().x() - point.pressPosition().x(), Qt::XAxis, &point, qRound(d->touchDragThreshold)));
+ else
+ setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().y() - point.pressPosition().y(), Qt::YAxis, &point, qRound(d->touchDragThreshold)));
+ }
+ if (keepTouchGrab())
+ d->handleMove(point.position(), event->timestamp());
+ break;
+ case QEventPoint::Released:
+ d->handleRelease(point.position(), event->timestamp());
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default:
+ QQuickControl::touchEvent(event);
+ break;
+ }
+}
+#endif
+
+void QQuickRangeSlider::mirrorChange()
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::mirrorChange();
+ emit d->first->visualPositionChanged();
+ emit d->second->visualPositionChanged();
+}
+
+void QQuickRangeSlider::classBegin()
+{
+ Q_D(QQuickRangeSlider);
+ QQuickControl::classBegin();
+
+ QQmlContext *context = qmlContext(this);
+ if (context) {
+ QQmlEngine::setContextForObject(d->first, context);
+ QQmlEngine::setContextForObject(d->second, context);
+ }
+}
+
+void QQuickRangeSlider::componentComplete()
+{
+ Q_D(QQuickRangeSlider);
+ QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ firstPrivate->executeHandle(true);
+ secondPrivate->executeHandle(true);
+
+ QQuickControl::componentComplete();
+
+ if (firstPrivate->isPendingValue || secondPrivate->isPendingValue
+ || !qFuzzyCompare(d->from, defaultFrom) || !qFuzzyCompare(d->to, defaultTo)) {
+ // Properties were set while we were loading. To avoid clamping issues that occur when setting the
+ // values of first and second overriding values set by the user, set them all at once at the end.
+ // Another reason that we must set these values here is that the from and to values might have made the old range invalid.
+ setValues(firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value,
+ secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value);
+
+ firstPrivate->pendingValue = 0;
+ firstPrivate->isPendingValue = false;
+ secondPrivate->pendingValue = 0;
+ secondPrivate->isPendingValue = false;
+ } else {
+ // If there was no pending data, we must still update the positions,
+ // as first.setValue()/second.setValue() won't be called as part of default construction.
+ // Don't need to ignore the second position when updating the first position here,
+ // as our default values are guaranteed to be valid.
+ firstPrivate->updatePosition();
+ secondPrivate->updatePosition();
+ }
+}
+
+/*!
+ \qmlmethod void QtQuick.Controls::RangeSlider::first.increase()
+
+ Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
+
+ \sa first
+*/
+
+/*!
+ \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease()
+
+ Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
+
+ \sa first
+*/
+
+/*!
+ \qmlmethod void QtQuick.Controls::RangeSlider::second.increase()
+
+ Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
+
+ \sa second
+*/
+
+/*!
+ \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease()
+
+ Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined.
+
+ \sa second
+*/
+
+#if QT_CONFIG(accessibility)
+QAccessible::Role QQuickRangeSlider::accessibleRole() const
+{
+ return QAccessible::Slider;
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "moc_qquickrangeslider_p.cpp"