1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <QtQuick/qquickview.h>
32#include <QtQuick/qquickitem.h>
33#include <QtQuick/private/qquickpointhandler_p.h>
34#include <qpa/qwindowsysteminterface.h>
35
36#include <private/qquickwindow_p.h>
37
38#include <QtQml/qqmlengine.h>
39#include <QtQml/qqmlproperty.h>
40
41#include "../../../shared/util.h"
42#include "../../shared/viewtestutil.h"
43
44Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
45
46class tst_PointHandler : public QQmlDataTest
47{
48 Q_OBJECT
49public:
50 tst_PointHandler()
51 : touchDevice(QTest::createTouchDevice())
52 {}
53
54private slots:
55 void initTestCase();
56
57 void singleTouch();
58 void tabletStylus();
59 void simultaneousMultiTouch();
60 void pressedMultipleButtons_data();
61 void pressedMultipleButtons();
62
63private:
64 void createView(QScopedPointer<QQuickView> &window, const char *fileName);
65 QTouchDevice *touchDevice;
66};
67
68void tst_PointHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName)
69{
70 window.reset(other: new QQuickView);
71 window->setSource(testFileUrl(fileName));
72 QTRY_COMPARE(window->status(), QQuickView::Ready);
73 QQuickViewTestUtil::centerOnScreen(window: window.data());
74 QQuickViewTestUtil::moveMouseAway(window: window.data());
75
76 window->show();
77 QVERIFY(QTest::qWaitForWindowActive(window.data()));
78 QVERIFY(window->rootObject() != nullptr);
79}
80
81void tst_PointHandler::initTestCase()
82{
83 // This test assumes that we don't get synthesized mouse events from QGuiApplication
84 qApp->setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTouchEvents, on: false);
85
86 QQmlDataTest::initTestCase();
87}
88
89void tst_PointHandler::singleTouch()
90{
91 QScopedPointer<QQuickView> windowPtr;
92 createView(window&: windowPtr, fileName: "pointTracker.qml");
93 QQuickView * window = windowPtr.data();
94 QQuickItem *tracker = window->rootObject()->findChild<QQuickItem *>(aName: "pointTracker");
95 QVERIFY(tracker);
96 QQuickPointHandler *handler = window->rootObject()->findChild<QQuickPointHandler *>(aName: "pointHandler");
97 QVERIFY(handler);
98
99 QSignalSpy activeSpy(handler, SIGNAL(activeChanged()));
100 QSignalSpy pointSpy(handler, SIGNAL(pointChanged()));
101 QSignalSpy translationSpy(handler, SIGNAL(translationChanged()));
102
103 QPoint point(100,100);
104 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: point, window);
105 QQuickTouchUtils::flush(window);
106 QTRY_COMPARE(handler->active(), true);
107 QCOMPARE(activeSpy.count(), 1);
108 QCOMPARE(pointSpy.count(), 1);
109 QCOMPARE(handler->point().position().toPoint(), point);
110 QCOMPARE(handler->point().scenePosition().toPoint(), point);
111 QCOMPARE(handler->point().pressedButtons(), Qt::NoButton);
112 QCOMPARE(handler->translation(), QVector2D());
113 QCOMPARE(translationSpy.count(), 1);
114
115 point += QPoint(10, 10);
116 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: point, window);
117 QQuickTouchUtils::flush(window);
118 QCOMPARE(handler->active(), true);
119 QCOMPARE(activeSpy.count(), 1);
120 QCOMPARE(pointSpy.count(), 2);
121 QCOMPARE(handler->point().position().toPoint(), point);
122 QCOMPARE(handler->point().scenePosition().toPoint(), point);
123 QCOMPARE(handler->point().pressPosition().toPoint(), QPoint(100, 100));
124 QCOMPARE(handler->point().scenePressPosition().toPoint(), QPoint(100, 100));
125 QCOMPARE(handler->point().pressedButtons(), Qt::NoButton);
126 QVERIFY(handler->point().velocity().x() > 0);
127 QVERIFY(handler->point().velocity().y() > 0);
128 QCOMPARE(handler->translation(), QVector2D(10, 10));
129 QCOMPARE(translationSpy.count(), 2);
130
131 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: point, window);
132 QQuickTouchUtils::flush(window);
133 QTRY_COMPARE(handler->active(), false);
134 QCOMPARE(activeSpy.count(), 2);
135 QCOMPARE(pointSpy.count(), 3);
136 QCOMPARE(handler->translation(), QVector2D());
137 QCOMPARE(translationSpy.count(), 3);
138}
139
140void tst_PointHandler::tabletStylus()
141{
142 qApp->setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTabletEvents, on: false);
143 QScopedPointer<QQuickView> windowPtr;
144 createView(window&: windowPtr, fileName: "pointTracker.qml");
145 QQuickView * window = windowPtr.data();
146 QQuickPointHandler *handler = window->rootObject()->findChild<QQuickPointHandler *>(aName: "pointHandler");
147 QVERIFY(handler);
148 handler->setAcceptedDevices(QQuickPointerDevice::Stylus);
149
150 QSignalSpy activeSpy(handler, SIGNAL(activeChanged()));
151 QSignalSpy pointSpy(handler, SIGNAL(pointChanged()));
152 QSignalSpy translationSpy(handler, SIGNAL(translationChanged()));
153
154 QPoint point(100,100);
155 const qint64 stylusId = 1234567890;
156
157 QWindowSystemInterface::handleTabletEvent(window, local: point, global: window->mapToGlobal(pos: point),
158 device: QTabletEvent::Stylus, pointerType: QTabletEvent::Pen, buttons: Qt::LeftButton, pressure: 0.5, xTilt: 25, yTilt: 35, tangentialPressure: 0.6, rotation: 12.3, z: 3, uid: stylusId, modifiers: Qt::NoModifier);
159 QTRY_COMPARE(handler->active(), true);
160 QCOMPARE(activeSpy.count(), 1);
161 QCOMPARE(pointSpy.count(), 1);
162 QCOMPARE(handler->point().position().toPoint(), point);
163 QCOMPARE(handler->point().scenePosition().toPoint(), point);
164 QCOMPARE(handler->point().pressedButtons(), Qt::LeftButton);
165 QCOMPARE(handler->point().pressure(), 0.5);
166 QCOMPARE(handler->point().rotation(), 12.3);
167 QCOMPARE(handler->point().uniqueId().numericId(), stylusId);
168 QCOMPARE(handler->translation(), QVector2D());
169 QCOMPARE(translationSpy.count(), 1);
170
171 point += QPoint(10, 10);
172 QWindowSystemInterface::handleTabletEvent(window, local: point, global: window->mapToGlobal(pos: point),
173 device: QTabletEvent::Stylus, pointerType: QTabletEvent::Pen, buttons: Qt::LeftButton, pressure: 0.45, xTilt: 23, yTilt: 33, tangentialPressure: 0.57, rotation: 15.6, z: 3.4, uid: stylusId, modifiers: Qt::NoModifier);
174 QTRY_COMPARE(pointSpy.count(), 2);
175 QCOMPARE(handler->active(), true);
176 QCOMPARE(activeSpy.count(), 1);
177 QCOMPARE(handler->point().position().toPoint(), point);
178 QCOMPARE(handler->point().scenePosition().toPoint(), point);
179 QCOMPARE(handler->point().pressPosition().toPoint(), QPoint(100, 100));
180 QCOMPARE(handler->point().scenePressPosition().toPoint(), QPoint(100, 100));
181 QCOMPARE(handler->point().pressedButtons(), Qt::LeftButton);
182 QCOMPARE(handler->point().pressure(), 0.45);
183 QCOMPARE(handler->point().rotation(), 15.6);
184 QCOMPARE(handler->point().uniqueId().numericId(), stylusId);
185 QVERIFY(handler->point().velocity().x() > 0);
186 QVERIFY(handler->point().velocity().y() > 0);
187 QCOMPARE(handler->translation(), QVector2D(10, 10));
188 QCOMPARE(translationSpy.count(), 2);
189
190 QWindowSystemInterface::handleTabletEvent(window, local: point, global: window->mapToGlobal(pos: point),
191 device: QTabletEvent::Stylus, pointerType: QTabletEvent::Pen, buttons: Qt::NoButton, pressure: 0, xTilt: 0, yTilt: 0, tangentialPressure: 0, rotation: 0, z: 0, uid: stylusId, modifiers: Qt::NoModifier);
192 QTRY_COMPARE(handler->active(), false);
193 QCOMPARE(activeSpy.count(), 2);
194 QCOMPARE(pointSpy.count(), 3);
195 QCOMPARE(handler->translation(), QVector2D());
196 QCOMPARE(translationSpy.count(), 3);
197}
198
199void tst_PointHandler::simultaneousMultiTouch()
200{
201 QScopedPointer<QQuickView> windowPtr;
202 createView(window&: windowPtr, fileName: "multiPointTracker.qml");
203 QQuickView * window = windowPtr.data();
204 QList<QQuickPointHandler *> handlers = window->rootObject()->findChildren<QQuickPointHandler *>();
205 QCOMPARE(handlers.count(), 3);
206
207 QVector<QSignalSpy*> activeSpies;
208 QVector<QSignalSpy*> pointSpies;
209 QVector<QSignalSpy*> translationSpies;
210 QVector<QPoint> points{{100,100}, {200, 200}, {100, 300}};
211 QVector<QPoint> pressPoints = points;
212 for (auto h : handlers) {
213 activeSpies << new QSignalSpy(h, SIGNAL(activeChanged()));
214 pointSpies << new QSignalSpy(h, SIGNAL(pointChanged()));
215 translationSpies << new QSignalSpy(h, SIGNAL(translationChanged()));
216 }
217
218 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: points[0], window).press(touchId: 2, pt: points[1], window).press(touchId: 3, pt: points[2], window);
219 QQuickTouchUtils::flush(window);
220 QVector<int> pointIndexPerHandler;
221 int i = 0;
222 for (auto h : handlers) {
223 QTRY_COMPARE(h->active(), true);
224 QCOMPARE(activeSpies[i]->count(), 1);
225 QCOMPARE(pointSpies[i]->count(), 1);
226 int chosenPointIndex = points.indexOf(t: h->point().position().toPoint());
227 QVERIFY(chosenPointIndex != -1);
228 // Verify that each handler chose a unique point
229 QVERIFY(!pointIndexPerHandler.contains(chosenPointIndex));
230 pointIndexPerHandler.append(t: chosenPointIndex);
231 QPoint point = points[chosenPointIndex];
232 QCOMPARE(h->point().scenePosition().toPoint(), point);
233 QCOMPARE(h->point().pressedButtons(), Qt::NoButton);
234 QCOMPARE(h->translation(), QVector2D());
235 QCOMPARE(translationSpies[i]->count(), 1);
236 ++i;
237 }
238
239 for (int i = 0; i < 3; ++i)
240 points[i] += QPoint(10 + 10 * i, 10 + 10 * i % 2);
241 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: points[0], window).move(touchId: 2, pt: points[1], window).move(touchId: 3, pt: points[2], window);
242 QQuickTouchUtils::flush(window);
243 i = 0;
244 for (auto h : handlers) {
245 QCOMPARE(h->active(), true);
246 QCOMPARE(activeSpies[i]->count(), 1);
247 QCOMPARE(pointSpies[i]->count(), 2);
248 QCOMPARE(h->point().position().toPoint(), points[pointIndexPerHandler[i]]);
249 QCOMPARE(h->point().scenePosition().toPoint(), points[pointIndexPerHandler[i]]);
250 QCOMPARE(h->point().pressPosition().toPoint(), pressPoints[pointIndexPerHandler[i]]);
251 QCOMPARE(h->point().scenePressPosition().toPoint(), pressPoints[pointIndexPerHandler[i]]);
252 QCOMPARE(h->point().pressedButtons(), Qt::NoButton);
253 QVERIFY(h->point().velocity().x() > 0);
254 QVERIFY(h->point().velocity().y() > 0);
255 QCOMPARE(h->translation(), QVector2D(10 + 10 * pointIndexPerHandler[i], 10 + 10 * pointIndexPerHandler[i] % 2));
256 QCOMPARE(translationSpies[i]->count(), 2);
257 ++i;
258 }
259
260 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: points[0], window).release(touchId: 2, pt: points[1], window).release(touchId: 3, pt: points[2], window);
261 QQuickTouchUtils::flush(window);
262 i = 0;
263 for (auto h : handlers) {
264 QTRY_COMPARE(h->active(), false);
265 QCOMPARE(activeSpies[i]->count(), 2);
266 QCOMPARE(pointSpies[i]->count(), 3);
267 QCOMPARE(h->translation(), QVector2D());
268 QCOMPARE(translationSpies[i]->count(), 3);
269 ++i;
270 }
271
272 qDeleteAll(c: activeSpies);
273 qDeleteAll(c: pointSpies);
274 qDeleteAll(c: translationSpies);
275}
276
277void tst_PointHandler::pressedMultipleButtons_data()
278{
279 QTest::addColumn<Qt::MouseButtons>(name: "accepted");
280 QTest::addColumn<QList<Qt::MouseButtons> >(name: "buttons");
281 QTest::addColumn<QList<bool> >(name: "active");
282 QTest::addColumn<QList<Qt::MouseButtons> >(name: "pressedButtons");
283 QTest::addColumn<int>(name: "changeCount");
284
285 QList<Qt::MouseButtons> buttons;
286 QList<bool> active;
287 QList<Qt::MouseButtons> pressedButtons;
288 buttons << Qt::LeftButton
289 << (Qt::LeftButton | Qt::RightButton)
290 << Qt::LeftButton
291 << Qt::NoButton;
292 active << true
293 << true
294 << true
295 << false;
296 pressedButtons << Qt::LeftButton
297 << (Qt::LeftButton | Qt::RightButton)
298 << Qt::LeftButton
299 << Qt::NoButton;
300 QTest::newRow(dataTag: "Accept Left - Press left, Press Right, Release Right")
301 << Qt::MouseButtons(Qt::LeftButton) << buttons << active << pressedButtons << 4;
302
303 buttons.clear();
304 active.clear();
305 pressedButtons.clear();
306 buttons << Qt::LeftButton
307 << (Qt::LeftButton | Qt::RightButton)
308 << Qt::RightButton
309 << Qt::NoButton;
310 active << true
311 << true
312 << false
313 << false;
314 pressedButtons << Qt::LeftButton
315 << (Qt::LeftButton | Qt::RightButton)
316 << Qt::NoButton // Not the "truth" but filtered according to this handler's acceptedButtons
317 << Qt::NoButton;
318 QTest::newRow(dataTag: "Accept Left - Press left, Press Right, Release Left")
319 << Qt::MouseButtons(Qt::LeftButton) << buttons << active << pressedButtons << 3;
320
321 buttons.clear();
322 active.clear();
323 pressedButtons.clear();
324 buttons << Qt::LeftButton
325 << (Qt::LeftButton | Qt::RightButton)
326 << Qt::LeftButton
327 << Qt::NoButton;
328 active << true
329 << true
330 << true
331 << false;
332 pressedButtons << Qt::LeftButton
333 << (Qt::LeftButton | Qt::RightButton)
334 << Qt::LeftButton
335 << Qt::NoButton;
336 QTest::newRow(dataTag: "Accept Left|Right - Press left, Press Right, Release Right")
337 << (Qt::LeftButton | Qt::RightButton) << buttons << active << pressedButtons << 4;
338
339 buttons.clear();
340 active.clear();
341 pressedButtons.clear();
342 buttons << Qt::RightButton
343 << (Qt::LeftButton | Qt::RightButton)
344 << Qt::LeftButton
345 << Qt::NoButton;
346 active << true
347 << true
348 << false
349 << false;
350 pressedButtons << Qt::RightButton
351 << (Qt::LeftButton | Qt::RightButton)
352 << Qt::NoButton // Not the "truth" but filtered according to this handler's acceptedButtons
353 << Qt::NoButton;
354 QTest::newRow(dataTag: "Accept Right - Press Right, Press Left, Release Right")
355 << Qt::MouseButtons(Qt::RightButton) << buttons << active << pressedButtons << 3;
356}
357
358void tst_PointHandler::pressedMultipleButtons()
359{
360 QFETCH(Qt::MouseButtons, accepted);
361 QFETCH(QList<Qt::MouseButtons>, buttons);
362 QFETCH(QList<bool>, active);
363 QFETCH(QList<Qt::MouseButtons>, pressedButtons);
364 QFETCH(int, changeCount);
365
366 QScopedPointer<QQuickView> windowPtr;
367 createView(window&: windowPtr, fileName: "pointTracker.qml");
368 QQuickView * window = windowPtr.data();
369 QQuickItem *tracker = window->rootObject()->findChild<QQuickItem *>(aName: "pointTracker");
370 QVERIFY(tracker);
371 QQuickPointHandler *handler = window->rootObject()->findChild<QQuickPointHandler *>(aName: "pointHandler");
372 QVERIFY(handler);
373
374 QSignalSpy activeSpy(handler, SIGNAL(activeChanged()));
375 QSignalSpy pointSpy(handler, SIGNAL(pointChanged()));
376 handler->setAcceptedButtons(accepted);
377
378 QPoint point(100,100);
379
380 for (int i = 0; i < buttons.count(); ++i) {
381 int btns = int(buttons.at(i));
382 int release = 0;
383 if (i > 0) {
384 int lastBtns = int(buttons.at(i: i - 1));
385 release = lastBtns & ~btns;
386 }
387 if (release)
388 QTest::mouseRelease(window: windowPtr.data(), button: Qt::MouseButton(release), stateKey: Qt::NoModifier, pos: point);
389 else
390 QTest::mousePress(window: windowPtr.data(), button: Qt::MouseButton(btns), stateKey: Qt::NoModifier, pos: point);
391
392 qCDebug(lcPointerTests) << i << ": acceptedButtons" << handler->acceptedButtons()
393 << "; comparing" << handler->point().pressedButtons() << pressedButtons.at(i);
394 QCOMPARE(handler->point().pressedButtons(), pressedButtons.at(i));
395 QCOMPARE(handler->active(), active.at(i));
396 }
397
398 QTest::mousePress(window: windowPtr.data(), button: Qt::NoButton, stateKey: Qt::NoModifier, pos: point);
399 QCOMPARE(handler->active(), false);
400 QCOMPARE(activeSpy.count(), 2);
401 QCOMPARE(pointSpy.count(), changeCount);
402}
403
404QTEST_MAIN(tst_PointHandler)
405
406#include "tst_qquickpointhandler.moc"
407

source code of qtdeclarative/tests/auto/quick/pointerhandlers/qquickpointhandler/tst_qquickpointhandler.cpp