aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/imports/handlers/handlers.pro11
-rw-r--r--src/imports/handlers/plugin.cpp88
-rw-r--r--src/imports/handlers/qmldir5
-rw-r--r--src/imports/imports.pro1
-rw-r--r--src/quick/handlers/handlers.pri19
-rw-r--r--src/quick/handlers/qquickdraghandler.cpp169
-rw-r--r--src/quick/handlers/qquickdraghandler_p.h134
-rw-r--r--src/quick/handlers/qquickhandlersmodule.cpp96
-rw-r--r--src/quick/handlers/qquickhandlersmodule_p.h68
-rw-r--r--src/quick/handlers/qquickmultipointerhandler.cpp219
-rw-r--r--src/quick/handlers/qquickmultipointerhandler_p.h112
-rw-r--r--src/quick/handlers/qquickpinchhandler.cpp302
-rw-r--r--src/quick/handlers/qquickpinchhandler_p.h170
-rw-r--r--src/quick/handlers/qquickpointerdevicehandler.cpp109
-rw-r--r--src/quick/handlers/qquickpointerdevicehandler_p.h97
-rw-r--r--src/quick/handlers/qquickpointerhandler.cpp190
-rw-r--r--src/quick/handlers/qquickpointerhandler_p.h121
-rw-r--r--src/quick/handlers/qquickpointersinglehandler.cpp180
-rw-r--r--src/quick/handlers/qquickpointersinglehandler_p.h127
-rw-r--r--src/quick/items/qquickevents.cpp217
-rw-r--r--src/quick/items/qquickevents_p_p.h87
-rw-r--r--src/quick/items/qquickflickable.cpp1
-rw-r--r--src/quick/items/qquickitem.cpp22
-rw-r--r--src/quick/items/qquickitem_p.h4
-rw-r--r--src/quick/items/qquickwindow.cpp79
-rw-r--r--src/quick/items/qquickwindow_p.h2
-rw-r--r--src/quick/quick.pro1
-rw-r--r--tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml60
-rw-r--r--tests/auto/quick/pointerhandlers/pointerhandlers.pro7
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml22
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro16
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp465
-rw-r--r--tests/auto/quick/qquickwindow/tst_qquickwindow.cpp88
-rw-r--r--tests/auto/quick/quick.pro1
-rw-r--r--tests/manual/pointer/content/Slider.qml117
-rw-r--r--tests/manual/pointer/joystick.qml75
-rw-r--r--tests/manual/pointer/main.cpp51
-rw-r--r--tests/manual/pointer/main.qml61
-rw-r--r--tests/manual/pointer/map.qml74
-rw-r--r--tests/manual/pointer/map2.qml91
-rw-r--r--tests/manual/pointer/mixer.qml72
-rw-r--r--tests/manual/pointer/pinchHandler.qml130
-rw-r--r--tests/manual/pointer/pointer.pro7
-rw-r--r--tests/manual/pointer/qml.qrc21
-rw-r--r--tests/manual/pointer/resources/arrowhead.pngbin0 -> 883 bytes
-rw-r--r--tests/manual/pointer/resources/grabbing-location.svg1
-rw-r--r--tests/manual/pointer/resources/map.svgzbin0 -> 27956 bytes
-rw-r--r--tests/manual/pointer/resources/mixer-knob.pngbin0 -> 8555 bytes
-rw-r--r--tests/manual/pointer/resources/mouse.pngbin0 -> 1919 bytes
-rw-r--r--tests/manual/pointer/resources/mouse_left.pngbin0 -> 740 bytes
-rw-r--r--tests/manual/pointer/resources/mouse_middle.pngbin0 -> 558 bytes
-rw-r--r--tests/manual/pointer/resources/mouse_right.pngbin0 -> 906 bytes
-rw-r--r--tests/manual/pointer/resources/redball.pngbin0 -> 10002 bytes
-rw-r--r--tests/manual/pointer/singlePointHandlerProperties.qml162
54 files changed, 4050 insertions, 102 deletions
diff --git a/src/imports/handlers/handlers.pro b/src/imports/handlers/handlers.pro
new file mode 100644
index 0000000000..0e32644773
--- /dev/null
+++ b/src/imports/handlers/handlers.pro
@@ -0,0 +1,11 @@
+CXX_MODULE = qml
+TARGET = handlersplugin
+TARGETPATH = Qt/labs/handlers
+IMPORT_VERSION = 1.0
+
+SOURCES += \
+ plugin.cpp
+
+QT += quick-private qml-private
+
+load(qml_plugin)
diff --git a/src/imports/handlers/plugin.cpp b/src/imports/handlers/plugin.cpp
new file mode 100644
index 0000000000..21b9764611
--- /dev/null
+++ b/src/imports/handlers/plugin.cpp
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins 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 <QtQml/qqmlextensionplugin.h>
+
+#include <private/qquickhandlersmodule_p.h>
+
+static void initResources()
+{
+#ifdef QT_STATIC
+ Q_INIT_RESOURCE(qmake_Qt_labs_handlers_1);
+#endif
+}
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmlmodule Qt.labs.handlers 1.0
+ \title Qt Quick Pointer Handlers
+ \ingroup qmlmodules
+ \brief Provides QML types for handling pointer events.
+
+ This QML module contains types for handling pointer events, which are an abstraction
+ of mouse, touch and tablet events.
+
+ To use the types in this module, import the module with the following line:
+
+ \code
+ import Qt.labs.handlers 1.0
+ \endcode
+*/
+
+
+//![class decl]
+class QtQuickHandlersPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+public:
+ QtQuickHandlersPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); }
+ void registerTypes(const char *uri) Q_DECL_OVERRIDE
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.handlers"));
+ Q_UNUSED(uri);
+ QQuickHandlersModule::defineModule();
+ }
+};
+//![class decl]
+
+QT_END_NAMESPACE
+
+#include "plugin.moc"
diff --git a/src/imports/handlers/qmldir b/src/imports/handlers/qmldir
new file mode 100644
index 0000000000..4896348c2e
--- /dev/null
+++ b/src/imports/handlers/qmldir
@@ -0,0 +1,5 @@
+module Qt.labs.handlers
+plugin handlersplugin
+classname QtQuickHandlersPlugin
+typeinfo plugins.qmltypes
+
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
index c49c05956d..0d84bf538f 100644
--- a/src/imports/imports.pro
+++ b/src/imports/imports.pro
@@ -12,6 +12,7 @@ qtConfig(statemachine): SUBDIRS += statemachine
qtHaveModule(quick) {
SUBDIRS += \
+ handlers \
layouts \
qtquick2 \
window \
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri
new file mode 100644
index 0000000000..f32f330d4e
--- /dev/null
+++ b/src/quick/handlers/handlers.pri
@@ -0,0 +1,19 @@
+HEADERS += \
+ $$PWD/qquickdraghandler_p.h \
+ $$PWD/qquickpointerhandler_p.h \
+ $$PWD/qquickpointerdevicehandler_p.h \
+ $$PWD/qquickmultipointerhandler_p.h \
+ $$PWD/qquickpinchhandler_p.h \
+ $$PWD/qquickpointersinglehandler_p.h \
+ $$PWD/qquickhandlersmodule_p.h \
+ $$PWD/qquickpointerdevicehandler_p.h \
+ $$PWD/qquickhandlersmodule_p.h \
+
+SOURCES += \
+ $$PWD/qquickdraghandler.cpp \
+ $$PWD/qquickpointerhandler.cpp \
+ $$PWD/qquickpointerdevicehandler.cpp \
+ $$PWD/qquickmultipointerhandler.cpp \
+ $$PWD/qquickpinchhandler.cpp \
+ $$PWD/qquickpointersinglehandler.cpp \
+ $$PWD/qquickhandlersmodule.cpp \
diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp
new file mode 100644
index 0000000000..6caee6be13
--- /dev/null
+++ b/src/quick/handlers/qquickdraghandler.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickdraghandler_p.h"
+#include <private/qquickwindow_p.h>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype DragHandler
+ \instantiates QQuickDragHandler
+ \inqmlmodule QtQuick
+ \ingroup qtquick-handlers
+ \brief Handler for dragging
+
+ DragHandler is a handler that is used to interactively move an Item.
+
+ At this time, drag-and-drop is not yet supported.
+
+ \sa Drag, MouseArea
+*/
+
+QQuickDragHandler::QQuickDragHandler(QObject *parent)
+ : QQuickPointerSingleHandler(parent)
+{
+}
+
+QQuickDragHandler::~QQuickDragHandler()
+{
+}
+
+bool QQuickDragHandler::wantsEventPoint(QQuickEventPoint *point)
+{
+ // If we've already been interested in a point, stay interested, even if it has strayed outside bounds.
+ return ((point->state() != QQuickEventPoint::Pressed && pointId() == point->pointId())
+ || QQuickPointerSingleHandler::wantsEventPoint(point));
+}
+
+void QQuickDragHandler::handleEventPoint(QQuickEventPoint *point)
+{
+ point->setAccepted();
+ switch (point->state()) {
+ case QQuickEventPoint::Pressed:
+ if (target() && target()->parentItem())
+ m_startPos = target()->parentItem()->mapToScene(target()->position());
+ break;
+ case QQuickEventPoint::Updated: {
+ QPointF delta = point->scenePos() - point->scenePressPos();
+ if (!m_xAxis.enabled())
+ delta.setX(0);
+ if (!m_yAxis.enabled())
+ delta.setY(0);
+ if (active()) {
+ setTranslation(delta);
+ if (target() && target()->parentItem()) {
+ QPointF pos = target()->parentItem()->mapFromScene(m_startPos + delta);
+ enforceAxisConstraints(&pos);
+ target()->setPosition(pos);
+ }
+ } else if ((m_xAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, point)) ||
+ (m_yAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, point))) {
+ setGrab(point, true);
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+void QQuickDragHandler::enforceConstraints()
+{
+ if (!target() || !target()->parentItem())
+ return;
+ QPointF pos = target()->position();
+ QPointF copy(pos);
+ enforceAxisConstraints(&pos);
+ if (pos != copy)
+ target()->setPosition(pos);
+}
+
+void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos)
+{
+ if (m_xAxis.enabled())
+ localPos->setX(qBound(m_xAxis.minimum(), localPos->x(), m_xAxis.maximum()));
+ if (m_yAxis.enabled())
+ localPos->setY(qBound(m_yAxis.minimum(), localPos->y(), m_yAxis.maximum()));
+}
+
+void QQuickDragHandler::setTranslation(const QPointF &trans)
+{
+ if (trans == m_translation) // fuzzy compare?
+ return;
+ m_translation = trans;
+ emit translationChanged();
+}
+
+
+QQuickDragAxis::QQuickDragAxis()
+ : m_minimum(-DBL_MAX)
+ , m_maximum(DBL_MAX)
+ , m_enabled(true)
+{
+}
+
+void QQuickDragAxis::setMinimum(qreal minimum)
+{
+ if (m_minimum == minimum)
+ return;
+
+ m_minimum = minimum;
+ emit minimumChanged();
+}
+
+void QQuickDragAxis::setMaximum(qreal maximum)
+{
+ if (m_maximum == maximum)
+ return;
+
+ m_maximum = maximum;
+ emit maximumChanged();
+}
+
+void QQuickDragAxis::setEnabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+
+ m_enabled = enabled;
+ emit enabledChanged();
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickdraghandler_p.h b/src/quick/handlers/qquickdraghandler_p.h
new file mode 100644
index 0000000000..69fa297b3c
--- /dev/null
+++ b/src/quick/handlers/qquickdraghandler_p.h
@@ -0,0 +1,134 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKDRAGHANDLER_H
+#define QQUICKDRAGHANDLER_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 "qquickpointersinglehandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQuickDragAxis : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY minimumChanged)
+ Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY maximumChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+public:
+ QQuickDragAxis();
+
+ qreal minimum() const { return m_minimum; }
+ void setMinimum(qreal minimum);
+
+ qreal maximum() const { return m_maximum; }
+ void setMaximum(qreal maximum);
+
+ bool enabled() const { return m_enabled; }
+ void setEnabled(bool enabled);
+
+signals:
+ void minimumChanged();
+ void maximumChanged();
+ void enabledChanged();
+
+private:
+ qreal m_minimum;
+ qreal m_maximum;
+ bool m_enabled;
+};
+
+class Q_AUTOTEST_EXPORT QQuickDragHandler : public QQuickPointerSingleHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT)
+ Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT)
+ Q_PROPERTY(QPointF translation READ translation NOTIFY translationChanged)
+
+public:
+ QQuickDragHandler(QObject *parent = 0);
+ ~QQuickDragHandler();
+
+ void handleEventPoint(QQuickEventPoint *point) override;
+
+ QQuickDragAxis *xAxis() { return &m_xAxis; }
+ QQuickDragAxis *yAxis() { return &m_yAxis; }
+
+ QPointF translation() const { return m_translation; }
+ void setTranslation(const QPointF &trans);
+
+ Q_INVOKABLE void enforceConstraints();
+
+Q_SIGNALS:
+// void gestureStarted(QQuickGestureEvent *gesture);
+ void translationChanged();
+
+protected:
+ bool wantsEventPoint(QQuickEventPoint *point) override;
+
+private:
+ void ungrab();
+ void enforceAxisConstraints(QPointF *localPos);
+
+private:
+ QPointF m_startPos;
+ QPointF m_translation;
+ QQuickDragAxis m_xAxis;
+ QQuickDragAxis m_yAxis;
+
+ friend class QQuickDragAxis;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickDragHandler)
+QML_DECLARE_TYPE(QQuickDragAxis)
+
+#endif // QQUICKDRAGHANDLER_H
diff --git a/src/quick/handlers/qquickhandlersmodule.cpp b/src/quick/handlers/qquickhandlersmodule.cpp
new file mode 100644
index 0000000000..697a198e67
--- /dev/null
+++ b/src/quick/handlers/qquickhandlersmodule.cpp
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickhandlersmodule_p.h"
+#include "qquickpointerhandler_p.h"
+#include "qquickdraghandler_p.h"
+#include "qquickpinchhandler_p.h"
+
+static void initResources()
+{
+#ifdef QT_STATIC
+ Q_INIT_RESOURCE(qmake_Qt_labs_handlers);
+#endif
+}
+
+QT_BEGIN_NAMESPACE
+
+static QQmlPrivate::AutoParentResult handler_autoParent(QObject *obj, QObject *parent)
+{
+ if (qmlobject_cast<QQuickItem *>(parent)) {
+ QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj);
+ if (handler) {
+ handler->setParent(parent);
+ return QQmlPrivate::Parented;
+ }
+ }
+ return QQmlPrivate::IncompatibleObject;
+}
+
+static void qt_quickhandlers_defineModule(const char *uri, int major, int minor)
+{
+ QQmlPrivate::RegisterAutoParent autoparent = { 0, &handler_autoParent };
+ QQmlPrivate::qmlregister(QQmlPrivate::AutoParentRegistration, &autoparent);
+ qmlRegisterUncreatableType<QQuickPointerEvent>(uri, major, minor, "PointerEvent",
+ QQuickPointerHandler::tr("PointerEvent is only available as a parameter of several signals in PointerHandler"));
+ qmlRegisterUncreatableType<QQuickPointerDevice>(uri, major, minor, "PointerDevice",
+ QQuickPointerHandler::tr("PointerDevice is only available as a property of PointerEvent"));
+ qRegisterMetaType<QPointingDeviceUniqueId>("QPointingDeviceUniqueId");
+ qmlRegisterUncreatableType<QPointingDeviceUniqueId>(uri, major, minor, "PointingDeviceUniqueId",
+ QQuickPointerHandler::tr("PointingDeviceUniqueId is only available as a property of PointerEvent"));
+
+ qmlRegisterType<QQuickPointerHandler>(uri,major,minor,"PointerHandler");
+ qmlRegisterType<QQuickDragHandler>(uri,major,minor,"DragHandler");
+ qmlRegisterUncreatableType<QQuickDragAxis>(uri, major, minor, "DragAxis",
+ QQuickDragHandler::tr("DragAxis is only available as a grouped property of DragHandler"));
+ qmlRegisterType<QQuickPinchHandler>(uri,major,minor,"PinchHandler");
+}
+
+void QQuickHandlersModule::defineModule()
+{
+ initResources();
+
+ const char uri[] = "Qt.labs.handlers";
+ int majorVersion = 1;
+ int minorVersion = 0;
+
+ qt_quickhandlers_defineModule(uri, majorVersion, minorVersion);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickhandlersmodule_p.h b/src/quick/handlers/qquickhandlersmodule_p.h
new file mode 100644
index 0000000000..7eb8d39b98
--- /dev/null
+++ b/src/quick/handlers/qquickhandlersmodule_p.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKHANDLERSMODULE_P_H
+#define QQUICKHANDLERSMODULE_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 <qqml.h>
+#include <private/qtquickglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QQuickHandlersModule
+{
+public:
+ static void defineModule();
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKHANDLERSMODULE_P_H
+
diff --git a/src/quick/handlers/qquickmultipointerhandler.cpp b/src/quick/handlers/qquickmultipointerhandler.cpp
new file mode 100644
index 0000000000..d3b986bcce
--- /dev/null
+++ b/src/quick/handlers/qquickmultipointerhandler.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickmultipointerhandler_p.h"
+#include <private/qquickitem_p.h>
+#include <QLineF>
+#include <QMouseEvent>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ An intermediate class (not registered as a QML type)
+ for the type of handler which requires and acts upon a specific number
+ of multiple touchpoints.
+*/
+QQuickMultiPointerHandler::QQuickMultiPointerHandler(QObject *parent, int requiredPointCount)
+ : QQuickPointerDeviceHandler(parent)
+ , m_requiredPointCount(requiredPointCount)
+ , m_pointDistanceThreshold(0)
+{
+}
+
+QQuickMultiPointerHandler::~QQuickMultiPointerHandler()
+{
+}
+
+bool QQuickMultiPointerHandler::wantsPointerEvent(QQuickPointerEvent *event)
+{
+ if (!QQuickPointerDeviceHandler::wantsPointerEvent(event))
+ return false;
+ if (event->pointCount() < m_requiredPointCount)
+ return false;
+ if (sameAsCurrentPoints(event))
+ return true;
+
+ const QVector<QQuickEventPoint *> candidatePoints = pointsInsideOrNearTarget(event);
+ const bool ret = (candidatePoints.size() == m_requiredPointCount);
+ if (ret)
+ m_currentPoints = candidatePoints;
+ return ret;
+}
+
+QVector<QQuickEventPoint *> QQuickMultiPointerHandler::pointsInsideOrNearTarget(QQuickPointerEvent *event)
+{
+ QVector<QQuickEventPoint *> ret;
+ int c = event->pointCount();
+ QRectF targetBounds = target()->mapRectToScene(target()->boundingRect())
+ .marginsAdded(QMarginsF(m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold));
+ for (int i = 0; i < c; ++i) {
+ QQuickEventPoint *p = event->point(i);
+ if (targetBounds.contains(p->scenePos()))
+ ret << p;
+ }
+ return ret;
+}
+
+void QQuickMultiPointerHandler::setRequiredPointCount(int c)
+{
+ if (m_requiredPointCount == c)
+ return;
+
+ m_requiredPointCount = c;
+ emit requiredPointCountChanged();
+}
+
+void QQuickMultiPointerHandler::setPointDistanceThreshold(qreal pointDistanceThreshold)
+{
+ if (m_pointDistanceThreshold == pointDistanceThreshold)
+ return;
+
+ m_pointDistanceThreshold = pointDistanceThreshold;
+ emit pointDistanceThresholdChanged();
+}
+
+bool QQuickMultiPointerHandler::sameAsCurrentPoints(QQuickPointerEvent *event)
+{
+ bool ret = true;
+ int c = event->pointCount();
+ if (c != m_currentPoints.size())
+ return false;
+ // TODO optimize: either ensure the points are sorted,
+ // or use std::equal with a predicate
+ for (int i = 0; ret && i < c; ++i) {
+ bool found = false;
+ quint64 pointId = event->point(i)->pointId();
+ for (QQuickEventPoint *o : qAsConst(m_currentPoints))
+ if (o && pointId == o->pointId())
+ found = true;
+ if (!found)
+ ret = false;
+ }
+ return ret;
+}
+
+// TODO make templates for these functions somehow?
+QPointF QQuickMultiPointerHandler::touchPointCentroid()
+{
+ QPointF ret;
+ if (Q_UNLIKELY(m_currentPoints.size() == 0))
+ return ret;
+ for (QQuickEventPoint *point : qAsConst(m_currentPoints))
+ ret += point->scenePos();
+ return ret / m_currentPoints.size();
+}
+
+qreal QQuickMultiPointerHandler::averageTouchPointDistance(const QPointF &ref)
+{
+ qreal ret = 0;
+ if (Q_UNLIKELY(m_currentPoints.size() == 0))
+ return ret;
+ for (QQuickEventPoint *point : qAsConst(m_currentPoints))
+ ret += QVector2D(point->scenePos() - ref).length();
+ return ret / m_currentPoints.size();
+}
+
+qreal QQuickMultiPointerHandler::averageStartingDistance(const QPointF &ref)
+{
+ // TODO cache it in setActive()?
+ qreal ret = 0;
+ if (Q_UNLIKELY(m_currentPoints.size() == 0))
+ return ret;
+ for (QQuickEventPoint *point : qAsConst(m_currentPoints))
+ ret += QVector2D(point->sceneGrabPos() - ref).length();
+ return ret / m_currentPoints.size();
+}
+
+QVector<QQuickMultiPointerHandler::PointData> QQuickMultiPointerHandler::angles(const QPointF &ref) const
+{
+ QVector<PointData> angles;
+ angles.reserve(m_currentPoints.count());
+ for (QQuickEventPoint *point : qAsConst(m_currentPoints)) {
+ qreal angle = QLineF(ref, point->scenePos()).angle();
+ angles.append(PointData(point->pointId(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation
+ }
+ return angles;
+}
+
+qreal QQuickMultiPointerHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles)
+{
+ qreal avgAngleDelta = 0;
+ int numSamples = 0;
+
+ auto oldBegin = old.constBegin();
+
+ for (PointData newData : newAngles) {
+ quint64 id = newData.id;
+ auto it = std::find_if(oldBegin, old.constEnd(), [id] (PointData pd) { return pd.id == id; });
+ qreal angleD = 0;
+ if (it != old.constEnd()) {
+ PointData oldData = *it;
+ // We might rotate from 359 degrees to 1 degree. However, this
+ // should be interpreted as a rotation of +2 degrees instead of
+ // -358 degrees. Therefore, we call remainder() to translate the angle
+ // to be in the range [-180, 180] (-350 to +10 etc)
+ angleD = remainder(newData.angle - oldData.angle, qreal(360));
+ // optimization: narrow down the O(n^2) search to optimally O(n)
+ // if both vectors have the same points and they are in the same order
+ if (it == oldBegin)
+ ++oldBegin;
+ numSamples++;
+ }
+ avgAngleDelta += angleD;
+ }
+ if (numSamples > 1)
+ avgAngleDelta /= numSamples;
+
+ return avgAngleDelta;
+}
+
+void QQuickMultiPointerHandler::acceptPoints(const QVector<QQuickEventPoint *> &points)
+{
+ for (QQuickEventPoint* point : points)
+ point->setAccepted();
+}
+
+void QQuickMultiPointerHandler::grabPoints(QVector<QQuickEventPoint *> points)
+{
+ for (QQuickEventPoint* point : points)
+ setGrab(point, true);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickmultipointerhandler_p.h b/src/quick/handlers/qquickmultipointerhandler_p.h
new file mode 100644
index 0000000000..2a23a14f8a
--- /dev/null
+++ b/src/quick/handlers/qquickmultipointerhandler_p.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKPOINTERMULTIHANDLER_H
+#define QQUICKPOINTERMULTIHANDLER_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 "qquickpointerdevicehandler_p.h"
+#include "../items/qquickmultipointtoucharea_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQuickMultiPointerHandler : public QQuickPointerDeviceHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(int requiredPointCount READ requiredPointCount WRITE setRequiredPointCount NOTIFY requiredPointCountChanged)
+ Q_PROPERTY(qreal pointDistanceThreshold READ pointDistanceThreshold WRITE setPointDistanceThreshold NOTIFY pointDistanceThresholdChanged)
+
+public:
+ QQuickMultiPointerHandler(QObject *parent = 0, int requiredPointCount = 2);
+ ~QQuickMultiPointerHandler();
+
+ int requiredPointCount() const { return m_requiredPointCount; }
+ void setRequiredPointCount(int c);
+
+ qreal pointDistanceThreshold() const { return m_pointDistanceThreshold; }
+ void setPointDistanceThreshold(qreal pointDistanceThreshold);
+
+signals:
+ void requiredPointCountChanged();
+ void pointDistanceThresholdChanged();
+
+protected:
+ struct PointData {
+ PointData() : id(0), angle(0) {}
+ PointData(quint64 id, qreal angle) : id(id), angle(angle) {}
+ quint64 id;
+ qreal angle;
+ };
+
+ bool wantsPointerEvent(QQuickPointerEvent *event) override;
+ bool sameAsCurrentPoints(QQuickPointerEvent *event);
+ QVector<QQuickEventPoint *> pointsInsideOrNearTarget(QQuickPointerEvent *event);
+ QPointF touchPointCentroid();
+ qreal averageTouchPointDistance(const QPointF &ref);
+ qreal averageStartingDistance(const QPointF &ref);
+ qreal averageTouchPointAngle(const QPointF &ref);
+ qreal averageStartingAngle(const QPointF &ref);
+ QVector<PointData> angles(const QPointF &ref) const;
+ static qreal averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles);
+ void acceptPoints(const QVector<QQuickEventPoint *> &points);
+ void grabPoints(QVector<QQuickEventPoint *> points);
+
+protected:
+ QVector<QQuickEventPoint *> m_currentPoints;
+ int m_requiredPointCount;
+ qreal m_pointDistanceThreshold;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickMultiPointerHandler)
+
+#endif // QQUICKPOINTERMULTIHANDLER_H
diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp
new file mode 100644
index 0000000000..4832d300c2
--- /dev/null
+++ b/src/quick/handlers/qquickpinchhandler.cpp
@@ -0,0 +1,302 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickpinchhandler_p.h"
+#include <QtQuick/qquickwindow.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qguiapplication_p.h>
+#include <private/qquickwindow_p.h>
+#include <QEvent>
+#include <QMouseEvent>
+#include <QDebug>
+#include <qpa/qplatformnativeinterface.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
+
+/*!
+ \qmltype PinchHandler
+ \instantiates QQuickPinchHandler
+ \inqmlmodule QtQuick
+ \ingroup qtquick-handlers
+ \brief Handler for pinch gestures
+
+ PinchHandler is a handler that is used to interactively rotate and zoom an Item.
+*/
+
+QQuickPinchHandler::QQuickPinchHandler(QObject *parent)
+ : QQuickMultiPointerHandler(parent, 2)
+ , m_activeScale(1)
+ , m_activeRotation(0)
+ , m_activeTranslation(0,0)
+ , m_minimumScale(-qInf())
+ , m_maximumScale(qInf())
+ , m_minimumRotation(-qInf())
+ , m_maximumRotation(qInf())
+ , m_minimumX(-qInf())
+ , m_maximumX(qInf())
+ , m_minimumY(-qInf())
+ , m_maximumY(qInf())
+ , m_pinchOrigin(PinchCenter)
+ , m_startScale(1)
+ , m_startRotation(0)
+{
+}
+
+QQuickPinchHandler::~QQuickPinchHandler()
+{
+}
+
+void QQuickPinchHandler::setMinimumScale(qreal minimumScale)
+{
+ if (m_minimumScale == minimumScale)
+ return;
+
+ m_minimumScale = minimumScale;
+ emit minimumScaleChanged();
+}
+
+void QQuickPinchHandler::setMaximumScale(qreal maximumScale)
+{
+ if (m_maximumScale == maximumScale)
+ return;
+
+ m_maximumScale = maximumScale;
+ emit maximumScaleChanged();
+}
+
+void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
+{
+ if (m_minimumRotation == minimumRotation)
+ return;
+
+ m_minimumRotation = minimumRotation;
+ emit minimumRotationChanged();
+}
+
+void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation)
+{
+ if (m_maximumRotation == maximumRotation)
+ return;
+
+ m_maximumRotation = maximumRotation;
+ emit maximumRotationChanged();
+}
+
+void QQuickPinchHandler::setPinchOrigin(QQuickPinchHandler::PinchOrigin pinchOrigin)
+{
+ if (m_pinchOrigin == pinchOrigin)
+ return;
+
+ m_pinchOrigin = pinchOrigin;
+ emit pinchOriginChanged();
+}
+
+/*!
+ \qmlproperty QQuickPinchHandler::minimumX
+
+ The minimum acceptable x coordinate of the centroid
+ */
+void QQuickPinchHandler::setMinimumX(qreal minX)
+{
+ if (m_minimumX == minX)
+ return;
+ m_minimumX = minX;
+ emit minimumXChanged();
+}
+
+/*!
+ \qmlproperty QQuickPinchHandler::maximumX
+
+ The maximum acceptable x coordinate of the centroid
+ */
+void QQuickPinchHandler::setMaximumX(qreal maxX)
+{
+ if (m_maximumX == maxX)
+ return;
+ m_maximumX = maxX;
+ emit maximumXChanged();
+}
+
+/*!
+ \qmlproperty QQuickPinchHandler::minimumY
+
+ The minimum acceptable y coordinate of the centroid
+ */
+void QQuickPinchHandler::setMinimumY(qreal minY)
+{
+ if (m_minimumY == minY)
+ return;
+ m_minimumY = minY;
+ emit minimumYChanged();
+}
+
+/*!
+ \qmlproperty QQuickPinchHandler::maximumY
+
+ The maximum acceptable y coordinate of the centroid
+ */
+void QQuickPinchHandler::setMaximumY(qreal maxY)
+{
+ if (m_maximumY == maxY)
+ return;
+ m_maximumY = maxY;
+ emit maximumYChanged();
+}
+
+/*!
+ \qmlproperty QQuickPinchHandler::minimumTouchPoints
+
+ The pinch begins when this number of fingers are pressed.
+ Until then, PinchHandler tracks the positions of any pressed fingers,
+ but if it's an insufficient number, it does not scale or rotate
+ its \l target, and the \l active property will remain false.
+*/
+
+/*!
+ \qmlproperty QQuickPinchHandler::active
+*/
+
+void QQuickPinchHandler::onActiveChanged()
+{
+ if (active()) {
+ if (const QQuickItem *t = target()) {
+ m_startScale = t->scale(); // TODO incompatible with independent x/y scaling
+ m_startRotation = t->rotation();
+ m_startCentroid = touchPointCentroid();
+ m_startAngles = angles(m_startCentroid);
+ m_startDistance = averageTouchPointDistance(m_startCentroid);
+ QVector3D xformOrigin(t->transformOriginPoint());
+ m_startMatrix = QMatrix4x4();
+ m_startMatrix.translate(t->x(), t->y());
+ m_startMatrix.translate(xformOrigin);
+ m_startMatrix.scale(m_startScale);
+ m_startMatrix.rotate(m_startRotation, 0, 0, -1);
+ m_startMatrix.translate(-xformOrigin);
+ m_activeRotation = 0;
+ m_activeTranslation = QPointF(0,0);
+ qCInfo(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation;
+ grabPoints(m_currentPoints);
+ }
+ } else {
+ qCInfo(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation;
+ }
+}
+
+void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event)
+{
+ Q_UNUSED(event)
+ // TODO wait for the drag threshold before setting active
+ // but the old behavior was that whenever we "want" all the points, we're active
+ // so that behavior is retained here temporarily
+ setActive(m_currentPoints.count() > 0 && m_currentPoints.at(0)->state() != QQuickEventPoint::Released);
+
+ if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) {
+ for (QQuickEventPoint *point : qAsConst(m_currentPoints))
+ qCDebug(lcPinchHandler) << point->state() << point->sceneGrabPos() << "->" << point->scenePos();
+ }
+
+ // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter
+ m_centroid = touchPointCentroid();
+ QRectF bounds(m_minimumX, m_minimumY, m_maximumX, m_maximumY);
+ // avoid mapping the minima and maxima, as they might have unmappable values
+ // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords.
+ QPointF centroidParentPos;
+ if (target() && target()->parentItem()) {
+ centroidParentPos = target()->parentItem()->mapFromScene(m_centroid);
+ centroidParentPos = QPointF(qBound(bounds.left(), centroidParentPos.x(), bounds.right()),
+ qBound(bounds.top(), centroidParentPos.y(), bounds.bottom()));
+ }
+ // 1. scale
+ const qreal dist = averageTouchPointDistance(m_centroid);
+ m_activeScale = dist / m_startDistance;
+ m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale);
+ const qreal scale = m_startScale * m_activeScale;
+
+ // 2. rotate
+ QVector<PointData> newAngles = angles(m_centroid);
+ const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles);
+ m_activeRotation += angleDelta;
+ const qreal totalRotation = m_startRotation + m_activeRotation;
+ const qreal rotation = qBound(m_minimumRotation, totalRotation, m_maximumRotation);
+ m_activeRotation += (rotation - totalRotation); //adjust for the potential bounding above
+ m_startAngles = std::move(newAngles);
+
+ if (target() && target()->parentItem()) {
+ // 3. Drag/translate
+ const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_startCentroid);
+ m_activeTranslation = centroidParentPos - centroidStartParentPos;
+
+ // apply rotation + scaling around the centroid - then apply translation.
+ QMatrix4x4 mat;
+
+ const QVector3D centroidParentVector(centroidParentPos);
+ mat.translate(centroidParentVector);
+ mat.rotate(m_activeRotation, 0, 0, 1);
+ mat.scale(m_activeScale);
+ mat.translate(-centroidParentVector);
+ mat.translate(QVector3D(m_activeTranslation));
+
+ mat = mat * m_startMatrix;
+
+ QPointF xformOriginPoint = target()->transformOriginPoint();
+ QPointF pos = mat * xformOriginPoint;
+ pos -= xformOriginPoint;
+
+ target()->setPosition(pos);
+ target()->setRotation(rotation);
+ target()->setScale(scale);
+
+
+ // TODO some translation inadvertently happens; try to hold the chosen pinch origin in place
+
+ qCDebug(lcPinchHandler) << "centroid" << m_startCentroid << "->" << m_centroid
+ << ", distance" << m_startDistance << "->" << dist
+ << ", startScale" << m_startScale << "->" << scale
+ << ", activeRotation" << m_activeRotation
+ << ", rotation" << rotation;
+ }
+
+ acceptPoints(m_currentPoints);
+ emit updated();
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h
new file mode 100644
index 0000000000..0861368682
--- /dev/null
+++ b/src/quick/handlers/qquickpinchhandler_p.h
@@ -0,0 +1,170 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKPINCHHANDLER_H
+#define QQUICKPINCHHANDLER_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 "qquickmultipointerhandler_p.h"
+#include <private/qquicktranslate_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQuickPinchHandler : public QQuickMultiPointerHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal minimumScale READ minimumScale WRITE setMinimumScale NOTIFY minimumScaleChanged)
+ Q_PROPERTY(qreal maximumScale READ maximumScale WRITE setMaximumScale NOTIFY maximumScaleChanged)
+ Q_PROPERTY(qreal minimumRotation READ minimumRotation WRITE setMinimumRotation NOTIFY minimumRotationChanged)
+ Q_PROPERTY(qreal maximumRotation READ maximumRotation WRITE setMaximumRotation NOTIFY maximumRotationChanged)
+ Q_PROPERTY(PinchOrigin pinchOrigin READ pinchOrigin WRITE setPinchOrigin NOTIFY pinchOriginChanged)
+ Q_PROPERTY(QPointF centroid READ centroid NOTIFY updated)
+ Q_PROPERTY(qreal scale READ scale NOTIFY updated)
+ Q_PROPERTY(qreal rotation READ rotation NOTIFY updated)
+ Q_PROPERTY(QPointF translation READ translation NOTIFY updated)
+ Q_PROPERTY(qreal minimumX READ minimumX WRITE setMinimumX NOTIFY minimumXChanged)
+ Q_PROPERTY(qreal maximumX READ maximumX WRITE setMaximumX NOTIFY maximumXChanged)
+ Q_PROPERTY(qreal minimumY READ minimumY WRITE setMinimumY NOTIFY minimumYChanged)
+ Q_PROPERTY(qreal maximumY READ maximumY WRITE setMaximumY NOTIFY maximumYChanged)
+
+public:
+ enum PinchOrigin {
+ FirstPoint, PinchCenter, TargetCenter
+ };
+ Q_ENUM(PinchOrigin)
+
+ QQuickPinchHandler(QObject *parent = 0);
+ ~QQuickPinchHandler();
+
+ qreal minimumScale() const { return m_minimumScale; }
+ void setMinimumScale(qreal minimumScale);
+
+ qreal maximumScale() const { return m_maximumScale; }
+ void setMaximumScale(qreal maximumScale);
+
+ qreal minimumRotation() const { return m_minimumRotation; }
+ void setMinimumRotation(qreal minimumRotation);
+
+ qreal maximumRotation() const { return m_maximumRotation; }
+ void setMaximumRotation(qreal maximumRotation);
+
+ PinchOrigin pinchOrigin() const { return m_pinchOrigin; }
+ void setPinchOrigin(PinchOrigin pinchOrigin);
+
+ QPointF translation() const { return m_activeTranslation; }
+ qreal scale() const { return m_activeScale; }
+ qreal rotation() const { return m_activeRotation; }
+ QPointF centroid() const { return m_centroid; }
+
+ qreal minimumX() const { return m_minimumX; }
+ void setMinimumX(qreal minX);
+ qreal maximumX() const { return m_maximumX; }
+ void setMaximumX(qreal maxX);
+ qreal minimumY() const { return m_minimumY; }
+ void setMinimumY(qreal minY);
+ qreal maximumY() const { return m_maximumY; }
+ void setMaximumY(qreal maxY);
+
+signals:
+ void requiredPointCountChanged();
+ void minimumScaleChanged();
+ void maximumScaleChanged();
+ void minimumRotationChanged();
+ void maximumRotationChanged();
+ void minimumXChanged();
+ void maximumXChanged();
+ void minimumYChanged();
+ void maximumYChanged();
+ void pinchOriginChanged();
+ void updated();
+
+protected:
+ void onActiveChanged() override;
+ void handlePointerEventImpl(QQuickPointerEvent *event) override;
+
+private:
+ // properties
+ qreal m_activeScale;
+ qreal m_activeRotation;
+ QPointF m_activeTranslation;
+ QPointF m_centroid;
+
+ qreal m_minimumScale;
+ qreal m_maximumScale;
+
+ qreal m_minimumRotation;
+ qreal m_maximumRotation;
+
+ qreal m_minimumX;
+ qreal m_maximumX;
+ qreal m_minimumY;
+ qreal m_maximumY;
+
+ PinchOrigin m_pinchOrigin;
+
+ // internal
+ qreal m_startScale;
+ qreal m_startRotation;
+ QPointF m_startCentroid;
+ qreal m_startDistance;
+ QPointF m_startPos;
+
+ QVector<PointData> m_startAngles;
+ QMatrix4x4 m_startMatrix;
+ QQuickMatrix4x4 m_transform;
+
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPinchHandler)
+
+#endif // QQUICKPINCHHANDLER_H
diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp
new file mode 100644
index 0000000000..03704ac09e
--- /dev/null
+++ b/src/quick/handlers/qquickpointerdevicehandler.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickpointerdevicehandler_p.h"
+#include <private/qquickitem_p.h>
+#include <QMouseEvent>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ An intermediate class (not registered as a QML type)
+ for handlers which allow filtering based on device type,
+ pointer type, or device-specific buttons (such as mouse or stylus buttons).
+ */
+QQuickPointerDeviceHandler::QQuickPointerDeviceHandler(QObject *parent)
+ : QQuickPointerHandler(parent)
+ , m_acceptedDevices(QQuickPointerDevice::AllDevices)
+ , m_acceptedPointerTypes(QQuickPointerDevice::AllPointerTypes)
+ , m_acceptedButtons(Qt::AllButtons)
+{
+}
+
+QQuickPointerDeviceHandler::~QQuickPointerDeviceHandler()
+{
+}
+
+void QQuickPointerDeviceHandler::setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices)
+{
+ if (m_acceptedDevices == acceptedDevices)
+ return;
+
+ m_acceptedDevices = acceptedDevices;
+ emit acceptedDevicesChanged();
+}
+
+void QQuickPointerDeviceHandler::setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes)
+{
+ if (m_acceptedPointerTypes == acceptedPointerTypes)
+ return;
+
+ m_acceptedPointerTypes = acceptedPointerTypes;
+ emit acceptedPointerTypesChanged();
+}
+
+void QQuickPointerDeviceHandler::setAcceptedButtons(Qt::MouseButtons buttons)
+{
+ if (m_acceptedButtons == buttons)
+ return;
+
+ m_acceptedButtons = buttons;
+ emit acceptedButtonsChanged();
+}
+
+bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event)
+{
+ if (!QQuickPointerHandler::wantsPointerEvent(event))
+ return false;
+ qCDebug(lcPointerHandlerDispatch) << objectName()
+ << "checking device type" << m_acceptedDevices
+ << "pointer type" << m_acceptedPointerTypes
+ << "buttons" << m_acceptedButtons;
+ if ((event->device()->type() & m_acceptedDevices) == 0)
+ return false;
+ if ((event->device()->pointerType() & m_acceptedPointerTypes) == 0)
+ return false;
+ if (event->device()->pointerType() != QQuickPointerDevice::Finger &&
+ (event->buttons() & m_acceptedButtons) == 0 && (event->button() & m_acceptedButtons) == 0)
+ return false;
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickpointerdevicehandler_p.h b/src/quick/handlers/qquickpointerdevicehandler_p.h
new file mode 100644
index 0000000000..bb12d3ccdb
--- /dev/null
+++ b/src/quick/handlers/qquickpointerdevicehandler_p.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKPOINTERDEVICEHANDLER_H
+#define QQUICKPOINTERDEVICEHANDLER_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 "qquickpointerhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQuickPointerDeviceHandler : public QQuickPointerHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickPointerDevice::DeviceTypes acceptedDevices READ acceptedDevices WRITE setAcceptedDevices NOTIFY acceptedDevicesChanged)
+ Q_PROPERTY(QQuickPointerDevice::PointerTypes acceptedPointerTypes READ acceptedPointerTypes WRITE setAcceptedPointerTypes NOTIFY acceptedPointerTypesChanged)
+ Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged)
+
+public:
+ QQuickPointerDeviceHandler(QObject *parent = 0);
+ ~QQuickPointerDeviceHandler();
+
+ QQuickPointerDevice::DeviceTypes acceptedDevices() const { return m_acceptedDevices; }
+ QQuickPointerDevice::PointerTypes acceptedPointerTypes() const { return m_acceptedPointerTypes; }
+ Qt::MouseButtons acceptedButtons() const { return m_acceptedButtons; }
+
+public slots:
+ void setAcceptedDevices(QQuickPointerDevice::DeviceTypes acceptedDevices);
+ void setAcceptedPointerTypes(QQuickPointerDevice::PointerTypes acceptedPointerTypes);
+ void setAcceptedButtons(Qt::MouseButtons buttons);
+
+Q_SIGNALS:
+ void acceptedDevicesChanged();
+ void acceptedPointerTypesChanged();
+ void acceptedButtonsChanged();
+
+protected:
+ bool wantsPointerEvent(QQuickPointerEvent *event) override;
+ void setPressed(bool pressed);
+
+protected:
+ QQuickPointerDevice::DeviceTypes m_acceptedDevices;
+ QQuickPointerDevice::PointerTypes m_acceptedPointerTypes;
+ Qt::MouseButtons m_acceptedButtons;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPointerDeviceHandler)
+
+#endif // QQUICKPOINTERDEVICEHANDLER_H
diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp
new file mode 100644
index 0000000000..0398863ba3
--- /dev/null
+++ b/src/quick/handlers/qquickpointerhandler.cpp
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickpointerhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch")
+
+/*!
+ \qmltype PointerHandler
+ \instantiates QQuickPointerHandler
+ \inqmlmodule QtQuick
+ \ingroup qtquick-handlers
+ \brief Handler for pointer events
+
+ PointerHandler is a handler for pointer events regardless of source.
+ They may represent events from a touch, mouse or tablet device.
+*/
+
+QQuickPointerHandler::QQuickPointerHandler(QObject *parent)
+ : QObject(parent)
+ , m_currentEvent(nullptr)
+ , m_target(nullptr)
+ , m_enabled(true)
+ , m_active(false)
+ , m_targetExplicitlySet(false)
+{
+}
+
+QQuickPointerHandler::~QQuickPointerHandler()
+{
+ QQuickItem *parItem = parentItem();
+ if (parItem) {
+ QQuickItemPrivate *p = QQuickItemPrivate::get(parItem);
+ p->extra.value().pointerHandlers.removeOne(this);
+ }
+}
+
+void QQuickPointerHandler::setGrab(QQuickEventPoint *point, bool grab)
+{
+ QQuickPointerHandler *oldGrabber = point->grabberPointerHandler();
+ if (grab && oldGrabber != this) {
+ if (oldGrabber)
+ oldGrabber->handleGrabCancel(point);
+ point->setGrabberPointerHandler(this);
+ onGrabChanged(point);
+ emit grabChanged(point);
+ } else if (!grab && oldGrabber == this) {
+ point->setGrabberPointerHandler(nullptr);
+ onGrabChanged(point);
+ emit grabChanged(point);
+ }
+}
+
+QPointF QQuickPointerHandler::eventPos(const QQuickEventPoint *point) const
+{
+ return (target() ? target()->mapFromScene(point->scenePos()) : point->scenePos());
+}
+
+bool QQuickPointerHandler::parentContains(const QQuickEventPoint *point) const
+{
+ if (point) {
+ if (QQuickItem *par = parentItem())
+ return par->contains(par->mapFromScene(point->scenePos()));
+ }
+ return false;
+}
+
+/*!
+ \qmlproperty QQuickPointerHandler::enabled
+
+ If a PointerHandler is disabled, it will reject all events
+ and no signals will be emitted.
+
+ TODO is it too extreme not even to emit pressed/updated/released?
+ or should we disable only the higher-level interpretation, in subclasses?
+*/
+void QQuickPointerHandler::setEnabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+
+ m_enabled = enabled;
+ emit enabledChanged();
+}
+
+void QQuickPointerHandler::setTarget(QQuickItem *target)
+{
+ m_targetExplicitlySet = true;
+ if (m_target == target)
+ return;
+
+ m_target = target;
+ emit targetChanged();
+}
+
+QQuickItem *QQuickPointerHandler::target() const
+{
+ if (!m_targetExplicitlySet)
+ return parentItem();
+ return m_target;
+}
+
+void QQuickPointerHandler::handlePointerEvent(QQuickPointerEvent *event)
+{
+ if (wantsPointerEvent(event))
+ handlePointerEventImpl(event);
+ else
+ setActive(false);
+}
+
+void QQuickPointerHandler::handleGrabCancel(QQuickEventPoint *point)
+{
+ qCDebug(lcPointerHandlerDispatch) << point;
+ Q_ASSERT(point);
+ setActive(false);
+ point->setAccepted(false);
+ emit canceled(point);
+}
+
+bool QQuickPointerHandler::wantsPointerEvent(QQuickPointerEvent *event)
+{
+ Q_UNUSED(event)
+ return m_enabled;
+}
+
+void QQuickPointerHandler::setActive(bool active)
+{
+ if (m_active != active) {
+ m_active = active;
+ onActiveChanged();
+ emit activeChanged();
+ }
+}
+
+void QQuickPointerHandler::handlePointerEventImpl(QQuickPointerEvent *event)
+{
+ m_currentEvent = event;
+}
+
+/*!
+ \qmlproperty QQuickPointerHandler::parent
+
+ The \l Item which is the scope of the handler; the Item in which it was declared.
+ The handler will handle events on behalf of this Item, which means a
+ pointer event is relevant if at least one of its event points occurs within
+ the Item's interior. Initially \l target() is the same, but target()
+ can be reassigned.
+
+ \sa QQuickPointerHandler::target(), QObject::parent()
+*/
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h
new file mode 100644
index 0000000000..85f1096410
--- /dev/null
+++ b/src/quick/handlers/qquickpointerhandler_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKPOINTERHANDLER_H
+#define QQUICKPOINTERHANDLER_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 "qevent.h"
+
+#include <QtQuick/private/qquickevents_p_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch)
+
+class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+ Q_PROPERTY(bool active READ active NOTIFY activeChanged)
+ Q_PROPERTY(QQuickItem * target READ target WRITE setTarget NOTIFY targetChanged)
+ Q_PROPERTY(QQuickItem * parent READ parentItem CONSTANT)
+
+public:
+ QQuickPointerHandler(QObject *parent = 0);
+ virtual ~QQuickPointerHandler();
+
+public:
+ bool enabled() const { return m_enabled; }
+ void setEnabled(bool enabled);
+
+ bool active() const { return m_active; }
+
+ QQuickItem *target() const;
+ void setTarget(QQuickItem *target);
+
+ QQuickItem * parentItem() const { return static_cast<QQuickItem *>(QObject::parent()); }
+
+ void handlePointerEvent(QQuickPointerEvent *event);
+
+Q_SIGNALS:
+ void enabledChanged();
+ void activeChanged();
+ void targetChanged();
+ void grabChanged(QQuickEventPoint *point);
+ void canceled(QQuickEventPoint *point);
+
+protected:
+ QQuickPointerEvent *currentEvent() { return m_currentEvent; }
+ virtual bool wantsPointerEvent(QQuickPointerEvent *event);
+ virtual void handlePointerEventImpl(QQuickPointerEvent *event);
+ void setActive(bool active);
+ virtual void onActiveChanged() { }
+ virtual void onGrabChanged(QQuickEventPoint *) { }
+ void setGrab(QQuickEventPoint *point, bool grab);
+ virtual void handleGrabCancel(QQuickEventPoint *point);
+ QPointF eventPos(const QQuickEventPoint *point) const;
+ bool parentContains(const QQuickEventPoint *point) const;
+
+private:
+ QQuickPointerEvent *m_currentEvent;
+ QQuickItem *m_target;
+ bool m_enabled : 1;
+ bool m_active : 1;
+ bool m_targetExplicitlySet : 1;
+
+ friend class QQuickEventPoint;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPointerHandler)
+
+#endif // QQUICKPOINTERHANDLER_H
diff --git a/src/quick/handlers/qquickpointersinglehandler.cpp b/src/quick/handlers/qquickpointersinglehandler.cpp
new file mode 100644
index 0000000000..f39d8ac976
--- /dev/null
+++ b/src/quick/handlers/qquickpointersinglehandler.cpp
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 "qquickpointersinglehandler_p.h"
+
+QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET)
+
+/*!
+ An intermediate class (not registered as a QML type)
+ for the most common handlers: those which expect only a single point.
+ wantsPointerEvent() will choose the first point which is inside the
+ \l target item, and return true as long as the event contains that point.
+ Override handleEventPoint() to implement a single-point handler.
+*/
+
+QQuickPointerSingleHandler::QQuickPointerSingleHandler(QObject *parent)
+ : QQuickPointerDeviceHandler(parent)
+ , m_pointId(0)
+ , m_rotation(0)
+ , m_pressure(0)
+{
+}
+
+bool QQuickPointerSingleHandler::wantsPointerEvent(QQuickPointerEvent *event)
+{
+ if (!QQuickPointerDeviceHandler::wantsPointerEvent(event))
+ return false;
+ if (m_pointId) {
+ // We already know which one we want, so check whether it's there.
+ // It's expected to be an update or a release.
+ // If we no longer want it, cancel the grab.
+ if (auto point = event->pointById(m_pointId)) {
+ if (wantsEventPoint(point)) {
+ point->setAccepted();
+ return true;
+ } else if (point->grabber() == this) {
+ point->cancelGrab();
+ }
+ } else {
+ qCWarning(DBG_TOUCH_TARGET) << this << "pointId" << m_pointId
+ << "is missing from current event, but was neither canceled nor released";
+ return false;
+ }
+ } else {
+ // We have not yet chosen a point; choose the first one for which wantsEventPoint() returns true.
+ int c = event->pointCount();
+ for (int i = 0; i < c && !m_pointId; ++i) {
+ QQuickEventPoint *p = event->point(i);
+ if (!p->grabber() && wantsEventPoint(p)) {
+ m_pointId = p->pointId();
+ p->setAccepted();
+ }
+ }
+ }
+ return m_pointId;
+}
+
+void QQuickPointerSingleHandler::handlePointerEventImpl(QQuickPointerEvent *event)
+{
+ QQuickPointerDeviceHandler::handlePointerEventImpl(event);
+ QQuickEventPoint *currentPoint = event->pointById(m_pointId);
+ Q_ASSERT(currentPoint);
+ bool grab = false;
+ if (!m_pointId || !currentPoint->isAccepted() || currentPoint->state() == QQuickEventPoint::Released) {
+ reset();
+ grab = false;
+ } else {
+ if (event->asPointerTouchEvent()) {
+ QQuickEventTouchPoint *tp = static_cast<QQuickEventTouchPoint *>(currentPoint);
+ m_uniquePointId = tp->uniqueId();
+ m_rotation = tp->rotation();
+ m_pressure = tp->pressure();
+ m_ellipseDiameters = tp->ellipseDiameters();
+ } else if (event->asPointerTabletEvent()) {
+ // TODO
+ } else {
+ m_uniquePointId = event->device()->uniqueId();
+ m_rotation = 0;
+ m_pressure = event->buttons() ? 1 : 0;
+ m_ellipseDiameters = QSizeF();
+ }
+ m_pos = currentPoint->pos();
+ m_velocity = currentPoint->velocity();
+ handleEventPoint(currentPoint);
+ if (currentPoint->state() == QQuickEventPoint::Pressed) {
+ m_pressPos = currentPoint->pos();
+ emit pointIdChanged();
+ }
+ setPressedButtons(event->buttons());
+ grab = true;
+ }
+ emit eventPointHandled();
+ // TODO don't call setGrab(true) here, only setGrab(false), because concrete subclasses may
+ // wait for the drag threshold to be exceeded, for example
+ setGrab(currentPoint, grab);
+}
+
+bool QQuickPointerSingleHandler::wantsEventPoint(QQuickEventPoint *point)
+{
+ return parentContains(point);
+}
+
+void QQuickPointerSingleHandler::handleGrabCancel(QQuickEventPoint *point)
+{
+ QQuickPointerHandler::handleGrabCancel(point);
+ reset();
+}
+
+void QQuickPointerSingleHandler::onGrabChanged(QQuickEventPoint *point)
+{
+ bool grabbing = (point->grabber() == this);
+ setActive(grabbing);
+ if (grabbing)
+ m_sceneGrabPos = point->sceneGrabPos();
+ emit singlePointGrabChanged();
+}
+
+void QQuickPointerSingleHandler::setPressedButtons(Qt::MouseButtons buttons)
+{
+ if (buttons != m_pressedButtons) {
+ m_pressedButtons = buttons;
+ emit pressedButtonsChanged();
+ }
+}
+
+void QQuickPointerSingleHandler::reset()
+{
+ bool pointIdChange = m_pointId != 0;
+ m_pointId = 0;
+ m_uniquePointId = QPointingDeviceUniqueId();
+ m_pos = QPointF();
+ m_pressPos = QPointF();
+ m_sceneGrabPos = QPointF();
+ m_velocity = QVector2D();
+ m_rotation = 0;
+ m_pressure = 0;
+ m_ellipseDiameters = QSizeF();
+ setPressedButtons(Qt::NoButton);
+ if (pointIdChange)
+ emit pointIdChanged();
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickpointersinglehandler_p.h b/src/quick/handlers/qquickpointersinglehandler_p.h
new file mode 100644
index 0000000000..8858b2c080
--- /dev/null
+++ b/src/quick/handlers/qquickpointersinglehandler_p.h
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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 QQUICKPOINTERSINGLEHANDLER_H
+#define QQUICKPOINTERSINGLEHANDLER_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 "qquickpointerdevicehandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QQuickPointerSingleHandler : public QQuickPointerDeviceHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(quint64 pointId READ pointId NOTIFY pointIdChanged)
+ Q_PROPERTY(QPointingDeviceUniqueId uniquePointId READ uniquePointId NOTIFY pointIdChanged)
+ Q_PROPERTY(QPointF pos READ pos NOTIFY eventPointHandled)
+ Q_PROPERTY(QPointF scenePos READ scenePos NOTIFY eventPointHandled)
+ Q_PROPERTY(QPointF pressPos READ pressPos NOTIFY pressedButtonsChanged)
+ Q_PROPERTY(QPointF scenePressPos READ scenePressPos NOTIFY pressedButtonsChanged)
+ Q_PROPERTY(QPointF sceneGrabPos READ sceneGrabPos NOTIFY singlePointGrabChanged)
+ Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons NOTIFY pressedButtonsChanged)
+ Q_PROPERTY(QVector2D velocity READ velocity NOTIFY eventPointHandled)
+ Q_PROPERTY(qreal rotation READ rotation NOTIFY eventPointHandled)
+ Q_PROPERTY(qreal pressure READ pressure NOTIFY eventPointHandled)
+ Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters NOTIFY eventPointHandled)
+
+public:
+ QQuickPointerSingleHandler(QObject *parent = 0);
+ virtual ~QQuickPointerSingleHandler() { }
+
+ Qt::MouseButtons pressedButtons() const { return m_pressedButtons; }
+ QPointF pressPos() const { return m_pressPos; }
+ QPointF scenePressPos() const { return parentItem()->mapToScene(m_pressPos); }
+ QPointF sceneGrabPos() const { return m_sceneGrabPos; }
+ QPointF pos() const { return m_pos; }
+ QPointF scenePos() const { return parentItem()->mapToScene(m_pos); }
+ QVector2D velocity() const { return m_velocity; }
+ qreal rotation() const { return m_rotation; }
+ qreal pressure() const { return m_pressure; }
+ QSizeF ellipseDiameters() const { return m_ellipseDiameters; }
+ QPointingDeviceUniqueId uniquePointId() const { return m_uniquePointId; }
+
+signals:
+ void pointIdChanged();
+ void pressedButtonsChanged();
+ void singlePointGrabChanged(); // QQuickPointerHandler::grabChanged signal can't be a property notifier here
+ void eventPointHandled();
+
+protected:
+ bool wantsPointerEvent(QQuickPointerEvent *event) override;
+ virtual bool wantsEventPoint(QQuickEventPoint *point);
+ void handlePointerEventImpl(QQuickPointerEvent *event) override;
+ virtual void handleEventPoint(QQuickEventPoint *point) = 0;
+ quint64 pointId() const { return m_pointId; }
+ QQuickEventPoint *currentPoint(QQuickPointerEvent *ev) { return ev->pointById(m_pointId); }
+ void handleGrabCancel(QQuickEventPoint *point) override;
+ void onGrabChanged(QQuickEventPoint *point) override;
+
+private:
+ void setPressedButtons(Qt::MouseButtons buttons);
+ void reset();
+
+private:
+ quint64 m_pointId;
+ QPointingDeviceUniqueId m_uniquePointId;
+ Qt::MouseButtons m_pressedButtons;
+ QPointF m_pos;
+ QPointF m_pressPos;
+ QPointF m_sceneGrabPos;
+ QVector2D m_velocity;
+ qreal m_rotation;
+ qreal m_pressure;
+ QSizeF m_ellipseDiameters;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPointerSingleHandler)
+
+#endif // QQUICKPOINTERSINGLEHANDLER_H
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp
index b6c45c40a8..0c18cb4104 100644
--- a/src/quick/items/qquickevents.cpp
+++ b/src/quick/items/qquickevents.cpp
@@ -38,8 +38,10 @@
****************************************************************************/
#include "qquickevents_p_p.h"
+#include <QtCore/qmap.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickpointerhandler_p.h>
#include <QtQuick/private/qquickwindow_p.h>
#include <private/qdebug_p.h>
@@ -448,7 +450,15 @@ Item {
typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash;
Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices)
-Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice,
+struct ConstructableQQuickPointerDevice : public QQuickPointerDevice
+{
+ ConstructableQQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps,
+ int maxPoints, int buttonCount, const QString &name,
+ qint64 uniqueId = 0)
+ : QQuickPointerDevice(devType, pType, caps, maxPoints, buttonCount, name, uniqueId) {}
+
+};
+Q_GLOBAL_STATIC_WITH_ARGS(ConstructableQQuickPointerDevice, g_genericMouseDevice,
(QQuickPointerDevice::Mouse,
QQuickPointerDevice::GenericPointer,
QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover,
@@ -505,35 +515,107 @@ QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id)
return nullptr;
}
-void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp)
+void QQuickEventPoint::reset(Qt::TouchPointState state, const QPointF &scenePos, quint64 pointId, ulong timestamp, const QVector2D &velocity)
{
m_scenePos = scenePos;
- m_pointId = pointId;
+ if (m_pointId != pointId) {
+ if (m_grabber) {
+ qWarning() << m_grabber << "failed to ungrab previous point" << m_pointId;
+ cancelGrab();
+ }
+ m_pointId = pointId;
+ }
m_valid = true;
m_accept = false;
m_state = static_cast<QQuickEventPoint::State>(state);
m_timestamp = timestamp;
- if (state == Qt::TouchPointPressed)
+ if (state == Qt::TouchPointPressed) {
m_pressTimestamp = timestamp;
- // TODO calculate velocity
+ m_scenePressPos = scenePos;
+ }
+ m_velocity = (Q_LIKELY(velocity.isNull()) ? estimatedVelocity() : velocity);
}
-QQuickItem *QQuickEventPoint::grabber() const
+void QQuickEventPoint::localize(QQuickItem *target)
+{
+ if (target)
+ m_pos = target->mapFromScene(scenePos());
+ else
+ m_pos = QPointF();
+}
+
+void QQuickEventPoint::invalidate()
+{
+ m_valid = false;
+ m_pointId = 0;
+}
+
+QObject *QQuickEventPoint::grabber() const
{
return m_grabber.data();
}
-void QQuickEventPoint::setGrabber(QQuickItem *grabber)
+void QQuickEventPoint::setGrabber(QObject *grabber)
+{
+ if (QQuickPointerHandler *phGrabber = qmlobject_cast<QQuickPointerHandler *>(grabber))
+ setGrabberPointerHandler(phGrabber);
+ else
+ setGrabberItem(static_cast<QQuickItem *>(grabber));
+}
+
+QQuickItem *QQuickEventPoint::grabberItem() const
{
- if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled()) && m_grabber.data() != grabber) {
- auto device = static_cast<const QQuickPointerEvent *>(parent())->device();
- static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State"));
- QString deviceName = (device ? device->name() : QLatin1String("null device"));
- deviceName.resize(16, ' '); // shorten, and align in case of sequential output
- qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state())
- << ": grab" << m_grabber << "->" << grabber;
+ return (m_grabberIsHandler ? nullptr : static_cast<QQuickItem *>(m_grabber.data()));
+}
+
+void QQuickEventPoint::setGrabberItem(QQuickItem *grabber)
+{
+ if (grabber != m_grabber.data()) {
+ if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
+ auto device = static_cast<const QQuickPointerEvent *>(parent())->device();
+ static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State"));
+ QString deviceName = (device ? device->name() : QLatin1String("null device"));
+ deviceName.resize(16, ' '); // shorten, and align in case of sequential output
+ qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state())
+ << ": grab" << m_grabber << "->" << grabber;
+ }
+ m_grabber = QPointer<QObject>(grabber);
+ m_grabberIsHandler = false;
+ m_sceneGrabPos = m_scenePos;
}
- m_grabber = QPointer<QQuickItem>(grabber);
+}
+
+QQuickPointerHandler *QQuickEventPoint::grabberPointerHandler() const
+{
+ return (m_grabberIsHandler ? static_cast<QQuickPointerHandler *>(m_grabber.data()) : nullptr);
+}
+
+void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber)
+{
+ if (grabber != m_grabber.data()) {
+ if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
+ auto device = static_cast<const QQuickPointerEvent *>(parent())->device();
+ static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State"));
+ QString deviceName = (device ? device->name() : QLatin1String("null device"));
+ deviceName.resize(16, ' '); // shorten, and align in case of sequential output
+ qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state())
+ << ": grab" << m_grabber << "->" << grabber;
+ }
+ m_grabber = QPointer<QObject>(grabber);
+ m_grabberIsHandler = true;
+ m_sceneGrabPos = m_scenePos;
+ }
+}
+
+void QQuickEventPoint::cancelGrab()
+{
+ if (m_grabber.isNull()) {
+ qWarning("cancelGrab: no grabber");
+ return;
+ }
+ if (auto handler = grabberPointerHandler())
+ handler->handleGrabCancel(this);
+ m_grabber.clear();
}
void QQuickEventPoint::setAccepted(bool accepted)
@@ -544,18 +626,86 @@ void QQuickEventPoint::setAccepted(bool accepted)
}
}
+bool QQuickEventPoint::isDraggedOverThreshold() const
+{
+ QPointF delta = scenePos() - scenePressPos();
+ return (QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, this) ||
+ QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, this));
+}
+
QQuickEventTouchPoint::QQuickEventTouchPoint(QQuickPointerTouchEvent *parent)
: QQuickEventPoint(parent), m_rotation(0), m_pressure(0)
{}
void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong timestamp)
{
- QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp);
+ QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp, tp.velocity());
m_rotation = tp.rotation();
m_pressure = tp.pressure();
+ m_ellipseDiameters = tp.ellipseDiameters();
m_uniqueId = tp.uniqueId();
}
+struct PointVelocityData {
+ QVector2D velocity;
+ QPointF pos;
+ ulong timestamp;
+};
+
+typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap;
+Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData)
+static const int PointVelocityAgeLimit = 500; // milliseconds
+
+/*!
+ * \interal
+ * \brief Estimates the velocity based on a weighted average of all previous velocities.
+ * The older the velocity is, the less significant it becomes for the estimate.
+ * \return
+ */
+QVector2D QQuickEventPoint::estimatedVelocity() const
+{
+ PointVelocityData *prevPoint = g_previousPointData->value(m_pointId);
+ if (!prevPoint) {
+ // cleanup events older than PointVelocityAgeLimit
+ auto end = g_previousPointData->end();
+ for (auto it = g_previousPointData->begin(); it != end; ) {
+ PointVelocityData *data = it.value();
+ if (m_timestamp - data->timestamp > PointVelocityAgeLimit) {
+ it = g_previousPointData->erase(it);
+ delete data;
+ } else {
+ ++it;
+ }
+ }
+ // TODO optimize: stop this dynamic memory thrashing
+ prevPoint = new PointVelocityData;
+ prevPoint->velocity = QVector2D();
+ prevPoint->timestamp = 0;
+ prevPoint->pos = QPointF();
+ g_previousPointData->insert(m_pointId, prevPoint);
+ }
+ if (prevPoint) {
+ const ulong timeElapsed = m_timestamp - prevPoint->timestamp;
+ if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint
+ return m_velocity;
+
+ QVector2D newVelocity;
+ if (prevPoint->timestamp != 0)
+ newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed;
+
+ // VERY simple kalman filter: does a weighted average
+ // where the older velocities get less and less significant
+ static const float KalmanGain = 0.7f;
+ QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain);
+
+ prevPoint->velocity = filteredVelocity;
+ prevPoint->pos = m_scenePos;
+ prevPoint->timestamp = m_timestamp;
+ return filteredVelocity;
+ }
+ return QVector2D();
+}
+
/*!
\internal
\class QQuickPointerEvent
@@ -598,10 +748,15 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event)
default:
break;
}
- m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0
+ m_mousePoint->reset(state, ev->windowPos(), quint64(1) << 24, ev->timestamp()); // mouse has device ID 1
return this;
}
+void QQuickPointerMouseEvent::localize(QQuickItem *target)
+{
+ m_mousePoint->localize(target);
+}
+
QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event)
{
auto ev = static_cast<QTouchEvent*>(event);
@@ -621,11 +776,11 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event)
m_touchPoints.insert(i, new QQuickEventTouchPoint(this));
// Make sure the grabbers are right from one event to the next
- QVector<QQuickItem*> grabbers;
+ QVector<QObject*> grabbers;
// Copy all grabbers, because the order of points might have changed in the event.
// The ID is all that we can rely on (release might remove the first point etc).
for (int i = 0; i < newPointCount; ++i) {
- QQuickItem *grabber = nullptr;
+ QObject *grabber = nullptr;
if (auto point = pointById(tps.at(i).id()))
grabber = point->grabber();
grabbers.append(grabber);
@@ -637,7 +792,7 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event)
if (point->state() == QQuickEventPoint::Pressed) {
if (grabbers.at(i))
qWarning() << "TouchPointPressed without previous release event" << point;
- point->setGrabber(nullptr);
+ point->setGrabberItem(nullptr);
} else {
point->setGrabber(grabbers.at(i));
}
@@ -646,6 +801,12 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event)
return this;
}
+void QQuickPointerTouchEvent::localize(QQuickItem *target)
+{
+ for (auto point : qAsConst(m_touchPoints))
+ point->localize(target);
+}
+
QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const {
if (i == 0)
return m_mousePoint;
@@ -660,7 +821,7 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const {
QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent)
: QObject(parent), m_pointId(0), m_grabber(nullptr), m_timestamp(0), m_pressTimestamp(0),
- m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false)
+ m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false), m_grabberIsHandler(false)
{
Q_UNUSED(m_reserved);
}
@@ -681,16 +842,16 @@ QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) cons
return event;
}
-QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const
+QVector<QObject *> QQuickPointerMouseEvent::grabbers() const
{
- QVector<QQuickItem *> result;
- if (QQuickItem *grabber = m_mousePoint->grabber())
+ QVector<QObject *> result;
+ if (QObject *grabber = m_mousePoint->grabber())
result << grabber;
return result;
}
void QQuickPointerMouseEvent::clearGrabbers() const {
- m_mousePoint->setGrabber(nullptr);
+ m_mousePoint->setGrabberItem(nullptr);
}
bool QQuickPointerMouseEvent::isPressEvent() const
@@ -708,12 +869,12 @@ bool QQuickPointerTouchEvent::allPointsAccepted() const {
return true;
}
-QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const
+QVector<QObject *> QQuickPointerTouchEvent::grabbers() const
{
- QVector<QQuickItem *> result;
+ QVector<QObject *> result;
for (int i = 0; i < m_pointCount; ++i) {
auto point = m_touchPoints.at(i);
- if (QQuickItem *grabber = point->grabber()) {
+ if (QObject *grabber = point->grabber()) {
if (!result.contains(grabber))
result << grabber;
}
diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h
index cf6f83e5b1..4a78e98705 100644
--- a/src/quick/items/qquickevents_p_p.h
+++ b/src/quick/items/qquickevents_p_p.h
@@ -68,6 +68,7 @@ class QQuickPointerEvent;
class QQuickPointerMouseEvent;
class QQuickPointerTabletEvent;
class QQuickPointerTouchEvent;
+class QQuickPointerHandler;
class QQuickKeyEvent : public QObject
{
@@ -251,12 +252,18 @@ private:
class Q_QUICK_PRIVATE_EXPORT QQuickEventPoint : public QObject
{
Q_OBJECT
+ Q_PROPERTY(QQuickPointerEvent *event READ pointerEvent)
+ Q_PROPERTY(QPointF pos READ pos)
Q_PROPERTY(QPointF scenePos READ scenePos)
+ Q_PROPERTY(QPointF scenePressPos READ scenePressPos)
+ Q_PROPERTY(QPointF sceneGrabPos READ sceneGrabPos)
+ Q_PROPERTY(QVector2D velocity READ velocity)
Q_PROPERTY(State state READ state)
Q_PROPERTY(quint64 pointId READ pointId)
Q_PROPERTY(qreal timeHeld READ timeHeld)
+ Q_PROPERTY(QVector2D velocity READ velocity)
Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- Q_PROPERTY(QQuickItem *grabber READ grabber WRITE setGrabber)
+ Q_PROPERTY(QObject *grabber READ grabber WRITE setGrabber)
public:
enum State {
@@ -270,31 +277,53 @@ public:
QQuickEventPoint(QQuickPointerEvent *parent);
- void reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp);
+ void reset(Qt::TouchPointState state, const QPointF &scenePos, quint64 pointId, ulong timestamp, const QVector2D &velocity = QVector2D());
+ void localize(QQuickItem *target);
- void invalidate() { m_valid = false; }
+ void invalidate();
QQuickPointerEvent *pointerEvent() const;
+ QPointF pos() const { return m_pos; }
QPointF scenePos() const { return m_scenePos; }
+ QPointF scenePressPos() const { return m_scenePressPos; }
+ QPointF sceneGrabPos() const { return m_sceneGrabPos; }
+ QVector2D velocity() const { return m_velocity; }
State state() const { return m_state; }
quint64 pointId() const { return m_pointId; }
bool isValid() const { return m_valid; }
qreal timeHeld() const { return (m_timestamp - m_pressTimestamp) / 1000.0; }
bool isAccepted() const { return m_accept; }
void setAccepted(bool accepted = true);
- QQuickItem *grabber() const;
- void setGrabber(QQuickItem *grabber);
+ bool isDraggedOverThreshold() const;
+ QObject *grabber() const;
+ void setGrabber(QObject *grabber);
+
+ QQuickItem *grabberItem() const;
+ void setGrabberItem(QQuickItem *grabber);
+
+ QQuickPointerHandler *grabberPointerHandler() const;
+ void setGrabberPointerHandler(QQuickPointerHandler *grabber);
+
+ Q_INVOKABLE void cancelGrab();
+
+private:
+ QVector2D estimatedVelocity() const;
private:
+ QPointF m_pos;
QPointF m_scenePos;
+ QPointF m_scenePressPos;
+ QPointF m_sceneGrabPos;
+ QVector2D m_velocity;
quint64 m_pointId;
- QPointer<QQuickItem> m_grabber;
+ QPointer<QObject> m_grabber;
ulong m_timestamp;
ulong m_pressTimestamp;
State m_state;
bool m_valid : 1;
bool m_accept : 1;
- int m_reserved : 30;
+ bool m_grabberIsHandler : 1;
+ int m_reserved : 29;
Q_DISABLE_COPY(QQuickEventPoint)
};
@@ -304,6 +333,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickEventTouchPoint : public QQuickEventPoint
Q_OBJECT
Q_PROPERTY(qreal rotation READ rotation)
Q_PROPERTY(qreal pressure READ pressure)
+ Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters)
Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId)
public:
@@ -313,11 +343,13 @@ public:
qreal rotation() const { return m_rotation; }
qreal pressure() const { return m_pressure; }
+ QSizeF ellipseDiameters() const { return m_ellipseDiameters; }
QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; }
private:
qreal m_rotation;
qreal m_pressure;
+ QSizeF m_ellipseDiameters;
QPointingDeviceUniqueId m_uniqueId;
Q_DISABLE_COPY(QQuickEventTouchPoint)
@@ -350,6 +382,7 @@ public: // property accessors
public: // helpers for C++ only (during event delivery)
virtual QQuickPointerEvent *reset(QEvent *ev) = 0;
+ virtual void localize(QQuickItem *target) = 0;
virtual bool isPressEvent() const = 0;
virtual QQuickPointerMouseEvent *asPointerMouseEvent() { return nullptr; }
@@ -367,7 +400,7 @@ public: // helpers for C++ only (during event delivery)
virtual int pointCount() const = 0;
virtual QQuickEventPoint *point(int i) const = 0;
virtual QQuickEventPoint *pointById(quint64 pointId) const = 0;
- virtual QVector<QQuickItem *> grabbers() const = 0;
+ virtual QVector<QObject *> grabbers() const = 0;
virtual void clearGrabbers() const = 0;
ulong timestamp() const { return m_event->timestamp(); }
@@ -389,6 +422,7 @@ public:
: QQuickPointerEvent(parent), m_mousePoint(new QQuickEventPoint(this)) { }
QQuickPointerEvent *reset(QEvent *) override;
+ void localize(QQuickItem *target) override;
bool isPressEvent() const override;
QQuickPointerMouseEvent *asPointerMouseEvent() override { return this; }
const QQuickPointerMouseEvent *asPointerMouseEvent() const override { return this; }
@@ -396,7 +430,7 @@ public:
QQuickEventPoint *point(int i) const override;
QQuickEventPoint *pointById(quint64 pointId) const override;
bool allPointsAccepted() const override;
- QVector<QQuickItem *> grabbers() const override;
+ QVector<QObject *> grabbers() const override;
void clearGrabbers() const override;
QMouseEvent *asMouseEvent(const QPointF& localPos) const;
@@ -418,6 +452,7 @@ public:
{ }
QQuickPointerEvent *reset(QEvent *) override;
+ void localize(QQuickItem *target) override;
bool isPressEvent() const override;
QQuickPointerTouchEvent *asPointerTouchEvent() override { return this; }
const QQuickPointerTouchEvent *asPointerTouchEvent() const override { return this; }
@@ -426,7 +461,7 @@ public:
QQuickEventPoint *pointById(quint64 pointId) const override;
const QTouchEvent::TouchPoint *touchPointById(int pointId) const;
bool allPointsAccepted() const override;
- QVector<QQuickItem *> grabbers() const override;
+ QVector<QObject *> grabbers() const override;
void clearGrabbers() const override;
QMouseEvent *syntheticMouseEvent(int pointID, QQuickItem *relativeTo) const;
@@ -497,21 +532,6 @@ public:
Q_ENUM(CapabilityFlag)
Q_FLAG(Capabilities)
- QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0)
- : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps)
- , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name)
- , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)), m_event(nullptr)
- {
- if (m_deviceType == Mouse) {
- m_event = new QQuickPointerMouseEvent;
- } else if (m_deviceType == TouchScreen || m_deviceType == TouchPad) {
- m_event = new QQuickPointerTouchEvent;
- } else {
- Q_ASSERT(false);
- }
- }
-
- ~QQuickPointerDevice() { delete m_event; }
DeviceType type() const { return m_deviceType; }
PointerType pointerType() const { return m_pointerType; }
Capabilities capabilities() const { return m_capabilities; }
@@ -528,6 +548,22 @@ public:
static QQuickPointerDevice *tabletDevice(qint64);
private:
+ QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0)
+ : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps)
+ , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name)
+ , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)), m_event(nullptr)
+ {
+ if (m_deviceType == Mouse) {
+ m_event = new QQuickPointerMouseEvent;
+ } else if (m_deviceType == TouchScreen || m_deviceType == TouchPad) {
+ m_event = new QQuickPointerTouchEvent;
+ } else {
+ Q_ASSERT(false);
+ }
+ }
+ ~QQuickPointerDevice() { delete m_event; }
+
+private:
DeviceType m_deviceType;
PointerType m_pointerType;
Capabilities m_capabilities;
@@ -539,6 +575,7 @@ private:
QQuickPointerEvent *m_event;
Q_DISABLE_COPY(QQuickPointerDevice)
+ friend struct ConstructableQQuickPointerDevice;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPointerDevice::DeviceTypes)
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp
index 4ab604b30f..45bb2a367d 100644
--- a/src/quick/items/qquickflickable.cpp
+++ b/src/quick/items/qquickflickable.cpp
@@ -44,6 +44,7 @@
#include "qquickwindow_p.h"
#include "qquickevents_p_p.h"
+#include <QtQuick/private/qquickpointerhandler_p.h>
#include <QtQuick/private/qquicktransition_p.h>
#include <private/qqmlglobal_p.h>
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index f7b9a58329..98cc3112df 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -67,6 +67,7 @@
#include <QtQuick/private/qquickstate_p.h>
#include <private/qquickitem_p.h>
#include <QtQuick/private/qquickaccessibleattached_p.h>
+#include <QtQuick/private/qquickpointerhandler_p.h>
#include <private/qv4engine_p.h>
#include <private/qv4object_p.h>
@@ -86,6 +87,7 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(DBG_MOUSE_TARGET)
Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE)
+Q_DECLARE_LOGGING_CATEGORY(lcPointerHandlerDispatch)
void debugFocusTree(QQuickItem *item, QQuickItem *scope = 0, int depth = 1)
{
@@ -3219,7 +3221,14 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
} else {
if (o->inherits("QGraphicsItem"))
qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className());
- else {
+ else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
+ Q_ASSERT(pointerHandler->parentItem() == that);
+ // Accept all buttons, and leave filtering to pointerEvent() and/or user JS,
+ // because there can be multiple handlers...
+ that->setAcceptedMouseButtons(Qt::AllButtons);
+ QQuickItemPrivate *p = QQuickItemPrivate::get(that);
+ p->extra.value().pointerHandlers.append(pointerHandler);
+ } else {
QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o);
QQuickItem *item = that;
QQuickWindow *itemWindow = that->window();
@@ -5082,6 +5091,17 @@ void QQuickItemPrivate::deliverShortcutOverrideEvent(QKeyEvent *event)
}
}
+void QQuickItemPrivate::handlePointerEvent(QQuickPointerEvent *event)
+{
+ Q_Q(QQuickItem);
+ if (extra.isAllocated()) {
+ for (QQuickPointerHandler *handler : extra->pointerHandlers) {
+ qCDebug(lcPointerHandlerDispatch) << " delivering" << event << "to" << handler << "on" << q;
+ handler->handlePointerEvent(event);
+ }
+ }
+}
+
/*!
Called when \a change occurs for this item.
diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h
index c0c9bd46bd..e7ead7fa87 100644
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -87,6 +87,7 @@ class QQuickItemKeyFilter;
class QQuickLayoutMirroringAttached;
class QQuickEnterKeyAttached;
class QQuickScreenAttached;
+class QQuickPointerHandler;
class QQuickContents : public QQuickItemChangeListener
{
@@ -344,6 +345,7 @@ public:
QQuickLayoutMirroringAttached* layoutDirectionAttached;
QQuickEnterKeyAttached *enterKeyAttached;
QQuickItemKeyFilter *keyHandler;
+ QVector<QQuickPointerHandler *> pointerHandlers;
#if QT_CONFIG(quick_shadereffect)
mutable QQuickItemLayer *layer;
#endif
@@ -561,6 +563,8 @@ public:
#endif
void deliverShortcutOverrideEvent(QKeyEvent *);
+ virtual void handlePointerEvent(QQuickPointerEvent *);
+
bool isTransparentForPositioner() const;
void setTransparentForPositioner(bool trans);
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 660c5f8067..fa3093c245 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -46,6 +46,7 @@
#include "qquickevents_p_p.h"
#include <private/qquickdrag_p.h>
+#include <private/qquickpointerhandler_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
#include <QtQuick/private/qsgtexture_p.h>
@@ -659,7 +660,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve
if (!q->mouseGrabberItem())
item->grabMouse();
auto pointerEventPoint = pointerEvent->pointById(p.id());
- pointerEventPoint->setGrabber(item);
+ pointerEventPoint->setGrabberItem(item);
if (checkIfDoubleClicked(event->timestamp())) {
QScopedPointer<QMouseEvent> mouseDoubleClick(touchToMouseEvent(QEvent::MouseButtonDblClick, p, event, item, false));
@@ -747,11 +748,11 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber)
qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << touchMouseId << "->" << q->mouseGrabberItem();
auto point = touchMouseDevice->pointerEvent()->pointById(touchMouseId);
if (point)
- point->setGrabber(grabber);
+ point->setGrabberItem(grabber);
} else {
QQuickPointerEvent *event = QQuickPointerDevice::genericMouseDevice()->pointerEvent();
Q_ASSERT(event->pointCount() == 1);
- event->point(0)->setGrabber(grabber);
+ event->point(0)->setGrabberItem(grabber);
}
if (oldGrabber) {
@@ -762,9 +763,9 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber)
}
}
-void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids)
+void QQuickWindowPrivate::grabTouchPoints(QObject *grabber, const QVector<int> &ids)
{
- QSet<QQuickItem*> ungrab;
+ QSet<QObject*> ungrab;
for (int i = 0; i < ids.count(); ++i) {
// FIXME: deprecate this function, we need a device
int id = ids.at(i);
@@ -774,7 +775,7 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int
}
if (id == touchMouseId) {
auto point = touchMouseDevice->pointerEvent()->pointById(id);
- auto touchMouseGrabber = point->grabber();
+ auto touchMouseGrabber = point->grabberItem();
if (touchMouseGrabber) {
point->setGrabber(nullptr);
touchMouseGrabber->mouseUngrabEvent();
@@ -790,7 +791,7 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int
auto point = device->pointerEvent()->pointById(id);
if (!point)
continue;
- QQuickItem *oldGrabber = point->grabber();
+ QObject *oldGrabber = point->grabber();
if (oldGrabber == grabber)
continue;
@@ -799,8 +800,10 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int
ungrab.insert(oldGrabber);
}
}
- for (QQuickItem *oldGrabber : qAsConst(ungrab))
- oldGrabber->touchUngrabEvent();
+ for (QObject *oldGrabber : qAsConst(ungrab))
+ if (QQuickItem *item = qmlobject_cast<QQuickItem *>(oldGrabber))
+ item->touchUngrabEvent();
+ // TODO else if the old grabber was a PointerHandler, notify it somehow?
}
void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch)
@@ -812,7 +815,7 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to
auto pointerEvent = device->pointerEvent();
for (int i = 0; i < pointerEvent->pointCount(); ++i) {
if (pointerEvent->point(i)->grabber() == grabber) {
- pointerEvent->point(i)->setGrabber(nullptr);
+ pointerEvent->point(i)->setGrabberItem(nullptr);
// FIXME send ungrab event only once
grabber->touchUngrabEvent();
}
@@ -1485,12 +1488,12 @@ QQuickItem *QQuickWindow::mouseGrabberItem() const
QQuickPointerEvent *event = d->touchMouseDevice->pointerEvent();
auto point = event->pointById(d->touchMouseId);
Q_ASSERT(point);
- return point->grabber();
+ return point->grabberItem();
}
QQuickPointerEvent *event = QQuickPointerDevice::genericMouseDevice()->pointerEvent();
Q_ASSERT(event->pointCount());
- return event->point(0)->grabber();
+ return event->point(0)->grabberItem();
}
@@ -1642,7 +1645,14 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven
Q_Q(QQuickWindow);
auto point = pointerEvent->point(0);
lastMousePosition = point->scenePos();
- QQuickItem *grabber = point->grabber();
+
+ if (auto handler = point->grabberPointerHandler()) {
+ pointerEvent->localize(handler->parentItem());
+ handler->handlePointerEvent(pointerEvent);
+ return;
+ }
+
+ QQuickItem *grabber = point->grabberItem();
if (grabber) {
// if the update consists of changing button state, then don't accept it
// unless the button is one in which the item is interested
@@ -1873,10 +1883,14 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event)
// A TouchCancel event will typically not contain any points.
// Deliver it to all items that have active touches.
QQuickPointerEvent *pointerEvent = QQuickPointerDevice::touchDevice(event->device())->pointerEvent();
- QVector<QQuickItem *> grabbers = pointerEvent->grabbers();
-
- for (QQuickItem *grabber: qAsConst(grabbers)) {
- q->sendEvent(grabber, event);
+ QVector<QObject *> grabbers = pointerEvent->grabbers();
+
+ for (QObject *grabber: qAsConst(grabbers)) {
+ if (QQuickItem *grabberItem = qmlobject_cast<QQuickItem *>(grabber))
+ q->sendEvent(grabberItem, event);
+ else //if (QQuickPointerHandler *grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber))
+// grabberHandler->handlePointerEvent()
+ qWarning("unexpected: can't deliver touch cancel to a PointerHandler (yet?)");
}
touchMouseId = -1;
touchMouseDevice = nullptr;
@@ -2014,8 +2028,6 @@ void QQuickWindow::mouseReleaseEvent(QMouseEvent *event)
void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event)
{
- Q_Q(QQuickWindow);
-
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
event->accept();
return;
@@ -2048,7 +2060,7 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event)
updateCursor(event->windowPos());
#endif
- if (!q->mouseGrabberItem()) {
+ if (!QQuickPointerDevice::genericMouseDevice()->pointerEvent()->point(0)->grabber()) {
QPointF last = lastMousePosition.isNull() ? event->windowPos() : lastMousePosition;
lastMousePosition = event->windowPos();
@@ -2217,7 +2229,7 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event)
if (point->state() == QQuickEventPoint::Released) {
int id = point->pointId();
qCDebug(DBG_TOUCH_TARGET) << "TP" << id << "released";
- point->setGrabber(nullptr);
+ point->setGrabberItem(nullptr);
if (id == touchMouseId) {
touchMouseId = -1;
touchMouseDevice = nullptr;
@@ -2237,8 +2249,14 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event)
bool QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered)
{
const auto grabbers = event->grabbers();
- for (auto grabber : grabbers)
- deliverMatchingPointsToItem(grabber, event, hasFiltered);
+ for (auto grabber : grabbers) {
+ // The grabber is guaranteed to be either an item or a handler, but
+ // we need the item in order to call deliverMatchingPointsToItem().
+ QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber);
+ if (!receiver)
+ receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
+ deliverMatchingPointsToItem(receiver, event, hasFiltered);
+ }
return false;
}
@@ -2269,6 +2287,13 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQui
bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, QSet<QQuickItem *> *hasFiltered)
{
Q_Q(QQuickWindow);
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+ pointerEvent->localize(item);
+
+ // Let the Item's handlers (if any) have the event first.
+ itemPrivate->handlePointerEvent(pointerEvent);
+ if (pointerEvent->allPointsAccepted())
+ return true;
// TODO: unite this mouse point delivery with the synthetic mouse event below
if (auto event = pointerEvent->asPointerMouseEvent()) {
@@ -2276,7 +2301,6 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
auto point = event->point(0);
if (point->isAccepted())
return false;
-
// The only reason to already have a mouse grabber here is
// synthetic events - flickable sends one when setPressDelay is used.
auto oldMouseGrabber = q->mouseGrabberItem();
@@ -2328,7 +2352,6 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
eventAccepted = touchEvent->isAccepted();
// If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it.
- QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) {
// send mouse event
if (deliverTouchAsMouse(item, event))
@@ -2342,7 +2365,7 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
auto pointerEventPoint = event->pointById(point.id());
pointerEventPoint->setAccepted();
if (point.state() == Qt::TouchPointPressed)
- pointerEventPoint->setGrabber(item);
+ pointerEventPoint->setGrabberItem(item);
}
} else {
// But if the event was not accepted then we know this item
@@ -2351,7 +2374,7 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
if (point.state() == Qt::TouchPointPressed) {
if (event->pointById(point.id())->grabber() == item) {
qCDebug(DBG_TOUCH_TARGET) << "TP" << point.id() << "disassociated";
- event->pointById(point.id())->setGrabber(nullptr);
+ event->pointById(point.id())->setGrabberItem(nullptr);
}
}
}
@@ -2590,7 +2613,7 @@ bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem
qCDebug(DBG_TOUCH_TARGET) << "TP" << tp.id() << "->" << target;
touchMouseId = tp.id();
touchMouseDevice = event->device();
- touchMouseDevice->pointerEvent()->pointById(tp.id())->setGrabber(target);
+ touchMouseDevice->pointerEvent()->pointById(tp.id())->setGrabberItem(target);
target->grabMouse();
}
filtered = true;
diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h
index be915903c6..30e3b71d0a 100644
--- a/src/quick/items/qquickwindow_p.h
+++ b/src/quick/items/qquickwindow_p.h
@@ -143,7 +143,7 @@ public:
bool deliverTouchAsMouse(QQuickItem *item, QQuickPointerEvent *pointerEvent);
void translateTouchEvent(QTouchEvent *touchEvent);
void setMouseGrabber(QQuickItem *grabber);
- void grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids);
+ void grabTouchPoints(QObject *grabber, const QVector<int> &ids);
void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true);
static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = 0);
void deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent);
diff --git a/src/quick/quick.pro b/src/quick/quick.pro
index eae9b09b2f..e9a8b84b2a 100644
--- a/src/quick/quick.pro
+++ b/src/quick/quick.pro
@@ -30,6 +30,7 @@ ANDROID_BUNDLED_FILES += \
include(util/util.pri)
include(scenegraph/scenegraph.pri)
include(items/items.pri)
+include(handlers/handlers.pri)
qtConfig(quick-designer): \
include(designer/designer.pri)
qtConfig(accessibility) {
diff --git a/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml b/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml
new file mode 100644
index 0000000000..3545badd86
--- /dev/null
+++ b/tests/auto/quick/handlers/qquickdraghandler/data/reparenting.qml
@@ -0,0 +1,60 @@
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Grid {
+ id: root
+ objectName: "root"
+ property bool reparentOnDrag: true
+ width: 200; height: 200
+ columns: 3
+ spacing: 10
+ Repeater {
+ model: 9
+ anchors.fill: parent
+ Item {
+ id: gridPlaceholder
+ objectName: "gridPlaceholder" + index
+ width: 60
+ height: 60
+ Rectangle {
+ id: icon
+ border.color: "black"
+ color: "beige"
+ radius: 3
+ width: 60
+ height: 60
+ onParentChanged :console.log("parent " + parent)
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ DragHandler {
+ id: dragArea
+ }
+ Text {
+ anchors.centerIn: parent
+ text: index + "@" + Math.round(icon.x) + "," + Math.round(icon.y)
+ font.pointSize: 8
+ }
+ states: [
+ State {
+ when: dragArea.dragging
+ AnchorChanges {
+ target: icon
+ anchors.horizontalCenter: undefined
+ anchors.verticalCenter: undefined
+ }
+ ParentChange {
+ target: root.reparentOnDrag ? icon : null
+ parent: root
+ }
+ PropertyChanges {
+ target: icon
+ color: "yellow"
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
new file mode 100644
index 0000000000..4a3f99765e
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+
+qtConfig(private_tests) {
+ SUBDIRS += \
+ qquickpointerhandler \
+}
+
diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml
new file mode 100644
index 0000000000..fe05a3f935
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml
@@ -0,0 +1,22 @@
+import QtQuick 2.8
+import Qt.test 1.0
+
+Rectangle {
+ id: root
+ width: 320
+ height: 480
+ color: "green"
+
+ EventItem {
+ objectName: "eventItem1"
+ x: 5
+ y: 5
+ height: 30
+ width: 30
+
+ EventHandler {
+
+ }
+ }
+}
+
diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro
new file mode 100644
index 0000000000..c386969206
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro
@@ -0,0 +1,16 @@
+CONFIG += testcase
+
+TARGET = tst_qquickpointerhandler
+QT += core-private gui-private qml-private quick-private testlib
+
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquickpointerhandler.cpp
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+# OTHER_FILES += data/foo.qml
+
diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp
new file mode 100644
index 0000000000..94bb44a12b
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp
@@ -0,0 +1,465 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module 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 <QtGui/qstylehints.h>
+
+#include <QtQuick/qquickview.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/private/qquickmousearea_p.h>
+#include <QtQuick/private/qquickmultipointtoucharea_p.h>
+#include <QtQuick/private/qquickpincharea_p.h>
+#include <QtQuick/private/qquickflickable_p.h>
+#include <QtQuick/private/qquickpointerhandler_p.h>
+#include <qpa/qwindowsysteminterface.h>
+
+#include <private/qquickwindow_p.h>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlproperty.h>
+
+#include "../../../shared/util.h"
+#include "../../shared/viewtestutil.h"
+
+Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
+
+struct Event
+{
+ Event(QEvent::Type t, QPointF item, QPointF scene)
+ :type(t), posWrtItem(item), posWrtScene(scene)
+ {}
+
+ QEvent::Type type;
+ QPointF posWrtItem;
+ QPointF posWrtScene;
+
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const struct Event &event) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ dbg << "Event(" << event.type << " @" << event.posWrtScene << ")";
+ return dbg;
+}
+#endif
+
+class EventItem : public QQuickItem
+{
+ Q_OBJECT
+public:
+ EventItem(QQuickItem *parent = 0)
+ : QQuickItem(parent), acceptPointer(false), acceptMouse(false), acceptTouch(false), filterTouch(false)
+ {}
+
+ void touchEvent(QTouchEvent *event)
+ {
+ qCDebug(lcPointerTests) << event << "will accept?" << acceptTouch;
+ for (const QTouchEvent::TouchPoint &tp : event->touchPoints())
+ eventList.append(Event(event->type(), tp.pos(), tp.scenePos()));
+ event->setAccepted(acceptTouch);
+ }
+ void mousePressEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(event->type(), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseMoveEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(event->type(), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseReleaseEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(event->type(), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseDoubleClickEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(event->type(), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+
+ void mouseUngrabEvent()
+ {
+ qCDebug(lcPointerTests);
+ eventList.append(Event(QEvent::UngrabMouse, QPoint(0,0), QPoint(0,0)));
+ }
+
+ bool event(QEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ return QQuickItem::event(event);
+ }
+
+ QList<Event> eventList;
+ bool acceptPointer;
+ bool acceptMouse;
+ bool acceptTouch;
+ bool filterTouch; // when used as event filter
+
+ bool eventFilter(QObject *o, QEvent *event)
+ {
+ qCDebug(lcPointerTests) << event << o;
+ if (event->type() == QEvent::TouchBegin ||
+ event->type() == QEvent::TouchUpdate ||
+ event->type() == QEvent::TouchCancel ||
+ event->type() == QEvent::TouchEnd) {
+ QTouchEvent *touch = static_cast<QTouchEvent*>(event);
+ for (const QTouchEvent::TouchPoint &tp : touch->touchPoints())
+ eventList.append(Event(event->type(), tp.pos(), tp.scenePos()));
+ if (filterTouch)
+ event->accept();
+ return true;
+ }
+ return false;
+ }
+};
+
+class EventHandler : public QQuickPointerHandler
+{
+ void handlePointerEventImpl(QQuickPointerEvent *event) override
+ {
+ QQuickPointerHandler::handlePointerEventImpl(event);
+ if (!enabled())
+ return;
+ EventItem *item = static_cast<EventItem *>(target());
+ qCDebug(lcPointerTests) << item->objectName() << event;
+ int c = event->pointCount();
+ for (int i = 0; i < c; ++i) {
+ QQuickEventPoint *point = event->point(i);
+ point->setAccepted(item->acceptPointer); // has no effect: grabbing is up to us
+ if (item->acceptPointer)
+ setGrab(point, true);
+ qCDebug(lcPointerTests) << " " << i << ":" << point << "grabbed?" << item->acceptPointer;
+ item->eventList.append(Event(QEvent::Pointer, eventPos(point), point->scenePos()));
+ }
+ }
+};
+
+class tst_PointerHandlers : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_PointerHandlers()
+ :touchDevice(QTest::createTouchDevice())
+ {}
+
+private slots:
+ void initTestCase();
+
+ void touchEventDelivery();
+ void mouseEventDelivery();
+
+protected:
+ bool eventFilter(QObject *, QEvent *event)
+ {
+ if (event->type() == QEvent::MouseButtonPress ||
+ event->type() == QEvent::MouseMove ||
+ event->type() == QEvent::MouseButtonRelease) {
+ QMouseEvent *me = static_cast<QMouseEvent*>(event);
+ filteredEventList.append(Event(me->type(), me->pos(), me->globalPos()));
+ }
+ return false;
+ }
+
+private:
+ QQuickView *createView();
+ QTouchDevice *touchDevice;
+ QList<Event> filteredEventList;
+};
+
+QQuickView *tst_PointerHandlers::createView()
+{
+ QQuickView *window = new QQuickView(0);
+ window->setGeometry(0,0,240,320);
+
+ return window;
+}
+
+void tst_PointerHandlers::initTestCase()
+{
+ // This test assumes that we don't get synthesized mouse events from QGuiApplication
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
+
+ QQmlDataTest::initTestCase();
+ qmlRegisterType<EventItem>("Qt.test", 1, 0, "EventItem");
+ qmlRegisterType<EventHandler>("Qt.test", 1, 0, "EventHandler");
+}
+
+void tst_PointerHandlers::touchEventDelivery()
+{
+ QQuickView *window = createView();
+
+ window->setSource(testFileUrl("singleitem.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(window->rootObject() != 0);
+
+ EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1");
+ QVERIFY(eventItem1);
+
+ // Do not accept anything
+ QPoint p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_COMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ eventItem1->eventList.clear();
+
+ // Accept touch
+ eventItem1->acceptTouch = true;
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin);
+ auto pointerEvent = QQuickPointerDevice::touchDevices().at(0)->pointerEvent();
+ QCOMPARE(pointerEvent->point(0)->grabber(), eventItem1);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(3).type, QEvent::TouchUpdate);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE(eventItem1->eventList.at(4).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(5).type, QEvent::TouchEnd);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Accept mouse
+ eventItem1->acceptTouch = false;
+ eventItem1->acceptMouse = true;
+ eventItem1->setAcceptedMouseButtons(Qt::LeftButton);
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress);
+ QCOMPARE(window->mouseGrabberItem(), eventItem1);
+
+ QPointF localPos = eventItem1->mapFromScene(p1);
+ QPointF scenePos = p1; // item is at 0,0
+ QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos);
+ QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos);
+ QCOMPARE(eventItem1->eventList.at(2).posWrtItem, localPos);
+ QCOMPARE(eventItem1->eventList.at(2).posWrtScene, scenePos);
+
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE(eventItem1->eventList.at(3).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(4).type, QEvent::TouchUpdate);
+ QCOMPARE(eventItem1->eventList.at(5).type, QEvent::MouseMove);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 10);
+ QCOMPARE(eventItem1->eventList.at(6).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(7).type, QEvent::TouchEnd);
+ QCOMPARE(eventItem1->eventList.at(8).type, QEvent::MouseButtonRelease);
+ QCOMPARE(eventItem1->eventList.at(9).type, QEvent::UngrabMouse);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Accept mouse buttons but not the touch event
+ eventItem1->acceptTouch = false;
+ eventItem1->acceptMouse = false;
+ eventItem1->setAcceptedMouseButtons(Qt::LeftButton);
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseButtonPress);
+ QCOMPARE(pointerEvent->point(0)->grabber(), nullptr);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Accept touch
+ eventItem1->acceptTouch = true;
+ eventItem1->setAcceptedMouseButtons(Qt::LeftButton);
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::TouchBegin);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(3).type, QEvent::TouchUpdate);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE(eventItem1->eventList.at(4).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(5).type, QEvent::TouchEnd);
+ eventItem1->eventList.clear();
+
+ // Accept pointer events
+ eventItem1->acceptPointer = true;
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 1);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::Pointer);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer);
+ eventItem1->eventList.clear();
+
+ delete window;
+}
+
+void tst_PointerHandlers::mouseEventDelivery()
+{
+ QQuickView *window = createView();
+
+ window->setSource(testFileUrl("singleitem.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(window->rootObject() != 0);
+
+ EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1");
+ QVERIFY(eventItem1);
+
+ // Do not accept anything
+ QPoint p1 = QPoint(20, 20);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ p1 += QPoint(10, 0);
+ QTest::mouseMove(window, p1);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QTest::mouseRelease(window, Qt::LeftButton);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Accept mouse
+ eventItem1->acceptTouch = false;
+ eventItem1->acceptMouse = true;
+ eventItem1->setAcceptedMouseButtons(Qt::LeftButton);
+ p1 = QPoint(20, 20);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonPress);
+ QCOMPARE(window->mouseGrabberItem(), eventItem1);
+
+ QPointF localPos = eventItem1->mapFromScene(p1);
+ QPointF scenePos = p1; // item is at 0,0
+ QCOMPARE(eventItem1->eventList.at(0).posWrtItem, localPos);
+ QCOMPARE(eventItem1->eventList.at(0).posWrtScene, scenePos);
+ QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos);
+ QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos);
+
+ p1 += QPoint(10, 0);
+ QTest::mouseMove(window, p1);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::MouseMove);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ QCOMPARE(eventItem1->eventList.at(3).type, QEvent::MouseButtonRelease);
+ QCOMPARE(eventItem1->eventList.at(4).type, QEvent::UngrabMouse);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Accept pointer events
+ eventItem1->acceptMouse = false;
+ eventItem1->acceptPointer = true;
+ p1 = QPoint(20, 20);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_COMPARE(eventItem1->eventList.size(), 1);
+ QCOMPARE(eventItem1->eventList.at(0).type, QEvent::Pointer);
+ p1 += QPoint(10, 0);
+ QTest::mouseMove(window, p1);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE(eventItem1->eventList.at(1).type, QEvent::Pointer);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE(eventItem1->eventList.at(2).type, QEvent::Pointer);
+ eventItem1->eventList.clear();
+
+ delete window;
+}
+
+QTEST_MAIN(tst_PointerHandlers)
+
+#include "tst_qquickpointerhandler.moc"
+
diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
index dd00154935..d3f43fcd4d 100644
--- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
+++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
@@ -46,6 +46,8 @@
#include <QOpenGLFunctions>
#include <QSGRendererInterface>
+Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
+
struct TouchEventData {
QEvent::Type type;
QWidget *widget;
@@ -139,7 +141,7 @@ class TestTouchItem : public QQuickRectangle
public:
TestTouchItem(QQuickItem *parent = 0)
: QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true),
- mousePressId(0),
+ mousePressCount(0), mouseMoveCount(0),
spinLoopWhenPressed(false), touchEventCount(0)
{
border()->setWidth(1);
@@ -158,9 +160,10 @@ public:
lastMousePos = QPointF();
lastMouseCapabilityFlags = 0;
touchEventCount = 0;
+ mouseMoveCount = 0;
}
- static void clearMousePressCounter()
+ static void clearMouseEventCounters()
{
mousePressNum = mouseMoveNum = mouseReleaseNum = 0;
}
@@ -173,7 +176,8 @@ public:
bool acceptTouchEvents;
bool acceptMouseEvents;
TouchEventData lastEvent;
- int mousePressId;
+ int mousePressCount;
+ int mouseMoveCount;
bool spinLoopWhenPressed;
int touchEventCount;
QVector2D lastVelocity;
@@ -203,7 +207,7 @@ public:
e->ignore();
return;
}
- mousePressId = ++mousePressNum;
+ mousePressCount = ++mousePressNum;
lastMousePos = e->pos();
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
}
@@ -213,7 +217,7 @@ public:
e->ignore();
return;
}
- ++mouseMoveNum;
+ mouseMoveCount = ++mouseMoveNum;
lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e);
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
lastMousePos = e->pos();
@@ -229,10 +233,19 @@ public:
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
}
- bool childMouseEventFilter(QQuickItem *, QEvent *event) {
- // TODO Is it a bug if a QTouchEvent comes here?
- if (event->type() == QEvent::MouseButtonPress)
- mousePressId = ++mousePressNum;
+ bool childMouseEventFilter(QQuickItem *item, QEvent *e) {
+ qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName();
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ mousePressCount = ++mousePressNum;
+ break;
+ case QEvent::MouseMove:
+ mouseMoveCount = ++mouseMoveNum;
+ break;
+ default:
+ break;
+ }
+
return false;
}
@@ -502,7 +515,7 @@ void tst_qquickwindow::constantUpdatesOnWindow()
void tst_qquickwindow::touchEvent_basic()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -631,7 +644,7 @@ void tst_qquickwindow::touchEvent_basic()
void tst_qquickwindow::touchEvent_propagation()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QFETCH(bool, acceptTouchEvents);
QFETCH(bool, acceptMouseEvents);
@@ -778,7 +791,7 @@ void tst_qquickwindow::touchEvent_propagation_data()
void tst_qquickwindow::touchEvent_cancel()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -812,7 +825,7 @@ void tst_qquickwindow::touchEvent_cancel()
void tst_qquickwindow::touchEvent_reentrant()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -851,7 +864,7 @@ void tst_qquickwindow::touchEvent_reentrant()
void tst_qquickwindow::touchEvent_velocity()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -977,7 +990,7 @@ void tst_qquickwindow::mouseFromTouch_basic()
// should result in sending mouse events generated from the touch
// with the new event propagation system.
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
window->resize(250, 250);
@@ -1066,7 +1079,7 @@ void tst_qquickwindow::clearWindow()
void tst_qquickwindow::mouseFiltering()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -1080,6 +1093,11 @@ void tst_qquickwindow::mouseFiltering()
bottomItem->setObjectName("Bottom Item");
bottomItem->setSize(QSizeF(150, 150));
+ TestTouchItem *siblingItem = new TestTouchItem(bottomItem);
+ siblingItem->setObjectName("Sibling of Middle Item");
+ siblingItem->setPosition(QPointF(90, 25));
+ siblingItem->setSize(QSizeF(150, 150));
+
TestTouchItem *middleItem = new TestTouchItem(bottomItem);
middleItem->setObjectName("Middle Item");
middleItem->setPosition(QPointF(50, 50));
@@ -1099,9 +1117,41 @@ void tst_qquickwindow::mouseFiltering()
// 1. middleItem filters event
// 2. bottomItem filters event
// 3. topItem receives event
- QTRY_COMPARE(middleItem->mousePressId, 1);
- QTRY_COMPARE(bottomItem->mousePressId, 2);
- QTRY_COMPARE(topItem->mousePressId, 3);
+ QTRY_COMPARE(middleItem->mousePressCount, 1);
+ QTRY_COMPARE(bottomItem->mousePressCount, 2);
+ QTRY_COMPARE(topItem->mousePressCount, 3);
+ QCOMPARE(siblingItem->mousePressCount, 0);
+
+ QTest::mouseRelease(window, Qt::LeftButton, 0, pos);
+ topItem->clearMouseEventCounters();
+ middleItem->clearMouseEventCounters();
+ bottomItem->clearMouseEventCounters();
+ siblingItem->clearMouseEventCounters();
+
+ // Repeat, but this time have the top item accept the press
+ topItem->acceptMouseEvents = true;
+
+ QTest::mousePress(window, Qt::LeftButton, 0, pos);
+
+ // Mouse filtering propagates down the stack, so the
+ // correct order is
+ // 1. middleItem filters event
+ // 2. bottomItem filters event
+ // 3. topItem receives event
+ QTRY_COMPARE(middleItem->mousePressCount, 1);
+ QTRY_COMPARE(bottomItem->mousePressCount, 2);
+ QTRY_COMPARE(topItem->mousePressCount, 3);
+ QCOMPARE(siblingItem->mousePressCount, 0);
+
+ pos += QPoint(50, 50);
+ QTest::mouseMove(window, pos);
+
+ // The top item has grabbed, so the move goes there, but again
+ // all the ancestors can filter, even when the mouse is outside their bounds
+ QTRY_COMPARE(middleItem->mouseMoveCount, 1);
+ QTRY_COMPARE(bottomItem->mouseMoveCount, 2);
+ QTRY_COMPARE(topItem->mouseMoveCount, 3);
+ QCOMPARE(siblingItem->mouseMoveCount, 0);
// clean up mouse press state for the next tests
QTest::mouseRelease(window, Qt::LeftButton, 0, pos);
diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro
index 6e9998c061..93a938b5e7 100644
--- a/tests/auto/quick/quick.pro
+++ b/tests/auto/quick/quick.pro
@@ -47,6 +47,7 @@ PRIVATETESTS += \
!qtHaveModule(xmlpatterns): PRIVATETESTS -= qquickxmllistmodel
QUICKTESTS = \
+ pointerhandlers \
qquickaccessible \
qquickanchors \
qquickanimatedimage \
diff --git a/tests/manual/pointer/content/Slider.qml b/tests/manual/pointer/content/Slider.qml
new file mode 100644
index 0000000000..cd52dfac80
--- /dev/null
+++ b/tests/manual/pointer/content/Slider.qml
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ property int value: 50
+ property int maximumValue: 99
+ property alias label: label.text
+
+ Rectangle {
+ id: slot
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: 10
+ anchors.topMargin: 30
+ anchors.bottomMargin: 30
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 10
+ color: "black"
+ radius: width / 2
+ smooth: true
+ }
+
+ Rectangle {
+ // RectangularGlow is better, but that's a different module
+ id: glow
+ anchors.fill: knob
+ anchors.margins: -5
+ anchors.leftMargin: -2
+ anchors.horizontalCenterOffset: 1
+ radius: 5
+ color: "#4400FFFF"
+ opacity: dragHandler.active ? 1 : 0
+ }
+ Image {
+ id: knob
+ source: "../resources/mixer-knob.png"
+ antialiasing: true
+ x: slot.x - width / 2 + slot.width / 2
+ 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
+ transformOrigin: Item.Center
+ function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier }
+ DragHandler {
+ id: dragHandler
+ xAxis.enabled: false
+ yAxis.minimum: slot.y
+ yAxis.maximum: slot.height + slot.y - knob.height
+ }
+ }
+
+ Text {
+ font.pointSize: 16
+ color: "red"
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: root.value
+ }
+
+ Text {
+ id: label
+ font.pointSize: 12
+ color: "red"
+ anchors.top: parent.top
+ anchors.topMargin: 5
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ onHeightChanged: {
+ knob.programmatic = true
+ knob.setValue(root.value)
+ knob.programmatic = false
+ }
+}
diff --git a/tests/manual/pointer/joystick.qml b/tests/manual/pointer/joystick.qml
new file mode 100644
index 0000000000..bcc4564471
--- /dev/null
+++ b/tests/manual/pointer/joystick.qml
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ width: 480
+ height: 480
+ color: "black"
+
+ Image {
+ id: knob
+ source: "resources/redball.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ DragHandler {
+ id: dragHandler
+ }
+ states: [
+ State {
+ when: dragHandler.active
+ AnchorChanges {
+ target: knob
+ anchors.horizontalCenter: undefined
+ anchors.verticalCenter: undefined
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ AnchorAnimation { easing.type: Easing.OutElastic }
+ }
+ ]
+ }
+}
diff --git a/tests/manual/pointer/main.cpp b/tests/manual/pointer/main.cpp
new file mode 100644
index 0000000000..a4e1060cf5
--- /dev/null
+++ b/tests/manual/pointer/main.cpp
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the manual tests 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$
+**
+****************************************************************************/
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/tests/manual/pointer/main.qml b/tests/manual/pointer/main.qml
new file mode 100644
index 0000000000..62202c39b1
--- /dev/null
+++ b/tests/manual/pointer/main.qml
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import QtQuick.Window 2.2
+import "qrc:/quick/shared/" as Examples
+
+Window {
+ width: 800
+ height: 600
+ visible: true
+ Examples.LauncherList {
+ id: ll
+ anchors.fill: parent
+ Component.onCompleted: {
+ addExample("single point handler", "QQuickPointerSingleHandler: test properties copied from events", Qt.resolvedUrl("singlePointHandlerProperties.qml"))
+ addExample("joystick", "DragHandler: move one item inside another with any pointing device", Qt.resolvedUrl("joystick.qml"))
+ addExample("mixer", "mixing console", Qt.resolvedUrl("mixer.qml"))
+ addExample("pinch", "PinchHandler: scale, rotate and drag", Qt.resolvedUrl("pinchHandler.qml"))
+ addExample("map", "scale and pan", Qt.resolvedUrl("map.qml"))
+ addExample("custom map", "scale and pan", Qt.resolvedUrl("map2.qml"))
+ }
+ }
+}
diff --git a/tests/manual/pointer/map.qml b/tests/manual/pointer/map.qml
new file mode 100644
index 0000000000..e1ca889064
--- /dev/null
+++ b/tests/manual/pointer/map.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+
+Item {
+ width: 640
+ height: 480
+
+ Rectangle {
+ id: map
+ color: "aqua"
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ width: image.implicitWidth
+ height: image.implicitHeight
+
+ Image {
+ id: image
+ anchors.centerIn: parent
+ fillMode: Image.PreserveAspectFit
+ source: "resources/map.svgz"
+ }
+ }
+
+ PinchHandler {
+ id: pinch
+ target: map
+ minimumScale: 0.1
+ maximumScale: 10
+ }
+
+ DragHandler {
+ target: map
+ }
+}
diff --git a/tests/manual/pointer/map2.qml b/tests/manual/pointer/map2.qml
new file mode 100644
index 0000000000..fcd144bd7f
--- /dev/null
+++ b/tests/manual/pointer/map2.qml
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+
+Item {
+ width: 640
+ height: 480
+
+ Rectangle {
+ id: map
+ color: "aqua"
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ width: image.implicitWidth
+ height: image.implicitHeight
+ property point center : Qt.point(x + map.width/2, y + map.height/2)
+
+ function setCenter(xx, yy) {
+ map.x = xx - map.width/2
+ map.y = yy - map.height/2
+ }
+
+
+ Image {
+ id: image
+ anchors.centerIn: parent
+ fillMode: Image.PreserveAspectFit
+ source: "resources/map.svgz"
+ }
+ }
+
+ PinchHandler {
+ id: pinch
+ target: map
+ minimumScale: 0.1
+ maximumScale: 10
+ }
+
+ DragHandler {
+ property point startDrag
+ target: null
+ onActiveChanged: {
+ if (active)
+ startDrag = map.center
+ }
+
+ onTranslationChanged: {
+ if (!target)
+ map.setCenter(startDrag.x + translation.x, startDrag.y + translation.y)
+ }
+ }
+}
diff --git a/tests/manual/pointer/mixer.qml b/tests/manual/pointer/mixer.qml
new file mode 100644
index 0000000000..84ad975340
--- /dev/null
+++ b/tests/manual/pointer/mixer.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+import "content"
+
+Rectangle {
+ id: root
+ width: 1280
+ height: 960
+ objectName: "root"
+ color: "#222222"
+
+ ListView {
+ id: list
+ objectName: "listView"
+ anchors.fill: parent
+ anchors.margins: 10
+ orientation: Qt.Horizontal
+
+ model: 20
+
+ delegate: Item {
+ objectName: "delegateItem" + index
+ width: 154
+ height: list.height
+
+ Slider {
+ anchors.fill: parent
+ label: "Channel " + (index + 1)
+ }
+ }
+ }
+}
diff --git a/tests/manual/pointer/pinchHandler.qml b/tests/manual/pointer/pinchHandler.qml
new file mode 100644
index 0000000000..f317f361e2
--- /dev/null
+++ b/tests/manual/pointer/pinchHandler.qml
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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: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.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ width: 1024; height: 600
+ color: "#eee"
+
+ 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) + ")"
+ + "\nrect.scale: " + item.scale.toFixed(2)
+ + "\nrect.rotation: " + item.rotation.toFixed(2)
+ + "\nrect.position: " + "(" + item.x.toFixed(2) + "," + item.y.toFixed(2) + ")"
+ }
+
+ Rectangle {
+ // Purpose of this item is just to make sure the rectangles are transformed into
+ // a coordinate system that is different from the scene coordinate system.
+ anchors.fill: parent
+ anchors.margins: 50
+ color: "#ffe0e0e0"
+
+ Rectangle {
+ id: rect2
+ width: 400
+ height: 300
+ color: "lightsteelblue"
+ antialiasing: true
+ x: 100
+ y: 200
+ rotation: 30
+ transformOrigin: Item.TopRight
+
+ Text {
+ anchors.centerIn: parent
+ text: "Pinch with 2 fingers to scale, rotate and translate"
+ + getTransformationDetails(rect2, pinch2)
+ }
+
+ PinchHandler {
+ id: pinch2
+ objectName: "2-finger pinch"
+ minimumRotation: -45
+ maximumRotation: 45
+ minimumScale: 0.5
+ maximumScale: 3
+ minimumX: 0
+ maximumX: 600
+ pointDistanceThreshold: 0
+ }
+ }
+
+ Rectangle {
+ id: rect3
+ x: 500
+ width: 400
+ height: 300
+ color: "wheat"
+ antialiasing: true
+
+ Text {
+ anchors.centerIn: parent
+ text: "Pinch with 3 fingers to scale, rotate and translate\nDrag with 1 finger"
+ + getTransformationDetails(rect3, pinch3)
+ }
+ DragHandler { objectName: "DragHandler" }
+
+ PinchHandler {
+ id: pinch3
+ objectName: "3-finger pinch"
+ requiredPointCount: 3
+ minimumScale: 0.1
+ maximumScale: 10
+ }
+ }
+ }
+ Rectangle {
+ id: centroidIndicator
+ property QtObject pincher: pinch2.active ? pinch2 : pinch3
+ x: pincher.centroid.x - radius
+ y: pincher.centroid.y - radius
+ z: 1
+ visible: pincher.active
+ radius: width / 2
+ width: 10
+ height: width
+ color: "red"
+ }
+}
diff --git a/tests/manual/pointer/pointer.pro b/tests/manual/pointer/pointer.pro
new file mode 100644
index 0000000000..3705d41df0
--- /dev/null
+++ b/tests/manual/pointer/pointer.pro
@@ -0,0 +1,7 @@
+TEMPLATE = app
+
+QT += qml quick
+
+SOURCES += main.cpp
+
+RESOURCES += qml.qrc ../../../examples/quick/shared/quick_shared.qrc
diff --git a/tests/manual/pointer/qml.qrc b/tests/manual/pointer/qml.qrc
new file mode 100644
index 0000000000..adf255ea1f
--- /dev/null
+++ b/tests/manual/pointer/qml.qrc
@@ -0,0 +1,21 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ <file>joystick.qml</file>
+ <file>map.qml</file>
+ <file>mixer.qml</file>
+ <file>pinchHandler.qml</file>
+ <file>singlePointHandlerProperties.qml</file>
+ <file>content/Slider.qml</file>
+ <file>resources/arrowhead.png</file>
+ <file>resources/grabbing-location.svg</file>
+ <file>resources/map.svgz</file>
+ <file>resources/mixer-knob.png</file>
+ <file>resources/mouse.png</file>
+ <file>resources/mouse_left.png</file>
+ <file>resources/mouse_middle.png</file>
+ <file>resources/mouse_right.png</file>
+ <file>resources/redball.png</file>
+ <file>map2.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/pointer/resources/arrowhead.png b/tests/manual/pointer/resources/arrowhead.png
new file mode 100644
index 0000000000..7719bc6d6a
--- /dev/null
+++ b/tests/manual/pointer/resources/arrowhead.png
Binary files differ
diff --git a/tests/manual/pointer/resources/grabbing-location.svg b/tests/manual/pointer/resources/grabbing-location.svg
new file mode 100644
index 0000000000..c26881e9ba
--- /dev/null
+++ b/tests/manual/pointer/resources/grabbing-location.svg
@@ -0,0 +1 @@
+<svg width="3408" height="3124"><path d="M1517 1562c0-126-93-229-208-229s-208 102-208 229c0 126 93 229 208 229s208-102 208-229zm123-172c-58-206-221-365-424-412l-270 531H380l203-223 219-241 346-380c42 14 82 32 121 54 232 128 402 375 449 671h-77zm-551-933c448 123 782 546 802 1055h1517C3386 673 2787 0 2050 0H696L0 1367h120l826-938 146 25c-1 1-2 2-3 4zm551 1277c-58 206-221 365-424 412l-270-531H380l203 223 219 241 346 380c42-14 82-32 121-54 232-128 402-375 449-671h-77zm-548 936l-146 25-826-938H0l696 1367h1354c737 0 1337-673 1358-1512H1891c-19 509-354 933-802 1055 1 1 2 2 3 4z" fill="#ee832b"/></svg>
diff --git a/tests/manual/pointer/resources/map.svgz b/tests/manual/pointer/resources/map.svgz
new file mode 100644
index 0000000000..64d509c106
--- /dev/null
+++ b/tests/manual/pointer/resources/map.svgz
Binary files differ
diff --git a/tests/manual/pointer/resources/mixer-knob.png b/tests/manual/pointer/resources/mixer-knob.png
new file mode 100644
index 0000000000..02cc9fc72b
--- /dev/null
+++ b/tests/manual/pointer/resources/mixer-knob.png
Binary files differ
diff --git a/tests/manual/pointer/resources/mouse.png b/tests/manual/pointer/resources/mouse.png
new file mode 100644
index 0000000000..268946df0a
--- /dev/null
+++ b/tests/manual/pointer/resources/mouse.png
Binary files differ
diff --git a/tests/manual/pointer/resources/mouse_left.png b/tests/manual/pointer/resources/mouse_left.png
new file mode 100644
index 0000000000..9292301b47
--- /dev/null
+++ b/tests/manual/pointer/resources/mouse_left.png
Binary files differ
diff --git a/tests/manual/pointer/resources/mouse_middle.png b/tests/manual/pointer/resources/mouse_middle.png
new file mode 100644
index 0000000000..064e8b9c16
--- /dev/null
+++ b/tests/manual/pointer/resources/mouse_middle.png
Binary files differ
diff --git a/tests/manual/pointer/resources/mouse_right.png b/tests/manual/pointer/resources/mouse_right.png
new file mode 100644
index 0000000000..cab1a36ba6
--- /dev/null
+++ b/tests/manual/pointer/resources/mouse_right.png
Binary files differ
diff --git a/tests/manual/pointer/resources/redball.png b/tests/manual/pointer/resources/redball.png
new file mode 100644
index 0000000000..68d2e1d638
--- /dev/null
+++ b/tests/manual/pointer/resources/redball.png
Binary files differ
diff --git a/tests/manual/pointer/singlePointHandlerProperties.qml b/tests/manual/pointer/singlePointHandlerProperties.qml
new file mode 100644
index 0000000000..f5a938e401
--- /dev/null
+++ b/tests/manual/pointer/singlePointHandlerProperties.qml
@@ -0,0 +1,162 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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: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.9
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ width: 480
+ height: 480
+ color: "black"
+
+ Item {
+ id: crosshairs
+ x: dragHandler.pos.x - width / 2
+ y: dragHandler.pos.y - height / 2
+ width: parent.width / 2; height: parent.height / 2
+ visible: dragHandler.active
+ rotation: dragHandler.rotation
+
+ Rectangle {
+ color: "goldenrod"
+ anchors.centerIn: parent
+ width: 2; height: parent.height
+ antialiasing: true
+ }
+ Rectangle {
+ color: "goldenrod"
+ anchors.centerIn: parent
+ width: parent.width; height: 2
+ antialiasing: true
+ }
+ Rectangle {
+ color: "goldenrod"
+ width: Math.max(2, 50 * dragHandler.pressure)
+ height: width
+ radius: width / 2
+ anchors.centerIn: parent
+ antialiasing: true
+ Rectangle {
+ y: -40
+ anchors.horizontalCenter: parent.horizontalCenter
+ color: "lightsteelblue"
+ implicitWidth: label.implicitWidth
+ implicitHeight: label.implicitHeight
+ Text {
+ id: label
+ text: 'id: ' + dragHandler.pointId.toString(16) + " uid: " + dragHandler.uniquePointId.numericId +
+ '\npos: (' + dragHandler.pos.x.toFixed(2) + ', ' + dragHandler.pos.y.toFixed(2) + ')'
+ }
+ }
+ }
+ Rectangle {
+ color: "transparent"
+ border.color: "white"
+ antialiasing: true
+ width: dragHandler.ellipseDiameters.width
+ height: dragHandler.ellipseDiameters.height
+ radius: Math.min(width / 2, height / 2)
+ anchors.centerIn: parent
+ }
+ }
+ Rectangle {
+ id: velocityVector
+ visible: width > 0
+ width: dragHandler.velocity.length() * 100
+ height: 2
+ x: dragHandler.pos.x
+ y: dragHandler.pos.y
+ rotation: Math.atan2(dragHandler.velocity.y, dragHandler.velocity.x) * 180 / Math.PI
+ transformOrigin: Item.BottomLeft
+ antialiasing: true
+
+ Image {
+ source: "resources/arrowhead.png"
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ width: 16
+ height: 12
+ antialiasing: true
+ }
+ }
+
+ Component {
+ id: grabbingLocationIndicator
+ Image {
+ source: "resources/grabbing-location.svg"
+ sourceSize.width: 32
+ sourceSize.height: 32
+ }
+ }
+
+ Component {
+ id: mouseButtonIndicator
+ Image {
+ property int buttons
+ source: "resources/mouse.png"
+ Image {
+ source: "resources/mouse_left.png"
+ visible: buttons & Qt.LeftButton
+ }
+ Image {
+ source: "resources/mouse_middle.png"
+ visible: buttons & Qt.MidButton
+ }
+ Image {
+ source: "resources/mouse_right.png"
+ visible: buttons & Qt.RightButton
+ }
+ }
+ }
+
+ DragHandler {
+ id: dragHandler
+ target: null
+ onGrabChanged: if (active) {
+ console.log("grabbed " + point.pointId + " @ " + sceneGrabPos)
+ grabbingLocationIndicator.createObject(root, {"x": sceneGrabPos.x, "y": sceneGrabPos.y - 16})
+ }
+ onPressedButtonsChanged: {
+ if (pressedButtons)
+ mouseButtonIndicator.createObject(root, {"x": pressPos.x - 44, "y": pressPos.y - 64, "buttons": pressedButtons})
+ }
+ }
+}