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 | |
40 | class tst_QQuickPinchArea: public QQmlDataTest |
41 | { |
42 | Q_OBJECT |
43 | public: |
44 | tst_QQuickPinchArea() { } |
45 | private 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 | |
58 | private: |
59 | QQuickView *createView(); |
60 | QTouchDevice *device = nullptr; |
61 | }; |
62 | void 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 | |
72 | void tst_QQuickPinchArea::cleanupTestCase() |
73 | { |
74 | |
75 | } |
76 | void 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 | |
190 | QTouchEvent::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 | |
199 | void 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 | |
266 | void 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 |
372 | void 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 | |
463 | void 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 | |
527 | void 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 | |
552 | void 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 | |
591 | void 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 | |
610 | void 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 | |
655 | QQuickView *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 | |
663 | QTEST_MAIN(tst_QQuickPinchArea) |
664 | |
665 | #include "tst_qquickpincharea.moc" |
666 | |