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
30#include <QtTest/QtTest>
31
32#include <QtGui/qstylehints.h>
33#include <QtQuick/qquickview.h>
34#include <QtQuick/qquickitem.h>
35#include <QtQuick/private/qquickflickable_p.h>
36#include <QtQuick/private/qquickitemview_p.h>
37#include <QtQuick/private/qquickpointerhandler_p.h>
38#include <QtQuick/private/qquickdraghandler_p.h>
39#include <QtQuick/private/qquicktaphandler_p.h>
40#include <QtQuick/private/qquicktableview_p.h>
41#include <qpa/qwindowsysteminterface.h>
42
43#include <private/qquickwindow_p.h>
44
45#include <QtQml/qqmlengine.h>
46#include <QtQml/qqmlproperty.h>
47
48#include "../../../shared/util.h"
49#include "../../shared/viewtestutil.h"
50
51Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
52
53class tst_FlickableInterop : public QQmlDataTest
54{
55 Q_OBJECT
56public:
57 tst_FlickableInterop()
58 :touchDevice(QTest::createTouchDevice())
59 {}
60
61private slots:
62 void touchTapButton_data();
63 void touchTapButton();
64 void touchDragFlickableBehindButton_data();
65 void touchDragFlickableBehindButton();
66 void mouseClickButton_data();
67 void mouseClickButton();
68 void mouseDragFlickableBehindButton_data();
69 void mouseDragFlickableBehindButton();
70 void touchDragSlider();
71 void touchDragFlickableBehindSlider();
72 void mouseDragSlider_data();
73 void mouseDragSlider();
74 void mouseDragFlickableBehindSlider();
75 void touchDragFlickableBehindItemWithHandlers_data();
76 void touchDragFlickableBehindItemWithHandlers();
77 void mouseDragFlickableBehindItemWithHandlers_data();
78 void mouseDragFlickableBehindItemWithHandlers();
79 void touchDragSliderAndFlickable();
80 void touchAndDragHandlerOnFlickable_data();
81 void touchAndDragHandlerOnFlickable();
82
83private:
84 void createView(QScopedPointer<QQuickView> &window, const char *fileName);
85 QTouchDevice *touchDevice;
86};
87
88void tst_FlickableInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName)
89{
90 window.reset(other: new QQuickView);
91 window->setSource(testFileUrl(fileName));
92 QTRY_COMPARE(window->status(), QQuickView::Ready);
93 QQuickViewTestUtil::centerOnScreen(window: window.data());
94 QQuickViewTestUtil::moveMouseAway(window: window.data());
95
96 window->show();
97 QVERIFY(QTest::qWaitForWindowActive(window.data()));
98 QVERIFY(window->rootObject() != nullptr);
99}
100
101void tst_FlickableInterop::touchTapButton_data()
102{
103 QTest::addColumn<QString>(name: "buttonName");
104 QTest::newRow(dataTag: "DragThreshold") << QStringLiteral("DragThreshold");
105 QTest::newRow(dataTag: "WithinBounds") << QStringLiteral("WithinBounds");
106 QTest::newRow(dataTag: "ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
107}
108
109void tst_FlickableInterop::touchTapButton()
110{
111 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
112 QScopedPointer<QQuickView> windowPtr;
113 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
114 QQuickView * window = windowPtr.data();
115
116 QFETCH(QString, buttonName);
117
118 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: buttonName);
119 QVERIFY(button);
120 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
121
122 // Button changes pressed state and emits tapped on release
123 QPoint p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
124 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
125 QQuickTouchUtils::flush(window);
126 QTRY_VERIFY(button->property("pressed").toBool());
127 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
128 QQuickTouchUtils::flush(window);
129 QTRY_VERIFY(!button->property("pressed").toBool());
130 QCOMPARE(tappedSpy.count(), 1);
131
132 // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab
133 p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
134 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
135 QQuickTouchUtils::flush(window);
136 QTRY_VERIFY(button->property("pressed").toBool());
137 p1 += QPoint(dragThreshold, 0);
138 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
139 QQuickTouchUtils::flush(window);
140 QVERIFY(button->property("pressed").toBool());
141 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
142 QQuickTouchUtils::flush(window);
143 QTRY_VERIFY(!button->property("pressed").toBool());
144 QCOMPARE(tappedSpy.count(), 2);
145}
146
147void tst_FlickableInterop::touchDragFlickableBehindButton_data()
148{
149 QTest::addColumn<QString>(name: "buttonName");
150 QTest::newRow(dataTag: "DragThreshold") << QStringLiteral("DragThreshold");
151 QTest::newRow(dataTag: "WithinBounds") << QStringLiteral("WithinBounds");
152 QTest::newRow(dataTag: "ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
153}
154
155void tst_FlickableInterop::touchDragFlickableBehindButton()
156{
157 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
158 QScopedPointer<QQuickView> windowPtr;
159 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
160 QQuickView * window = windowPtr.data();
161
162 QFETCH(QString, buttonName);
163
164 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: buttonName);
165 QVERIFY(button);
166 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
167 QVERIFY(flickable);
168 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
169
170 tappedSpy.clear();
171 QPoint p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
172 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
173 QQuickTouchUtils::flush(window);
174 QTRY_VERIFY(button->property("pressed").toBool());
175 p1 += QPoint(dragThreshold, 0);
176 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
177 QQuickTouchUtils::flush(window);
178 QVERIFY(button->property("pressed").toBool());
179 int i = 0;
180 // Start dragging; eventually when the touchpoint goes beyond dragThreshold,
181 // Button is no longer pressed because Flickable steals the grab
182 for (; i < 100 && !flickable->isMoving(); ++i) {
183 p1 += QPoint(1, 0);
184 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
185 QQuickTouchUtils::flush(window);
186 }
187 qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1;
188 QVERIFY(flickable->isMoving());
189 QCOMPARE(i, 2);
190 QVERIFY(!button->property("pressed").toBool());
191 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
192 QQuickTouchUtils::flush(window);
193 QVERIFY(!button->property("pressed").toBool());
194 QCOMPARE(tappedSpy.count(), 0);
195}
196
197void tst_FlickableInterop::mouseClickButton_data()
198{
199 QTest::addColumn<QString>(name: "buttonName");
200 QTest::newRow(dataTag: "DragThreshold") << QStringLiteral("DragThreshold");
201 QTest::newRow(dataTag: "WithinBounds") << QStringLiteral("WithinBounds");
202 QTest::newRow(dataTag: "ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
203}
204
205void tst_FlickableInterop::mouseClickButton()
206{
207 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
208 QScopedPointer<QQuickView> windowPtr;
209 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
210 QQuickView * window = windowPtr.data();
211
212 QFETCH(QString, buttonName);
213
214 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: buttonName);
215 QVERIFY(button);
216 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
217
218 // Button changes pressed state and emits tapped on release
219 QPoint p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
220 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
221 QTRY_VERIFY(button->property("pressed").toBool());
222 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
223 QTRY_VERIFY(!button->property("pressed").toBool());
224 QCOMPARE(tappedSpy.count(), 1);
225
226 // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab
227 p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
228 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1, qApp->styleHints()->mouseDoubleClickInterval() + 10);
229 QTRY_VERIFY(button->property("pressed").toBool());
230 p1 += QPoint(dragThreshold, 0);
231 QTest::mouseMove(window, pos: p1);
232 QVERIFY(button->property("pressed").toBool());
233 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
234 QTRY_VERIFY(!button->property("pressed").toBool());
235 QCOMPARE(tappedSpy.count(), 2);
236}
237
238void tst_FlickableInterop::mouseDragFlickableBehindButton_data()
239{
240 QTest::addColumn<QString>(name: "buttonName");
241 QTest::newRow(dataTag: "DragThreshold") << QStringLiteral("DragThreshold");
242 QTest::newRow(dataTag: "WithinBounds") << QStringLiteral("WithinBounds");
243 QTest::newRow(dataTag: "ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds");
244}
245
246void tst_FlickableInterop::mouseDragFlickableBehindButton()
247{
248 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
249 QScopedPointer<QQuickView> windowPtr;
250 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
251 QQuickView * window = windowPtr.data();
252
253 QFETCH(QString, buttonName);
254
255 QQuickItem *button = window->rootObject()->findChild<QQuickItem*>(aName: buttonName);
256 QVERIFY(button);
257 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
258 QVERIFY(flickable);
259 QSignalSpy tappedSpy(button, SIGNAL(tapped()));
260
261 // Button is no longer pressed if touchpoint goes beyond dragThreshold,
262 // because Flickable steals the grab
263 tappedSpy.clear();
264 QPoint p1 = button->mapToScene(point: QPointF(20, 20)).toPoint();
265 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
266 QTRY_VERIFY(button->property("pressed").toBool());
267 p1 += QPoint(dragThreshold, 0);
268 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
269 QVERIFY(button->property("pressed").toBool());
270 int i = 0;
271 for (; i < 100 && !flickable->isMoving(); ++i) {
272 p1 += QPoint(1, 0);
273 QTest::mouseMove(window, pos: p1);
274 }
275 qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1;
276 QVERIFY(flickable->isMoving());
277 QCOMPARE(i, 2);
278 QVERIFY(!button->property("pressed").toBool());
279 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
280 QVERIFY(!button->property("pressed").toBool());
281 QCOMPARE(tappedSpy.count(), 0);
282}
283
284void tst_FlickableInterop::touchDragSlider()
285{
286 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
287 QScopedPointer<QQuickView> windowPtr;
288 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
289 QQuickView * window = windowPtr.data();
290
291 QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>(aName: "knobSlider");
292 QVERIFY(slider);
293 QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
294 QVERIFY(drag);
295 QQuickItem *knob = slider->findChild<QQuickItem*>(aName: "Slider Knob");
296 QVERIFY(knob);
297 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
298 QVERIFY(flickable);
299 QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
300 QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
301
302 // Drag the slider in the allowed (vertical) direction
303 tappedSpy.clear();
304 QPoint p1 = knob->mapToScene(point: knob->clipRect().center()).toPoint() - QPoint(0, 8);
305 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
306 QQuickTouchUtils::flush(window);
307 QTRY_VERIFY(slider->property("pressed").toBool());
308 p1 += QPoint(0, dragThreshold);
309 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
310 QQuickTouchUtils::flush(window);
311 QVERIFY(slider->property("pressed").toBool());
312 QCOMPARE(slider->property("value").toInt(), 49);
313 p1 += QPoint(0, 1);
314 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
315 QQuickTouchUtils::flush(window);
316 p1 += QPoint(0, 10);
317 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
318 QQuickTouchUtils::flush(window);
319 QVERIFY(slider->property("value").toInt() < 49);
320 QVERIFY(!flickable->isMoving());
321 QVERIFY(!slider->property("pressed").toBool());
322
323 // Now that the DragHandler is active, the Flickable will not steal the grab
324 // even if we move a large distance horizontally
325 p1 += QPoint(dragThreshold * 2, 0);
326 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
327 QQuickTouchUtils::flush(window);
328 QVERIFY(!flickable->isMoving());
329
330 // Release, and do not expect the tapped signal
331 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
332 QQuickTouchUtils::flush(window);
333 QCOMPARE(tappedSpy.count(), 0);
334 QCOMPARE(translationChangedSpy.count(), 1);
335}
336
337void tst_FlickableInterop::mouseDragSlider_data()
338{
339 QTest::addColumn<QString>(name: "nameOfSliderToDrag");
340 QTest::addColumn<QPoint>(name: "pressPositionRelativeToKnob");
341 QTest::addColumn<QPoint>(name: "dragDirection"); // a unit vector
342 QTest::addColumn<bool>(name: "expectedTapHandlerPressed");
343 QTest::addColumn<bool>(name: "expectedDragHandlerActive");
344 QTest::addColumn<bool>(name: "expectedFlickableMoving");
345
346 QTest::newRow(dataTag: "drag down on knob of knobSlider") << "knobSlider" << QPoint(0, -8) << QPoint(0, 1) << true << true << false;
347 QTest::newRow(dataTag: "drag sideways on knob of knobSlider") << "knobSlider" << QPoint(0, 0) << QPoint(1, 0) << true << false << true;
348 QTest::newRow(dataTag: "drag down on groove of knobSlider") << "knobSlider" << QPoint(0, 20) << QPoint(0, 1) << false << false << true;
349 QTest::newRow(dataTag: "drag sideways on groove of knobSlider") << "knobSlider" << QPoint(0, 20) << QPoint(1, 0) << false << false << true;
350
351 QTest::newRow(dataTag: "drag down on knob of grooveSlider") << "grooveSlider" << QPoint(0, -8) << QPoint(0, 1) << true << true << false;
352 QTest::newRow(dataTag: "drag sideways on knob of grooveSlider") << "grooveSlider" << QPoint(0, 0) << QPoint(1, 0) << true << false << true;
353 QTest::newRow(dataTag: "drag down on groove of grooveSlider") << "grooveSlider" << QPoint(0, 20) << QPoint(0, 1) << false << true << false;
354 QTest::newRow(dataTag: "drag sideways on groove of grooveSlider") << "grooveSlider" << QPoint(0, 20) << QPoint(1, 0) << false << false << true;
355}
356
357void tst_FlickableInterop::mouseDragSlider()
358{
359 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
360 QScopedPointer<QQuickView> windowPtr;
361 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
362 QQuickView * window = windowPtr.data();
363
364 QFETCH(QString, nameOfSliderToDrag);
365 QFETCH(QPoint, pressPositionRelativeToKnob);
366 QFETCH(QPoint, dragDirection); // a unit vector
367 QFETCH(bool, expectedTapHandlerPressed);
368 QFETCH(bool, expectedDragHandlerActive);
369 QFETCH(bool, expectedFlickableMoving);
370
371 QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>(aName: nameOfSliderToDrag);
372 QVERIFY(slider);
373 QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
374 QVERIFY(drag);
375 QQuickItem *knob = slider->findChild<QQuickItem*>(aName: "Slider Knob");
376 QVERIFY(knob);
377 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
378 QVERIFY(flickable);
379 QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
380 QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
381
382 // Drag the slider
383 tappedSpy.clear();
384 QPoint p1 = knob->mapToScene(point: knob->clipRect().center()).toPoint() + pressPositionRelativeToKnob;
385 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
386 QTRY_COMPARE(slider->property("pressed").toBool(), expectedTapHandlerPressed);
387 p1 += QPoint(dragThreshold * dragDirection.x(), dragThreshold * dragDirection.y());
388 QTest::mouseMove(window, pos: p1);
389 QCOMPARE(drag->active(), false);
390 QCOMPARE(slider->property("pressed").toBool(), expectedTapHandlerPressed);
391 QCOMPARE(slider->property("value").toInt(), 49);
392 p1 += dragDirection; // one more pixel
393 QTest::mouseMove(window, pos: p1);
394 // After moving by the drag threshold, the point should still be inside the knob.
395 // However, QQuickTapHandler::wantsEventPoint() returns false because the drag threshold is exceeded.
396 // Therefore QQuickTapHandler::setPressed(false, true, point) is called: the active state is canceled.
397 QCOMPARE(slider->property("pressed").toBool(), false);
398 QCOMPARE(drag->active(), expectedDragHandlerActive);
399 // drag farther, to make sure the knob gets adjusted significantly
400 p1 += QPoint(10 * dragDirection.x(), 10 * dragDirection.y());
401 QTest::mouseMove(window, pos: p1);
402 if (expectedDragHandlerActive && dragDirection.y() > 0)
403 QVERIFY(slider->property("value").toInt() < 49);
404 // by now, Flickable will have stolen the grab, if it decided that it wanted to during filtering of the last event
405 QCOMPARE(flickable->isMoving(), expectedFlickableMoving);
406 QCOMPARE(slider->property("pressed").toBool(), false);
407
408 // If the DragHandler is active, the Flickable will not steal the grab
409 // even if we move a large distance horizontally
410 if (expectedDragHandlerActive) {
411 p1 += QPoint(dragThreshold * 2, 0);
412 QTest::mouseMove(window, pos: p1);
413 QCOMPARE(flickable->isMoving(), false);
414 }
415
416 // Release, and do not expect the tapped signal
417 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
418 QCOMPARE(tappedSpy.count(), 0);
419 QCOMPARE(translationChangedSpy.count(), expectedDragHandlerActive ? 1 : 0);
420}
421
422void tst_FlickableInterop::touchDragFlickableBehindSlider()
423{
424 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
425 QScopedPointer<QQuickView> windowPtr;
426 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
427 QQuickView * window = windowPtr.data();
428
429 QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>(aName: "knobSlider");
430 QVERIFY(slider);
431 QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
432 QVERIFY(drag);
433 QQuickItem *knob = slider->findChild<QQuickItem*>(aName: "Slider Knob");
434 QVERIFY(knob);
435 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
436 QVERIFY(flickable);
437 QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
438 QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
439
440 // Button is no longer pressed if touchpoint goes beyond dragThreshold,
441 // because Flickable steals the grab
442 tappedSpy.clear();
443 QPoint p1 = knob->mapToScene(point: knob->clipRect().center()).toPoint();
444 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
445 QQuickTouchUtils::flush(window);
446 QTRY_VERIFY(slider->property("pressed").toBool());
447 p1 += QPoint(dragThreshold, 0);
448 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
449 QQuickTouchUtils::flush(window);
450 QVERIFY(slider->property("pressed").toBool());
451 int i = 0;
452 for (; i < 100 && !flickable->isMoving(); ++i) {
453 p1 += QPoint(1, 0);
454 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
455 QQuickTouchUtils::flush(window);
456 }
457 qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1;
458 QVERIFY(flickable->isMoving());
459 QCOMPARE(i, 2);
460 QVERIFY(!slider->property("pressed").toBool());
461 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
462 QQuickTouchUtils::flush(window);
463 QVERIFY(!slider->property("pressed").toBool());
464 QCOMPARE(tappedSpy.count(), 0);
465 QCOMPARE(translationChangedSpy.count(), 0);
466}
467
468void tst_FlickableInterop::mouseDragFlickableBehindSlider()
469{
470 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
471 QScopedPointer<QQuickView> windowPtr;
472 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
473 QQuickView * window = windowPtr.data();
474
475 QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>(aName: "knobSlider");
476 QVERIFY(slider);
477 QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
478 QVERIFY(drag);
479 QQuickItem *knob = slider->findChild<QQuickItem*>(aName: "Slider Knob");
480 QVERIFY(knob);
481 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
482 QVERIFY(flickable);
483 QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped()));
484 QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged()));
485
486 // Button is no longer pressed if touchpoint goes beyond dragThreshold,
487 // because Flickable steals the grab
488 tappedSpy.clear();
489 QPoint p1 = knob->mapToScene(point: knob->clipRect().center()).toPoint();
490 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
491 QTRY_VERIFY(slider->property("pressed").toBool());
492 p1 += QPoint(dragThreshold, 0);
493 QTest::mouseMove(window, pos: p1);
494 QQuickTouchUtils::flush(window);
495 QVERIFY(slider->property("pressed").toBool());
496 int i = 0;
497 for (; i < 100 && !flickable->isMoving(); ++i) {
498 p1 += QPoint(1, 0);
499 QTest::mouseMove(window, pos: p1);
500 }
501 qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1;
502 QVERIFY(flickable->isMoving());
503 QCOMPARE(i, 2);
504 QVERIFY(!slider->property("pressed").toBool());
505 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
506 QCOMPARE(tappedSpy.count(), 0);
507 QCOMPARE(translationChangedSpy.count(), 0);
508}
509
510void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers_data()
511{
512 QTest::addColumn<QByteArray>(name: "nameOfRectangleToDrag");
513 QTest::addColumn<bool>(name: "expectedFlickableMoving");
514 QTest::newRow(dataTag: "drag") << QByteArray("drag") << false;
515 QTest::newRow(dataTag: "tap") << QByteArray("tap") << true;
516 QTest::newRow(dataTag: "dragAndTap") << QByteArray("dragAndTap") << false;
517 QTest::newRow(dataTag: "tapAndDrag") << QByteArray("tapAndDrag") << false;
518}
519
520void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers()
521{
522 QFETCH(bool, expectedFlickableMoving);
523 QFETCH(QByteArray, nameOfRectangleToDrag);
524 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
525 QScopedPointer<QQuickView> windowPtr;
526 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
527 QQuickView * window = windowPtr.data();
528 QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(aName: nameOfRectangleToDrag);
529 QVERIFY(rect);
530 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
531 QVERIFY(flickable);
532 QPoint p1 = rect->mapToScene(point: rect->clipRect().center()).toPoint();
533 QPoint originP1 = p1;
534
535 QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window);
536 QQuickTouchUtils::flush(window);
537 for (int i = 0; i < dragThreshold * 3; ++i) {
538 p1 = originP1;
539 p1.rx() += i;
540 QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window);
541 QQuickTouchUtils::flush(window);
542 }
543 QCOMPARE(flickable->isMoving(), expectedFlickableMoving);
544 if (!expectedFlickableMoving) {
545 QVERIFY(rect->mapToScene(rect->clipRect().center()).toPoint().x() > originP1.x());
546 }
547 QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window);
548 QQuickTouchUtils::flush(window);
549}
550
551void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers_data()
552{
553 touchDragFlickableBehindItemWithHandlers_data();
554}
555
556void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers()
557{
558 QFETCH(bool, expectedFlickableMoving);
559 QFETCH(QByteArray, nameOfRectangleToDrag);
560 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
561 QScopedPointer<QQuickView> windowPtr;
562 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
563 QQuickView * window = windowPtr.data();
564 QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(aName: nameOfRectangleToDrag);
565 QVERIFY(rect);
566 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
567 QVERIFY(flickable);
568 QPoint p1 = rect->mapToScene(point: rect->clipRect().center()).toPoint();
569 QPoint originP1 = p1;
570 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
571 for (int i = 0; i < 3; ++i) {
572 p1 += QPoint(dragThreshold, 0);
573 QTest::mouseMove(window, pos: p1);
574 QQuickTouchUtils::flush(window);
575 }
576 QCOMPARE(flickable->isMoving(), expectedFlickableMoving);
577 if (!expectedFlickableMoving) {
578 QCOMPARE(originP1 + QPoint(3*dragThreshold, 0), p1);
579 }
580 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
581 // wait until flickable stops
582 QTRY_COMPARE(flickable->isMoving(), false);
583
584 // After the mouse button has been released, move the mouse and ensure that nothing is moving
585 // because of that (this tests if all grabs are released when the mouse button is released).
586 p1 = rect->mapToScene(point: rect->clipRect().center()).toPoint();
587 originP1 = p1;
588 for (int i = 0; i < 3; ++i) {
589 p1 += QPoint(dragThreshold, 0);
590 QTest::mouseMove(window, pos: p1);
591 QQuickTouchUtils::flush(window);
592 }
593 QCOMPARE(flickable->isMoving(), false);
594 QCOMPARE(originP1, rect->mapToScene(rect->clipRect().center()).toPoint());
595}
596
597void tst_FlickableInterop::touchDragSliderAndFlickable()
598{
599 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
600 QScopedPointer<QQuickView> windowPtr;
601 createView(window&: windowPtr, fileName: "flickableWithHandlers.qml");
602 QQuickView * window = windowPtr.data();
603
604 QQuickItem *slider = window->rootObject()->findChild<QQuickItem*>(aName: "knobSlider");
605 QVERIFY(slider);
606 QQuickDragHandler *drag = slider->findChild<QQuickDragHandler*>();
607 QVERIFY(drag);
608 QQuickItem *knob = slider->findChild<QQuickItem*>(aName: "Slider Knob");
609 QVERIFY(knob);
610 QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>();
611 QVERIFY(flickable);
612 QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, device: touchDevice, autoCommit: false);
613
614 // The knob is initially centered over the slider's "groove"
615 qreal initialXOffset = qAbs(t: knob->mapToScene(point: knob->clipRect().center()).x() - slider->mapToScene
616 (point: slider->clipRect().center()).x());
617 QVERIFY(initialXOffset <= 1);
618
619 // Drag the slider in the allowed (vertical) direction with one finger
620 QPoint p1 = knob->mapToScene(point: knob->clipRect().center()).toPoint();
621 touchSeq.press(touchId: 1, pt: p1, window).commit();
622 QQuickTouchUtils::flush(window);
623 p1 += QPoint(0, dragThreshold);
624 touchSeq.move(touchId: 1, pt: p1, window).commit();
625 QQuickTouchUtils::flush(window);
626 p1 += QPoint(0, dragThreshold);
627 touchSeq.move(touchId: 1, pt: p1, window).commit();
628 QQuickTouchUtils::flush(window);
629 p1 += QPoint(0, dragThreshold);
630 touchSeq.move(touchId: 1, pt: p1, window).commit();
631 QQuickTouchUtils::flush(window);
632 QTRY_VERIFY(slider->property("value").toInt() < 49);
633 QVERIFY(!flickable->isMoving());
634
635 // Drag the Flickable with a second finger
636 QPoint p2(300,300);
637 touchSeq.stationary(touchId: 1).press(touchId: 2, pt: p2, window).commit();
638 QQuickTouchUtils::flush(window);
639 for (int i = 0; i < 4; ++i) {
640 p1 += QPoint(-10, -10);
641 p2 += QPoint(dragThreshold, 0);
642 touchSeq.move(touchId: 1, pt: p1, window).stationary(touchId: 2).commit();
643 QQuickTouchUtils::flush(window);
644 p1 += QPoint(-10, -10);
645 p2 += QPoint(dragThreshold, 0);
646 touchSeq.stationary(touchId: 1).move(touchId: 2, pt: p2, window).commit();
647 QQuickTouchUtils::flush(window);
648 qCDebug(lcPointerTests) << "step" << i << ": fingers @" << p1 << p2 << "is Flickable moving yet?" << flickable->isMoving();
649 }
650 QVERIFY(flickable->isMoving());
651 qreal knobSliderXOffset = qAbs(t: knob->mapToScene(point: knob->clipRect().center()).toPoint().x() -
652 slider->mapToScene(point: slider->clipRect().center()).toPoint().x()) - initialXOffset;
653 if (knobSliderXOffset > 1)
654 qCDebug(lcPointerTests) << "knob has slipped out of groove by" << knobSliderXOffset << "pixels";
655 // See if the knob is still centered over the slider's "groove"
656 QVERIFY(qAbs(knobSliderXOffset) <= 1);
657
658 // Release
659 touchSeq.release(touchId: 1, pt: p1, window).release(touchId: 2, pt: p2, window).commit();
660}
661
662void tst_FlickableInterop::touchAndDragHandlerOnFlickable_data()
663{
664 QTest::addColumn<QByteArray>(name: "qmlFile");
665 QTest::addColumn<bool>(name: "pressDelay");
666 QTest::addColumn<bool>(name: "targetNull");
667 QTest::newRow(dataTag: "tapOnFlickable") << QByteArray("tapOnFlickable.qml") << false << false;
668 QTest::newRow(dataTag: "tapOnList") << QByteArray("tapOnList.qml") << false << false;
669 QTest::newRow(dataTag: "tapOnTable") << QByteArray("tapOnTable.qml") << false << false;
670 QTest::newRow(dataTag: "dragOnFlickable") << QByteArray("dragOnFlickable.qml") << false << false;
671 QTest::newRow(dataTag: "dragOnList") << QByteArray("dragOnList.qml") << false << false;
672 QTest::newRow(dataTag: "dragOnTable") << QByteArray("dragOnTable.qml") << false << false;
673 QTest::newRow(dataTag: "tapDelayOnFlickable") << QByteArray("tapOnFlickable.qml") << true << false;
674 QTest::newRow(dataTag: "tapDelayOnList") << QByteArray("tapOnList.qml") << true << false;
675 QTest::newRow(dataTag: "tapDelayOnTable") << QByteArray("tapOnTable.qml") << true << false;
676 QTest::newRow(dataTag: "dragDelayOnFlickable") << QByteArray("dragOnFlickable.qml") << true << false;
677 QTest::newRow(dataTag: "dragDelayOnList") << QByteArray("dragOnList.qml") << true << false;
678 QTest::newRow(dataTag: "dragDelayOnTable") << QByteArray("dragOnTable.qml") << true << false;
679 QTest::newRow(dataTag: "tapOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << false << true;
680 QTest::newRow(dataTag: "tapOnListWithNullTargets") << QByteArray("tapOnList.qml") << false << true;
681 QTest::newRow(dataTag: "tapOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << false << true;
682 QTest::newRow(dataTag: "dragOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << false << true;
683 QTest::newRow(dataTag: "dragOnListWithNullTargets") << QByteArray("dragOnList.qml") << false << true;
684 QTest::newRow(dataTag: "dragOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << false << true;
685 QTest::newRow(dataTag: "tapDelayOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << true << true;
686 QTest::newRow(dataTag: "tapDelayOnListWithNullTargets") << QByteArray("tapOnList.qml") << true << true;
687 QTest::newRow(dataTag: "tapDelayOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << true << true;
688 QTest::newRow(dataTag: "dragDelayOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << true << true;
689 QTest::newRow(dataTag: "dragDelayOnListWithNullTargets") << QByteArray("dragOnList.qml") << true << true;
690 QTest::newRow(dataTag: "dragDelayOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << true << true;
691}
692
693void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
694{
695 QFETCH(QByteArray, qmlFile);
696 QFETCH(bool, pressDelay);
697 QFETCH(bool, targetNull);
698
699 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
700 QScopedPointer<QQuickView> windowPtr;
701 createView(window&: windowPtr, fileName: qmlFile.constData());
702 QQuickView * window = windowPtr.data();
703 QQuickFlickable *flickable = qmlobject_cast<QQuickFlickable*>(object: window->rootObject());
704 QVERIFY(flickable);
705 flickable->setPressDelay(pressDelay ? 5000 : 0);
706 QQuickItem *delegate = nullptr;
707 if (QQuickItemView *itemView = qmlobject_cast<QQuickItemView *>(object: flickable))
708 delegate = itemView->currentItem();
709 if (!delegate)
710 delegate = flickable->property(name: "delegateUnderTest").value<QQuickItem*>();
711 QQuickItem *button = delegate ? delegate->findChild<QQuickItem*>(aName: "button")
712 : flickable->findChild<QQuickItem*>(aName: "button");
713 if (!button)
714 button = flickable->property(name: "buttonUnderTest").value<QQuickItem*>();
715 QVERIFY(button);
716 QQuickPointerHandler *buttonHandler = button->findChild<QQuickPointerHandler*>();
717 QVERIFY(buttonHandler);
718 QQuickTapHandler *buttonTapHandler = qmlobject_cast<QQuickTapHandler *>(object: buttonHandler);
719 QQuickDragHandler *buttonDragHandler = qmlobject_cast<QQuickDragHandler *>(object: buttonHandler);
720 QQuickPointerHandler *delegateHandler = delegate ? delegate->findChild<QQuickPointerHandler*>() : nullptr;
721 QQuickPointerHandler *contentItemHandler = flickable->findChild<QQuickPointerHandler*>();
722 QVERIFY(contentItemHandler);
723 // a handler declared directly in a Flickable (or item view) must actually be a child of the contentItem,
724 // just as Items declared inside are (QTBUG-71918 and QTBUG-73035)
725 QCOMPARE(contentItemHandler->parentItem(), flickable->contentItem());
726 if (targetNull) {
727 buttonHandler->setTarget(nullptr);
728 if (delegateHandler)
729 delegateHandler->setTarget(nullptr);
730 contentItemHandler->setTarget(nullptr);
731 }
732
733 // Drag one finger on the Flickable and make sure it flicks
734 QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, device: touchDevice, autoCommit: false);
735 QPoint p1(780, 460);
736 touchSeq.press(touchId: 1, pt: p1, window).commit();
737 QQuickTouchUtils::flush(window);
738 for (int i = 0; i < 4; ++i) {
739 p1 -= QPoint(dragThreshold, dragThreshold);
740 touchSeq.move(touchId: 1, pt: p1, window).commit();
741 QQuickTouchUtils::flush(window);
742 }
743 if (!(buttonDragHandler && !pressDelay))
744 QTRY_VERIFY(flickable->contentY() >= dragThreshold);
745 if (buttonTapHandler)
746 QCOMPARE(buttonTapHandler->isPressed(), false);
747 touchSeq.release(touchId: 1, pt: p1, window).commit();
748 QQuickTouchUtils::flush(window);
749
750 // Drag one finger on the delegate and make sure Flickable flicks
751 if (delegate) {
752 flickable->setContentY(0);
753 QTRY_COMPARE(flickable->isMoving(), false);
754 QVERIFY(delegateHandler);
755 QQuickTapHandler *delegateTapHandler = qmlobject_cast<QQuickTapHandler *>(object: delegateHandler);
756 p1 = button->mapToScene(point: button->clipRect().bottomRight()).toPoint() + QPoint(10, 0);
757 touchSeq.press(touchId: 1, pt: p1, window).commit();
758 QQuickTouchUtils::flush(window);
759 if (delegateTapHandler && !pressDelay)
760 QCOMPARE(delegateTapHandler->isPressed(), true);
761 for (int i = 0; i < 4; ++i) {
762 p1 -= QPoint(dragThreshold, dragThreshold);
763 touchSeq.move(touchId: 1, pt: p1, window).commit();
764 QQuickTouchUtils::flush(window);
765 if (i > 1)
766 QTRY_VERIFY(delegateHandler->active() || flickable->isMoving());
767 }
768 if (!(buttonDragHandler && !pressDelay))
769 QVERIFY(flickable->contentY() > 0);
770 if (delegateTapHandler)
771 QCOMPARE(delegateTapHandler->isPressed(), false);
772 touchSeq.release(touchId: 1, pt: p1, window).commit();
773 QQuickTouchUtils::flush(window);
774 }
775
776 // Drag one finger on the button and make sure Flickable flicks
777 flickable->setContentY(0);
778 QTRY_COMPARE(flickable->isMoving(), false);
779 p1 = button->mapToScene(point: button->clipRect().center()).toPoint();
780 touchSeq.press(touchId: 1, pt: p1, window).commit();
781 QQuickTouchUtils::flush(window);
782 if (buttonTapHandler && !pressDelay)
783 QTRY_COMPARE(buttonTapHandler->isPressed(), true);
784 for (int i = 0; i < 4; ++i) {
785 p1 -= QPoint(dragThreshold, dragThreshold);
786 touchSeq.move(touchId: 1, pt: p1, window).commit();
787 QQuickTouchUtils::flush(window);
788 }
789 if (!(buttonDragHandler && !pressDelay))
790 QVERIFY(flickable->contentY() > 0);
791 if (buttonTapHandler)
792 QCOMPARE(buttonTapHandler->isPressed(), false);
793 touchSeq.release(touchId: 1, pt: p1, window).commit();
794}
795
796QTEST_MAIN(tst_FlickableInterop)
797
798#include "tst_flickableinterop.moc"
799
800

source code of qtdeclarative/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp