diff options
34 files changed, 2068 insertions, 2 deletions
diff --git a/src/imports/controls/RangeSlider.qml b/src/imports/controls/RangeSlider.qml new file mode 100644 index 00000000..e301eed2 --- /dev/null +++ b/src/imports/controls/RangeSlider.qml @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.controls 1.0 +import Qt.labs.templates 1.0 as T + +T.RangeSlider { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + Math.max(track ? track.implicitWidth : 0, + first.handle ? first.handle.implicitWidth : 0, + second.handle ? second.handle.implicitWidth : 0) + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + Math.max(track ? track.implicitHeight : 0, + first.handle ? first.handle.implicitHeight : 0, + second.handle ? second.handle.implicitHeight : 0) + topPadding + bottomPadding) + + padding: 6 + + //! [firstHandle] + first.handle: Rectangle { + x: control.leftPadding + (horizontal ? control.first.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2) + y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : control.first.visualPosition * (control.availableHeight - height)) + implicitWidth: 20 + implicitHeight: 20 + radius: width / 2 + border.width: activeFocus ? 2 : 1 + border.color: activeFocus ? control.Theme.focusColor : control.Theme.frameColor + color: control.Theme.backgroundColor + + readonly property bool horizontal: control.orientation === Qt.Horizontal + + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: 12 + height: 12 + radius: width / 2 + + color: Qt.tint(!control.enabled ? control.Theme.disabledColor : + parent.activeFocus ? control.Theme.focusColor : control.Theme.accentColor, + control.first.pressed ? control.Theme.pressColor : "transparent") + } + } + //! [firstHandle] + + //! [secondHandle] + second.handle: Rectangle { + x: control.leftPadding + (horizontal ? control.second.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2) + y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : control.second.visualPosition * (control.availableHeight - height)) + implicitWidth: 20 + implicitHeight: 20 + radius: width / 2 + border.width: activeFocus ? 2 : 1 + border.color: activeFocus ? control.Theme.focusColor : control.Theme.frameColor + color: control.Theme.backgroundColor + + readonly property bool horizontal: control.orientation === Qt.Horizontal + + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: 12 + height: 12 + radius: width / 2 + + color: Qt.tint(!control.enabled ? control.Theme.disabledColor : + parent.activeFocus ? control.Theme.focusColor : control.Theme.accentColor, + control.second.pressed ? control.Theme.pressColor : "transparent") + } + } + //! [secondHandle] + + //! [track] + track: Rectangle { + x: control.leftPadding + (horizontal ? 0 : (control.availableWidth - width) / 2) + y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : 0) + implicitWidth: horizontal ? 200 : 6 + implicitHeight: horizontal ? 6 : 200 + width: horizontal ? control.availableWidth : implicitWidth + height: horizontal ? implicitHeight : control.availableHeight + + readonly property bool horizontal: control.orientation === Qt.Horizontal + + radius: 3 + border.color: control.Theme.frameColor + color: control.Theme.backgroundColor + scale: horizontal && control.mirrored ? -1 : 1 + + Rectangle { + x: parent.horizontal ? control.first.position * parent.width : 2 + y: parent.horizontal ? 2 : control.second.visualPosition * parent.height + 2 + width: parent.horizontal ? control.second.position * parent.width - control.first.position * parent.width - 4 : 2 + height: parent.horizontal ? 2 : control.second.position * parent.height - control.first.position * parent.height - 4 + + radius: 3 + color: control.enabled ? control.Theme.accentColor : control.Theme.disabledColor + } + } + //! [track] +} diff --git a/src/imports/controls/controls.pri b/src/imports/controls/controls.pri index ee9c2e63..41651a24 100644 --- a/src/imports/controls/controls.pri +++ b/src/imports/controls/controls.pri @@ -11,6 +11,7 @@ QML_FILES = \ PageIndicator.qml \ ProgressBar.qml \ RadioButton.qml \ + RangeSlider.qml \ ScrollBar.qml \ ScrollIndicator.qml \ Slider.qml \ diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-background.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-background.png Binary files differnew file mode 100644 index 00000000..5b3c603b --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-background.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png Binary files differnew file mode 100644 index 00000000..0194a240 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle-focused.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle-focused.png Binary files differnew file mode 100644 index 00000000..390a6399 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle-focused.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png Binary files differnew file mode 100644 index 00000000..e04ed6df --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png Binary files differnew file mode 100644 index 00000000..7032728a --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle-focused.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle-focused.png Binary files differnew file mode 100644 index 00000000..803f8141 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle-focused.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png Binary files differnew file mode 100644 index 00000000..0fe3d630 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png Binary files differnew file mode 100644 index 00000000..685a9d44 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif b/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif Binary files differnew file mode 100644 index 00000000..afd909f3 --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif diff --git a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc index ec3bf839..89ff8456 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc @@ -207,6 +207,38 @@ \snippet RadioButton.qml indicator + \section1 Customizing RangeSlider + + RangeSlider consists of four visual items: + \l {Control::background}{background}, \l {RangeSlider::track}{track}, + \l {RangeSlider::first}{first.handle} and + \l {RangeSlider::second.handle}{second.handle}. + + \section3 Background + + \image qtlabscontrols-rangeslider-background.png + + RangeSlider has no background item by default. + + \section3 Track + + \image qtlabscontrols-rangeslider-track.png + + \snippet RangeSlider.qml track + + \section3 First Handle + + \image qtlabscontrols-rangeslider-first-handle.png + + \snippet RangeSlider.qml firstHandle + + \section3 Second Handle + + \image qtlabscontrols-rangeslider-second-handle.png + + \snippet RangeSlider.qml secondHandle + + \section1 Customizing ScrollBar ScrollBar consists of two visual items: \l {Control::background}{background} diff --git a/src/imports/controls/qtlabscontrolsplugin.cpp b/src/imports/controls/qtlabscontrolsplugin.cpp index d1d88be2..8d042381 100644 --- a/src/imports/controls/qtlabscontrolsplugin.cpp +++ b/src/imports/controls/qtlabscontrolsplugin.cpp @@ -81,6 +81,7 @@ void QtLabsControlsPlugin::registerTypes(const char *uri) qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/PageIndicator.qml"))), uri, 1, 0, "PageIndicator"); qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/ProgressBar.qml"))), uri, 1, 0, "ProgressBar"); qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/RadioButton.qml"))), uri, 1, 0, "RadioButton"); + qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/RangeSlider.qml"))), uri, 1, 0, "RangeSlider"); qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/ScrollBar.qml"))), uri, 1, 0, "ScrollBar"); qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/ScrollIndicator.qml"))), uri, 1, 0, "ScrollIndicator"); qmlRegisterType(selector.select(QUrl(base + QStringLiteral("/Slider.qml"))), uri, 1, 0, "Slider"); diff --git a/src/imports/templates/qtlabstemplatesplugin.cpp b/src/imports/templates/qtlabstemplatesplugin.cpp index 9ebd14d9..51c15dd9 100644 --- a/src/imports/templates/qtlabstemplatesplugin.cpp +++ b/src/imports/templates/qtlabstemplatesplugin.cpp @@ -50,6 +50,7 @@ #include <QtLabsTemplates/private/qquickpageindicator_p.h> #include <QtLabsTemplates/private/qquickprogressbar_p.h> #include <QtLabsTemplates/private/qquickradiobutton_p.h> +#include <QtLabsTemplates/private/qquickrangeslider_p.h> #include <QtLabsTemplates/private/qquickscrollbar_p.h> #include <QtLabsTemplates/private/qquickscrollindicator_p.h> #include <QtLabsTemplates/private/qquickslider_p.h> @@ -91,6 +92,8 @@ void QtLabsTemplatesPlugin::registerTypes(const char *uri) qmlRegisterType<QQuickPageIndicator>(uri, 1, 0, "PageIndicator"); qmlRegisterType<QQuickProgressBar>(uri, 1, 0, "ProgressBar"); qmlRegisterType<QQuickRadioButton>(uri, 1, 0, "RadioButton"); + qmlRegisterType<QQuickRangeSlider>(uri, 1, 0, "RangeSlider"); + qmlRegisterType<QQuickRangeSliderNode>(); qmlRegisterType<QQuickScrollBar>(uri, 1, 0, "ScrollBar"); qmlRegisterType<QQuickScrollIndicator>(uri, 1, 0, "ScrollIndicator"); qmlRegisterType<QQuickSlider>(uri, 1, 0, "Slider"); diff --git a/src/templates/qquickrangeslider.cpp b/src/templates/qquickrangeslider.cpp new file mode 100644 index 00000000..227402f4 --- /dev/null +++ b/src/templates/qquickrangeslider.cpp @@ -0,0 +1,885 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickrangeslider_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtCore/qscopedpointer.h> +#include <QtQuick/private/qquickwindow_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RangeSlider + \inherits Control + \instantiates QQuickRangeSlider + \inqmlmodule Qt.labs.controls + \ingroup sliders + \brief A slider control used to select a range of values. + + \image qtlabscontrols-rangeslider.gif + + RangeSlider is used to select a range specified by two values, by sliding + each handle along a track. + + \table + \row \li \image qtlabscontrols-rangeslider-normal.png + \li A range slider in its normal state. + \row \li \image qtlabscontrols-rangeslider-first-handle-focused.png + \li A range slider whose first handle has active focus. + \row \li \image qtlabscontrols-rangeslider-second-handle-focused.png + \li A range slider whose second handle has active focus. + \row \li \image qtlabscontrols-rangeslider-disabled.png + \li A range slider that is disabled. + \endtable + + \code + RangeSlider { + first.value: 0.25 + second.value: 0.75 + } + \endcode + + \sa {Customizing RangeSlider} +*/ + +class QQuickRangeSliderNodePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickRangeSliderNode) +public: + QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider) : + value(value), + isPendingValue(false), + pendingValue(0), + position(0), + handle(Q_NULLPTR), + slider(slider), + pressed(false) + { + } + + bool isFirst() const; + + void setPosition(qreal position, bool ignoreOtherPosition = false); + void updatePosition(bool ignoreOtherPosition = false); + + static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node); + +private: + friend class QQuickRangeSlider; + + qreal value; + bool isPendingValue; + qreal pendingValue; + qreal position; + QQuickItem *handle; + QQuickRangeSlider *slider; + bool pressed; +}; + +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(0.0, slider->first()->position()); + const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin(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); +} + +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 +{ + Q_D(const QQuickRangeSliderNode); + return d->handle; +} + +void QQuickRangeSliderNode::setHandle(QQuickItem *handle) +{ + Q_D(QQuickRangeSliderNode); + if (d->handle != handle) { + delete d->handle; + d->handle = handle; + if (handle) { + if (!handle->parentItem()) + handle->setParentItem(d->slider); + + QQuickItem *firstHandle = d->slider->first()->handle(); + QQuickItem *secondHandle = 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); + } + 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) { + d->pressed = pressed; + d->slider->setAccessibleProperty("pressed", pressed || d->slider->second()->isPressed()); + emit pressedChanged(); + } +} + +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: + QQuickRangeSliderPrivate() : + from(defaultFrom), + to(defaultTo), + stepSize(0), + first(Q_NULLPTR), + second(Q_NULLPTR), + orientation(Qt::Horizontal), + snapMode(QQuickRangeSlider::NoSnap), + track(Q_NULLPTR) + { + } + + qreal from; + qreal to; + qreal stepSize; + QQuickRangeSliderNode *first; + QQuickRangeSliderNode *second; + QPoint pressPoint; + Qt::Orientation orientation; + QQuickRangeSlider::SnapMode snapMode; + QQuickItem *track; +}; + +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 stepSize = slider->stepSize(); + if (qFuzzyIsNull(stepSize)) + return position; + return qRound(position / stepSize) * stepSize; +} + +static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPoint &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; +} + +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); + + setAcceptedMouseButtons(Qt::LeftButton); + setAccessibleRole(0x00000033); //QAccessible::Slider + setFlag(QQuickItem::ItemIsFocusScope); +} + +/*! + \qmlproperty real Qt.labs.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()); + } +} + +/*! + \qmlproperty real Qt.labs.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()); + } +} + +/*! + \qmlpropertygroup Qt.labs.controls::RangeSlider::first + \qmlproperty real Qt.labs.controls::RangeSlider::first.value + \qmlproperty real Qt.labs.controls::RangeSlider::first.position + \qmlproperty real Qt.labs.controls::RangeSlider::first.visualPosition + \qmlproperty Item Qt.labs.controls::RangeSlider::first.handle + \qmlproperty bool Qt.labs.controls::RangeSlider::first.pressed + \qmlmethod void Qt.labs.controls::RangeSlider::first.increase() + \qmlmethod void Qt.labs.controls::RangeSlider::first.decrease() + + \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 to is greater than \l from, the value of the first handle + must be greater than the second, and vice versa. + + Unlike \l {first.position}{position}, value is not updated while the + handle is dragged, but rather when it has been released. + + 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 defined as a percentage of the control's size, scaled to + \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 defined as a percentage of the control's size, scaled + to \c {0.0 - 1.0}. Unlike \l {first.value}{value}, position is + continuously updated while the handle is dragged. 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. + \endtable + + \table + \header + \li Function + \li Description + \row + \li increase + \li Increases the value of the handle by \l stepSize, or \c 0.1 if + stepSize is not defined. + \row + \li decrease + \li Decreases the value of the handle by \l stepSize, or \c 0.1 if + stepSize is not defined. + \endtable +*/ +QQuickRangeSliderNode *QQuickRangeSlider::first() const +{ + Q_D(const QQuickRangeSlider); + return d->first; +} + +/*! + \qmlpropertygroup Qt.labs.controls::RangeSlider::second + \qmlproperty real Qt.labs.controls::RangeSlider::second.value + \qmlproperty real Qt.labs.controls::RangeSlider::second.position + \qmlproperty real Qt.labs.controls::RangeSlider::second.visualPosition + \qmlproperty Item Qt.labs.controls::RangeSlider::second.handle + \qmlproperty bool Qt.labs.controls::RangeSlider::second.pressed + \qmlmethod void Qt.labs.controls::RangeSlider::second.increase() + \qmlmethod void Qt.labs.controls::RangeSlider::second.decrease() + + \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 to is greater than \l from, the value of the first handle + must be greater than the second, and vice versa. + + Unlike \l {second.position}{position}, value is not updated while the + handle is dragged, but rather when it has been released. + + 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 defined as a percentage of the control's size, scaled to + \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 defined as a percentage of the control's size, scaled + to \c {0.0 - 1.0}. Unlike \l {second.value}{value}, position is + continuously updated while the handle is dragged. 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. + \endtable + + \table + \header + \li Function + \li Description + \row + \li increase + \li Increases the value of the handle by \l stepSize, or \c 0.1 if + stepSize is not defined. + \row + \li decrease + \li Decreases the value of the handle by \l stepSize, or \c 0.1 if + stepSize is not defined. + \endtable +*/ +QQuickRangeSliderNode *QQuickRangeSlider::second() const +{ + Q_D(const QQuickRangeSlider); + return d->second; +} + +/*! + \qmlproperty real Qt.labs.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)) { + d->stepSize = step; + emit stepSizeChanged(); + } +} + +/*! + \qmlproperty enumeration Qt.labs.controls::RangeSlider::snapMode + + This property holds the snap mode. + + 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. + + \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) { + d->snapMode = mode; + emit snapModeChanged(); + } +} + +/*! + \qmlproperty enumeration Qt.labs.controls::RangeSlider::orientation + + This property holds the orientation. + + Possible values: + \value Qt.Horizontal Horizontal (default) + \value Qt.Vertical 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) { + d->orientation = orientation; + emit orientationChanged(); + } +} + +/*! + \qmlproperty Item Qt.labs.controls::RangeSlider::track + + This property holds the track item. + + \sa {Customizing Slider} +*/ +QQuickItem *QQuickRangeSlider::track() const +{ + Q_D(const QQuickRangeSlider); + return d->track; +} + +void QQuickRangeSlider::setTrack(QQuickItem *track) +{ + Q_D(QQuickRangeSlider); + if (d->track != track) { + delete d->track; + d->track = track; + if (track && !track->parentItem()) + track->setParentItem(this); + emit trackChanged(); + } +} + +/*! + \qmlmethod void Qt.labs.controls::RangeSlider::setValues(real firstValue, real secondValue) + + Sets \l first.value and \l second.value with the given arguments. + + If \a to is larger than \a from and \a firstValue is larger than + \a secondValue, \a firstValue will be clamped to \a secondValue. + + If \a from is larger than \a to and \a secondValue is larger than + \a firstValue, \a secondValue will be clamped to \a 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(); +} + +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 : Q_NULLPTR); + if (!focusNode) + return; + + 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(); + } + } +} + +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->pressPoint = event->pos(); + + QQuickItem *firstHandle = d->first->handle(); + QQuickItem *secondHandle = d->second->handle(); + const bool firstHit = firstHandle && firstHandle->contains(mapToItem(firstHandle, d->pressPoint)); + const bool secondHit = secondHandle && secondHandle->contains(mapToItem(secondHandle, d->pressPoint)); + QQuickRangeSliderNode *hitNode = Q_NULLPTR; + QQuickRangeSliderNode *otherNode = Q_NULLPTR; + + if (firstHit && secondHit) { + // choose highest + hitNode = firstHandle->z() > secondHandle->z() ? d->first : d->second; + otherNode = firstHandle->z() > secondHandle->z() ? d->second : d->first; + } else if (firstHit) { + hitNode = d->first; + otherNode = d->second; + } else if (secondHit) { + hitNode = d->second; + otherNode = d->first; + } + + if (hitNode) { + hitNode->setPressed(true); + hitNode->handle()->setZ(1); + } + if (otherNode) + otherNode->handle()->setZ(0); +} + +void QQuickRangeSlider::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::mouseMoveEvent(event); + if (!keepMouseGrab()) { + if (d->orientation == Qt::Horizontal) + setKeepMouseGrab(QQuickWindowPrivate::dragOverThreshold(event->pos().x() - d->pressPoint.x(), Qt::XAxis, event)); + else + setKeepMouseGrab(QQuickWindowPrivate::dragOverThreshold(event->pos().y() - d->pressPoint.y(), Qt::YAxis, event)); + } + if (keepMouseGrab()) { + QQuickRangeSliderNode *pressedNode = d->first->isPressed() ? d->first : (d->second->isPressed() ? d->second : Q_NULLPTR); + if (pressedNode) { + qreal pos = positionAt(this, pressedNode->handle(), event->pos()); + if (d->snapMode == SnapAlways) + pos = snapPosition(this, pos); + QQuickRangeSliderNodePrivate::get(pressedNode)->setPosition(pos); + } + } +} + +void QQuickRangeSlider::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::mouseReleaseEvent(event); + + d->pressPoint = QPoint(); + if (!keepMouseGrab()) + return; + + QQuickRangeSliderNode *pressedNode = d->first->isPressed() ? d->first : (d->second->isPressed() ? d->second : Q_NULLPTR); + if (!pressedNode) + return; + + qreal pos = positionAt(this, pressedNode->handle(), event->pos()); + if (d->snapMode != NoSnap) + pos = snapPosition(this, pos); + pressedNode->setValue(valueAt(this, pos)); + setKeepMouseGrab(false); + pressedNode->setPressed(false); +} + +void QQuickRangeSlider::mouseUngrabEvent() +{ + Q_D(QQuickRangeSlider); + QQuickControl::mouseUngrabEvent(); + d->pressPoint = QPoint(); + d->first->setPressed(false); + d->second->setPressed(false); +} + +void QQuickRangeSlider::mirrorChange() +{ + Q_D(QQuickRangeSlider); + QQuickControl::mirrorChange(); + emit d->first->visualPositionChanged(); + emit d->second->visualPositionChanged(); +} + +void QQuickRangeSlider::componentComplete() +{ + Q_D(QQuickRangeSlider); + QQuickControl::componentComplete(); + + QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first); + QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second); + + 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(); + } +} + +QT_END_NAMESPACE diff --git a/src/templates/qquickrangeslider_p.h b/src/templates/qquickrangeslider_p.h new file mode 100644 index 00000000..2761813a --- /dev/null +++ b/src/templates/qquickrangeslider_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKRANGESLIDER_H +#define QQUICKRANGESLIDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLabsTemplates/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickRangeSliderPrivate; +class QQuickRangeSliderNode; + +class Q_LABSTEMPLATES_EXPORT QQuickRangeSlider : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(qreal to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(QQuickRangeSliderNode *first READ first CONSTANT) + Q_PROPERTY(QQuickRangeSliderNode *second READ second CONSTANT) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(QQuickItem *track READ track WRITE setTrack NOTIFY trackChanged FINAL) + +public: + explicit QQuickRangeSlider(QQuickItem *parent = Q_NULLPTR); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + QQuickRangeSliderNode *first() const; + QQuickRangeSliderNode *second() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + QQuickItem *track() const; + void setTrack(QQuickItem *track); + + Q_INVOKABLE void setValues(qreal firstValue, qreal secondValue); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void stepSizeChanged(); + void snapModeChanged(); + void orientationChanged(); + void trackChanged(); + +protected: + void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; + void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseUngrabEvent() Q_DECL_OVERRIDE; + void mirrorChange() Q_DECL_OVERRIDE; + void componentComplete() Q_DECL_OVERRIDE; + +private: + friend class QQuickRangeSliderNode; + + Q_DISABLE_COPY(QQuickRangeSlider) + Q_DECLARE_PRIVATE(QQuickRangeSlider) +}; + +Q_DECLARE_TYPEINFO(QQuickRangeSlider, Q_COMPLEX_TYPE); + +class QQuickRangeSliderNodePrivate; + +class Q_LABSTEMPLATES_EXPORT QQuickRangeSliderNode : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + +public: + explicit QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider); + ~QQuickRangeSliderNode(); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + qreal visualPosition() const; + + QQuickItem *handle() const; + void setHandle(QQuickItem *handle); + + bool isPressed() const; + void setPressed(bool pressed); + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void valueChanged(); + void positionChanged(); + void visualPositionChanged(); + void handleChanged(); + void pressedChanged(); + +private: + Q_DISABLE_COPY(QQuickRangeSliderNode) + Q_DECLARE_PRIVATE(QQuickRangeSliderNode) +}; + +Q_DECLARE_TYPEINFO(QQuickRangeSliderNode, Q_COMPLEX_TYPE); + +QT_END_NAMESPACE + +#endif // QQUICKRANGESLIDER_H diff --git a/src/templates/templates.pri b/src/templates/templates.pri index 9ba8bbd4..48405c42 100644 --- a/src/templates/templates.pri +++ b/src/templates/templates.pri @@ -23,6 +23,7 @@ HEADERS += \ $$PWD/qquickpressandholdhelper_p.h \ $$PWD/qquickprogressbar_p.h \ $$PWD/qquickradiobutton_p.h \ + $$PWD/qquickrangeslider_p.h \ $$PWD/qquickscrollbar_p.h \ $$PWD/qquickscrollindicator_p.h \ $$PWD/qquickslider_p.h \ @@ -58,6 +59,7 @@ SOURCES += \ $$PWD/qquickpressandholdhelper.cpp \ $$PWD/qquickprogressbar.cpp \ $$PWD/qquickradiobutton.cpp \ + $$PWD/qquickrangeslider.cpp \ $$PWD/qquickscrollbar.cpp \ $$PWD/qquickscrollindicator.cpp \ $$PWD/qquickslider.cpp \ diff --git a/tests/auto/accessibility/data/rangeslider.qml b/tests/auto/accessibility/data/rangeslider.qml new file mode 100644 index 00000000..9262f3cd --- /dev/null +++ b/tests/auto/accessibility/data/rangeslider.qml @@ -0,0 +1,18 @@ +import QtQuick 2.5 +import QtQuick.Window 2.2 +import Qt.labs.controls 1.0 + +Window { + visible: true + + RangeSlider { + id: rangeSlider + objectName: "rangeslider" + from: 0 + to: 100 + first.value: 25 + second.value: 75 + stepSize: 1 + orientation: "Horizontal" + } +} diff --git a/tests/auto/accessibility/tst_accessibility.cpp b/tests/auto/accessibility/tst_accessibility.cpp index 857cefd2..7baec700 100644 --- a/tests/auto/accessibility/tst_accessibility.cpp +++ b/tests/auto/accessibility/tst_accessibility.cpp @@ -78,6 +78,7 @@ void tst_accessibility::a11y_data() QTest::newRow("PageIndicator") << "pageindicator" << 0x00000027 << ""; //QAccessible::Indicator QTest::newRow("ProgressBar") << "progressbar" << 0x00000030 << ""; //QAccessible::ProgressBar QTest::newRow("RadioButton") << "radiobutton" << 0x0000002D << "RadioButton"; //QAccessible::RadioButton + QTest::newRow("RangeSlider") << "rangeslider" << 0x00000033 << ""; //QAccessible::Slider QTest::newRow("ScrollBar") << "scrollbar" << 0x00000003 << ""; //QAccessible::ScrollBar QTest::newRow("ScrollIndicator") << "scrollindicator" << 0x00000027 << ""; //QAccessible::Indicator QTest::newRow("Slider") << "slider" << 0x00000033 << ""; //QAccessible::Slider diff --git a/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml index 05cb464b..5ab0191e 100644 --- a/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml +++ b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml @@ -127,6 +127,10 @@ Item { } } } + RangeSlider { + id: rangeslider + objectName: "rangeslider" + } // ScrollBar ScrollIndicator { id: scrollindicator diff --git a/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp index 79532b45..88607514 100644 --- a/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp +++ b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp @@ -162,7 +162,27 @@ void tst_activeFocusOnTab::allControls() QVERIFY(item); QVERIFY(item->hasActiveFocus()); - // Tab: radiobutton2->slider + // Tab: radiobutton2->rangeslider.first.handle + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "rangeslider"); + QVERIFY(item); + item = item->property("first").value<QObject*>()->property("handle").value<QQuickItem*>(); + QVERIFY(item->hasActiveFocus()); + + // Tab: rangeslider.first.handle->rangeslider.second.handle + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "rangeslider"); + QVERIFY(item); + item = item->property("second").value<QObject*>()->property("handle").value<QQuickItem*>(); + QVERIFY(item->hasActiveFocus()); + + // Tab: rangeslider.second.handle->slider key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); QGuiApplication::sendEvent(window, &key); QVERIFY(key.isAccepted()); @@ -279,7 +299,27 @@ void tst_activeFocusOnTab::allControls() QVERIFY(item); QVERIFY(item->hasActiveFocus()); - // BackTab: slider->radiobutton2 + // BackTab: slider->rangeslider.second.handle + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "rangeslider"); + QVERIFY(item); + item = item->property("second").value<QObject*>()->property("handle").value<QQuickItem*>(); + QVERIFY(item->hasActiveFocus()); + + // BackTab: rangeslider.second.handle->rangeslider.first.handle + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "rangeslider"); + QVERIFY(item); + item = item->property("first").value<QObject*>()->property("handle").value<QQuickItem*>(); + QVERIFY(item->hasActiveFocus()); + + // BackTab: rangeslider.first.handle->radiobutton2 key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); QGuiApplication::sendEvent(window, &key); QVERIFY(key.isAccepted()); diff --git a/tests/auto/controls/data/tst_rangeslider.qml b/tests/auto/controls/data/tst_rangeslider.qml new file mode 100644 index 00000000..345013f2 --- /dev/null +++ b/tests/auto/controls/data/tst_rangeslider.qml @@ -0,0 +1,580 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import Qt.labs.controls 1.0 + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "RangeSlider" + + SignalSpy { + id: firstPressedSpy + signalName: "pressedChanged" + } + + SignalSpy { + id: secondPressedSpy + signalName: "pressedChanged" + } + + Component { + id: sliderComponent + RangeSlider { + id: slider + + Component.onCompleted: { + first.handle.objectName = "firstHandle" + second.handle.objectName = "secondHandle" + } + + Text { + text: "1" + parent: slider.first.handle + anchors.centerIn: parent + } + + Text { + text: "2" + parent: slider.second.handle + anchors.centerIn: parent + } + } + } + + function init() { + verify(!firstPressedSpy.target) + compare(firstPressedSpy.count, 0) + + verify(!secondPressedSpy.target) + compare(secondPressedSpy.count, 0) + } + + function cleanup() { + firstPressedSpy.target = null + firstPressedSpy.clear() + secondPressedSpy.target = null + secondPressedSpy.clear() + } + + function test_defaults() { + var control = sliderComponent.createObject(testCase) + verify(control) + + compare(control.stepSize, 0) + compare(control.snapMode, RangeSlider.NoSnap) + compare(control.orientation, Qt.Horizontal) + + control.destroy() + } + + function test_values() { + var control = sliderComponent.createObject(testCase) + verify(control) + + compare(control.first.value, 0.0) + compare(control.second.value, 1.0) + control.first.value = 0.5 + compare(control.first.value, 0.5) + control.first.value = 1.0 + compare(control.first.value, 1.0) + control.first.value = -1.0 + compare(control.first.value, 0.0) + control.first.value = 2.0 + compare(control.first.value, 1.0) + + control.first.value = 0 + compare(control.first.value, 0.0) + control.second.value = 0.5 + compare(control.second.value, 0.5) + control.first.value = 1 + compare(control.first.value, 0.5) + control.second.value = 0 + compare(control.second.value, 0.5) + + control.destroy() + } + + function test_range() { + var control = sliderComponent.createObject(testCase, { from: 0, to: 100, "first.value": 50, "second.value": 100 }) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.first.value, 50) + compare(control.second.value, 100) + compare(control.first.position, 0.5) + compare(control.second.position, 1.0) + + control.first.value = 1000 + compare(control.first.value, 100) + compare(control.first.position, 1.0) + + control.first.value = -1 + compare(control.first.value, 0) + compare(control.first.position, 0) + + control.from = 25 + compare(control.from, 25) + compare(control.first.value, 25) + compare(control.first.position, 0) + + control.to = 75 + compare(control.to, 75) + compare(control.second.value, 75) + compare(control.second.position, 1.0) + + control.first.value = 50 + compare(control.first.value, 50) + compare(control.first.position, 0.5) + + control.destroy() + } + + function test_setValues() { + var control = sliderComponent.createObject(testCase) + verify(control) + + compare(control.from, 0) + compare(control.to, 1) + compare(control.first.value, 0) + compare(control.second.value, 1) + compare(control.first.position, 0.0) + compare(control.second.position, 1.0) + + control.setValues(100, 200) + compare(control.first.value, 1) + compare(control.second.value, 1) + compare(control.first.position, 1.0) + compare(control.second.position, 1.0) + + control.to = 300; + control.setValues(100, 200) + compare(control.first.value, 100) + compare(control.second.value, 200) + compare(control.first.position, 0.333333) + compare(control.second.position, 0.666666) + + control.destroy() + } + + function test_inverted() { + var control = sliderComponent.createObject(testCase, { from: 1.0, to: -1.0 }) + verify(control) + + compare(control.from, 1.0) + compare(control.to, -1.0) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = 2.0 + compare(control.first.value, 1.0) + compare(control.first.position, 0.0) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = -2.0 + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = 0.0 + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.destroy() + } + + function test_visualPosition() { + var control = sliderComponent.createObject(testCase) + verify(control) + + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 0.0) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, 1.0) + + control.first.value = 0.25 + compare(control.first.value, 0.25) + compare(control.first.position, 0.25) + compare(control.first.visualPosition, 0.25) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, 1.0) + + control.layoutDirection = Qt.RightToLeft + compare(control.first.visualPosition, 0.75) + compare(control.second.visualPosition, 0.0) + + control.LayoutMirroring.enabled = true + compare(control.first.visualPosition, 0.25) + compare(control.second.visualPosition, 1.0) + + control.layoutDirection = Qt.LeftToRight + compare(control.first.visualPosition, 0.75) + compare(control.second.visualPosition, 0.0) + + control.LayoutMirroring.enabled = false + compare(control.first.visualPosition, 0.25) + compare(control.second.visualPosition, 1.0) + + control.destroy() + } + + function test_orientation() { + var control = sliderComponent.createObject(testCase) + verify(control) + + compare(control.orientation, Qt.Horizontal) + verify(control.width > control.height) + control.orientation = Qt.Vertical + compare(control.orientation, Qt.Vertical) + verify(control.width < control.height) + + control.destroy() + } + + function test_mouse_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal }, + { tag: "vertical", orientation: Qt.Vertical } + ] + } + + function test_mouse(data) { + var control = sliderComponent.createObject(testCase, { orientation: data.orientation }) + verify(control) + + firstPressedSpy.target = control.first + verify(firstPressedSpy.valid) + + secondPressedSpy.target = control.second + verify(secondPressedSpy.valid) + + mousePress(control, control.width * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mouseRelease(control, control.width * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mousePress(control, 0, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mouseRelease(control, 0, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + var horizontal = control.orientation === Qt.Horizontal + var toX = horizontal ? control.width * 0.5 : control.first.handle.x + var toY = horizontal ? control.first.handle.y : control.height * 0.5 + mouseMove(control, toX, toY, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(secondPressedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, horizontal ? 1.0 : 0.0) + + control.destroy() + } + + function test_overlappingHandles() { + var control = sliderComponent.createObject(testCase, { orientation: data.orientation }) + verify(control) + + // By default, we force the second handle to be after the first in + // terms of stacking order *and* z value. + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + control.first.value = 0 + control.second.value = 0 + + // Both are at the same position, so it doesn't matter whose coordinates we use. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.second.pressed) + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + + // Move the second handle out of the way. + mouseMove(control, control.width, control.first.handle.y, Qt.LeftButton) + mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton) + verify(!control.second.pressed) + compare(control.second.value, 1.0) + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + + // Move the first handle on top of the second. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + mouseMove(control, control.width, control.first.handle.y, Qt.LeftButton) + mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton) + verify(!control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + // The most recently pressed handle (the first) should have the higher z value. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + mouseRelease(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(!control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + control.destroy() + } + + function test_keys_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, decrease: Qt.Key_Left, increase: Qt.Key_Right }, + { tag: "vertical", orientation: Qt.Vertical, decrease: Qt.Key_Down, increase: Qt.Key_Up } + ] + } + + function test_keys(data) { + var control = sliderComponent.createObject(testCase, { orientation: data.orientation }) + verify(control) + + var pressedCount = 0 + + firstPressedSpy.target = control.first + verify(firstPressedSpy.valid) + + control.first.handle.forceActiveFocus() + verify(control.first.handle.activeFocus) + + control.first.value = 0.5 + + for (var d1 = 1; d1 <= 10; ++d1) { + keyPress(data.decrease) + compare(control.first.pressed, true) + compare(firstPressedSpy.count, ++pressedCount) + + compare(control.first.value, Math.max(0.0, 0.5 - d1 * 0.1)) + compare(control.first.value, control.first.position) + + keyRelease(data.decrease) + compare(control.first.pressed, false) + compare(firstPressedSpy.count, ++pressedCount) + } + + for (var i1 = 1; i1 <= 20; ++i1) { + keyPress(data.increase) + compare(control.first.pressed, true) + compare(firstPressedSpy.count, ++pressedCount) + + compare(control.first.value, Math.min(1.0, 0.0 + i1 * 0.1)) + compare(control.first.value, control.first.position) + + keyRelease(data.increase) + compare(control.first.pressed, false) + compare(firstPressedSpy.count, ++pressedCount) + } + + control.first.value = 0; + control.stepSize = 0.25 + + pressedCount = 0; + secondPressedSpy.target = control.second + verify(secondPressedSpy.valid) + + control.second.handle.forceActiveFocus() + verify(control.second.handle.activeFocus) + + for (var d2 = 1; d2 <= 10; ++d2) { + keyPress(data.decrease) + compare(control.second.pressed, true) + compare(secondPressedSpy.count, ++pressedCount) + + compare(control.second.value, Math.max(0.0, 1.0 - d2 * 0.25)) + compare(control.second.value, control.second.position) + + keyRelease(data.decrease) + compare(control.second.pressed, false) + compare(secondPressedSpy.count, ++pressedCount) + } + + for (var i2 = 1; i2 <= 10; ++i2) { + keyPress(data.increase) + compare(control.second.pressed, true) + compare(secondPressedSpy.count, ++pressedCount) + + compare(control.second.value, Math.min(1.0, 0.0 + i2 * 0.25)) + compare(control.second.value, control.second.position) + + keyRelease(data.increase) + compare(control.second.pressed, false) + compare(secondPressedSpy.count, ++pressedCount) + } + + control.destroy() + } + + function test_padding() { + // test with "unbalanced" paddings (left padding != right padding) to ensure + // that the slider position calculation is done taking padding into account + // ==> the position is _not_ 0.5 in the middle of the control + var control = sliderComponent.createObject(testCase, { leftPadding: 10, rightPadding: 20 }) + verify(control) + + firstPressedSpy.target = control.first + verify(firstPressedSpy.valid) + + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 0.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + verify(control.first.position > 0.5) + verify(control.first.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 2) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + // RTL + control.first.value = 0 + control.layoutDirection = Qt.RightToLeft + + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 1.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0, Qt.LeftButton) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + verify(control.first.position < 0.5) + verify(control.first.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 4) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + control.destroy() + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-background.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-background.qml new file mode 100644 index 00000000..88a97a70 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-background.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + background: Rectangle { + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-disabled.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-disabled.qml new file mode 100644 index 00000000..648e283d --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-disabled.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + enabled: false +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle-focused.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle-focused.qml new file mode 100644 index 00000000..03241df2 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle-focused.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + first.handle.focus: true +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle.qml new file mode 100644 index 00000000..a9a28d90 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + Rectangle { + anchors.fill: first.handle + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-normal.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-normal.qml new file mode 100644 index 00000000..51cdacd2 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-normal.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle-focused.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle-focused.qml new file mode 100644 index 00000000..c820ed66 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle-focused.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + second.handle.focus: true +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle.qml new file mode 100644 index 00000000..5edce3f1 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + Rectangle { + anchors.fill: second.handle + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-rangeslider-track.qml b/tests/auto/snippets/data/qtlabscontrols-rangeslider-track.qml new file mode 100644 index 00000000..b39e7541 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-rangeslider-track.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +RangeSlider { + first.value: 0.25 + second.value: 0.75 + Rectangle { + anchors.fill: track + color: "transparent" + border.color: "red" + } +} diff --git a/tests/benchmarks/objectcount/tst_objectcount.cpp b/tests/benchmarks/objectcount/tst_objectcount.cpp index 7d419c81..3b5db87d 100644 --- a/tests/benchmarks/objectcount/tst_objectcount.cpp +++ b/tests/benchmarks/objectcount/tst_objectcount.cpp @@ -189,6 +189,10 @@ void tst_ObjectCount::testCount_data() << QByteArray("import QtQuick.Controls 1.3; RadioButton { }") << QByteArray("import Qt.labs.controls 1.0; RadioButton { }"); + QTest::newRow("RangeSlider") + << QByteArray() + << QByteArray("import Qt.labs.controls 1.0; RangeSlider { }"); + QTest::newRow("ScrollView") << QByteArray("import QtQuick.Controls 1.3; ScrollView { }") << QByteArray(); diff --git a/tests/manual/gifs/data/qtlabscontrols-rangeslider.qml b/tests/manual/gifs/data/qtlabscontrols-rangeslider.qml new file mode 100644 index 00000000..bc0d00c6 --- /dev/null +++ b/tests/manual/gifs/data/qtlabscontrols-rangeslider.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.controls 1.0 + +Item { + width: slider.implicitWidth + height: slider.implicitHeight + + RangeSlider { + id: slider + anchors.centerIn: parent + } +} diff --git a/tests/manual/gifs/eventcapturer.cpp b/tests/manual/gifs/eventcapturer.cpp index f84d7840..17994e3b 100644 --- a/tests/manual/gifs/eventcapturer.cpp +++ b/tests/manual/gifs/eventcapturer.cpp @@ -228,6 +228,7 @@ void EventCapturer::stopCapturing() void EventCapturer::captureEvent(const QEvent *event) { + qDebug() << "captured" << event->type(); CapturedEvent capturedEvent(*event, mDelayTimer.elapsed() - mLastCaptureTime); mEvents.append(capturedEvent); mLastCaptureTime = mDelayTimer.elapsed(); diff --git a/tests/manual/gifs/tst_gifs.cpp b/tests/manual/gifs/tst_gifs.cpp index df636cd2..16e78706 100644 --- a/tests/manual/gifs/tst_gifs.cpp +++ b/tests/manual/gifs/tst_gifs.cpp @@ -51,12 +51,17 @@ private slots: void tumblerWrap(); void slider(); + void rangeSlider(); void busyIndicator(); void switchGif(); void button(); void tabBar(); private: + void moveSmoothly(const QPoint &from, const QPoint &to, int movements, + QEasingCurve::Type easingCurveType = QEasingCurve::OutQuint, + int movementDelay = 15); + QQuickView view; QString dataDirPath; QDir outputDir; @@ -75,6 +80,19 @@ void tst_Gifs::initTestCase() view.setFlags(view.flags() | Qt::FramelessWindowHint); } +void tst_Gifs::moveSmoothly(const QPoint &from, const QPoint &to, int movements, QEasingCurve::Type easingCurveType, int movementDelay) +{ + QEasingCurve curve(easingCurveType); + int xDifference = to.x() - from.x(); + int yDifference = to.y() - from.y(); + for (int movement = 0; movement < movements; ++movement) { + QPoint pos = QPoint( + from.x() + curve.valueForProgress(movement / qreal(qAbs(xDifference))) * xDifference, + from.y() + curve.valueForProgress(movement / qreal(qAbs(yDifference))) * yDifference); + QTest::mouseMove(&view, pos, movementDelay); + } +} + void tst_Gifs::tumblerWrap() { GifRecorder gifRecorder; @@ -266,6 +284,39 @@ void tst_Gifs::slider() gifRecorder.waitForFinish(); } +void tst_Gifs::rangeSlider() +{ + GifRecorder gifRecorder; + gifRecorder.setDataDirPath(dataDirPath); + gifRecorder.setOutputDir(outputDir); + gifRecorder.setRecordingDuration(5); + gifRecorder.setHighQuality(true); + gifRecorder.setQmlFileName("qtlabscontrols-rangeslider.qml"); + gifRecorder.setView(&view); + + view.show(); + + gifRecorder.start(); + + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(17, 18), 200); + moveSmoothly(QPoint(0, 17), QPoint(54, 17), 54); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(54, 17), 20); + + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(182, 14), 200); + moveSmoothly(QPoint(183, 17), QPoint(145, 17), 183 - 145); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(145, 17), 20); + + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(142, 13), 200); + moveSmoothly(QPoint(143, 17), QPoint(189, 17), 189 - 143); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(189, 12), 20); + + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(63, 14), 200); + moveSmoothly(QPoint(62, 17), QPoint(6, 17), 62 - 6); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(6, 17), 20); + + gifRecorder.waitForFinish(); +} + void tst_Gifs::busyIndicator() { GifRecorder gifRecorder; |