diff options
Diffstat (limited to 'tests/auto/quick')
72 files changed, 5762 insertions, 257 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..4b2935b52e --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/FlashAnimation.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +SequentialAnimation { + id: tapFlash + running: false + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } +} diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml new file mode 100644 index 0000000000..f6acd53615 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/Slider.qml @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + id: root + property int value: 50 + property int maximumValue: 99 + property alias label: label.text + property alias tapEnabled: tap.enabled + property alias pressed: tap.pressed + signal tapped + + Rectangle { + id: slot + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 10 + anchors.topMargin: 30 + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + width: 10 + color: "black" + radius: width / 2 + smooth: true + } + + Rectangle { + id: glow + anchors.fill: knob + anchors.margins: -5 + anchors.leftMargin: -2 + anchors.horizontalCenterOffset: 1 + radius: 5 + color: "#4400FFFF" + opacity: tap.pressed || tapFlash.running ? 1 : 0 + FlashAnimation on visible { + id: tapFlash + } + } + Rectangle { + id: knob + objectName: "Slider Knob" + width: parent.width - 2 + height: 20 + radius: 5 + color: "darkgray" + border.color: "black" + property bool programmatic: false + property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum) + onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier + transformOrigin: Item.Center + function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier } + DragHandler { + id: dragHandler + objectName: label.text + " DragHandler" + xAxis.enabled: false + yAxis.minimum: slot.y + yAxis.maximum: slot.height + slot.y - knob.height + } + TapHandler { + id: tap + objectName: label.text + " TapHandler" + gesturePolicy: TapHandler.DragThreshold + onTapped: { + tapFlash.start() + root.tapped + } + } + } + + Text { + font.pointSize: 16 + color: "red" + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: root.value + } + + Text { + id: label + font.pointSize: 12 + color: "red" + anchors.top: parent.top + anchors.topMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + + Component.onCompleted: { + knob.programmatic = true + knob.setValue(root.value) + knob.programmatic = false + } +} diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml new file mode 100644 index 0000000000..55f77460f1 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/TapHandlerButton.qml @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + property alias label: label.text + property alias pressed: tap.pressed + property bool checked: false + property alias gesturePolicy: tap.gesturePolicy + property alias enabled: tap.enabled + signal tapped + + width: label.implicitWidth * 1.5; height: label.implicitHeight * 2.0 + border.color: "#9f9d9a"; border.width: 1; radius: height / 4; antialiasing: true + + gradient: Gradient { + GradientStop { position: 0.0; color: tap.pressed ? "#b8b5b2" : "#efebe7" } + GradientStop { position: 1.0; color: "#b8b5b2" } + } + + TapHandler { + id: tap + objectName: label.text + longPressThreshold: 100 // CI can be insanely slow, so don't demand a timely release to generate onTapped + onTapped: { + tapFlash.start() + root.tapped() + } + } + + Text { + id: label + font.pointSize: 14 + text: "Button" + anchors.centerIn: parent + } + + Rectangle { + anchors.fill: parent; anchors.margins: -5 + color: "transparent"; border.color: "#4400FFFF" + border.width: 5; radius: root.radius; antialiasing: true + opacity: tapFlash.running ? 1 : 0 + FlashAnimation on visible { id: tapFlash } + } + + Rectangle { + objectName: "expandingCircle" + radius: tap.timeHeld * 100 + visible: radius > 0 && tap.pressed + border.width: 3 + border.color: "cyan" + color: "transparent" + width: radius * 2 + height: radius * 2 + x: tap.point.scenePressPosition.x - radius + y: tap.point.scenePressPosition.y - radius + opacity: 0.25 + Component.onCompleted: { + // get on top of all the buttons + var par = root.parent; + while (par.parent) + par = par.parent; + parent = par; + } + } +} diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml new file mode 100644 index 0000000000..95ecf702be --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/flickableWithHandlers.qml @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + width: 500 + height: 480 + objectName: "root" + color: "#222222" + + Flickable { + anchors.fill: parent + anchors.margins: 10 + anchors.topMargin: 40 + contentHeight: 600 + contentWidth: 600 +// pressDelay: TODO + + Row { + spacing: 6 + Slider { + label: "DragHandler" + objectName: "Slider" + value: 49; width: 100; height: 400 + } + Column { + spacing: 6 + TapHandlerButton { + objectName: "DragThreshold" + label: "DragThreshold" + gesturePolicy: TapHandler.DragThreshold + } + TapHandlerButton { + objectName: "WithinBounds" + label: "WithinBounds" + gesturePolicy: TapHandler.WithinBounds + } + TapHandlerButton { + objectName: "ReleaseWithinBounds" + label: "ReleaseWithinBounds" + gesturePolicy: TapHandler.ReleaseWithinBounds // the default + } + } + Column { + spacing: 6 + Rectangle { + width: 50 + height: 50 + color: "aqua" + border.color: drag1.active ? "darkgreen" : "transparent" + border.width: 3 + objectName: "drag" + DragHandler { + id: drag1 + } + Text { + anchors.centerIn: parent + enabled: false + text: "drag" + } + } + Rectangle { + width: 50 + height: 50 + color: "aqua" + objectName: "tap" + border.color: tap1.isPressed ? "red" : "transparent" + border.width: 3 + TapHandler { + id: tap1 + gesturePolicy: TapHandler.DragThreshold + } + Text { + anchors.centerIn: parent + enabled: false + text: "tap" + } + } + Rectangle { + width: 50 + height: 50 + color: "aqua" + border.color: tap2.isPressed ? "red" : drag2.active ? "darkgreen" : "transparent" + border.width: 3 + objectName: "dragAndTap" + DragHandler { + id: drag2 + } + TapHandler { + id: tap2 + gesturePolicy: TapHandler.DragThreshold + } + Text { + anchors.centerIn: parent + enabled: false + text: "drag\nand\ntap" + } + } + Rectangle { + width: 50 + height: 50 + color: "aqua" + border.color: tap3.isPressed ? "red" : drag3.active ? "darkgreen" : "transparent" + border.width: 3 + objectName: "tapAndDrag" + TapHandler { + id: tap3 + gesturePolicy: TapHandler.DragThreshold + } + DragHandler { + id: drag3 + } + Text { + anchors.centerIn: parent + enabled: false + text: "tap\nand\ndrag" + } + } + } + } + } +} + diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro b/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro new file mode 100644 index 0000000000..9075044bd3 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/flickableinterop.pro @@ -0,0 +1,15 @@ +CONFIG += testcase + +TARGET = tst_flickableinterop +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_flickableinterop.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +OTHER_FILES += data/flickableWithHandlers.qml data/Slider.qml diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp new file mode 100644 index 0000000000..7862d72db8 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp @@ -0,0 +1,552 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QtGui/qstylehints.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> +#include <QtQuick/private/qquickdraghandler_p.h> +#include <QtQuick/private/qquicktaphandler_p.h> +#include <qpa/qwindowsysteminterface.h> + +#include <private/qquickwindow_p.h> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlproperty.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class tst_FlickableInterop : public QQmlDataTest +{ + Q_OBJECT +public: + tst_FlickableInterop() + :touchDevice(QTest::createTouchDevice()) + {} + +private slots: + void initTestCase(); + + void touchTapButton_data(); + void touchTapButton(); + void touchDragFlickableBehindButton_data(); + void touchDragFlickableBehindButton(); + void mouseClickButton_data(); + void mouseClickButton(); + void mouseDragFlickableBehindButton_data(); + void mouseDragFlickableBehindButton(); + void touchDragSlider(); + void touchDragFlickableBehindSlider(); + void mouseDragSlider(); + void mouseDragFlickableBehindSlider(); + void touchDragFlickableBehindItemWithHandlers_data(); + void touchDragFlickableBehindItemWithHandlers(); + void mouseDragFlickableBehindItemWithHandlers_data(); + void mouseDragFlickableBehindItemWithHandlers(); + +private: + void createView(QScopedPointer<QQuickView> &window, const char *fileName); + QTouchDevice *touchDevice; +}; + +void tst_FlickableInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName) +{ + window.reset(new QQuickView); + window->setSource(testFileUrl(fileName)); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtil::centerOnScreen(window.data()); + QQuickViewTestUtil::moveMouseAway(window.data()); + + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(window->rootObject() != 0); +} + +void tst_FlickableInterop::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); +} + +void tst_FlickableInterop::touchTapButton_data() +{ + QTest::addColumn<QString>("buttonName"); + QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); + QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); + QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); +} + +void tst_FlickableInterop::touchTapButton() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QFETCH(QString, buttonName); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName); + QVERIFY(button); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + // Button changes pressed state and emits tapped on release + QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 1); + + // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab + p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 2); +} + +void tst_FlickableInterop::touchDragFlickableBehindButton_data() +{ + QTest::addColumn<QString>("buttonName"); + QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); + QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); + QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); +} + +void tst_FlickableInterop::touchDragFlickableBehindButton() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QFETCH(QString, buttonName); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName); + QVERIFY(button); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + tappedSpy.clear(); + QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(button->property("pressed").toBool()); + int i = 0; + // Start dragging; eventually when the touchpoint goes beyond dragThreshold, + // Button is no longer pressed because Flickable steals the grab + for (; i < 100 && !flickable->isMoving(); ++i) { + p1 += QPoint(1, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + } + QVERIFY(flickable->isMoving()); + qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1; + QCOMPARE(i, 2); + QVERIFY(!button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 0); +} + +void tst_FlickableInterop::mouseClickButton_data() +{ + QTest::addColumn<QString>("buttonName"); + QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); + QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); + QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); +} + +void tst_FlickableInterop::mouseClickButton() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QFETCH(QString, buttonName); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName); + QVERIFY(button); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + // Button changes pressed state and emits tapped on release + QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 1); + + // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab + p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QVERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 2); +} + +void tst_FlickableInterop::mouseDragFlickableBehindButton_data() +{ + QTest::addColumn<QString>("buttonName"); + QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); + QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); + QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); +} + +void tst_FlickableInterop::mouseDragFlickableBehindButton() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QFETCH(QString, buttonName); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(buttonName); + QVERIFY(button); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + // Button is no longer pressed if touchpoint goes beyond dragThreshold, + // because Flickable steals the grab + tappedSpy.clear(); + QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QVERIFY(button->property("pressed").toBool()); + int i = 0; + for (; i < 100 && !flickable->isMoving(); ++i) { + p1 += QPoint(1, 0); + QTest::mouseMove(window, p1); + } + qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1; + QVERIFY(flickable->isMoving()); + QCOMPARE(i, 2); + QVERIFY(!button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QVERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 0); +} + +void tst_FlickableInterop::touchDragSlider() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider"); + QVERIFY(slider); + QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob"); + QVERIFY(knob); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); + QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); + + // Drag the slider in the allowed (vertical) direction + tappedSpy.clear(); + QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(slider->property("pressed").toBool()); + p1 += QPoint(0, dragThreshold); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(slider->property("pressed").toBool()); + QCOMPARE(slider->property("value").toInt(), 49); + p1 += QPoint(0, 1); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + p1 += QPoint(0, 10); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(slider->property("value").toInt() < 49); + QVERIFY(!flickable->isMoving()); + QVERIFY(!slider->property("pressed").toBool()); + + // Now that the DragHandler is active, the Flickable will not steal the grab + // even if we move a large distance horizontally + p1 += QPoint(dragThreshold * 2, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!flickable->isMoving()); + + // Release, and do not expect the tapped signal + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(tappedSpy.count(), 0); + QCOMPARE(translationChangedSpy.count(), 1); +} + +void tst_FlickableInterop::mouseDragSlider() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider"); + QVERIFY(slider); + QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob"); + QVERIFY(knob); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); + QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); + + // Drag the slider in the allowed (vertical) direction + tappedSpy.clear(); + QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(slider->property("pressed").toBool()); + p1 += QPoint(0, dragThreshold); + QTest::mouseMove(window, p1); + QVERIFY(slider->property("pressed").toBool()); + QCOMPARE(slider->property("value").toInt(), 49); + p1 += QPoint(0, 1); + QTest::mouseMove(window, p1); + p1 += QPoint(0, 10); + QTest::mouseMove(window, p1); + QVERIFY(slider->property("value").toInt() < 49); + QVERIFY(!flickable->isMoving()); + QVERIFY(!slider->property("pressed").toBool()); + + // Now that the DragHandler is active, the Flickable will not steal the grab + // even if we move a large distance horizontally + p1 += QPoint(dragThreshold * 2, 0); + QTest::mouseMove(window, p1); + QVERIFY(!flickable->isMoving()); + + // Release, and do not expect the tapped signal + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(tappedSpy.count(), 0); + QCOMPARE(translationChangedSpy.count(), 1); +} + +void tst_FlickableInterop::touchDragFlickableBehindSlider() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider"); + QVERIFY(slider); + QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob"); + QVERIFY(knob); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); + QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); + + // Button is no longer pressed if touchpoint goes beyond dragThreshold, + // because Flickable steals the grab + tappedSpy.clear(); + QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(slider->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(slider->property("pressed").toBool()); + int i = 0; + for (; i < 100 && !flickable->isMoving(); ++i) { + p1 += QPoint(1, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + } + qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1; + QVERIFY(flickable->isMoving()); + QCOMPARE(i, 2); + QVERIFY(!slider->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!slider->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 0); + QCOMPARE(translationChangedSpy.count(), 0); +} + +void tst_FlickableInterop::mouseDragFlickableBehindSlider() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>("Slider"); + QVERIFY(slider); + QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQuickItem *knob = slider->findChild<QQuickItem*>("Slider Knob"); + QVERIFY(knob); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); + QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); + + // Button is no longer pressed if touchpoint goes beyond dragThreshold, + // because Flickable steals the grab + tappedSpy.clear(); + QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(slider->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QQuickTouchUtils::flush(window); + QVERIFY(slider->property("pressed").toBool()); + int i = 0; + for (; i < 100 && !flickable->isMoving(); ++i) { + p1 += QPoint(1, 0); + QTest::mouseMove(window, p1); + } + qDebug() << "flickable started moving after" << i << "moves, when we got to" << p1; + QVERIFY(flickable->isMoving()); + QCOMPARE(i, 2); + QVERIFY(!slider->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(tappedSpy.count(), 0); + QCOMPARE(translationChangedSpy.count(), 0); +} + +void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers_data() +{ + QTest::addColumn<QByteArray>("nameOfRectangleToDrag"); + QTest::addColumn<bool>("expectedFlickableMoving"); + QTest::newRow("drag") << QByteArray("drag") << false; + QTest::newRow("tap") << QByteArray("tap") << true; + QTest::newRow("dragAndTap") << QByteArray("dragAndTap") << false; + QTest::newRow("tapAndDrag") << QByteArray("tapAndDrag") << false; +} + +void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers() +{ + QFETCH(bool, expectedFlickableMoving); + QFETCH(QByteArray, nameOfRectangleToDrag); + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(nameOfRectangleToDrag); + QVERIFY(rect); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint(); + QPoint originP1 = p1; + + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + for (int i = 0; i < dragThreshold * 3; ++i) { + p1 = originP1; + p1.rx() += i; + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + } + QCOMPARE(flickable->isMoving(), expectedFlickableMoving); + if (!expectedFlickableMoving) { + QVERIFY(rect->mapToScene(rect->clipRect().center()).toPoint().x() > originP1.x()); + } + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); +} + +void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers_data() +{ + touchDragFlickableBehindItemWithHandlers_data(); +} + +void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers() +{ + QFETCH(bool, expectedFlickableMoving); + QFETCH(QByteArray, nameOfRectangleToDrag); + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "flickableWithHandlers.qml"); + QQuickView * window = windowPtr.data(); + QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(nameOfRectangleToDrag); + QVERIFY(rect); + QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint(); + QPoint originP1 = p1; + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + for (int i = 0; i < 3; ++i) { + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QQuickTouchUtils::flush(window); + } + QCOMPARE(flickable->isMoving(), expectedFlickableMoving); + if (!expectedFlickableMoving) { + QCOMPARE(originP1 + QPoint(3*dragThreshold, 0), p1); + } + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); +} + +QTEST_MAIN(tst_FlickableInterop) + +#include "tst_flickableinterop.moc" + diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml new file mode 100644 index 0000000000..084cc25414 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + width: 1024; height: 600 + color: "beige" + objectName: "beige root" + + Rectangle { + id: container + objectName: "container rect" + width: 600 + height: 500 + color: "black" + border.color: pinch3.active ? "red" : "black" + border.width: 3 + antialiasing: true + + MultiPointTouchArea { + id: mpta + anchors.fill: parent + //onGestureStarted: gesture.grab() // in case this is embedded in something that might steal + touchPoints: [ + TouchPoint { property color color: "red" }, + TouchPoint { property color color: "orange" }, + TouchPoint { property color color: "lightsteelblue" }, + TouchPoint { property color color: "green" } + ] } + + Repeater { + model: 4 + + Item { + id: crosshairs + property TouchPoint touchPoint + x: touchPoint.x - width / 2 + y: touchPoint.y - height / 2 + width: 300; height: 300 + visible: touchPoint.pressed + rotation: touchPoint.rotation + + Rectangle { + color: touchPoint.color + anchors.centerIn: parent + width: 2; height: parent.height + antialiasing: true + } + Rectangle { + color: touchPoint.color + anchors.centerIn: parent + width: parent.width; height: 2 + antialiasing: true + } + Component.onCompleted: touchPoint = mpta.touchPoints[index] + } + } + + Item { + objectName: "pinch and drag" + anchors.fill: parent + // In order for PinchHandler to get a chance to take a passive grab, it has to get the touchpoints first. + // In order to get the touchpoints first, it has to be on top of the Z order: i.e. come last in paintOrderChildItems(). + // This is the opposite situation as with filtersChildMouseEvents: e.g. PinchArea would have wanted to be the parent, + // if it even knew that trick (which it doesn't). + DragHandler { + id: dragHandler + objectName: "DragHandler" + target: container + } + PinchHandler { + id: pinch3 + objectName: "3-finger pinch" + target: container + minimumPointCount: 3 + minimumScale: 0.1 + maximumScale: 10 + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro new file mode 100644 index 0000000000..10d0ff8018 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/multipointtoucharea_interop.pro @@ -0,0 +1,15 @@ +CONFIG += testcase + +TARGET = tst_multipointtoucharea_interop +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_multipointtoucharea_interop.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +OTHER_FILES += data/pinchDragMPTA.qml diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp new file mode 100644 index 0000000000..a2934eee32 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlproperty.h> +#include <QtQuick/private/qquickdraghandler_p.h> +#include <QtQuick/private/qquickmultipointtoucharea_p.h> +#include <QtQuick/private/qquickpinchhandler_p.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class tst_MptaInterop : public QQmlDataTest +{ + Q_OBJECT +public: + tst_MptaInterop() + : touchDevice(QTest::createTouchDevice()) + , touchPointerDevice(QQuickPointerDevice::touchDevice(touchDevice)) + {} + +private slots: + void initTestCase(); + + void touchDrag(); + void touchesThenPinch(); + +private: + void createView(QScopedPointer<QQuickView> &window, const char *fileName); + QTouchDevice *touchDevice; + QQuickPointerDevice *touchPointerDevice; +}; + +void tst_MptaInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName) +{ + window.reset(new QQuickView); + window->setSource(testFileUrl(fileName)); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtil::centerOnScreen(window.data()); + QQuickViewTestUtil::moveMouseAway(window.data()); + + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(window->rootObject() != 0); +} + +void tst_MptaInterop::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); +} + +void tst_MptaInterop::touchDrag() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "pinchDragMPTA.qml"); + QQuickView * window = windowPtr.data(); + + QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>(); + QVERIFY(mpta); + QQuickPinchHandler *pinch = window->rootObject()->findChild<QQuickPinchHandler*>(); + QVERIFY(pinch); + QQuickDragHandler *drag = window->rootObject()->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQmlListReference tp(mpta, "touchPoints"); + QVERIFY(tp.at(3)); // the QML declares four touchpoints + QSignalSpy mptaPressedSpy(mpta, SIGNAL(pressed(QList<QObject*>))); + QSignalSpy mptaReleasedSpy(mpta, SIGNAL(released(QList<QObject*>))); + QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice); + + // Press one touchpoint: + // DragHandler gets a passive grab + // PinchHandler declines, because it wants 3 touchpoints + // MPTA doesn't get a chance, because DragHandler accepted the single EventPoint + QPoint p1 = mpta->mapToScene(QPointF(20, 20)).toPoint(); + touch.press(1, p1).commit(); + QQuickTouchUtils::flush(window); + auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice); + QCOMPARE(tp.at(0)->property("pressed").toBool(), false); + QTRY_VERIFY(pointerEvent->point(0)->passiveGrabbers().contains(drag)); + + // Start moving + // DragHandler keeps monitoring, due to its passive grab, + // and eventually steals the exclusive grab from MPTA + int dragStoleGrab = 0; + for (int i = 0; i < 4; ++i) { + p1 += QPoint(dragThreshold / 2, 0); + touch.move(1, p1).commit(); + QQuickTouchUtils::flush(window); + if (!dragStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == drag) + dragStoleGrab = i; + } + if (dragStoleGrab) + qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab); + QVERIFY(dragStoleGrab > 1); + + touch.release(1, p1).commit(); + QQuickTouchUtils::flush(window); +} + +// TODO touchesThenPinch_data with press/release sequences somehow: vectors of touchpoint IDs? or a string representation? +void tst_MptaInterop::touchesThenPinch() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "pinchDragMPTA.qml"); + QQuickView * window = windowPtr.data(); + + QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>(); + QVERIFY(mpta); + QQuickPinchHandler *pinch = window->rootObject()->findChild<QQuickPinchHandler*>(); + QVERIFY(pinch); + QQuickDragHandler *drag = window->rootObject()->findChild<QQuickDragHandler*>(); + QVERIFY(drag); + QQmlListReference tp(mpta, "touchPoints"); + QVERIFY(tp.at(3)); // the QML declares four touchpoints + QSignalSpy mptaPressedSpy(mpta, SIGNAL(pressed(QList<QObject*>))); + QSignalSpy mptaReleasedSpy(mpta, SIGNAL(released(QList<QObject*>))); + QSignalSpy mptaCanceledSpy(mpta, SIGNAL(canceled(QList<QObject*>))); + QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice); + auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice); + + // Press one touchpoint: + // DragHandler gets a passive grab + // PinchHandler declines, because it wants 3 touchpoints + // MPTA doesn't get a chance, because DragHandler accepted the single EventPoint + QPoint p1 = mpta->mapToScene(QPointF(20, 20)).toPoint(); + touch.press(1, p1).commit(); + QQuickTouchUtils::flush(window); + QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr); + QTRY_COMPARE(pointerEvent->point(0)->passiveGrabbers().first(), drag); + + // Press a second touchpoint: MPTA grabs it + QPoint p2 = mpta->mapToScene(QPointF(200, 30)).toPoint(); + touch.stationary(1).press(2, p2).commit(); + QQuickTouchUtils::flush(window); + QVERIFY(tp.at(0)->property("pressed").toBool()); + QTRY_VERIFY(tp.at(1)->property("pressed").toBool()); + QCOMPARE(mptaPressedSpy.count(), 1); + + // ATM it's required that when PinchHandler sees the third touchpoint, + // the pre-existing points must have moved far enough to exceed the drag threshold. + // If MPTA is allowed to grab that third point, then PinchHandler won't steal. + // TODO should we change that? make sure that if PH has a passive grab, it always gets updated even though MPTA has the grab? + for (int i = 0; i < 2; ++i) { + p1 += QPoint(dragThreshold, dragThreshold); + p2 += QPoint(dragThreshold, dragThreshold); + touch.move(1, p1).move(2, p2).commit(); + } + + // Press a third touchpoint: PinchHandler grabs, MPTA loses its grabs + QPoint p3 = mpta->mapToScene(QPointF(110, 200)).toPoint(); + touch.stationary(1).stationary(2).press(3, p3).commit(); + QQuickTouchUtils::flush(window); + QCOMPARE(tp.at(0)->property("pressed").toBool(), false); + QCOMPARE(tp.at(1)->property("pressed").toBool(), false); + QCOMPARE(tp.at(2)->property("pressed").toBool(), false); + QCOMPARE(mptaPressedSpy.count(), 1); + QCOMPARE(mptaCanceledSpy.count(), 1); + QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch); + QTRY_COMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch); + QTRY_COMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch); + QVERIFY(pinch->active()); + + // Start moving: PinchHandler steals the exclusive grab from MPTA as soon as dragThreshold is exceeded + int pinchStoleGrab = 0; + for (int i = 0; i < 8; ++i) { + p1 += QPoint(dragThreshold / 2, dragThreshold / 2); + p2 += QPoint(dragThreshold / 2, dragThreshold / 2); + p3 += QPoint(-dragThreshold / 2, dragThreshold / 2); + touch.move(1, p1).move(2, p2).move(3, p3).commit(); + QQuickTouchUtils::flush(window); + QTRY_COMPARE(tp.at(0)->property("pressed").toBool(), false); + QCOMPARE(tp.at(1)->property("pressed").toBool(), false); + QCOMPARE(tp.at(2)->property("pressed").toBool(), false); + if (!pinchStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == pinch) + pinchStoleGrab = i; + } + qCDebug(lcPointerTests) << "pinch started after" << pinchStoleGrab << "moves; ended with scale" << pinch->scale() << "rot" << pinch->rotation(); + QTRY_VERIFY(pinch->rotation() > 8); + QVERIFY(pinch->scale() > 1); + + // Press one more point (pinkie finger) + QPoint p4 = mpta->mapToScene(QPointF(300, 200)).toPoint(); + touch.move(1, p1).move(2, p2).move(3, p3).press(4, p4).commit(); + // PinchHandler gives up its grabs (only on non-stationary points at this time: see QQuickPointerHandler::handlePointerEvent()) + // because it has minimum touch points 3, maximum touch points 3, and now there are 4 points. + // MPTA grabs all points which are not already grabbed + QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta); + // Move some more... MPTA keeps reacting + for (int i = 0; i < 8; ++i) { + p1 += QPoint(4, 4); + p2 += QPoint(4, 4); + p3 += QPoint(-4, 4); + p4 += QPoint(-4, -4); + touch.move(1, p1).move(2, p2).move(3, p3).move(4, p4).commit(); + QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta); + QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta); + QCOMPARE(tp.at(0)->property("pressed").toBool(), true); + QCOMPARE(tp.at(1)->property("pressed").toBool(), true); + QCOMPARE(tp.at(2)->property("pressed").toBool(), true); + QCOMPARE(tp.at(3)->property("pressed").toBool(), true); + } + + // Release the pinkie: PinchHandler acquires passive grabs on the 3 remaining points + touch.move(1, p1).move(2, p2).move(3, p3).release(4, p4).commit(); + // Move some more: PinchHander grabs again, and reacts + for (int i = 0; i < 8; ++i) { + p1 -= QPoint(4, 4); + p2 += QPoint(4, 4); + p3 -= QPoint(-4, 4); + touch.move(1, p1).move(2, p2).move(3, p3).commit(); + QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch); + QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch); + QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch); + } + + // Release the first finger + touch.stationary(2).stationary(3).release(1, p1).commit(); + // Move some more: PinchHander isn't interested in a mere 2 points. + // MPTA could maybe react; but QQuickWindowPrivate::deliverTouchEvent() calls + // deliverPressOrReleaseEvent() in a way which "starts over" with event delivery + // only for handlers, not for Items; therefore MPTA is not visited at this time. + for (int i = 0; i < 8; ++i) { + p2 -= QPoint(4, 4); + p3 += QPoint(4, 4); + touch.move(2, p2).move(3, p3).commit(); + QQuickTouchUtils::flush(window); + } + + // Release another finger + touch.stationary(2).release(3, p3).commit(); + // Move some more: DragHandler reacts. + // It had a passive grab this whole time; now it activates and gets an exclusive grab. + for (int i = 0; i < 8; ++i) { + p2 += QPoint(8, -8); + touch.move(2, p2).commit(); + QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), drag); + } + + touch.release(2, p2).commit(); + QQuickTouchUtils::flush(window); + QTRY_COMPARE(mptaReleasedSpy.count(), 1); +} + +QTEST_MAIN(tst_MptaInterop) + +#include "tst_multipointtoucharea_interop.moc" diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro new file mode 100644 index 0000000000..2492924944 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro @@ -0,0 +1,11 @@ +TEMPLATE = subdirs + +qtConfig(private_tests) { + SUBDIRS += \ + flickableinterop \ + multipointtoucharea_interop \ + qquickpointerhandler \ + qquickdraghandler \ + qquicktaphandler \ +} + diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml new file mode 100644 index 0000000000..fe5f74743d --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/DragAnywhereSlider.qml @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + id: root + objectName: label + property int value: 50 + property int maximumValue: 99 + property alias label: label.text + property alias tapEnabled: tap.enabled + property alias pressed: tap.pressed + signal tapped + width: 140 + height: 400 + + DragHandler { + id: dragHandler + objectName: label.text + " DragHandler" + target: knob + xAxis.enabled: false + yAxis.minimum: slot.y + yAxis.maximum: slot.height + slot.y - knob.height + } + + Rectangle { + id: slot + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 10 + anchors.topMargin: 30 + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + width: 10 + color: "black" + radius: width / 2 + smooth: true + } + + Rectangle { + id: glow + anchors.fill: knob + anchors.margins: -5 + anchors.leftMargin: -2 + anchors.horizontalCenterOffset: 1 + radius: 5 + color: "#4400FFFF" + opacity: tap.pressed || tapFlash.running ? 1 : 0 + FlashAnimation on visible { + id: tapFlash + } + } + Rectangle { + id: knob + objectName: "Slider Knob" + width: parent.width - 2 + height: 30 + radius: 5 + color: "darkgray" + border.color: "black" + property bool programmatic: false + property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum) + onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier + transformOrigin: Item.Center + function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier } + TapHandler { + id: tap + objectName: label.text + " TapHandler" + gesturePolicy: TapHandler.DragThreshold + onTapped: { + tapFlash.start() + root.tapped + } + } + } + + Text { + font.pointSize: 16 + color: "red" + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: root.value + } + + Text { + id: label + font.pointSize: 12 + color: "red" + anchors.top: parent.top + anchors.topMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + + Component.onCompleted: { + knob.programmatic = true + knob.setValue(root.value) + knob.programmatic = false + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml new file mode 100644 index 0000000000..158a02b7a6 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/FlashAnimation.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +SequentialAnimation { + id: tapFlash + running: false + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml new file mode 100644 index 0000000000..a41a8285b6 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/Slider.qml @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + id: root + objectName: label + property int value: 50 + property int maximumValue: 99 + property alias label: label.text + property alias tapEnabled: tap.enabled + property alias pressed: tap.pressed + signal tapped + width: 140 + height: 400 + + Rectangle { + id: slot + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 10 + anchors.topMargin: 30 + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + width: 10 + color: "black" + radius: width / 2 + smooth: true + } + + Rectangle { + id: glow + anchors.fill: knob + anchors.margins: -5 + anchors.leftMargin: -2 + anchors.horizontalCenterOffset: 1 + radius: 5 + color: "#4400FFFF" + opacity: tap.pressed || tapFlash.running ? 1 : 0 + FlashAnimation on visible { + id: tapFlash + } + } + Rectangle { + id: knob + objectName: root.label + " Knob" + width: parent.width - 2 + height: 30 + radius: 5 + color: "darkgray" + border.color: "black" + property bool programmatic: false + property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum) + onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier + transformOrigin: Item.Center + function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier } + function flash() { tapFlash.start() } + DragHandler { + id: dragHandler + objectName: label.text + " DragHandler" + xAxis.enabled: false + yAxis.minimum: slot.y + yAxis.maximum: slot.height + slot.y - knob.height + } + TapHandler { + id: tap + objectName: label.text + " TapHandler" + gesturePolicy: TapHandler.DragThreshold + onTapped: { + tapFlash.start() + root.tapped + } + } + } + + Text { + font.pointSize: 16 + color: "red" + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: root.value + } + + Text { + id: label + font.pointSize: 12 + color: "red" + anchors.top: parent.top + anchors.topMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + + Component.onCompleted: { + knob.programmatic = true + knob.setValue(root.value) + knob.programmatic = false + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml new file mode 100644 index 0000000000..5b701b4033 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/draggables.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Item { + id: root + objectName: "Draggables" + width: 640 + height: 480 + + Repeater { + model: 2 + + Rectangle { + id: ball + objectName: "Ball " + index + color: dragHandler.active ? "blue" : "lightsteelblue" + width: 80; height: 80; x: 200 + index * 200; y: 200; radius: width / 2 + onParentChanged: console.log(this + " parent " + parent) + + DragHandler { + id: dragHandler + objectName: "DragHandler " + index + } + + Text { + color: "white" + anchors.centerIn: parent + text: dragHandler.point.position.x.toFixed(1) + "," + dragHandler.point.position.y.toFixed(1) + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml new file mode 100644 index 0000000000..ba6e2d00a8 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/multipleSliders.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + width: 900 + height: 850 + objectName: "root" + color: "#222222" + + Grid { + objectName: "grid" + anchors.fill: parent + spacing: 10 + columns: 6 + Repeater { + id: top + objectName: "top" + model: 6 + + delegate: Slider { + objectName: label + label: "Drag Knob " + index + width: 140 + } + } + Repeater { + id: bottom + objectName: "bottom" + model: 6 + + delegate: DragAnywhereSlider { + objectName: label + label: "Drag Anywhere " + index + width: 140 + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml new file mode 100644 index 0000000000..3545badd86 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/reparenting.qml @@ -0,0 +1,60 @@ +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Grid { + id: root + objectName: "root" + property bool reparentOnDrag: true + width: 200; height: 200 + columns: 3 + spacing: 10 + Repeater { + model: 9 + anchors.fill: parent + Item { + id: gridPlaceholder + objectName: "gridPlaceholder" + index + width: 60 + height: 60 + Rectangle { + id: icon + border.color: "black" + color: "beige" + radius: 3 + width: 60 + height: 60 + onParentChanged :console.log("parent " + parent) + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + DragHandler { + id: dragArea + } + Text { + anchors.centerIn: parent + text: index + "@" + Math.round(icon.x) + "," + Math.round(icon.y) + font.pointSize: 8 + } + states: [ + State { + when: dragArea.dragging + AnchorChanges { + target: icon + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + } + ParentChange { + target: root.reparentOnDrag ? icon : null + parent: root + } + PropertyChanges { + target: icon + color: "yellow" + } + } + ] + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml new file mode 100644 index 0000000000..adb8332213 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/simpleTapAndDragHandlers.qml @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + width: 900 + height: 850 + objectName: "root" + color: "#222222" + + Row { + objectName: "row" + anchors.fill: parent + spacing: 10 + Rectangle { + width: 50 + height: 50 + color: "aqua" + objectName: "dragAndTap" + DragHandler { + objectName: "drag" + } + TapHandler { + objectName: "tap" + gesturePolicy: TapHandler.DragThreshold + } + } + Rectangle { + width: 50 + height: 50 + color: "aqua" + objectName: "tapAndDrag" + TapHandler { + objectName: "tap" + gesturePolicy: TapHandler.DragThreshold + } + DragHandler { + objectName: "drag" + } + } + + Rectangle { + color: "aqua" + width: 50 + height: 50 + objectName: "dragAndTapNotSiblings" + DragHandler { + objectName: "drag" + } + Rectangle { + color: "blue" + width: 30 + height: 30 + anchors.centerIn: parent + TapHandler { + objectName: "tap" + gesturePolicy: TapHandler.DragThreshold + } + } + } + Rectangle { + color: "aqua" + width: 50 + height: 50 + objectName: "tapAndDragNotSiblings" + TapHandler { + objectName: "tap" + gesturePolicy: TapHandler.DragThreshold + } + Rectangle { + color: "blue" + x: 10 + y: 10 + width: 30 + height: 30 + DragHandler { + objectName: "drag" + } + } + } + + + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro new file mode 100644 index 0000000000..42c4e46c4f --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/qquickdraghandler.pro @@ -0,0 +1,21 @@ +CONFIG += testcase + +TARGET = tst_qquickdraghandler +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_qquickdraghandler.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +OTHER_FILES += data/DragAnywhereSlider.qml \ + data/FlashAnimation.qml \ + data/Slider.qml \ + data/draggables.qml \ + data/grabberstate.qml \ + data/multipleSliders.qml \ + data/reparenting.qml \ diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp new file mode 100644 index 0000000000..f827b82205 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp @@ -0,0 +1,487 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlproperty.h> +#include <QtQuick/private/qquickdraghandler_p.h> +#include <QtQuick/private/qquickrepeater_p.h> +#include <QtQuick/private/qquicktaphandler_p.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class tst_DragHandler : public QQmlDataTest +{ + Q_OBJECT +public: + tst_DragHandler() + :touchDevice(QTest::createTouchDevice()) + {} + +private slots: + void initTestCase(); + + void defaultPropertyValues(); + void touchDrag(); + void mouseDrag(); + void touchDragMulti(); + void touchDragMultiSliders_data(); + void touchDragMultiSliders(); + void touchPassiveGrabbers_data(); + void touchPassiveGrabbers(); + +private: + void createView(QScopedPointer<QQuickView> &window, const char *fileName); + QSet<QQuickPointerHandler *> passiveGrabbers(QQuickWindow *window, int pointId = 0); + QTouchDevice *touchDevice; +}; + +void tst_DragHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) +{ + window.reset(new QQuickView); + window->setSource(testFileUrl(fileName)); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtil::centerOnScreen(window.data()); + QQuickViewTestUtil::moveMouseAway(window.data()); + + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(window->rootObject() != 0); +} + +QSet<QQuickPointerHandler*> tst_DragHandler::passiveGrabbers(QQuickWindow *window, int pointId /*= 0*/) +{ + QSet<QQuickPointerHandler*> result; + QQuickWindowPrivate *winp = QQuickWindowPrivate::get(window); + if (QQuickPointerDevice* device = QQuickPointerDevice::touchDevice(touchDevice)) { + QQuickPointerEvent *pointerEvent = winp->pointerEventInstance(device); + for (int i = 0; i < pointerEvent->pointCount(); ++i) { + QQuickEventPoint *eventPoint = pointerEvent->point(i); + QVector<QPointer <QQuickPointerHandler> > passives = eventPoint->passiveGrabbers(); + if (!pointId || eventPoint->pointId() == pointId) { + for (auto it = passives.constBegin(); it != passives.constEnd(); ++it) + result << it->data(); + } + } + } + return result; +} + +void tst_DragHandler::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); +} + +void tst_DragHandler::defaultPropertyValues() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "draggables.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *ball = window->rootObject()->childItems().first(); + QVERIFY(ball); + QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler); + + QCOMPARE(dragHandler->acceptedButtons(), Qt::LeftButton); + QCOMPARE(dragHandler->translation(), QVector2D()); + QCOMPARE(dragHandler->point().position(), QPointF()); + QCOMPARE(dragHandler->point().scenePosition(), QPointF()); + QCOMPARE(dragHandler->point().pressPosition(), QPointF()); + QCOMPARE(dragHandler->point().scenePressPosition(), QPointF()); + QCOMPARE(dragHandler->point().sceneGrabPosition(), QPointF()); +} + +void tst_DragHandler::touchDrag() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "draggables.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *ball = window->rootObject()->childItems().first(); + QVERIFY(ball); + QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler); + + QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); + + QPointF ballCenter = ball->clipRect().center(); + QPointF scenePressPos = ball->mapToScene(ballCenter); + QPoint p1 = scenePressPos.toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!dragHandler->active()); + QCOMPARE(dragHandler->point().position(), ballCenter); + QCOMPARE(dragHandler->point().pressPosition(), ballCenter); + QCOMPARE(dragHandler->point().scenePosition(), scenePressPos); + QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!dragHandler->active()); + p1 += QPoint(1, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(dragHandler->active()); + QCOMPARE(translationChangedSpy.count(), 0); + QCOMPARE(dragHandler->translation().x(), 0.0); + QPointF sceneGrabPos = p1; + QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos); + p1 += QPoint(19, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(dragHandler->active()); + QCOMPARE(dragHandler->point().position(), ballCenter); + QCOMPARE(dragHandler->point().pressPosition(), ballCenter); + QCOMPARE(dragHandler->point().scenePosition(), ball->mapToScene(ballCenter)); + QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos); + QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos); + QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); + QCOMPARE(dragHandler->translation().y(), 0.0); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!dragHandler->active()); + QCOMPARE(dragHandler->point().pressedButtons(), Qt::NoButton); + QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); + QCOMPARE(translationChangedSpy.count(), 1); +} + +void tst_DragHandler::mouseDrag() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "draggables.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *ball = window->rootObject()->childItems().first(); + QVERIFY(ball); + QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler); + + QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); + + QPointF ballCenter = ball->clipRect().center(); + QPointF scenePressPos = ball->mapToScene(ballCenter); + QPoint p1 = scenePressPos.toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QVERIFY(!dragHandler->active()); + QCOMPARE(dragHandler->point().position(), ballCenter); + QCOMPARE(dragHandler->point().pressPosition(), ballCenter); + QCOMPARE(dragHandler->point().scenePosition(), scenePressPos); + QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos); + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QVERIFY(!dragHandler->active()); + p1 += QPoint(1, 0); + QTest::mouseMove(window, p1); + QTRY_VERIFY(dragHandler->active()); + QCOMPARE(translationChangedSpy.count(), 0); + QCOMPARE(dragHandler->translation().x(), 0.0); + QPointF sceneGrabPos = p1; + QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos); + p1 += QPoint(19, 0); + QTest::mouseMove(window, p1); + QTRY_VERIFY(dragHandler->active()); + QCOMPARE(dragHandler->point().position(), ballCenter); + QCOMPARE(dragHandler->point().pressPosition(), ballCenter); + QCOMPARE(dragHandler->point().scenePosition(), ball->mapToScene(ballCenter)); + QCOMPARE(dragHandler->point().scenePressPosition(), scenePressPos); + QCOMPARE(dragHandler->point().sceneGrabPosition(), sceneGrabPos); + QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); + QCOMPARE(dragHandler->translation().y(), 0.0); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!dragHandler->active()); + QCOMPARE(dragHandler->point().pressedButtons(), Qt::NoButton); + QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); + QCOMPARE(translationChangedSpy.count(), 1); +} + +void tst_DragHandler::touchDragMulti() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "draggables.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *ball1 = window->rootObject()->childItems().first(); + QVERIFY(ball1); + QQuickDragHandler *dragHandler1 = ball1->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler1); + QSignalSpy translationChangedSpy1(dragHandler1, SIGNAL(translationChanged())); + + QQuickItem *ball2 = window->rootObject()->childItems().at(1); + QVERIFY(ball2); + QQuickDragHandler *dragHandler2 = ball2->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler2); + QSignalSpy translationChangedSpy2(dragHandler2, SIGNAL(translationChanged())); + + QPointF ball1Center = ball1->clipRect().center(); + QPointF scenePressPos1 = ball1->mapToScene(ball1Center); + QPoint p1 = scenePressPos1.toPoint(); + QPointF ball2Center = ball2->clipRect().center(); + QPointF scenePressPos2 = ball2->mapToScene(ball2Center); + QPoint p2 = scenePressPos2.toPoint(); + + QTest::touchEvent(window, touchDevice).press(1, p1, window).press(2, p2, window); + QQuickTouchUtils::flush(window); + QVERIFY(!dragHandler1->active()); + QCOMPARE(dragHandler1->point().position(), ball1Center); + QCOMPARE(dragHandler1->point().pressPosition(), ball1Center); + QCOMPARE(dragHandler1->point().scenePosition(), scenePressPos1); + QCOMPARE(dragHandler1->point().scenePressPosition(), scenePressPos1); + QVERIFY(!dragHandler2->active()); + QCOMPARE(dragHandler2->point().position(), ball2Center); + QCOMPARE(dragHandler2->point().pressPosition(), ball2Center); + QCOMPARE(dragHandler2->point().scenePosition(), scenePressPos2); + QCOMPARE(dragHandler2->point().scenePressPosition(), scenePressPos2); + p1 += QPoint(dragThreshold, 0); + p2 += QPoint(0, dragThreshold); + QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window); + QQuickTouchUtils::flush(window); + QVERIFY(!dragHandler1->active()); + p1 += QPoint(1, 0); + p2 += QPoint(0, 1); + QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(dragHandler1->active()); + QVERIFY(dragHandler2->active()); + QCOMPARE(translationChangedSpy1.count(), 0); + QCOMPARE(dragHandler1->translation().x(), 0.0); + QPointF sceneGrabPos1 = p1; + QPointF sceneGrabPos2 = p2; + QCOMPARE(dragHandler1->point().sceneGrabPosition(), sceneGrabPos1); + QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2); + p1 += QPoint(19, 0); + p2 += QPoint(0, 19); + QVERIFY(dragHandler2->active()); + QCOMPARE(translationChangedSpy2.count(), 0); + QCOMPARE(dragHandler2->translation().x(), 0.0); + QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2); + QTest::touchEvent(window, touchDevice).move(1, p1, window).move(2, p2, window); + QQuickTouchUtils::flush(window); + QVERIFY(dragHandler1->active()); + QVERIFY(dragHandler2->active()); + QCOMPARE(dragHandler1->point().position(), ball1Center); + QCOMPARE(dragHandler1->point().pressPosition(), ball1Center); + QCOMPARE(dragHandler1->point().scenePosition(), ball1->mapToScene(ball1Center)); + QCOMPARE(dragHandler1->point().scenePressPosition(), scenePressPos1); + QCOMPARE(dragHandler1->point().sceneGrabPosition(), sceneGrabPos1); + QCOMPARE(dragHandler1->translation().x(), dragThreshold + 20.0); + QCOMPARE(dragHandler1->translation().y(), 0.0); + QCOMPARE(dragHandler2->point().position(), ball2Center); + QCOMPARE(dragHandler2->point().pressPosition(), ball2Center); + QCOMPARE(dragHandler2->point().scenePosition(), ball2->mapToScene(ball2Center)); + QCOMPARE(dragHandler2->point().scenePressPosition(), scenePressPos2); + QCOMPARE(dragHandler2->point().sceneGrabPosition(), sceneGrabPos2); + QCOMPARE(dragHandler2->translation().x(), 0.0); + QCOMPARE(dragHandler2->translation().y(), dragThreshold + 20.0); + QTest::touchEvent(window, touchDevice).release(1, p1, window).stationary(2); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!dragHandler1->active()); + QVERIFY(dragHandler2->active()); + QCOMPARE(dragHandler1->point().pressedButtons(), Qt::NoButton); + QCOMPARE(ball1->mapToScene(ball1Center).toPoint(), p1); + QCOMPARE(translationChangedSpy1.count(), 1); + QTest::touchEvent(window, touchDevice).release(2, p2, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!dragHandler2->active()); + QCOMPARE(ball2->mapToScene(ball2Center).toPoint(), p2); + QCOMPARE(translationChangedSpy2.count(), 1); +} + +void tst_DragHandler::touchDragMultiSliders_data() +{ + QTest::addColumn<int>("sliderRow"); + QTest::addColumn<QVector<int> >("whichSliders"); + QTest::addColumn<QVector<int> >("startingCenterOffsets"); + QTest::addColumn<QVector<QVector2D> >("movements"); + + QTest::newRow("Drag Knob: start on the knobs, drag down") << + 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; + QTest::newRow("Drag Knob: start on the knobs, drag diagonally downward") << + 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; + QTest::newRow("Drag Anywhere: start on the knobs, drag down") << + 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; + QTest::newRow("Drag Anywhere: start on the knobs, drag diagonally downward") << + 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; + // TODO these next two fail because the DragHandler grabs when a finger + // drags across it from outside, but should rather start only if it is pressed inside +// QTest::newRow("Drag Knob: start above the knobs, drag down") << +// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {0, 40}, {0, 60}, {0, 80} }; +// QTest::newRow("Drag Knob: start above the knobs, drag diagonally downward") << +// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; + QTest::newRow("Drag Anywhere: start above the knobs, drag down") << + 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; + QTest::newRow("Drag Anywhere: start above the knobs, drag diagonally downward") << + 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; +} + +void tst_DragHandler::touchDragMultiSliders() +{ + QFETCH(int, sliderRow); + QFETCH(QVector<int>, whichSliders); + QFETCH(QVector<int>, startingCenterOffsets); + QFETCH(QVector<QVector2D>, movements); + const int moveCount = 8; + + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "multipleSliders.qml"); + QQuickView * window = windowPtr.data(); + QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice); + + QQuickRepeater *rowRepeater = window->rootObject()->findChildren<QQuickRepeater *>()[sliderRow]; + QVector<QQuickItem *> knobs; + QVector<QQuickDragHandler *> dragHandlers; + QVector<QQuickTapHandler *> tapHandlers; + QVector<QPointF> startPoints; + for (int sli : whichSliders) { + QQuickItem *slider = rowRepeater->itemAt(sli); + QVERIFY(slider); + dragHandlers << slider->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandlers[sli]); + tapHandlers << slider->findChild<QQuickTapHandler*>(); + QVERIFY(tapHandlers[sli]); + knobs << tapHandlers[sli]->parentItem(); + QPointF startPoint = knobs[sli]->mapToScene(knobs[sli]->clipRect().center()); + startPoint.setY(startPoint.y() + startingCenterOffsets[sli]); + startPoints << startPoint; + qCDebug(lcPointerTests) << "row" << sliderRow << "slider" << sli << slider->objectName() << + "start" << startingCenterOffsets[sli] << startPoints[sli]; + } + QVector<QPointF> touchPoints = startPoints; + + // Press + for (int sli : whichSliders) + touch.press(sli, touchPoints[sli].toPoint()); + touch.commit(); + + // Moves + for (int m = 0; m < moveCount; ++m) { + for (int sli : whichSliders) { + QVector2D incr = movements[sli] / moveCount; + touchPoints[sli] += incr.toPointF(); + touch.move(sli, touchPoints[sli].toPoint()); + } + touch.commit(); + QQuickTouchUtils::flush(window); + } + + // Check that they moved to where they should: since the slider is constrained, + // only the y component should have an effect; knobs should not come out of their "grooves" + for (int sli : whichSliders) { + QPoint endPosition = knobs[sli]->mapToScene(knobs[sli]->clipRect().center()).toPoint(); + QPoint expectedEndPosition(startPoints[sli].x(), startPoints[sli].y() + movements[sli].y()); + if (sliderRow == 0 && qAbs(startingCenterOffsets[sli]) > knobs[sli]->height() / 2) + expectedEndPosition = startPoints[sli].toPoint(); + qCDebug(lcPointerTests) << "slider " << knobs[sli]->objectName() << "started @" << startPoints[sli] + << "tried to move by" << movements[sli] << "ended up @" << endPosition << "expected" << expectedEndPosition; + QTRY_COMPARE(endPosition, expectedEndPosition); + } + + // Release + for (int sli : whichSliders) + touch.release(sli, touchPoints[sli].toPoint()); + touch.commit(); +} + +void tst_DragHandler::touchPassiveGrabbers_data() +{ + QTest::addColumn<QString>("itemName"); + QTest::addColumn<QStringList>("expectedPassiveGrabberNames"); + + QTest::newRow("Drag And Tap") << "dragAndTap" << QStringList({"drag", "tap"}); + QTest::newRow("Tap And Drag") << "tapAndDrag" << QStringList({"tap", "drag"}); + QTest::newRow("Drag And Tap (not siblings)") << "dragAndTapNotSiblings" << QStringList({"drag", "tap"}); + QTest::newRow("Tap And Drag (not siblings)") << "tapAndDragNotSiblings" << QStringList({"tap", "drag"}); +} + +void tst_DragHandler::touchPassiveGrabbers() +{ + QFETCH(QString, itemName); + QFETCH(QStringList, expectedPassiveGrabberNames); + + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "simpleTapAndDragHandlers.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *row2 = window->rootObject()->findChild<QQuickItem*>(itemName); + QSet<QQuickPointerHandler *> expectedPassiveGrabbers; + for (QString objectName : expectedPassiveGrabberNames) + expectedPassiveGrabbers << row2->findChild<QQuickPointerHandler*>(objectName); + + QPointF p1 = row2->mapToScene(row2->clipRect().center()); + QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice); + touch.press(1, p1.toPoint()).commit(); + QQuickTouchUtils::flush(window); + + QCOMPARE(passiveGrabbers(window), expectedPassiveGrabbers); + + QQuickDragHandler *dragHandler = nullptr; + for (QQuickPointerHandler *handler: expectedPassiveGrabbers) { + QCOMPARE(static_cast<QQuickSinglePointHandler *>(handler)->point().scenePressPosition(), p1); + QQuickDragHandler *dh = qmlobject_cast<QQuickDragHandler *>(handler); + if (dh) + dragHandler = dh; + } + QVERIFY(dragHandler); + QPointF initialPos = dragHandler->target()->position(); + + p1 += QPointF(50, 50); + touch.move(1, p1.toPoint()).commit(); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(dragHandler->active()); + + p1 += QPointF(50, 50); + touch.move(1, p1.toPoint()).commit(); + QQuickTouchUtils::flush(window); + QPointF movementDelta = dragHandler->target()->position() - initialPos; + qCDebug(lcPointerTests) << "DragHandler moved the target by" << movementDelta; + QVERIFY(movementDelta.x() >= 100); + QVERIFY(movementDelta.y() >= 100); + + QTest::qWait(500); + + touch.release(1, p1.toPoint()); + touch.commit(); + QQuickTouchUtils::flush(window); +} + +QTEST_MAIN(tst_DragHandler) + +#include "tst_qquickdraghandler.moc" + diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml new file mode 100644 index 0000000000..126cf3ff2b --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/singleitem.qml @@ -0,0 +1,28 @@ +import QtQuick 2.8 +import Qt.test 1.0 + +Item { + id: root + objectName: "root Item" + width: 320 + height: 480 + + Rectangle { + objectName: "eventItem's bounds" + anchors.fill: eventItem + color: "lightsteelblue" + } + + EventItem { + id: eventItem + objectName: "eventItem1" + x: 5 + y: 5 + height: 30 + width: 30 + + EventHandler { + objectName: "eventHandler" + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro new file mode 100644 index 0000000000..c386969206 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/qquickpointerhandler.pro @@ -0,0 +1,16 @@ +CONFIG += testcase + +TARGET = tst_qquickpointerhandler +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_qquickpointerhandler.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +# OTHER_FILES += data/foo.qml + diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp new file mode 100644 index 0000000000..d38ae3190e --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -0,0 +1,580 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <private/qdebug_p.h> +#include <QtGui/qstylehints.h> +#include <QtQuick/private/qquickpointerhandler_p.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class Event +{ + Q_GADGET +public: + enum Destination { + FilterDestination, + MouseDestination, + TouchDestination, + HandlerDestination + }; + Q_ENUM(Destination) + + Event(Destination d, QEvent::Type t, Qt::TouchPointState s, int grabState, QPointF item, QPointF scene) + : destination(d), type(t), state(s), grabState(grabState), posWrtItem(item), posWrtScene(scene) + {} + + Destination destination; + QEvent::Type type; // if this represents a QEvent that was received + Qt::TouchPointState state; // if this represents an event (pointer, touch or mouse) + int grabState; // if this represents an onGrabChanged() notification (QQuickEventPoint::GrabState) + QPointF posWrtItem; + QPointF posWrtScene; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const class Event &event) { + QDebugStateSaver saver(dbg); + dbg.nospace(); + dbg << "Event("; + QtDebugUtils::formatQEnum(dbg, event.destination); + dbg << ' '; + QtDebugUtils::formatQEnum(dbg, event.type); + dbg << ' '; + QtDebugUtils::formatQEnum(dbg, event.state); + if (event.grabState) { + dbg << ' '; + QtDebugUtils::formatQEnum(dbg, QQuickEventPoint::GrabState(event.grabState)); + } + dbg << " @ "; + QtDebugUtils::formatQPoint(dbg, event.posWrtItem); + dbg << " S "; + QtDebugUtils::formatQPoint(dbg, event.posWrtScene); + dbg << ')'; + return dbg; +} +#endif + +enum { + NoGrab = 0, +}; + +class EventItem : public QQuickItem +{ + Q_OBJECT +public: + EventItem(QQuickItem *parent = 0) + : QQuickItem(parent), acceptPointer(false), grabPointer(false), acceptMouse(false), acceptTouch(false), filterTouch(false) + {} + + inline int grabState(bool accept, Qt::TouchPointState state) { + return (accept && (state != Qt::TouchPointReleased)) ? (int)QQuickEventPoint::GrabExclusive : (int)NoGrab; + } + + void touchEvent(QTouchEvent *event) + { + qCDebug(lcPointerTests) << event << "will accept?" << acceptTouch; + for (const QTouchEvent::TouchPoint &tp : event->touchPoints()) + eventList.append(Event(Event::TouchDestination, event->type(), tp.state(), grabState(acceptTouch, tp.state()), tp.pos(), tp.scenePos())); + event->setAccepted(acceptTouch); + } + void mousePressEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointPressed, grabState(acceptMouse, Qt::TouchPointPressed), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseMoveEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointMoved, grabState(acceptMouse, Qt::TouchPointMoved), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseReleaseEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointReleased, grabState(acceptMouse, Qt::TouchPointReleased), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + void mouseDoubleClickEvent(QMouseEvent *event) + { + qCDebug(lcPointerTests) << event; + eventList.append(Event(Event::MouseDestination, event->type(), Qt::TouchPointPressed, grabState(acceptMouse, Qt::TouchPointPressed), event->pos(), event->windowPos())); + event->setAccepted(acceptMouse); + } + + void mouseUngrabEvent() + { + qCDebug(lcPointerTests); + eventList.append(Event(Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive, QPoint(0,0), QPoint(0,0))); + } + + bool event(QEvent *event) + { + qCDebug(lcPointerTests) << event; + return QQuickItem::event(event); + } + + QList<Event> eventList; + bool acceptPointer; + bool grabPointer; + bool acceptMouse; + bool acceptTouch; + bool filterTouch; // when used as event filter + + bool eventFilter(QObject *o, QEvent *event) + { + qCDebug(lcPointerTests) << event << o; + if (event->type() == QEvent::TouchBegin || + event->type() == QEvent::TouchUpdate || + event->type() == QEvent::TouchCancel || + event->type() == QEvent::TouchEnd) { + QTouchEvent *touch = static_cast<QTouchEvent*>(event); + for (const QTouchEvent::TouchPoint &tp : touch->touchPoints()) + eventList.append(Event(Event::FilterDestination, event->type(), tp.state(), QQuickEventPoint::GrabExclusive, tp.pos(), tp.scenePos())); + if (filterTouch) + event->accept(); + return true; + } + return false; + } +}; + +#define QCOMPARE_EVENT(i, d, t, s, g) \ + {\ + const Event &event = eventItem1->eventList.at(i);\ + QCOMPARE(event.destination, d);\ + QCOMPARE(event.type, t);\ + QCOMPARE(event.state, s);\ + QCOMPARE(event.grabState, g);\ + }\ + +class EventHandler : public QQuickPointerHandler +{ + void handlePointerEventImpl(QQuickPointerEvent *event) override + { + QQuickPointerHandler::handlePointerEventImpl(event); + if (!enabled()) + return; + EventItem *item = static_cast<EventItem *>(target()); + qCDebug(lcPointerTests) << item->objectName() << event; + int c = event->pointCount(); + for (int i = 0; i < c; ++i) { + QQuickEventPoint *point = event->point(i); + if (item->acceptPointer) + point->setAccepted(item->acceptPointer); // does NOT imply a grab + if (item->grabPointer) + setExclusiveGrab(point, true); + qCDebug(lcPointerTests) << " " << i << ":" << point << "accepted?" << item->acceptPointer << "grabbed?" << (point->exclusiveGrabber() == this); + item->eventList.append(Event(Event::HandlerDestination, QEvent::Pointer, + static_cast<Qt::TouchPointState>(point->state()), + item->grabPointer ? (int)QQuickEventPoint::GrabExclusive : (int)NoGrab, + eventPos(point), point->scenePosition())); + } + } + + void onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) override + { + EventItem *item = static_cast<EventItem *>(target()); + item->eventList.append(Event(Event::HandlerDestination, QEvent::None, + static_cast<Qt::TouchPointState>(point->state()), stateChange, eventPos(point), point->scenePosition())); + } +}; + +class tst_PointerHandlers : public QQmlDataTest +{ + Q_OBJECT +public: + tst_PointerHandlers() + :touchDevice(QTest::createTouchDevice()) + {} + +private slots: + void initTestCase(); + + void touchEventDelivery(); + void mouseEventDelivery(); + void touchReleaseOutside_data(); + void touchReleaseOutside(); + +protected: + bool eventFilter(QObject *, QEvent *event) + { + Qt::TouchPointState tpState; + switch (event->type()) { + case QEvent::MouseButtonPress: + tpState = Qt::TouchPointPressed; + break; + case QEvent::MouseMove: + tpState = Qt::TouchPointMoved; + break; + case QEvent::MouseButtonRelease: + tpState = Qt::TouchPointReleased; + break; + default: + // So far we aren't recording filtered touch events here - they would be quite numerous in some cases + return false; + } + QMouseEvent *me = static_cast<QMouseEvent*>(event); + filteredEventList.append(Event(Event::FilterDestination, event->type(), tpState, + 0, me->pos(), me->globalPos())); + return false; + } + +private: + void createView(QScopedPointer<QQuickView> &window, const char *fileName); + QTouchDevice *touchDevice; + QList<Event> filteredEventList; +}; + +void tst_PointerHandlers::createView(QScopedPointer<QQuickView> &window, const char *fileName) +{ + window.reset(new QQuickView); +// window->setGeometry(0,0,240,320); + window->setSource(testFileUrl(fileName)); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtil::centerOnScreen(window.data()); + QQuickViewTestUtil::moveMouseAway(window.data()); + + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(window->rootObject() != 0); +} + +void tst_PointerHandlers::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); + qmlRegisterType<EventItem>("Qt.test", 1, 0, "EventItem"); + qmlRegisterType<EventHandler>("Qt.test", 1, 0, "EventHandler"); +} + +void tst_PointerHandlers::touchEventDelivery() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "singleitem.qml"); + QQuickView * window = windowPtr.data(); + + EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); + QVERIFY(eventItem1); + + // Do not accept anything + QPoint p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QTRY_COMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, NoGrab); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 5); + QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab); + eventItem1->eventList.clear(); + + // Accept touch + eventItem1->acceptTouch = true; + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(QQuickPointerDevice::touchDevices().at(0)); + QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), eventItem1); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab); + QCOMPARE_EVENT(3, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(5, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + QCOMPARE(window->mouseGrabberItem(), eventItem1); + + QPointF localPos = eventItem1->mapFromScene(p1); + QPointF scenePos = p1; // item is at 0,0 + QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos); + QCOMPARE(eventItem1->eventList.at(2).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(2).posWrtScene, scenePos); + + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab); + QCOMPARE_EVENT(4, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, NoGrab); + QCOMPARE_EVENT(5, Event::MouseDestination, QEvent::MouseMove, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 10); + QCOMPARE_EVENT(6, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(7, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(8, Event::MouseDestination, QEvent::MouseButtonRelease, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(9, Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse buttons but not the touch event + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = false; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, NoGrab); + QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 5); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept touch + eventItem1->acceptTouch = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::TouchDestination, QEvent::TouchBegin, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, NoGrab); + QCOMPARE_EVENT(3, Event::TouchDestination, QEvent::TouchUpdate, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(5, Event::TouchDestination, QEvent::TouchEnd, Qt::TouchPointReleased, NoGrab); + eventItem1->eventList.clear(); + + // Accept pointer events + eventItem1->acceptPointer = true; + eventItem1->grabPointer = true; + p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::None, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + p1 += QPoint(10, 0); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + QCOMPARE(eventItem1->eventList.size(), 5); + qCDebug(lcPointerTests) << eventItem1->eventList; + QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, QQuickEventPoint::GrabExclusive); + QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::None, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive); + eventItem1->eventList.clear(); +} + +void tst_PointerHandlers::mouseEventDelivery() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "singleitem.qml"); + QQuickView * window = windowPtr.data(); + + EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); + QVERIFY(eventItem1); + + // Do not accept anything + QPoint p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 3); + QTest::mouseRelease(window, Qt::LeftButton); + QCOMPARE(eventItem1->eventList.size(), 3); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Accept mouse + eventItem1->acceptTouch = false; + eventItem1->acceptMouse = true; + eventItem1->setAcceptedMouseButtons(Qt::LeftButton); + p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab); + QCOMPARE_EVENT(1, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + QCOMPARE(window->mouseGrabberItem(), eventItem1); + + QPointF localPos = eventItem1->mapFromScene(p1); + QPointF scenePos = p1; // item is at 0,0 + QCOMPARE(eventItem1->eventList.at(0).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(0).posWrtScene, scenePos); + QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos); + QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos); + + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseMove, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 5); + QCOMPARE_EVENT(3, Event::MouseDestination, QEvent::MouseButtonRelease, Qt::TouchPointReleased, NoGrab); + QCOMPARE_EVENT(4, Event::MouseDestination, QEvent::UngrabMouse, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive); + eventItem1->eventList.clear(); + + // wait to avoid getting a double click event + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); + + // Grab pointer events + eventItem1->acceptMouse = false; + eventItem1->acceptPointer = true; + eventItem1->grabPointer = true; + p1 = QPoint(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_COMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::None, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, QQuickEventPoint::GrabExclusive); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, 0); + p1 += QPoint(10, 0); + QTest::mouseMove(window, p1); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointMoved, QQuickEventPoint::GrabExclusive); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QCOMPARE(eventItem1->eventList.size(), 6); + QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointReleased, QQuickEventPoint::GrabExclusive); + QCOMPARE_EVENT(5, Event::HandlerDestination, QEvent::None, Qt::TouchPointReleased, QQuickEventPoint::UngrabExclusive); + eventItem1->eventList.clear(); +} + +void tst_PointerHandlers::touchReleaseOutside_data() +{ + QTest::addColumn<bool>("acceptPointer"); + QTest::addColumn<bool>("grabPointer"); + QTest::addColumn<int>("eventCount"); + QTest::addColumn<int>("endIndexToTest"); + QTest::addColumn<int>("endDestination"); // Event::Destination + QTest::addColumn<int>("endType"); // QEvent::Type + QTest::addColumn<int>("endState"); // Qt::TouchPointState + QTest::addColumn<int>("endGrabState"); // Qt::TouchPointState + + QTest::newRow("reject and ignore") << false << false << 6 << 5 << (int)Event::TouchDestination + << (int)QEvent::TouchEnd << (int)Qt::TouchPointReleased << (int)NoGrab; + QTest::newRow("reject and grab") << false << true << 5 << 4 << (int)Event::HandlerDestination + << (int)QEvent::None << (int)Qt::TouchPointReleased << (int)QQuickEventPoint::UngrabExclusive; + QTest::newRow("accept and ignore") << true << false << 1 << 0 << (int)Event::HandlerDestination + << (int)QEvent::Pointer << (int)Qt::TouchPointPressed << (int)NoGrab; + QTest::newRow("accept and grab") << true << true << 5 << 4 << (int)Event::HandlerDestination + << (int)QEvent::None << (int)Qt::TouchPointReleased << (int)QQuickEventPoint::UngrabExclusive; +} + +void tst_PointerHandlers::touchReleaseOutside() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "singleitem.qml"); + QQuickView * window = windowPtr.data(); + + QFETCH(bool, acceptPointer); + QFETCH(bool, grabPointer); + QFETCH(int, eventCount); + QFETCH(int, endIndexToTest); + QFETCH(int, endDestination); + QFETCH(int, endType); + QFETCH(int, endState); + QFETCH(int, endGrabState); + + EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); + QVERIFY(eventItem1); + + eventItem1->acceptTouch = true; + eventItem1->acceptPointer = acceptPointer; + eventItem1->grabPointer = grabPointer; + + QPoint p1 = QPoint(20, 20); + QTest::touchEvent(window, touchDevice).press(0, p1, window); + QQuickTouchUtils::flush(window); + p1.setX(eventItem1->mapToScene(eventItem1->clipRect().bottomRight()).x() + 10); + QTest::touchEvent(window, touchDevice).move(0, p1, window); + QTest::touchEvent(window, touchDevice).release(0, p1, window); + QQuickTouchUtils::flush(window); + qCDebug(lcPointerTests) << eventItem1->eventList; + QCOMPARE(eventItem1->eventList.size(), eventCount); + QCOMPARE_EVENT(endIndexToTest, endDestination, endType, endState, endGrabState); +} + +QTEST_MAIN(tst_PointerHandlers) + +#include "tst_qquickpointerhandler.moc" + diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml new file mode 100644 index 0000000000..0b7713bd13 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.8 +import Qt.labs.handlers 1.0 + +Rectangle { + id: root + property alias label: label.text + property alias pressed: tap.pressed + property bool checked: false + property alias gesturePolicy: tap.gesturePolicy + signal tapped + + width: label.implicitWidth * 1.5; height: label.implicitHeight * 2.0 + border.color: "#9f9d9a"; border.width: 1; radius: height / 4; antialiasing: true + + gradient: Gradient { + GradientStop { position: 0.0; color: tap.pressed ? "#b8b5b2" : "#efebe7" } + GradientStop { position: 1.0; color: "#b8b5b2" } + } + + TapHandler { + id: tap + objectName: label.text + longPressThreshold: 100 // CI can be insanely slow, so don't demand a timely release to generate onTapped + onTapped: { + tapFlash.start() + root.tapped() + } + } + + Text { + id: label + font.pointSize: 14 + text: "Button" + anchors.centerIn: parent + } + + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 2; radius: root.radius; antialiasing: true + opacity: tapFlash.running ? 1 : 0 + FlashAnimation on visible { id: tapFlash } + } + + Rectangle { + objectName: "expandingCircle" + radius: tap.timeHeld * 100 + visible: radius > 0 && tap.pressed + border.width: 3 + border.color: "blue" + color: "transparent" + width: radius * 2 + height: radius * 2 + x: tap.point.scenePressPosition.x - radius + y: tap.point.scenePressPosition.y - radius + opacity: 0.25 + Component.onCompleted: parent = root.parent + } +} diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml new file mode 100644 index 0000000000..158a02b7a6 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/FlashAnimation.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +SequentialAnimation { + id: tapFlash + running: false + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } + PauseAnimation { duration: 100 } + PropertyAction { value: false } + PauseAnimation { duration: 100 } + PropertyAction { value: true } +} diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml new file mode 100644 index 0000000000..219d4a70a8 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttonOverrideHandler.qml @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.handlers 1.0 + +Item { + width: 320 + height: 240 + Button { + id: button + objectName: "Overridden" + label: "Overridden" + x: 10; y: 10; width: parent.width - 20; height: 40 + TapHandler { + gesturePolicy: TapHandler.ReleaseWithinBounds + objectName: "override" + onTapped: button.tapped() + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml new file mode 100644 index 0000000000..1df9bb9b96 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/buttons.qml @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.handlers 1.0 + +Item { + width: 320 + height: 240 + Button { + objectName: "DragThreshold" + label: "DragThreshold" + x: 10; y: 10; width: parent.width - 20; height: 40 + gesturePolicy: TapHandler.DragThreshold + } + Button { + objectName: "WithinBounds" + label: "WithinBounds" + x: 10; y: 60; width: parent.width - 20; height: 40 + gesturePolicy: TapHandler.WithinBounds + } + Button { + objectName: "ReleaseWithinBounds" + label: "ReleaseWithinBounds" + x: 10; y: 110; width: parent.width - 20; height: 40 + gesturePolicy: TapHandler.ReleaseWithinBounds + } +} diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro b/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro new file mode 100644 index 0000000000..b41a94b55e --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/qquicktaphandler.pro @@ -0,0 +1,16 @@ +CONFIG += testcase + +TARGET = tst_qquicktaphandler +QT += core-private gui-private qml-private quick-private testlib + +macos:CONFIG -= app_bundle + +SOURCES += tst_qquicktaphandler.cpp + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +# OTHER_FILES += data/foo.qml + diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp new file mode 100644 index 0000000000..9e3261f7b2 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QtGui/qstylehints.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickpointerhandler_p.h> +#include <QtQuick/private/qquicktaphandler_p.h> +#include <qpa/qwindowsysteminterface.h> + +#include <private/qquickwindow_p.h> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlproperty.h> + +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class tst_TapHandler : public QQmlDataTest +{ + Q_OBJECT +public: + tst_TapHandler() + :touchDevice(QTest::createTouchDevice()) + {} + +private slots: + void initTestCase(); + + void touchGesturePolicyDragThreshold(); + void mouseGesturePolicyDragThreshold(); + void touchGesturePolicyWithinBounds(); + void mouseGesturePolicyWithinBounds(); + void touchGesturePolicyReleaseWithinBounds(); + void mouseGesturePolicyReleaseWithinBounds(); + void touchMultiTap(); + void mouseMultiTap(); + void touchLongPress(); + void mouseLongPress(); + void buttonsMultiTouch(); + void componentUserBehavioralOverride(); + +private: + void createView(QScopedPointer<QQuickView> &window, const char *fileName); + QTouchDevice *touchDevice; +}; + +void tst_TapHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) +{ + window.reset(new QQuickView); + window->setSource(testFileUrl(fileName)); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtil::centerOnScreen(window.data()); + QQuickViewTestUtil::moveMouseAway(window.data()); + + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(window->rootObject() != 0); +} + +void tst_TapHandler::initTestCase() +{ + // This test assumes that we don't get synthesized mouse events from QGuiApplication + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); + + QQmlDataTest::initTestCase(); +} + +void tst_TapHandler::touchGesturePolicyDragThreshold() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(buttonDragThreshold); + QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped())); + + // DragThreshold button stays pressed while touchpoint stays within dragThreshold, emits tapped on release + QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 1); + + // DragThreshold button is no longer pressed if touchpoint goes beyond dragThreshold + dragThresholdTappedSpy.clear(); + p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(1, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!buttonDragThreshold->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 0); +} + +void tst_TapHandler::mouseGesturePolicyDragThreshold() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(buttonDragThreshold); + QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped())); + + // DragThreshold button stays pressed while mouse stays within dragThreshold, emits tapped on release + QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); + QTRY_COMPARE(dragThresholdTappedSpy.count(), 1); + + // DragThreshold button is no longer pressed if mouse goes beyond dragThreshold + dragThresholdTappedSpy.clear(); + p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(dragThreshold, 0); + QTest::mouseMove(window, p1); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + p1 += QPoint(1, 0); + QTest::mouseMove(window, p1); + QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QVERIFY(!buttonDragThreshold->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 0); +} + +void tst_TapHandler::touchGesturePolicyWithinBounds() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds"); + QVERIFY(buttonWithinBounds); + QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped())); + + // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release + QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool()); + p1 += QPoint(50, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 1); + + // WithinBounds button is no longer pressed if touchpoint leaves bounds + withinBoundsTappedSpy.clear(); + p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool()); + p1 += QPoint(0, 100); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(!buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 0); +} + +void tst_TapHandler::mouseGesturePolicyWithinBounds() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds"); + QVERIFY(buttonWithinBounds); + QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped())); + + // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release + QPoint p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool()); + p1 += QPoint(50, 0); + QTest::mouseMove(window, p1); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 1); + + // WithinBounds button is no longer pressed if touchpoint leaves bounds + withinBoundsTappedSpy.clear(); + p1 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool()); + p1 += QPoint(0, 100); + QTest::mouseMove(window, p1); + QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QVERIFY(!buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 0); +} + +void tst_TapHandler::touchGesturePolicyReleaseWithinBounds() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds"); + QVERIFY(buttonReleaseWithinBounds); + QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped())); + + // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere, + // then if it comes back within bounds, emits tapped on release + QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(50, 0); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(250, 100); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint(); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1); + + // ReleaseWithinBounds button does not emit tapped if released out of bounds + releaseWithinBoundsTappedSpy.clear(); + p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(0, 100); + QTest::touchEvent(window, touchDevice).move(1, p1, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0); +} + +void tst_TapHandler::mouseGesturePolicyReleaseWithinBounds() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds"); + QVERIFY(buttonReleaseWithinBounds); + QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped())); + + // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere, + // then if it comes back within bounds, emits tapped on release + QPoint p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(50, 0); + QTest::mouseMove(window, p1); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(250, 100); + QTest::mouseMove(window, p1); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 = buttonReleaseWithinBounds->mapToScene(QPointF(25, 15)).toPoint(); + QTest::mouseMove(window, p1); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1); + + // ReleaseWithinBounds button does not emit tapped if released out of bounds + releaseWithinBoundsTappedSpy.clear(); + p1 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + p1 += QPoint(0, 100); + QTest::mouseMove(window, p1); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0); +} + +void tst_TapHandler::touchMultiTap() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(button); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + // Tap once + QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 1); + + // Tap again in exactly the same place (not likely with touch in the real world) + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 2); + + // Tap a third time, nearby + p1 += QPoint(dragThreshold, dragThreshold); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 3); + + // Tap a fourth time, drifting farther away + p1 += QPoint(dragThreshold, dragThreshold); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 4); +} + +void tst_TapHandler::mouseMultiTap() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(button); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + + // Tap once + QPoint p1 = button->mapToScene(QPointF(2, 2)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 1); + + // Tap again in exactly the same place (not likely with touch in the real world) + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 2); + + // Tap a third time, nearby + p1 += QPoint(dragThreshold, dragThreshold); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 3); + + // Tap a fourth time, drifting farther away + p1 += QPoint(dragThreshold, dragThreshold); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 4); +} + +void tst_TapHandler::touchLongPress() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(button); + QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold"); + QVERIFY(tapHandler); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged())); + QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged())); + QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed())); + + // Reduce the threshold so that we can get a long press quickly + tapHandler->setLongPressThreshold(0.5); + QCOMPARE(longPressThresholdChangedSpy.count(), 1); + + // Press and hold + QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(button->property("pressed").toBool()); + QTRY_COMPARE(longPressedSpy.count(), 1); + timeHeldSpy.wait(); // the longer we hold it, the more this will occur + qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times"; + QVERIFY(timeHeldSpy.count() > 0); + QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere + + // Release and verify that tapped was not emitted + QTest::touchEvent(window, touchDevice).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 0); +} + +void tst_TapHandler::mouseLongPress() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(button); + QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>("DragThreshold"); + QVERIFY(tapHandler); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged())); + QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged())); + QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed())); + + // Reduce the threshold so that we can get a long press quickly + tapHandler->setLongPressThreshold(0.5); + QCOMPARE(longPressThresholdChangedSpy.count(), 1); + + // Press and hold + QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_VERIFY(button->property("pressed").toBool()); + QTRY_COMPARE(longPressedSpy.count(), 1); + timeHeldSpy.wait(); // the longer we hold it, the more this will occur + qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times"; + QVERIFY(timeHeldSpy.count() > 0); + QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere + + // Release and verify that tapped was not emitted + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1, 500); + QTRY_VERIFY(!button->property("pressed").toBool()); + QCOMPARE(tappedSpy.count(), 0); +} + +void tst_TapHandler::buttonsMultiTouch() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttons.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>("DragThreshold"); + QVERIFY(buttonDragThreshold); + QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped())); + + QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>("WithinBounds"); + QVERIFY(buttonWithinBounds); + QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped())); + + QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>("ReleaseWithinBounds"); + QVERIFY(buttonReleaseWithinBounds); + QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped())); + + // can press multiple buttons at the same time + QPoint p1 = buttonDragThreshold->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + QPoint p2 = buttonWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).stationary(1).press(2, p2, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool()); + QPoint p3 = buttonReleaseWithinBounds->mapToScene(QPointF(20, 20)).toPoint(); + QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).press(3, p3, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + + // can release top button and press again: others stay pressed the whole time + QTest::touchEvent(window, touchDevice).stationary(2).stationary(3).release(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 1); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 0); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0); + QTest::touchEvent(window, touchDevice).stationary(2).stationary(3).press(1, p1, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + + // can release middle button and press again: others stay pressed the whole time + QTest::touchEvent(window, touchDevice).stationary(1).stationary(3).release(2, p2, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 1); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 1); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0); + QTest::touchEvent(window, touchDevice).stationary(1).stationary(3).press(2, p2, window); + QQuickTouchUtils::flush(window); + QVERIFY(buttonDragThreshold->property("pressed").toBool()); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); + + // can release bottom button and press again: others stay pressed the whole time + QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).release(3, p3, window); + QQuickTouchUtils::flush(window); + QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QCOMPARE(withinBoundsTappedSpy.count(), 1); + QVERIFY(!buttonReleaseWithinBounds->property("pressed").toBool()); + QCOMPARE(dragThresholdTappedSpy.count(), 1); + QTest::touchEvent(window, touchDevice).stationary(1).stationary(2).press(3, p3, window); + QQuickTouchUtils::flush(window); + QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool()); + QVERIFY(buttonWithinBounds->property("pressed").toBool()); + QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool()); +} + +void tst_TapHandler::componentUserBehavioralOverride() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "buttonOverrideHandler.qml"); + QQuickView * window = windowPtr.data(); + + QQuickItem *button = window->rootObject()->findChild<QQuickItem*>("Overridden"); + QVERIFY(button); + QQuickTapHandler *innerTapHandler = button->findChild<QQuickTapHandler*>("Overridden"); + QVERIFY(innerTapHandler); + QQuickTapHandler *userTapHandler = button->findChild<QQuickTapHandler*>("override"); + QVERIFY(userTapHandler); + QSignalSpy tappedSpy(button, SIGNAL(tapped())); + QSignalSpy innerGrabChangedSpy(innerTapHandler, SIGNAL(grabChanged(QQuickEventPoint *))); + QSignalSpy userGrabChangedSpy(userTapHandler, SIGNAL(grabChanged(QQuickEventPoint *))); + QSignalSpy innerPressedChangedSpy(innerTapHandler, SIGNAL(pressedChanged())); + QSignalSpy userPressedChangedSpy(userTapHandler, SIGNAL(pressedChanged())); + + // Press + QPoint p1 = button->mapToScene(button->clipRect().center()).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_COMPARE(userPressedChangedSpy.count(), 1); + QCOMPARE(innerPressedChangedSpy.count(), 0); + QCOMPARE(innerGrabChangedSpy.count(), 0); + QCOMPARE(userGrabChangedSpy.count(), 1); + + // Release + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_COMPARE(userPressedChangedSpy.count(), 2); + QCOMPARE(innerPressedChangedSpy.count(), 0); + QCOMPARE(tappedSpy.count(), 1); // only because the override handler makes that happen + QCOMPARE(innerGrabChangedSpy.count(), 0); + QCOMPARE(userGrabChangedSpy.count(), 2); +} + +QTEST_MAIN(tst_TapHandler) + +#include "tst_qquicktaphandler.moc" + 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/BLACKLIST b/tests/auto/quick/qquickflickable/BLACKLIST index f35397f119..826c024732 100644 --- a/tests/auto/quick/qquickflickable/BLACKLIST +++ b/tests/auto/quick/qquickflickable/BLACKLIST @@ -1,7 +1,8 @@ # QTBUG-36804 osx-10.10 +# QTBUG-26696 [rebound] -osx-10.10 +osx [movingAndFlicking:vertical] osx-10.10 [movingAndFlicking:both] 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 4ae021e609..3f66daf87f 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -64,7 +64,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; } @@ -2224,6 +2230,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")); @@ -2240,6 +2247,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)); @@ -2248,23 +2256,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); @@ -2274,23 +2289,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); @@ -2303,23 +2325,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); @@ -2329,37 +2358,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/qquickframebufferobject/BLACKLIST b/tests/auto/quick/qquickframebufferobject/BLACKLIST new file mode 100644 index 0000000000..bd8128c6da --- /dev/null +++ b/tests/auto/quick/qquickframebufferobject/BLACKLIST @@ -0,0 +1,3 @@ +# QTBUG-64470 +[testInvalidate) +osx diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 115fe53430..f6720f96e3 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: @@ -398,11 +400,11 @@ void tst_qquickimage::svg() component.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickImage *obj = qobject_cast<QQuickImage*>(component.create()); QVERIFY(obj != 0); - QCOMPARE(int(obj->width()), 212); // round down: highdpi can give back fractional values + QCOMPARE(obj->width(), 300.0); QCOMPARE(obj->height(), 300.0); obj->setSourceSize(QSize(200,200)); - QCOMPARE(int(obj->width()), 141); // round down: highdpi can give back fractional values + QCOMPARE(obj->width(), 200.0); QCOMPARE(obj->height(), 200.0); delete obj; } @@ -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/BLACKLIST b/tests/auto/quick/qquicklistview/BLACKLIST index d259c11219..8cf8a57eee 100644 --- a/tests/auto/quick/qquicklistview/BLACKLIST +++ b/tests/auto/quick/qquicklistview/BLACKLIST @@ -5,3 +5,6 @@ osx #QTBUG-53863 [populateTransitions] opensuse-42.1 +#QTBUG-65964 +[QTBUG_34576_velocityZero] +osx-10.11 ci diff --git a/tests/auto/quick/qquicklistview/randomsortmodel.cpp b/tests/auto/quick/qquicklistview/randomsortmodel.cpp index 7affb182c0..7375fe0dbe 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::global()->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::global()->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/data/bindings.qml b/tests/auto/quick/qquickloader/data/bindings.qml new file mode 100644 index 0000000000..e0eae2a3e5 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/bindings.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: root + + property alias game: theGame + property alias loader: theLoader + + Item { + id: theGame + + property bool isReady: false + + onStateChanged: { + if (state == "invalid") { + // The Loader's active property is bound to isReady, so none of its bindings + // should be updated when isReady becomes false + isReady = false; + + player.destroy(); + player = null; + } else if (state == "running") { + player = Qt.createQmlObject("import QtQuick 2.0; Item { property color color: 'black' }", root); + + isReady = true; + } + } + + property Item player + } + + Loader { + id: theLoader + active: theGame.isReady + sourceComponent: Rectangle { + width: 400 + height: 400 + color: game.player.color + + property var game: theGame + } + } +} diff --git a/tests/auto/quick/qquickloader/data/parentErrors.qml b/tests/auto/quick/qquickloader/data/parentErrors.qml new file mode 100644 index 0000000000..36607e7f05 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/parentErrors.qml @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: 360 + height: 360 + + property alias loader: loader + + Loader { + id: loader + anchors.fill: parent + } + + property Component component: Rectangle { + width: parent.width + height: parent.height + color: "pink" + } +} 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/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 582ba2aabc..65493f52e2 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -79,8 +79,6 @@ public: tst_QQuickLoader(); private slots: - void cleanup(); - void sourceOrComponent(); void sourceOrComponent_data(); void clear(); @@ -123,19 +121,16 @@ private slots: void sourceComponentGarbageCollection(); -private: - QQmlEngine engine; + void bindings(); + void parentErrors(); }; -void tst_QQuickLoader::cleanup() -{ - // clear components. otherwise we even bypass the test server by using the cache. - engine.clearComponentCache(); -} +Q_DECLARE_METATYPE(QList<QQmlError>) tst_QQuickLoader::tst_QQuickLoader() { qmlRegisterType<SlowComponent>("LoaderTest", 1, 0, "SlowComponent"); + qRegisterMetaType<QList<QQmlError>>(); } void tst_QQuickLoader::sourceOrComponent() @@ -149,6 +144,7 @@ void tst_QQuickLoader::sourceOrComponent() if (error) QTest::ignoreMessage(QtWarningMsg, errorString.toUtf8().constData()); + QQmlEngine engine; QQmlComponent component(&engine); component.setData(QByteArray( "import QtQuick 2.0\n" @@ -170,14 +166,14 @@ void tst_QQuickLoader::sourceOrComponent() "}") , dataDirectoryUrl()); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QCOMPARE(loader->item() == 0, error); QCOMPARE(loader->source(), sourceUrl); QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->status(), error ? QQuickLoader::Error : QQuickLoader::Ready); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), error ? 0: 1); + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), error ? 0: 1); if (!error) { bool sourceComponentIsChildOfLoader = false; @@ -202,8 +198,6 @@ void tst_QQuickLoader::sourceOrComponent() QCOMPARE(loader->property("onItemChangedCount").toInt(), 1); QCOMPARE(loader->property("onLoadedCount").toInt(), error ? 0 : 1); - - delete loader; } void tst_QQuickLoader::sourceOrComponent_data() @@ -225,6 +219,8 @@ void tst_QQuickLoader::sourceOrComponent_data() void tst_QQuickLoader::clear() { + QQmlEngine engine; + { QQmlComponent component(&engine); component.setData(QByteArray( @@ -234,22 +230,20 @@ void tst_QQuickLoader::clear() " Timer { interval: 200; running: true; onTriggered: loader.source = '' }\n" " }") , dataDirectoryUrl()); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QVERIFY(loader->item()); QCOMPARE(loader->progress(), 1.0); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); QTRY_VERIFY(!loader->item()); QCOMPARE(loader->progress(), 0.0); QCOMPARE(loader->status(), QQuickLoader::Null); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); - - delete loader; + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0); } { QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); @@ -264,12 +258,10 @@ void tst_QQuickLoader::clear() QCOMPARE(loader->progress(), 0.0); QCOMPARE(loader->status(), QQuickLoader::Null); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); - - delete item; } { QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); @@ -278,19 +270,18 @@ void tst_QQuickLoader::clear() QCOMPARE(loader->progress(), 1.0); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); - QMetaObject::invokeMethod(item, "clear"); + QMetaObject::invokeMethod(item.data(), "clear"); QVERIFY(!loader->item()); QCOMPARE(loader->progress(), 0.0); QCOMPARE(loader->status(), QQuickLoader::Null); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); - - delete item; } } void tst_QQuickLoader::urlToComponent() { + QQmlEngine engine; QQmlComponent component(&engine); component.setData(QByteArray("import QtQuick 2.0\n" "Loader {\n" @@ -300,22 +291,21 @@ void tst_QQuickLoader::urlToComponent() " Timer { interval: 100; running: true; onTriggered: loader.sourceComponent = myComp }\n" "}" ) , dataDirectoryUrl()); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QTest::qWait(200); QTRY_VERIFY(loader != 0); QVERIFY(loader->item()); QCOMPARE(loader->progress(), 1.0); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); QCOMPARE(loader->width(), 10.0); QCOMPARE(loader->height(), 10.0); - - delete loader; } void tst_QQuickLoader::componentToUrl() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); @@ -330,14 +320,13 @@ void tst_QQuickLoader::componentToUrl() QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); QCOMPARE(loader->width(), 120.0); QCOMPARE(loader->height(), 60.0); - - delete item; } void tst_QQuickLoader::anchoredLoader() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/AnchoredLoader.qml")); - QQuickItem *rootItem = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> rootItem(qobject_cast<QQuickItem*>(component.create())); QVERIFY(rootItem != 0); QQuickItem *loader = rootItem->findChild<QQuickItem*>("loader"); QQuickItem *sourceElement = rootItem->findChild<QQuickItem*>("sourceElement"); @@ -357,8 +346,9 @@ void tst_QQuickLoader::anchoredLoader() void tst_QQuickLoader::sizeLoaderToItem() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/SizeToItem.qml")); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QCOMPARE(loader->width(), 120.0); QCOMPARE(loader->height(), 60.0); @@ -392,14 +382,13 @@ void tst_QQuickLoader::sizeLoaderToItem() loader->setHeight(30); QCOMPARE(rect->width(), 180.0); QCOMPARE(rect->height(), 30.0); - - delete loader; } void tst_QQuickLoader::sizeItemToLoader() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/SizeToLoader.qml")); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QCOMPARE(loader->width(), 200.0); QCOMPARE(loader->height(), 80.0); @@ -428,31 +417,29 @@ void tst_QQuickLoader::sizeItemToLoader() rect->setHeight(45); QCOMPARE(loader->width(), 160.0); QCOMPARE(loader->height(), 45.0); - - delete loader; } void tst_QQuickLoader::noResize() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/NoResize.qml")); - QQuickItem* item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item != 0); QCOMPARE(item->width(), 200.0); QCOMPARE(item->height(), 80.0); - - delete item; } void tst_QQuickLoader::networkRequestUrl() { ThreadedTestHTTPServer server(dataDirectory()); + QQmlEngine engine; QQmlComponent component(&engine); const QString qml = "import QtQuick 2.0\nLoader { property int signalCount : 0; source: \"" + server.baseUrl().toString() + "/Rect120x60.qml\"; onLoaded: signalCount += 1 }"; component.setData(qml.toUtf8(), testFileUrl("../dummy.qml")); if (component.isError()) qDebug() << component.errors(); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QTRY_COMPARE(loader->status(), QQuickLoader::Ready); @@ -460,9 +447,7 @@ void tst_QQuickLoader::networkRequestUrl() QVERIFY(loader->item()); QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->property("signalCount").toInt(), 1); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); - - delete loader; + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); } /* XXX Component waits until all dependencies are loaded. Is this actually possible? */ @@ -470,6 +455,7 @@ void tst_QQuickLoader::networkComponent() { ThreadedTestHTTPServer server(dataDirectory(), TestHTTPServer::Delay); + QQmlEngine engine; QQmlComponent component(&engine); const QString qml = "import QtQuick 2.0\n" "import \"" + server.baseUrl().toString() + "/\" as NW\n" @@ -502,11 +488,12 @@ void tst_QQuickLoader::failNetworkRequest() QTest::ignoreMessage(QtWarningMsg, QString(server.baseUrl().toString() + "/IDontExist.qml: File not found").toUtf8()); + QQmlEngine engine; QQmlComponent component(&engine); const QString qml = "import QtQuick 2.0\nLoader { property int did_load: 123; source: \"" + server.baseUrl().toString() + "/IDontExist.qml\"; onLoaded: did_load=456 }"; component.setData(qml.toUtf8(), server.url("/dummy.qml")); QTRY_COMPARE(component.status(), QQmlComponent::Ready); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QTRY_COMPARE(loader->status(), QQuickLoader::Error); @@ -514,104 +501,94 @@ void tst_QQuickLoader::failNetworkRequest() QVERIFY(!loader->item()); QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->property("did_load").toInt(), 123); - QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); - - delete loader; + QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0); } void tst_QQuickLoader::active() { + QQmlEngine engine; + // check that the item isn't instantiated until active is set to true { QQmlComponent component(&engine, testFileUrl("active.1.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader->active() == false); // set manually to false QVERIFY(!loader->item()); - QMetaObject::invokeMethod(object, "doSetSourceComponent"); + QMetaObject::invokeMethod(object.data(), "doSetSourceComponent"); QVERIFY(!loader->item()); - QMetaObject::invokeMethod(object, "doSetSource"); + QMetaObject::invokeMethod(object.data(), "doSetSource"); QVERIFY(!loader->item()); - QMetaObject::invokeMethod(object, "doSetActive"); + QMetaObject::invokeMethod(object.data(), "doSetActive"); QVERIFY(loader->item() != 0); - - delete object; } // check that the status is Null if active is set to false { QQmlComponent component(&engine, testFileUrl("active.2.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader->active() == true); // active is true by default QCOMPARE(loader->status(), QQuickLoader::Ready); int currStatusChangedCount = loader->property("statusChangedCount").toInt(); - QMetaObject::invokeMethod(object, "doSetInactive"); + QMetaObject::invokeMethod(object.data(), "doSetInactive"); QCOMPARE(loader->status(), QQuickLoader::Null); QCOMPARE(loader->property("statusChangedCount").toInt(), (currStatusChangedCount+1)); - - delete object; } // check that the source is not cleared if active is set to false { QQmlComponent component(&engine, testFileUrl("active.3.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader->active() == true); // active is true by default QVERIFY(!loader->source().isEmpty()); int currSourceChangedCount = loader->property("sourceChangedCount").toInt(); - QMetaObject::invokeMethod(object, "doSetInactive"); + QMetaObject::invokeMethod(object.data(), "doSetInactive"); QVERIFY(!loader->source().isEmpty()); QCOMPARE(loader->property("sourceChangedCount").toInt(), currSourceChangedCount); - - delete object; } // check that the sourceComponent is not cleared if active is set to false { QQmlComponent component(&engine, testFileUrl("active.4.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader->active() == true); // active is true by default QVERIFY(loader->sourceComponent() != 0); int currSourceComponentChangedCount = loader->property("sourceComponentChangedCount").toInt(); - QMetaObject::invokeMethod(object, "doSetInactive"); + QMetaObject::invokeMethod(object.data(), "doSetInactive"); QVERIFY(loader->sourceComponent() != 0); QCOMPARE(loader->property("sourceComponentChangedCount").toInt(), currSourceComponentChangedCount); - - delete object; } // check that the item is released if active is set to false { QQmlComponent component(&engine, testFileUrl("active.5.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader->active() == true); // active is true by default QVERIFY(loader->item() != 0); int currItemChangedCount = loader->property("itemChangedCount").toInt(); - QMetaObject::invokeMethod(object, "doSetInactive"); + QMetaObject::invokeMethod(object.data(), "doSetInactive"); QVERIFY(!loader->item()); QCOMPARE(loader->property("itemChangedCount").toInt(), (currItemChangedCount+1)); - - delete object; } // check that the activeChanged signal is emitted correctly { QQmlComponent component(&engine, testFileUrl("active.6.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); @@ -626,34 +603,30 @@ void tst_QQuickLoader::active() QCOMPARE(loader->property("activeChangedCount").toInt(), 2); loader->setActive(false); // change signal should be emitted QCOMPARE(loader->property("activeChangedCount").toInt(), 3); - QMetaObject::invokeMethod(object, "doSetActive"); + QMetaObject::invokeMethod(object.data(), "doSetActive"); QCOMPARE(loader->property("activeChangedCount").toInt(), 4); - QMetaObject::invokeMethod(object, "doSetActive"); + QMetaObject::invokeMethod(object.data(), "doSetActive"); QCOMPARE(loader->property("activeChangedCount").toInt(), 4); - QMetaObject::invokeMethod(object, "doSetInactive"); + QMetaObject::invokeMethod(object.data(), "doSetInactive"); QCOMPARE(loader->property("activeChangedCount").toInt(), 5); loader->setActive(true); // change signal should be emitted QCOMPARE(loader->property("activeChangedCount").toInt(), 6); - - delete object; } // check that the component isn't loaded until active is set to true { QQmlComponent component(&engine, testFileUrl("active.7.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("success").toBool(), true); - delete object; } // check that the component is loaded if active is not set (true by default) { QQmlComponent component(&engine, testFileUrl("active.8.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("success").toBool(), true); - delete object; } } @@ -717,14 +690,15 @@ void tst_QQuickLoader::initialPropertyValues() foreach (const QString &warning, expectedWarnings) QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); + QQmlEngine engine; QQmlComponent component(&engine, qmlFile); - QObject *object = component.beginCreate(engine.rootContext()); + QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); QVERIFY(object != 0); const int serverBaseUrlPropertyIndex = object->metaObject()->indexOfProperty("serverBaseUrl"); if (serverBaseUrlPropertyIndex != -1) { QMetaProperty prop = object->metaObject()->property(serverBaseUrlPropertyIndex); - QVERIFY(prop.write(object, server.baseUrl().toString())); + QVERIFY(prop.write(object.data(), server.baseUrl().toString())); } component.completeCreate(); @@ -735,20 +709,17 @@ void tst_QQuickLoader::initialPropertyValues() for (int i = 0; i < propertyNames.size(); ++i) QCOMPARE(object->property(propertyNames.at(i).toLatin1().constData()), propertyValues.at(i)); - - delete object; } void tst_QQuickLoader::initialPropertyValuesBinding() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("initialPropertyValues.binding.qml")); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QVERIFY(object->setProperty("bindable", QVariant(8))); QCOMPARE(object->property("canaryValue").toInt(), 8); - - delete object; } void tst_QQuickLoader::initialPropertyValuesError_data() @@ -778,23 +749,24 @@ void tst_QQuickLoader::initialPropertyValuesError() foreach (const QString &warning, expectedWarnings) QTest::ignoreMessage(QtWarningMsg, warning.toUtf8().constData()); + QQmlEngine engine; QQmlComponent component(&engine, qmlFile); - QObject *object = component.create(); + QScopedPointer<QObject> object(component.create()); QVERIFY(object != 0); QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); QVERIFY(loader != 0); QVERIFY(!loader->item()); - delete object; } // QTBUG-9241 void tst_QQuickLoader::deleteComponentCrash() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("crash.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); - item->metaObject()->invokeMethod(item, "setLoaderSource"); + item->metaObject()->invokeMethod(item.data(), "setLoaderSource"); QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); QVERIFY(loader); @@ -806,69 +778,64 @@ void tst_QQuickLoader::deleteComponentCrash() QCoreApplication::processEvents(); QTRY_COMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); QCOMPARE(loader->source(), testFileUrl("BlueRect.qml")); - - delete item; } void tst_QQuickLoader::nonItem() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("nonItem.qml")); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader); QVERIFY(loader->item()); - QCOMPARE(loader, loader->item()->parent()); + QCOMPARE(loader.data(), loader->item()->parent()); QPointer<QObject> item = loader->item(); loader->setActive(false); QVERIFY(!loader->item()); QTRY_VERIFY(!item); - - delete loader; } void tst_QQuickLoader::vmeErrors() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("vmeErrors.qml")); QString err = testFileUrl("VmeError.qml").toString() + ":6:26: Cannot assign object type QObject with no default method"; QTest::ignoreMessage(QtWarningMsg, err.toLatin1().constData()); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader); QVERIFY(!loader->item()); - - delete loader; } // QTBUG-13481 void tst_QQuickLoader::creationContext() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("creationContext.qml")); - QObject *o = component.create(); + QScopedPointer<QObject> o(component.create()); QVERIFY(o != 0); QCOMPARE(o->property("test").toBool(), true); - - delete o; } void tst_QQuickLoader::QTBUG_16928() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("QTBUG_16928.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QCOMPARE(item->width(), 250.); QCOMPARE(item->height(), 250.); - - delete item; } void tst_QQuickLoader::implicitSize() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("implicitSize.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QCOMPARE(item->width(), 150.); @@ -881,27 +848,24 @@ void tst_QQuickLoader::implicitSize() QSignalSpy implWidthSpy(loader, SIGNAL(implicitWidthChanged())); QSignalSpy implHeightSpy(loader, SIGNAL(implicitHeightChanged())); - QMetaObject::invokeMethod(item, "changeImplicitSize"); + QMetaObject::invokeMethod(item.data(), "changeImplicitSize"); QCOMPARE(loader->property("implicitWidth").toReal(), 200.); QCOMPARE(loader->property("implicitHeight").toReal(), 300.); QCOMPARE(implWidthSpy.count(), 1); QCOMPARE(implHeightSpy.count(), 1); - - delete item; } void tst_QQuickLoader::QTBUG_17114() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("QTBUG_17114.qml")); - QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); QVERIFY(item); QCOMPARE(item->property("loaderWidth").toReal(), 32.); QCOMPARE(item->property("loaderHeight").toReal(), 32.); - - delete item; } void tst_QQuickLoader::asynchronous_data() @@ -924,13 +888,14 @@ void tst_QQuickLoader::asynchronous() QFETCH(QUrl, qmlFile); QFETCH(QStringList, expectedWarnings); + QQmlEngine engine; PeriodicIncubationController *controller = new PeriodicIncubationController; QQmlIncubationController *previous = engine.incubationController(); engine.setIncubationController(controller); delete previous; QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); @@ -942,7 +907,7 @@ void tst_QQuickLoader::asynchronous() QVERIFY(!loader->item()); QCOMPARE(loader->progress(), 0.0); root->setProperty("comp", qmlFile.toString()); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); if (expectedWarnings.isEmpty()) { @@ -958,19 +923,18 @@ void tst_QQuickLoader::asynchronous() QTRY_COMPARE(loader->progress(), 1.0); QTRY_COMPARE(loader->status(), QQuickLoader::Error); } - - delete root; } void tst_QQuickLoader::asynchronous_clear() { + QQmlEngine engine; PeriodicIncubationController *controller = new PeriodicIncubationController; QQmlIncubationController *previous = engine.incubationController(); engine.setIncubationController(controller); delete previous; QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); @@ -978,7 +942,7 @@ void tst_QQuickLoader::asynchronous_clear() QVERIFY(!loader->item()); root->setProperty("comp", "BigComponent.qml"); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); controller->start(); @@ -987,7 +951,7 @@ void tst_QQuickLoader::asynchronous_clear() // clear before component created root->setProperty("comp", ""); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); QCOMPARE(engine.incubationController()->incubatingObjectCount(), 0); @@ -997,7 +961,7 @@ void tst_QQuickLoader::asynchronous_clear() // check loading component root->setProperty("comp", "BigComponent.qml"); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); QCOMPARE(loader->status(), QQuickLoader::Loading); @@ -1007,19 +971,18 @@ void tst_QQuickLoader::asynchronous_clear() QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->status(), QQuickLoader::Ready); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); - - delete root; } void tst_QQuickLoader::simultaneousSyncAsync() { + QQmlEngine engine; PeriodicIncubationController *controller = new PeriodicIncubationController; QQmlIncubationController *previous = engine.incubationController(); engine.setIncubationController(controller); delete previous; QQmlComponent component(&engine, testFileUrl("simultaneous.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *asyncLoader = root->findChild<QQuickLoader*>("asyncLoader"); @@ -1029,7 +992,7 @@ void tst_QQuickLoader::simultaneousSyncAsync() QVERIFY(!asyncLoader->item()); QVERIFY(!syncLoader->item()); - QMetaObject::invokeMethod(root, "loadComponents"); + QMetaObject::invokeMethod(root.data(), "loadComponents"); QVERIFY(!asyncLoader->item()); QVERIFY(syncLoader->item()); @@ -1040,8 +1003,6 @@ void tst_QQuickLoader::simultaneousSyncAsync() QTRY_VERIFY(asyncLoader->item()); QCOMPARE(asyncLoader->progress(), 1.0); QCOMPARE(asyncLoader->status(), QQuickLoader::Ready); - - delete root; } void tst_QQuickLoader::asyncToSync1() @@ -1053,7 +1014,7 @@ void tst_QQuickLoader::asyncToSync1() delete previous; QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); @@ -1061,7 +1022,7 @@ void tst_QQuickLoader::asyncToSync1() QVERIFY(!loader->item()); root->setProperty("comp", "BigComponent.qml"); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); controller->start(); @@ -1074,19 +1035,18 @@ void tst_QQuickLoader::asyncToSync1() QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->status(), QQuickLoader::Ready); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); - - delete root; } void tst_QQuickLoader::asyncToSync2() { + QQmlEngine engine; PeriodicIncubationController *controller = new PeriodicIncubationController; QQmlIncubationController *previous = engine.incubationController(); engine.setIncubationController(controller); delete previous; QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); @@ -1094,7 +1054,7 @@ void tst_QQuickLoader::asyncToSync2() QVERIFY(!loader->item()); root->setProperty("comp", "BigComponent.qml"); - QMetaObject::invokeMethod(root, "loadComponent"); + QMetaObject::invokeMethod(root.data(), "loadComponent"); QVERIFY(!loader->item()); controller->start(); @@ -1107,12 +1067,11 @@ void tst_QQuickLoader::asyncToSync2() QCOMPARE(loader->progress(), 1.0); QCOMPARE(loader->status(), QQuickLoader::Ready); QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); - - delete root; } void tst_QQuickLoader::loadedSignal() { + QQmlEngine engine; PeriodicIncubationController *controller = new PeriodicIncubationController; QQmlIncubationController *previous = engine.incubationController(); engine.setIncubationController(controller); @@ -1123,62 +1082,58 @@ void tst_QQuickLoader::loadedSignal() // and then immediately setting active to false, causes the // loader to be deactivated, including disabling the incubator. QQmlComponent component(&engine, testFileUrl("loadedSignal.qml")); - QObject *obj = component.create(); + QScopedPointer<QObject> obj(component.create()); - QMetaObject::invokeMethod(obj, "triggerLoading"); + QMetaObject::invokeMethod(obj.data(), "triggerLoading"); QTest::qWait(100); // ensure that loading would have finished if it wasn't deactivated QCOMPARE(obj->property("loadCount").toInt(), 0); QVERIFY(obj->property("success").toBool()); - QMetaObject::invokeMethod(obj, "triggerLoading"); + QMetaObject::invokeMethod(obj.data(), "triggerLoading"); QTest::qWait(100); QCOMPARE(obj->property("loadCount").toInt(), 0); QVERIFY(obj->property("success").toBool()); - QMetaObject::invokeMethod(obj, "triggerMultipleLoad"); + QMetaObject::invokeMethod(obj.data(), "triggerMultipleLoad"); controller->start(); QTest::qWait(100); QTRY_COMPARE(obj->property("loadCount").toInt(), 1); // only one loaded signal should be emitted. QVERIFY(obj->property("success").toBool()); - - delete obj; } { // ensure that an error doesn't result in the onLoaded signal being emitted. QQmlComponent component(&engine, testFileUrl("loadedSignal.2.qml")); - QObject *obj = component.create(); + QScopedPointer<QObject> obj(component.create()); - QMetaObject::invokeMethod(obj, "triggerLoading"); + QMetaObject::invokeMethod(obj.data(), "triggerLoading"); QTest::qWait(100); QCOMPARE(obj->property("loadCount").toInt(), 0); QVERIFY(obj->property("success").toBool()); - - delete obj; } } void tst_QQuickLoader::parented() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("parented.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickItem *item = root->findChild<QQuickItem*>("comp"); QVERIFY(item); - QCOMPARE(item->parentItem(), root); + QCOMPARE(item->parentItem(), root.data()); QCOMPARE(item->width(), 300.); QCOMPARE(item->height(), 300.); - - delete root; } void tst_QQuickLoader::sizeBound() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("sizebound.qml")); - QQuickItem *root = qobject_cast<QQuickItem*>(component.create()); + QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); QVERIFY(root); QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); QVERIFY(loader != 0); @@ -1188,18 +1143,17 @@ void tst_QQuickLoader::sizeBound() QCOMPARE(loader->width(), 50.0); QCOMPARE(loader->height(), 60.0); - QMetaObject::invokeMethod(root, "switchComponent"); + QMetaObject::invokeMethod(root.data(), "switchComponent"); QCOMPARE(loader->width(), 80.0); QCOMPARE(loader->height(), 90.0); - - delete root; } void tst_QQuickLoader::QTBUG_30183() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("/QTBUG_30183.qml")); - QQuickLoader *loader = qobject_cast<QQuickLoader*>(component.create()); + QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); QVERIFY(loader != 0); QCOMPARE(loader->width(), 240.0); QCOMPARE(loader->height(), 120.0); @@ -1209,8 +1163,6 @@ void tst_QQuickLoader::QTBUG_30183() QVERIFY(rect); QCOMPARE(rect->width(), 240.0); QCOMPARE(rect->height(), 120.0); - - delete loader; } void tst_QQuickLoader::transientWindow() // QTBUG-52944 @@ -1295,6 +1247,7 @@ void tst_QQuickLoader::nestedTransientWindow() // QTBUG-52944 void tst_QQuickLoader::sourceComponentGarbageCollection() { + QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("sourceComponentGarbageCollection.qml")); QScopedPointer<QObject> obj(component.create()); QVERIFY(!obj.isNull()); @@ -1313,6 +1266,67 @@ void tst_QQuickLoader::sourceComponentGarbageCollection() QCOMPARE(spy.count(), 1); } +// QTBUG-51995 +void tst_QQuickLoader::bindings() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("bindings.qml")); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QQuickItem *game = object->property("game").value<QQuickItem*>(); + QVERIFY(game); + + QQuickLoader *loader = object->property("loader").value<QQuickLoader*>(); + QVERIFY(loader); + + QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); + + // Causes the Loader to become active + game->setState(QLatin1String("running")); + QTRY_VERIFY(loader->item()); + + // Causes the Loader to become inactive - should not cause binding errors + game->setState(QLatin1String("invalid")); + QTRY_VERIFY(!loader->item()); + + QString failureMessage; + if (!warningsSpy.isEmpty()) { + QDebug stream(&failureMessage); + stream << warningsSpy.first().first().value<QList<QQmlError>>(); + } + QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); +} + +// QTBUG-47321 +void tst_QQuickLoader::parentErrors() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("parentErrors.qml")); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QQuickLoader *loader = object->property("loader").value<QQuickLoader*>(); + QVERIFY(loader); + + QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); + + // Give the loader a component + loader->setSourceComponent(object->property("component").value<QQmlComponent*>()); + QTRY_VERIFY(loader->item()); + + // Clear the loader's component; should not cause binding errors + loader->setSourceComponent(nullptr); + QTRY_VERIFY(!loader->item()); + + QString failureMessage; + if (!warningsSpy.isEmpty()) { + QDebug stream(&failureMessage); + stream << warningsSpy.first().first().value<QList<QQmlError>>(); + } + QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); +} + QTEST_MAIN(tst_QQuickLoader) #include "tst_qquickloader.moc" diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 12c6233ded..393a57e7e8 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -2183,7 +2183,8 @@ void tst_QQuickMouseArea::pressOneAndTapAnother_data() QTest::newRow("press mouse, tap touch, release mouse") << true << false; // QTBUG-64249 as written QTest::newRow("press touch, press mouse, release touch, release mouse") << false << false; QTest::newRow("press mouse, press touch, release mouse, release touch") << true << true; - QTest::newRow("press touch, click mouse, release touch") << false << true; + // TODO fix in a separate patch after the 5.9->5.10 merge + // QTest::newRow("press touch, click mouse, release touch") << false << true; } void tst_QQuickMouseArea::pressOneAndTapAnother() 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 Binary files differnew file mode 100644 index 0000000000..a8b4483c96 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem3.png 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 Binary files differnew file mode 100644 index 0000000000..3a988ba249 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem4.png 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 Binary files differnew file mode 100644 index 0000000000..cb5cfd25dc --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem5.png 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 Binary files differnew file mode 100644 index 0000000000..d9e53d6c00 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem6.png 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 cb710e2c8e..a968d4179b 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" @@ -142,8 +143,9 @@ class TestTouchItem : public QQuickRectangle public: TestTouchItem(QQuickItem *parent = 0) : QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true), - mousePressId(0), - spinLoopWhenPressed(false), touchEventCount(0) + mousePressCount(0), mouseMoveCount(0), + spinLoopWhenPressed(false), touchEventCount(0), + mouseUngrabEventCount(0) { border()->setWidth(1); setAcceptedMouseButtons(Qt::LeftButton); @@ -161,9 +163,11 @@ public: lastMousePos = QPointF(); lastMouseCapabilityFlags = 0; touchEventCount = 0; + mouseMoveCount = 0; + mouseUngrabEventCount = 0; } - static void clearMousePressCounter() + static void clearMouseEventCounters() { mousePressNum = mouseMoveNum = mouseReleaseNum = 0; } @@ -176,9 +180,11 @@ public: bool acceptTouchEvents; bool acceptMouseEvents; TouchEventData lastEvent; - int mousePressId; + int mousePressCount; + int mouseMoveCount; bool spinLoopWhenPressed; int touchEventCount; + int mouseUngrabEventCount; QVector2D lastVelocity; QVector2D lastVelocityFromMouseMove; QPointF lastMousePos; @@ -206,7 +212,7 @@ public: e->ignore(); return; } - mousePressId = ++mousePressNum; + mousePressCount = ++mousePressNum; lastMousePos = e->pos(); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); } @@ -216,7 +222,7 @@ public: e->ignore(); return; } - ++mouseMoveNum; + mouseMoveCount = ++mouseMoveNum; lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); lastMousePos = e->pos(); @@ -232,10 +238,23 @@ 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; + void mouseUngrabEvent() { + ++mouseUngrabEventCount; + } + + 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; } @@ -367,6 +386,7 @@ private slots: void touchEvent_propagation(); void touchEvent_propagation_data(); void touchEvent_cancel(); + void touchEvent_cancelClearsMouseGrab(); void touchEvent_reentrant(); void touchEvent_velocity(); @@ -436,6 +456,7 @@ private slots: void testHoverChildMouseEventFilter(); void testHoverTimestamp(); + void test_circleMapItem(); void pointerEventTypeAndPointCount(); @@ -445,6 +466,9 @@ private slots: void findChild(); + void testChildMouseEventFilter(); + void testChildMouseEventFilter_data(); + private: QTouchDevice *touchDevice; QTouchDevice *touchDeviceWithVelocity; @@ -575,7 +599,7 @@ void tst_qquickwindow::constantUpdatesOnWindow() void tst_qquickwindow::touchEvent_basic() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -704,7 +728,7 @@ void tst_qquickwindow::touchEvent_basic() void tst_qquickwindow::touchEvent_propagation() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QFETCH(bool, acceptTouchEvents); QFETCH(bool, acceptMouseEvents); @@ -848,7 +872,7 @@ void tst_qquickwindow::touchEvent_propagation_data() void tst_qquickwindow::touchEvent_cancel() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -880,9 +904,41 @@ void tst_qquickwindow::touchEvent_cancel() delete item; } +void tst_qquickwindow::touchEvent_cancelClearsMouseGrab() +{ + TestTouchItem::clearMouseEventCounters(); + + QQuickWindow *window = new QQuickWindow; + QScopedPointer<QQuickWindow> cleanup(window); + + window->resize(250, 250); + window->setPosition(100, 100); + window->setTitle(QTest::currentTestFunction()); + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + TestTouchItem *item = new TestTouchItem(window->contentItem()); + item->setPosition(QPointF(50, 50)); + item->setSize(QSizeF(150, 150)); + item->acceptMouseEvents = true; + item->acceptTouchEvents = false; + + QPointF pos(50, 50); + QTest::touchEvent(window, touchDevice).press(0, item->mapToScene(pos).toPoint(), window); + QCoreApplication::processEvents(); + + QTRY_COMPARE(item->mousePressCount, 1); + QTRY_COMPARE(item->mouseUngrabEventCount, 0); + + QWindowSystemInterface::handleTouchCancelEvent(0, touchDevice); + QCoreApplication::processEvents(); + + QTRY_COMPARE(item->mouseUngrabEventCount, 1); +} + void tst_qquickwindow::touchEvent_reentrant() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -921,7 +977,7 @@ void tst_qquickwindow::touchEvent_reentrant() void tst_qquickwindow::touchEvent_velocity() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -1056,7 +1112,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); @@ -1196,7 +1252,7 @@ void tst_qquickwindow::clearWindow() void tst_qquickwindow::mouseFiltering() { - TestTouchItem::clearMousePressCounter(); + TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer<QQuickWindow> cleanup(window); @@ -1210,6 +1266,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)); @@ -1229,9 +1290,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); @@ -1478,12 +1571,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()); @@ -2640,6 +2727,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); @@ -2653,20 +2818,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()); @@ -2986,6 +3149,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 4c1c667489..0f904e0c09 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..d5bf0110c4 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::global()->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::global()->bounded(m_rowCount); beginInsertRows(QModelIndex(), rnum, rnum); m_rowCount++; endInsertRows(); diff --git a/tests/auto/quick/touchmouse/BLACKLIST b/tests/auto/quick/touchmouse/BLACKLIST index b2ba52eca9..e0d4bff131 100644 --- a/tests/auto/quick/touchmouse/BLACKLIST +++ b/tests/auto/quick/touchmouse/BLACKLIST @@ -1,2 +1,5 @@ +# QTBUG-40856 hover regression on pointerhandler branch: TODO fix before merging to dev +[hoverEnabled] +* [buttonOnDelayedPressFlickable] windows gcc developer-build 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()); |