// 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;

    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);
        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);

    setFlag(QQuickItem::ItemIsFocusScope);
    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 Application.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;

    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"
