1/****************************************************************************
2**
3** Copyright (C) 2017 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
30#include <QtTest/QtTest>
31
32#include <QtGui/qstylehints.h>
33#include <QtQuick/qquickview.h>
34#include <QtQuick/qquickitem.h>
35#include <QtQuick/private/qquickpointerhandler_p.h>
36#include <QtQuick/private/qquicktaphandler_p.h>
37#include <qpa/qwindowsysteminterface.h>
38
39#include <private/qquickwindow_p.h>
40
41#include <QtQml/qqmlengine.h>
42#include <QtQml/qqmlproperty.h>
43
44#include "../../../shared/util.h"
45#include "../../shared/viewtestutil.h"
46
47Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
48
49class tst_TapHandler : public QQmlDataTest
50{
51 Q_OBJECT
52public:
53 tst_TapHandler()
54 :touchDevice(QTest::createTouchDevice())
55 {}
56
57private slots:
58 void initTestCase();
59
60 void touchGesturePolicyDragThreshold();
61 void mouseGesturePolicyDragThreshold();
62 void touchMouseGesturePolicyDragThreshold();
63 void touchGesturePolicyWithinBounds();
64 void mouseGesturePolicyWithinBounds();
65 void touchGesturePolicyReleaseWithinBounds();
66 void mouseGesturePolicyReleaseWithinBounds();
67 void touchMultiTap();
68 void mouseMultiTap();
69 void touchLongPress();
70 void mouseLongPress();
71 void buttonsMultiTouch();
72 void componentUserBehavioralOverride();
73 void rightLongPressIgnoreWheel();
74 void nonTopLevelParentWindow();
75
76private:
77 void createView(QScopedPointer<QQuickView> &window, const char *fileName,
78 QWindow *parent = nullptr);
79 QTouchDevice *touchDevice;
80 void mouseEvent(QEvent::Type type, Qt::MouseButton button, const QPoint &point,
81 QWindow *targetWindow, QWindow *mapToWindow);
82};
83
84void tst_TapHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName,
85 QWindow *parent)
86{
87 window.reset(other: new QQuickView(parent));
88 if (parent) {
89 parent->show();
90 QVERIFY(QTest::qWaitForWindowActive(parent));
91 }
92
93 window->setSource(testFileUrl(fileName));
94 QTRY_COMPARE(window->status(), QQuickView::Ready);
95 QQuickViewTestUtil::centerOnScreen(window: window.data());
96 QQuickViewTestUtil::moveMouseAway(window: window.data());
97
98 window->show();
99 QVERIFY(QTest::qWaitForWindowActive(window.data()));
100 QVERIFY(window->rootObject() != nullptr);
101}
102
103void tst_TapHandler::mouseEvent(QEvent::Type type, Qt::MouseButton button, const QPoint &point,
104 QWindow *targetWindow, QWindow *mapToWindow)
105{
106 QVERIFY(targetWindow);
107 QVERIFY(mapToWindow);
108 auto buttons = button;
109 if (type == QEvent::MouseButtonRelease) {
110 buttons = Qt::NoButton;
111 }
112 QMouseEvent me(type, point, mapToWindow->mapToGlobal(pos: point), button, buttons,
113 Qt::KeyboardModifiers());
114 QVERIFY(qApp->notify(targetWindow, &me));
115}
116
117void tst_TapHandler::initTestCase()
118{
119 // This test assumes that we don't get synthesized mouse events from QGuiApplication
120 qApp->setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTouchEvents, on: false);
121
122 QQmlDataTest::initTestCase();
123}
124
125void tst_TapHandler::touchGesturePolicyDragThreshold()
126{
127 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
128 QScopedPointer<QQuickView> windowPtr;
129 createView(window&: windowPtr, fileName: "buttons.qml");
130 QQuickView * window = windowPtr.data();
131
132 QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
133 QVERIFY(buttonDragThreshold);
134 QQuickTapHandler *tapHandler = buttonDragThreshold->findChild<QQuickTapHandler*>();
135 QVERIFY(tapHandler);
136 QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
137
138 // DragThreshold button stays pressed while touchpoint stays within dragThreshold, emits tapped on release
139 QPoint p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
140 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
141 QQuickTouchUtils::flush(window);
142 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
143 p1 += QPoint(dragThreshold, 0);
144 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
145 QQuickTouchUtils::flush(window);
146 QVERIFY(buttonDragThreshold->property("pressed").toBool());
147 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
148 QQuickTouchUtils::flush(window);
149 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
150 QCOMPARE(dragThresholdTappedSpy.count(), 1);
151 QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1);
152 QCOMPARE(tapHandler->point().position(), QPointF());
153
154 // DragThreshold button is no longer pressed if touchpoint goes beyond dragThreshold
155 dragThresholdTappedSpy.clear();
156 p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
157 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
158 QQuickTouchUtils::flush(window);
159 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
160 p1 += QPoint(dragThreshold, 0);
161 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
162 QQuickTouchUtils::flush(window);
163 QVERIFY(buttonDragThreshold->property("pressed").toBool());
164 p1 += QPoint(1, 0);
165 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
166 QQuickTouchUtils::flush(window);
167 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
168 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
169 QQuickTouchUtils::flush(window);
170 QVERIFY(!buttonDragThreshold->property("pressed").toBool());
171 QCOMPARE(dragThresholdTappedSpy.count(), 0);
172}
173
174void tst_TapHandler::mouseGesturePolicyDragThreshold()
175{
176 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
177 QScopedPointer<QQuickView> windowPtr;
178 createView(window&: windowPtr, fileName: "buttons.qml");
179 QQuickView * window = windowPtr.data();
180
181 QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
182 QVERIFY(buttonDragThreshold);
183 QQuickTapHandler *tapHandler = buttonDragThreshold->findChild<QQuickTapHandler*>();
184 QVERIFY(tapHandler);
185 QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
186
187 // DragThreshold button stays pressed while mouse stays within dragThreshold, emits tapped on release
188 QPoint p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
189 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
190 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
191 p1 += QPoint(dragThreshold, 0);
192 QTest::mouseMove(window, pos: p1);
193 QVERIFY(buttonDragThreshold->property("pressed").toBool());
194 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
195 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
196 QTRY_COMPARE(dragThresholdTappedSpy.count(), 1);
197 QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1);
198 QCOMPARE(tapHandler->point().position(), QPointF());
199
200 // DragThreshold button is no longer pressed if mouse goes beyond dragThreshold
201 dragThresholdTappedSpy.clear();
202 p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
203 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
204 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
205 p1 += QPoint(dragThreshold, 0);
206 QTest::mouseMove(window, pos: p1);
207 QVERIFY(buttonDragThreshold->property("pressed").toBool());
208 p1 += QPoint(1, 0);
209 QTest::mouseMove(window, pos: p1);
210 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
211 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
212 QVERIFY(!buttonDragThreshold->property("pressed").toBool());
213 QCOMPARE(dragThresholdTappedSpy.count(), 0);
214}
215
216void tst_TapHandler::touchMouseGesturePolicyDragThreshold()
217{
218 QScopedPointer<QQuickView> windowPtr;
219 createView(window&: windowPtr, fileName: "buttons.qml");
220 QQuickView * window = windowPtr.data();
221
222 QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
223 QVERIFY(buttonDragThreshold);
224 QSignalSpy tappedSpy(buttonDragThreshold, SIGNAL(tapped()));
225 QSignalSpy canceledSpy(buttonDragThreshold, SIGNAL(canceled()));
226
227 // Press mouse, drag it outside the button, release
228 QPoint p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
229 QPoint p2 = p1 + QPoint(int(buttonDragThreshold->height()), 0);
230 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
231 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
232 QTest::mouseMove(window, pos: p2);
233 QTRY_COMPARE(canceledSpy.count(), 1);
234 QCOMPARE(tappedSpy.count(), 0);
235 QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
236 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p2);
237
238 // Press and release touch, verify that it still works (QTBUG-71466)
239 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
240 QQuickTouchUtils::flush(window);
241 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
242 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
243 QQuickTouchUtils::flush(window);
244 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
245 QCOMPARE(tappedSpy.count(), 1);
246
247 // Press touch, drag it outside the button, release
248 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
249 QQuickTouchUtils::flush(window);
250 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
251 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p2, window);
252 QQuickTouchUtils::flush(window);
253 QTRY_COMPARE(buttonDragThreshold->property("pressed").toBool(), false);
254 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p2, window);
255 QQuickTouchUtils::flush(window);
256 QTRY_COMPARE(canceledSpy.count(), 2);
257 QCOMPARE(tappedSpy.count(), 1); // didn't increase
258 QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
259
260 // Press and release mouse, verify that it still works
261 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
262 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
263 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
264 QTRY_COMPARE(tappedSpy.count(), 2);
265 QCOMPARE(canceledSpy.count(), 2); // didn't increase
266 QCOMPARE(buttonDragThreshold->property("pressed").toBool(), false);
267}
268
269void tst_TapHandler::touchGesturePolicyWithinBounds()
270{
271 QScopedPointer<QQuickView> windowPtr;
272 createView(window&: windowPtr, fileName: "buttons.qml");
273 QQuickView * window = windowPtr.data();
274
275 QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "WithinBounds");
276 QVERIFY(buttonWithinBounds);
277 QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
278
279 // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
280 QPoint p1 = buttonWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
281 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
282 QQuickTouchUtils::flush(window);
283 QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
284 p1 += QPoint(50, 0);
285 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
286 QQuickTouchUtils::flush(window);
287 QVERIFY(buttonWithinBounds->property("pressed").toBool());
288 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
289 QQuickTouchUtils::flush(window);
290 QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
291 QCOMPARE(withinBoundsTappedSpy.count(), 1);
292
293 // WithinBounds button is no longer pressed if touchpoint leaves bounds
294 withinBoundsTappedSpy.clear();
295 p1 = buttonWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
296 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
297 QQuickTouchUtils::flush(window);
298 QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
299 p1 += QPoint(0, 100);
300 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
301 QQuickTouchUtils::flush(window);
302 QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
303 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
304 QQuickTouchUtils::flush(window);
305 QVERIFY(!buttonWithinBounds->property("pressed").toBool());
306 QCOMPARE(withinBoundsTappedSpy.count(), 0);
307}
308
309void tst_TapHandler::mouseGesturePolicyWithinBounds()
310{
311 QScopedPointer<QQuickView> windowPtr;
312 createView(window&: windowPtr, fileName: "buttons.qml");
313 QQuickView * window = windowPtr.data();
314
315 QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "WithinBounds");
316 QVERIFY(buttonWithinBounds);
317 QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
318
319 // WithinBounds button stays pressed while touchpoint stays within bounds, emits tapped on release
320 QPoint p1 = buttonWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
321 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
322 QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
323 p1 += QPoint(50, 0);
324 QTest::mouseMove(window, pos: p1);
325 QVERIFY(buttonWithinBounds->property("pressed").toBool());
326 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
327 QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
328 QCOMPARE(withinBoundsTappedSpy.count(), 1);
329
330 // WithinBounds button is no longer pressed if touchpoint leaves bounds
331 withinBoundsTappedSpy.clear();
332 p1 = buttonWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
333 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
334 QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
335 p1 += QPoint(0, 100);
336 QTest::mouseMove(window, pos: p1);
337 QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
338 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
339 QVERIFY(!buttonWithinBounds->property("pressed").toBool());
340 QCOMPARE(withinBoundsTappedSpy.count(), 0);
341}
342
343void tst_TapHandler::touchGesturePolicyReleaseWithinBounds()
344{
345 QScopedPointer<QQuickView> windowPtr;
346 createView(window&: windowPtr, fileName: "buttons.qml");
347 QQuickView * window = windowPtr.data();
348
349 QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "ReleaseWithinBounds");
350 QVERIFY(buttonReleaseWithinBounds);
351 QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
352
353 // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
354 // then if it comes back within bounds, emits tapped on release
355 QPoint p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
356 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
357 QQuickTouchUtils::flush(window);
358 QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
359 p1 += QPoint(50, 0);
360 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
361 QQuickTouchUtils::flush(window);
362 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
363 p1 += QPoint(250, 100);
364 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
365 QQuickTouchUtils::flush(window);
366 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
367 p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(25, 15)).toPoint();
368 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
369 QQuickTouchUtils::flush(window);
370 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
371 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
372 QQuickTouchUtils::flush(window);
373 QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
374 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
375
376 // ReleaseWithinBounds button does not emit tapped if released out of bounds
377 releaseWithinBoundsTappedSpy.clear();
378 p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
379 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
380 QQuickTouchUtils::flush(window);
381 QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
382 p1 += QPoint(0, 100);
383 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
384 QQuickTouchUtils::flush(window);
385 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
386 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
387 QQuickTouchUtils::flush(window);
388 QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
389 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
390}
391
392void tst_TapHandler::mouseGesturePolicyReleaseWithinBounds()
393{
394 QScopedPointer<QQuickView> windowPtr;
395 createView(window&: windowPtr, fileName: "buttons.qml");
396 QQuickView * window = windowPtr.data();
397
398 QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "ReleaseWithinBounds");
399 QVERIFY(buttonReleaseWithinBounds);
400 QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
401
402 // ReleaseWithinBounds button stays pressed while touchpoint wanders anywhere,
403 // then if it comes back within bounds, emits tapped on release
404 QPoint p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
405 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
406 QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
407 p1 += QPoint(50, 0);
408 QTest::mouseMove(window, pos: p1);
409 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
410 p1 += QPoint(250, 100);
411 QTest::mouseMove(window, pos: p1);
412 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
413 p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(25, 15)).toPoint();
414 QTest::mouseMove(window, pos: p1);
415 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
416 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
417 QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
418 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
419
420 // ReleaseWithinBounds button does not emit tapped if released out of bounds
421 releaseWithinBoundsTappedSpy.clear();
422 p1 = buttonReleaseWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
423 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
424 QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
425 p1 += QPoint(0, 100);
426 QTest::mouseMove(window, pos: p1);
427 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
428 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
429 QTRY_VERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
430 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
431}
432
433void tst_TapHandler::touchMultiTap()
434{
435 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
436 QScopedPointer<QQuickView> windowPtr;
437 createView(window&: windowPtr, fileName: "buttons.qml");
438 QQuickView * window = windowPtr.data();
439
440 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
441 QVERIFY(button);
442 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
443
444 // Tap once
445 QPoint p1 = button->mapToScene(point: QPointF(2, 2)).toPoint();
446 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
447 QQuickTouchUtils::flush(window);
448 QTRY_VERIFY(button->property("pressed").toBool());
449 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
450 QQuickTouchUtils::flush(window);
451 QTRY_VERIFY(!button->property("pressed").toBool());
452 QCOMPARE(tappedSpy.count(), 1);
453
454 // Tap again in exactly the same place (not likely with touch in the real world)
455 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
456 QQuickTouchUtils::flush(window);
457 QTRY_VERIFY(button->property("pressed").toBool());
458 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
459 QQuickTouchUtils::flush(window);
460 QTRY_VERIFY(!button->property("pressed").toBool());
461 QCOMPARE(tappedSpy.count(), 2);
462
463 // Tap a third time, nearby
464 p1 += QPoint(dragThreshold, dragThreshold);
465 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
466 QQuickTouchUtils::flush(window);
467 QTRY_VERIFY(button->property("pressed").toBool());
468 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
469 QQuickTouchUtils::flush(window);
470 QTRY_VERIFY(!button->property("pressed").toBool());
471 QCOMPARE(tappedSpy.count(), 3);
472
473 // Tap a fourth time, drifting farther away
474 p1 += QPoint(dragThreshold, dragThreshold);
475 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
476 QQuickTouchUtils::flush(window);
477 QTRY_VERIFY(button->property("pressed").toBool());
478 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
479 QQuickTouchUtils::flush(window);
480 QTRY_VERIFY(!button->property("pressed").toBool());
481 QCOMPARE(tappedSpy.count(), 4);
482}
483
484void tst_TapHandler::mouseMultiTap()
485{
486 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
487 QScopedPointer<QQuickView> windowPtr;
488 createView(window&: windowPtr, fileName: "buttons.qml");
489 QQuickView * window = windowPtr.data();
490
491 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
492 QVERIFY(button);
493 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
494
495 // Tap once
496 QPoint p1 = button->mapToScene(point: QPointF(2, 2)).toPoint();
497 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
498 QTRY_VERIFY(button->property("pressed").toBool());
499 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
500 QTRY_VERIFY(!button->property("pressed").toBool());
501 QCOMPARE(tappedSpy.count(), 1);
502
503 // Tap again in exactly the same place (not likely with touch in the real world)
504 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
505 QTRY_VERIFY(button->property("pressed").toBool());
506 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
507 QTRY_VERIFY(!button->property("pressed").toBool());
508 QCOMPARE(tappedSpy.count(), 2);
509
510 // Tap a third time, nearby
511 p1 += QPoint(dragThreshold, dragThreshold);
512 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
513 QTRY_VERIFY(button->property("pressed").toBool());
514 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
515 QTRY_VERIFY(!button->property("pressed").toBool());
516 QCOMPARE(tappedSpy.count(), 3);
517
518 // Tap a fourth time, drifting farther away
519 p1 += QPoint(dragThreshold, dragThreshold);
520 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
521 QTRY_VERIFY(button->property("pressed").toBool());
522 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
523 QTRY_VERIFY(!button->property("pressed").toBool());
524 QCOMPARE(tappedSpy.count(), 4);
525}
526
527void tst_TapHandler::touchLongPress()
528{
529 QScopedPointer<QQuickView> windowPtr;
530 createView(window&: windowPtr, fileName: "buttons.qml");
531 QQuickView * window = windowPtr.data();
532
533 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
534 QVERIFY(button);
535 QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>(aName: "DragThreshold");
536 QVERIFY(tapHandler);
537 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
538 QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
539 QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
540 QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
541
542 // Reduce the threshold so that we can get a long press quickly
543 tapHandler->setLongPressThreshold(0.5);
544 QCOMPARE(longPressThresholdChangedSpy.count(), 1);
545
546 // Press and hold
547 QPoint p1 = button->mapToScene(point: button->clipRect().center()).toPoint();
548 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
549 QQuickTouchUtils::flush(window);
550 QTRY_VERIFY(button->property("pressed").toBool());
551 QTRY_COMPARE(longPressedSpy.count(), 1);
552 timeHeldSpy.wait(); // the longer we hold it, the more this will occur
553 qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times";
554 QVERIFY(timeHeldSpy.count() > 0);
555 QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
556
557 // Release and verify that tapped was not emitted
558 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
559 QQuickTouchUtils::flush(window);
560 QTRY_VERIFY(!button->property("pressed").toBool());
561 QCOMPARE(tappedSpy.count(), 0);
562}
563
564void tst_TapHandler::mouseLongPress()
565{
566 QScopedPointer<QQuickView> windowPtr;
567 createView(window&: windowPtr, fileName: "buttons.qml");
568 QQuickView * window = windowPtr.data();
569
570 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
571 QVERIFY(button);
572 QQuickTapHandler *tapHandler = button->findChild<QQuickTapHandler*>(aName: "DragThreshold");
573 QVERIFY(tapHandler);
574 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
575 QSignalSpy longPressThresholdChangedSpy(tapHandler, SIGNAL(longPressThresholdChanged()));
576 QSignalSpy timeHeldSpy(tapHandler, SIGNAL(timeHeldChanged()));
577 QSignalSpy longPressedSpy(tapHandler, SIGNAL(longPressed()));
578
579 // Reduce the threshold so that we can get a long press quickly
580 tapHandler->setLongPressThreshold(0.5);
581 QCOMPARE(longPressThresholdChangedSpy.count(), 1);
582
583 // Press and hold
584 QPoint p1 = button->mapToScene(point: button->clipRect().center()).toPoint();
585 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
586 QTRY_VERIFY(button->property("pressed").toBool());
587 QTRY_COMPARE(longPressedSpy.count(), 1);
588 timeHeldSpy.wait(); // the longer we hold it, the more this will occur
589 qDebug() << "held" << tapHandler->timeHeld() << "secs; timeHeld updated" << timeHeldSpy.count() << "times";
590 QVERIFY(timeHeldSpy.count() > 0);
591 QVERIFY(tapHandler->timeHeld() > 0.4); // Should be > 0.5 but slow CI and timer granularity can interfere
592
593 // Release and verify that tapped was not emitted
594 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1, delay: 500);
595 QTRY_VERIFY(!button->property("pressed").toBool());
596 QCOMPARE(tappedSpy.count(), 0);
597}
598
599void tst_TapHandler::buttonsMultiTouch()
600{
601 QScopedPointer<QQuickView> windowPtr;
602 createView(window&: windowPtr, fileName: "buttons.qml");
603 QQuickView * window = windowPtr.data();
604
605 QQuickItem *buttonDragThreshold = window->rootObject()->findChild<QQuickItem*>(aName: "DragThreshold");
606 QVERIFY(buttonDragThreshold);
607 QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped()));
608
609 QQuickItem *buttonWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "WithinBounds");
610 QVERIFY(buttonWithinBounds);
611 QSignalSpy withinBoundsTappedSpy(buttonWithinBounds, SIGNAL(tapped()));
612
613 QQuickItem *buttonReleaseWithinBounds = window->rootObject()->findChild<QQuickItem*>(aName: "ReleaseWithinBounds");
614 QVERIFY(buttonReleaseWithinBounds);
615 QSignalSpy releaseWithinBoundsTappedSpy(buttonReleaseWithinBounds, SIGNAL(tapped()));
616 QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, device: touchDevice, autoCommit: false);
617
618 // can press multiple buttons at the same time
619 QPoint p1 = buttonDragThreshold->mapToScene(point: QPointF(20, 20)).toPoint();
620 touchSeq.press(touchId: 1, pt: p1, window).commit();
621 QQuickTouchUtils::flush(window);
622 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
623 QPoint p2 = buttonWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
624 touchSeq.stationary(touchId: 1).press(touchId: 2, pt: p2, window).commit();
625 QQuickTouchUtils::flush(window);
626 QTRY_VERIFY(buttonWithinBounds->property("pressed").toBool());
627 QVERIFY(buttonWithinBounds->property("active").toBool());
628 QPoint p3 = buttonReleaseWithinBounds->mapToScene(point: QPointF(20, 20)).toPoint();
629 touchSeq.stationary(touchId: 1).stationary(touchId: 2).press(touchId: 3, pt: p3, window).commit();
630 QQuickTouchUtils::flush(window);
631 QTRY_VERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
632 QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
633 QVERIFY(buttonWithinBounds->property("pressed").toBool());
634 QVERIFY(buttonWithinBounds->property("active").toBool());
635 QVERIFY(buttonDragThreshold->property("pressed").toBool());
636
637 // combinations of small touchpoint movements and stationary points should not cause state changes
638 p1 += QPoint(2, 0);
639 p2 += QPoint(3, 0);
640 touchSeq.move(touchId: 1, pt: p1).move(touchId: 2, pt: p2).stationary(touchId: 3).commit();
641 QVERIFY(buttonDragThreshold->property("pressed").toBool());
642 QVERIFY(buttonWithinBounds->property("pressed").toBool());
643 QVERIFY(buttonWithinBounds->property("active").toBool());
644 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
645 QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
646 p3 += QPoint(4, 0);
647 touchSeq.stationary(touchId: 1).stationary(touchId: 2).move(touchId: 3, pt: p3).commit();
648 QVERIFY(buttonDragThreshold->property("pressed").toBool());
649 QVERIFY(buttonWithinBounds->property("pressed").toBool());
650 QVERIFY(buttonWithinBounds->property("active").toBool());
651 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
652 QVERIFY(buttonReleaseWithinBounds->property("active").toBool());
653
654 // can release top button and press again: others stay pressed the whole time
655 touchSeq.stationary(touchId: 2).stationary(touchId: 3).release(touchId: 1, pt: p1, window).commit();
656 QQuickTouchUtils::flush(window);
657 QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool());
658 QCOMPARE(dragThresholdTappedSpy.count(), 1);
659 QVERIFY(buttonWithinBounds->property("pressed").toBool());
660 QCOMPARE(withinBoundsTappedSpy.count(), 0);
661 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
662 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
663 touchSeq.stationary(touchId: 2).stationary(touchId: 3).press(touchId: 1, pt: p1, window).commit();
664 QQuickTouchUtils::flush(window);
665 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
666 QVERIFY(buttonWithinBounds->property("pressed").toBool());
667 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
668
669 // can release middle button and press again: others stay pressed the whole time
670 touchSeq.stationary(touchId: 1).stationary(touchId: 3).release(touchId: 2, pt: p2, window).commit();
671 QQuickTouchUtils::flush(window);
672 QTRY_VERIFY(!buttonWithinBounds->property("pressed").toBool());
673 QCOMPARE(withinBoundsTappedSpy.count(), 1);
674 QVERIFY(buttonDragThreshold->property("pressed").toBool());
675 QCOMPARE(dragThresholdTappedSpy.count(), 1);
676 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
677 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 0);
678 touchSeq.stationary(touchId: 1).stationary(touchId: 3).press(touchId: 2, pt: p2, window).commit();
679 QQuickTouchUtils::flush(window);
680 QVERIFY(buttonDragThreshold->property("pressed").toBool());
681 QVERIFY(buttonWithinBounds->property("pressed").toBool());
682 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
683
684 // can release bottom button and press again: others stay pressed the whole time
685 touchSeq.stationary(touchId: 1).stationary(touchId: 2).release(touchId: 3, pt: p3, window).commit();
686 QQuickTouchUtils::flush(window);
687 QCOMPARE(releaseWithinBoundsTappedSpy.count(), 1);
688 QVERIFY(buttonWithinBounds->property("pressed").toBool());
689 QCOMPARE(withinBoundsTappedSpy.count(), 1);
690 QVERIFY(!buttonReleaseWithinBounds->property("pressed").toBool());
691 QCOMPARE(dragThresholdTappedSpy.count(), 1);
692 touchSeq.stationary(touchId: 1).stationary(touchId: 2).press(touchId: 3, pt: p3, window).commit();
693 QQuickTouchUtils::flush(window);
694 QTRY_VERIFY(buttonDragThreshold->property("pressed").toBool());
695 QVERIFY(buttonWithinBounds->property("pressed").toBool());
696 QVERIFY(buttonReleaseWithinBounds->property("pressed").toBool());
697}
698
699void tst_TapHandler::componentUserBehavioralOverride()
700{
701 QScopedPointer<QQuickView> windowPtr;
702 createView(window&: windowPtr, fileName: "buttonOverrideHandler.qml");
703 QQuickView * window = windowPtr.data();
704
705 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: "Overridden");
706 QVERIFY(button);
707 QQuickTapHandler *innerTapHandler = button->findChild<QQuickTapHandler*>(aName: "Overridden");
708 QVERIFY(innerTapHandler);
709 QQuickTapHandler *userTapHandler = button->findChild<QQuickTapHandler*>(aName: "override");
710 QVERIFY(userTapHandler);
711 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
712 QSignalSpy innerGrabChangedSpy(innerTapHandler, SIGNAL(grabChanged(QQuickEventPoint::GrabTransition, QQuickEventPoint *)));
713 QSignalSpy userGrabChangedSpy(userTapHandler, SIGNAL(grabChanged(QQuickEventPoint::GrabTransition, QQuickEventPoint *)));
714 QSignalSpy innerPressedChangedSpy(innerTapHandler, SIGNAL(pressedChanged()));
715 QSignalSpy userPressedChangedSpy(userTapHandler, SIGNAL(pressedChanged()));
716
717 // Press
718 QPoint p1 = button->mapToScene(point: button->clipRect().center()).toPoint();
719 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
720 QTRY_COMPARE(userPressedChangedSpy.count(), 1);
721 QCOMPARE(innerPressedChangedSpy.count(), 0);
722 QCOMPARE(innerGrabChangedSpy.count(), 0);
723 QCOMPARE(userGrabChangedSpy.count(), 1);
724
725 // Release
726 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
727 QTRY_COMPARE(userPressedChangedSpy.count(), 2);
728 QCOMPARE(innerPressedChangedSpy.count(), 0);
729 QCOMPARE(tappedSpy.count(), 1); // only because the override handler makes that happen
730 QCOMPARE(innerGrabChangedSpy.count(), 0);
731 QCOMPARE(userGrabChangedSpy.count(), 2);
732}
733
734void tst_TapHandler::rightLongPressIgnoreWheel()
735{
736 QScopedPointer<QQuickView> windowPtr;
737 createView(window&: windowPtr, fileName: "rightTapHandler.qml");
738 QQuickView * window = windowPtr.data();
739
740 QQuickTapHandler *tap = window->rootObject()->findChild<QQuickTapHandler*>();
741 QVERIFY(tap);
742 QSignalSpy tappedSpy(tap, SIGNAL(tapped(QQuickEventPoint *)));
743 QSignalSpy longPressedSpy(tap, SIGNAL(longPressed()));
744 QPoint p1(100, 100);
745
746 // Mouse wheel with ScrollBegin phase (because as soon as two fingers are touching
747 // the trackpad, it will send such an event: QTBUG-71955)
748 {
749 QWheelEvent wheelEvent(p1, p1, QPoint(0, 0), QPoint(0, 0),
750 Qt::NoButton, Qt::NoModifier, Qt::ScrollBegin, false, Qt::MouseEventNotSynthesized);
751 QGuiApplication::sendEvent(receiver: window, event: &wheelEvent);
752 }
753
754 // Press
755 QTest::mousePress(window, button: Qt::RightButton, stateKey: Qt::NoModifier, pos: p1);
756 QTRY_COMPARE(tap->isPressed(), true);
757
758 // Mouse wheel ScrollEnd phase
759 QWheelEvent wheelEvent(p1, p1, QPoint(0, 0), QPoint(0, 0),
760 Qt::NoButton, Qt::NoModifier, Qt::ScrollEnd, false, Qt::MouseEventNotSynthesized);
761 QGuiApplication::sendEvent(receiver: window, event: &wheelEvent);
762 QTRY_COMPARE(longPressedSpy.count(), 1);
763 QCOMPARE(tap->isPressed(), true);
764 QCOMPARE(tappedSpy.count(), 0);
765
766 // Release
767 QTest::mouseRelease(window, button: Qt::RightButton, stateKey: Qt::NoModifier, pos: p1, delay: 500);
768 QTRY_COMPARE(tap->isPressed(), false);
769 QCOMPARE(tappedSpy.count(), 0);
770}
771
772void tst_TapHandler::nonTopLevelParentWindow() // QTBUG-91716
773{
774 QScopedPointer<QQuickWindow> parentWindowPtr(new QQuickWindow);
775 auto parentWindow = parentWindowPtr.get();
776 parentWindow->setGeometry(posx: 400, posy: 400, w: 250, h: 250);
777
778 QScopedPointer<QQuickView> windowPtr;
779 createView(window&: windowPtr, fileName: "simpleTapHandler.qml", parent: parentWindow);
780 auto window = windowPtr.get();
781 window->setGeometry(posx: 10, posy: 10, w: 100, h: 100);
782
783 QQuickItem *root = window->rootObject();
784
785 auto p1 = QPoint(20, 20);
786 mouseEvent(type: QEvent::MouseButtonPress, button: Qt::LeftButton, point: p1, targetWindow: window, mapToWindow: parentWindow);
787 mouseEvent(type: QEvent::MouseButtonRelease, button: Qt::LeftButton, point: p1, targetWindow: window, mapToWindow: parentWindow);
788
789 QCOMPARE(root->property("tapCount").toInt(), 1);
790
791 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: p1, window: parentWindow).commit();
792 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: p1, window: parentWindow).commit();
793
794 QCOMPARE(root->property("tapCount").toInt(), 2);
795}
796
797QTEST_MAIN(tst_TapHandler)
798
799#include "tst_qquicktaphandler.moc"
800
801

source code of qtdeclarative/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp