aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/doc/snippets/pointerHandlers/handlerFlick.qml88
-rw-r--r--src/quick/doc/snippets/pointerHandlers/wheelHandler.qml63
-rw-r--r--src/quick/handlers/handlers.pri6
-rw-r--r--src/quick/handlers/qquickwheelhandler.cpp520
-rw-r--r--src/quick/handlers/qquickwheelhandler_p.h128
-rw-r--r--src/quick/handlers/qquickwheelhandler_p_p.h87
-rw-r--r--src/quick/items/qquickevents_p_p.h1
-rw-r--r--src/quick/items/qquickitemsmodule.cpp6
-rw-r--r--tests/auto/quick/pointerhandlers/pointerhandlers.pro1
-rw-r--r--tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml55
-rw-r--r--tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml43
-rw-r--r--tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro14
-rw-r--r--tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp344
-rw-r--r--tests/manual/pointer/content/FakeFlickable.qml101
-rw-r--r--tests/manual/pointer/content/Slider.qml28
-rw-r--r--tests/manual/pointer/fakeFlickable.qml13
-rw-r--r--tests/manual/pointer/map.qml18
-rw-r--r--tests/manual/pointer/pinchAndWheel.qml160
18 files changed, 1628 insertions, 48 deletions
diff --git a/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml
new file mode 100644
index 0000000000..f74b075357
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.14
+import Qt.labs.animation 1.0
+
+Item {
+ width: 320; height: 480
+ Flow {
+ id: content
+ width: parent.width
+ spacing: 2; padding: 2
+
+ WheelHandler {
+ orientation: Qt.Vertical
+ property: "y"
+ rotationScale: 15
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ onActiveChanged: if (!active) ybr.returnToBounds()
+ }
+
+ DragHandler {
+ xAxis.enabled: false
+ onActiveChanged: if (!active) ybr.returnToBounds()
+ }
+
+ BoundaryRule on y {
+ id: ybr
+ minimum: content.parent.height - content.height
+ maximum: 0
+ minimumOvershoot: 400; maximumOvershoot: 400
+ overshootFilter: BoundaryRule.Peak
+ }
+
+ Repeater {
+ model: 1000
+ Rectangle { color: "gray"; width: 10 + Math.random() * 100; height: 15 }
+ }
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml
new file mode 100644
index 0000000000..2c9913ac97
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.14
+
+Rectangle {
+ width: 170; height: 120
+ color: "green"; antialiasing: true
+
+ WheelHandler {
+ property: "rotation"
+ onWheel: console.log("rotation", event.angleDelta.y,
+ "scaled", rotation, "@", point.position, "=>", parent.rotation)
+ }
+}
+//![0]
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri
index fa2f25c793..49975bd1ca 100644
--- a/src/quick/handlers/handlers.pri
+++ b/src/quick/handlers/handlers.pri
@@ -27,3 +27,9 @@ SOURCES += \
$$PWD/qquicksinglepointhandler.cpp \
$$PWD/qquicktaphandler.cpp \
$$PWD/qquickdragaxis.cpp
+
+qtConfig(wheelevent) {
+ HEADERS += $$PWD/qquickwheelhandler_p.h $$PWD/qquickwheelhandler_p_p.h
+ SOURCES += $$PWD/qquickwheelhandler.cpp
+}
+
diff --git a/src/quick/handlers/qquickwheelhandler.cpp b/src/quick/handlers/qquickwheelhandler.cpp
new file mode 100644
index 0000000000..90e4fef97e
--- /dev/null
+++ b/src/quick/handlers/qquickwheelhandler.cpp
@@ -0,0 +1,520 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickwheelhandler_p.h"
+#include "qquickwheelhandler_p_p.h"
+#include <QLoggingCategory>
+#include <QtMath>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel")
+
+/*!
+ \qmltype WheelHandler
+ \instantiates QQuickWheelHandler
+ \inqmlmodule QtQuick
+ \ingroup qtquick-input-handlers
+ \brief Handler for the mouse wheel.
+
+ WheelHandler is a handler that is used to interactively manipulate some
+ numeric property of an Item as the user rotates the mouse wheel. Like other
+ Input Handlers, by default it manipulates its \l {PointerHandler::target}
+ {target}. Declare \l property to control which target property will be
+ manipulated:
+
+ \snippet pointerHandlers/wheelHandler.qml 0
+
+ \l BoundaryRule is quite useful in combination with WheelHandler (as well
+ as with other Input Handlers) to declare the allowed range of values that
+ the target property can have. For example it is possible to implement
+ scrolling using a combination of WheelHandler and \l DragHandler to
+ manipulate the scrollable Item's \l{QQuickItem::y}{y} property when the
+ user rotates the wheel or drags the item on a touchscreen, and
+ \l BoundaryRule to limit the range of motion from the top to the bottom:
+
+ \snippet pointerHandlers/handlerFlick.qml 0
+
+ Alternatively if \l targetProperty is not set or \l target is null,
+ WheelHandler will not automatically manipulate anything; but the
+ \l rotation property can be used in a binding to manipulate another
+ property, or you can implement \c onWheel and handle the wheel event
+ directly.
+
+ WheelHandler handles only a rotating mouse wheel by default.
+ Optionally it can handle smooth-scrolling events from touchpad gestures,
+ by setting \l acceptedDevices to \c{PointerDevice.Mouse | PointerDevice.TouchPad}.
+
+ \note Some non-mouse hardware (such as a touch-sensitive Wacom tablet, or
+ a Linux laptop touchpad) generates real wheel events from gestures.
+ WheelHandler will respond to those events as wheel events regardless of the
+ setting of the \l acceptedDevices property.
+
+ \sa MouseArea
+ \sa Flickable
+*/
+
+QQuickWheelHandler::QQuickWheelHandler(QQuickItem *parent)
+ : QQuickSinglePointHandler(*(new QQuickWheelHandlerPrivate), parent)
+{
+ setAcceptedDevices(QQuickPointerDevice::Mouse);
+}
+
+/*!
+ \qmlproperty enum QtQuick::WheelHandler::orientation
+
+ Which wheel to react to. The default is \c Qt.Vertical.
+
+ Not every mouse has a \c Horizontal wheel; sometimes it is emulated by
+ tilting the wheel sideways. A touchpad can usually generate both vertical
+ and horizontal wheel events.
+*/
+Qt::Orientation QQuickWheelHandler::orientation() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->orientation;
+}
+
+void QQuickWheelHandler::setOrientation(Qt::Orientation orientation)
+{
+ Q_D(QQuickWheelHandler);
+ if (d->orientation == orientation)
+ return;
+
+ d->orientation = orientation;
+ emit orientationChanged();
+}
+
+/*!
+ \qmlproperty bool QtQuick::WheelHandler::invertible
+
+ Whether or not to reverse the direction of property change if
+ \l QQuickPointerScrollEvent::inverted is true. The default is \c true.
+
+ If the operating system has a "natural scrolling" setting that causes
+ scrolling to be in the same direction as the finger movement, then if this
+ property is set to \c true, and WheelHandler is directly setting a property
+ on \l target, the direction of movement will correspond to the system setting.
+ If this property is set to \l false, it will invert the \l rotation so that
+ the direction of motion is always the same as the direction of finger movement.
+*/
+bool QQuickWheelHandler::isInvertible() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->invertible;
+}
+
+void QQuickWheelHandler::setInvertible(bool invertible)
+{
+ Q_D(QQuickWheelHandler);
+ if (d->invertible == invertible)
+ return;
+
+ d->invertible = invertible;
+ emit invertibleChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::WheelHandler::activeTimeout
+
+ The amount of time in seconds after which the \l active property will
+ revert to \c false if no more wheel events are received. The default is
+ \c 0.1 (100 ms).
+
+ When WheelHandler handles events that contain
+ \l {Qt::ScrollPhase}{scroll phase} information, such as events from some
+ touchpads, the \l active property will become \c false as soon as an event
+ with phase \l Qt::ScrollEnd is received; in that case the timeout is not
+ necessary. But a conventional mouse with a wheel does not provide the
+ \l {QQuickPointerScrollEvent::phase}{scroll phase}: the mouse cannot detect
+ when the user has decided to stop scrolling, so the \l active property
+ transitions to \c false after this much time has elapsed.
+*/
+qreal QQuickWheelHandler::activeTimeout() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->activeTimeout;
+}
+
+void QQuickWheelHandler::setActiveTimeout(qreal timeout)
+{
+ Q_D(QQuickWheelHandler);
+ if (qFuzzyCompare(d->activeTimeout, timeout))
+ return;
+
+ if (timeout < 0) {
+ qWarning("activeTimeout must be positive");
+ return;
+ }
+
+ d->activeTimeout = timeout;
+ emit activeTimeoutChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::WheelHandler::rotation
+
+ The angle through which the mouse wheel has been rotated since the last
+ time this property was set, in wheel degrees.
+
+ A positive value indicates that the wheel was rotated up/right;
+ a negative value indicates that the wheel was rotated down/left.
+
+ A basic mouse click-wheel works in steps of 15 degrees.
+
+ The default is \c 0 at startup. It can be programmatically set to any value
+ at any time. The value will be adjusted from there as the user rotates the
+ mouse wheel.
+
+ \sa orientation
+*/
+qreal QQuickWheelHandler::rotation() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->rotation * d->rotationScale;
+}
+
+void QQuickWheelHandler::setRotation(qreal rotation)
+{
+ Q_D(QQuickWheelHandler);
+ if (qFuzzyCompare(d->rotation, rotation / d->rotationScale))
+ return;
+
+ d->rotation = rotation / d->rotationScale;
+ emit rotationChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::WheelHandler::rotationScale
+
+ The scaling to be applied to the \l rotation property, and to the
+ \l property on the \l target item, if any. The default is 1, such that
+ \l rotation will be in units of degrees of rotation. It can be set to a
+ negative number to invert the effect of the direction of mouse wheel
+ rotation.
+*/
+qreal QQuickWheelHandler::rotationScale() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->rotationScale;
+}
+
+void QQuickWheelHandler::setRotationScale(qreal rotationScale)
+{
+ Q_D(QQuickWheelHandler);
+ if (qFuzzyCompare(d->rotationScale, rotationScale))
+ return;
+ if (qFuzzyIsNull(rotationScale)) {
+ qWarning("rotationScale cannot be set to zero");
+ return;
+ }
+
+ d->rotationScale = rotationScale;
+ emit rotationScaleChanged();
+}
+
+/*!
+ \qmlproperty string QtQuick::WheelHandler::property
+
+ The property to be modified on the \l target when the mouse wheel is rotated.
+
+ The default is no property (empty string). When no target property is being
+ automatically modified, you can use bindings to react to mouse wheel
+ rotation in arbitrary ways.
+
+ You can use the mouse wheel to adjust any numeric property. For example if
+ \c property is set to \c x, the \l target will move horizontally as the
+ wheel is rotated. The following properties have special behavior:
+
+ \value scale
+ \l{QQuickItem::scale}{scale} will be modified in a non-linear fashion
+ as described under \l targetScaleMultiplier. If
+ \l targetTransformAroundCursor is \c true, the \l{QQuickItem::x}{x} and
+ \l{QQuickItem::y}{y} properties will be simultaneously adjusted so that
+ the user will effectively zoom into or out of the point under the mouse
+ cursor.
+ \value rotation
+ \l{QQuickItem::rotation}{rotation} will be set to \l rotation. If
+ \l targetTransformAroundCursor is \c true, the l{QQuickItem::x}{x} and
+ \l{QQuickItem::y}{y} properties will be simultaneously adjusted so
+ that the user will effectively rotate the item around the point under
+ the mouse cursor.
+
+ The adjustment of the given target property is always scaled by \l rotationScale.
+*/
+QString QQuickWheelHandler::property() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->propertyName;
+}
+
+void QQuickWheelHandler::setProperty(const QString &propertyName)
+{
+ Q_D(QQuickWheelHandler);
+ if (d->propertyName == propertyName)
+ return;
+
+ d->propertyName = propertyName;
+ d->metaPropertyDirty = true;
+ emit propertyChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::WheelHandler::targetScaleMultiplier
+
+ The amount by which the \l target \l{QQuickItem::scale}{scale} is to be
+ multiplied whenever the \l rotation changes by 15 degrees. This
+ is relevant only when \l property is \c "scale".
+
+ The \c scale will be multiplied by
+ \c targetScaleMultiplier \sup {angleDelta * rotationScale / 15}.
+ The default is \c 2 \sup {1/3}, which means that if \l rotationScale is left
+ at its default value, and the mouse wheel is rotated by one "click"
+ (15 degrees), the \l target will be scaled by approximately 1.25; after
+ three "clicks" its size will be doubled or halved, depending on the
+ direction that the wheel is rotated. If you want to make it double or halve
+ with every 2 clicks of the wheel, set this to \c 2 \sup {1/2} (1.4142).
+ If you want to make it scale the opposite way as the wheel is rotated,
+ set \c rotationScale to a negative value.
+*/
+qreal QQuickWheelHandler::targetScaleMultiplier() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->targetScaleMultiplier;
+}
+
+void QQuickWheelHandler::setTargetScaleMultiplier(qreal targetScaleMultiplier)
+{
+ Q_D(QQuickWheelHandler);
+ if (qFuzzyCompare(d->targetScaleMultiplier, targetScaleMultiplier))
+ return;
+
+ d->targetScaleMultiplier = targetScaleMultiplier;
+ emit targetScaleMultiplierChanged();
+}
+
+/*!
+ \qmlproperty bool QtQuick::WheelHandler::targetTransformAroundCursor
+
+ Whether the \l target should automatically be repositioned in such a way
+ that it is transformed around the mouse cursor position while the
+ \l property is adjusted. The default is \c true.
+
+ If \l property is set to \c "rotation" and \l targetTransformAroundCursor
+ is \c true, then as the wheel is rotated, the \l target item will rotate in
+ place around the mouse cursor position. If \c targetTransformAroundCursor
+ is \c false, it will rotate around its
+ \l{QQuickItem::transformOrigin}{transformOrigin} instead.
+*/
+bool QQuickWheelHandler::isTargetTransformAroundCursor() const
+{
+ Q_D(const QQuickWheelHandler);
+ return d->targetTransformAroundCursor;
+}
+
+void QQuickWheelHandler::setTargetTransformAroundCursor(bool ttac)
+{
+ Q_D(QQuickWheelHandler);
+ if (d->targetTransformAroundCursor == ttac)
+ return;
+
+ d->targetTransformAroundCursor = ttac;
+ emit targetTransformAroundCursorChanged();
+}
+
+bool QQuickWheelHandler::wantsPointerEvent(QQuickPointerEvent *event)
+{
+ if (!event)
+ return false;
+ QQuickPointerScrollEvent *scroll = event->asPointerScrollEvent();
+ if (!scroll)
+ return false;
+ if (!acceptedDevices().testFlag(QQuickPointerDevice::DeviceType::TouchPad)
+ && scroll->synthSource() != Qt::MouseEventNotSynthesized)
+ return false;
+ if (!active()) {
+ switch (orientation()) {
+ case Qt::Horizontal:
+ if (qFuzzyIsNull(scroll->angleDelta().x()) && qFuzzyIsNull(scroll->pixelDelta().x()))
+ return false;
+ break;
+ case Qt::Vertical:
+ if (qFuzzyIsNull(scroll->angleDelta().y()) && qFuzzyIsNull(scroll->pixelDelta().y()))
+ return false;
+ break;
+ }
+ }
+ QQuickEventPoint *point = event->point(0);
+ if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) {
+ setPointId(point->pointId());
+ return true;
+ }
+ return false;
+}
+
+void QQuickWheelHandler::handleEventPoint(QQuickEventPoint *point)
+{
+ Q_D(QQuickWheelHandler);
+ QQuickPointerScrollEvent *event = point->pointerEvent()->asPointerScrollEvent();
+ setActive(true); // ScrollEnd will not happen unless it was already active (see setActive(false) below)
+ point->setAccepted();
+ qreal inversion = !d->invertible && event->isInverted() ? -1 : 1;
+ qreal angleDelta = inversion * qreal(orientation() == Qt::Horizontal ? event->angleDelta().x() :
+ event->angleDelta().y()) / 8;
+ d->rotation += angleDelta;
+ emit rotationChanged();
+ emit wheel(event);
+ if (!d->propertyName.isEmpty() && target()) {
+ QQuickItem *t = target();
+ // writing target()'s property is done via QMetaProperty::write() so that any registered interceptors can react.
+ if (d->propertyName == QLatin1String("scale")) {
+ qreal multiplier = qPow(d->targetScaleMultiplier, angleDelta * d->rotationScale / 15); // wheel "clicks"
+ const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition());
+ const QPointF positionWas = t->position();
+ const qreal scaleWas = t->scale();
+ const qreal activePropertyValue = scaleWas * multiplier;
+ qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta()
+ << "@" << point->position() << "in parent" << centroidParentPos
+ << "in scene" << point->scenePosition()
+ << "multiplier" << multiplier << "scale" << scaleWas
+ << "->" << activePropertyValue;
+ d->targetMetaProperty().write(t, activePropertyValue);
+ if (d->targetTransformAroundCursor) {
+ // If an interceptor intervened, scale may now be different than we asked for. Adjust accordingly.
+ multiplier = t->scale() / scaleWas;
+ const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform(
+ centroidParentPos, positionWas, QVector2D(), scaleWas, multiplier, t->rotation(), 0);
+ qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos);
+ t->setPosition(adjPos);
+ }
+ } else if (d->propertyName == QLatin1String("rotation")) {
+ const QPointF positionWas = t->position();
+ const qreal rotationWas = t->rotation();
+ const qreal activePropertyValue = rotationWas + angleDelta * d->rotationScale;
+ const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition());
+ qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta()
+ << "@" << point->position() << "in parent" << centroidParentPos
+ << "in scene" << point->scenePosition() << "rotation" << t->rotation()
+ << "->" << activePropertyValue;
+ d->targetMetaProperty().write(t, activePropertyValue);
+ if (d->targetTransformAroundCursor) {
+ // If an interceptor intervened, rotation may now be different than we asked for. Adjust accordingly.
+ const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform(
+ centroidParentPos, positionWas, QVector2D(),
+ t->scale(), 1, rotationWas, t->rotation() - rotationWas);
+ qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos);
+ t->setPosition(adjPos);
+ }
+ } else {
+ qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "scaled" << angleDelta << "total" << d->rotation << "pixel delta" << event->pixelDelta()
+ << "@" << point->position() << "in scene" << point->scenePosition() << "rotation" << t->rotation();
+ qreal delta = 0;
+ if (event->hasPixelDelta()) {
+ delta = inversion * d->rotationScale * qreal(orientation() == Qt::Horizontal ? event->pixelDelta().x() : event->pixelDelta().y());
+ qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by pixel delta" << delta << "from" << event;
+ } else {
+ delta = angleDelta * d->rotationScale;
+ qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by scaled angle delta" << delta << "from" << event;
+ }
+ bool ok = false;
+ qreal value = d->targetMetaProperty().read(t).toReal(&ok);
+ if (ok)
+ d->targetMetaProperty().write(t, value + qreal(delta));
+ else
+ qWarning() << "failed to read property" << d->propertyName << "of" << t;
+ }
+ }
+ switch (event->phase()) {
+ case Qt::ScrollEnd:
+ qCDebug(lcWheelHandler) << objectName() << "deactivating due to ScrollEnd phase";
+ setActive(false);
+ break;
+ case Qt::NoScrollPhase:
+ d->deactivationTimer.start(qRound(d->activeTimeout * 1000), this);
+ break;
+ case Qt::ScrollBegin:
+ case Qt::ScrollUpdate:
+ case Qt::ScrollMomentum:
+ break;
+ }
+}
+
+void QQuickWheelHandler::onTargetChanged(QQuickItem *oldTarget)
+{
+ Q_UNUSED(oldTarget)
+ Q_D(QQuickWheelHandler);
+ d->metaPropertyDirty = true;
+}
+
+void QQuickWheelHandler::onActiveChanged()
+{
+ Q_D(QQuickWheelHandler);
+ if (!active())
+ d->deactivationTimer.stop();
+}
+
+void QQuickWheelHandler::timerEvent(QTimerEvent *event)
+{
+ Q_D(const QQuickWheelHandler);
+ if (event->timerId() == d->deactivationTimer.timerId()) {
+ qCDebug(lcWheelHandler) << objectName() << "deactivating due to timeout";
+ setActive(false);
+ }
+}
+
+QQuickWheelHandlerPrivate::QQuickWheelHandlerPrivate()
+ : QQuickSinglePointHandlerPrivate()
+{
+}
+
+QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const
+{
+ Q_Q(const QQuickWheelHandler);
+ if (metaPropertyDirty && q->target()) {
+ if (!propertyName.isEmpty()) {
+ const QMetaObject *targetMeta = q->target()->metaObject();
+ metaProperty = targetMeta->property(
+ targetMeta->indexOfProperty(propertyName.toLocal8Bit().constData()));
+ }
+ metaPropertyDirty = false;
+ }
+ return metaProperty;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickwheelhandler_p.h b/src/quick/handlers/qquickwheelhandler_p.h
new file mode 100644
index 0000000000..f8d1c00726
--- /dev/null
+++ b/src/quick/handlers/qquickwheelhandler_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKWHEELHANDLER_H
+#define QQUICKWHEELHANDLER_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 "qquickitem.h"
+#include "qevent.h"
+#include "qquicksinglepointhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQuickWheelEvent;
+class QQuickWheelHandlerPrivate;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandler : public QQuickSinglePointHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
+ Q_PROPERTY(bool invertible READ isInvertible WRITE setInvertible NOTIFY invertibleChanged)
+ Q_PROPERTY(qreal activeTimeout READ activeTimeout WRITE setActiveTimeout NOTIFY activeTimeoutChanged)
+ Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged)
+ Q_PROPERTY(qreal rotationScale READ rotationScale WRITE setRotationScale NOTIFY rotationScaleChanged)
+ Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged)
+ Q_PROPERTY(qreal targetScaleMultiplier READ targetScaleMultiplier WRITE setTargetScaleMultiplier NOTIFY targetScaleMultiplierChanged)
+ Q_PROPERTY(bool targetTransformAroundCursor READ isTargetTransformAroundCursor WRITE setTargetTransformAroundCursor NOTIFY targetTransformAroundCursorChanged)
+
+public:
+ explicit QQuickWheelHandler(QQuickItem *parent = nullptr);
+
+ Qt::Orientation orientation() const;
+ void setOrientation(Qt::Orientation orientation);
+
+ bool isInvertible() const;
+ void setInvertible(bool invertible);
+
+ qreal activeTimeout() const;
+ void setActiveTimeout(qreal timeout);
+
+ qreal rotation() const;
+ void setRotation(qreal rotation);
+
+ qreal rotationScale() const;
+ void setRotationScale(qreal rotationScale);
+
+ QString property() const;
+ void setProperty(const QString &name);
+
+ qreal targetScaleMultiplier() const;
+ void setTargetScaleMultiplier(qreal targetScaleMultiplier);
+
+ bool isTargetTransformAroundCursor() const;
+ void setTargetTransformAroundCursor(bool ttac);
+
+Q_SIGNALS:
+ void wheel(QQuickPointerScrollEvent *event);
+
+ void orientationChanged();
+ void invertibleChanged();
+ void activeTimeoutChanged();
+ void rotationChanged();
+ void rotationScaleChanged();
+ void propertyChanged();
+ void targetScaleMultiplierChanged();
+ void targetTransformAroundCursorChanged();
+
+protected:
+ bool wantsPointerEvent(QQuickPointerEvent *event) override;
+ void handleEventPoint(QQuickEventPoint *point) override;
+ void onTargetChanged(QQuickItem *oldTarget) override;
+ void onActiveChanged() override;
+ void timerEvent(QTimerEvent *event) override;
+
+ Q_DECLARE_PRIVATE(QQuickWheelHandler)
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickWheelHandler)
+
+#endif // QQUICKWHEELHANDLER_H
diff --git a/src/quick/handlers/qquickwheelhandler_p_p.h b/src/quick/handlers/qquickwheelhandler_p_p.h
new file mode 100644
index 0000000000..d35e04c51b
--- /dev/null
+++ b/src/quick/handlers/qquickwheelhandler_p_p.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKWHEELHANDLER_P_P_H
+#define QQUICKWHEELHANDLER_P_P_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 "qquicksinglepointhandler_p_p.h"
+#include "qquickwheelhandler_p.h"
+#include <QtCore/qbasictimer.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandlerPrivate : public QQuickSinglePointHandlerPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickWheelHandler)
+
+public:
+ static QQuickWheelHandlerPrivate* get(QQuickWheelHandler *q) { return q->d_func(); }
+ static const QQuickWheelHandlerPrivate* get(const QQuickWheelHandler *q) { return q->d_func(); }
+
+ QQuickWheelHandlerPrivate();
+
+ QMetaProperty &targetMetaProperty() const;
+
+ QBasicTimer deactivationTimer;
+ qreal activeTimeout = 0.1;
+ qreal rotationScale = 1;
+ qreal rotation = 0; // in units of degrees
+ qreal targetScaleMultiplier = 1.25992104989487; // qPow(2, 1/3)
+ QString propertyName;
+ mutable QMetaProperty metaProperty;
+ Qt::Orientation orientation = Qt::Vertical;
+ mutable bool metaPropertyDirty = true;
+ bool invertible = true;
+ bool targetTransformAroundCursor = true;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKWHEELHANDLER_P_P_H
diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h
index e614b1bd6d..1a3737091f 100644
--- a/src/quick/items/qquickevents_p_p.h
+++ b/src/quick/items/qquickevents_p_p.h
@@ -605,6 +605,7 @@ private:
bool m_inverted = false;
friend class QQuickWindowPrivate;
+ friend class QQuickWheelHandler;
Q_DISABLE_COPY(QQuickPointerScrollEvent)
};
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index a6dce33f45..2a1c442653 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
@@ -120,6 +120,7 @@
#include "handlers/qquickpinchhandler_p.h"
#include "handlers/qquickpointhandler_p.h"
#include "handlers/qquicktaphandler_p.h"
+#include "handlers/qquickwheelhandler_p.h"
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcTransient)
@@ -478,6 +479,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
#if QT_CONFIG(quick_tableview)
qmlRegisterType<QQuickTableView, 14>(uri, 2, 14, "TableView");
#endif
+#if QT_CONFIG(wheelevent)
+ qmlRegisterType<QQuickWheelHandler>(uri, 2, 14, "WheelHandler");
+#endif
}
static void initResources()
diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
index 950d6835eb..4d6311bdb2 100644
--- a/tests/auto/quick/pointerhandlers/pointerhandlers.pro
+++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
@@ -10,4 +10,5 @@ qtConfig(private_tests) {
qquickpointerhandler \
qquickpointhandler \
qquicktaphandler \
+ qquickwheelhandler \
}
diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml
new file mode 100644
index 0000000000..3243515180
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.14
+
+Rectangle {
+ width: 320; height: 240
+ color: "lightsteelblue"; antialiasing: true
+ border.color: outerWheelHandler.active ? "red" : "white"
+
+ WheelHandler {
+ id: outerWheelHandler
+ objectName: "outerWheelHandler"
+ property: "x"
+ }
+
+ Rectangle {
+ width: 120; height: 120; x: 100; y: 60
+ color: "beige"; antialiasing: true
+ border.color: innerWheelHandler.active ? "red" : "white"
+
+ WheelHandler {
+ id: innerWheelHandler
+ objectName: "innerWheelHandler"
+ // should deactivate because events go to the outer handler, not because of timeout
+ activeTimeout: 1
+ property: "x"
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml
new file mode 100644
index 0000000000..b2272a57c2
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.14
+
+Rectangle {
+ width: 320; height: 240
+ color: "green"; antialiasing: true
+
+ Rectangle {
+ width: 100; height: 2; anchors.centerIn: parent
+ Rectangle {
+ width: 2; height: 100; anchors.centerIn: parent
+ }
+ }
+
+ WheelHandler { }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro b/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro
new file mode 100644
index 0000000000..7509e38dd3
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro
@@ -0,0 +1,14 @@
+CONFIG += testcase
+TARGET = tst_qquickwheelhandler
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquickwheelhandler.cpp
+OTHER_FILES = \
+ data/rectWheel.qml \
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+QT += core-private gui-private qml-private quick-private testlib
diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp b/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp
new file mode 100644
index 0000000000..2abf2ea8c3
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp
@@ -0,0 +1,344 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtTest/QSignalSpy>
+#include <QtGui/QStyleHints>
+#include <qpa/qwindowsysteminterface.h>
+#include <private/qquickwheelhandler_p.h>
+#include <QtQuick/private/qquickrectangle_p.h>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlcontext.h>
+#include "../../../shared/util.h"
+#include "../../shared/viewtestutil.h"
+
+Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
+
+class tst_QQuickWheelHandler: public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_QQuickWheelHandler() { }
+
+private slots:
+ void singleHandler_data();
+ void singleHandler();
+ void nestedHandler_data();
+ void nestedHandler();
+
+private:
+ void sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta,
+ QPoint pixelDelta = QPoint(), Qt::KeyboardModifiers modifiers = Qt::NoModifier,
+ Qt::ScrollPhase phase = Qt::NoScrollPhase, bool inverted = false);
+};
+
+void tst_QQuickWheelHandler::sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta,
+ QPoint pixelDelta, Qt::KeyboardModifiers modifiers, Qt::ScrollPhase phase, bool inverted)
+{
+ QWheelEvent wheelEvent(pos, window.mapToGlobal(pos), pixelDelta, angleDelta,
+ Qt::NoButton, modifiers, phase, inverted);
+ QGuiApplication::sendEvent(&window, &wheelEvent);
+ qApp->processEvents();
+}
+
+void tst_QQuickWheelHandler::singleHandler_data()
+{
+ // handler properties
+ QTest::addColumn<Qt::Orientation>("orientation");
+ QTest::addColumn<bool>("invertible");
+ QTest::addColumn<int>("rotationScale");
+ QTest::addColumn<QString>("property");
+ QTest::addColumn<qreal>("targetScaleMultiplier");
+ QTest::addColumn<bool>("targetTransformAroundCursor");
+ // event
+ QTest::addColumn<QPoint>("eventPos");
+ QTest::addColumn<QPoint>("eventAngleDelta");
+ QTest::addColumn<QPoint>("eventPixelDelta");
+ QTest::addColumn<Qt::KeyboardModifiers>("eventModifiers");
+ QTest::addColumn<bool>("eventPhases");
+ QTest::addColumn<bool>("eventInverted");
+ // result
+ QTest::addColumn<QPoint>("expectedPosition");
+ QTest::addColumn<qreal>("expectedScale");
+ QTest::addColumn<int>("expectedRotation");
+
+ // move the item
+ QTest::newRow("vertical wheel angle delta to adjust x")
+ << Qt::Vertical << false << 1 << "x" << 1.5 << true
+ << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(15, 0) << 1.0 << 0;
+ QTest::newRow("horizontal wheel angle delta to adjust y")
+ << Qt::Horizontal << false << 1 << "y" << 1.5 << false
+ << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(0, -45) << 1.0 << 0;
+ QTest::newRow("vertical wheel angle delta to adjust y, amplified and inverted")
+ << Qt::Vertical << true << 4 << "y" << 1.5 << true
+ << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << true
+ << QPoint(0, 30) << 1.0 << 0;
+ QTest::newRow("horizontal wheel angle delta to adjust x, amplified and reversed")
+ << Qt::Horizontal << false << -4 << "x" << 1.5 << false
+ << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(-30, 0) << 1.0 << 0;
+ QTest::newRow("vertical wheel pixel delta to adjust x")
+ << Qt::Vertical << false << 1 << "x" << 1.5 << true
+ << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
+ << QPoint(20, 0) << 1.0 << 0;
+ QTest::newRow("horizontal wheel pixel delta to adjust y")
+ << Qt::Horizontal << false << 1 << "y" << 1.5 << false
+ << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
+ << QPoint(0, 20) << 1.0 << 0;
+ QTest::newRow("vertical wheel pixel delta to adjust y, amplified and inverted")
+ << Qt::Vertical << true << 4 << "y" << 1.5 << true
+ << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << true
+ << QPoint(0, 80) << 1.0 << 0;
+ QTest::newRow("horizontal wheel pixel delta to adjust x, amplified and reversed")
+ << Qt::Horizontal << false << -4 << "x" << 1.5 << false
+ << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
+ << QPoint(-80, 0) << 1.0 << 0;
+
+ // scale the item
+ QTest::newRow("vertical wheel angle delta to adjust scale")
+ << Qt::Vertical << false << 1 << "scale" << 1.5 << true
+ << QPoint(50, 32) << QPoint(360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(55, 44) << 1.5 << 0;
+ QTest::newRow("horizontal wheel angle delta to adjust scale, amplified and reversed, don't adjust position")
+ << Qt::Horizontal << false << -2 << "scale" << 1.5 << false
+ << QPoint(50, 32) << QPoint(-240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(0, 0) << 5.0625 << 0;
+
+ // rotate the item
+ QTest::newRow("vertical wheel angle delta to adjust rotation")
+ << Qt::Vertical << false << 1 << "rotation" << 1.5 << true
+ << QPoint(50, 32) << QPoint(360, -120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(19, -31) << 1.0 << -15;
+ QTest::newRow("horizontal wheel angle delta to adjust rotation, amplified and reversed, don't adjust position")
+ << Qt::Horizontal << false << -2 << "rotation" << 1.5 << false
+ << QPoint(80, 80) << QPoint(240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
+ << QPoint(0, 0) << 1.0 << -60;
+}
+
+void tst_QQuickWheelHandler::singleHandler()
+{
+ // handler properties
+ QFETCH(Qt::Orientation, orientation);
+ QFETCH(bool, invertible);
+ QFETCH(int, rotationScale);
+ QFETCH(QString, property);
+ QFETCH(qreal, targetScaleMultiplier);
+ QFETCH(bool, targetTransformAroundCursor);
+ // event
+ QFETCH(QPoint, eventPos);
+ QFETCH(QPoint, eventAngleDelta);
+ QFETCH(QPoint, eventPixelDelta);
+ QFETCH(Qt::KeyboardModifiers, eventModifiers);
+ QFETCH(bool, eventPhases);
+ QFETCH(bool, eventInverted);
+ // result
+ QFETCH(QPoint, expectedPosition);
+ QFETCH(qreal, expectedScale);
+ QFETCH(int, expectedRotation);
+
+ QQuickView window;
+ QByteArray errorMessage;
+ QVERIFY2(QQuickTest::initView(window, testFileUrl("rectWheel.qml"), true, &errorMessage), errorMessage.constData());
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ QQuickItem *rect = window.rootObject();
+ QVERIFY(rect != nullptr);
+ QQuickWheelHandler *handler = rect->findChild<QQuickWheelHandler*>();
+ QVERIFY(handler != nullptr);
+ handler->setOrientation(orientation);
+ handler->setInvertible(invertible);
+ handler->setRotationScale(rotationScale);
+ handler->setProperty(property);
+ handler->setTargetScaleMultiplier(targetScaleMultiplier);
+ handler->setTargetTransformAroundCursor(targetTransformAroundCursor);
+ QSignalSpy activeChangedSpy(handler, SIGNAL(activeChanged()));
+
+ if (eventPhases) {
+ sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollBegin, eventInverted);
+ sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers, Qt::ScrollUpdate, eventInverted);
+ } else {
+ sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers, Qt::NoScrollPhase, eventInverted);
+ }
+ QCOMPARE(rect->position().toPoint(), expectedPosition);
+ QCOMPARE(activeChangedSpy.count(), 1);
+ QCOMPARE(handler->active(), true);
+ QCOMPARE(rect->scale(), expectedScale);
+ QCOMPARE(rect->rotation(), expectedRotation);
+ if (!eventPhases) {
+ QTRY_COMPARE(handler->active(), false);
+ QCOMPARE(activeChangedSpy.count(), 2);
+ }
+
+ // restore by rotating backwards
+ if (eventPhases) {
+ sendWheelEvent(window, eventPos, eventAngleDelta * -1, eventPixelDelta * -1, eventModifiers, Qt::ScrollUpdate, eventInverted);
+ sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollEnd, eventInverted);
+ } else {
+ sendWheelEvent(window, eventPos, eventAngleDelta * -1, eventPixelDelta * -1, eventModifiers, Qt::NoScrollPhase, eventInverted);
+ }
+ QCOMPARE(activeChangedSpy.count(), eventPhases ? 2 : 3);
+ QCOMPARE(handler->active(), !eventPhases);
+ QCOMPARE(rect->position().toPoint(), QPoint(0, 0));
+ QCOMPARE(rect->scale(), 1);
+ QCOMPARE(rect->rotation(), 0);
+}
+
+void tst_QQuickWheelHandler::nestedHandler_data()
+{
+ // handler properties
+ QTest::addColumn<Qt::Orientation>("orientation");
+ QTest::addColumn<bool>("invertible");
+ QTest::addColumn<int>("rotationScale");
+ QTest::addColumn<QString>("property");
+ QTest::addColumn<qreal>("targetScaleMultiplier");
+ QTest::addColumn<bool>("targetTransformAroundCursor");
+ // event
+ QTest::addColumn<QPoint>("eventPos");
+ QTest::addColumn<QPoint>("eventAngleDelta");
+ QTest::addColumn<QPoint>("eventPixelDelta");
+ QTest::addColumn<Qt::KeyboardModifiers>("eventModifiers");
+ QTest::addColumn<bool>("eventPhases");
+ QTest::addColumn<bool>("eventInverted");
+ QTest::addColumn<int>("eventCount");
+ // result: inner handler
+ QTest::addColumn<QPoint>("innerPosition");
+ QTest::addColumn<qreal>("innerScale");
+ QTest::addColumn<int>("innerRotation");
+ // result: outer handler
+ QTest::addColumn<QPoint>("outerPosition");
+ QTest::addColumn<qreal>("outerScale");
+ QTest::addColumn<int>("outerRotation");
+
+ // move the item
+ QTest::newRow("vertical wheel angle delta to adjust x")
+ << Qt::Vertical << false << 1 << "x" << 1.5 << true
+ << QPoint(160, 120) << QPoint(120, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false << 10
+ << QPoint(175,60) << 1.0 << 0
+ << QPoint(75, 0) << 1.0 << 0;
+ QTest::newRow("horizontal wheel pixel delta to adjust y")
+ << Qt::Horizontal << false << 1 << "y" << 1.5 << false
+ << QPoint(160, 120) << QPoint(120, 120) << QPoint(50, 50) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false << 4
+ << QPoint(100, 160) << 1.0 << 0
+ << QPoint(0, 100) << 1.0 << 0;
+}
+
+void tst_QQuickWheelHandler::nestedHandler()
+{
+ // handler properties
+ QFETCH(Qt::Orientation, orientation);
+ QFETCH(bool, invertible);
+ QFETCH(int, rotationScale);
+ QFETCH(QString, property);
+ QFETCH(qreal, targetScaleMultiplier);
+ QFETCH(bool, targetTransformAroundCursor);
+ // event
+ QFETCH(QPoint, eventPos);
+ QFETCH(QPoint, eventAngleDelta);
+ QFETCH(QPoint, eventPixelDelta);
+ QFETCH(Qt::KeyboardModifiers, eventModifiers);
+ QFETCH(bool, eventPhases);
+ QFETCH(bool, eventInverted);
+ QFETCH(int, eventCount);
+ // result: inner handler
+ QFETCH(QPoint, innerPosition);
+ QFETCH(qreal, innerScale);
+ QFETCH(int, innerRotation);
+ // result: outer handler
+ QFETCH(QPoint, outerPosition);
+ QFETCH(qreal, outerScale);
+ QFETCH(int, outerRotation);
+
+ QQuickView window;
+ QByteArray errorMessage;
+ QVERIFY2(QQuickTest::initView(window, testFileUrl("nested.qml"), true, &errorMessage), errorMessage.constData());
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ QQuickItem *outerRect = window.rootObject();
+ QVERIFY(outerRect != nullptr);
+ QQuickWheelHandler *outerHandler = outerRect->findChild<QQuickWheelHandler*>("outerWheelHandler");
+ QVERIFY(outerHandler != nullptr);
+ QQuickWheelHandler *innerHandler = outerRect->findChild<QQuickWheelHandler*>("innerWheelHandler");
+ QVERIFY(innerHandler != nullptr);
+ QQuickItem *innerRect = innerHandler->parentItem();
+ QVERIFY(innerRect != nullptr);
+ innerHandler->setOrientation(orientation);
+ innerHandler->setInvertible(invertible);
+ innerHandler->setRotationScale(rotationScale);
+ innerHandler->setProperty(property);
+ innerHandler->setTargetScaleMultiplier(targetScaleMultiplier);
+ innerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor);
+ outerHandler->setOrientation(orientation);
+ outerHandler->setInvertible(invertible);
+ outerHandler->setRotationScale(rotationScale);
+ outerHandler->setProperty(property);
+ outerHandler->setTargetScaleMultiplier(targetScaleMultiplier);
+ outerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor);
+ QSignalSpy innerActiveChangedSpy(innerHandler, SIGNAL(activeChanged()));
+ QSignalSpy outerActiveChangedSpy(outerHandler, SIGNAL(activeChanged()));
+
+ if (eventPhases)
+ sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollBegin, eventInverted);
+ for (int i = 0; i < eventCount; ++i)
+ sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers,
+ (eventPhases ? Qt::ScrollUpdate : Qt::NoScrollPhase), eventInverted);
+ QCOMPARE(innerRect->position().toPoint(), innerPosition);
+
+ /*
+ If outer is activated, maybe inner should be deactivated? But the event
+ doesn't get delivered to inner anymore, so it doesn't find out that
+ it's no longer getting events. It will get deactivated after the
+ timeout, just as if the user stopped scrolling.
+
+ This situation is similar to QTBUG-50199, but it's questionable whether
+ that was really so important. So far in Qt Quick, if you move the mouse
+ while wheel momentum continues, or if the item moves out from under the
+ mouse, a different item starts getting the events immediately. In
+ non-Qt applications on most OSes, that's quite normal.
+ */
+ // QCOMPARE(innerActiveChangedSpy.count(), 2);
+ // QCOMPARE(innerHandler->active(), false);
+ QCOMPARE(innerRect->scale(), innerScale);
+ QCOMPARE(innerRect->rotation(), innerRotation);
+ QCOMPARE(outerRect->position().toPoint(), outerPosition);
+ QCOMPARE(outerActiveChangedSpy.count(), 1);
+ QCOMPARE(outerHandler->active(), true);
+ QCOMPARE(outerRect->scale(), outerScale);
+ QCOMPARE(outerRect->rotation(), outerRotation);
+ if (!eventPhases) {
+ QTRY_COMPARE(outerHandler->active(), false);
+ QCOMPARE(outerActiveChangedSpy.count(), 2);
+ }
+}
+
+QTEST_MAIN(tst_QQuickWheelHandler)
+
+#include "tst_qquickwheelhandler.moc"
diff --git a/tests/manual/pointer/content/FakeFlickable.qml b/tests/manual/pointer/content/FakeFlickable.qml
index ffb5c4e914..636399ba2c 100644
--- a/tests/manual/pointer/content/FakeFlickable.qml
+++ b/tests/manual/pointer/content/FakeFlickable.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the manual tests of the Qt Toolkit.
@@ -26,10 +26,12 @@
**
****************************************************************************/
-import QtQuick 2.12
+import QtQuick 2.14
+import Qt.labs.animation 1.0
Item {
id: root
+ objectName: "viewport"
default property alias data: __contentItem.data
property alias velocity: anim.velocity
property alias contentX: __contentItem.x // sign is reversed compared to Flickable.contentX
@@ -45,52 +47,81 @@ Item {
width: childrenRect.width
height: childrenRect.height
- property real xlimit: root.width - __contentItem.width
- property real ylimit: root.height - __contentItem.height
+ BoundaryRule on x {
+ id: xbr
+ minimum: root.width - __contentItem.width
+ maximum: 0
+ minimumOvershoot: 100
+ maximumOvershoot: 100
+ overshootFilter: BoundaryRule.Peak
+ }
- function returnToBounds() {
- if (x > 0) {
- returnXAnim.to = 0
- returnXAnim.start()
- } else if (x < xlimit) {
- returnXAnim.to = xlimit
- returnXAnim.start()
- }
- if (y > 0) {
- returnYAnim.to = 0
- returnYAnim.start()
- } else if (y < ylimit) {
- returnYAnim.to = ylimit
- returnYAnim.start()
- }
+ BoundaryRule on y {
+ id: ybr
+ minimum: root.height - __contentItem.height
+ maximum: 0
+ minimumOvershoot: 100
+ maximumOvershoot: 100
+ overshootFilter: BoundaryRule.Peak
}
DragHandler {
id: dragHandler
- onActiveChanged: if (!active) anim.restart(centroid.velocity)
+ onActiveChanged:
+ if (active) {
+ anim.stop()
+ root.flickStarted()
+ } else {
+ var vel = centroid.velocity
+ if (xbr.returnToBounds())
+ vel.x = 0
+ if (ybr.returnToBounds())
+ vel.y = 0
+ if (vel.x !== 0 || vel.y !== 0)
+ anim.restart(vel)
+ else
+ root.flickEnded()
+ }
+ }
+ WheelHandler {
+ rotationScale: 15
+ property: "x"
+ orientation: Qt.Horizontal
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ onActiveChanged:
+ // emitting signals in both instances is redundant but hard to avoid
+ // when the touchpad is flicking along both axes
+ if (active) {
+ anim.stop()
+ root.flickStarted()
+ } else {
+ xbr.returnToBounds()
+ root.flickEnded()
+ }
+ }
+ WheelHandler {
+ rotationScale: 15
+ property: "y"
+ orientation: Qt.Vertical
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ onActiveChanged:
+ if (active) {
+ anim.stop()
+ root.flickStarted()
+ } else {
+ ybr.returnToBounds()
+ root.flickEnded()
+ }
}
MomentumAnimation {
id: anim
target: __contentItem
onStarted: root.flickStarted()
onStopped: {
- __contentItem.returnToBounds()
+ xbr.returnToBounds()
+ ybr.returnToBounds()
root.flickEnded()
}
}
- NumberAnimation {
- id: returnXAnim
- target: __contentItem
- property: "x"
- duration: 200
- easing.type: Easing.OutQuad
- }
- NumberAnimation {
- id: returnYAnim
- target: __contentItem
- property: "y"
- duration: 200
- easing.type: Easing.OutQuad
- }
}
}
diff --git a/tests/manual/pointer/content/Slider.qml b/tests/manual/pointer/content/Slider.qml
index e3e02b0a2f..beb84b176b 100644
--- a/tests/manual/pointer/content/Slider.qml
+++ b/tests/manual/pointer/content/Slider.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the manual tests of the Qt Toolkit.
@@ -26,7 +26,8 @@
**
****************************************************************************/
-import QtQuick 2.12
+import QtQuick 2.14
+import Qt.labs.animation 1.0
Item {
id: root
@@ -42,8 +43,16 @@ Item {
objectName: label.text + " DragHandler"
target: knob
xAxis.enabled: false
- yAxis.minimum: slot.y
- yAxis.maximum: slot.height + slot.y - knob.height
+ }
+
+ WheelHandler {
+ id: wheelHandler
+ objectName: label.text + " WheelHandler"
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ invertible: false // Don't let the system "natural scrolling" setting affect this
+ rotationScale: -0.5 // But make it go consistently in the same direction as the fingers or wheel, a bit slow
+ target: knob
+ property: "y"
}
Rectangle {
@@ -82,10 +91,10 @@ Item {
height: root.width / 2
width: implicitWidth / implicitHeight * height
property bool programmatic: false
- property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum)
- onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier
+ property real multiplier: root.maximumValue / (ybr.maximum - ybr.minimum)
+ onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - ybr.minimum) * multiplier
transformOrigin: Item.Center
- function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier }
+ function setValue(value) { knob.y = ybr.maximum - value / knob.multiplier }
TapHandler {
id: tap
objectName: label.text + " TapHandler"
@@ -95,6 +104,11 @@ Item {
root.tapped
}
}
+ BoundaryRule on y {
+ id: ybr
+ minimum: slot.y
+ maximum: slot.height + slot.y - knob.height
+ }
}
Text {
diff --git a/tests/manual/pointer/fakeFlickable.qml b/tests/manual/pointer/fakeFlickable.qml
index 3007848c9f..be52e4dbaa 100644
--- a/tests/manual/pointer/fakeFlickable.qml
+++ b/tests/manual/pointer/fakeFlickable.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the manual tests of the Qt Toolkit.
@@ -30,6 +30,7 @@ import QtQuick 2.12
import "content"
Rectangle {
+ id: root
color: "#444"
width: 480
height: 640
@@ -49,8 +50,14 @@ Rectangle {
", parent " + parent + " geom " + parent.width + "x" + parent.height)
}
- onFlickStarted: console.log("flick started with velocity " + velocity)
- onFlickEnded: console.log("flick ended with velocity " + velocity)
+ onFlickStarted: {
+ root.border.color = "green"
+ console.log("flick started with velocity " + velocity)
+ }
+ onFlickEnded: {
+ root.border.color = "transparent"
+ console.log("flick ended with velocity " + velocity)
+ }
Component.onCompleted: {
var request = new XMLHttpRequest()
diff --git a/tests/manual/pointer/map.qml b/tests/manual/pointer/map.qml
index c400874d58..0e815ccd9c 100644
--- a/tests/manual/pointer/map.qml
+++ b/tests/manual/pointer/map.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the manual tests of the Qt Toolkit.
@@ -26,7 +26,7 @@
**
****************************************************************************/
-import QtQuick 2.12
+import QtQuick 2.14
Item {
width: 640
@@ -41,6 +41,20 @@ Item {
height: image.height
transform: Rotation { id: tilt; origin.x: width / 2; origin.y: height / 2; axis { x: 1; y: 0; z: 0 } }
+ WheelHandler {
+ id: wheelHandler
+ objectName: "vertical mouse wheel for scaling"
+ property: "scale"
+ onWheel: console.log("rotation " + event.angleDelta + " scaled " + rotation + " @ " + point.position + " => " + map.scale)
+ }
+
+ WheelHandler {
+ id: horizontalWheelHandler
+ objectName: "horizontal mouse wheel for side-scrolling"
+ property: "x"
+ orientation: Qt.Horizontal
+ }
+
Image {
id: image
anchors.centerIn: parent
diff --git a/tests/manual/pointer/pinchAndWheel.qml b/tests/manual/pointer/pinchAndWheel.qml
new file mode 100644
index 0000000000..0944717bb7
--- /dev/null
+++ b/tests/manual/pointer/pinchAndWheel.qml
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.14
+import Qt.labs.animation 1.0
+import "content"
+
+Rectangle {
+ id: root
+ width: 1024; height: 600
+ color: "#eee"
+
+ CheckBox {
+ id: cbTouchpadEnabled
+ x: 10; y: 6
+ label: "Touchpad wheel emulation enabled"
+ }
+
+ CheckBox {
+ id: cbHorzWheelEnabled
+ x: cbTouchpadEnabled.width + 20; y: 6
+ label: "Horizontal wheel rotation"
+ }
+
+ function getTransformationDetails(item, pinchhandler) {
+ return "\n\npinch.scale:" + pinchhandler.scale.toFixed(2)
+ + "\npinch.rotation:" + pinchhandler.rotation.toFixed(2)
+ + "°\npinch.translation:" + "(" + pinchhandler.translation.x.toFixed(2) + "," + pinchhandler.translation.y.toFixed(2) + ")"
+ + "\nscale wheel.rotation:" + scaleWheelHandler.rotation.toFixed(2)
+ + "°\nhorizontal wheel.rotation:" + horizontalRotationWheelHandler.rotation.toFixed(2)
+ + "°\ncontrol-rotation wheel.rotation:" + controlRotationWheelHandler.rotation.toFixed(2)
+ + "°\nrect.scale: " + item.scale.toFixed(2)
+ + "\nrect.rotation: " + item.rotation.toFixed(2)
+ + "°\nrect.position: " + "(" + item.x.toFixed(2) + "," + item.y.toFixed(2) + ")"
+ }
+
+ Rectangle {
+ id: transformable
+ width: parent.width - 100; height: parent.height - 100; x: 50; y: 50
+ color: "#ffe0e0e0"
+ antialiasing: true
+
+ PinchHandler {
+ id: parentPinch
+ objectName: "parent pinch"
+ minimumScale: 0.5
+ maximumScale: 3
+ }
+
+ WheelHandler {
+ id: scaleWheelHandler
+ objectName: "mouse wheel for scaling"
+ acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse
+ acceptedModifiers: Qt.NoModifier
+ property: "scale"
+ onActiveChanged: if (!active) sbr.returnToBounds();
+ onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.scale)
+ }
+
+ BoundaryRule on scale {
+ id: sbr
+ minimum: 0.1
+ maximum: 2
+ minimumOvershoot: 0.05
+ maximumOvershoot: 0.05
+ }
+
+ BoundaryRule on rotation {
+ id: rbr
+ minimum: -90
+ maximum: 360
+ minimumOvershoot: 10
+ maximumOvershoot: 10
+ }
+
+ WheelHandler {
+ id: horizontalRotationWheelHandler
+ enabled: cbHorzWheelEnabled.checked
+ objectName: "horizontal mouse wheel for rotation"
+ acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse
+ acceptedModifiers: Qt.NoModifier
+ property: "rotation"
+ orientation: Qt.Horizontal
+ onActiveChanged: if (!active) rbr.returnToBounds()
+ onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.rotation)
+ }
+
+ WheelHandler {
+ id: controlRotationWheelHandler
+ objectName: "control-mouse wheel for rotation"
+ acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse
+ acceptedModifiers: Qt.ControlModifier
+ property: "rotation"
+ orientation: Qt.Vertical // already the default
+ // TODO returnToBounds() causes trouble because position isn't being adjusted when this happens
+ onActiveChanged: if (!active) rbr.returnToBounds()
+ onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.rotation)
+ }
+
+ HoverHandler {
+ id: hover
+ acceptedDevices: PointerDevice.AllDevices
+ property var scenePoint: transformable.mapToItem(root, point.position.x, point.position.y)
+ }
+
+ Text {
+ text: "Pinch with 2 fingers to scale, rotate and translate\nMouse wheel to scale, Ctrl+mouse wheel or horizontal wheel to rotate"
+ + getTransformationDetails(parent, parentPinch)
+ }
+ }
+
+ Rectangle {
+ width: 1; height: parent.height
+ color: "blue"
+ x: hover.scenePoint.x
+ Text {
+ x: implicitWidth / -2; style: Text.Outline; styleColor: "white"
+ y: 30
+ color: "blue"
+ text: "outer " + parent.x.toFixed(2) + " inner " + hover.point.position.x.toFixed(2)
+ }
+ }
+
+ Rectangle {
+ width: parent.width; height: 1
+ color: "blue"
+ y: hover.scenePoint.y
+ Text {
+ x: 45
+ y: implicitHeight / -2; style: Text.Outline; styleColor: "white"
+ color: "blue"
+ text: "outer " + parent.y.toFixed(2) + " inner " + hover.point.position.y.toFixed(2)
+ }
+ }
+}