diff options
author | Mitch Curtis <mitch.curtis@theqtcompany.com> | 2015-07-10 15:25:03 +0200 |
---|---|---|
committer | Mitch Curtis <mitch.curtis@theqtcompany.com> | 2015-07-16 16:01:35 +0000 |
commit | 7975760a4fc0a9ba6f05fa405ba07381131c0f79 (patch) | |
tree | 02961959c18e2bd9c9a7358f82ee7bd1c8731f84 | |
parent | 00eec16115770b0822c250237949243816ac2e1c (diff) |
Add Dial.
Change-Id: Ie80fac70141ef72bb68f7c9e91827e834dc1fc4a
Reviewed-by: Mitch Curtis <mitch.curtis@theqtcompany.com>
-rw-r--r-- | src/extras/doc/images/.directory | 4 | ||||
-rw-r--r-- | src/extras/doc/images/qtquickextras2-dial-background.png | bin | 0 -> 3009 bytes | |||
-rw-r--r-- | src/extras/doc/images/qtquickextras2-dial-handle.png | bin | 0 -> 3765 bytes | |||
-rw-r--r-- | src/extras/doc/qtquickextras2.qdocconf | 2 | ||||
-rw-r--r-- | src/extras/doc/src/qtquickextras2-customize.qdoc | 57 | ||||
-rw-r--r-- | src/extras/extras.pri | 2 | ||||
-rw-r--r-- | src/extras/qquickdial.cpp | 527 | ||||
-rw-r--r-- | src/extras/qquickdial_p.h | 141 | ||||
-rw-r--r-- | src/imports/extras/Dial.qml | 83 | ||||
-rw-r--r-- | src/imports/extras/extras.pro | 1 | ||||
-rw-r--r-- | src/imports/extras/qmldir | 1 | ||||
-rw-r--r-- | src/imports/extras/qtquickextras2plugin.cpp | 2 | ||||
-rw-r--r-- | tests/auto/extras/data/tst_dial.qml | 281 |
13 files changed, 1100 insertions, 1 deletions
diff --git a/src/extras/doc/images/.directory b/src/extras/doc/images/.directory new file mode 100644 index 00000000..7d49c166 --- /dev/null +++ b/src/extras/doc/images/.directory @@ -0,0 +1,4 @@ +[Dolphin] +Timestamp=2015,7,13,18,47,2 +Version=3 +ViewMode=1 diff --git a/src/extras/doc/images/qtquickextras2-dial-background.png b/src/extras/doc/images/qtquickextras2-dial-background.png Binary files differnew file mode 100644 index 00000000..8b621dbe --- /dev/null +++ b/src/extras/doc/images/qtquickextras2-dial-background.png diff --git a/src/extras/doc/images/qtquickextras2-dial-handle.png b/src/extras/doc/images/qtquickextras2-dial-handle.png Binary files differnew file mode 100644 index 00000000..5caa44ab --- /dev/null +++ b/src/extras/doc/images/qtquickextras2-dial-handle.png diff --git a/src/extras/doc/qtquickextras2.qdocconf b/src/extras/doc/qtquickextras2.qdocconf index b97288ce..ee2311c7 100644 --- a/src/extras/doc/qtquickextras2.qdocconf +++ b/src/extras/doc/qtquickextras2.qdocconf @@ -28,7 +28,7 @@ depends = qtcore qtgui qtdoc qtqml qtquick qtquicklayouts qtquickdialogs qtquick # Specify the install path under QT_INSTALL_EXAMPLES # Examples will be installed under quick/extras - 'extras' subdirectory # is given as part of \example commands -exampledirs += ../../../examples/quick/extras +exampledirs += ../../../examples/quick/extras ../../imports/extras examplesinstallpath = quick/extras2 headerdirs += ../ diff --git a/src/extras/doc/src/qtquickextras2-customize.qdoc b/src/extras/doc/src/qtquickextras2-customize.qdoc new file mode 100644 index 00000000..2982aedd --- /dev/null +++ b/src/extras/doc/src/qtquickextras2-customize.qdoc @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtquickextras2-customize.html + \title Customizing Qt Quick Extras 2 + \brief A set of UI controls to create user interfaces in Qt Quick + + Qt Quick Controls consist of a hierarchy (tree) of items. In order to + provide a custom look'n'feel, the default implementation of each can be + replaced with a custom item. The following snippets present the default + implementations of various items. These can be used as a starting point + to implement custom look'n'feel. + + \sa {Customizing Qt Quick Controls 2} + + \section1 Customizing Dial + + Dial consists of two visual items: \l {Control::background}{background} + and \l {Dial::handle}{handle}. + + \section3 Background + + \image qtquickextras2-dial-background.png + + \snippet Dial.qml background + + \section3 Indicator + + \image qtquickextras2-dial-handle.png + + \snippet Dial.qml handle +*/ diff --git a/src/extras/extras.pri b/src/extras/extras.pri index edbc754a..684fd6e5 100644 --- a/src/extras/extras.pri +++ b/src/extras/extras.pri @@ -1,11 +1,13 @@ INCLUDEPATH += $$PWD HEADERS += \ + $$PWD/qquickdial_p.h \ $$PWD/qquickdrawer_p.h \ $$PWD/qquickswipeview_p.h \ $$PWD/qquicktumbler_p.h SOURCES += \ + $$PWD/qquickdial.cpp \ $$PWD/qquickdrawer.cpp \ $$PWD/qquickswipeview.cpp \ $$PWD/qquicktumbler.cpp diff --git a/src/extras/qquickdial.cpp b/src/extras/qquickdial.cpp new file mode 100644 index 00000000..fe196659 --- /dev/null +++ b/src/extras/qquickdial.cpp @@ -0,0 +1,527 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Extras 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 "qquickdial_p.h" + +#include <QtCore/qmath.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuickControls/private/qquickcontrol_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Dial + \inherits Control + \instantiates QQuickDial + \inqmlmodule QtQuick.Extras + \ingroup sliders + \brief A circular dial that is rotated to set a value. + + The Dial is similar to a traditional dial knob that is found on devices + such as stereos or industrial equipment. It allows the user to specify a + value within a range. + + The value of the dial is set with the \l value property. The range is + set with the \l from and \l to properties. + + The dial can be manipulated with a keyboard. It supports the following + actions: + + \table + \header \li \b {Action} \li \b {Key} + \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left + \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down + \row \li Set \l value to \l from \li \c Qt.Key_Home + \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right + \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up + \row \li Set \l value to \l to \li \c Qt.Key_End + \endtable + + \sa {Customizing Dial} +*/ + +static const qreal startAngle = -140; +static const qreal endAngle = 140; + +class QQuickDialPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickDial) + +public: + QQuickDialPrivate() : + from(0), + to(1), + value(0), + position(0), + angle(startAngle), + stepSize(0), + pressed(false), + snapMode(QQuickDial::NoSnap), + handle(Q_NULLPTR) + { + } + + qreal valueAt(qreal position) const; + qreal snapPosition(qreal position) const; + qreal positionAt(const QPoint &point) const; + void setPosition(qreal position); + void updatePosition(); + + qreal from; + qreal to; + qreal value; + qreal position; + qreal angle; + qreal stepSize; + bool pressed; + QPoint pressPoint; + QQuickDial::SnapMode snapMode; + QQuickItem *handle; +}; + +qreal QQuickDialPrivate::valueAt(qreal position) const +{ + return from + (to - from) * position; +} + +qreal QQuickDialPrivate::snapPosition(qreal position) const +{ + if (qFuzzyIsNull(stepSize)) + return position; + return qRound(position / stepSize) * stepSize; +} + +qreal QQuickDialPrivate::positionAt(const QPoint &point) const +{ + qreal yy = height / 2.0 - point.y(); + qreal xx = point.x() - width / 2.0; + qreal angle = (xx || yy) ? atan2(yy, xx) : 0; + + if (angle < M_PI / -2) + angle = angle + M_PI * 2; + + qreal normalizedAngle = (M_PI * 4 / 3 - angle) / (M_PI * 10 / 6); + return normalizedAngle; +} + +void QQuickDialPrivate::setPosition(qreal pos) +{ + Q_Q(QQuickDial); + pos = qBound(0.0, pos, 1.0); + if (!qFuzzyCompare(position, pos)) { + position = pos; + + angle = startAngle + position * qAbs(endAngle - startAngle); + + emit q->positionChanged(); + emit q->angleChanged(); + } +} + +void QQuickDialPrivate::updatePosition() +{ + qreal pos = 0; + if (!qFuzzyCompare(from, to)) + pos = (value - from) / (to - from); + setPosition(pos); +} + +QQuickDial::QQuickDial(QQuickItem *parent) : + QQuickControl(*(new QQuickDialPrivate), parent) +{ + setActiveFocusOnTab(true); + setAcceptedMouseButtons(Qt::LeftButton); +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::from + + This property holds the starting value for the range. The default value is \c 0.0. + + \sa to, value +*/ +qreal QQuickDial::from() const +{ + Q_D(const QQuickDial); + return d->from; +} + +void QQuickDial::setFrom(qreal from) +{ + Q_D(QQuickDial); + if (!qFuzzyCompare(d->from, from)) { + d->from = from; + emit fromChanged(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } + } +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::to + + This property holds the end value for the range. The default value is + \c 1.0. + + \sa from, value +*/ +qreal QQuickDial::to() const +{ + Q_D(const QQuickDial); + return d->to; +} + +void QQuickDial::setTo(qreal to) +{ + Q_D(QQuickDial); + if (!qFuzzyCompare(d->to, to)) { + d->to = to; + emit toChanged(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } + } +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::value + + This property holds the value in the range \c from - \c to. The default + value is \c 0.0. + + Unlike the \l position property, the \c value is not updated while the + handle is dragged. The value is updated after the value has been chosen + and the dial has been released. + + \sa position +*/ +qreal QQuickDial::value() const +{ + Q_D(const QQuickDial); + return d->value; +} + +void QQuickDial::setValue(qreal value) +{ + Q_D(QQuickDial); + if (isComponentComplete()) + value = d->from > d->to ? qBound(d->to, value, d->from) : qBound(d->from, value, d->to); + + if (!qFuzzyCompare(d->value, value)) { + d->value = value; + d->updatePosition(); + emit valueChanged(); + } +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::position + + This property holds the logical position of the handle. + + The position is defined as a percentage of the control's angle range (the + range within which the handle can be moved) scaled to \c {0.0 - 1.0}. + Unlike the \l value property, the \c position is continuously updated while + the handle is dragged. + + \sa value, angle +*/ +qreal QQuickDial::position() const +{ + Q_D(const QQuickDial); + return d->position; +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::angle \readonly + + This property holds the angle of the handle. + + Like the \l position property, angle is continuously updated while the + handle is dragged. + + \sa position +*/ +qreal QQuickDial::angle() const +{ + Q_D(const QQuickDial); + return d->angle; +} + +/*! + \qmlproperty real QtQuickExtras2::Dial::stepSize + + This property holds the step size. The default value is \c 0.0. + + \sa snapMode, increase(), decrease() +*/ +qreal QQuickDial::stepSize() const +{ + Q_D(const QQuickDial); + return d->stepSize; +} + +void QQuickDial::setStepSize(qreal step) +{ + Q_D(QQuickDial); + if (!qFuzzyCompare(d->stepSize, step)) { + d->stepSize = step; + emit stepSizeChanged(); + } +} + +/*! + \qmlproperty enumeration QtQuickExtras2::Dial::snapMode + + This property holds the snap mode. + + The snap mode works with the \l stepSize to allow the handle to snap to + certain points along the dial. + + Possible values: + \list + \li \c Dial.NoSnap (default) - The dial does not snap. + \li \c Dial.SnapAlways - The dial snaps while the handle is dragged. + \li \c Dial.SnapOnRelease - The dial does not snap while being dragged, but only after the handle is released. + \endlist + + \sa stepSize +*/ +QQuickDial::SnapMode QQuickDial::snapMode() const +{ + Q_D(const QQuickDial); + return d->snapMode; +} + +void QQuickDial::setSnapMode(SnapMode mode) +{ + Q_D(QQuickDial); + if (d->snapMode != mode) { + d->snapMode = mode; + emit snapModeChanged(); + } +} + +/*! + \qmlproperty bool QtQuickExtras2::Dial::pressed + + This property holds whether the dial is pressed. + + The dial will be pressed when either the mouse is pressed over it, or a key + such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial + be pressed upon key presses (due to styling reasons, for example), you can + use the \l {Keys}{Keys attached property}: + + \code + Dial { + Keys.onLeftPressed: {} + } + \endcode + + This will result in pressed only being \c true upon mouse presses. +*/ +bool QQuickDial::isPressed() const +{ + Q_D(const QQuickDial); + return d->pressed; +} + +void QQuickDial::setPressed(bool pressed) +{ + Q_D(QQuickDial); + if (d->pressed != pressed) { + d->pressed = pressed; + emit pressedChanged(); + } +} + +/*! + \qmlmethod void QtQuickExtras2::Dial::increase() + + Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickDial::increase() +{ + Q_D(QQuickDial); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value + step); +} + +/*! + \qmlmethod void QtQuickExtras2::Dial::decrease() + + Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickDial::decrease() +{ + Q_D(QQuickDial); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value - step); +} + +/*! + \qmlproperty component QtQuickExtras2::Dial::handle + + This property holds the handle of the dial. + + The handle acts as a visual indicator of the position of the dial. + + \sa {Customizing Dial} +*/ +QQuickItem *QQuickDial::handle() const +{ + Q_D(const QQuickDial); + return d->handle; +} + +void QQuickDial::setHandle(QQuickItem *handle) +{ + Q_D(QQuickDial); + if (handle != d->handle) { + d->handle = handle; + if (d->handle && !d->handle->parentItem()) + d->handle->setParentItem(this); + emit handleChanged(); + } +} + +void QQuickDial::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickDial); + if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Down) { + setPressed(true); + if (isMirrored()) + increase(); + else + decrease(); + } else if (event->key() == Qt::Key_Right || event->key() == Qt::Key_Up) { + setPressed(true); + if (isMirrored()) + decrease(); + else + increase(); + } else if (event->key() == Qt::Key_Home) { + setPressed(true); + setValue(isMirrored() ? d->to : d->from); + } else if (event->key() == Qt::Key_End) { + setPressed(true); + setValue(isMirrored() ? d->from : d->to); + } else { + event->ignore(); + QQuickControl::keyPressEvent(event); + } +} + +void QQuickDial::keyReleaseEvent(QKeyEvent *event) +{ + QQuickControl::keyReleaseEvent(event); + setPressed(false); +} + +void QQuickDial::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickDial); + QQuickControl::mousePressEvent(event); + d->pressPoint = event->pos(); + setPressed(true); +} + +void QQuickDial::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickDial); + QQuickControl::mouseMoveEvent(event); + if (!keepMouseGrab()) { + bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(event->pos().x() - d->pressPoint.x(), Qt::XAxis, event); + setKeepMouseGrab(overXDragThreshold); + + if (!overXDragThreshold) { + bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(event->pos().y() - d->pressPoint.y(), Qt::YAxis, event); + setKeepMouseGrab(overYDragThreshold); + } + } + if (keepMouseGrab()) { + qreal pos = d->positionAt(event->pos()); + if (d->snapMode == SnapAlways) + pos = d->snapPosition(pos); + d->setPosition(pos); + } +} + +void QQuickDial::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickDial); + QQuickControl::mouseReleaseEvent(event); + d->pressPoint = QPoint(); + if (keepMouseGrab()) { + qreal pos = d->positionAt(event->pos()); + if (d->snapMode != NoSnap) + pos = d->snapPosition(pos); + setValue(d->valueAt(pos)); + setKeepMouseGrab(false); + } + setPressed(false); +} + +void QQuickDial::mouseUngrabEvent() +{ + Q_D(QQuickDial); + QQuickControl::mouseUngrabEvent(); + d->pressPoint = QPoint(); + setPressed(false); +} + +void QQuickDial::mirrorChange() +{ + QQuickControl::mirrorChange(); + emit angleChanged(); +} + +void QQuickDial::componentComplete() +{ + Q_D(QQuickDial); + QQuickControl::componentComplete(); + setValue(d->value); + d->updatePosition(); +} + +QT_END_NAMESPACE diff --git a/src/extras/qquickdial_p.h b/src/extras/qquickdial_p.h new file mode 100644 index 00000000..f1588fff --- /dev/null +++ b/src/extras/qquickdial_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Extras 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 QQUICKDIAL_H +#define QQUICKDIAL_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 <QtCore/qvariant.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuickControls/private/qquickcontrol_p.h> +#include <QtQuickExtras/private/qtquickextrasglobal_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickDialAttached; +class QQuickDialPrivate; + +class Q_QUICKEXTRAS_EXPORT QQuickDial : 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(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged FINAL) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + +public: + explicit QQuickDial(QQuickItem *parent = Q_NULLPTR); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + + qreal angle() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + bool isPressed() const; + void setPressed(bool pressed); + + QQuickItem *handle() const; + void setHandle(QQuickItem *handle); + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void valueChanged(); + void positionChanged(); + void angleChanged(); + void stepSizeChanged(); + void snapModeChanged(); + void pressedChanged(); + void handleChanged(); + +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: + Q_DISABLE_COPY(QQuickDial) + Q_DECLARE_PRIVATE(QQuickDial) +}; + +QT_END_NAMESPACE + +#endif // QQUICKDIAL_H diff --git a/src/imports/extras/Dial.qml b/src/imports/extras/Dial.qml new file mode 100644 index 00000000..fbf2807f --- /dev/null +++ b/src/imports/extras/Dial.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Extras 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 QtQuick.Controls 2.0 +import QtQuick.Extras 2.0 + +AbstractDial { + id: control + + implicitWidth: 100 + implicitHeight: 100 + + Accessible.pressed: pressed + Accessible.role: Accessible.Dial + + //! [background] + background: Rectangle { + color: control.Theme.backgroundColor + radius: width / 2 + + border.color: control.activeFocus ? control.Theme.focusColor : control.Theme.frameColor + } + //! [background] + + //! [handle] + handle: Rectangle { + id: handleItem + + x: background.width / 2 - handle.width / 2 + y: background.height / 2 - handle.height / 2 + transform: [ + Translate { + y: -background.height * 0.35 + }, + Rotation { + angle: control.angle + origin.x: handle.width / 2 + origin.y: handle.height / 2 + } + ] + implicitWidth: 20 + implicitHeight: 20 + radius: width / 2 + border.width: control.activeFocus ? 2 : 1 + border.color: control.activeFocus ? control.Theme.focusColor : control.Theme.frameColor + color: control.Theme.baseColor + } + //! [handle] +} diff --git a/src/imports/extras/extras.pro b/src/imports/extras/extras.pro index 79e9bbfe..5aa6f04e 100644 --- a/src/imports/extras/extras.pro +++ b/src/imports/extras/extras.pro @@ -9,6 +9,7 @@ OTHER_FILES += \ qmldir QML_FILES = \ + Dial.qml \ Drawer.qml \ SwipeView.qml \ Tumbler.qml diff --git a/src/imports/extras/qmldir b/src/imports/extras/qmldir index 4ba7c40b..51c99e31 100644 --- a/src/imports/extras/qmldir +++ b/src/imports/extras/qmldir @@ -1,6 +1,7 @@ module QtQuick.Extras plugin qtquickextras2plugin classname QtQuickExtras2Plugin +Dial 2.0 Dial.qml Drawer 2.0 Drawer.qml SwipeView 2.0 SwipeView.qml Tumbler 2.0 Tumbler.qml diff --git a/src/imports/extras/qtquickextras2plugin.cpp b/src/imports/extras/qtquickextras2plugin.cpp index 2a930ffd..a17ae159 100644 --- a/src/imports/extras/qtquickextras2plugin.cpp +++ b/src/imports/extras/qtquickextras2plugin.cpp @@ -36,6 +36,7 @@ #include <QtQml/qqmlextensionplugin.h> +#include <QtQuickExtras/private/qquickdial_p.h> #include <QtQuickExtras/private/qquickdrawer_p.h> #include <QtQuickExtras/private/qquickswipeview_p.h> #include <QtQuickExtras/private/qquicktumbler_p.h> @@ -53,6 +54,7 @@ public: void QtQuickExtras2Plugin::registerTypes(const char *uri) { + qmlRegisterType<QQuickDial>(uri, 2, 0, "AbstractDial"); qmlRegisterType<QQuickDrawer>(uri, 2, 0, "AbstractDrawer"); qmlRegisterType<QQuickSwipeView>(uri, 2, 0, "AbstractSwipeView"); qmlRegisterType<QQuickTumbler>(uri, 2, 0, "AbstractTumbler"); diff --git a/tests/auto/extras/data/tst_dial.qml b/tests/auto/extras/data/tst_dial.qml new file mode 100644 index 00000000..0870f6b5 --- /dev/null +++ b/tests/auto/extras/data/tst_dial.qml @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** 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 QtQuick.Extras 2.0 + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "Dial" + + Component { + id: dialComponent + Dial {} + } + + property var dial: null + + function init() { + dial = dialComponent.createObject(testCase); + verify(dial, "Dial: failed to create an instance"); + } + + function cleanup() { + if (dial) + dial.destroy(); + } + + function test_instance() { + compare(dial.value, 0.0); + compare(dial.from, 0.0); + compare(dial.to, 1.0); + compare(dial.stepSize, 0.0); + verify(dial.activeFocusOnTab); + verify(!dial.pressed); + } + + function test_value() { + compare(dial.value, 0.0); + dial.value = 0.5; + compare(dial.value, 0.5); + dial.value = 1.0; + compare(dial.value, 1.0); + dial.value = -1.0; + compare(dial.value, 0.0); + dial.value = 2.0; + compare(dial.value, 1.0); + } + + function test_range() { + dial.from = 0; + dial.to = 100; + dial.value = 50; + compare(dial.from, 0); + compare(dial.to, 100); + compare(dial.value, 50); + compare(dial.position, 0.5); + + dial.value = 1000 + compare(dial.value, 100); + compare(dial.position, 1); + + dial.value = -1 + compare(dial.value, 0); + compare(dial.position, 0); + + dial.from = 25 + compare(dial.from, 25); + compare(dial.value, 25); + compare(dial.position, 0); + + dial.to = 75 + compare(dial.to, 75); + compare(dial.value, 25); + compare(dial.position, 0); + + dial.value = 50 + compare(dial.value, 50); + compare(dial.position, 0.5); + } + + function test_inverted() { + dial.destroy(); + dial = dialComponent.createObject(testCase, { from: 1.0, to: -1.0 }); + verify(dial, "Dial: failed to create an instance"); + compare(dial.from, 1.0); + compare(dial.to, -1.0); + compare(dial.value, 0.0); + compare(dial.position, 0.5); + + dial.value = 2.0; + compare(dial.value, 1.0); + compare(dial.position, 0.0); + + dial.value = -2.0; + compare(dial.value, -1.0); + compare(dial.position, 1.0); + + dial.value = 0.0; + compare(dial.value, 0.0); + compare(dial.position, 0.5); + } + + SignalSpy { + id: pressSpy + signalName: "pressedChanged" + } + + function test_pressed() { + pressSpy.target = dial; + verify(pressSpy.valid); + verify(!dial.pressed); + + mousePress(dial, dial.width / 2, dial.height / 2); + verify(dial.pressed); + compare(pressSpy.count, 1); + + mouseRelease(dial, dial.width / 2, dial.height / 2); + verify(!dial.pressed); + compare(pressSpy.count, 2); + } + + SignalSpy { + id: valueSpy + signalName: "valueChanged" + } + + function test_dragging_data() { + return [ + { tag: "default", from: 0, to: 1, leftValue: 0.20, topValue: 0.5, rightValue: 0.8, bottomValue: 1.0 }, + { tag: "scaled2", from: 0, to: 2, leftValue: 0.4, topValue: 1.0, rightValue: 1.6, bottomValue: 2.0 }, + { tag: "scaled1", from: -1, to: 0, leftValue: -0.8, topValue: -0.5, rightValue: -0.2, bottomValue: 0.0 } + ] + } + + function test_dragging(data) { + dial.from = data.from; + dial.to = data.to; + + valueSpy.target = dial; + verify(valueSpy.valid); + + // drag to the left + mouseDrag(dial, dial.width / 2, dial.height / 2, -dial.width / 2, 0, Qt.LeftButton); + fuzzyCompare(dial.value, data.leftValue, 0.1); + verify(valueSpy.count > 0); + valueSpy.clear(); + + // drag to the top + mouseDrag(dial, dial.width / 2, dial.height / 2, 0, -dial.height / 2, Qt.LeftButton); + fuzzyCompare(dial.value, data.topValue, 0.1); + verify(valueSpy.count > 0); + valueSpy.clear(); + + // drag to the right + mouseDrag(dial, dial.width / 2, dial.height / 2, dial.width / 2, 0, Qt.LeftButton); + fuzzyCompare(dial.value, data.rightValue, 0.1); + verify(valueSpy.count > 0); + valueSpy.clear(); + + // drag to the bottom (* 0.6 to ensure we don't go over to the minimum position) + mouseDrag(dial, dial.width / 2, dial.height / 2, 10, dial.height / 2, Qt.LeftButton); + fuzzyCompare(dial.value, data.bottomValue, 0.1); + verify(valueSpy.count > 0); + valueSpy.clear(); + } + + property Component focusTest: Component { + FocusScope { + signal receivedKeyPress + + Component.onCompleted: forceActiveFocus() + anchors.fill: parent + Keys.onPressed: receivedKeyPress() + } + } + + SignalSpy { + id: parentEventSpy + } + + function test_keyboardNavigation() { + var focusScope = focusTest.createObject(testCase); + verify(focusScope); + + // Tests that we've accepted events that we're interested in. + parentEventSpy.target = focusScope; + parentEventSpy.signalName = "receivedKeyPress"; + + dial.parent = focusScope; + compare(dial.activeFocusOnTab, true); + compare(dial.value, 0); + + dial.focus = true; + compare(dial.activeFocus, true); + dial.stepSize = 0.1; + + keyClick(Qt.Key_Left); + compare(parentEventSpy.count, 0); + compare(dial.value, 0); + + + var keyPairs = [[Qt.Key_Left, Qt.Key_Right], [Qt.Key_Down, Qt.Key_Up]]; + for (var keyPairIndex = 0; keyPairIndex < 2; ++keyPairIndex) { + for (var i = 1; i <= 10; ++i) { + keyClick(keyPairs[keyPairIndex][1]); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.stepSize * i); + } + + compare(dial.value, dial.to); + + for (i = 10; i > 0; --i) { + keyClick(keyPairs[keyPairIndex][0]); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.stepSize * (i - 1)); + } + } + + compare(dial.value, dial.from); + + keyClick(Qt.Key_Home); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.from); + + keyClick(Qt.Key_End); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.to); + + keyClick(Qt.Key_End); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.to); + + keyClick(Qt.Key_Home); + compare(parentEventSpy.count, 0); + compare(dial.value, dial.from); + + focusScope.destroy(); + } +} |