aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/imports/controls/RangeSlider.qml136
-rw-r--r--src/imports/controls/controls.pri1
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-background.pngbin0 -> 1271 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.pngbin0 -> 1170 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle-focused.pngbin0 -> 1448 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.pngbin0 -> 1329 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.pngbin0 -> 1218 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle-focused.pngbin0 -> 1449 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.pngbin0 -> 1345 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.pngbin0 -> 1094 bytes
-rw-r--r--src/imports/controls/doc/images/qtlabscontrols-rangeslider.gifbin0 -> 16218 bytes
-rw-r--r--src/imports/controls/doc/src/qtlabscontrols-customize.qdoc32
-rw-r--r--src/imports/controls/qtlabscontrolsplugin.cpp1
-rw-r--r--src/imports/templates/qtlabstemplatesplugin.cpp3
-rw-r--r--src/templates/qquickrangeslider.cpp885
-rw-r--r--src/templates/qquickrangeslider_p.h177
-rw-r--r--src/templates/templates.pri2
-rw-r--r--tests/auto/accessibility/data/rangeslider.qml18
-rw-r--r--tests/auto/accessibility/tst_accessibility.cpp1
-rw-r--r--tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml4
-rw-r--r--tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp44
-rw-r--r--tests/auto/controls/data/tst_rangeslider.qml580
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-background.qml11
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-disabled.qml8
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle-focused.qml8
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-first-handle.qml12
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-normal.qml7
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle-focused.qml8
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-second-handle.qml12
-rw-r--r--tests/auto/snippets/data/qtlabscontrols-rangeslider-track.qml12
-rw-r--r--tests/benchmarks/objectcount/tst_objectcount.cpp4
-rw-r--r--tests/manual/gifs/data/qtlabscontrols-rangeslider.qml52
-rw-r--r--tests/manual/gifs/eventcapturer.cpp1
-rw-r--r--tests/manual/gifs/tst_gifs.cpp51
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
new file mode 100644
index 00000000..5b3c603b
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-background.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png
new file mode 100644
index 00000000..0194a240
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-disabled.png
Binary files differ
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
new file mode 100644
index 00000000..390a6399
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle-focused.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png
new file mode 100644
index 00000000..e04ed6df
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-first-handle.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png
new file mode 100644
index 00000000..7032728a
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-normal.png
Binary files differ
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
new file mode 100644
index 00000000..803f8141
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle-focused.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png
new file mode 100644
index 00000000..0fe3d630
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-second-handle.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png
new file mode 100644
index 00000000..685a9d44
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider-track.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif b/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif
new file mode 100644
index 00000000..afd909f3
--- /dev/null
+++ b/src/imports/controls/doc/images/qtlabscontrols-rangeslider.gif
Binary files differ
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;