1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite 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#include <QtTest/QSignalSpy>
31#include <QtGui/QStyleHints>
32#include <qpa/qwindowsysteminterface.h>
33#include <private/qquickpinchhandler_p.h>
34#include <QtQuick/private/qquickrectangle_p.h>
35#include <QtQuick/qquickview.h>
36#include <QtQml/qqmlcontext.h>
37#include "../../../shared/util.h"
38#include "../../shared/viewtestutil.h"
39
40Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
41
42class tst_QQuickPinchHandler: public QQmlDataTest
43{
44 Q_OBJECT
45public:
46 tst_QQuickPinchHandler() : device(0) { }
47private slots:
48 void initTestCase();
49 void cleanupTestCase();
50 void pinchProperties();
51 void scale();
52 void scaleThreeFingers();
53 void pan();
54 void dragAxesEnabled_data();
55 void dragAxesEnabled();
56 void retouch();
57 void cancel();
58 void transformedpinchHandler_data();
59 void transformedpinchHandler();
60
61private:
62 QQuickView *createView();
63 QTouchDevice *device;
64};
65void tst_QQuickPinchHandler::initTestCase()
66{
67 QQmlDataTest::initTestCase();
68 if (!device) {
69 device = new QTouchDevice;
70 device->setType(QTouchDevice::TouchScreen);
71 QWindowSystemInterface::registerTouchDevice(device);
72 }
73}
74
75void tst_QQuickPinchHandler::cleanupTestCase()
76{
77
78}
79
80static bool withinBounds(qreal lower, qreal num, qreal upper)
81{
82 return num >= lower && num <= upper;
83}
84
85void tst_QQuickPinchHandler::pinchProperties()
86{
87 QScopedPointer<QQuickView> window(createView());
88 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
89 window->show();
90 QVERIFY(window->rootObject() != nullptr);
91
92 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
93 QVERIFY(pinchHandler != nullptr);
94
95 // target
96 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
97 QVERIFY(blackRect != nullptr);
98 QCOMPARE(blackRect, pinchHandler->target());
99 QQuickItem *rootItem = qobject_cast<QQuickItem*>(object: window->rootObject());
100 QVERIFY(rootItem != nullptr);
101 QSignalSpy targetSpy(pinchHandler, SIGNAL(targetChanged()));
102 pinchHandler->setTarget(rootItem);
103 QCOMPARE(targetSpy.count(),1);
104 pinchHandler->setTarget(rootItem);
105 QCOMPARE(targetSpy.count(),1);
106
107 // axis
108 /*
109 QCOMPARE(pinchHandler->axis(), QQuickPinch::XAndYAxis);
110 QSignalSpy axisSpy(pinchHandler, SIGNAL(dragAxisChanged()));
111 pinchHandler->setAxis(QQuickPinch::XAxis);
112 QCOMPARE(pinchHandler->axis(), QQuickPinch::XAxis);
113 QCOMPARE(axisSpy.count(),1);
114 pinchHandler->setAxis(QQuickPinch::XAxis);
115 QCOMPARE(axisSpy.count(),1);
116
117 // minimum and maximum drag properties
118 QSignalSpy xminSpy(pinchHandler, SIGNAL(minimumXChanged()));
119 QSignalSpy xmaxSpy(pinchHandler, SIGNAL(maximumXChanged()));
120 QSignalSpy yminSpy(pinchHandler, SIGNAL(minimumYChanged()));
121 QSignalSpy ymaxSpy(pinchHandler, SIGNAL(maximumYChanged()));
122
123 QCOMPARE(pinchHandler->xmin(), 0.0);
124 QCOMPARE(pinchHandler->xmax(), rootItem->width()-blackRect->width());
125 QCOMPARE(pinchHandler->ymin(), 0.0);
126 QCOMPARE(pinchHandler->ymax(), rootItem->height()-blackRect->height());
127
128 pinchHandler->setXmin(10);
129 pinchHandler->setXmax(10);
130 pinchHandler->setYmin(10);
131 pinchHandler->setYmax(10);
132
133 QCOMPARE(pinchHandler->xmin(), 10.0);
134 QCOMPARE(pinchHandler->xmax(), 10.0);
135 QCOMPARE(pinchHandler->ymin(), 10.0);
136 QCOMPARE(pinchHandler->ymax(), 10.0);
137
138 QCOMPARE(xminSpy.count(),1);
139 QCOMPARE(xmaxSpy.count(),1);
140 QCOMPARE(yminSpy.count(),1);
141 QCOMPARE(ymaxSpy.count(),1);
142
143 pinchHandler->setXmin(10);
144 pinchHandler->setXmax(10);
145 pinchHandler->setYmin(10);
146 pinchHandler->setYmax(10);
147
148 QCOMPARE(xminSpy.count(),1);
149 QCOMPARE(xmaxSpy.count(),1);
150 QCOMPARE(yminSpy.count(),1);
151 QCOMPARE(ymaxSpy.count(),1);
152 */
153
154 // minimum and maximum scale properties
155 QSignalSpy scaleMinSpy(pinchHandler, SIGNAL(minimumScaleChanged()));
156 QSignalSpy scaleMaxSpy(pinchHandler, SIGNAL(maximumScaleChanged()));
157
158 QCOMPARE(pinchHandler->minimumScale(), 1.0);
159 QCOMPARE(pinchHandler->maximumScale(), 4.0);
160
161 pinchHandler->setMinimumScale(0.5);
162 pinchHandler->setMaximumScale(1.5);
163
164 QCOMPARE(pinchHandler->minimumScale(), 0.5);
165 QCOMPARE(pinchHandler->maximumScale(), 1.5);
166
167 QCOMPARE(scaleMinSpy.count(),1);
168 QCOMPARE(scaleMaxSpy.count(),1);
169
170 pinchHandler->setMinimumScale(0.5);
171 pinchHandler->setMaximumScale(1.5);
172
173 QCOMPARE(scaleMinSpy.count(),1);
174 QCOMPARE(scaleMaxSpy.count(),1);
175
176 // minimum and maximum rotation properties
177 QSignalSpy rotMinSpy(pinchHandler, SIGNAL(minimumRotationChanged()));
178 QSignalSpy rotMaxSpy(pinchHandler, SIGNAL(maximumRotationChanged()));
179
180 QCOMPARE(pinchHandler->minimumRotation(), 0.0);
181 QCOMPARE(pinchHandler->maximumRotation(), 90.0);
182
183 pinchHandler->setMinimumRotation(-90.0);
184 pinchHandler->setMaximumRotation(45.0);
185
186 QCOMPARE(pinchHandler->minimumRotation(), -90.0);
187 QCOMPARE(pinchHandler->maximumRotation(), 45.0);
188
189 QCOMPARE(rotMinSpy.count(),1);
190 QCOMPARE(rotMaxSpy.count(),1);
191
192 pinchHandler->setMinimumRotation(-90.0);
193 pinchHandler->setMaximumRotation(45.0);
194
195 QCOMPARE(rotMinSpy.count(),1);
196 QCOMPARE(rotMaxSpy.count(),1);
197}
198
199QTouchEvent::TouchPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i)
200{
201 QTouchEvent::TouchPoint touchPoint(id);
202 touchPoint.setPos(i->mapFromScene(point: p));
203 touchPoint.setScreenPos(v->mapToGlobal(pos: p));
204 touchPoint.setScenePos(p);
205 return touchPoint;
206}
207
208void tst_QQuickPinchHandler::scale()
209{
210 QQuickView *window = createView();
211 QScopedPointer<QQuickView> scope(window);
212 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
213 window->show();
214 QVERIFY(QTest::qWaitForWindowExposed(window));
215 QVERIFY(window->rootObject() != nullptr);
216 qApp->processEvents();
217
218 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
219 QVERIFY(pinchHandler != nullptr);
220 QSignalSpy grabChangedSpy(pinchHandler, SIGNAL(grabChanged(QQuickEventPoint::GrabTransition, QQuickEventPoint*)));
221
222 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
223 QVERIFY(root != nullptr);
224
225 // target
226 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
227 QVERIFY(blackRect != nullptr);
228
229 QPoint p0(80, 80);
230 QPoint p1(100, 100);
231 {
232 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
233 pinchSequence.press(touchId: 0, pt: p0, window).commit();
234 QQuickTouchUtils::flush(window);
235 // In order for the stationary point to remember its previous position,
236 // we have to reuse the same pinchSequence object. Otherwise if we let it
237 // be destroyed and then start a new sequence, point 0 will default to being
238 // stationary at 0, 0, and pinchHandler will filter out that touchpoint because
239 // it is outside its bounds.
240 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
241 QQuickTouchUtils::flush(window);
242 QTRY_COMPARE(grabChangedSpy.count(), 1); // passive grab
243
244 QPoint pd(10, 10);
245 // move one point until PinchHandler activates
246 for (int pi = 0; pi < 10 && !pinchHandler->active(); ++pi) {
247 p1 += pd;
248 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p1, window).commit();
249 QQuickTouchUtils::flush(window);
250 }
251 QCOMPARE(pinchHandler->active(), true);
252 // first point got a passive grab; both points got exclusive grabs
253 QCOMPARE(grabChangedSpy.count(), 3);
254 QLineF line(p0, p1);
255 const qreal startLength = line.length();
256
257 p1+=pd;
258 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p1, window).commit();
259 QQuickTouchUtils::flush(window);
260 line.setP2(p1);
261 qreal scale = line.length() / startLength;
262 QVERIFY(qFloatDistance(root->property("scale").toReal(), scale) < 10);
263 QVERIFY(qFloatDistance(blackRect->scale(), scale) < 10);
264
265 p1+=pd;
266 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p1, window).commit();
267 QQuickTouchUtils::flush(window);
268 line.setP2(p1);
269 scale = line.length() / startLength;
270
271 QVERIFY(qFloatDistance(root->property("scale").toReal(), scale) < 10);
272 QVERIFY(qFloatDistance(blackRect->scale(), scale) < 10);
273
274 QPointF expectedCentroid = p0 + (p1 - p0)/2;
275 QCOMPARE(pinchHandler->centroid().scenePosition(), expectedCentroid);
276 }
277
278 // scale beyond bound
279 p1 += QPoint(20, 20);
280 {
281 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
282 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p1, window).commit();
283 QQuickTouchUtils::flush(window);
284 QCOMPARE(blackRect->scale(), qreal(4)); // qquickpinchhandler does not manipulate scale property
285 pinchSequence.release(touchId: 0, pt: p0, window).release(touchId: 1, pt: p1, window).commit();
286 QQuickTouchUtils::flush(window);
287 }
288 QCOMPARE(pinchHandler->active(), false);
289}
290
291void tst_QQuickPinchHandler::scaleThreeFingers()
292{
293 QQuickView *window = createView();
294 QScopedPointer<QQuickView> scope(window);
295 window->setSource(testFileUrl(fileName: "threeFingers.qml"));
296 window->show();
297 QVERIFY(QTest::qWaitForWindowExposed(window));
298 QVERIFY(window->rootObject() != nullptr);
299 qApp->processEvents();
300
301 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
302 QVERIFY(pinchHandler != nullptr);
303
304 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
305 QVERIFY(root != nullptr);
306
307 // target
308 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
309 QVERIFY(blackRect != nullptr);
310
311 // center of blackrect is at 150,150
312 QPoint p0(80, 80);
313 QPoint p1(220, 80);
314 QPoint p2(150, 220);
315 {
316 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
317 pinchSequence.press(touchId: 0, pt: p0, window).commit();
318 QQuickTouchUtils::flush(window);
319 // In order for the stationary point to remember its previous position,
320 // we have to reuse the same pinchSequence object. Otherwise if we let it
321 // be destroyed and then start a new sequence, point 0 will default to being
322 // stationary at 0, 0, and pinchHandler will filter out that touchpoint because
323 // it is outside its bounds.
324 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
325 QQuickTouchUtils::flush(window);
326 pinchSequence.stationary(touchId: 0).stationary(touchId: 1).press(touchId: 2, pt: p2, window).commit();
327 QQuickTouchUtils::flush(window);
328 for (int i = 0; i < 5;++i) {
329 p0 += QPoint(-4, -4);
330 p1 += QPoint(+4, -4);
331 p2 += QPoint( 0, +6);
332 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).move(touchId: 2, pt: p2,window).commit();
333 QQuickTouchUtils::flush(window);
334 }
335
336 QCOMPARE(pinchHandler->active(), true);
337 // scale we got was 1.1729088738267854364, but keep some slack
338 QVERIFY(withinBounds(1.163, root->property("scale").toReal(), 1.183));
339 // should not rotate
340 QCOMPARE(root->property("rotation").toReal(), 0.);
341
342 for (int i = 0; i < 5;++i) {
343 p0 += QPoint(-4, -4);
344 p1 += QPoint(+4, -4);
345 p2 += QPoint( 0, +6);
346 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).move(touchId: 2, pt: p2,window).commit();
347 QQuickTouchUtils::flush(window);
348 }
349 // scale we got was 1.4613, but keep some slack
350 QVERIFY(withinBounds(1.361, root->property("scale").toReal(), 1.561));
351
352 // since points were moved symetrically around the y axis, centroid should remain at x:150
353 QCOMPARE(root->property("centroid").value<QQuickHandlerPoint>().scenePosition().x(), 150); // blackrect is at 50,50
354
355 // scale beyond bound, we should reach the maximumScale
356 p0 += QPoint(-40, -40);
357 p1 += QPoint(+40, -40);
358 p2 += QPoint( 0, +60);
359 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).move(touchId: 2, pt: p2,window).commit();
360 QQuickTouchUtils::flush(window);
361
362 QCOMPARE(root->property("scale").toReal(), 2.);
363 pinchSequence.release(touchId: 0, pt: p0, window).release(touchId: 1, pt: p1, window).release(touchId: 2, pt: p2, window).commit();
364 QQuickTouchUtils::flush(window);
365 }
366 QCOMPARE(pinchHandler->active(), false);
367}
368
369void tst_QQuickPinchHandler::pan()
370{
371 QQuickView *window = createView();
372 QScopedPointer<QQuickView> scope(window);
373 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
374 window->show();
375 QVERIFY(QTest::qWaitForWindowExposed(window));
376 QVERIFY(window->rootObject() != nullptr);
377 qApp->processEvents();
378
379 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
380 QVERIFY(pinchHandler != nullptr);
381
382 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
383 QVERIFY(root != nullptr);
384
385 // target
386 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
387 QVERIFY(blackRect != nullptr);
388
389 QPoint p0(80, 80);
390 QPoint p1(100, 100);
391 {
392 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
393 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
394 pinchSequence.press(touchId: 0, pt: p0, window).commit();
395 QQuickTouchUtils::flush(window);
396 // In order for the stationary point to remember its previous position,
397 // we have to reuse the same pinchSequence object.
398 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
399 QQuickTouchUtils::flush(window);
400 QVERIFY(!root->property("pinchActive").toBool());
401 QCOMPARE(root->property("scale").toReal(), -1.0);
402
403 p0 += QPoint(dragThreshold, 0);
404 p1 += QPoint(dragThreshold, 0);
405 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
406 QQuickTouchUtils::flush(window);
407 // movement < dragThreshold: pinchHandler not yet active
408 QVERIFY(!root->property("pinchActive").toBool());
409 QCOMPARE(root->property("scale").toReal(), -1.0);
410
411 // just above the dragThreshold: pinchHandler starts
412 p0 += QPoint(1, 0);
413 p1 += QPoint(1, 0);
414 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
415 QQuickTouchUtils::flush(window);
416 QCOMPARE(pinchHandler->active(), true);
417 QCOMPARE(root->property("scale").toReal(), 1.0);
418
419 // Calculation of the center point is tricky at first:
420 // center point of the two touch points in item coordinates:
421 // scene coordinates: (80, 80) + (dragThreshold, 0), (100, 100) + (dragThreshold, 0)
422 // = ((180+dT)/2, 180/2) = (90+dT, 90)
423 // item coordinates: (scene) - (50, 50) = (40+dT, 40)
424 QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 1, 90));
425 // pan started, but no actual movement registered yet:
426 // blackrect starts at 50,50
427 QCOMPARE(blackRect->x(), 50.0);
428 QCOMPARE(blackRect->y(), 50.0);
429
430 p0 += QPoint(10, 0);
431 p1 += QPoint(10, 0);
432 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
433 QQuickTouchUtils::flush(window);
434 QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 11, 90));
435 QCOMPARE(blackRect->x(), 60.0);
436 QCOMPARE(blackRect->y(), 50.0);
437
438 p0 += QPoint(0, 10);
439 p1 += QPoint(0, 10);
440 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
441 QQuickTouchUtils::flush(window);
442 QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 11, 90 + 10));
443 QCOMPARE(blackRect->x(), 60.0);
444 QCOMPARE(blackRect->y(), 60.0);
445
446 p0 += QPoint(10, 10);
447 p1 += QPoint(10, 10);
448 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
449 QQuickTouchUtils::flush(window);
450 // now the item moved again, thus the center point of the touch is moved in total by (10, 10)
451 QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 21, 90 + 20));
452 QCOMPARE(blackRect->x(), 70.0);
453 QCOMPARE(blackRect->y(), 70.0);
454 }
455
456 // pan x beyond bound
457 p0 += QPoint(100,100);
458 p1 += QPoint(100,100);
459 QTest::touchEvent(window, device).move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window);
460 QQuickTouchUtils::flush(window);
461
462 QCOMPARE(blackRect->x(), 140.0);
463 QCOMPARE(blackRect->y(), 170.0);
464
465 QTest::touchEvent(window, device).release(touchId: 0, pt: p0, window).release(touchId: 1, pt: p1, window);
466 QQuickTouchUtils::flush(window);
467 QVERIFY(!root->property("pinchActive").toBool());
468}
469
470void tst_QQuickPinchHandler::dragAxesEnabled_data()
471{
472 QTest::addColumn<bool>(name: "xEnabled");
473 QTest::addColumn<bool>(name: "yEnabled");
474
475 QTest::newRow(dataTag: "both enabled") << true << true;
476 QTest::newRow(dataTag: "x enabled") << true << false;
477 QTest::newRow(dataTag: "y enabled") << false << true;
478 QTest::newRow(dataTag: "both disabled") << false << false;
479}
480
481void tst_QQuickPinchHandler::dragAxesEnabled()
482{
483 QQuickView *window = createView();
484 QScopedPointer<QQuickView> scope(window);
485 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
486 window->show();
487 QVERIFY(QTest::qWaitForWindowExposed(window));
488 QVERIFY(window->rootObject() != nullptr);
489 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
490 QVERIFY(blackRect != nullptr);
491 QQuickPinchHandler *pinchHandler = blackRect->findChild<QQuickPinchHandler*>();
492 QVERIFY(pinchHandler != nullptr);
493
494 QFETCH(bool, xEnabled);
495 QFETCH(bool, yEnabled);
496 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
497 pinchHandler->xAxis()->setEnabled(xEnabled);
498 pinchHandler->yAxis()->setEnabled(yEnabled);
499 QPoint c = blackRect->mapToScene(point: blackRect->clipRect().center()).toPoint();
500 QPoint p0 = c - QPoint(0, dragThreshold);
501 QPoint p1 = c + QPoint(0, dragThreshold);
502 QPoint blackRectPos = blackRect->position().toPoint();
503
504 // press two points, one above the rectangle's center and one below
505 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
506 pinchSequence.press(touchId: 0, pt: p0, window).press(touchId: 1, pt: p1, window).commit();
507 QQuickTouchUtils::flush(window);
508
509 // expand the pinch vertically
510 p0 -= QPoint(0, dragThreshold);
511 p1 += QPoint(0, dragThreshold);
512 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
513 for (int pi = 0; pi < 4; ++pi) {
514 p0 -= QPoint(0, dragThreshold);
515 p1 += QPoint(0, dragThreshold);
516 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
517 QQuickTouchUtils::flush(window);
518 qCDebug(lcPointerTests) << pi << "active" << pinchHandler->active() << "pts" << p0 << p1
519 << "centroid" << pinchHandler->centroid().scenePosition()
520 << "rect pos" << blackRect->position() << "scale" << blackRect->scale();
521 }
522 QCOMPARE(pinchHandler->active(), true);
523 QVERIFY(blackRect->scale() >= 2.0);
524 // drag started, but we only did scaling without any translation
525 QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), c);
526 QCOMPARE(blackRect->position().toPoint().x(), blackRectPos.x());
527 QCOMPARE(blackRect->position().toPoint().y(), blackRectPos.y());
528
529 // drag diagonally
530 p0 += QPoint(150, 150);
531 p1 += QPoint(150, 150);
532 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
533 QQuickTouchUtils::flush(window);
534 // the target should move if the xAxis is enabled, or stay in place if not
535 qCDebug(lcPointerTests) << "after diagonal drag: pts" << p0 << p1
536 << "centroid" << pinchHandler->centroid().scenePosition()
537 << "rect pos" << blackRect->position() << "scale" << blackRect->scale();
538 QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), QPoint(250, 250));
539 QCOMPARE(blackRect->position().toPoint().x(), xEnabled ? 140 : blackRectPos.x()); // because of xAxis.maximum
540 QCOMPARE(blackRect->position().toPoint().y(), yEnabled ? 170 : blackRectPos.y()); // because of yAxis.maximum
541
542 QTest::touchEvent(window, device).release(touchId: 0, pt: p0, window).release(touchId: 1, pt: p1, window);
543 QQuickTouchUtils::flush(window);
544}
545
546// test pinchHandler, release one point, touch again to continue pinchHandler
547void tst_QQuickPinchHandler::retouch()
548{
549 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
550 QQuickView *window = createView();
551 QScopedPointer<QQuickView> scope(window);
552 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
553 window->show();
554 QVERIFY(QTest::qWaitForWindowExposed(window));
555 QVERIFY(window->rootObject() != nullptr);
556 qApp->processEvents();
557
558 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
559 QVERIFY(pinchHandler != nullptr);
560
561 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
562 QVERIFY(root != nullptr);
563
564 // target
565 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
566 QVERIFY(blackRect != nullptr);
567
568 QPoint p0(80, 80);
569 QPoint p1(100, 100);
570 {
571 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
572 pinchSequence.press(touchId: 0, pt: p0, window).commit();
573 QQuickTouchUtils::flush(window);
574 // In order for the stationary point to remember its previous position,
575 // we have to reuse the same pinchSequence object.
576 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
577 QQuickTouchUtils::flush(window);
578 const QPoint delta(dragThreshold + 1, dragThreshold + 1);
579 p0 -= delta;
580 p1 += delta;
581 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).commit();
582 QQuickTouchUtils::flush(window);
583
584 QCOMPARE(root->property("scale").toReal(), 1.0);
585 QCOMPARE(pinchHandler->active(), true);
586
587 p0 -= delta;
588 p1 += delta;
589 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).commit();
590 QQuickTouchUtils::flush(window);
591
592 QCOMPARE(pinchHandler->active(), true);
593
594 // accept some slack
595 QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6));
596 QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50
597 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6));
598
599 QCOMPARE(root->property("activeCount").toInt(), 1);
600 QCOMPARE(root->property("deactiveCount").toInt(), 0);
601
602 // Hold down the first finger but release the second one
603 pinchSequence.stationary(touchId: 0).release(touchId: 1, pt: p1, window).commit();
604 QQuickTouchUtils::flush(window);
605
606 QCOMPARE(root->property("activeCount").toInt(), 1);
607 QCOMPARE(root->property("deactiveCount").toInt(), 1);
608
609 // Keep holding down the first finger and re-touch the second one, then move them both
610 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
611 QQuickTouchUtils::flush(window);
612 p0 -= QPoint(10,10);
613 p1 += QPoint(10,10);
614 pinchSequence.move(touchId: 0, pt: p0, window).move(touchId: 1, pt: p1, window).commit();
615 QQuickTouchUtils::flush(window);
616
617 // Lifting and retouching results in onPinchStarted being called again
618 QCOMPARE(root->property("activeCount").toInt(), 2);
619 QCOMPARE(root->property("deactiveCount").toInt(), 1);
620
621 pinchSequence.release(touchId: 0, pt: p0, window).release(touchId: 1, pt: p1, window).commit();
622 QQuickTouchUtils::flush(window);
623
624 QCOMPARE(pinchHandler->active(), false);
625 QCOMPARE(root->property("activeCount").toInt(), 2);
626 QCOMPARE(root->property("deactiveCount").toInt(), 2);
627 }
628}
629
630void tst_QQuickPinchHandler::cancel()
631{
632 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
633 QQuickView *window = createView();
634 QScopedPointer<QQuickView> scope(window);
635 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
636 window->show();
637 QVERIFY(QTest::qWaitForWindowExposed(window));
638 QVERIFY(window->rootObject() != nullptr);
639 qApp->processEvents();
640
641 QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
642 QVERIFY(pinchHandler != nullptr);
643
644 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
645 QVERIFY(root != nullptr);
646
647 // target
648 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
649 QVERIFY(blackRect != nullptr);
650
651 QPoint p0(80, 80);
652 QPoint p1(100, 100);
653 {
654 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
655 pinchSequence.press(touchId: 0, pt: p0, window).commit();
656 QQuickTouchUtils::flush(window);
657 // In order for the stationary point to remember its previous position,
658 // we have to reuse the same pinchSequence object. Otherwise if we let it
659 // be destroyed and then start a new sequence, point 0 will default to being
660 // stationary at 0, 0, and pinchHandler will filter out that touchpoint because
661 // it is outside its bounds.
662 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window).commit();
663 QQuickTouchUtils::flush(window);
664 const QPoint delta(dragThreshold + 1, dragThreshold + 1);
665 p0 -= delta;
666 p1 += delta;
667 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).commit();
668 QQuickTouchUtils::flush(window);
669
670 QCOMPARE(root->property("scale").toReal(), 1.0);
671 QCOMPARE(pinchHandler->active(), true);
672
673 p0 -= delta;
674 p1 += delta;
675 pinchSequence.move(touchId: 0, pt: p0,window).move(touchId: 1, pt: p1,window).commit();
676 QQuickTouchUtils::flush(window);
677
678 QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6));
679 QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50
680 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6));
681
682 QSKIP("cancel is not supported atm");
683
684 QTouchEvent cancelEvent(QEvent::TouchCancel);
685 cancelEvent.setDevice(device);
686 QCoreApplication::sendEvent(receiver: window, event: &cancelEvent);
687 QQuickTouchUtils::flush(window);
688
689 QCOMPARE(root->property("scale").toReal(), 1.0);
690 QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
691 QCOMPARE(blackRect->scale(), 1.0);
692 QVERIFY(!root->property("pinchActive").toBool());
693 }
694}
695
696void tst_QQuickPinchHandler::transformedpinchHandler_data()
697{
698 QTest::addColumn<QPoint>(name: "p0");
699 QTest::addColumn<QPoint>(name: "p1");
700 QTest::addColumn<bool>(name: "shouldPinch");
701
702 QTest::newRow(dataTag: "checking inner pinchHandler 1")
703 << QPoint(200, 140) << QPoint(200, 260) << true;
704
705 QTest::newRow(dataTag: "checking inner pinchHandler 2")
706 << QPoint(140, 200) << QPoint(200, 140) << true;
707
708 QTest::newRow(dataTag: "checking inner pinchHandler 3")
709 << QPoint(140, 200) << QPoint(260, 200) << true;
710
711 QTest::newRow(dataTag: "checking outer pinchHandler 1")
712 << QPoint(140, 140) << QPoint(260, 260) << false;
713
714 QTest::newRow(dataTag: "checking outer pinchHandler 2")
715 << QPoint(140, 140) << QPoint(200, 200) << false;
716
717 QTest::newRow(dataTag: "checking outer pinchHandler 3")
718 << QPoint(140, 260) << QPoint(260, 260) << false;
719}
720
721void tst_QQuickPinchHandler::transformedpinchHandler()
722{
723 QFETCH(QPoint, p0);
724 QFETCH(QPoint, p1);
725 QFETCH(bool, shouldPinch);
726
727 QQuickView *view = createView();
728 QScopedPointer<QQuickView> scope(view);
729 view->setSource(testFileUrl(fileName: "transformedPinchHandler.qml"));
730 view->show();
731 QVERIFY(QTest::qWaitForWindowExposed(view));
732 QVERIFY(view->rootObject() != nullptr);
733 qApp->processEvents();
734
735 QQuickPinchHandler *pinchHandler = view->rootObject()->findChild<QQuickPinchHandler*>(aName: "pinchHandler");
736 QVERIFY(pinchHandler != nullptr);
737
738 const int threshold = qApp->styleHints()->startDragDistance();
739
740 {
741 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window: view, device);
742 // start pinchHandler
743 pinchSequence.press(touchId: 0, pt: p0, window: view).commit();
744 QQuickTouchUtils::flush(window: view);
745 // In order for the stationary point to remember its previous position,
746 // we have to reuse the same pinchSequence object.
747 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p1, window: view).commit();
748 QQuickTouchUtils::flush(window: view);
749
750 // we move along the line that the two points form.
751 // The distance we move should be above the threshold (threshold * 2 to be safe)
752 QVector2D delta(p1 - p0);
753 delta.normalize();
754 QVector2D movement = delta * (threshold * 2);
755 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p1 + movement.toPoint(), window: view).commit();
756 QQuickTouchUtils::flush(window: view);
757 QCOMPARE(pinchHandler->active(), shouldPinch);
758
759 // release pinchHandler
760 pinchSequence.release(touchId: 0, pt: p0, window: view).release(touchId: 1, pt: p1, window: view).commit();
761 QQuickTouchUtils::flush(window: view);
762 QCOMPARE(pinchHandler->active(), false);
763 }
764}
765
766QQuickView *tst_QQuickPinchHandler::createView()
767{
768 QQuickView *window = new QQuickView(0);
769 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
770
771 return window;
772}
773
774QTEST_MAIN(tst_QQuickPinchHandler)
775
776#include "tst_qquickpinchhandler.moc"
777

source code of qtdeclarative/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp