aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick/pointerhandlers
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick/pointerhandlers')
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml45
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml121
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml94
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml151
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro15
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp552
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml108
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro15
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp287
-rw-r--r--tests/auto/quick/pointerhandlers/pointerhandlers.pro11
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml126
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml45
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml125
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml60
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml67
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml60
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml112
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro21
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp487
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml28
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro16
-rw-r--r--tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp580
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml87
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml45
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml46
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml53
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro16
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp627
28 files changed, 4000 insertions, 0 deletions
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml
new file mode 100644
index 0000000000..4b2935b52e
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** 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:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+SequentialAnimation {
+ id: tapFlash
+ running: false
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+}
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml
new file mode 100644
index 0000000000..f6acd53615
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ property int value: 50
+ property int maximumValue: 99
+ property alias label: label.text
+ property alias tapEnabled: tap.enabled
+ property alias pressed: tap.pressed
+ signal tapped
+
+ 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 {
+ id: glow
+ anchors.fill: knob
+ anchors.margins: -5
+ anchors.leftMargin: -2
+ anchors.horizontalCenterOffset: 1
+ radius: 5
+ color: "#4400FFFF"
+ opacity: tap.pressed || tapFlash.running ? 1 : 0
+ FlashAnimation on visible {
+ id: tapFlash
+ }
+ }
+ Rectangle {
+ id: knob
+ objectName: "Slider Knob"
+ width: parent.width - 2
+ height: 20
+ radius: 5
+ color: "darkgray"
+ border.color: "black"
+ 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
+ objectName: label.text + " DragHandler"
+ xAxis.enabled: false
+ yAxis.minimum: slot.y
+ yAxis.maximum: slot.height + slot.y - knob.height
+ }
+ TapHandler {
+ id: tap
+ objectName: label.text + " TapHandler"
+ gesturePolicy: TapHandler.DragThreshold
+ onTapped: {
+ tapFlash.start()
+ root.tapped
+ }
+ }
+ }
+
+ 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
+ }
+
+ Component.onCompleted: {
+ knob.programmatic = true
+ knob.setValue(root.value)
+ knob.programmatic = false
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml
new file mode 100644
index 0000000000..55f77460f1
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ property alias label: label.text
+ property alias pressed: tap.pressed
+ property bool checked: false
+ property alias gesturePolicy: tap.gesturePolicy
+ property alias enabled: tap.enabled
+ signal tapped
+
+ width: label.implicitWidth * 1.5; height: label.implicitHeight * 2.0
+ border.color: "#9f9d9a"; border.width: 1; radius: height / 4; antialiasing: true
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: tap.pressed ? "#b8b5b2" : "#efebe7" }
+ GradientStop { position: 1.0; color: "#b8b5b2" }
+ }
+
+ TapHandler {
+ id: tap
+ objectName: label.text
+ longPressThreshold: 100 // CI can be insanely slow, so don't demand a timely release to generate onTapped
+ onTapped: {
+ tapFlash.start()
+ root.tapped()
+ }
+ }
+
+ Text {
+ id: label
+ font.pointSize: 14
+ text: "Button"
+ anchors.centerIn: parent
+ }
+
+ Rectangle {
+ anchors.fill: parent; anchors.margins: -5
+ color: "transparent"; border.color: "#4400FFFF"
+ border.width: 5; radius: root.radius; antialiasing: true
+ opacity: tapFlash.running ? 1 : 0
+ FlashAnimation on visible { id: tapFlash }
+ }
+
+ Rectangle {
+ objectName: "expandingCircle"
+ radius: tap.timeHeld * 100
+ visible: radius > 0 && tap.pressed
+ border.width: 3
+ border.color: "cyan"
+ color: "transparent"
+ width: radius * 2
+ height: radius * 2
+ x: tap.point.scenePressPosition.x - radius
+ y: tap.point.scenePressPosition.y - radius
+ opacity: 0.25
+ Component.onCompleted: {
+ // get on top of all the buttons
+ var par = root.parent;
+ while (par.parent)
+ par = par.parent;
+ parent = par;
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml
new file mode 100644
index 0000000000..95ecf702be
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml
@@ -0,0 +1,151 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 480
+ objectName: "root"
+ color: "#222222"
+
+ Flickable {
+ anchors.fill: parent
+ anchors.margins: 10
+ anchors.topMargin: 40
+ contentHeight: 600
+ contentWidth: 600
+// pressDelay: TODO
+
+ Row {
+ spacing: 6
+ Slider {
+ label: "DragHandler"
+ objectName: "Slider"
+ value: 49; width: 100; height: 400
+ }
+ Column {
+ spacing: 6
+ TapHandlerButton {
+ objectName: "DragThreshold"
+ label: "DragThreshold"
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ TapHandlerButton {
+ objectName: "WithinBounds"
+ label: "WithinBounds"
+ gesturePolicy: TapHandler.WithinBounds
+ }
+ TapHandlerButton {
+ objectName: "ReleaseWithinBounds"
+ label: "ReleaseWithinBounds"
+ gesturePolicy: TapHandler.ReleaseWithinBounds // the default
+ }
+ }
+ Column {
+ spacing: 6
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ border.color: drag1.active ? "darkgreen" : "transparent"
+ border.width: 3
+ objectName: "drag"
+ DragHandler {
+ id: drag1
+ }
+ Text {
+ anchors.centerIn: parent
+ enabled: false
+ text: "drag"
+ }
+ }
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ objectName: "tap"
+ border.color: tap1.isPressed ? "red" : "transparent"
+ border.width: 3
+ TapHandler {
+ id: tap1
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ Text {
+ anchors.centerIn: parent
+ enabled: false
+ text: "tap"
+ }
+ }
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ border.color: tap2.isPressed ? "red" : drag2.active ? "darkgreen" : "transparent"
+ border.width: 3
+ objectName: "dragAndTap"
+ DragHandler {
+ id: drag2
+ }
+ TapHandler {
+ id: tap2
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ Text {
+ anchors.centerIn: parent
+ enabled: false
+ text: "drag\nand\ntap"
+ }
+ }
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ border.color: tap3.isPressed ? "red" : drag3.active ? "darkgreen" : "transparent"
+ border.width: 3
+ objectName: "tapAndDrag"
+ TapHandler {
+ id: tap3
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ DragHandler {
+ id: drag3
+ }
+ Text {
+ anchors.centerIn: parent
+ enabled: false
+ text: "tap\nand\ndrag"
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro b/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro
new file mode 100644
index 0000000000..9075044bd3
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro
@@ -0,0 +1,15 @@
+CONFIG += testcase
+
+TARGET = tst_flickableinterop
+QT += core-private gui-private qml-private quick-private testlib
+
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_flickableinterop.cpp
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+OTHER_FILES += data/flickableWithHandlers.qml data/Slider.qml
diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp
new file mode 100644
index 0000000000..7862d72db8
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp
@@ -0,0 +1,552 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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/qquickflickable_p.h>
+#include <QtQuick/private/qquickpointerhandler_p.h>
+#include <QtQuick/private/qquickdraghandler_p.h>
+#include <QtQuick/private/qquicktaphandler_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")
+
+class tst_FlickableInterop : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_FlickableInterop()
+ :touchDevice(QTest::createTouchDevice())
+ {}
+
+private slots:
+ void initTestCase();
+
+ void touchTapButton_data();
+ void touchTapButton();
+ void touchDragFlickableBehindButton_data();
+ void touchDragFlickableBehindButton();
+ void mouseClickButton_data();
+ void mouseClickButton();
+ void mouseDragFlickableBehindButton_data();
+ void mouseDragFlickableBehindButton();
+ void touchDragSlider();
+ void touchDragFlickableBehindSlider();
+ void mouseDragSlider();
+ void mouseDragFlickableBehindSlider();
+ void touchDragFlickableBehindItemWithHandlers_data();
+ void touchDragFlickableBehindItemWithHandlers();
+ void mouseDragFlickableBehindItemWithHandlers_data();
+ void mouseDragFlickableBehindItemWithHandlers();
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ QTouchDevice *touchDevice;
+};
+
+void tst_FlickableInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName)
+{
+ window.reset(new QQuickView);
+ window->setSource(testFileUrl(fileName));
+ QTRY_COMPARE(window->status(), QQuickView::Ready);
+ QQuickViewTestUtil::centerOnScreen(window.data());
+ QQuickViewTestUtil::moveMouseAway(window.data());
+
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window.data()));
+ QVERIFY(window->rootObject() != 0);
+}
+
+void tst_FlickableInterop::initTestCase()
+{
+ // This test assumes that we don't get synthesized mouse events from QGuiApplication
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
+
+ QQmlDataTest::initTestCase();
+}
+
+void tst_FlickableInterop::touchTapButton_data()
+{
+ QTest::addColumn<QString>("buttonName");
+ QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold");
+ QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds");
+ QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
+}
+
+void tst_FlickableInterop::touchTapButton()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QFETCH(QString, buttonName);
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName);
+ QVERIFY(button);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ // Button changes pressed state and emits tapped on release
+ QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 1);
+
+ // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab
+ p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 2);
+}
+
+void tst_FlickableInterop::touchDragFlickableBehindButton_data()
+{
+ QTest::addColumn<QString>("buttonName");
+ QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold");
+ QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds");
+ QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
+}
+
+void tst_FlickableInterop::touchDragFlickableBehindButton()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QFETCH(QString, buttonName);
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName);
+ QVERIFY(button);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ tappedSpy.clear();
+ QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(button->property("pressed").toBool());
+ int i = 0;
+ // Start dragging; eventually when the touchpoint goes beyond dragThreshold,
+ // Button is no longer pressed because Flickable steals the grab
+ for (; i < 100 && !flickable->isMoving(); ++i) {
+ p1 += QPoint(1, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ }
+ QVERIFY(flickable->isMoving());
+ qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1;
+ QCOMPARE(i, 2);
+ QVERIFY(!button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 0);
+}
+
+void tst_FlickableInterop::mouseClickButton_data()
+{
+ QTest::addColumn<QString>("buttonName");
+ QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold");
+ QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds");
+ QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
+}
+
+void tst_FlickableInterop::mouseClickButton()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QFETCH(QString, buttonName);
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName);
+ QVERIFY(button);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ // Button changes pressed state and emits tapped on release
+ QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 1);
+
+ // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab
+ p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 2);
+}
+
+void tst_FlickableInterop::mouseDragFlickableBehindButton_data()
+{
+ QTest::addColumn<QString>("buttonName");
+ QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold");
+ QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds");
+ QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
+}
+
+void tst_FlickableInterop::mouseDragFlickableBehindButton()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QFETCH(QString, buttonName);
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName);
+ QVERIFY(button);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ // Button is no longer pressed if touchpoint goes beyond dragThreshold,
+ // because Flickable steals the grab
+ tappedSpy.clear();
+ QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QVERIFY(button->property("pressed").toBool());
+ int i = 0;
+ for (; i < 100 && !flickable->isMoving(); ++i) {
+ p1 += QPoint(1, 0);
+ QTest::mouseMove(window, p1);
+ }
+ qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1;
+ QVERIFY(flickable->isMoving());
+ QCOMPARE(i, 2);
+ QVERIFY(!button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QVERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 0);
+}
+
+void tst_FlickableInterop::touchDragSlider()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider");
+ QVERIFY(slider);
+ QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob");
+ QVERIFY(knob);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
+ QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
+
+ // Drag the slider in the allowed (vertical) direction
+ tappedSpy.clear();
+ QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(slider->property("pressed").toBool());
+ p1 += QPoint(0, dragThreshold);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(slider->property("pressed").toBool());
+ QCOMPARE(slider->property("value").toInt(), 49);
+ p1 += QPoint(0, 1);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ p1 += QPoint(0, 10);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(slider->property("value").toInt() < 49);
+ QVERIFY(!flickable->isMoving());
+ QVERIFY(!slider->property("pressed").toBool());
+
+ // Now that the DragHandler is active, the Flickable will not steal the grab
+ // even if we move a large distance horizontally
+ p1 += QPoint(dragThreshold * 2, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!flickable->isMoving());
+
+ // Release, and do not expect the tapped signal
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(tappedSpy.count(), 0);
+ QCOMPARE(translationChangedSpy.count(), 1);
+}
+
+void tst_FlickableInterop::mouseDragSlider()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider");
+ QVERIFY(slider);
+ QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob");
+ QVERIFY(knob);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
+ QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
+
+ // Drag the slider in the allowed (vertical) direction
+ tappedSpy.clear();
+ QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(slider->property("pressed").toBool());
+ p1 += QPoint(0, dragThreshold);
+ QTest::mouseMove(window, p1);
+ QVERIFY(slider->property("pressed").toBool());
+ QCOMPARE(slider->property("value").toInt(), 49);
+ p1 += QPoint(0, 1);
+ QTest::mouseMove(window, p1);
+ p1 += QPoint(0, 10);
+ QTest::mouseMove(window, p1);
+ QVERIFY(slider->property("value").toInt() < 49);
+ QVERIFY(!flickable->isMoving());
+ QVERIFY(!slider->property("pressed").toBool());
+
+ // Now that the DragHandler is active, the Flickable will not steal the grab
+ // even if we move a large distance horizontally
+ p1 += QPoint(dragThreshold * 2, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(!flickable->isMoving());
+
+ // Release, and do not expect the tapped signal
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(tappedSpy.count(), 0);
+ QCOMPARE(translationChangedSpy.count(), 1);
+}
+
+void tst_FlickableInterop::touchDragFlickableBehindSlider()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider");
+ QVERIFY(slider);
+ QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob");
+ QVERIFY(knob);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
+ QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
+
+ // Button is no longer pressed if touchpoint goes beyond dragThreshold,
+ // because Flickable steals the grab
+ tappedSpy.clear();
+ QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(slider->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(slider->property("pressed").toBool());
+ int i = 0;
+ for (; i < 100 && !flickable->isMoving(); ++i) {
+ p1 += QPoint(1, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ }
+ qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1;
+ QVERIFY(flickable->isMoving());
+ QCOMPARE(i, 2);
+ QVERIFY(!slider->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!slider->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 0);
+ QCOMPARE(translationChangedSpy.count(), 0);
+}
+
+void tst_FlickableInterop::mouseDragFlickableBehindSlider()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider");
+ QVERIFY(slider);
+ QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob");
+ QVERIFY(knob);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
+ QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
+
+ // Button is no longer pressed if touchpoint goes beyond dragThreshold,
+ // because Flickable steals the grab
+ tappedSpy.clear();
+ QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(slider->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(slider->property("pressed").toBool());
+ int i = 0;
+ for (; i < 100 && !flickable->isMoving(); ++i) {
+ p1 += QPoint(1, 0);
+ QTest::mouseMove(window, p1);
+ }
+ qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1;
+ QVERIFY(flickable->isMoving());
+ QCOMPARE(i, 2);
+ QVERIFY(!slider->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(tappedSpy.count(), 0);
+ QCOMPARE(translationChangedSpy.count(), 0);
+}
+
+void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers_data()
+{
+ QTest::addColumn<QByteArray>("nameOfRectangleToDrag");
+ QTest::addColumn<bool>("expectedFlickableMoving");
+ QTest::newRow("drag") << QByteArray("drag") << false;
+ QTest::newRow("tap") << QByteArray("tap") << true;
+ QTest::newRow("dragAndTap") << QByteArray("dragAndTap") << false;
+ QTest::newRow("tapAndDrag") << QByteArray("tapAndDrag") << false;
+}
+
+void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers()
+{
+ QFETCH(bool, expectedFlickableMoving);
+ QFETCH(QByteArray, nameOfRectangleToDrag);
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+ QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(nameOfRectangleToDrag);
+ QVERIFY(rect);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint();
+ QPoint originP1 = p1;
+
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ for (int i = 0; i < dragThreshold * 3; ++i) {
+ p1 = originP1;
+ p1.rx() += i;
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ }
+ QCOMPARE(flickable->isMoving(), expectedFlickableMoving);
+ if (!expectedFlickableMoving) {
+ QVERIFY(rect->mapToScene(rect->clipRect().center()).toPoint().x() > originP1.x());
+ }
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+}
+
+void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers_data()
+{
+ touchDragFlickableBehindItemWithHandlers_data();
+}
+
+void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers()
+{
+ QFETCH(bool, expectedFlickableMoving);
+ QFETCH(QByteArray, nameOfRectangleToDrag);
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "flickableWithHandlers.qml");
+ QQuickView * window = windowPtr.data();
+ QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(nameOfRectangleToDrag);
+ QVERIFY(rect);
+ QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
+ QVERIFY(flickable);
+ QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint();
+ QPoint originP1 = p1;
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ for (int i = 0; i < 3; ++i) {
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QQuickTouchUtils::flush(window);
+ }
+ QCOMPARE(flickable->isMoving(), expectedFlickableMoving);
+ if (!expectedFlickableMoving) {
+ QCOMPARE(originP1 + QPoint(3*dragThreshold, 0), p1);
+ }
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+}
+
+QTEST_MAIN(tst_FlickableInterop)
+
+#include "tst_flickableinterop.moc"
+
diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml
new file mode 100644
index 0000000000..084cc25414
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ width: 1024; height: 600
+ color: "beige"
+ objectName: "beige root"
+
+ Rectangle {
+ id: container
+ objectName: "container rect"
+ width: 600
+ height: 500
+ color: "black"
+ border.color: pinch3.active ? "red" : "black"
+ border.width: 3
+ antialiasing: true
+
+ MultiPointTouchArea {
+ id: mpta
+ anchors.fill: parent
+ //onGestureStarted: gesture.grab() // in case this is embedded in something that might steal
+ touchPoints: [
+ TouchPoint { property color color: "red" },
+ TouchPoint { property color color: "orange" },
+ TouchPoint { property color color: "lightsteelblue" },
+ TouchPoint { property color color: "green" }
+ ] }
+
+ Repeater {
+ model: 4
+
+ Item {
+ id: crosshairs
+ property TouchPoint touchPoint
+ x: touchPoint.x - width / 2
+ y: touchPoint.y - height / 2
+ width: 300; height: 300
+ visible: touchPoint.pressed
+ rotation: touchPoint.rotation
+
+ Rectangle {
+ color: touchPoint.color
+ anchors.centerIn: parent
+ width: 2; height: parent.height
+ antialiasing: true
+ }
+ Rectangle {
+ color: touchPoint.color
+ anchors.centerIn: parent
+ width: parent.width; height: 2
+ antialiasing: true
+ }
+ Component.onCompleted: touchPoint = mpta.touchPoints[index]
+ }
+ }
+
+ Item {
+ objectName: "pinch and drag"
+ anchors.fill: parent
+ // In order for PinchHandler to get a chance to take a passive grab, it has to get the touchpoints first.
+ // In order to get the touchpoints first, it has to be on top of the Z order: i.e. come last in paintOrderChildItems().
+ // This is the opposite situation as with filtersChildMouseEvents: e.g. PinchArea would have wanted to be the parent,
+ // if it even knew that trick (which it doesn't).
+ DragHandler {
+ id: dragHandler
+ objectName: "DragHandler"
+ target: container
+ }
+ PinchHandler {
+ id: pinch3
+ objectName: "3-finger pinch"
+ target: container
+ minimumPointCount: 3
+ minimumScale: 0.1
+ maximumScale: 10
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro
new file mode 100644
index 0000000000..10d0ff8018
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro
@@ -0,0 +1,15 @@
+CONFIG += testcase
+
+TARGET = tst_multipointtoucharea_interop
+QT += core-private gui-private qml-private quick-private testlib
+
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_multipointtoucharea_interop.cpp
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+OTHER_FILES += data/pinchDragMPTA.qml
diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp
new file mode 100644
index 0000000000..a2934eee32
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp
@@ -0,0 +1,287 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 <QtQml/qqmlengine.h>
+#include <QtQml/qqmlproperty.h>
+#include <QtQuick/private/qquickdraghandler_p.h>
+#include <QtQuick/private/qquickmultipointtoucharea_p.h>
+#include <QtQuick/private/qquickpinchhandler_p.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+
+#include "../../../shared/util.h"
+#include "../../shared/viewtestutil.h"
+
+Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
+
+class tst_MptaInterop : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_MptaInterop()
+ : touchDevice(QTest::createTouchDevice())
+ , touchPointerDevice(QQuickPointerDevice::touchDevice(touchDevice))
+ {}
+
+private slots:
+ void initTestCase();
+
+ void touchDrag();
+ void touchesThenPinch();
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ QTouchDevice *touchDevice;
+ QQuickPointerDevice *touchPointerDevice;
+};
+
+void tst_MptaInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName)
+{
+ window.reset(new QQuickView);
+ window->setSource(testFileUrl(fileName));
+ QTRY_COMPARE(window->status(), QQuickView::Ready);
+ QQuickViewTestUtil::centerOnScreen(window.data());
+ QQuickViewTestUtil::moveMouseAway(window.data());
+
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window.data()));
+ QVERIFY(window->rootObject() != 0);
+}
+
+void tst_MptaInterop::initTestCase()
+{
+ // This test assumes that we don't get synthesized mouse events from QGuiApplication
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
+
+ QQmlDataTest::initTestCase();
+}
+
+void tst_MptaInterop::touchDrag()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "pinchDragMPTA.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>();
+ QVERIFY(mpta);
+ QQuickPinchHandler *pinch = window->rootObject()->findChild<QQuickPinchHandler*>();
+ QVERIFY(pinch);
+ QQuickDragHandler *drag = window->rootObject()->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQmlListReference tp(mpta, "touchPoints");
+ QVERIFY(tp.at(3)); // the QML declares four touchpoints
+ QSignalSpy mptaPressedSpy(mpta, SIGNAL(pressed(QList<QObject*>)));
+ QSignalSpy mptaReleasedSpy(mpta, SIGNAL(released(QList<QObject*>)));
+ QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
+
+ // Press one touchpoint:
+ // DragHandler gets a passive grab
+ // PinchHandler declines, because it wants 3 touchpoints
+ // MPTA doesn't get a chance, because DragHandler accepted the single EventPoint
+ QPoint p1 = mpta->mapToScene(QPointF(20, 20)).toPoint();
+ touch.press(1, p1).commit();
+ QQuickTouchUtils::flush(window);
+ auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice);
+ QCOMPARE(tp.at(0)->property("pressed").toBool(), false);
+ QTRY_VERIFY(pointerEvent->point(0)->passiveGrabbers().contains(drag));
+
+ // Start moving
+ // DragHandler keeps monitoring, due to its passive grab,
+ // and eventually steals the exclusive grab from MPTA
+ int dragStoleGrab = 0;
+ for (int i = 0; i < 4; ++i) {
+ p1 += QPoint(dragThreshold / 2, 0);
+ touch.move(1, p1).commit();
+ QQuickTouchUtils::flush(window);
+ if (!dragStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == drag)
+ dragStoleGrab = i;
+ }
+ if (dragStoleGrab)
+ qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab);
+ QVERIFY(dragStoleGrab > 1);
+
+ touch.release(1, p1).commit();
+ QQuickTouchUtils::flush(window);
+}
+
+// TODO touchesThenPinch_data with press/release sequences somehow: vectors of touchpoint IDs? or a string representation?
+void tst_MptaInterop::touchesThenPinch()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "pinchDragMPTA.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>();
+ QVERIFY(mpta);
+ QQuickPinchHandler *pinch = window->rootObject()->findChild<QQuickPinchHandler*>();
+ QVERIFY(pinch);
+ QQuickDragHandler *drag = window->rootObject()->findChild<QQuickDragHandler*>();
+ QVERIFY(drag);
+ QQmlListReference tp(mpta, "touchPoints");
+ QVERIFY(tp.at(3)); // the QML declares four touchpoints
+ QSignalSpy mptaPressedSpy(mpta, SIGNAL(pressed(QList<QObject*>)));
+ QSignalSpy mptaReleasedSpy(mpta, SIGNAL(released(QList<QObject*>)));
+ QSignalSpy mptaCanceledSpy(mpta, SIGNAL(canceled(QList<QObject*>)));
+ QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
+ auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice);
+
+ // Press one touchpoint:
+ // DragHandler gets a passive grab
+ // PinchHandler declines, because it wants 3 touchpoints
+ // MPTA doesn't get a chance, because DragHandler accepted the single EventPoint
+ QPoint p1 = mpta->mapToScene(QPointF(20, 20)).toPoint();
+ touch.press(1, p1).commit();
+ QQuickTouchUtils::flush(window);
+ QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr);
+ QTRY_COMPARE(pointerEvent->point(0)->passiveGrabbers().first(), drag);
+
+ // Press a second touchpoint: MPTA grabs it
+ QPoint p2 = mpta->mapToScene(QPointF(200, 30)).toPoint();
+ touch.stationary(1).press(2, p2).commit();
+ QQuickTouchUtils::flush(window);
+ QVERIFY(tp.at(0)->property("pressed").toBool());
+ QTRY_VERIFY(tp.at(1)->property("pressed").toBool());
+ QCOMPARE(mptaPressedSpy.count(), 1);
+
+ // ATM it's required that when PinchHandler sees the third touchpoint,
+ // the pre-existing points must have moved far enough to exceed the drag threshold.
+ // If MPTA is allowed to grab that third point, then PinchHandler won't steal.
+ // TODO should we change that? make sure that if PH has a passive grab, it always gets updated even though MPTA has the grab?
+ for (int i = 0; i < 2; ++i) {
+ p1 += QPoint(dragThreshold, dragThreshold);
+ p2 += QPoint(dragThreshold, dragThreshold);
+ touch.move(1, p1).move(2, p2).commit();
+ }
+
+ // Press a third touchpoint: PinchHandler grabs, MPTA loses its grabs
+ QPoint p3 = mpta->mapToScene(QPointF(110, 200)).toPoint();
+ touch.stationary(1).stationary(2).press(3, p3).commit();
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(tp.at(0)->property("pressed").toBool(), false);
+ QCOMPARE(tp.at(1)->property("pressed").toBool(), false);
+ QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
+ QCOMPARE(mptaPressedSpy.count(), 1);
+ QCOMPARE(mptaCanceledSpy.count(), 1);
+ QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch);
+ QTRY_COMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch);
+ QTRY_COMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch);
+ QVERIFY(pinch->active());
+
+ // Start moving: PinchHandler steals the exclusive grab from MPTA as soon as dragThreshold is exceeded
+ int pinchStoleGrab = 0;
+ for (int i = 0; i < 8; ++i) {
+ p1 += QPoint(dragThreshold / 2, dragThreshold / 2);
+ p2 += QPoint(dragThreshold / 2, dragThreshold / 2);
+ p3 += QPoint(-dragThreshold / 2, dragThreshold / 2);
+ touch.move(1, p1).move(2, p2).move(3, p3).commit();
+ QQuickTouchUtils::flush(window);
+ QTRY_COMPARE(tp.at(0)->property("pressed").toBool(), false);
+ QCOMPARE(tp.at(1)->property("pressed").toBool(), false);
+ QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
+ if (!pinchStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == pinch)
+ pinchStoleGrab = i;
+ }
+ qCDebug(lcPointerTests) << "pinch started after" << pinchStoleGrab << "moves; ended with scale" << pinch->scale() << "rot" << pinch->rotation();
+ QTRY_VERIFY(pinch->rotation() > 8);
+ QVERIFY(pinch->scale() > 1);
+
+ // Press one more point (pinkie finger)
+ QPoint p4 = mpta->mapToScene(QPointF(300, 200)).toPoint();
+ touch.move(1, p1).move(2, p2).move(3, p3).press(4, p4).commit();
+ // PinchHandler gives up its grabs (only on non-stationary points at this time: see QQuickPointerHandler::handlePointerEvent())
+ // because it has minimum touch points 3, maximum touch points 3, and now there are 4 points.
+ // MPTA grabs all points which are not already grabbed
+ QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
+ // Move some more... MPTA keeps reacting
+ for (int i = 0; i < 8; ++i) {
+ p1 += QPoint(4, 4);
+ p2 += QPoint(4, 4);
+ p3 += QPoint(-4, 4);
+ p4 += QPoint(-4, -4);
+ touch.move(1, p1).move(2, p2).move(3, p3).move(4, p4).commit();
+ QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta);
+ QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
+ QCOMPARE(tp.at(0)->property("pressed").toBool(), true);
+ QCOMPARE(tp.at(1)->property("pressed").toBool(), true);
+ QCOMPARE(tp.at(2)->property("pressed").toBool(), true);
+ QCOMPARE(tp.at(3)->property("pressed").toBool(), true);
+ }
+
+ // Release the pinkie: PinchHandler acquires passive grabs on the 3 remaining points
+ touch.move(1, p1).move(2, p2).move(3, p3).release(4, p4).commit();
+ // Move some more: PinchHander grabs again, and reacts
+ for (int i = 0; i < 8; ++i) {
+ p1 -= QPoint(4, 4);
+ p2 += QPoint(4, 4);
+ p3 -= QPoint(-4, 4);
+ touch.move(1, p1).move(2, p2).move(3, p3).commit();
+ QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch);
+ QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch);
+ QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch);
+ }
+
+ // Release the first finger
+ touch.stationary(2).stationary(3).release(1, p1).commit();
+ // Move some more: PinchHander isn't interested in a mere 2 points.
+ // MPTA could maybe react; but QQuickWindowPrivate::deliverTouchEvent() calls
+ // deliverPressOrReleaseEvent() in a way which "starts over" with event delivery
+ // only for handlers, not for Items; therefore MPTA is not visited at this time.
+ for (int i = 0; i < 8; ++i) {
+ p2 -= QPoint(4, 4);
+ p3 += QPoint(4, 4);
+ touch.move(2, p2).move(3, p3).commit();
+ QQuickTouchUtils::flush(window);
+ }
+
+ // Release another finger
+ touch.stationary(2).release(3, p3).commit();
+ // Move some more: DragHandler reacts.
+ // It had a passive grab this whole time; now it activates and gets an exclusive grab.
+ for (int i = 0; i < 8; ++i) {
+ p2 += QPoint(8, -8);
+ touch.move(2, p2).commit();
+ QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), drag);
+ }
+
+ touch.release(2, p2).commit();
+ QQuickTouchUtils::flush(window);
+ QTRY_COMPARE(mptaReleasedSpy.count(), 1);
+}
+
+QTEST_MAIN(tst_MptaInterop)
+
+#include "tst_multipointtoucharea_interop.moc"
diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
new file mode 100644
index 0000000000..2492924944
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro
@@ -0,0 +1,11 @@
+TEMPLATE = subdirs
+
+qtConfig(private_tests) {
+ SUBDIRS += \
+ flickableinterop \
+ multipointtoucharea_interop \
+ qquickpointerhandler \
+ qquickdraghandler \
+ qquicktaphandler \
+}
+
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml
new file mode 100644
index 0000000000..fe5f74743d
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ objectName: label
+ property int value: 50
+ property int maximumValue: 99
+ property alias label: label.text
+ property alias tapEnabled: tap.enabled
+ property alias pressed: tap.pressed
+ signal tapped
+ width: 140
+ height: 400
+
+ DragHandler {
+ id: dragHandler
+ objectName: label.text + " DragHandler"
+ target: knob
+ xAxis.enabled: false
+ yAxis.minimum: slot.y
+ yAxis.maximum: slot.height + slot.y - knob.height
+ }
+
+ 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 {
+ id: glow
+ anchors.fill: knob
+ anchors.margins: -5
+ anchors.leftMargin: -2
+ anchors.horizontalCenterOffset: 1
+ radius: 5
+ color: "#4400FFFF"
+ opacity: tap.pressed || tapFlash.running ? 1 : 0
+ FlashAnimation on visible {
+ id: tapFlash
+ }
+ }
+ Rectangle {
+ id: knob
+ objectName: "Slider Knob"
+ width: parent.width - 2
+ height: 30
+ radius: 5
+ color: "darkgray"
+ border.color: "black"
+ 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 }
+ TapHandler {
+ id: tap
+ objectName: label.text + " TapHandler"
+ gesturePolicy: TapHandler.DragThreshold
+ onTapped: {
+ tapFlash.start()
+ root.tapped
+ }
+ }
+ }
+
+ 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
+ }
+
+ Component.onCompleted: {
+ knob.programmatic = true
+ knob.setValue(root.value)
+ knob.programmatic = false
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml
new file mode 100644
index 0000000000..158a02b7a6
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+SequentialAnimation {
+ id: tapFlash
+ running: false
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml
new file mode 100644
index 0000000000..a41a8285b6
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ objectName: label
+ property int value: 50
+ property int maximumValue: 99
+ property alias label: label.text
+ property alias tapEnabled: tap.enabled
+ property alias pressed: tap.pressed
+ signal tapped
+ width: 140
+ height: 400
+
+ 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 {
+ id: glow
+ anchors.fill: knob
+ anchors.margins: -5
+ anchors.leftMargin: -2
+ anchors.horizontalCenterOffset: 1
+ radius: 5
+ color: "#4400FFFF"
+ opacity: tap.pressed || tapFlash.running ? 1 : 0
+ FlashAnimation on visible {
+ id: tapFlash
+ }
+ }
+ Rectangle {
+ id: knob
+ objectName: root.label + " Knob"
+ width: parent.width - 2
+ height: 30
+ radius: 5
+ color: "darkgray"
+ border.color: "black"
+ 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 }
+ function flash() { tapFlash.start() }
+ DragHandler {
+ id: dragHandler
+ objectName: label.text + " DragHandler"
+ xAxis.enabled: false
+ yAxis.minimum: slot.y
+ yAxis.maximum: slot.height + slot.y - knob.height
+ }
+ TapHandler {
+ id: tap
+ objectName: label.text + " TapHandler"
+ gesturePolicy: TapHandler.DragThreshold
+ onTapped: {
+ tapFlash.start()
+ root.tapped
+ }
+ }
+ }
+
+ 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
+ }
+
+ Component.onCompleted: {
+ knob.programmatic = true
+ knob.setValue(root.value)
+ knob.programmatic = false
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml
new file mode 100644
index 0000000000..5b701b4033
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** 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:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ objectName: "Draggables"
+ width: 640
+ height: 480
+
+ Repeater {
+ model: 2
+
+ Rectangle {
+ id: ball
+ objectName: "Ball " + index
+ color: dragHandler.active ? "blue" : "lightsteelblue"
+ width: 80; height: 80; x: 200 + index * 200; y: 200; radius: width / 2
+ onParentChanged: console.log(this + " parent " + parent)
+
+ DragHandler {
+ id: dragHandler
+ objectName: "DragHandler " + index
+ }
+
+ Text {
+ color: "white"
+ anchors.centerIn: parent
+ text: dragHandler.point.position.x.toFixed(1) + "," + dragHandler.point.position.y.toFixed(1)
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml
new file mode 100644
index 0000000000..ba6e2d00a8
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ width: 900
+ height: 850
+ objectName: "root"
+ color: "#222222"
+
+ Grid {
+ objectName: "grid"
+ anchors.fill: parent
+ spacing: 10
+ columns: 6
+ Repeater {
+ id: top
+ objectName: "top"
+ model: 6
+
+ delegate: Slider {
+ objectName: label
+ label: "Drag Knob " + index
+ width: 140
+ }
+ }
+ Repeater {
+ id: bottom
+ objectName: "bottom"
+ model: 6
+
+ delegate: DragAnywhereSlider {
+ objectName: label
+ label: "Drag Anywhere " + index
+ width: 140
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml
new file mode 100644
index 0000000000..3545badd86
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/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/qquickdraghandler/data/simpleTapAndDragHandlers.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml
new file mode 100644
index 0000000000..adb8332213
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ width: 900
+ height: 850
+ objectName: "root"
+ color: "#222222"
+
+ Row {
+ objectName: "row"
+ anchors.fill: parent
+ spacing: 10
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ objectName: "dragAndTap"
+ DragHandler {
+ objectName: "drag"
+ }
+ TapHandler {
+ objectName: "tap"
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ }
+ Rectangle {
+ width: 50
+ height: 50
+ color: "aqua"
+ objectName: "tapAndDrag"
+ TapHandler {
+ objectName: "tap"
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ DragHandler {
+ objectName: "drag"
+ }
+ }
+
+ Rectangle {
+ color: "aqua"
+ width: 50
+ height: 50
+ objectName: "dragAndTapNotSiblings"
+ DragHandler {
+ objectName: "drag"
+ }
+ Rectangle {
+ color: "blue"
+ width: 30
+ height: 30
+ anchors.centerIn: parent
+ TapHandler {
+ objectName: "tap"
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ }
+ }
+ Rectangle {
+ color: "aqua"
+ width: 50
+ height: 50
+ objectName: "tapAndDragNotSiblings"
+ TapHandler {
+ objectName: "tap"
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ Rectangle {
+ color: "blue"
+ x: 10
+ y: 10
+ width: 30
+ height: 30
+ DragHandler {
+ objectName: "drag"
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro
new file mode 100644
index 0000000000..42c4e46c4f
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro
@@ -0,0 +1,21 @@
+CONFIG += testcase
+
+TARGET = tst_qquickdraghandler
+QT += core-private gui-private qml-private quick-private testlib
+
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquickdraghandler.cpp
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+OTHER_FILES += data/DragAnywhereSlider.qml \
+ data/FlashAnimation.qml \
+ data/Slider.qml \
+ data/draggables.qml \
+ data/grabberstate.qml \
+ data/multipleSliders.qml \
+ data/reparenting.qml \
diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp
new file mode 100644
index 0000000000..f827b82205
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp
@@ -0,0 +1,487 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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 <QtQml/qqmlengine.h>
+#include <QtQml/qqmlproperty.h>
+#include <QtQuick/private/qquickdraghandler_p.h>
+#include <QtQuick/private/qquickrepeater_p.h>
+#include <QtQuick/private/qquicktaphandler_p.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+
+#include "../../../shared/util.h"
+#include "../../shared/viewtestutil.h"
+
+Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
+
+class tst_DragHandler : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_DragHandler()
+ :touchDevice(QTest::createTouchDevice())
+ {}
+
+private slots:
+ void initTestCase();
+
+ void defaultPropertyValues();
+ void touchDrag();
+ void mouseDrag();
+ void touchDragMulti();
+ void touchDragMultiSliders_data();
+ void touchDragMultiSliders();
+ void touchPassiveGrabbers_data();
+ void touchPassiveGrabbers();
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ QSet<QQuickPointerHandler *> passiveGrabbers(QQuickWindow *window, int pointId = 0);
+ QTouchDevice *touchDevice;
+};
+
+void tst_DragHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName)
+{
+ window.reset(new QQuickView);
+ window->setSource(testFileUrl(fileName));
+ QTRY_COMPARE(window->status(), QQuickView::Ready);
+ QQuickViewTestUtil::centerOnScreen(window.data());
+ QQuickViewTestUtil::moveMouseAway(window.data());
+
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window.data()));
+ QVERIFY(window->rootObject() != 0);
+}
+
+QSet<QQuickPointerHandler*> tst_DragHandler::passiveGrabbers(QQuickWindow *window, int pointId /*= 0*/)
+{
+ QSet<QQuickPointerHandler*> result;
+ QQuickWindowPrivate *winp = QQuickWindowPrivate::get(window);
+ if (QQuickPointerDevice* device = QQuickPointerDevice::touchDevice(touchDevice)) {
+ QQuickPointerEvent *pointerEvent = winp->pointerEventInstance(device);
+ for (int i = 0; i < pointerEvent->pointCount(); ++i) {
+ QQuickEventPoint *eventPoint = pointerEvent->point(i);
+ QVector<QPointer <QQuickPointerHandler> > passives = eventPoint->passiveGrabbers();
+ if (!pointId || eventPoint->pointId() == pointId) {
+ for (auto it = passives.constBegin(); it != passives.constEnd(); ++it)
+ result << it->data();
+ }
+ }
+ }
+ return result;
+}
+
+void tst_DragHandler::initTestCase()
+{
+ // This test assumes that we don't get synthesized mouse events from QGuiApplication
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
+
+ QQmlDataTest::initTestCase();
+}
+
+void tst_DragHandler::defaultPropertyValues()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "draggables.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *ball = window->rootObject()->childItems().first();
+ QVERIFY(ball);
+ QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandler);
+
+ QCOMPARE(dragHandler->acceptedButtons(), Qt::LeftButton);
+ QCOMPARE(dragHandler->translation(), QVector2D());
+ QCOMPARE(dragHandler->point().position(), QPointF());
+ QCOMPARE(dragHandler->point().scenePosition(), QPointF());
+ QCOMPARE(dragHandler->point().pressPosition(), QPointF());
+ QCOMPARE(dragHandler->point().scenePressPosition(), QPointF());
+ QCOMPARE(dragHandler->point().sceneGrabPosition(), QPointF());
+}
+
+void tst_DragHandler::touchDrag()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "draggables.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *ball = window->rootObject()->childItems().first();
+ QVERIFY(ball);
+ QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandler);
+
+ QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged()));
+
+ QPointF ballCenter = ball->clipRect().center();
+ QPointF scenePressPos = ball->mapToScene(ballCenter);
+ QPoint p1 = scenePressPos.toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!dragHandler->active());
+ QCOMPARE(dragHandler->point().position(), ballCenter);
+ QCOMPARE(dragHandler->point().pressPosition(), ballCenter);
+ QCOMPARE(dragHandler->point().scenePosition(), scenePressPos);
+ QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos);
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!dragHandler->active());
+ p1 += QPoint(1, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(dragHandler->active());
+ QCOMPARE(translationChangedSpy.count(), 0);
+ QCOMPARE(dragHandler->translation().x(), 0.0);
+ QPointF sceneGrabPos = p1;
+ QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos);
+ p1 += QPoint(19, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(dragHandler->active());
+ QCOMPARE(dragHandler->point().position(), ballCenter);
+ QCOMPARE(dragHandler->point().pressPosition(), ballCenter);
+ QCOMPARE(dragHandler->point().scenePosition(), ball->mapToScene(ballCenter));
+ QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos);
+ QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos);
+ QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0);
+ QCOMPARE(dragHandler->translation().y(), 0.0);
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!dragHandler->active());
+ QCOMPARE(dragHandler->point().pressedButtons(), Qt::NoButton);
+ QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1);
+ QCOMPARE(translationChangedSpy.count(), 1);
+}
+
+void tst_DragHandler::mouseDrag()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "draggables.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *ball = window->rootObject()->childItems().first();
+ QVERIFY(ball);
+ QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandler);
+
+ QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged()));
+
+ QPointF ballCenter = ball->clipRect().center();
+ QPointF scenePressPos = ball->mapToScene(ballCenter);
+ QPoint p1 = scenePressPos.toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QVERIFY(!dragHandler->active());
+ QCOMPARE(dragHandler->point().position(), ballCenter);
+ QCOMPARE(dragHandler->point().pressPosition(), ballCenter);
+ QCOMPARE(dragHandler->point().scenePosition(), scenePressPos);
+ QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos);
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(!dragHandler->active());
+ p1 += QPoint(1, 0);
+ QTest::mouseMove(window, p1);
+ QTRY_VERIFY(dragHandler->active());
+ QCOMPARE(translationChangedSpy.count(), 0);
+ QCOMPARE(dragHandler->translation().x(), 0.0);
+ QPointF sceneGrabPos = p1;
+ QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos);
+ p1 += QPoint(19, 0);
+ QTest::mouseMove(window, p1);
+ QTRY_VERIFY(dragHandler->active());
+ QCOMPARE(dragHandler->point().position(), ballCenter);
+ QCOMPARE(dragHandler->point().pressPosition(), ballCenter);
+ QCOMPARE(dragHandler->point().scenePosition(), ball->mapToScene(ballCenter));
+ QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos);
+ QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos);
+ QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0);
+ QCOMPARE(dragHandler->translation().y(), 0.0);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!dragHandler->active());
+ QCOMPARE(dragHandler->point().pressedButtons(), Qt::NoButton);
+ QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1);
+ QCOMPARE(translationChangedSpy.count(), 1);
+}
+
+void tst_DragHandler::touchDragMulti()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "draggables.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *ball1 = window->rootObject()->childItems().first();
+ QVERIFY(ball1);
+ QQuickDragHandler *dragHandler1 = ball1->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandler1);
+ QSignalSpy translationChangedSpy1(dragHandler1, SIGNAL(translationChanged()));
+
+ QQuickItem *ball2 = window->rootObject()->childItems().at(1);
+ QVERIFY(ball2);
+ QQuickDragHandler *dragHandler2 = ball2->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandler2);
+ QSignalSpy translationChangedSpy2(dragHandler2, SIGNAL(translationChanged()));
+
+ QPointF ball1Center = ball1->clipRect().center();
+ QPointF scenePressPos1 = ball1->mapToScene(ball1Center);
+ QPoint p1 = scenePressPos1.toPoint();
+ QPointF ball2Center = ball2->clipRect().center();
+ QPointF scenePressPos2 = ball2->mapToScene(ball2Center);
+ QPoint p2 = scenePressPos2.toPoint();
+
+ QTest::touchEvent(window, touchDevice).press(1, p1, window).press(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!dragHandler1->active());
+ QCOMPARE(dragHandler1->point().position(), ball1Center);
+ QCOMPARE(dragHandler1->point().pressPosition(), ball1Center);
+ QCOMPARE(dragHandler1->point().scenePosition(), scenePressPos1);
+ QCOMPARE(dragHandler1->point().scenePressPosition(), scenePressPos1);
+ QVERIFY(!dragHandler2->active());
+ QCOMPARE(dragHandler2->point().position(), ball2Center);
+ QCOMPARE(dragHandler2->point().pressPosition(), ball2Center);
+ QCOMPARE(dragHandler2->point().scenePosition(), scenePressPos2);
+ QCOMPARE(dragHandler2->point().scenePressPosition(), scenePressPos2);
+ p1 += QPoint(dragThreshold, 0);
+ p2 += QPoint(0, dragThreshold);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!dragHandler1->active());
+ p1 += QPoint(1, 0);
+ p2 += QPoint(0, 1);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(dragHandler1->active());
+ QVERIFY(dragHandler2->active());
+ QCOMPARE(translationChangedSpy1.count(), 0);
+ QCOMPARE(dragHandler1->translation().x(), 0.0);
+ QPointF sceneGrabPos1 = p1;
+ QPointF sceneGrabPos2 = p2;
+ QCOMPARE(dragHandler1->point().sceneGrabPosition(), sceneGrabPos1);
+ QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2);
+ p1 += QPoint(19, 0);
+ p2 += QPoint(0, 19);
+ QVERIFY(dragHandler2->active());
+ QCOMPARE(translationChangedSpy2.count(), 0);
+ QCOMPARE(dragHandler2->translation().x(), 0.0);
+ QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(dragHandler1->active());
+ QVERIFY(dragHandler2->active());
+ QCOMPARE(dragHandler1->point().position(), ball1Center);
+ QCOMPARE(dragHandler1->point().pressPosition(), ball1Center);
+ QCOMPARE(dragHandler1->point().scenePosition(), ball1->mapToScene(ball1Center));
+ QCOMPARE(dragHandler1->point().scenePressPosition(), scenePressPos1);
+ QCOMPARE(dragHandler1->point().sceneGrabPosition(), sceneGrabPos1);
+ QCOMPARE(dragHandler1->translation().x(), dragThreshold + 20.0);
+ QCOMPARE(dragHandler1->translation().y(), 0.0);
+ QCOMPARE(dragHandler2->point().position(), ball2Center);
+ QCOMPARE(dragHandler2->point().pressPosition(), ball2Center);
+ QCOMPARE(dragHandler2->point().scenePosition(), ball2->mapToScene(ball2Center));
+ QCOMPARE(dragHandler2->point().scenePressPosition(), scenePressPos2);
+ QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2);
+ QCOMPARE(dragHandler2->translation().x(), 0.0);
+ QCOMPARE(dragHandler2->translation().y(), dragThreshold + 20.0);
+ QTest::touchEvent(window, touchDevice).release(1, p1, window).stationary(2);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!dragHandler1->active());
+ QVERIFY(dragHandler2->active());
+ QCOMPARE(dragHandler1->point().pressedButtons(), Qt::NoButton);
+ QCOMPARE(ball1->mapToScene(ball1Center).toPoint(), p1);
+ QCOMPARE(translationChangedSpy1.count(), 1);
+ QTest::touchEvent(window, touchDevice).release(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!dragHandler2->active());
+ QCOMPARE(ball2->mapToScene(ball2Center).toPoint(), p2);
+ QCOMPARE(translationChangedSpy2.count(), 1);
+}
+
+void tst_DragHandler::touchDragMultiSliders_data()
+{
+ QTest::addColumn<int>("sliderRow");
+ QTest::addColumn<QVector<int> >("whichSliders");
+ QTest::addColumn<QVector<int> >("startingCenterOffsets");
+ QTest::addColumn<QVector<QVector2D> >("movements");
+
+ QTest::newRow("Drag Knob: start on the knobs, drag down") <<
+ 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
+ QTest::newRow("Drag Knob: start on the knobs, drag diagonally downward") <<
+ 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
+ QTest::newRow("Drag Anywhere: start on the knobs, drag down") <<
+ 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
+ QTest::newRow("Drag Anywhere: start on the knobs, drag diagonally downward") <<
+ 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
+ // TODO these next two fail because the DragHandler grabs when a finger
+ // drags across it from outside, but should rather start only if it is pressed inside
+// QTest::newRow("Drag Knob: start above the knobs, drag down") <<
+// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {0, 40}, {0, 60}, {0, 80} };
+// QTest::newRow("Drag Knob: start above the knobs, drag diagonally downward") <<
+// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
+ QTest::newRow("Drag Anywhere: start above the knobs, drag down") <<
+ 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
+ QTest::newRow("Drag Anywhere: start above the knobs, drag diagonally downward") <<
+ 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
+}
+
+void tst_DragHandler::touchDragMultiSliders()
+{
+ QFETCH(int, sliderRow);
+ QFETCH(QVector<int>, whichSliders);
+ QFETCH(QVector<int>, startingCenterOffsets);
+ QFETCH(QVector<QVector2D>, movements);
+ const int moveCount = 8;
+
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "multipleSliders.qml");
+ QQuickView * window = windowPtr.data();
+ QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
+
+ QQuickRepeater *rowRepeater = window->rootObject()->findChildren<QQuickRepeater *>()[sliderRow];
+ QVector<QQuickItem *> knobs;
+ QVector<QQuickDragHandler *> dragHandlers;
+ QVector<QQuickTapHandler *> tapHandlers;
+ QVector<QPointF> startPoints;
+ for (int sli : whichSliders) {
+ QQuickItem *slider = rowRepeater->itemAt(sli);
+ QVERIFY(slider);
+ dragHandlers << slider->findChild<QQuickDragHandler*>();
+ QVERIFY(dragHandlers[sli]);
+ tapHandlers << slider->findChild<QQuickTapHandler*>();
+ QVERIFY(tapHandlers[sli]);
+ knobs << tapHandlers[sli]->parentItem();
+ QPointF startPoint = knobs[sli]->mapToScene(knobs[sli]->clipRect().center());
+ startPoint.setY(startPoint.y() + startingCenterOffsets[sli]);
+ startPoints << startPoint;
+ qCDebug(lcPointerTests) << "row" << sliderRow << "slider" << sli << slider->objectName() <<
+ "start" << startingCenterOffsets[sli] << startPoints[sli];
+ }
+ QVector<QPointF> touchPoints = startPoints;
+
+ // Press
+ for (int sli : whichSliders)
+ touch.press(sli, touchPoints[sli].toPoint());
+ touch.commit();
+
+ // Moves
+ for (int m = 0; m < moveCount; ++m) {
+ for (int sli : whichSliders) {
+ QVector2D incr = movements[sli] / moveCount;
+ touchPoints[sli] += incr.toPointF();
+ touch.move(sli, touchPoints[sli].toPoint());
+ }
+ touch.commit();
+ QQuickTouchUtils::flush(window);
+ }
+
+ // Check that they moved to where they should: since the slider is constrained,
+ // only the y component should have an effect; knobs should not come out of their "grooves"
+ for (int sli : whichSliders) {
+ QPoint endPosition = knobs[sli]->mapToScene(knobs[sli]->clipRect().center()).toPoint();
+ QPoint expectedEndPosition(startPoints[sli].x(), startPoints[sli].y() + movements[sli].y());
+ if (sliderRow == 0 && qAbs(startingCenterOffsets[sli]) > knobs[sli]->height() / 2)
+ expectedEndPosition = startPoints[sli].toPoint();
+ qCDebug(lcPointerTests) << "slider " << knobs[sli]->objectName() << "started @" << startPoints[sli]
+ << "tried to move by" << movements[sli] << "ended up @" << endPosition << "expected" << expectedEndPosition;
+ QTRY_COMPARE(endPosition, expectedEndPosition);
+ }
+
+ // Release
+ for (int sli : whichSliders)
+ touch.release(sli, touchPoints[sli].toPoint());
+ touch.commit();
+}
+
+void tst_DragHandler::touchPassiveGrabbers_data()
+{
+ QTest::addColumn<QString>("itemName");
+ QTest::addColumn<QStringList>("expectedPassiveGrabberNames");
+
+ QTest::newRow("Drag And Tap") << "dragAndTap" << QStringList({"drag", "tap"});
+ QTest::newRow("Tap And Drag") << "tapAndDrag" << QStringList({"tap", "drag"});
+ QTest::newRow("Drag And Tap (not siblings)") << "dragAndTapNotSiblings" << QStringList({"drag", "tap"});
+ QTest::newRow("Tap And Drag (not siblings)") << "tapAndDragNotSiblings" << QStringList({"tap", "drag"});
+}
+
+void tst_DragHandler::touchPassiveGrabbers()
+{
+ QFETCH(QString, itemName);
+ QFETCH(QStringList, expectedPassiveGrabberNames);
+
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "simpleTapAndDragHandlers.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *row2 = window->rootObject()->findChild<QQuickItem*>(itemName);
+ QSet<QQuickPointerHandler *> expectedPassiveGrabbers;
+ for (QString objectName : expectedPassiveGrabberNames)
+ expectedPassiveGrabbers << row2->findChild<QQuickPointerHandler*>(objectName);
+
+ QPointF p1 = row2->mapToScene(row2->clipRect().center());
+ QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
+ touch.press(1, p1.toPoint()).commit();
+ QQuickTouchUtils::flush(window);
+
+ QCOMPARE(passiveGrabbers(window), expectedPassiveGrabbers);
+
+ QQuickDragHandler *dragHandler = nullptr;
+ for (QQuickPointerHandler *handler: expectedPassiveGrabbers) {
+ QCOMPARE(static_cast<QQuickSinglePointHandler *>(handler)->point().scenePressPosition(), p1);
+ QQuickDragHandler *dh = qmlobject_cast<QQuickDragHandler *>(handler);
+ if (dh)
+ dragHandler = dh;
+ }
+ QVERIFY(dragHandler);
+ QPointF initialPos = dragHandler->target()->position();
+
+ p1 += QPointF(50, 50);
+ touch.move(1, p1.toPoint()).commit();
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(dragHandler->active());
+
+ p1 += QPointF(50, 50);
+ touch.move(1, p1.toPoint()).commit();
+ QQuickTouchUtils::flush(window);
+ QPointF movementDelta = dragHandler->target()->position() - initialPos;
+ qCDebug(lcPointerTests) << "DragHandler moved the target by" << movementDelta;
+ QVERIFY(movementDelta.x() >= 100);
+ QVERIFY(movementDelta.y() >= 100);
+
+ QTest::qWait(500);
+
+ touch.release(1, p1.toPoint());
+ touch.commit();
+ QQuickTouchUtils::flush(window);
+}
+
+QTEST_MAIN(tst_DragHandler)
+
+#include "tst_qquickdraghandler.moc"
+
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..126cf3ff2b
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml
@@ -0,0 +1,28 @@
+import QtQuick 2.8
+import Qt.test 1.0
+
+Item {
+ id: root
+ objectName: "root Item"
+ width: 320
+ height: 480
+
+ Rectangle {
+ objectName: "eventItem's bounds"
+ anchors.fill: eventItem
+ color: "lightsteelblue"
+ }
+
+ EventItem {
+ id: eventItem
+ objectName: "eventItem1"
+ x: 5
+ y: 5
+ height: 30
+ width: 30
+
+ EventHandler {
+ objectName: "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..d38ae3190e
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp
@@ -0,0 +1,580 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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 <private/qdebug_p.h>
+#include <QtGui/qstylehints.h>
+#include <QtQuick/private/qquickpointerhandler_p.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+
+#include "../../../shared/util.h"
+#include "../../shared/viewtestutil.h"
+
+Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
+
+class Event
+{
+ Q_GADGET
+public:
+ enum Destination {
+ FilterDestination,
+ MouseDestination,
+ TouchDestination,
+ HandlerDestination
+ };
+ Q_ENUM(Destination)
+
+ Event(Destination d, QEvent::Type t, Qt::TouchPointState s, int grabState, QPointF item, QPointF scene)
+ : destination(d), type(t), state(s), grabState(grabState), posWrtItem(item), posWrtScene(scene)
+ {}
+
+ Destination destination;
+ QEvent::Type type; // if this represents a QEvent that was received
+ Qt::TouchPointState state; // if this represents an event (pointer, touch or mouse)
+ int grabState; // if this represents an onGrabChanged() notification (QQuickEventPoint::GrabState)
+ QPointF posWrtItem;
+ QPointF posWrtScene;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const class Event &event) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ dbg << "Event(";
+ QtDebugUtils::formatQEnum(dbg, event.destination);
+ dbg << ' ';
+ QtDebugUtils::formatQEnum(dbg, event.type);
+ dbg << ' ';
+ QtDebugUtils::formatQEnum(dbg, event.state);
+ if (event.grabState) {
+ dbg << ' ';
+ QtDebugUtils::formatQEnum(dbg, QQuickEventPoint::GrabState(event.grabState));
+ }
+ dbg << " @ ";
+ QtDebugUtils::formatQPoint(dbg, event.posWrtItem);
+ dbg << " S ";
+ QtDebugUtils::formatQPoint(dbg, event.posWrtScene);
+ dbg << ')';
+ return dbg;
+}
+#endif
+
+enum {
+ NoGrab = 0,
+};
+
+class EventItem : public QQuickItem
+{
+ Q_OBJECT
+public:
+ EventItem(QQuickItem *parent = 0)
+ : QQuickItem(parent), acceptPointer(false), grabPointer(false), acceptMouse(false), acceptTouch(false), filterTouch(false)
+ {}
+
+ inline int grabState(bool accept, Qt::TouchPointState state) {
+ return (accept && (state != Qt::TouchPointReleased)) ? (int)QQuickEventPoint::GrabExclusive : (int)NoGrab;
+ }
+
+ void touchEvent(QTouchEvent *event)
+ {
+ qCDebug(lcPointerTests) << event << "will accept?" << acceptTouch;
+ for (const QTouchEvent::TouchPoint &tp : event->touchPoints())
+ eventList.append(Event(Event::TouchDestination, event->type(), tp.state(), grabState(acceptTouch, tp.state()), tp.pos(), tp.scenePos()));
+ event->setAccepted(acceptTouch);
+ }
+ void mousePressEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointPressed, grabState(acceptMouse, Qt::TouchPointPressed), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseMoveEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointMoved, grabState(acceptMouse, Qt::TouchPointMoved), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseReleaseEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointReleased, grabState(acceptMouse, Qt::TouchPointReleased), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+ void mouseDoubleClickEvent(QMouseEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointPressed, grabState(acceptMouse, Qt::TouchPointPressed), event->pos(), event->windowPos()));
+ event->setAccepted(acceptMouse);
+ }
+
+ void mouseUngrabEvent()
+ {
+ qCDebug(lcPointerTests);
+ eventList.append(Event(Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive, QPoint(0,0), QPoint(0,0)));
+ }
+
+ bool event(QEvent *event)
+ {
+ qCDebug(lcPointerTests) << event;
+ return QQuickItem::event(event);
+ }
+
+ QList<Event> eventList;
+ bool acceptPointer;
+ bool grabPointer;
+ 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::FilterDestination, event->type(), tp.state(), QQuickEventPoint::GrabExclusive, tp.pos(), tp.scenePos()));
+ if (filterTouch)
+ event->accept();
+ return true;
+ }
+ return false;
+ }
+};
+
+#define QCOMPARE_EVENT(i, d, t, s, g) \
+ {\
+ const Event &event = eventItem1->eventList.at(i);\
+ QCOMPARE(event.destination, d);\
+ QCOMPARE(event.type, t);\
+ QCOMPARE(event.state, s);\
+ QCOMPARE(event.grabState, g);\
+ }\
+
+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);
+ if (item->acceptPointer)
+ point->setAccepted(item->acceptPointer); // does NOT imply a grab
+ if (item->grabPointer)
+ setExclusiveGrab(point, true);
+ qCDebug(lcPointerTests) << " " << i << ":" << point << "accepted?" << item->acceptPointer << "grabbed?" << (point->exclusiveGrabber() == this);
+ item->eventList.append(Event(Event::HandlerDestination, QEvent::Pointer,
+ static_cast<Qt::TouchPointState>(point->state()),
+ item->grabPointer ? (int)QQuickEventPoint::GrabExclusive : (int)NoGrab,
+ eventPos(point), point->scenePosition()));
+ }
+ }
+
+ void onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) override
+ {
+ EventItem *item = static_cast<EventItem *>(target());
+ item->eventList.append(Event(Event::HandlerDestination, QEvent::None,
+ static_cast<Qt::TouchPointState>(point->state()), stateChange, eventPos(point), point->scenePosition()));
+ }
+};
+
+class tst_PointerHandlers : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_PointerHandlers()
+ :touchDevice(QTest::createTouchDevice())
+ {}
+
+private slots:
+ void initTestCase();
+
+ void touchEventDelivery();
+ void mouseEventDelivery();
+ void touchReleaseOutside_data();
+ void touchReleaseOutside();
+
+protected:
+ bool eventFilter(QObject *, QEvent *event)
+ {
+ Qt::TouchPointState tpState;
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ tpState = Qt::TouchPointPressed;
+ break;
+ case QEvent::MouseMove:
+ tpState = Qt::TouchPointMoved;
+ break;
+ case QEvent::MouseButtonRelease:
+ tpState = Qt::TouchPointReleased;
+ break;
+ default:
+ // So far we aren't recording filtered touch events here - they would be quite numerous in some cases
+ return false;
+ }
+ QMouseEvent *me = static_cast<QMouseEvent*>(event);
+ filteredEventList.append(Event(Event::FilterDestination, event->type(), tpState,
+ 0, me->pos(), me->globalPos()));
+ return false;
+ }
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ QTouchDevice *touchDevice;
+ QList<Event> filteredEventList;
+};
+
+void tst_PointerHandlers::createView(QScopedPointer<QQuickView> &window, const char *fileName)
+{
+ window.reset(new QQuickView);
+// window->setGeometry(0,0,240,320);
+ window->setSource(testFileUrl(fileName));
+ QTRY_COMPARE(window->status(), QQuickView::Ready);
+ QQuickViewTestUtil::centerOnScreen(window.data());
+ QQuickViewTestUtil::moveMouseAway(window.data());
+
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window.data()));
+ QVERIFY(window->rootObject() != 0);
+}
+
+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()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "singleitem.qml");
+ QQuickView * window = windowPtr.data();
+
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, NoGrab);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab);
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(QQuickPointerDevice::touchDevices().at(0));
+ QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), eventItem1);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab);
+ QCOMPARE_EVENT(3, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(5, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab);
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ 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_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab);
+ QCOMPARE_EVENT(4, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, NoGrab);
+ QCOMPARE_EVENT(5, Event::MouseDestination, QEvent::MouseMove, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 10);
+ QCOMPARE_EVENT(6, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(7, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(8, Event::MouseDestination, QEvent::MouseButtonRelease, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(9, Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive);
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab);
+ QCOMPARE_EVENT(3, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(5, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab);
+ eventItem1->eventList.clear();
+
+ // Accept pointer events
+ eventItem1->acceptPointer = true;
+ eventItem1->grabPointer = true;
+ p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 2);
+ QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::None, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ p1 += QPoint(10, 0);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ qCDebug(lcPointerTests) << eventItem1->eventList;
+ QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, QQuickEventPoint::GrabExclusive);
+ QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::None, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive);
+ eventItem1->eventList.clear();
+}
+
+void tst_PointerHandlers::mouseEventDelivery()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "singleitem.qml");
+ QQuickView * window = windowPtr.data();
+
+ 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(), 3);
+ QTest::mouseRelease(window, Qt::LeftButton);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ 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_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
+ QCOMPARE_EVENT(1, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ 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_EVENT(2, Event::MouseDestination, QEvent::MouseMove, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ QCOMPARE_EVENT(3, Event::MouseDestination, QEvent::MouseButtonRelease, Qt::TouchPointReleased, NoGrab);
+ QCOMPARE_EVENT(4, Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive);
+ eventItem1->eventList.clear();
+
+ // wait to avoid getting a double click event
+ QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+ // Grab pointer events
+ eventItem1->acceptMouse = false;
+ eventItem1->acceptPointer = true;
+ eventItem1->grabPointer = true;
+ p1 = QPoint(20, 20);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_COMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::None, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive);
+ QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, 0);
+ p1 += QPoint(10, 0);
+ QTest::mouseMove(window, p1);
+ QCOMPARE(eventItem1->eventList.size(), 4);
+ QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 6);
+ QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, QQuickEventPoint::GrabExclusive);
+ QCOMPARE_EVENT(5, Event::HandlerDestination, QEvent::None, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive);
+ eventItem1->eventList.clear();
+}
+
+void tst_PointerHandlers::touchReleaseOutside_data()
+{
+ QTest::addColumn<bool>("acceptPointer");
+ QTest::addColumn<bool>("grabPointer");
+ QTest::addColumn<int>("eventCount");
+ QTest::addColumn<int>("endIndexToTest");
+ QTest::addColumn<int>("endDestination"); // Event::Destination
+ QTest::addColumn<int>("endType"); // QEvent::Type
+ QTest::addColumn<int>("endState"); // Qt::TouchPointState
+ QTest::addColumn<int>("endGrabState"); // Qt::TouchPointState
+
+ QTest::newRow("reject and ignore") << false << false << 6 << 5 << (int)Event::TouchDestination
+ << (int)QEvent::TouchEnd << (int)Qt::TouchPointReleased << (int)NoGrab;
+ QTest::newRow("reject and grab") << false << true << 5 << 4 << (int)Event::HandlerDestination
+ << (int)QEvent::None << (int)Qt::TouchPointReleased << (int)QQuickEventPoint::UngrabExclusive;
+ QTest::newRow("accept and ignore") << true << false << 1 << 0 << (int)Event::HandlerDestination
+ << (int)QEvent::Pointer << (int)Qt::TouchPointPressed << (int)NoGrab;
+ QTest::newRow("accept and grab") << true << true << 5 << 4 << (int)Event::HandlerDestination
+ << (int)QEvent::None << (int)Qt::TouchPointReleased << (int)QQuickEventPoint::UngrabExclusive;
+}
+
+void tst_PointerHandlers::touchReleaseOutside()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "singleitem.qml");
+ QQuickView * window = windowPtr.data();
+
+ QFETCH(bool, acceptPointer);
+ QFETCH(bool, grabPointer);
+ QFETCH(int, eventCount);
+ QFETCH(int, endIndexToTest);
+ QFETCH(int, endDestination);
+ QFETCH(int, endType);
+ QFETCH(int, endState);
+ QFETCH(int, endGrabState);
+
+ EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1");
+ QVERIFY(eventItem1);
+
+ eventItem1->acceptTouch = true;
+ eventItem1->acceptPointer = acceptPointer;
+ eventItem1->grabPointer = grabPointer;
+
+ QPoint p1 = QPoint(20, 20);
+ QTest::touchEvent(window, touchDevice).press(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ p1.setX(eventItem1->mapToScene(eventItem1->clipRect().bottomRight()).x() + 10);
+ QTest::touchEvent(window, touchDevice).move(0, p1, window);
+ QTest::touchEvent(window, touchDevice).release(0, p1, window);
+ QQuickTouchUtils::flush(window);
+ qCDebug(lcPointerTests) << eventItem1->eventList;
+ QCOMPARE(eventItem1->eventList.size(), eventCount);
+ QCOMPARE_EVENT(endIndexToTest, endDestination, endType, endState, endGrabState);
+}
+
+QTEST_MAIN(tst_PointerHandlers)
+
+#include "tst_qquickpointerhandler.moc"
+
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml
new file mode 100644
index 0000000000..0b7713bd13
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ property alias label: label.text
+ property alias pressed: tap.pressed
+ property bool checked: false
+ property alias gesturePolicy: tap.gesturePolicy
+ signal tapped
+
+ width: label.implicitWidth * 1.5; height: label.implicitHeight * 2.0
+ border.color: "#9f9d9a"; border.width: 1; radius: height / 4; antialiasing: true
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: tap.pressed ? "#b8b5b2" : "#efebe7" }
+ GradientStop { position: 1.0; color: "#b8b5b2" }
+ }
+
+ TapHandler {
+ id: tap
+ objectName: label.text
+ longPressThreshold: 100 // CI can be insanely slow, so don't demand a timely release to generate onTapped
+ onTapped: {
+ tapFlash.start()
+ root.tapped()
+ }
+ }
+
+ Text {
+ id: label
+ font.pointSize: 14
+ text: "Button"
+ anchors.centerIn: parent
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+ border.width: 2; radius: root.radius; antialiasing: true
+ opacity: tapFlash.running ? 1 : 0
+ FlashAnimation on visible { id: tapFlash }
+ }
+
+ Rectangle {
+ objectName: "expandingCircle"
+ radius: tap.timeHeld * 100
+ visible: radius > 0 && tap.pressed
+ border.width: 3
+ border.color: "blue"
+ color: "transparent"
+ width: radius * 2
+ height: radius * 2
+ x: tap.point.scenePressPosition.x - radius
+ y: tap.point.scenePressPosition.y - radius
+ opacity: 0.25
+ Component.onCompleted: parent = root.parent
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml
new file mode 100644
index 0000000000..158a02b7a6
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+SequentialAnimation {
+ id: tapFlash
+ running: false
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: false }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: true }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml
new file mode 100644
index 0000000000..219d4a70a8
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import Qt.labs.handlers 1.0
+
+Item {
+ width: 320
+ height: 240
+ Button {
+ id: button
+ objectName: "Overridden"
+ label: "Overridden"
+ x: 10; y: 10; width: parent.width - 20; height: 40
+ TapHandler {
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ objectName: "override"
+ onTapped: button.tapped()
+ }
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml
new file mode 100644
index 0000000000..1df9bb9b96
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import Qt.labs.handlers 1.0
+
+Item {
+ width: 320
+ height: 240
+ Button {
+ objectName: "DragThreshold"
+ label: "DragThreshold"
+ x: 10; y: 10; width: parent.width - 20; height: 40
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ Button {
+ objectName: "WithinBounds"
+ label: "WithinBounds"
+ x: 10; y: 60; width: parent.width - 20; height: 40
+ gesturePolicy: TapHandler.WithinBounds
+ }
+ Button {
+ objectName: "ReleaseWithinBounds"
+ label: "ReleaseWithinBounds"
+ x: 10; y: 110; width: parent.width - 20; height: 40
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro b/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro
new file mode 100644
index 0000000000..b41a94b55e
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro
@@ -0,0 +1,16 @@
+CONFIG += testcase
+
+TARGET = tst_qquicktaphandler
+QT += core-private gui-private qml-private quick-private testlib
+
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquicktaphandler.cpp
+
+include (../../../shared/util.pri)
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+# OTHER_FILES += data/foo.qml
+
diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp
new file mode 100644
index 0000000000..9e3261f7b2
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp
@@ -0,0 +1,627 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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/qquickpointerhandler_p.h>
+#include <QtQuick/private/qquicktaphandler_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")
+
+class tst_TapHandler : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_TapHandler()
+ :touchDevice(QTest::createTouchDevice())
+ {}
+
+private slots:
+ void initTestCase();
+
+ void touchGesturePolicyDragThreshold();
+ void mouseGesturePolicyDragThreshold();
+ void touchGesturePolicyWithinBounds();
+ void mouseGesturePolicyWithinBounds();
+ void touchGesturePolicyReleaseWithinBounds();
+ void mouseGesturePolicyReleaseWithinBounds();
+ void touchMultiTap();
+ void mouseMultiTap();
+ void touchLongPress();
+ void mouseLongPress();
+ void buttonsMultiTouch();
+ void componentUserBehavioralOverride();
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ QTouchDevice *touchDevice;
+};
+
+void tst_TapHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName)
+{
+ window.reset(new QQuickView);
+ window->setSource(testFileUrl(fileName));
+ QTRY_COMPARE(window->status(), QQuickView::Ready);
+ QQuickViewTestUtil::centerOnScreen(window.data());
+ QQuickViewTestUtil::moveMouseAway(window.data());
+
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window.data()));
+ QVERIFY(window->rootObject() != 0);
+}
+
+void tst_TapHandler::initTestCase()
+{
+ // This test assumes that we don't get synthesized mouse events from QGuiApplication
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
+
+ QQmlDataTest::initTestCase();
+}
+
+void tst_TapHandler::touchGesturePolicyDragThreshold()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(buttonDragThreshold);
+ QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
+
+ // DragThreshold button stays pressed while touchpoint stays within dragThreshold, emits tapped on release
+ QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 1);
+
+ // DragThreshold button is no longer pressed if touchpoint goes beyond dragThreshold
+ dragThresholdTappedSpy.clear();
+ p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(1, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::mouseGesturePolicyDragThreshold()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(buttonDragThreshold);
+ QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
+
+ // DragThreshold button stays pressed while mouse stays within dragThreshold, emits tapped on release
+ QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QTRY_COMPARE(dragThresholdTappedSpy.count(), 1);
+
+ // DragThreshold button is no longer pressed if mouse goes beyond dragThreshold
+ dragThresholdTappedSpy.clear();
+ p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(dragThreshold, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ p1 += QPoint(1, 0);
+ QTest::mouseMove(window, p1);
+ QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QVERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::touchGesturePolicyWithinBounds()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
+ QVERIFY(buttonWithinBounds);
+ QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
+
+ // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
+ QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
+ p1 += QPoint(50, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 1);
+
+ // WithinBounds button is no longer pressed if touchpoint leaves bounds
+ withinBoundsTappedSpy.clear();
+ p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
+ p1 += QPoint(0, 100);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::mouseGesturePolicyWithinBounds()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
+ QVERIFY(buttonWithinBounds);
+ QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
+
+ // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
+ QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
+ p1 += QPoint(50, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 1);
+
+ // WithinBounds button is no longer pressed if touchpoint leaves bounds
+ withinBoundsTappedSpy.clear();
+ p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
+ p1 += QPoint(0, 100);
+ QTest::mouseMove(window, p1);
+ QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QVERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::touchGesturePolicyReleaseWithinBounds()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
+ QVERIFY(buttonReleaseWithinBounds);
+ QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
+
+ // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
+ // then if it comes back within bounds, emits tapped on release
+ QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(50, 0);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(250, 100);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint();
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
+
+ // ReleaseWithinBounds button does not emit tapped if released out of bounds
+ releaseWithinBoundsTappedSpy.clear();
+ p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(0, 100);
+ QTest::touchEvent(window, touchDevice).move(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::mouseGesturePolicyReleaseWithinBounds()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
+ QVERIFY(buttonReleaseWithinBounds);
+ QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
+
+ // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
+ // then if it comes back within bounds, emits tapped on release
+ QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(50, 0);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(250, 100);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint();
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
+
+ // ReleaseWithinBounds button does not emit tapped if released out of bounds
+ releaseWithinBoundsTappedSpy.clear();
+ p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ p1 += QPoint(0, 100);
+ QTest::mouseMove(window, p1);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
+}
+
+void tst_TapHandler::touchMultiTap()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(button);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ // Tap once
+ QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 1);
+
+ // Tap again in exactly the same place (not likely with touch in the real world)
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 2);
+
+ // Tap a third time, nearby
+ p1 += QPoint(dragThreshold, dragThreshold);
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 3);
+
+ // Tap a fourth time, drifting farther away
+ p1 += QPoint(dragThreshold, dragThreshold);
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 4);
+}
+
+void tst_TapHandler::mouseMultiTap()
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(button);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+
+ // Tap once
+ QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 1);
+
+ // Tap again in exactly the same place (not likely with touch in the real world)
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 2);
+
+ // Tap a third time, nearby
+ p1 += QPoint(dragThreshold, dragThreshold);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 3);
+
+ // Tap a fourth time, drifting farther away
+ p1 += QPoint(dragThreshold, dragThreshold);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 4);
+}
+
+void tst_TapHandler::touchLongPress()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(button);
+ QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold");
+ QVERIFY(tapHandler);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+ QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
+ QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
+ QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
+
+ // Reduce the threshold so that we can get a long press quickly
+ tapHandler->setLongPressThreshold(0.5);
+ QCOMPARE(longPressThresholdChangedSpy.count(), 1);
+
+ // Press and hold
+ QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTRY_COMPARE(longPressedSpy.count(), 1);
+ timeHeldSpy.wait(); // the longer we hold it, the more this will occur
+ qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times";
+ QVERIFY(timeHeldSpy.count() > 0);
+ QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
+
+ // Release and verify that tapped was not emitted
+ QTest::touchEvent(window, touchDevice).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 0);
+}
+
+void tst_TapHandler::mouseLongPress()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(button);
+ QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold");
+ QVERIFY(tapHandler);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+ QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
+ QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
+ QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
+
+ // Reduce the threshold so that we can get a long press quickly
+ tapHandler->setLongPressThreshold(0.5);
+ QCOMPARE(longPressThresholdChangedSpy.count(), 1);
+
+ // Press and hold
+ QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_VERIFY(button->property("pressed").toBool());
+ QTRY_COMPARE(longPressedSpy.count(), 1);
+ timeHeldSpy.wait(); // the longer we hold it, the more this will occur
+ qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times";
+ QVERIFY(timeHeldSpy.count() > 0);
+ QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
+
+ // Release and verify that tapped was not emitted
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 500);
+ QTRY_VERIFY(!button->property("pressed").toBool());
+ QCOMPARE(tappedSpy.count(), 0);
+}
+
+void tst_TapHandler::buttonsMultiTouch()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttons.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold");
+ QVERIFY(buttonDragThreshold);
+ QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
+
+ QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds");
+ QVERIFY(buttonWithinBounds);
+ QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
+
+ QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds");
+ QVERIFY(buttonReleaseWithinBounds);
+ QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
+
+ // can press multiple buttons at the same time
+ QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ QPoint p2 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).stationary(1).press(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
+ QPoint p3 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint();
+ QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).press(3, p3, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+
+ // can release top button and press again: others stay pressed the whole time
+ QTest::touchEvent(window, touchDevice).stationary(2).stationary(3).release(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 1);
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 0);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
+ QTest::touchEvent(window, touchDevice).stationary(2).stationary(3).press(1, p1, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+
+ // can release middle button and press again: others stay pressed the whole time
+ QTest::touchEvent(window, touchDevice).stationary(1).stationary(3).release(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 1);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 1);
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
+ QTest::touchEvent(window, touchDevice).stationary(1).stationary(3).press(2, p2, window);
+ QQuickTouchUtils::flush(window);
+ QVERIFY(buttonDragThreshold->property("pressed").toBool());
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+
+ // can release bottom button and press again: others stay pressed the whole time
+ QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).release(3, p3, window);
+ QQuickTouchUtils::flush(window);
+ QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QCOMPARE(withinBoundsTappedSpy.count(), 1);
+ QVERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
+ QCOMPARE(dragThresholdTappedSpy.count(), 1);
+ QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).press(3, p3, window);
+ QQuickTouchUtils::flush(window);
+ QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
+ QVERIFY(buttonWithinBounds->property("pressed").toBool());
+ QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
+}
+
+void tst_TapHandler::componentUserBehavioralOverride()
+{
+ QScopedPointer<QQuickView> windowPtr;
+ createView(windowPtr, "buttonOverrideHandler.qml");
+ QQuickView * window = windowPtr.data();
+
+ QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("Overridden");
+ QVERIFY(button);
+ QQuickTapHandler *innerTapHandler = button->findChild<QQuickTapHandler*>("Overridden");
+ QVERIFY(innerTapHandler);
+ QQuickTapHandler *userTapHandler = button->findChild<QQuickTapHandler*>("override");
+ QVERIFY(userTapHandler);
+ QSignalSpy tappedSpy(button, SIGNAL(tapped()));
+ QSignalSpy innerGrabChangedSpy(innerTapHandler, SIGNAL(grabChanged(QQuickEventPoint *)));
+ QSignalSpy userGrabChangedSpy(userTapHandler, SIGNAL(grabChanged(QQuickEventPoint *)));
+ QSignalSpy innerPressedChangedSpy(innerTapHandler, SIGNAL(pressedChanged()));
+ QSignalSpy userPressedChangedSpy(userTapHandler, SIGNAL(pressedChanged()));
+
+ // Press
+ QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_COMPARE(userPressedChangedSpy.count(), 1);
+ QCOMPARE(innerPressedChangedSpy.count(), 0);
+ QCOMPARE(innerGrabChangedSpy.count(), 0);
+ QCOMPARE(userGrabChangedSpy.count(), 1);
+
+ // Release
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTRY_COMPARE(userPressedChangedSpy.count(), 2);
+ QCOMPARE(innerPressedChangedSpy.count(), 0);
+ QCOMPARE(tappedSpy.count(), 1); // only because the override handler makes that happen
+ QCOMPARE(innerGrabChangedSpy.count(), 0);
+ QCOMPARE(userGrabChangedSpy.count(), 2);
+}
+
+QTEST_MAIN(tst_TapHandler)
+
+#include "tst_qquicktaphandler.moc"
+