aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick')
-rw-r--r--tests/auto/quick/examples/tst_examples.cpp2
-rw-r--r--tests/auto/quick/nokeywords/tst_nokeywords.cpp1
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml57
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml133
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml106
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml87
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro15
-rw-r--r--tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp475
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml120
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro15
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp261
-rw-r--r--tests/auto/quick/pointerhandlers/pointerhandlers.pro11
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml138
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml57
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml137
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml72
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml79
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml60
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro15
-rw-r--r--tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp405
-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.cpp579
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml99
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml57
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml65
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro16
-rw-r--r--tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp591
-rw-r--r--tests/auto/quick/qquickapplication/data/tst_platformname.qml6
-rw-r--r--tests/auto/quick/qquickapplication/qquickapplication.pro3
-rw-r--r--tests/auto/quick/qquickapplication/tst_qquickapplication.cpp19
-rw-r--r--tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml3
-rw-r--r--tests/auto/quick/qquickflickable/tst_qquickflickable.cpp151
-rw-r--r--tests/auto/quick/qquickimage/tst_qquickimage.cpp61
-rw-r--r--tests/auto/quick/qquickitem/tst_qquickitem.cpp7
-rw-r--r--tests/auto/quick/qquicklistview/randomsortmodel.cpp5
-rw-r--r--tests/auto/quick/qquickloader/qquickloader.pro1
-rw-r--r--tests/auto/quick/qquickpixmapcache/qquickpixmapcache.pro1
-rw-r--r--tests/auto/quick/qquickrectangle/data/gradient-multiple.qml30
-rw-r--r--tests/auto/quick/qquickrectangle/data/gradient-separate.qml20
-rw-r--r--tests/auto/quick/qquickrectangle/tst_qquickrectangle.cpp59
-rw-r--r--tests/auto/quick/qquickscreen/tst_qquickscreen.cpp3
-rw-r--r--tests/auto/quick/qquickshape/BLACKLIST8
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem1.qml5
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem2.qml7
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem3.pngbin0 -> 5214 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem3.qml33
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem4.pngbin0 -> 5713 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem4.qml56
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem5.pngbin0 -> 9377 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem5.qml37
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem6.pngbin0 -> 11024 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem6.qml35
-rw-r--r--tests/auto/quick/qquickshape/qquickshape.pro35
-rw-r--r--tests/auto/quick/qquickshape/tst_qquickshape.cpp288
-rw-r--r--tests/auto/quick/qquicktext/qquicktext.pro1
-rw-r--r--tests/auto/quick/qquicktext/tst_qquicktext.cpp55
-rw-r--r--tests/auto/quick/qquickwindow/BLACKLIST3
-rw-r--r--tests/auto/quick/qquickwindow/tst_qquickwindow.cpp540
-rw-r--r--tests/auto/quick/quick.pro2
-rw-r--r--tests/auto/quick/shared/viewtestutil.cpp6
-rw-r--r--tests/auto/quick/touchmouse/BLACKLIST3
-rw-r--r--tests/auto/quick/touchmouse/tst_touchmouse.cpp29
63 files changed, 5115 insertions, 94 deletions
diff --git a/tests/auto/quick/examples/tst_examples.cpp b/tests/auto/quick/examples/tst_examples.cpp
index b6742b9efe..2dc5a8ed23 100644
--- a/tests/auto/quick/examples/tst_examples.cpp
+++ b/tests/auto/quick/examples/tst_examples.cpp
@@ -86,6 +86,8 @@ tst_examples::tst_examples()
excludedDirs << "snippets/qml/visualdatamodel_rootindex";
excludedDirs << "snippets/qml/qtbinding";
excludedDirs << "snippets/qml/imports";
+ excludedFiles << "examples/quick/shapes/content/main.qml"; // relies on resources
+ excludedFiles << "examples/quick/shapes/content/interactive.qml"; // relies on resources
#ifdef QT_NO_XMLPATTERNS
excludedDirs << "demos/twitter";
diff --git a/tests/auto/quick/nokeywords/tst_nokeywords.cpp b/tests/auto/quick/nokeywords/tst_nokeywords.cpp
index ad77743ddd..e6655589a3 100644
--- a/tests/auto/quick/nokeywords/tst_nokeywords.cpp
+++ b/tests/auto/quick/nokeywords/tst_nokeywords.cpp
@@ -55,7 +55,6 @@
#include <QtQuick/private/qsgdefaultinternalrectanglenode_p.h>
#include <QtQuick/private/qsgdepthstencilbuffer_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p.h>
-#include <QtQuick/private/qsgdistancefieldutil_p.h>
#endif
#include <QtQuick/private/qsggeometry_p.h>
#include <QtQuick/private/qsgnode_p.h>
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..b628255a3d
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.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..d64527cc0f
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ property int value: 50
+ property int maximumValue: 99
+ property alias label: label.text
+ 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..9e65290d84
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ 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..833fef0a81
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ id: root
+ width: 400
+ 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
+ }
+ }
+ }
+ }
+}
+
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..c8fe6052fb
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp
@@ -0,0 +1,475 @@
+/****************************************************************************
+**
+** 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();
+
+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);
+}
+
+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..5446377f9c
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ width: 1024; height: 600
+ color: "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).
+ PinchHandler {
+ id: pinch3
+ objectName: "3-finger pinch"
+ target: container
+ minimumPointCount: 3
+ minimumScale: 0.1
+ maximumScale: 10
+ }
+ DragHandler {
+ id: dragHandler
+ objectName: "DragHandler"
+ target: container
+ }
+ }
+ }
+}
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..09a3c36b6d
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp
@@ -0,0 +1,261 @@
+/****************************************************************************
+**
+** 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);
+// QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
+
+ // Start moving
+ // DragHandler gets 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;
+// QCOMPARE(tp.at(0)->property("pressed").toBool(), !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*>)));
+ 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);
+// QTRY_VERIFY(tp.at(0)->property("pressed").toBool());
+
+ // 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 doesn't
+ 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(), true);
+ QCOMPARE(tp.at(1)->property("pressed").toBool(), true);
+ QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
+ QCOMPARE(mptaPressedSpy.count(), 1);
+ QTRY_COMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch);
+ QVERIFY(pinch->active());
+
+ // Move some more: PinchHandler 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(tp.at(0)->property("pressed").toBool(), false); // TODO fails; MPTA doesn't know it lost its grabs
+// QCOMPARE(tp.at(1)->property("pressed").toBool(), false);
+// QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
+ }
+ qCDebug(lcPointerTests) << "scale" << pinch->scale() << "rot" << pinch->rotation();
+ QTRY_VERIFY(pinch->rotation() > 10);
+ QVERIFY(pinch->scale() > 1);
+
+ // Press one more point (pinkie finger)
+ QPoint p4 = mpta->mapToScene(QPointF(300, 200)).toPoint();
+ touch.stationary(1).stationary(2).stationary(3).press(4, p4).commit();
+ // MPTA grabs p4 (which is at index 3)
+// QTRY_COMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
+ // PinchHandler wantsPointerEvent declines, because it wants exactly 3 touchpoints, and there are now 4.
+ // Move some more... MPTA reacts, in spite of not grabbing all the points
+ 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();
+// QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr);
+// QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), nullptr);
+// QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), nullptr);
+// 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
+ touch.stationary(1).stationary(2).stationary(3).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);
+ }
+
+ // 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, and MPTA should react... but it doesn't (TODO?)
+ for (int i = 0; i < 8; ++i) {
+ p1 -= QPoint(4, 4);
+ p2 += QPoint(4, 4);
+ touch.move(1, p1).move(2, p2).commit();
+ QTest::qWait(100);
+ }
+
+ touch.release(1, p1).release(2, p2).release(3, p3).commit();
+ QQuickTouchUtils::flush(window);
+// QTRY_COMPARE(mptaReleasedSpy.count(), 1); // all points at once
+}
+
+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..315da81d04
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ 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..2224276819
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.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..52555bc8d7
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Item {
+ id: root
+ 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..5ed9bd1523
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+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..bcb16f54cb
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ 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/qquickdraghandler.pro b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro
new file mode 100644
index 0000000000..b50fe5ca6f
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro
@@ -0,0 +1,15 @@
+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/foo.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..cd60be6a4c
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp
@@ -0,0 +1,405 @@
+/****************************************************************************
+**
+** 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/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();
+
+private:
+ void createView(QScopedPointer<QQuickView> &window, const char *fileName);
+ 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);
+}
+
+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} };
+ // TOOD these fail
+// 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();
+}
+
+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..cc39dd54f7
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp
@@ -0,0 +1,579 @@
+/****************************************************************************
+**
+** 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 <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(), 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::mouseMove(window, p1);
+ QCOMPARE(eventItem1->eventList.size(), 3);
+ QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
+ QCOMPARE(eventItem1->eventList.size(), 5);
+ 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::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..63c30b4d34
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import Qt.labs.handlers 1.0
+
+Rectangle {
+ 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..2224276819
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.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/buttons.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml
new file mode 100644
index 0000000000..ca1aba71fb
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** 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:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.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..d7eda5e19c
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp
@@ -0,0 +1,591 @@
+/****************************************************************************
+**
+** 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();
+
+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());
+}
+
+QTEST_MAIN(tst_TapHandler)
+
+#include "tst_qquicktaphandler.moc"
+
diff --git a/tests/auto/quick/qquickapplication/data/tst_platformname.qml b/tests/auto/quick/qquickapplication/data/tst_platformname.qml
new file mode 100644
index 0000000000..1bcd66ac8d
--- /dev/null
+++ b/tests/auto/quick/qquickapplication/data/tst_platformname.qml
@@ -0,0 +1,6 @@
+import QtQuick 2.0;
+
+Item {
+ id: root;
+ property string platformName: Qt.platform.pluginName
+}
diff --git a/tests/auto/quick/qquickapplication/qquickapplication.pro b/tests/auto/quick/qquickapplication/qquickapplication.pro
index c47f5472b7..00b5bb3a18 100644
--- a/tests/auto/quick/qquickapplication/qquickapplication.pro
+++ b/tests/auto/quick/qquickapplication/qquickapplication.pro
@@ -3,7 +3,8 @@ TARGET = tst_qquickapplication
macx:CONFIG -= app_bundle
SOURCES += tst_qquickapplication.cpp
-OTHER_FILES += data/tst_displayname.qml
+OTHER_FILES += data/tst_displayname.qml \
+ data/tst_platformname.qml
include (../../shared/util.pri)
diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp
index d780b91260..e428a1fc6e 100644
--- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp
+++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp
@@ -53,6 +53,7 @@ private slots:
void styleHints();
void cleanup();
void displayName();
+ void platformName();
private:
QQmlEngine engine;
@@ -264,6 +265,24 @@ void tst_qquickapplication::displayName()
QCOMPARE(QGuiApplication::applicationDisplayName(), name[2]);
}
+void tst_qquickapplication::platformName()
+{
+ // Set up QML component
+ QQmlComponent component(&engine, testFileUrl("tst_platformname.qml"));
+ QQuickItem *item = qobject_cast<QQuickItem *>(component.create());
+ QVERIFY(item);
+ QQuickView view;
+ item->setParentItem(view.rootObject());
+
+ // Get native platform name
+ QString guiApplicationPlatformName = QGuiApplication::platformName();
+ QVERIFY(!guiApplicationPlatformName.isEmpty());
+
+ // Get platform name from QML component and verify it's same
+ QString qmlPlatformName = qvariant_cast<QString>(item->property("platformName"));
+ QCOMPARE(qmlPlatformName, guiApplicationPlatformName);
+}
+
QTEST_MAIN(tst_qquickapplication)
#include "tst_qquickapplication.moc"
diff --git a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
index 81187f3c2f..902920babc 100644
--- a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
+++ b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
@@ -18,6 +18,8 @@ Flickable {
height: 300
color: "yellow"
+ objectName: "yellowRect"
+
Flickable {
id: inner
objectName: "innerFlickable"
@@ -30,6 +32,7 @@ Flickable {
Rectangle {
anchors.fill: parent
anchors.margins: 100
+ objectName: "blueRect"
color: "blue"
}
MouseArea {
diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp
index f8277c6895..0bb913d104 100644
--- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp
+++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp
@@ -63,7 +63,13 @@ public:
, touchReleases(0)
, ungrabs(0)
, m_active(false)
- { }
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ setAcceptTouchEvents(true);
+#else
+ setAcceptedMouseButtons(Qt::LeftButton); // not really, but we want touch events
+#endif
+ }
QPointF pos() const { return m_pos; }
@@ -2216,6 +2222,7 @@ Q_DECLARE_METATYPE(QQuickFlickable::BoundsBehavior)
void tst_qquickflickable::overshoot()
{
QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior);
+ QFETCH(int, boundsMovement);
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("overshoot.qml"));
@@ -2232,6 +2239,7 @@ void tst_qquickflickable::overshoot()
QCOMPARE(flickable->contentHeight(), 400.0);
flickable->setBoundsBehavior(boundsBehavior);
+ flickable->setBoundsMovement(QQuickFlickable::BoundsMovement(boundsMovement));
// drag past the beginning
QTest::mousePress(window.data(), Qt::LeftButton, 0, QPoint(10, 10));
@@ -2240,23 +2248,30 @@ void tst_qquickflickable::overshoot()
QTest::mouseMove(window.data(), QPoint(40, 40));
QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(50, 50));
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) && (boundsBehavior & QQuickFlickable::DragOverBounds)) {
+ QVERIFY(flickable->property("minContentX").toReal() < 0.0);
+ QVERIFY(flickable->property("minContentY").toReal() < 0.0);
+ } else {
+ QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
+ QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
+ }
if (boundsBehavior & QQuickFlickable::DragOverBounds) {
- QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
QVERIFY(flickable->property("minHorizontalOvershoot").toReal() < 0.0);
- QCOMPARE(flickable->property("minContentY").toReal(),
- flickable->property("minVerticalOvershoot").toReal());
- QCOMPARE(flickable->property("minContentX").toReal(),
- flickable->property("minHorizontalOvershoot").toReal());
+ QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
} else {
- QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
- QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
- QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
+ }
+ if (bool(boundsMovement == QQuickFlickable::FollowBoundsBehavior) == bool(boundsBehavior & QQuickFlickable::DragOverBounds)) {
+ QCOMPARE(flickable->property("minContentX").toReal(),
+ flickable->property("minHorizontalOvershoot").toReal());
+ QCOMPARE(flickable->property("minContentY").toReal(),
+ flickable->property("minVerticalOvershoot").toReal());
}
- QCOMPARE(flickable->property("maxContentY").toReal(), 0.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 0.0);
- QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxContentY").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
flickable->setContentX(20.0);
flickable->setContentY(20.0);
@@ -2266,23 +2281,30 @@ void tst_qquickflickable::overshoot()
flick(window.data(), QPoint(10, 10), QPoint(50, 50), 100);
QTRY_VERIFY(!flickable->property("flicking").toBool());
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) && (boundsBehavior & QQuickFlickable::OvershootBounds)) {
+ QVERIFY(flickable->property("minContentX").toReal() < 0.0);
+ QVERIFY(flickable->property("minContentY").toReal() < 0.0);
+ } else {
+ QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
+ QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
+ }
if (boundsBehavior & QQuickFlickable::OvershootBounds) {
- QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
QVERIFY(flickable->property("minHorizontalOvershoot").toReal() < 0.0);
- QCOMPARE(flickable->property("minContentY").toReal(),
- flickable->property("minVerticalOvershoot").toReal());
- QCOMPARE(flickable->property("minContentX").toReal(),
- flickable->property("minHorizontalOvershoot").toReal());
+ QVERIFY(flickable->property("minVerticalOvershoot").toReal() < 0.0);
} else {
- QCOMPARE(flickable->property("minContentY").toReal(), 0.0);
- QCOMPARE(flickable->property("minContentX").toReal(), 0.0);
- QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
+ }
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) == (boundsBehavior & QQuickFlickable::OvershootBounds)) {
+ QCOMPARE(flickable->property("minContentX").toReal(),
+ flickable->property("minHorizontalOvershoot").toReal());
+ QCOMPARE(flickable->property("minContentY").toReal(),
+ flickable->property("minVerticalOvershoot").toReal());
}
- QCOMPARE(flickable->property("maxContentY").toReal(), 20.0);
QCOMPARE(flickable->property("maxContentX").toReal(), 20.0);
- QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxContentY").toReal(), 20.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
flickable->setContentX(200.0);
flickable->setContentY(200.0);
@@ -2295,23 +2317,30 @@ void tst_qquickflickable::overshoot()
QTest::mouseMove(window.data(), QPoint(20, 20));
QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(10, 10));
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) && (boundsBehavior & QQuickFlickable::DragOverBounds)) {
+ QVERIFY(flickable->property("maxContentX").toReal() > 200.0);
+ QVERIFY(flickable->property("maxContentX").toReal() > 200.0);
+ } else {
+ QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
+ QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
+ }
if (boundsBehavior & QQuickFlickable::DragOverBounds) {
- QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
QVERIFY(flickable->property("maxHorizontalOvershoot").toReal() > 0.0);
- QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
- flickable->property("maxVerticalOvershoot").toReal());
- QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
- flickable->property("maxHorizontalOvershoot").toReal());
+ QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
} else {
- QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
- QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
- QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
+ }
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) == (boundsBehavior & QQuickFlickable::DragOverBounds)) {
+ QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
+ flickable->property("maxHorizontalOvershoot").toReal());
+ QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
+ flickable->property("maxVerticalOvershoot").toReal());
}
- QCOMPARE(flickable->property("minContentY").toReal(), 200.0);
QCOMPARE(flickable->property("minContentX").toReal(), 200.0);
- QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minContentY").toReal(), 200.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
flickable->setContentX(180.0);
flickable->setContentY(180.0);
@@ -2321,37 +2350,59 @@ void tst_qquickflickable::overshoot()
flick(window.data(), QPoint(50, 50), QPoint(10, 10), 100);
QTRY_VERIFY(!flickable->property("flicking").toBool());
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) && (boundsBehavior & QQuickFlickable::OvershootBounds)) {
+ QVERIFY(flickable->property("maxContentX").toReal() > 200.0);
+ QVERIFY(flickable->property("maxContentY").toReal() > 200.0);
+ } else {
+ QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
+ QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
+ }
if (boundsBehavior & QQuickFlickable::OvershootBounds) {
- QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
QVERIFY(flickable->property("maxHorizontalOvershoot").toReal() > 0.0);
- QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
- flickable->property("maxVerticalOvershoot").toReal());
- QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
- flickable->property("maxHorizontalOvershoot").toReal());
+ QVERIFY(flickable->property("maxVerticalOvershoot").toReal() > 0.0);
} else {
- QCOMPARE(flickable->property("maxContentY").toReal(), 200.0);
- QCOMPARE(flickable->property("maxContentX").toReal(), 200.0);
- QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
QCOMPARE(flickable->property("maxHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("maxVerticalOvershoot").toReal(), 0.0);
+ }
+ if ((boundsMovement == QQuickFlickable::FollowBoundsBehavior) == (boundsBehavior & QQuickFlickable::OvershootBounds)) {
+ QCOMPARE(flickable->property("maxContentX").toReal() - 200.0,
+ flickable->property("maxHorizontalOvershoot").toReal());
+ QCOMPARE(flickable->property("maxContentY").toReal() - 200.0,
+ flickable->property("maxVerticalOvershoot").toReal());
}
- QCOMPARE(flickable->property("minContentY").toReal(), 180.0);
QCOMPARE(flickable->property("minContentX").toReal(), 180.0);
- QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minContentY").toReal(), 180.0);
QCOMPARE(flickable->property("minHorizontalOvershoot").toReal(), 0.0);
+ QCOMPARE(flickable->property("minVerticalOvershoot").toReal(), 0.0);
}
void tst_qquickflickable::overshoot_data()
{
QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior");
-
- QTest::newRow("StopAtBounds")
- << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds);
- QTest::newRow("DragOverBounds")
- << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds);
- QTest::newRow("OvershootBounds")
- << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds);
- QTest::newRow("DragAndOvershootBounds")
- << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds);
+ QTest::addColumn<int>("boundsMovement");
+
+ QTest::newRow("StopAtBounds,FollowBoundsBehavior")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds)
+ << int(QQuickFlickable::FollowBoundsBehavior);
+ QTest::newRow("DragOverBounds,FollowBoundsBehavior")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds)
+ << int(QQuickFlickable::FollowBoundsBehavior);
+ QTest::newRow("OvershootBounds,FollowBoundsBehavior")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds)
+ << int(QQuickFlickable::FollowBoundsBehavior);
+ QTest::newRow("DragAndOvershootBounds,FollowBoundsBehavior")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds)
+ << int(QQuickFlickable::FollowBoundsBehavior);
+
+ QTest::newRow("DragOverBounds,StopAtBounds")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds)
+ << int(QQuickFlickable::StopAtBounds);
+ QTest::newRow("OvershootBounds,StopAtBounds")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds)
+ << int(QQuickFlickable::StopAtBounds);
+ QTest::newRow("DragAndOvershootBounds,StopAtBounds")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds)
+ << int(QQuickFlickable::StopAtBounds);
}
void tst_qquickflickable::overshoot_reentrant()
diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp
index 115fe53430..a2a65aa803 100644
--- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp
+++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp
@@ -91,6 +91,8 @@ private slots:
void sourceSizeChanges();
void correctStatus();
void highdpi();
+ void highDpiFillModesAndSizes_data();
+ void highDpiFillModesAndSizes();
void hugeImages();
private:
@@ -971,6 +973,65 @@ void tst_qquickimage::highdpi()
delete obj;
}
+void tst_qquickimage::highDpiFillModesAndSizes_data()
+{
+ QTest::addColumn<QQuickImage::FillMode>("fillMode");
+ QTest::addColumn<qreal>("expectedHeightAfterSettingWidthTo100");
+ QTest::addColumn<qreal>("expectedImplicitHeightAfterSettingWidthTo100");
+ QTest::addColumn<qreal>("expectedPaintedWidthAfterSettingWidthTo100");
+ QTest::addColumn<qreal>("expectedPaintedHeightAfterSettingWidthTo100");
+
+ QTest::addRow("Stretch") << QQuickImage::Stretch << 150.0 << 150.0 << 100.0 << 150.0;
+ QTest::addRow("PreserveAspectFit") << QQuickImage::PreserveAspectFit << 100.0 << 100.0 << 100.0 << 100.0;
+ QTest::addRow("PreserveAspectCrop") << QQuickImage::PreserveAspectCrop << 150.0 << 150.0 << 150.0 << 150.0;
+ QTest::addRow("Tile") << QQuickImage::Tile << 150.0 << 150.0 << 100.0 << 150.0;
+ QTest::addRow("TileVertically") << QQuickImage::TileVertically << 150.0 << 150.0 << 100.0 << 150.0;
+ QTest::addRow("TileHorizontally") << QQuickImage::TileHorizontally << 150.0 << 150.0 << 100.0 << 150.0;
+ QTest::addRow("Pad") << QQuickImage::Pad << 150.0 << 150.0 << 150.0 << 150.0;
+}
+
+void tst_qquickimage::highDpiFillModesAndSizes()
+{
+ QFETCH(QQuickImage::FillMode, fillMode);
+ QFETCH(qreal, expectedHeightAfterSettingWidthTo100);
+ QFETCH(qreal, expectedImplicitHeightAfterSettingWidthTo100);
+ QFETCH(qreal, expectedPaintedWidthAfterSettingWidthTo100);
+ QFETCH(qreal, expectedPaintedHeightAfterSettingWidthTo100);
+
+ QString componentStr = "import QtQuick 2.0\nImage { source: srcImage; }";
+ QQmlComponent component(&engine);
+ component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
+
+ engine.rootContext()->setContextProperty("srcImage", testFileUrl("heart-highdpi@2x.png"));
+
+ QScopedPointer<QQuickImage> image(qobject_cast<QQuickImage*>(component.create()));
+ QVERIFY(image);
+ QCOMPARE(image->width(), 150.0);
+ QCOMPARE(image->height(), 150.0);
+ QCOMPARE(image->paintedWidth(), 150.0);
+ QCOMPARE(image->paintedHeight(), 150.0);
+ QCOMPARE(image->implicitWidth(), 150.0);
+ QCOMPARE(image->implicitHeight(), 150.0);
+ QCOMPARE(image->paintedWidth(), 150.0);
+ QCOMPARE(image->paintedHeight(), 150.0);
+
+ // The implicit size should not change when setting any fillMode here.
+ image->setFillMode(fillMode);
+ QCOMPARE(image->fillMode(), fillMode);
+ QCOMPARE(image->implicitWidth(), 150.0);
+ QCOMPARE(image->implicitHeight(), 150.0);
+ QCOMPARE(image->paintedWidth(), 150.0);
+ QCOMPARE(image->paintedHeight(), 150.0);
+
+ image->setWidth(100.0);
+ QCOMPARE(image->width(), 100.0);
+ QCOMPARE(image->height(), expectedHeightAfterSettingWidthTo100);
+ QCOMPARE(image->implicitWidth(), 150.0);
+ QCOMPARE(image->implicitHeight(), expectedImplicitHeightAfterSettingWidthTo100);
+ QCOMPARE(image->paintedWidth(), expectedPaintedWidthAfterSettingWidthTo100);
+ QCOMPARE(image->paintedHeight(), expectedPaintedHeightAfterSettingWidthTo100);
+}
+
void tst_qquickimage::hugeImages()
{
QQuickView view;
diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp
index 10a3a0bfa8..f4434d9d3f 100644
--- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp
+++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp
@@ -49,7 +49,12 @@ public:
: QQuickItem(parent), focused(false), pressCount(0), releaseCount(0)
, wheelCount(0), acceptIncomingTouchEvents(true)
, touchEventReached(false), timestamp(0)
- , lastWheelEventPos(0, 0), lastWheelEventGlobalPos(0, 0) {}
+ , lastWheelEventPos(0, 0), lastWheelEventGlobalPos(0, 0)
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ setAcceptTouchEvents(true);
+#endif
+ }
bool focused;
int pressCount;
diff --git a/tests/auto/quick/qquicklistview/randomsortmodel.cpp b/tests/auto/quick/qquicklistview/randomsortmodel.cpp
index 7affb182c0..7c4bd5111c 100644
--- a/tests/auto/quick/qquicklistview/randomsortmodel.cpp
+++ b/tests/auto/quick/qquicklistview/randomsortmodel.cpp
@@ -27,6 +27,7 @@
****************************************************************************/
#include "randomsortmodel.h"
+#include <QRandomGenerator>
RandomSortModel::RandomSortModel(QObject* parent):
QAbstractListModel(parent)
@@ -73,14 +74,14 @@ QVariant RandomSortModel::data(const QModelIndex& index, int role) const
void RandomSortModel::randomize()
{
- const int row = qrand() % mData.count();
+ const int row = QRandomGenerator::bounded(mData.count());
int random;
bool exists = false;
// Make sure we won't end up with two items with the same weight, as that
// would make unit-testing much harder
do {
exists = false;
- random = qrand() % (mData.count() * 10);
+ random = QRandomGenerator::bounded(mData.count() * 10);
QList<QPair<QString, int> >::ConstIterator iter, end;
for (iter = mData.constBegin(), end = mData.constEnd(); iter != end; ++iter) {
if ((*iter).second == random) {
diff --git a/tests/auto/quick/qquickloader/qquickloader.pro b/tests/auto/quick/qquickloader/qquickloader.pro
index 06fb75793d..c754c78bec 100644
--- a/tests/auto/quick/qquickloader/qquickloader.pro
+++ b/tests/auto/quick/qquickloader/qquickloader.pro
@@ -2,7 +2,6 @@ CONFIG += testcase
TARGET = tst_qquickloader
macx:CONFIG -= app_bundle
-INCLUDEPATH += ../../shared/
HEADERS += ../../shared/testhttpserver.h
SOURCES += tst_qquickloader.cpp \
diff --git a/tests/auto/quick/qquickpixmapcache/qquickpixmapcache.pro b/tests/auto/quick/qquickpixmapcache/qquickpixmapcache.pro
index 185eb2c213..52b798e829 100644
--- a/tests/auto/quick/qquickpixmapcache/qquickpixmapcache.pro
+++ b/tests/auto/quick/qquickpixmapcache/qquickpixmapcache.pro
@@ -5,7 +5,6 @@ macx:CONFIG -= app_bundle
SOURCES += tst_qquickpixmapcache.cpp \
../../shared/testhttpserver.cpp
HEADERS += ../../shared/testhttpserver.h
-INCLUDEPATH += ../../shared/
include (../../shared/util.pri)
diff --git a/tests/auto/quick/qquickrectangle/data/gradient-multiple.qml b/tests/auto/quick/qquickrectangle/data/gradient-multiple.qml
new file mode 100644
index 0000000000..d58c857008
--- /dev/null
+++ b/tests/auto/quick/qquickrectangle/data/gradient-multiple.qml
@@ -0,0 +1,30 @@
+import QtQuick 2.0
+
+Item {
+ property alias firstRectangle: r1
+ property alias secondRectangle: r2
+ Rectangle {
+ id: r1
+ gradient: someObject.someGradient
+ anchors.fill: parent
+ }
+ Rectangle {
+ id: r2
+ gradient: someObject.someGradient
+ anchors.fill: parent
+ }
+
+ function changeGradient() {
+ firstStop.color = "red"
+ secondStop.color = "blue"
+ }
+
+ QtObject {
+ id: someObject
+ property Gradient someGradient: Gradient {
+ GradientStop { id: firstStop; position: 0.0; color: "gray" }
+ GradientStop { id: secondStop; position: 1.0; color: "white" }
+ }
+ }
+}
+
diff --git a/tests/auto/quick/qquickrectangle/data/gradient-separate.qml b/tests/auto/quick/qquickrectangle/data/gradient-separate.qml
new file mode 100644
index 0000000000..8ae3f3296b
--- /dev/null
+++ b/tests/auto/quick/qquickrectangle/data/gradient-separate.qml
@@ -0,0 +1,20 @@
+import QtQuick 2.0
+
+Rectangle {
+
+ function changeGradient() {
+ firstStop.color = "red"
+ secondStop.color = "blue"
+ }
+
+ QtObject {
+ id: someObject
+ property Gradient someGradient: Gradient {
+ GradientStop { id: firstStop; position: 0.0; color: "gray" }
+ GradientStop { id: secondStop; position: 1.0; color: "white" }
+ }
+ }
+
+ gradient: someObject.someGradient
+}
+
diff --git a/tests/auto/quick/qquickrectangle/tst_qquickrectangle.cpp b/tests/auto/quick/qquickrectangle/tst_qquickrectangle.cpp
index 65c7e387a0..0d79592e37 100644
--- a/tests/auto/quick/qquickrectangle/tst_qquickrectangle.cpp
+++ b/tests/auto/quick/qquickrectangle/tst_qquickrectangle.cpp
@@ -32,6 +32,7 @@
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickview.h>
+#include <private/qquickitem_p.h>
#include <private/qquickrectangle_p.h>
#include "../../shared/util.h"
@@ -46,6 +47,8 @@ private slots:
void color();
void gradient();
void gradient_border();
+ void gradient_separate();
+ void gradient_multiple();
void antialiasing();
private:
@@ -111,6 +114,62 @@ void tst_qquickrectangle::gradient_border()
QVERIFY(QTest::qWaitForWindowExposed(&view));
}
+// A gradient not defined inline with the Rectangle using it should still change
+// that Rectangle.
+void tst_qquickrectangle::gradient_separate()
+{
+ QQuickView view;
+ view.setSource(testFileUrl("gradient-separate.qml"));
+ view.show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QQuickRectangle *rect = qobject_cast<QQuickRectangle*>(view.rootObject());
+ QVERIFY(rect);
+
+ // Start off clean
+ QQuickItemPrivate *rectPriv = QQuickItemPrivate::get(rect);
+ bool isDirty = rectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ QVERIFY(!isDirty);
+
+ QMetaObject::invokeMethod(rect, "changeGradient");
+
+ // Changing the gradient should have scheduled an update of the item.
+ isDirty = rectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ QVERIFY(isDirty);
+}
+
+// When a gradient is changed, every Rectangle connected to it must update.
+void tst_qquickrectangle::gradient_multiple()
+{
+ QQuickView view;
+ view.setSource(testFileUrl("gradient-multiple.qml"));
+ view.show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QQuickRectangle *firstRect = qobject_cast<QQuickRectangle*>(view.rootObject()->property("firstRectangle").value<QObject*>());
+ QQuickRectangle *secondRect = qobject_cast<QQuickRectangle*>(view.rootObject()->property("secondRectangle").value<QObject*>());
+ QVERIFY(firstRect);
+ QVERIFY(secondRect);
+
+ // Start off clean
+ QQuickItemPrivate *firstRectPriv = QQuickItemPrivate::get(firstRect);
+ QQuickItemPrivate *secondRectPriv = QQuickItemPrivate::get(secondRect);
+ bool firstIsDirty = firstRectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ bool secondIsDirty = secondRectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ QVERIFY(!firstIsDirty);
+ QVERIFY(!secondIsDirty);
+
+ QMetaObject::invokeMethod(view.rootObject(), "changeGradient");
+
+ // Changing the gradient should have scheduled an update of both items
+ firstIsDirty = firstRectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ secondIsDirty = secondRectPriv->dirtyAttributes & QQuickItemPrivate::Content;
+ QVERIFY(firstIsDirty);
+ QVERIFY(secondIsDirty);
+}
+
void tst_qquickrectangle::antialiasing()
{
QQmlComponent component(&engine);
diff --git a/tests/auto/quick/qquickscreen/tst_qquickscreen.cpp b/tests/auto/quick/qquickscreen/tst_qquickscreen.cpp
index 26b687a4a6..0a3796402a 100644
--- a/tests/auto/quick/qquickscreen/tst_qquickscreen.cpp
+++ b/tests/auto/quick/qquickscreen/tst_qquickscreen.cpp
@@ -113,6 +113,9 @@ void tst_qquickscreen::fullScreenList()
QQuickScreenInfo *info = qobject_cast<QQuickScreenInfo *>(screensArray.property(i).toQObject());
QVERIFY(info != nullptr);
QCOMPARE(screenList[i]->name(), info->name());
+ QCOMPARE(screenList[i]->manufacturer(), info->manufacturer());
+ QCOMPARE(screenList[i]->model(), info->model());
+ QCOMPARE(screenList[i]->serialNumber(), info->serialNumber());
QCOMPARE(screenList[i]->size().width(), info->width());
QCOMPARE(screenList[i]->size().height(), info->height());
QCOMPARE(screenList[i]->availableVirtualGeometry().width(), info->desktopAvailableWidth());
diff --git a/tests/auto/quick/qquickshape/BLACKLIST b/tests/auto/quick/qquickshape/BLACKLIST
new file mode 100644
index 0000000000..d0ebc2f505
--- /dev/null
+++ b/tests/auto/quick/qquickshape/BLACKLIST
@@ -0,0 +1,8 @@
+[render]
+osx ci
+[renderWithMultipleSp]
+osx ci
+[radialGrad]
+osx ci
+[conicalGrad]
+osx ci
diff --git a/tests/auto/quick/qquickshape/data/pathitem1.qml b/tests/auto/quick/qquickshape/data/pathitem1.qml
new file mode 100644
index 0000000000..29ca67b0bb
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem1.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Shape {
+}
diff --git a/tests/auto/quick/qquickshape/data/pathitem2.qml b/tests/auto/quick/qquickshape/data/pathitem2.qml
new file mode 100644
index 0000000000..a255a37af6
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem2.qml
@@ -0,0 +1,7 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Shape {
+ ShapePath { }
+ ShapePath { }
+}
diff --git a/tests/auto/quick/qquickshape/data/pathitem3.png b/tests/auto/quick/qquickshape/data/pathitem3.png
new file mode 100644
index 0000000000..a8b4483c96
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem3.png
Binary files differ
diff --git a/tests/auto/quick/qquickshape/data/pathitem3.qml b/tests/auto/quick/qquickshape/data/pathitem3.qml
new file mode 100644
index 0000000000..8328f2fc33
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem3.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Item {
+ width: 200
+ height: 150
+
+ Shape {
+ vendorExtensionsEnabled: false
+ objectName: "pathItem"
+ anchors.fill: parent
+
+ ShapePath {
+ strokeWidth: 4
+ strokeColor: "red"
+ fillGradient: LinearGradient {
+ x1: 20; y1: 20
+ x2: 180; y2: 130
+ GradientStop { position: 0; color: "blue" }
+ GradientStop { position: 0.2; color: "green" }
+ GradientStop { position: 0.4; color: "red" }
+ GradientStop { position: 0.6; color: "yellow" }
+ GradientStop { position: 1; color: "cyan" }
+ }
+ strokeStyle: ShapePath.DashLine
+ dashPattern: [ 1, 4 ]
+ startX: 20; startY: 20
+ PathLine { x: 180; y: 130 }
+ PathLine { x: 20; y: 130 }
+ PathLine { x: 20; y: 20 }
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickshape/data/pathitem4.png b/tests/auto/quick/qquickshape/data/pathitem4.png
new file mode 100644
index 0000000000..3a988ba249
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem4.png
Binary files differ
diff --git a/tests/auto/quick/qquickshape/data/pathitem4.qml b/tests/auto/quick/qquickshape/data/pathitem4.qml
new file mode 100644
index 0000000000..635113416f
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem4.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Item {
+ width: 200
+ height: 150
+
+ Shape {
+ vendorExtensionsEnabled: false
+ objectName: "pathItem"
+ anchors.fill: parent
+
+ ShapePath {
+ strokeColor: "red"
+ fillColor: "green"
+ startX: 40; startY: 30
+ PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
+ PathLine { x: 150; y: 80 }
+ PathQuad { x: 160; y: 30; controlX: 200; controlY: 80 }
+ }
+
+ ShapePath {
+ strokeWidth: 10
+ fillColor: "transparent"
+ strokeColor: "blue"
+ startX: 40; startY: 30
+ PathCubic { x: 50; y: 80; control1X: 0; control1Y: 80; control2X: 100; control2Y: 100 }
+ }
+
+ ShapePath {
+ fillGradient: LinearGradient {
+ y2: 150
+ GradientStop { position: 0; color: "yellow" }
+ GradientStop { position: 1; color: "green" }
+ }
+
+ startX: 10; startY: 100
+ PathArc {
+ relativeX: 50; y: 100
+ radiusX: 25; radiusY: 25
+ }
+ PathArc {
+ relativeX: 50; y: 100
+ radiusX: 25; radiusY: 35
+ }
+ PathArc {
+ relativeX: 50; y: 100
+ radiusX: 25; radiusY: 60
+ }
+ PathArc {
+ relativeX: 50; y: 100
+ radiusX: 50; radiusY: 120
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickshape/data/pathitem5.png b/tests/auto/quick/qquickshape/data/pathitem5.png
new file mode 100644
index 0000000000..cb5cfd25dc
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem5.png
Binary files differ
diff --git a/tests/auto/quick/qquickshape/data/pathitem5.qml b/tests/auto/quick/qquickshape/data/pathitem5.qml
new file mode 100644
index 0000000000..1bd465d5c0
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem5.qml
@@ -0,0 +1,37 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Item {
+ width: 200
+ height: 150
+
+ Shape {
+ vendorExtensionsEnabled: false
+ objectName: "pathItem"
+ anchors.fill: parent
+
+ ShapePath {
+ strokeWidth: 4
+ strokeColor: "red"
+ fillGradient: RadialGradient {
+ centerX: 100; centerY: 100; centerRadius: 100
+ focalX: 100; focalY: 100; focalRadius: 10
+ GradientStop { position: 0; color: "#ffffff" }
+ GradientStop { position: 0.11; color: "#f9ffa0" }
+ GradientStop { position: 0.13; color: "#f9ff99" }
+ GradientStop { position: 0.14; color: "#f3ff86" }
+ GradientStop { position: 0.49; color: "#93b353" }
+ GradientStop { position: 0.87; color: "#264619" }
+ GradientStop { position: 0.96; color: "#0c1306" }
+ GradientStop { position: 1; color: "#000000" }
+ }
+ fillColor: "blue" // ignored with the gradient set
+ strokeStyle: ShapePath.DashLine
+ dashPattern: [ 1, 4 ]
+ startX: 20; startY: 20
+ PathLine { x: 180; y: 130 }
+ PathLine { x: 20; y: 130 }
+ PathLine { x: 20; y: 20 }
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickshape/data/pathitem6.png b/tests/auto/quick/qquickshape/data/pathitem6.png
new file mode 100644
index 0000000000..d9e53d6c00
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem6.png
Binary files differ
diff --git a/tests/auto/quick/qquickshape/data/pathitem6.qml b/tests/auto/quick/qquickshape/data/pathitem6.qml
new file mode 100644
index 0000000000..fafcc48196
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem6.qml
@@ -0,0 +1,35 @@
+import QtQuick 2.9
+import tst_qquickpathitem 1.0
+
+Item {
+ width: 200
+ height: 150
+
+ Shape {
+ vendorExtensionsEnabled: false
+ objectName: "pathItem"
+ anchors.fill: parent
+
+ ShapePath {
+ strokeWidth: 4
+ strokeColor: "red"
+ fillGradient: ConicalGradient {
+ centerX: 100; centerY: 100; angle: 45
+ GradientStop { position: 0; color: "#00000000" }
+ GradientStop { position: 0.10; color: "#ffe0cc73" }
+ GradientStop { position: 0.17; color: "#ffc6a006" }
+ GradientStop { position: 0.46; color: "#ff600659" }
+ GradientStop { position: 0.72; color: "#ff0680ac" }
+ GradientStop { position: 0.92; color: "#ffb9d9e6" }
+ GradientStop { position: 1.00; color: "#00000000" }
+ }
+ fillColor: "blue" // ignored with the gradient set
+ strokeStyle: ShapePath.DashLine
+ dashPattern: [ 1, 4 ]
+ startX: 20; startY: 20
+ PathLine { x: 180; y: 130 }
+ PathLine { x: 20; y: 130 }
+ PathLine { x: 20; y: 20 }
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickshape/qquickshape.pro b/tests/auto/quick/qquickshape/qquickshape.pro
new file mode 100644
index 0000000000..29c3502b86
--- /dev/null
+++ b/tests/auto/quick/qquickshape/qquickshape.pro
@@ -0,0 +1,35 @@
+CONFIG += testcase
+TARGET = tst_qquickshape
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquickshape.cpp
+
+include (../../shared/util.pri)
+include (../shared/util.pri)
+
+TESTDATA = data/*
+
+HEADERS += \
+ ../../../../src/imports/shapes/qquickshape_p.h \
+ ../../../../src/imports/shapes/qquickshape_p_p.h \
+ ../../../../src/imports/shapes/qquickshapegenericrenderer_p.h \
+ ../../../../src/imports/shapes/qquickshapesoftwarerenderer_p.h
+
+SOURCES += \
+ ../../../../src/imports/shapes/qquickshape.cpp \
+ ../../../../src/imports/shapes/qquickshapegenericrenderer.cpp \
+ ../../../../src/imports/shapes/qquickshapesoftwarerenderer.cpp
+
+qtConfig(opengl) {
+ HEADERS += \
+ ../../../../src/imports/shapes/qquicknvprfunctions_p.h \
+ ../../../../src/imports/shapes/qquicknvprfunctions_p_p.h \
+ ../../../../src/imports/shapes/qquickshapenvprrenderer_p.h
+
+ SOURCES += \
+ ../../../../src/imports/shapes/qquicknvprfunctions.cpp \
+ ../../../../src/imports/shapes/qquickshapenvprrenderer.cpp
+}
+
+QT += core-private gui-private qml-private quick-private testlib
+qtHaveModule(widgets): QT += widgets
diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp
new file mode 100644
index 0000000000..1b5b345d19
--- /dev/null
+++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp
@@ -0,0 +1,288 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlexpression.h>
+#include <QtQml/qqmlincubator.h>
+#include "../../../../src/imports/shapes/qquickshape_p.h"
+
+#include "../../shared/util.h"
+#include "../shared/viewtestutil.h"
+#include "../shared/visualtestutil.h"
+
+using namespace QQuickViewTestUtil;
+using namespace QQuickVisualTestUtil;
+
+class tst_QQuickShape : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_QQuickShape();
+
+private slots:
+ void initValues();
+ void vpInitValues();
+ void basicShape();
+ void changeSignals();
+ void render();
+ void renderWithMultipleSp();
+ void radialGrad();
+ void conicalGrad();
+};
+
+tst_QQuickShape::tst_QQuickShape()
+{
+ // Force the software backend to get reliable rendering results regardless of the hw and drivers.
+ QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
+
+ const char *uri = "tst_qquickpathitem";
+ qmlRegisterType<QQuickShape>(uri, 1, 0, "Shape");
+ qmlRegisterType<QQuickShapePath>(uri, 1, 0, "ShapePath");
+ qmlRegisterUncreatableType<QQuickShapeGradient>(uri, 1, 0, "ShapeGradient", QQuickShapeGradient::tr("ShapeGradient is an abstract base class"));
+ qmlRegisterType<QQuickShapeLinearGradient>(uri, 1, 0, "LinearGradient");
+ qmlRegisterType<QQuickShapeRadialGradient>(uri, 1, 0, "RadialGradient");
+ qmlRegisterType<QQuickShapeConicalGradient>(uri, 1, 0, "ConicalGradient");
+}
+
+void tst_QQuickShape::initValues()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("pathitem1.qml"));
+ QQuickShape *obj = qobject_cast<QQuickShape *>(c.create());
+
+ QVERIFY(obj != nullptr);
+ QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer);
+ QVERIFY(!obj->asynchronous());
+ QVERIFY(obj->vendorExtensionsEnabled());
+ QVERIFY(obj->status() == QQuickShape::Null);
+ auto vps = obj->data();
+ QVERIFY(vps.count(&vps) == 0);
+
+ delete obj;
+}
+
+void tst_QQuickShape::vpInitValues()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("pathitem2.qml"));
+ QQuickShape *obj = qobject_cast<QQuickShape *>(c.create());
+
+ QVERIFY(obj != nullptr);
+ QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer);
+ QVERIFY(!obj->asynchronous());
+ QVERIFY(obj->vendorExtensionsEnabled());
+ QVERIFY(obj->status() == QQuickShape::Null);
+ auto vps = obj->data();
+ QVERIFY(vps.count(&vps) == 2);
+
+ QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(vps.at(&vps, 0));
+ QVERIFY(vp != nullptr);
+ QQmlListReference pathList(vp, "pathElements");
+ QCOMPARE(pathList.count(), 0);
+ QCOMPARE(vp->strokeColor(), QColor(Qt::white));
+ QCOMPARE(vp->strokeWidth(), 1.0f);
+ QCOMPARE(vp->fillColor(), QColor(Qt::white));
+ QCOMPARE(vp->fillRule(), QQuickShapePath::OddEvenFill);
+ QCOMPARE(vp->joinStyle(), QQuickShapePath::BevelJoin);
+ QCOMPARE(vp->miterLimit(), 2);
+ QCOMPARE(vp->capStyle(), QQuickShapePath::SquareCap);
+ QCOMPARE(vp->strokeStyle(), QQuickShapePath::SolidLine);
+ QCOMPARE(vp->dashOffset(), 0.0f);
+ QCOMPARE(vp->dashPattern(), QVector<qreal>() << 4 << 2);
+ QVERIFY(!vp->fillGradient());
+
+ delete obj;
+}
+
+void tst_QQuickShape::basicShape()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem3.qml"));
+ qApp->processEvents();
+
+ QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "pathItem");
+ QVERIFY(obj != nullptr);
+ QQmlListReference list(obj, "data");
+ QCOMPARE(list.count(), 1);
+ QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(list.at(0));
+ QVERIFY(vp != nullptr);
+ QCOMPARE(vp->strokeWidth(), 4.0f);
+ QVERIFY(vp->fillGradient() != nullptr);
+ QCOMPARE(vp->strokeStyle(), QQuickShapePath::DashLine);
+
+ vp->setStrokeWidth(5.0f);
+ QCOMPARE(vp->strokeWidth(), 5.0f);
+
+ QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(vp->fillGradient());
+ QVERIFY(lgrad != nullptr);
+ QCOMPARE(lgrad->spread(), QQuickShapeGradient::PadSpread);
+ QCOMPARE(lgrad->x1(), 20.0f);
+ QQmlListReference stopList(lgrad, "stops");
+ QCOMPARE(stopList.count(), 5);
+ QVERIFY(stopList.at(2) != nullptr);
+
+ QQuickPath *path = vp;
+ QCOMPARE(path->startX(), 20.0f);
+ QQmlListReference pathList(path, "pathElements");
+ QCOMPARE(pathList.count(), 3);
+}
+
+void tst_QQuickShape::changeSignals()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem3.qml"));
+ qApp->processEvents();
+
+ QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "pathItem");
+ QVERIFY(obj != nullptr);
+
+ QSignalSpy asyncPropSpy(obj, SIGNAL(asynchronousChanged()));
+ obj->setAsynchronous(true);
+ obj->setAsynchronous(false);
+ QCOMPARE(asyncPropSpy.count(), 2);
+
+ QQmlListReference list(obj, "data");
+ QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(list.at(0));
+ QVERIFY(vp != nullptr);
+
+ // Verify that VisualPath property changes emit shapePathChanged().
+ QSignalSpy vpChangeSpy(vp, SIGNAL(shapePathChanged()));
+ QSignalSpy strokeColorPropSpy(vp, SIGNAL(strokeColorChanged()));
+ vp->setStrokeColor(Qt::blue);
+ vp->setStrokeWidth(1.0f);
+ QQuickShapeGradient *g = vp->fillGradient();
+ vp->setFillGradient(nullptr);
+ vp->setFillColor(Qt::yellow);
+ vp->setFillRule(QQuickShapePath::WindingFill);
+ vp->setJoinStyle(QQuickShapePath::MiterJoin);
+ vp->setMiterLimit(5);
+ vp->setCapStyle(QQuickShapePath::RoundCap);
+ vp->setDashOffset(10);
+ vp->setDashPattern(QVector<qreal>() << 1 << 2 << 3 << 4);
+ QCOMPARE(strokeColorPropSpy.count(), 1);
+ QCOMPARE(vpChangeSpy.count(), 10);
+
+ // Verify that property changes from Path and its elements bubble up and result in shapePathChanged().
+ QQuickPath *path = vp;
+ path->setStartX(30);
+ QCOMPARE(vpChangeSpy.count(), 11);
+ QQmlListReference pathList(path, "pathElements");
+ qobject_cast<QQuickPathLine *>(pathList.at(1))->setY(200);
+ QCOMPARE(vpChangeSpy.count(), 12);
+
+ // Verify that property changes from the gradient bubble up and result in shapePathChanged().
+ vp->setFillGradient(g);
+ QCOMPARE(vpChangeSpy.count(), 13);
+ QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(g);
+ lgrad->setX2(200);
+ QCOMPARE(vpChangeSpy.count(), 14);
+ QQmlListReference stopList(lgrad, "stops");
+ QCOMPARE(stopList.count(), 5);
+ qobject_cast<QQuickGradientStop *>(stopList.at(1))->setPosition(0.3);
+ QCOMPARE(vpChangeSpy.count(), 15);
+ qobject_cast<QQuickGradientStop *>(stopList.at(1))->setColor(Qt::black);
+ QCOMPARE(vpChangeSpy.count(), 16);
+}
+
+void tst_QQuickShape::render()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem3.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QImage img = window->grabWindow();
+ QVERIFY(!img.isNull());
+
+ QImage refImg(testFileUrl("pathitem3.png").toLocalFile());
+ QVERIFY(!refImg.isNull());
+
+ QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg));
+}
+
+void tst_QQuickShape::renderWithMultipleSp()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem4.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QImage img = window->grabWindow();
+ QVERIFY(!img.isNull());
+
+ QImage refImg(testFileUrl("pathitem4.png").toLocalFile());
+ QVERIFY(!refImg.isNull());
+
+ QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg));
+}
+
+void tst_QQuickShape::radialGrad()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem5.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QImage img = window->grabWindow();
+ QVERIFY(!img.isNull());
+
+ QImage refImg(testFileUrl("pathitem5.png").toLocalFile());
+ QVERIFY(!refImg.isNull());
+
+ QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg));
+}
+
+void tst_QQuickShape::conicalGrad()
+{
+ QScopedPointer<QQuickView> window(createView());
+
+ window->setSource(testFileUrl("pathitem6.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QImage img = window->grabWindow();
+ QVERIFY(!img.isNull());
+
+ QImage refImg(testFileUrl("pathitem6.png").toLocalFile());
+ QVERIFY(!refImg.isNull());
+
+ QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg));
+}
+
+QTEST_MAIN(tst_QQuickShape)
+
+#include "tst_qquickshape.moc"
diff --git a/tests/auto/quick/qquicktext/qquicktext.pro b/tests/auto/quick/qquicktext/qquicktext.pro
index 4f4b77ed7b..f76e8c95b2 100644
--- a/tests/auto/quick/qquicktext/qquicktext.pro
+++ b/tests/auto/quick/qquicktext/qquicktext.pro
@@ -4,7 +4,6 @@ macx:CONFIG -= app_bundle
SOURCES += tst_qquicktext.cpp
-INCLUDEPATH += ../../shared/
HEADERS += ../../shared/testhttpserver.h
SOURCES += ../../shared/testhttpserver.cpp
diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp
index c5fa0e19fa..4e643bb9d9 100644
--- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp
+++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp
@@ -723,6 +723,61 @@ void tst_qquicktext::textFormat()
QCOMPARE(text->textFormat(), QQuickText::AutoText);
QCOMPARE(spy.count(), 2);
}
+
+ {
+ QQmlComponent component(&engine);
+ component.setData("import QtQuick 2.0\n Text { text: \"<b>Hello</b>\" }", QUrl());
+ QScopedPointer<QObject> object(component.create());
+ QQuickText *text = qobject_cast<QQuickText *>(object.data());
+ QVERIFY(text);
+ QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text);
+ QVERIFY(textPrivate);
+
+ QCOMPARE(text->textFormat(), QQuickText::AutoText);
+ QVERIFY(!textPrivate->layout.formats().isEmpty());
+
+ text->setTextFormat(QQuickText::StyledText);
+ QVERIFY(!textPrivate->layout.formats().isEmpty());
+
+ text->setTextFormat(QQuickText::PlainText);
+ QVERIFY(textPrivate->layout.formats().isEmpty());
+
+ text->setTextFormat(QQuickText::AutoText);
+ QVERIFY(!textPrivate->layout.formats().isEmpty());
+ }
+
+ {
+ QQmlComponent component(&engine);
+ component.setData("import QtQuick 2.0\nText { text: \"Hello\"; elide: Text.ElideRight }", QUrl::fromLocalFile(""));
+ QScopedPointer<QObject> object(component.create());
+ QQuickText *text = qobject_cast<QQuickText *>(object.data());
+ QVERIFY(text);
+ QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text);
+ QVERIFY(textPrivate);
+
+ // underline a mnemonic
+ QVector<QTextLayout::FormatRange> formats;
+ QTextLayout::FormatRange range;
+ range.start = 0;
+ range.length = 1;
+ range.format.setFontUnderline(true);
+ formats << range;
+
+ // the mnemonic format should be retained
+ textPrivate->layout.setFormats(formats);
+ text->forceLayout();
+ QCOMPARE(textPrivate->layout.formats(), formats);
+
+ // and carried over to the elide layout
+ text->setWidth(text->implicitWidth() - 1);
+ QVERIFY(textPrivate->elideLayout);
+ QCOMPARE(textPrivate->elideLayout->formats(), formats);
+
+ // but cleared when the text changes
+ text->setText("Changed");
+ QVERIFY(textPrivate->elideLayout);
+ QVERIFY(textPrivate->layout.formats().isEmpty());
+ }
}
//the alignment tests may be trivial o.oa
diff --git a/tests/auto/quick/qquickwindow/BLACKLIST b/tests/auto/quick/qquickwindow/BLACKLIST
new file mode 100644
index 0000000000..bb9f403188
--- /dev/null
+++ b/tests/auto/quick/qquickwindow/BLACKLIST
@@ -0,0 +1,3 @@
+# QTBUG-62177
+[attachedProperty]
+osx
diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
index d358383ecb..c95d9b311e 100644
--- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
+++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
@@ -37,6 +37,7 @@
#include <QtQml/QQmlComponent>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/private/qquickloader_p.h>
+#include <QtQuick/private/qquickmousearea_p.h>
#include "../../shared/util.h"
#include "../shared/visualtestutil.h"
#include "../shared/viewtestutil.h"
@@ -47,6 +48,8 @@
#include <QOpenGLFunctions>
#include <QSGRendererInterface>
+Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
+
struct TouchEventData {
QEvent::Type type;
QWidget *widget;
@@ -140,7 +143,7 @@ class TestTouchItem : public QQuickRectangle
public:
TestTouchItem(QQuickItem *parent = 0)
: QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true),
- mousePressId(0),
+ mousePressCount(0), mouseMoveCount(0),
spinLoopWhenPressed(false), touchEventCount(0)
{
border()->setWidth(1);
@@ -159,9 +162,10 @@ public:
lastMousePos = QPointF();
lastMouseCapabilityFlags = 0;
touchEventCount = 0;
+ mouseMoveCount = 0;
}
- static void clearMousePressCounter()
+ static void clearMouseEventCounters()
{
mousePressNum = mouseMoveNum = mouseReleaseNum = 0;
}
@@ -174,7 +178,8 @@ public:
bool acceptTouchEvents;
bool acceptMouseEvents;
TouchEventData lastEvent;
- int mousePressId;
+ int mousePressCount;
+ int mouseMoveCount;
bool spinLoopWhenPressed;
int touchEventCount;
QVector2D lastVelocity;
@@ -204,7 +209,7 @@ public:
e->ignore();
return;
}
- mousePressId = ++mousePressNum;
+ mousePressCount = ++mousePressNum;
lastMousePos = e->pos();
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
}
@@ -214,7 +219,7 @@ public:
e->ignore();
return;
}
- ++mouseMoveNum;
+ mouseMoveCount = ++mouseMoveNum;
lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e);
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
lastMousePos = e->pos();
@@ -230,10 +235,19 @@ public:
lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
}
- bool childMouseEventFilter(QQuickItem *, QEvent *event) {
- // TODO Is it a bug if a QTouchEvent comes here?
- if (event->type() == QEvent::MouseButtonPress)
- mousePressId = ++mousePressNum;
+ bool childMouseEventFilter(QQuickItem *item, QEvent *e) {
+ qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName();
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ mousePressCount = ++mousePressNum;
+ break;
+ case QEvent::MouseMove:
+ mouseMoveCount = ++mouseMoveNum;
+ break;
+ default:
+ break;
+ }
+
return false;
}
@@ -368,6 +382,7 @@ private slots:
void testHoverChildMouseEventFilter();
void testHoverTimestamp();
+ void test_circleMapItem();
void pointerEventTypeAndPointCount();
@@ -377,6 +392,9 @@ private slots:
void findChild();
+ void testChildMouseEventFilter();
+ void testChildMouseEventFilter_data();
+
private:
QTouchDevice *touchDevice;
QTouchDevice *touchDeviceWithVelocity;
@@ -507,7 +525,7 @@ void tst_qquickwindow::constantUpdatesOnWindow()
void tst_qquickwindow::touchEvent_basic()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -636,7 +654,7 @@ void tst_qquickwindow::touchEvent_basic()
void tst_qquickwindow::touchEvent_propagation()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QFETCH(bool, acceptTouchEvents);
QFETCH(bool, acceptMouseEvents);
@@ -783,7 +801,7 @@ void tst_qquickwindow::touchEvent_propagation_data()
void tst_qquickwindow::touchEvent_cancel()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -817,7 +835,7 @@ void tst_qquickwindow::touchEvent_cancel()
void tst_qquickwindow::touchEvent_reentrant()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -856,7 +874,7 @@ void tst_qquickwindow::touchEvent_reentrant()
void tst_qquickwindow::touchEvent_velocity()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -991,7 +1009,7 @@ void tst_qquickwindow::mouseFromTouch_basic()
// should result in sending mouse events generated from the touch
// with the new event propagation system.
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
window->resize(250, 250);
@@ -1092,7 +1110,7 @@ void tst_qquickwindow::clearWindow()
void tst_qquickwindow::mouseFiltering()
{
- TestTouchItem::clearMousePressCounter();
+ TestTouchItem::clearMouseEventCounters();
QQuickWindow *window = new QQuickWindow;
QScopedPointer<QQuickWindow> cleanup(window);
@@ -1106,6 +1124,11 @@ void tst_qquickwindow::mouseFiltering()
bottomItem->setObjectName("Bottom Item");
bottomItem->setSize(QSizeF(150, 150));
+ TestTouchItem *siblingItem = new TestTouchItem(bottomItem);
+ siblingItem->setObjectName("Sibling of Middle Item");
+ siblingItem->setPosition(QPointF(90, 25));
+ siblingItem->setSize(QSizeF(150, 150));
+
TestTouchItem *middleItem = new TestTouchItem(bottomItem);
middleItem->setObjectName("Middle Item");
middleItem->setPosition(QPointF(50, 50));
@@ -1125,9 +1148,41 @@ void tst_qquickwindow::mouseFiltering()
// 1. middleItem filters event
// 2. bottomItem filters event
// 3. topItem receives event
- QTRY_COMPARE(middleItem->mousePressId, 1);
- QTRY_COMPARE(bottomItem->mousePressId, 2);
- QTRY_COMPARE(topItem->mousePressId, 3);
+ QTRY_COMPARE(middleItem->mousePressCount, 1);
+ QTRY_COMPARE(bottomItem->mousePressCount, 2);
+ QTRY_COMPARE(topItem->mousePressCount, 3);
+ QCOMPARE(siblingItem->mousePressCount, 0);
+
+ QTest::mouseRelease(window, Qt::LeftButton, 0, pos);
+ topItem->clearMouseEventCounters();
+ middleItem->clearMouseEventCounters();
+ bottomItem->clearMouseEventCounters();
+ siblingItem->clearMouseEventCounters();
+
+ // Repeat, but this time have the top item accept the press
+ topItem->acceptMouseEvents = true;
+
+ QTest::mousePress(window, Qt::LeftButton, 0, pos);
+
+ // Mouse filtering propagates down the stack, so the
+ // correct order is
+ // 1. middleItem filters event
+ // 2. bottomItem filters event
+ // 3. topItem receives event
+ QTRY_COMPARE(middleItem->mousePressCount, 1);
+ QTRY_COMPARE(bottomItem->mousePressCount, 2);
+ QTRY_COMPARE(topItem->mousePressCount, 3);
+ QCOMPARE(siblingItem->mousePressCount, 0);
+
+ pos += QPoint(50, 50);
+ QTest::mouseMove(window, pos);
+
+ // The top item has grabbed, so the move goes there, but again
+ // all the ancestors can filter, even when the mouse is outside their bounds
+ QTRY_COMPARE(middleItem->mouseMoveCount, 1);
+ QTRY_COMPARE(bottomItem->mouseMoveCount, 2);
+ QTRY_COMPARE(topItem->mouseMoveCount, 3);
+ QCOMPARE(siblingItem->mouseMoveCount, 0);
// clean up mouse press state for the next tests
QTest::mouseRelease(window, Qt::LeftButton, 0, pos);
@@ -1374,12 +1429,6 @@ void tst_qquickwindow::headless()
if (isGL)
QVERIFY(!window->isSceneGraphInitialized());
}
-#if QT_CONFIG(opengl)
- if (QGuiApplication::platformName() == QLatin1String("windows")
- && QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
- QSKIP("Crashes on Windows/ANGLE, QTBUG-42967");
- }
-#endif
// Destroy the native windowing system buffers
window->destroy();
QVERIFY(!window->handle());
@@ -2536,6 +2585,84 @@ void tst_qquickwindow::testHoverTimestamp()
QCOMPARE(hoverConsumer->hoverTimestamps.last(), 5UL);
}
+class CircleItem : public QQuickRectangle
+{
+public:
+ CircleItem(QQuickItem *parent = 0) : QQuickRectangle(parent) { }
+
+ void setRadius(qreal radius) {
+ const qreal diameter = radius*2;
+ setWidth(diameter);
+ setHeight(diameter);
+ }
+
+ bool childMouseEventFilter(QQuickItem *item, QEvent *event) override
+ {
+ Q_UNUSED(item)
+ if (event->type() == QEvent::MouseButtonPress && !contains(static_cast<QMouseEvent*>(event)->pos())) {
+ // This is an evil hack: in case of items that are not rectangles, we never accept the event.
+ // Instead the events are now delivered to QDeclarativeGeoMapItemBase which doesn't to anything with them.
+ // The map below it still works since it filters events and steals the events at some point.
+ event->setAccepted(false);
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool contains(const QPointF &pos) const override {
+ // returns true if the point is inside the the embedded circle inside the (square) rect
+ const float radius = (float)width()/2;
+ const QVector2D center(radius, radius);
+ const QVector2D dx = QVector2D(pos) - center;
+ const bool ret = dx.lengthSquared() < radius*radius;
+ return ret;
+ }
+};
+
+void tst_qquickwindow::test_circleMapItem()
+{
+ QQuickWindow window;
+
+ window.resize(250, 250);
+ window.setPosition(100, 100);
+ window.setTitle(QTest::currentTestFunction());
+
+ QQuickItem *root = window.contentItem();
+ QQuickMouseArea *mab = new QQuickMouseArea(root);
+ mab->setObjectName("Bottom MouseArea");
+ mab->setSize(QSizeF(100, 100));
+
+ CircleItem *topItem = new CircleItem(root);
+ topItem->setFiltersChildMouseEvents(true);
+ topItem->setColor(Qt::green);
+ topItem->setObjectName("Top Item");
+ topItem->setPosition(QPointF(30, 30));
+ topItem->setRadius(20);
+ QQuickMouseArea *mat = new QQuickMouseArea(topItem);
+ mat->setObjectName("Top Item/MouseArea");
+ mat->setSize(QSizeF(40, 40));
+
+ QSignalSpy bottomSpy(mab, SIGNAL(clicked(QQuickMouseEvent *)));
+ QSignalSpy topSpy(mat, SIGNAL(clicked(QQuickMouseEvent *)));
+
+ window.show();
+ QTest::qWaitForWindowExposed(&window);
+ QTest::qWait(1000);
+
+ QPoint pos(50, 50);
+ QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos);
+
+ QCOMPARE(topSpy.count(), 1);
+ QCOMPARE(bottomSpy.count(), 0);
+
+ // Outside the "Circles" "input area", but on top of the bottomItem rectangle
+ pos = QPoint(66, 66);
+ QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos);
+
+ QCOMPARE(bottomSpy.count(), 1);
+ QCOMPARE(topSpy.count(), 1);
+}
+
void tst_qquickwindow::pointerEventTypeAndPointCount()
{
QPointF localPosition(33, 66);
@@ -2549,20 +2676,18 @@ void tst_qquickwindow::pointerEventTypeAndPointCount()
QQuickPointerMouseEvent pme;
pme.reset(&me);
- QVERIFY(pme.isValid());
QCOMPARE(pme.asMouseEvent(localPosition), &me);
QVERIFY(pme.asPointerMouseEvent());
QVERIFY(!pme.asPointerTouchEvent());
QVERIFY(!pme.asPointerTabletEvent());
// QVERIFY(!pe->asTabletEvent()); // TODO
QCOMPARE(pme.pointCount(), 1);
- QCOMPARE(pme.point(0)->scenePos(), scenePosition);
+ QCOMPARE(pme.point(0)->scenePosition(), scenePosition);
QCOMPARE(pme.asMouseEvent(localPosition)->localPos(), localPosition);
QCOMPARE(pme.asMouseEvent(localPosition)->screenPos(), screenPosition);
QQuickPointerTouchEvent pte;
pte.reset(&te);
- QVERIFY(pte.isValid());
QCOMPARE(pte.asTouchEvent(), &te);
QVERIFY(!pte.asPointerMouseEvent());
QVERIFY(pte.asPointerTouchEvent());
@@ -2882,6 +3007,365 @@ void tst_qquickwindow::findChild()
QCOMPARE(window.contentItem()->findChild<QObject *>("contentItemChild"), contentItemChild);
}
+class DeliveryRecord : public QPair<QString, QString>
+{
+public:
+ DeliveryRecord(const QString &filter, const QString &receiver) : QPair(filter, receiver) { }
+ DeliveryRecord(const QString &receiver) : QPair(QString(), receiver) { }
+ DeliveryRecord() : QPair() { }
+ QString toString() const {
+ if (second.isEmpty())
+ return QLatin1String("Delivery(no receiver)");
+ else if (first.isEmpty())
+ return QString(QLatin1String("Delivery(to '%1')")).arg(second);
+ else
+ return QString(QLatin1String("Delivery('%1' filtering for '%2')")).arg(first).arg(second);
+ }
+};
+
+Q_DECLARE_METATYPE(DeliveryRecord)
+
+QDebug operator<<(QDebug dbg, const DeliveryRecord &pair)
+{
+ dbg << pair.toString();
+ return dbg;
+}
+
+typedef QVector<DeliveryRecord> DeliveryRecordVector;
+
+class EventItem : public QQuickRectangle
+{
+ Q_OBJECT
+public:
+ EventItem(QQuickItem *parent)
+ : QQuickRectangle(parent)
+ , m_eventAccepts(true)
+ , m_filterReturns(true)
+ , m_filterAccepts(true)
+ , m_filterNotPreAccepted(false)
+ {
+ QSizeF psize(parent->width(), parent->height());
+ psize -= QSizeF(20, 20);
+ setWidth(psize.width());
+ setHeight(psize.height());
+ setPosition(QPointF(10, 10));
+ }
+
+ void setFilterReturns(bool filterReturns) { m_filterReturns = filterReturns; }
+ void setFilterAccepts(bool accepts) { m_filterAccepts = accepts; }
+ void setEventAccepts(bool accepts) { m_eventAccepts = accepts; }
+
+ /*!
+ * \internal
+ *
+ * returns false if any of the calls to childMouseEventFilter had the wrong
+ * preconditions. If all calls had the expected precondition, returns true.
+ */
+ bool testFilterPreConditions() const { return !m_filterNotPreAccepted; }
+ static QVector<DeliveryRecord> &deliveryList() { return m_deliveryList; }
+ static QSet<QEvent::Type> &includedEventTypes()
+ {
+ if (m_includedEventTypes.isEmpty())
+ m_includedEventTypes << QEvent::MouseButtonPress;
+ return m_includedEventTypes;
+ }
+ static void setExpectedDeliveryList(const QVector<DeliveryRecord> &v) { m_expectedDeliveryList = v; }
+
+protected:
+ bool childMouseEventFilter(QQuickItem *i, QEvent *e) override
+ {
+ appendEvent(this, i, e);
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ if (!e->isAccepted())
+ m_filterNotPreAccepted = true;
+ e->setAccepted(m_filterAccepts);
+ // qCDebug(lcTests) << objectName() << i->objectName();
+ return m_filterReturns;
+ default:
+ break;
+ }
+ return QQuickRectangle::childMouseEventFilter(i, e);
+ }
+
+ bool event(QEvent *e) override
+ {
+ appendEvent(nullptr, this, e);
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ // qCDebug(lcTests) << objectName();
+ e->setAccepted(m_eventAccepts);
+ return true;
+ default:
+ break;
+ }
+ return QQuickRectangle::event(e);
+ }
+
+private:
+ static void appendEvent(QQuickItem *filter, QQuickItem *receiver, QEvent *event) {
+ if (includedEventTypes().contains(event->type())) {
+ auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString());
+ int i = m_deliveryList.count();
+ if (m_expectedDeliveryList.count() > i && m_expectedDeliveryList[i] == record)
+ qCDebug(lcTests).noquote().nospace() << i << ": " << record;
+ else
+ qCDebug(lcTests).noquote().nospace() << i << ": " << record
+ << ", expected " << (m_expectedDeliveryList.count() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---";
+ m_deliveryList << record;
+ }
+ }
+ bool m_eventAccepts;
+ bool m_filterReturns;
+ bool m_filterAccepts;
+ bool m_filterNotPreAccepted;
+
+ // list of (filtering-parent . receiver) pairs
+ static DeliveryRecordVector m_expectedDeliveryList;
+ static DeliveryRecordVector m_deliveryList;
+ static QSet<QEvent::Type> m_includedEventTypes;
+};
+
+DeliveryRecordVector EventItem::m_expectedDeliveryList;
+DeliveryRecordVector EventItem::m_deliveryList;
+QSet<QEvent::Type> EventItem::m_includedEventTypes;
+
+typedef QVector<const char*> CharStarVector;
+
+Q_DECLARE_METATYPE(CharStarVector)
+
+struct InputState {
+ struct {
+ // event() behavior
+ bool eventAccepts;
+ // filterChildMouse behavior
+ bool returns;
+ bool accepts;
+ bool filtersChildMouseEvent;
+ } r[4];
+};
+
+Q_DECLARE_METATYPE(InputState)
+
+void tst_qquickwindow::testChildMouseEventFilter_data()
+{
+ // HIERARCHY:
+ // r0->r1->r2->r3
+ //
+ QTest::addColumn<QPoint>("mousePos");
+ QTest::addColumn<InputState>("inputState");
+ QTest::addColumn<DeliveryRecordVector>("expectedDeliveryOrder");
+
+ QTest::newRow("if filtered and rejected, do not deliver it to the item that filtered it")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, false},
+ { true, false, false, false},
+ { false, true, false, true},
+ { false, false, false, false}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r2", "r3")
+ //<< DeliveryRecord("r3") // it got filtered -> do not deliver
+ // DeliveryRecord("r2") // r2 filtered it -> do not deliver
+ << DeliveryRecord("r1")
+ );
+
+ QTest::newRow("no filtering, no accepting")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, false},
+ { false , false, false, false},
+ { false, false, false, false},
+ { false, false, false, false}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r3")
+ << DeliveryRecord("r2")
+ << DeliveryRecord("r1")
+ << DeliveryRecord("r0")
+ << DeliveryRecord("root")
+ );
+
+ QTest::newRow("all filtering, no accepting")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, true},
+ { false, false, false, true},
+ { false, false, false, true},
+ { false, false, false, true}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r2", "r3")
+ << DeliveryRecord("r1", "r3")
+ << DeliveryRecord("r0", "r3")
+ << DeliveryRecord("r3")
+ << DeliveryRecord("r1", "r2")
+ << DeliveryRecord("r0", "r2")
+ << DeliveryRecord("r2")
+ << DeliveryRecord("r0", "r1")
+ << DeliveryRecord("r1")
+ << DeliveryRecord("r0")
+ << DeliveryRecord("root")
+ );
+
+
+ QTest::newRow("some filtering, no accepting")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, true},
+ { false, false, false, true},
+ { false, false, false, false},
+ { false, false, false, false}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r1", "r3")
+ << DeliveryRecord("r0", "r3")
+ << DeliveryRecord("r3")
+ << DeliveryRecord("r1", "r2")
+ << DeliveryRecord("r0", "r2")
+ << DeliveryRecord("r2")
+ << DeliveryRecord("r0", "r1")
+ << DeliveryRecord("r1")
+ << DeliveryRecord("r0")
+ << DeliveryRecord("root")
+ );
+
+ QTest::newRow("r1 accepts")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, true},
+ { true , false, false, true},
+ { false, false, false, false},
+ { false, false, false, false}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r1", "r3")
+ << DeliveryRecord("r0", "r3")
+ << DeliveryRecord("r3")
+ << DeliveryRecord("r1", "r2")
+ << DeliveryRecord("r0", "r2")
+ << DeliveryRecord("r2")
+ << DeliveryRecord("r0", "r1")
+ << DeliveryRecord("r1")
+ );
+
+ QTest::newRow("r1 rejects and filters")
+ << QPoint(100, 100)
+ << InputState({
+ // | event() | child mouse filter
+ // +---------+---------+---------+---------
+ { // | accepts | returns | accepts | filtersChildMouseEvent
+ { false, false, false, true},
+ { false , true, false, true},
+ { false, false, false, false},
+ { false, false, false, false}
+ }
+ })
+ << (DeliveryRecordVector()
+ << DeliveryRecord("r1", "r3")
+ << DeliveryRecord("r0", "r3")
+// << DeliveryRecord("r3") // since it got filtered we don't deliver to r3
+ << DeliveryRecord("r1", "r2")
+ << DeliveryRecord("r0", "r2")
+// << DeliveryRecord("r2" // since it got filtered we don't deliver to r2
+ << DeliveryRecord("r0", "r1")
+// << DeliveryRecord("r1") // since it acted as a filter and returned true, we don't deliver to r1
+ << DeliveryRecord("r0")
+ << DeliveryRecord("root")
+ );
+
+}
+
+void tst_qquickwindow::testChildMouseEventFilter()
+{
+ QFETCH(QPoint, mousePos);
+ QFETCH(InputState, inputState);
+ QFETCH(DeliveryRecordVector, expectedDeliveryOrder);
+
+ EventItem::setExpectedDeliveryList(expectedDeliveryOrder);
+
+ QQuickWindow window;
+ window.resize(500, 809);
+ QQuickItem *root = window.contentItem();
+ root->setAcceptedMouseButtons(Qt::LeftButton);
+
+ root->setObjectName("root");
+ EventFilter *rootFilter = new EventFilter;
+ root->installEventFilter(rootFilter);
+
+ // Create 4 items; each item a child of the previous item.
+ EventItem *r[4];
+ r[0] = new EventItem(root);
+ r[0]->setColor(QColor(0x404040));
+ r[0]->setWidth(200);
+ r[0]->setHeight(200);
+
+ r[1] = new EventItem(r[0]);
+ r[1]->setColor(QColor(0x606060));
+
+ r[2] = new EventItem(r[1]);
+ r[2]->setColor(Qt::red);
+
+ r[3] = new EventItem(r[2]);
+ r[3]->setColor(Qt::green);
+
+ for (uint i = 0; i < sizeof(r)/sizeof(EventItem*); ++i) {
+ r[i]->setEventAccepts(inputState.r[i].eventAccepts);
+ r[i]->setFilterReturns(inputState.r[i].returns);
+ r[i]->setFilterAccepts(inputState.r[i].accepts);
+ r[i]->setFiltersChildMouseEvents(inputState.r[i].filtersChildMouseEvent);
+ r[i]->setObjectName(QString::fromLatin1("r%1").arg(i));
+ r[i]->setAcceptedMouseButtons(Qt::LeftButton);
+ }
+
+ window.show();
+ window.requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(&window));
+
+ DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList();
+ actualDeliveryOrder.clear();
+ QTest::mousePress(&window, Qt::LeftButton, 0, mousePos);
+
+ // Check if event got delivered to the root item. If so, append it to the list of items the event got delivered to
+ if (rootFilter->events.contains(QEvent::MouseButtonPress))
+ actualDeliveryOrder.append(DeliveryRecord("root"));
+
+ for (int i = 0; i < qMax(actualDeliveryOrder.count(), expectedDeliveryOrder.count()); ++i) {
+ const DeliveryRecord expectedNames = expectedDeliveryOrder.value(i);
+ const DeliveryRecord actualNames = actualDeliveryOrder.value(i);
+ QCOMPARE(actualNames.toString(), expectedNames.toString());
+ }
+
+ for (EventItem *item : r) {
+ QVERIFY(item->testFilterPreConditions());
+ }
+
+ // "restore" mouse state
+ QTest::mouseRelease(&window, Qt::LeftButton, 0, mousePos);
+}
+
+
QTEST_MAIN(tst_qquickwindow)
#include "tst_qquickwindow.moc"
diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro
index 00be8240e5..9b7740646a 100644
--- a/tests/auto/quick/quick.pro
+++ b/tests/auto/quick/quick.pro
@@ -47,6 +47,7 @@ PRIVATETESTS += \
!qtHaveModule(xmlpatterns): PRIVATETESTS -= qquickxmllistmodel
QUICKTESTS = \
+ pointerhandlers \
qquickaccessible \
qquickanchors \
qquickanimatedimage \
@@ -69,6 +70,7 @@ QUICKTESTS = \
qquickmousearea \
qquickmultipointtoucharea \
qquickpainteditem \
+ qquickshape \
qquickpathview \
qquickpincharea \
qquickpositioners \
diff --git a/tests/auto/quick/shared/viewtestutil.cpp b/tests/auto/quick/shared/viewtestutil.cpp
index cb2b8be97a..a33c5eae96 100644
--- a/tests/auto/quick/shared/viewtestutil.cpp
+++ b/tests/auto/quick/shared/viewtestutil.cpp
@@ -28,6 +28,7 @@
#include "viewtestutil.h"
+#include <QtCore/QRandomGenerator>
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickView>
#include <QtGui/QScreen>
@@ -356,7 +357,6 @@ QQuickViewTestUtil::StressTestModel::StressTestModel()
t->setInterval(500);
t->start();
- qsrand(qHash(QDateTime::currentDateTime()));
connect(t, &QTimer::timeout, this, &StressTestModel::updateModel);
}
@@ -374,7 +374,7 @@ void QQuickViewTestUtil::StressTestModel::updateModel()
{
if (m_rowCount > 10) {
for (int i = 0; i < 10; ++i) {
- int rnum = qrand() % m_rowCount;
+ int rnum = QRandomGenerator::bounded(m_rowCount);
beginRemoveRows(QModelIndex(), rnum, rnum);
m_rowCount--;
endRemoveRows();
@@ -382,7 +382,7 @@ void QQuickViewTestUtil::StressTestModel::updateModel()
}
if (m_rowCount < 20) {
for (int i = 0; i < 10; ++i) {
- int rnum = qrand() % m_rowCount;
+ int rnum = QRandomGenerator::bounded(m_rowCount);
beginInsertRows(QModelIndex(), rnum, rnum);
m_rowCount++;
endInsertRows();
diff --git a/tests/auto/quick/touchmouse/BLACKLIST b/tests/auto/quick/touchmouse/BLACKLIST
new file mode 100644
index 0000000000..ac0352d10b
--- /dev/null
+++ b/tests/auto/quick/touchmouse/BLACKLIST
@@ -0,0 +1,3 @@
+# QTBUG-40856 hover regression on pointerhandler branch: TODO fix before merging to dev
+[hoverEnabled]
+*
diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp
index 39f2961927..646317078b 100644
--- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp
+++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp
@@ -30,6 +30,7 @@
#include <QtTest/QtTest>
#include <QtGui/qstylehints.h>
+#include <private/qdebug_p.h>
#include <QtQuick/qquickview.h>
#include <QtQuick/qquickitem.h>
@@ -62,6 +63,21 @@ struct Event
QList<QTouchEvent::TouchPoint> points;
};
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const struct Event &event) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ dbg << "Event(";
+ QtDebugUtils::formatQEnum(dbg, event.type);
+ if (event.points.isEmpty())
+ dbg << " @ " << event.mousePos << " global " << event.mousePosGlobal;
+ else
+ dbg << ", " << event.points.count() << " touchpoints: " << event.points;
+ dbg << ')';
+ return dbg;
+}
+#endif
+
class EventItem : public QQuickItem
{
Q_OBJECT
@@ -74,6 +90,9 @@ public:
: QQuickItem(parent), touchUngrabCount(0), acceptMouse(false), acceptTouch(false), filterTouch(false), point0(-1)
{
setAcceptedMouseButtons(Qt::LeftButton);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ setAcceptTouchEvents(true);
+#endif
}
void touchEvent(QTouchEvent *event)
@@ -572,7 +591,7 @@ void tst_TouchMouse::buttonOnFlickable()
QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window.data());
QVERIFY(windowPriv->touchMouseId != -1);
auto pointerEvent = windowPriv->pointerEventInstance(QQuickPointerDevice::touchDevices().at(0));
- QCOMPARE(pointerEvent->point(0)->grabber(), eventItem1);
+ QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), eventItem1);
QCOMPARE(window->mouseGrabberItem(), eventItem1);
int dragDelta = -qApp->styleHints()->startDragDistance();
@@ -594,7 +613,7 @@ void tst_TouchMouse::buttonOnFlickable()
QCOMPARE(window->mouseGrabberItem(), flickable);
QVERIFY(windowPriv->touchMouseId != -1);
- QCOMPARE(pointerEvent->point(0)->grabber(), flickable);
+ QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), flickable);
QVERIFY(flickable->isMovingVertically());
QTest::touchEvent(window.data(), device).release(0, p3, window.data());
@@ -633,7 +652,7 @@ void tst_TouchMouse::touchButtonOnFlickable()
QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window.data());
QVERIFY(windowPriv->touchMouseId == -1);
auto pointerEvent = windowPriv->pointerEventInstance(QQuickPointerDevice::touchDevices().at(0));
- QCOMPARE(pointerEvent->point(0)->grabber(), eventItem2);
+ QCOMPARE(pointerEvent->point(0)->grabberItem(), eventItem2);
QCOMPARE(window->mouseGrabberItem(), nullptr);
int dragDelta = qApp->styleHints()->startDragDistance() * -0.7;
@@ -654,7 +673,7 @@ void tst_TouchMouse::touchButtonOnFlickable()
QCOMPARE(eventItem2->touchUngrabCount, 1);
QCOMPARE(window->mouseGrabberItem(), flickable);
QVERIFY(windowPriv->touchMouseId != -1);
- QCOMPARE(pointerEvent->point(0)->grabber(), flickable);
+ QCOMPARE(pointerEvent->point(0)->grabberItem(), flickable);
QVERIFY(flickable->isMovingVertically());
QTest::touchEvent(window.data(), device).release(0, p3, window.data());
@@ -759,7 +778,7 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable()
QCOMPARE(window->mouseGrabberItem(), flickable);
QVERIFY(windowPriv->touchMouseId != -1);
auto pointerEvent = windowPriv->pointerEventInstance(QQuickPointerDevice::touchDevices().at(0));
- QCOMPARE(pointerEvent->point(0)->grabber(), flickable);
+ QCOMPARE(pointerEvent->point(0)->grabberItem(), flickable);
QTest::touchEvent(window.data(), device).release(0, p3, window.data());
QQuickTouchUtils::flush(window.data());