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 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/qquickpincharea_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
40class tst_QQuickPinchArea: public QQmlDataTest
41{
42 Q_OBJECT
43public:
44 tst_QQuickPinchArea() { }
45private slots:
46 void initTestCase();
47 void cleanupTestCase();
48 void pinchProperties();
49 void scale();
50 void pan();
51 void retouch();
52 void cancel();
53 void transformedPinchArea_data();
54 void transformedPinchArea();
55 void dragTransformedPinchArea_data();
56 void dragTransformedPinchArea();
57
58private:
59 QQuickView *createView();
60 QTouchDevice *device = nullptr;
61};
62void tst_QQuickPinchArea::initTestCase()
63{
64 QQmlDataTest::initTestCase();
65 if (!device) {
66 device = new QTouchDevice;
67 device->setType(QTouchDevice::TouchScreen);
68 QWindowSystemInterface::registerTouchDevice(device);
69 }
70}
71
72void tst_QQuickPinchArea::cleanupTestCase()
73{
74
75}
76void tst_QQuickPinchArea::pinchProperties()
77{
78 QScopedPointer<QQuickView> window(createView());
79 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
80 window->show();
81 QVERIFY(window->rootObject() != nullptr);
82
83 QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>(aName: "pincharea");
84 QQuickPinch *pinch = pinchArea->pinch();
85 QVERIFY(pinchArea != nullptr);
86 QVERIFY(pinch != nullptr);
87
88 // target
89 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
90 QVERIFY(blackRect != nullptr);
91 QCOMPARE(blackRect, pinch->target());
92 QQuickItem *rootItem = qobject_cast<QQuickItem*>(object: window->rootObject());
93 QVERIFY(rootItem != nullptr);
94 QSignalSpy targetSpy(pinch, SIGNAL(targetChanged()));
95 pinch->setTarget(rootItem);
96 QCOMPARE(targetSpy.count(),1);
97 pinch->setTarget(rootItem);
98 QCOMPARE(targetSpy.count(),1);
99
100 // axis
101 QCOMPARE(pinch->axis(), QQuickPinch::XAndYAxis);
102 QSignalSpy axisSpy(pinch, SIGNAL(dragAxisChanged()));
103 pinch->setAxis(QQuickPinch::XAxis);
104 QCOMPARE(pinch->axis(), QQuickPinch::XAxis);
105 QCOMPARE(axisSpy.count(),1);
106 pinch->setAxis(QQuickPinch::XAxis);
107 QCOMPARE(axisSpy.count(),1);
108
109 // minimum and maximum drag properties
110 QSignalSpy xminSpy(pinch, SIGNAL(minimumXChanged()));
111 QSignalSpy xmaxSpy(pinch, SIGNAL(maximumXChanged()));
112 QSignalSpy yminSpy(pinch, SIGNAL(minimumYChanged()));
113 QSignalSpy ymaxSpy(pinch, SIGNAL(maximumYChanged()));
114
115 QCOMPARE(pinch->xmin(), 0.0);
116 QCOMPARE(pinch->xmax(), rootItem->width()-blackRect->width());
117 QCOMPARE(pinch->ymin(), 0.0);
118 QCOMPARE(pinch->ymax(), rootItem->height()-blackRect->height());
119
120 pinch->setXmin(10);
121 pinch->setXmax(10);
122 pinch->setYmin(10);
123 pinch->setYmax(10);
124
125 QCOMPARE(pinch->xmin(), 10.0);
126 QCOMPARE(pinch->xmax(), 10.0);
127 QCOMPARE(pinch->ymin(), 10.0);
128 QCOMPARE(pinch->ymax(), 10.0);
129
130 QCOMPARE(xminSpy.count(),1);
131 QCOMPARE(xmaxSpy.count(),1);
132 QCOMPARE(yminSpy.count(),1);
133 QCOMPARE(ymaxSpy.count(),1);
134
135 pinch->setXmin(10);
136 pinch->setXmax(10);
137 pinch->setYmin(10);
138 pinch->setYmax(10);
139
140 QCOMPARE(xminSpy.count(),1);
141 QCOMPARE(xmaxSpy.count(),1);
142 QCOMPARE(yminSpy.count(),1);
143 QCOMPARE(ymaxSpy.count(),1);
144
145 // minimum and maximum scale properties
146 QSignalSpy scaleMinSpy(pinch, SIGNAL(minimumScaleChanged()));
147 QSignalSpy scaleMaxSpy(pinch, SIGNAL(maximumScaleChanged()));
148
149 QCOMPARE(pinch->minimumScale(), 1.0);
150 QCOMPARE(pinch->maximumScale(), 2.0);
151
152 pinch->setMinimumScale(0.5);
153 pinch->setMaximumScale(1.5);
154
155 QCOMPARE(pinch->minimumScale(), 0.5);
156 QCOMPARE(pinch->maximumScale(), 1.5);
157
158 QCOMPARE(scaleMinSpy.count(),1);
159 QCOMPARE(scaleMaxSpy.count(),1);
160
161 pinch->setMinimumScale(0.5);
162 pinch->setMaximumScale(1.5);
163
164 QCOMPARE(scaleMinSpy.count(),1);
165 QCOMPARE(scaleMaxSpy.count(),1);
166
167 // minimum and maximum rotation properties
168 QSignalSpy rotMinSpy(pinch, SIGNAL(minimumRotationChanged()));
169 QSignalSpy rotMaxSpy(pinch, SIGNAL(maximumRotationChanged()));
170
171 QCOMPARE(pinch->minimumRotation(), 0.0);
172 QCOMPARE(pinch->maximumRotation(), 90.0);
173
174 pinch->setMinimumRotation(-90.0);
175 pinch->setMaximumRotation(45.0);
176
177 QCOMPARE(pinch->minimumRotation(), -90.0);
178 QCOMPARE(pinch->maximumRotation(), 45.0);
179
180 QCOMPARE(rotMinSpy.count(),1);
181 QCOMPARE(rotMaxSpy.count(),1);
182
183 pinch->setMinimumRotation(-90.0);
184 pinch->setMaximumRotation(45.0);
185
186 QCOMPARE(rotMinSpy.count(),1);
187 QCOMPARE(rotMaxSpy.count(),1);
188}
189
190QTouchEvent::TouchPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i)
191{
192 QTouchEvent::TouchPoint touchPoint(id);
193 touchPoint.setPos(i->mapFromScene(point: p));
194 touchPoint.setScreenPos(v->mapToGlobal(pos: p));
195 touchPoint.setScenePos(p);
196 return touchPoint;
197}
198
199void tst_QQuickPinchArea::scale()
200{
201 QQuickView *window = createView();
202 QScopedPointer<QQuickView> scope(window);
203 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
204 window->show();
205 QVERIFY(QTest::qWaitForWindowExposed(window));
206 QVERIFY(window->rootObject() != nullptr);
207 qApp->processEvents();
208
209 QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>(aName: "pincharea");
210 QQuickPinch *pinch = pinchArea->pinch();
211 QVERIFY(pinchArea != nullptr);
212 QVERIFY(pinch != nullptr);
213
214 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
215 QVERIFY(root != nullptr);
216
217 // target
218 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
219 QVERIFY(blackRect != nullptr);
220
221 QPoint p1(80, 80);
222 QPoint p2(100, 100);
223 {
224 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
225 pinchSequence.press(touchId: 0, pt: p1, window).commit();
226 QQuickTouchUtils::flush(window);
227 // In order for the stationary point to remember its previous position,
228 // we have to reuse the same pinchSequence object. Otherwise if we let it
229 // be destroyed and then start a new sequence, point 0 will default to being
230 // stationary at 0, 0, and PinchArea will filter out that touchpoint because
231 // it is outside its bounds.
232 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window).commit();
233 QQuickTouchUtils::flush(window);
234 p1 -= QPoint(10,10);
235 p2 += QPoint(10,10);
236 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
237 QQuickTouchUtils::flush(window);
238
239 QCOMPARE(root->property("scale").toReal(), 1.0);
240 QVERIFY(root->property("pinchActive").toBool());
241
242 p1 -= QPoint(10,10);
243 p2 += QPoint(10,10);
244 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
245 QQuickTouchUtils::flush(window);
246
247 QCOMPARE(root->property("scale").toReal(), 1.5);
248 QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
249 QCOMPARE(blackRect->scale(), 1.5);
250 }
251
252 // scale beyond bound
253 p1 -= QPoint(50,50);
254 p2 += QPoint(50,50);
255 {
256 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
257 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
258 QQuickTouchUtils::flush(window);
259 QCOMPARE(blackRect->scale(), 2.0);
260 pinchSequence.release(touchId: 0, pt: p1, window).release(touchId: 1, pt: p2, window).commit();
261 QQuickTouchUtils::flush(window);
262 }
263 QVERIFY(!root->property("pinchActive").toBool());
264}
265
266void tst_QQuickPinchArea::pan()
267{
268 QQuickView *window = createView();
269 QScopedPointer<QQuickView> scope(window);
270 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
271 window->show();
272 QVERIFY(QTest::qWaitForWindowExposed(window));
273 QVERIFY(window->rootObject() != nullptr);
274 qApp->processEvents();
275
276 QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>(aName: "pincharea");
277 QQuickPinch *pinch = pinchArea->pinch();
278 QVERIFY(pinchArea != nullptr);
279 QVERIFY(pinch != nullptr);
280
281 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
282 QVERIFY(root != nullptr);
283
284 // target
285 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
286 QVERIFY(blackRect != nullptr);
287
288 QPoint p1(80, 80);
289 QPoint p2(100, 100);
290 {
291 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
292 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
293 pinchSequence.press(touchId: 0, pt: p1, window).commit();
294 QQuickTouchUtils::flush(window);
295 // In order for the stationary point to remember its previous position,
296 // we have to reuse the same pinchSequence object.
297 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window).commit();
298 QQuickTouchUtils::flush(window);
299 QVERIFY(!root->property("pinchActive").toBool());
300 QCOMPARE(root->property("scale").toReal(), -1.0);
301
302 p1 += QPoint(dragThreshold - 1, 0);
303 p2 += QPoint(dragThreshold - 1, 0);
304 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
305 QQuickTouchUtils::flush(window);
306 // movement < dragThreshold: pinch not yet active
307 QVERIFY(!root->property("pinchActive").toBool());
308 QCOMPARE(root->property("scale").toReal(), -1.0);
309
310 // exactly the dragThreshold: pinch starts
311 p1 += QPoint(1, 0);
312 p2 += QPoint(1, 0);
313 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
314 QQuickTouchUtils::flush(window);
315 QVERIFY(root->property("pinchActive").toBool());
316 QCOMPARE(root->property("scale").toReal(), 1.0);
317
318 // Calculation of the center point is tricky at first:
319 // center point of the two touch points in item coordinates:
320 // scene coordinates: (80, 80) + (dragThreshold, 0), (100, 100) + (dragThreshold, 0)
321 // = ((180+dT)/2, 180/2) = (90+dT, 90)
322 // item coordinates: (scene) - (50, 50) = (40+dT, 40)
323 QCOMPARE(root->property("center").toPointF(), QPointF(40 + dragThreshold, 40));
324 // pan started, but no actual movement registered yet:
325 // blackrect starts at 50,50
326 QCOMPARE(blackRect->x(), 50.0);
327 QCOMPARE(blackRect->y(), 50.0);
328
329 p1 += QPoint(10, 0);
330 p2 += QPoint(10, 0);
331 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
332 QQuickTouchUtils::flush(window);
333 QCOMPARE(root->property("center").toPointF(), QPointF(40 + 10 + dragThreshold, 40));
334 QCOMPARE(blackRect->x(), 60.0);
335 QCOMPARE(blackRect->y(), 50.0);
336
337 p1 += QPoint(0, 10);
338 p2 += QPoint(0, 10);
339 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
340 QQuickTouchUtils::flush(window);
341 // next big surprise: the center is in item local coordinates and the item was just
342 // moved 10 to the right... which offsets the center point 10 to the left
343 QCOMPARE(root->property("center").toPointF(), QPointF(40 + 10 - 10 + dragThreshold, 40 + 10));
344 QCOMPARE(blackRect->x(), 60.0);
345 QCOMPARE(blackRect->y(), 60.0);
346
347 p1 += QPoint(10, 10);
348 p2 += QPoint(10, 10);
349 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
350 QQuickTouchUtils::flush(window);
351 // now the item moved again, thus the center point of the touch is moved in total by (10, 10)
352 QCOMPARE(root->property("center").toPointF(), QPointF(50 + dragThreshold, 50));
353 QCOMPARE(blackRect->x(), 70.0);
354 QCOMPARE(blackRect->y(), 70.0);
355 }
356
357 // pan x beyond bound
358 p1 += QPoint(100,100);
359 p2 += QPoint(100,100);
360 QTest::touchEvent(window, device).move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window);
361 QQuickTouchUtils::flush(window);
362
363 QCOMPARE(blackRect->x(), 140.0);
364 QCOMPARE(blackRect->y(), 170.0);
365
366 QTest::touchEvent(window, device).release(touchId: 0, pt: p1, window).release(touchId: 1, pt: p2, window);
367 QQuickTouchUtils::flush(window);
368 QVERIFY(!root->property("pinchActive").toBool());
369}
370
371// test pinch, release one point, touch again to continue pinch
372void tst_QQuickPinchArea::retouch()
373{
374 QQuickView *window = createView();
375 QScopedPointer<QQuickView> scope(window);
376 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
377 window->show();
378 QVERIFY(QTest::qWaitForWindowExposed(window));
379 QVERIFY(window->rootObject() != nullptr);
380 qApp->processEvents();
381
382 QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>(aName: "pincharea");
383 QQuickPinch *pinch = pinchArea->pinch();
384 QVERIFY(pinchArea != nullptr);
385 QVERIFY(pinch != nullptr);
386
387 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
388 QVERIFY(root != nullptr);
389
390 QSignalSpy startedSpy(pinchArea, SIGNAL(pinchStarted(QQuickPinchEvent*)));
391 QSignalSpy finishedSpy(pinchArea, SIGNAL(pinchFinished(QQuickPinchEvent*)));
392
393 // target
394 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
395 QVERIFY(blackRect != nullptr);
396
397 QPoint p1(80, 80);
398 QPoint p2(100, 100);
399 {
400 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
401 pinchSequence.press(touchId: 0, pt: p1, window).commit();
402 QQuickTouchUtils::flush(window);
403 // In order for the stationary point to remember its previous position,
404 // we have to reuse the same pinchSequence object.
405 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window).commit();
406 QQuickTouchUtils::flush(window);
407 p1 -= QPoint(10,10);
408 p2 += QPoint(10,10);
409 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
410 QQuickTouchUtils::flush(window);
411
412 QCOMPARE(root->property("scale").toReal(), 1.0);
413 QVERIFY(root->property("pinchActive").toBool());
414
415 p1 -= QPoint(10,10);
416 p2 += QPoint(10,10);
417 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
418 QQuickTouchUtils::flush(window);
419
420 QCOMPARE(startedSpy.count(), 1);
421
422 QCOMPARE(root->property("scale").toReal(), 1.5);
423 QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
424 QCOMPARE(blackRect->scale(), 1.5);
425
426 QCOMPARE(window->rootObject()->property("pointCount").toInt(), 2);
427
428 QCOMPARE(startedSpy.count(), 1);
429 QCOMPARE(finishedSpy.count(), 0);
430
431 // Hold down the first finger but release the second one
432 pinchSequence.stationary(touchId: 0).release(touchId: 1, pt: p2, window).commit();
433 QQuickTouchUtils::flush(window);
434
435 QCOMPARE(startedSpy.count(), 1);
436 QCOMPARE(finishedSpy.count(), 0);
437
438 QCOMPARE(window->rootObject()->property("pointCount").toInt(), 1);
439
440 // Keep holding down the first finger and re-touch the second one, then move them both
441 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window).commit();
442 QQuickTouchUtils::flush(window);
443 p1 -= QPoint(10,10);
444 p2 += QPoint(10,10);
445 pinchSequence.move(touchId: 0, pt: p1, window).move(touchId: 1, pt: p2, window).commit();
446 QQuickTouchUtils::flush(window);
447
448 // Lifting and retouching results in onPinchStarted being called again
449 QCOMPARE(startedSpy.count(), 2);
450 QCOMPARE(finishedSpy.count(), 0);
451
452 QCOMPARE(window->rootObject()->property("pointCount").toInt(), 2);
453
454 pinchSequence.release(touchId: 0, pt: p1, window).release(touchId: 1, pt: p2, window).commit();
455 QQuickTouchUtils::flush(window);
456
457 QVERIFY(!root->property("pinchActive").toBool());
458 QCOMPARE(startedSpy.count(), 2);
459 QCOMPARE(finishedSpy.count(), 1);
460 }
461}
462
463void tst_QQuickPinchArea::cancel()
464{
465 QQuickView *window = createView();
466 QScopedPointer<QQuickView> scope(window);
467 window->setSource(testFileUrl(fileName: "pinchproperties.qml"));
468 window->show();
469 QVERIFY(QTest::qWaitForWindowExposed(window));
470 QVERIFY(window->rootObject() != nullptr);
471 qApp->processEvents();
472
473 QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>(aName: "pincharea");
474 QQuickPinch *pinch = pinchArea->pinch();
475 QVERIFY(pinchArea != nullptr);
476 QVERIFY(pinch != nullptr);
477
478 QQuickItem *root = qobject_cast<QQuickItem*>(object: window->rootObject());
479 QVERIFY(root != nullptr);
480
481 // target
482 QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>(aName: "blackrect");
483 QVERIFY(blackRect != nullptr);
484
485 QPoint p1(80, 80);
486 QPoint p2(100, 100);
487 {
488 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
489 pinchSequence.press(touchId: 0, pt: p1, window).commit();
490 QQuickTouchUtils::flush(window);
491 // In order for the stationary point to remember its previous position,
492 // we have to reuse the same pinchSequence object. Otherwise if we let it
493 // be destroyed and then start a new sequence, point 0 will default to being
494 // stationary at 0, 0, and PinchArea will filter out that touchpoint because
495 // it is outside its bounds.
496 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window).commit();
497 QQuickTouchUtils::flush(window);
498 p1 -= QPoint(10,10);
499 p2 += QPoint(10,10);
500 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
501 QQuickTouchUtils::flush(window);
502
503 QCOMPARE(root->property("scale").toReal(), 1.0);
504 QVERIFY(root->property("pinchActive").toBool());
505
506 p1 -= QPoint(10,10);
507 p2 += QPoint(10,10);
508 pinchSequence.move(touchId: 0, pt: p1,window).move(touchId: 1, pt: p2,window).commit();
509 QQuickTouchUtils::flush(window);
510
511 QCOMPARE(root->property("scale").toReal(), 1.5);
512 QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
513 QCOMPARE(blackRect->scale(), 1.5);
514
515 QTouchEvent cancelEvent(QEvent::TouchCancel);
516 cancelEvent.setDevice(device);
517 QCoreApplication::sendEvent(receiver: window, event: &cancelEvent);
518 QQuickTouchUtils::flush(window);
519
520 QCOMPARE(root->property("scale").toReal(), 1.0);
521 QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
522 QCOMPARE(blackRect->scale(), 1.0);
523 QVERIFY(!root->property("pinchActive").toBool());
524 }
525}
526
527void tst_QQuickPinchArea::transformedPinchArea_data()
528{
529 QTest::addColumn<QPoint>(name: "p1");
530 QTest::addColumn<QPoint>(name: "p2");
531 QTest::addColumn<bool>(name: "shouldPinch");
532
533 QTest::newRow(dataTag: "checking inner pinch 1")
534 << QPoint(200, 140) << QPoint(200, 260) << true;
535
536 QTest::newRow(dataTag: "checking inner pinch 2")
537 << QPoint(140, 200) << QPoint(200, 140) << true;
538
539 QTest::newRow(dataTag: "checking inner pinch 3")
540 << QPoint(140, 200) << QPoint(260, 200) << true;
541
542 QTest::newRow(dataTag: "checking outer pinch 1")
543 << QPoint(140, 140) << QPoint(260, 260) << false;
544
545 QTest::newRow(dataTag: "checking outer pinch 2")
546 << QPoint(140, 140) << QPoint(200, 200) << false;
547
548 QTest::newRow(dataTag: "checking outer pinch 3")
549 << QPoint(140, 260) << QPoint(260, 260) << false;
550}
551
552void tst_QQuickPinchArea::transformedPinchArea()
553{
554 QFETCH(QPoint, p1);
555 QFETCH(QPoint, p2);
556 QFETCH(bool, shouldPinch);
557
558 QQuickView *view = createView();
559 QScopedPointer<QQuickView> scope(view);
560 view->setSource(testFileUrl(fileName: "transformedPinchArea.qml"));
561 view->show();
562 QVERIFY(QTest::qWaitForWindowExposed(view));
563 QVERIFY(view->rootObject() != nullptr);
564 qApp->processEvents();
565
566 QQuickPinchArea *pinchArea = view->rootObject()->findChild<QQuickPinchArea*>(aName: "pinchArea");
567 QVERIFY(pinchArea != nullptr);
568
569 const int threshold = qApp->styleHints()->startDragDistance();
570
571 {
572 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window: view, device);
573 // start pinch
574 pinchSequence.press(touchId: 0, pt: p1, window: view).commit();
575 QQuickTouchUtils::flush(window: view);
576 // In order for the stationary point to remember its previous position,
577 // we have to reuse the same pinchSequence object.
578 pinchSequence.stationary(touchId: 0).press(touchId: 1, pt: p2, window: view).commit();
579 QQuickTouchUtils::flush(window: view);
580 pinchSequence.stationary(touchId: 0).move(touchId: 1, pt: p2 + QPoint(threshold * 2, 0), window: view).commit();
581 QQuickTouchUtils::flush(window: view);
582 QCOMPARE(pinchArea->property("pinching").toBool(), shouldPinch);
583
584 // release pinch
585 pinchSequence.release(touchId: 0, pt: p1, window: view).release(touchId: 1, pt: p2, window: view).commit();
586 QQuickTouchUtils::flush(window: view);
587 QCOMPARE(pinchArea->property("pinching").toBool(), false);
588 }
589}
590
591void tst_QQuickPinchArea::dragTransformedPinchArea_data()
592{
593 QTest::addColumn<int>(name: "rotation");
594 QTest::addColumn<QPoint>(name: "p1");
595 QTest::addColumn<QPoint>(name: "p2");
596 QTest::addColumn<QPoint>(name: "delta");
597
598 QTest::newRow(dataTag: "unrotated")
599 << 0 << QPoint(100, 100) << QPoint(200, 100) << QPoint(40, 40);
600 QTest::newRow(dataTag: "20 deg")
601 << 20 << QPoint(100, 100) << QPoint(200, 100) << QPoint(40, 40);
602 QTest::newRow(dataTag: "90 deg")
603 << 90 << QPoint(100, 100) << QPoint(200, 100) << QPoint(0, 40);
604 QTest::newRow(dataTag: "180 deg")
605 << 180 << QPoint(100, 100) << QPoint(200, 100) << QPoint(40, 0);
606 QTest::newRow(dataTag: "225 deg")
607 << 210 << QPoint(200, 200) << QPoint(300, 200) << QPoint(80, 80);
608}
609
610void tst_QQuickPinchArea::dragTransformedPinchArea() // QTBUG-63673
611{
612 QFETCH(int, rotation);
613 QFETCH(QPoint, p1);
614 QFETCH(QPoint, p2);
615 QFETCH(QPoint, delta);
616 const int threshold = qApp->styleHints()->startDragDistance();
617
618 QQuickView *view = createView();
619 QScopedPointer<QQuickView> scope(view);
620 view->setSource(testFileUrl(fileName: "draggablePinchArea.qml"));
621 view->show();
622 QVERIFY(QTest::qWaitForWindowExposed(view));
623 QVERIFY(view->rootObject());
624 QQuickPinchArea *pinchArea = view->rootObject()->findChild<QQuickPinchArea*>();
625 QVERIFY(pinchArea);
626 QQuickItem *pinchAreaTarget = pinchArea->parentItem();
627 QVERIFY(pinchAreaTarget);
628 QQuickItem *pinchAreaContainer = pinchAreaTarget->parentItem();
629 QVERIFY(pinchAreaContainer);
630 pinchAreaContainer->setRotation(rotation);
631
632 QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window: view, device);
633 // start pinch
634 pinchSequence.press(touchId: 1, pt: pinchArea->mapToScene(point: p1).toPoint(), window: view)
635 .press(touchId: 2, pt: pinchArea->mapToScene(point: p2).toPoint(), window: view).commit();
636 QQuickTouchUtils::flush(window: view);
637 pinchSequence.move(touchId: 1, pt: pinchArea->mapToScene(point: p1 + QPoint(threshold, threshold)).toPoint(), window: view)
638 .move(touchId: 2, pt: pinchArea->mapToScene(point: p2 + QPoint(threshold, threshold)).toPoint(), window: view).commit();
639 QQuickTouchUtils::flush(window: view);
640 pinchSequence.move(touchId: 1, pt: pinchArea->mapToScene(point: p1 + delta).toPoint(), window: view)
641 .move(touchId: 2, pt: pinchArea->mapToScene(point: p2 + delta).toPoint(), window: view).commit();
642 QQuickTouchUtils::flush(window: view);
643 QCOMPARE(pinchArea->pinch()->active(), true);
644 auto error = delta - QPoint(threshold, threshold) -
645 pinchAreaTarget->position().toPoint(); // expect 0, 0
646 QVERIFY(qAbs(error.x()) <= 1);
647 QVERIFY(qAbs(error.y()) <= 1);
648
649 // release pinch
650 pinchSequence.release(touchId: 1, pt: p1, window: view).release(touchId: 2, pt: p2, window: view).commit();
651 QQuickTouchUtils::flush(window: view);
652 QCOMPARE(pinchArea->pinch()->active(), false);
653}
654
655QQuickView *tst_QQuickPinchArea::createView()
656{
657 QQuickView *window = new QQuickView(nullptr);
658 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
659
660 return window;
661}
662
663QTEST_MAIN(tst_QQuickPinchArea)
664
665#include "tst_qquickpincharea.moc"
666

source code of qtdeclarative/tests/auto/quick/qquickpincharea/tst_qquickpincharea.cpp