Propagate synthesized mouse events in parallel with touch.
[profile/ivi/qtdeclarative.git] / tests / auto / quick / qquickcanvas / tst_qquickcanvas.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <qtest.h>
43 #include <QDebug>
44 #include <QTouchEvent>
45 #include <QtQuick/QQuickItem>
46 #include <QtQuick/QQuickCanvas>
47 #include <QtQml/QQmlEngine>
48 #include <QtQml/QQmlComponent>
49 #include <QtQuick/private/qquickrectangle_p.h>
50 #include <QtGui/QWindowSystemInterface>
51 #include "../../shared/util.h"
52 #include <QSignalSpy>
53 #include <private/qquickcanvas_p.h>
54 #include <private/qguiapplication_p.h>
55
56 struct TouchEventData {
57     QEvent::Type type;
58     QWidget *widget;
59     QWindow *window;
60     Qt::TouchPointStates states;
61     QList<QTouchEvent::TouchPoint> touchPoints;
62 };
63
64 static QTouchEvent::TouchPoint makeTouchPoint(QQuickItem *item, const QPointF &p, const QPointF &lastPoint = QPointF())
65 {
66     QPointF last = lastPoint.isNull() ? p : lastPoint;
67
68     QTouchEvent::TouchPoint tp;
69
70     tp.setPos(p);
71     tp.setLastPos(last);
72     tp.setScenePos(item->mapToScene(p));
73     tp.setLastScenePos(item->mapToScene(last));
74     tp.setScreenPos(item->canvas()->mapToGlobal(tp.scenePos().toPoint()));
75     tp.setLastScreenPos(item->canvas()->mapToGlobal(tp.lastScenePos().toPoint()));
76     return tp;
77 }
78
79 static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states = 0,
80                                     const QList<QTouchEvent::TouchPoint>& touchPoints = QList<QTouchEvent::TouchPoint>())
81 {
82     TouchEventData d = { type, 0, w, states, touchPoints };
83     return d;
84 }
85 static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states, const QTouchEvent::TouchPoint &touchPoint)
86 {
87     QList<QTouchEvent::TouchPoint> points;
88     points << touchPoint;
89     return makeTouchData(type, w, states, points);
90 }
91
92 #define COMPARE_TOUCH_POINTS(tp1, tp2) \
93 { \
94     QCOMPARE(tp1.pos(), tp2.pos()); \
95     QCOMPARE(tp1.lastPos(), tp2.lastPos()); \
96     QCOMPARE(tp1.scenePos(), tp2.scenePos()); \
97     QCOMPARE(tp1.lastScenePos(), tp2.lastScenePos()); \
98     QCOMPARE(tp1.screenPos(), tp2.screenPos()); \
99     QCOMPARE(tp1.lastScreenPos(), tp2.lastScreenPos()); \
100 }
101
102 #define COMPARE_TOUCH_DATA(d1, d2) \
103 { \
104     QCOMPARE((int)d1.type, (int)d2.type); \
105     QCOMPARE(d1.widget, d2.widget); \
106     QCOMPARE((int)d1.states, (int)d2.states); \
107     QCOMPARE(d1.touchPoints.count(), d2.touchPoints.count()); \
108     for (int i=0; i<d1.touchPoints.count(); i++) { \
109         COMPARE_TOUCH_POINTS(d1.touchPoints[i], d2.touchPoints[i]); \
110     } \
111 }
112
113
114 class RootItemAccessor : public QQuickItem
115 {
116     Q_OBJECT
117 public:
118     RootItemAccessor()
119         : m_rootItemDestroyed(false)
120         , m_rootItem(0)
121     {
122     }
123     Q_INVOKABLE QQuickItem *rootItem()
124     {
125         if (!m_rootItem) {
126             QQuickCanvasPrivate *c = QQuickCanvasPrivate::get(canvas());
127             m_rootItem = c->rootItem;
128             QObject::connect(m_rootItem, SIGNAL(destroyed()), this, SLOT(rootItemDestroyed()));
129         }
130         return m_rootItem;
131     }
132     bool isRootItemDestroyed() {return m_rootItemDestroyed;}
133 public slots:
134     void rootItemDestroyed() {
135         m_rootItemDestroyed = true;
136     }
137
138 private:
139     bool m_rootItemDestroyed;
140     QQuickItem *m_rootItem;
141 };
142
143 class TestTouchItem : public QQuickRectangle
144 {
145     Q_OBJECT
146 public:
147     TestTouchItem(QQuickItem *parent = 0)
148         : QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true),
149           mousePressId(0),
150           spinLoopWhenPressed(false), touchEventCount(0)
151     {
152         border()->setWidth(1);
153         setAcceptedMouseButtons(Qt::LeftButton);
154         setFiltersChildMouseEvents(true);
155     }
156
157     void reset() {
158         acceptTouchEvents = acceptMouseEvents = true;
159         setEnabled(true);
160         setOpacity(1.0);
161
162         lastEvent = makeTouchData(QEvent::None, canvas(), 0, QList<QTouchEvent::TouchPoint>());//CHECK_VALID
163
164         lastVelocity = lastVelocityFromMouseMove = QVector2D();
165         lastMousePos = QPointF();
166         lastMouseCapabilityFlags = 0;
167     }
168
169     static void clearMousePressCounter()
170     {
171         mousePressNum = mouseMoveNum = mouseReleaseNum = 0;
172     }
173
174     void clearTouchEventCounter()
175     {
176         touchEventCount = 0;
177     }
178
179     bool acceptTouchEvents;
180     bool acceptMouseEvents;
181     TouchEventData lastEvent;
182     int mousePressId;
183     bool spinLoopWhenPressed;
184     int touchEventCount;
185     QVector2D lastVelocity;
186     QVector2D lastVelocityFromMouseMove;
187     QPointF lastMousePos;
188     int lastMouseCapabilityFlags;
189
190     void touchEvent(QTouchEvent *event) {
191         if (!acceptTouchEvents) {
192             event->ignore();
193             return;
194         }
195         ++touchEventCount;
196         lastEvent = makeTouchData(event->type(), event->window(), event->touchPointStates(), event->touchPoints());
197         if (event->device()->capabilities().testFlag(QTouchDevice::Velocity) && !event->touchPoints().isEmpty()) {
198             lastVelocity = event->touchPoints().first().velocity();
199         } else {
200             lastVelocity = QVector2D();
201         }
202         if (spinLoopWhenPressed && event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
203             QCoreApplication::processEvents();
204         }
205     }
206
207     void mousePressEvent(QMouseEvent *e) {
208         if (!acceptMouseEvents) {
209             e->ignore();
210             return;
211         }
212         mousePressId = ++mousePressNum;
213         lastMousePos = e->pos();
214         lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
215     }
216
217     void mouseMoveEvent(QMouseEvent *e) {
218         if (!acceptMouseEvents) {
219             e->ignore();
220             return;
221         }
222         ++mouseMoveNum;
223         lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e);
224         lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
225         lastMousePos = e->pos();
226     }
227
228     void mouseReleaseEvent(QMouseEvent *e) {
229         if (!acceptMouseEvents) {
230             e->ignore();
231             return;
232         }
233         ++mouseReleaseNum;
234         lastMousePos = e->pos();
235         lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e);
236     }
237
238     bool childMouseEventFilter(QQuickItem *, QEvent *event) {
239         // TODO Is it a bug if a QTouchEvent comes here?
240         if (event->type() == QEvent::MouseButtonPress)
241             mousePressId = ++mousePressNum;
242         return false;
243     }
244
245     static int mousePressNum, mouseMoveNum, mouseReleaseNum;
246 };
247
248 int TestTouchItem::mousePressNum = 0;
249 int TestTouchItem::mouseMoveNum = 0;
250 int TestTouchItem::mouseReleaseNum = 0;
251
252 class ConstantUpdateItem : public QQuickItem
253 {
254 Q_OBJECT
255 public:
256     ConstantUpdateItem(QQuickItem *parent = 0) : QQuickItem(parent), iterations(0) {setFlag(ItemHasContents);}
257
258     int iterations;
259 protected:
260     QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *){
261         iterations++;
262         update();
263         return 0;
264     }
265 };
266
267 class tst_qquickcanvas : public QQmlDataTest
268 {
269     Q_OBJECT
270 public:
271
272 private slots:
273     void initTestCase()
274     {
275         QQmlDataTest::initTestCase();
276         touchDevice = new QTouchDevice;
277         touchDevice->setType(QTouchDevice::TouchScreen);
278         QWindowSystemInterface::registerTouchDevice(touchDevice);
279         touchDeviceWithVelocity = new QTouchDevice;
280         touchDeviceWithVelocity->setType(QTouchDevice::TouchScreen);
281         touchDeviceWithVelocity->setCapabilities(QTouchDevice::Position | QTouchDevice::Velocity);
282         QWindowSystemInterface::registerTouchDevice(touchDeviceWithVelocity);
283     }
284
285
286     void constantUpdates();
287     void mouseFiltering();
288     void headless();
289
290     void touchEvent_basic();
291     void touchEvent_propagation();
292     void touchEvent_propagation_data();
293     void touchEvent_cancel();
294     void touchEvent_reentrant();
295     void touchEvent_velocity();
296
297     void mouseFromTouch_basic();
298
299     void clearCanvas();
300
301     void qmlCreation();
302     void clearColor();
303
304     void grab();
305     void multipleWindows();
306
307     void animationsWhileHidden();
308
309     void focusObject();
310
311     void ignoreUnhandledMouseEvents();
312
313     void ownershipRootItem();
314 private:
315     QTouchDevice *touchDevice;
316     QTouchDevice *touchDeviceWithVelocity;
317 };
318
319 //If the item calls update inside updatePaintNode, it should schedule another update
320 void tst_qquickcanvas::constantUpdates()
321 {
322     QQuickCanvas canvas;
323     canvas.resize(250, 250);
324     ConstantUpdateItem item(canvas.rootItem());
325     canvas.show();
326     QTRY_VERIFY(item.iterations > 60);
327 }
328
329 void tst_qquickcanvas::touchEvent_basic()
330 {
331     TestTouchItem::clearMousePressCounter();
332
333     QQuickCanvas *canvas = new QQuickCanvas;
334     canvas->resize(250, 250);
335     canvas->setPos(100, 100);
336     canvas->show();
337     QTest::qWaitForWindowShown(canvas);
338
339     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
340     bottomItem->setObjectName("Bottom Item");
341     bottomItem->setSize(QSizeF(150, 150));
342
343     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
344     middleItem->setObjectName("Middle Item");
345     middleItem->setPos(QPointF(50, 50));
346     middleItem->setSize(QSizeF(150, 150));
347
348     TestTouchItem *topItem = new TestTouchItem(middleItem);
349     topItem->setObjectName("Top Item");
350     topItem->setPos(QPointF(50, 50));
351     topItem->setSize(QSizeF(150, 150));
352
353     QPointF pos(10, 10);
354
355     // press single point
356     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(),canvas);
357     QTest::qWait(50);
358
359     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
360
361     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
362     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
363     // At one point this was failing with kwin (KDE window manager) because canvas->setPos(100, 100)
364     // would put the decorated window at that position rather than the canvas itself.
365     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
366     topItem->reset();
367
368     // press multiple points
369     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(),canvas)
370             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
371     QTest::qWait(50);
372     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
373     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
374     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
375     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
376     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
377     topItem->reset();
378     bottomItem->reset();
379
380     // touch point on top item moves to bottom item, but top item should still receive the event
381     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(), canvas);
382     QTest::qWait(50);
383     QTest::touchEvent(canvas, touchDevice).move(0, bottomItem->mapToScene(pos).toPoint(), canvas);
384     QTest::qWait(50);
385     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
386     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, canvas, Qt::TouchPointMoved,
387             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
388     topItem->reset();
389
390     // touch point on bottom item moves to top item, but bottom item should still receive the event
391     QTest::touchEvent(canvas, touchDevice).press(0, bottomItem->mapToScene(pos).toPoint(), canvas);
392     QTest::qWait(50);
393     QTest::touchEvent(canvas, touchDevice).move(0, topItem->mapToScene(pos).toPoint(), canvas);
394     QTest::qWait(50);
395     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
396     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, canvas, Qt::TouchPointMoved,
397             makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos), pos)));
398     bottomItem->reset();
399
400     // a single stationary press on an item shouldn't cause an event
401     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(), canvas);
402     QTest::qWait(50);
403     QTest::touchEvent(canvas, touchDevice).stationary(0)
404             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
405     QTest::qWait(50);
406     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);    // received press only, not stationary
407     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
408     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
409     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
410     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
411     topItem->reset();
412     bottomItem->reset();
413     // cleanup: what is pressed must be released
414     // Otherwise you will get an assertion failure:
415     // ASSERT: "itemForTouchPointId.isEmpty()" in file items/qquickcanvas.cpp
416     QTest::touchEvent(canvas, touchDevice).release(0, pos.toPoint(), canvas).release(1, pos.toPoint(), canvas);
417
418     // move touch point from top item to bottom, and release
419     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(),canvas);
420     QTest::qWait(50);
421     QTest::touchEvent(canvas, touchDevice).release(0, bottomItem->mapToScene(pos).toPoint(),canvas);
422     QTest::qWait(50);
423     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
424     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, canvas, Qt::TouchPointReleased,
425             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
426     topItem->reset();
427
428     // release while another point is pressed
429     QTest::touchEvent(canvas, touchDevice).press(0, topItem->mapToScene(pos).toPoint(),canvas)
430             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
431     QTest::qWait(50);
432     QTest::touchEvent(canvas, touchDevice).move(0, bottomItem->mapToScene(pos).toPoint(), canvas);
433     QTest::qWait(50);
434     QTest::touchEvent(canvas, touchDevice).release(0, bottomItem->mapToScene(pos).toPoint(), canvas)
435                              .stationary(1);
436     QTest::qWait(50);
437     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
438     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
439     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
440     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, canvas, Qt::TouchPointReleased,
441             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos))));
442     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
443     topItem->reset();
444     bottomItem->reset();
445
446     delete topItem;
447     delete middleItem;
448     delete bottomItem;
449     delete canvas;
450 }
451
452 void tst_qquickcanvas::touchEvent_propagation()
453 {
454     TestTouchItem::clearMousePressCounter();
455
456     QFETCH(bool, acceptTouchEvents);
457     QFETCH(bool, acceptMouseEvents);
458     QFETCH(bool, enableItem);
459     QFETCH(qreal, itemOpacity);
460
461     QQuickCanvas *canvas = new QQuickCanvas;
462     canvas->resize(250, 250);
463     canvas->setPos(100, 100);
464     canvas->show();
465     QTest::qWaitForWindowShown(canvas);
466
467     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
468     bottomItem->setObjectName("Bottom Item");
469     bottomItem->setSize(QSizeF(150, 150));
470
471     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
472     middleItem->setObjectName("Middle Item");
473     middleItem->setPos(QPointF(50, 50));
474     middleItem->setSize(QSizeF(150, 150));
475
476     TestTouchItem *topItem = new TestTouchItem(middleItem);
477     topItem->setObjectName("Top Item");
478     topItem->setPos(QPointF(50, 50));
479     topItem->setSize(QSizeF(150, 150));
480
481     QPointF pos(10, 10);
482     QPoint pointInBottomItem = bottomItem->mapToScene(pos).toPoint();  // (10, 10)
483     QPoint pointInMiddleItem = middleItem->mapToScene(pos).toPoint();  // (60, 60) overlaps with bottomItem
484     QPoint pointInTopItem = topItem->mapToScene(pos).toPoint();  // (110, 110) overlaps with bottom & top items
485
486     // disable topItem
487     topItem->acceptTouchEvents = acceptTouchEvents;
488     topItem->acceptMouseEvents = acceptMouseEvents;
489     topItem->setEnabled(enableItem);
490     topItem->setOpacity(itemOpacity);
491
492     // single touch to top item, should be received by middle item
493     QTest::touchEvent(canvas, touchDevice).press(0, pointInTopItem, canvas);
494     QTest::qWait(50);
495     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
496     QCOMPARE(middleItem->lastEvent.touchPoints.count(), 1);
497     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
498     COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
499             makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))));
500
501     // touch top and middle items, middle item should get both events
502     QTest::touchEvent(canvas, touchDevice).press(0, pointInTopItem, canvas)
503             .press(1, pointInMiddleItem, canvas);
504     QTest::qWait(50);
505     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
506     QCOMPARE(middleItem->lastEvent.touchPoints.count(), 2);
507     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
508     COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
509            (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))
510                                               << makeTouchPoint(middleItem, pos) )));
511     middleItem->reset();
512
513     // disable middleItem as well
514     middleItem->acceptTouchEvents = acceptTouchEvents;
515     middleItem->acceptMouseEvents = acceptMouseEvents;
516     middleItem->setEnabled(enableItem);
517     middleItem->setOpacity(itemOpacity);
518
519     // touch top and middle items, bottom item should get all events
520     QTest::touchEvent(canvas, touchDevice).press(0, pointInTopItem, canvas)
521             .press(1, pointInMiddleItem, canvas);
522     QTest::qWait(50);
523     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
524     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
525     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 2);
526     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
527             (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))
528                                               << makeTouchPoint(bottomItem, bottomItem->mapFromItem(middleItem, pos)) )));
529     bottomItem->reset();
530
531     // disable bottom item as well
532     bottomItem->acceptTouchEvents = acceptTouchEvents;
533     bottomItem->setEnabled(enableItem);
534     bottomItem->setOpacity(itemOpacity);
535
536     // no events should be received
537     QTest::touchEvent(canvas, touchDevice).press(0, pointInTopItem, canvas)
538             .press(1, pointInMiddleItem, canvas)
539             .press(2, pointInBottomItem, canvas);
540     QTest::qWait(50);
541     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
542     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
543     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
544
545     topItem->reset();
546     middleItem->reset();
547     bottomItem->reset();
548
549     // disable middle item, touch on top item
550     middleItem->acceptTouchEvents = acceptTouchEvents;
551     middleItem->setEnabled(enableItem);
552     middleItem->setOpacity(itemOpacity);
553     QTest::touchEvent(canvas, touchDevice).press(0, pointInTopItem, canvas);
554     QTest::qWait(50);
555     if (!enableItem || itemOpacity == 0) {
556         // middle item is disabled or has 0 opacity, bottom item receives the event
557         QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
558         QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
559         QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
560         COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
561                 makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))));
562     } else {
563         // middle item ignores event, sends it to the top item (top-most child)
564         QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
565         QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
566         QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
567         COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
568                 makeTouchPoint(topItem, pos)));
569     }
570
571     delete topItem;
572     delete middleItem;
573     delete bottomItem;
574     delete canvas;
575 }
576
577 void tst_qquickcanvas::touchEvent_propagation_data()
578 {
579     QTest::addColumn<bool>("acceptTouchEvents");
580     QTest::addColumn<bool>("acceptMouseEvents");
581     QTest::addColumn<bool>("enableItem");
582     QTest::addColumn<qreal>("itemOpacity");
583
584     QTest::newRow("disable events") << false << false << true << 1.0;
585     QTest::newRow("disable item") << true << true << false << 1.0;
586     QTest::newRow("opacity of 0") << true << true << true << 0.0;
587 }
588
589 void tst_qquickcanvas::touchEvent_cancel()
590 {
591     TestTouchItem::clearMousePressCounter();
592
593     QQuickCanvas *canvas = new QQuickCanvas;
594     canvas->resize(250, 250);
595     canvas->setPos(100, 100);
596     canvas->show();
597     QTest::qWaitForWindowShown(canvas);
598
599     TestTouchItem *item = new TestTouchItem(canvas->rootItem());
600     item->setPos(QPointF(50, 50));
601     item->setSize(QSizeF(150, 150));
602
603     QPointF pos(10, 10);
604     QTest::touchEvent(canvas, touchDevice).press(0, item->mapToScene(pos).toPoint(),canvas);
605     QCoreApplication::processEvents();
606
607     QTRY_COMPARE(item->lastEvent.touchPoints.count(), 1);
608     TouchEventData d = makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(item,pos));
609     COMPARE_TOUCH_DATA(item->lastEvent, d);
610     item->reset();
611
612     QWindowSystemInterface::handleTouchCancelEvent(0, touchDevice);
613     QCoreApplication::processEvents();
614     d = makeTouchData(QEvent::TouchCancel, canvas);
615     COMPARE_TOUCH_DATA(item->lastEvent, d);
616
617     delete item;
618     delete canvas;
619 }
620
621 void tst_qquickcanvas::touchEvent_reentrant()
622 {
623     TestTouchItem::clearMousePressCounter();
624
625     QQuickCanvas *canvas = new QQuickCanvas;
626     canvas->resize(250, 250);
627     canvas->setPos(100, 100);
628     canvas->show();
629     QTest::qWaitForWindowShown(canvas);
630
631     TestTouchItem *item = new TestTouchItem(canvas->rootItem());
632
633     item->spinLoopWhenPressed = true; // will call processEvents() from the touch handler
634
635     item->setPos(QPointF(50, 50));
636     item->setSize(QSizeF(150, 150));
637     QPointF pos(60, 60);
638
639     // None of these should commit from the dtor.
640     QTest::QTouchEventSequence press = QTest::touchEvent(canvas, touchDevice, false).press(0, pos.toPoint(), canvas);
641     pos += QPointF(2, 2);
642     QTest::QTouchEventSequence move = QTest::touchEvent(canvas, touchDevice, false).move(0, pos.toPoint(), canvas);
643     QTest::QTouchEventSequence release = QTest::touchEvent(canvas, touchDevice, false).release(0, pos.toPoint(), canvas);
644
645     // Now commit (i.e. call QWindowSystemInterface::handleTouchEvent), but do not process the events yet.
646     press.commit(false);
647     move.commit(false);
648     release.commit(false);
649
650     QCoreApplication::processEvents();
651
652     QTRY_COMPARE(item->touchEventCount, 3);
653
654     delete item;
655     delete canvas;
656 }
657
658 void tst_qquickcanvas::touchEvent_velocity()
659 {
660     TestTouchItem::clearMousePressCounter();
661
662     QQuickCanvas *canvas = new QQuickCanvas;
663     canvas->resize(250, 250);
664     canvas->setPos(100, 100);
665     canvas->show();
666     QTest::qWaitForWindowShown(canvas);
667     QTest::qWait(10);
668
669     TestTouchItem *item = new TestTouchItem(canvas->rootItem());
670     item->setPos(QPointF(50, 50));
671     item->setSize(QSizeF(150, 150));
672
673     QList<QWindowSystemInterface::TouchPoint> points;
674     QWindowSystemInterface::TouchPoint tp;
675     tp.id = 1;
676     tp.state = Qt::TouchPointPressed;
677     QPoint pos = canvas->mapToGlobal(item->mapToScene(QPointF(10, 10)).toPoint());
678     tp.area = QRectF(pos, QSizeF(4, 4));
679     points << tp;
680     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
681     points[0].state = Qt::TouchPointMoved;
682     points[0].area.adjust(5, 5, 5, 5);
683     QVector2D velocity(1.5, 2.5);
684     points[0].velocity = velocity;
685     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
686     QCoreApplication::processEvents();
687     QCOMPARE(item->touchEventCount, 2);
688     QCOMPARE(item->lastEvent.touchPoints.count(), 1);
689     QCOMPARE(item->lastVelocity, velocity);
690
691     // Now have a transformation on the item and check if velocity and position are transformed accordingly.
692     item->setRotation(90); // clockwise
693     QMatrix4x4 transformMatrix;
694     transformMatrix.rotate(-90, 0, 0, 1); // counterclockwise
695     QVector2D transformedVelocity = transformMatrix.mapVector(velocity).toVector2D();
696     points[0].area.adjust(5, 5, 5, 5);
697     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
698     QCoreApplication::processEvents();
699     QCOMPARE(item->lastVelocity, transformedVelocity);
700     QPoint itemLocalPos = item->mapFromScene(canvas->mapFromGlobal(points[0].area.center().toPoint())).toPoint();
701     QPoint itemLocalPosFromEvent = item->lastEvent.touchPoints[0].pos().toPoint();
702     QCOMPARE(itemLocalPos, itemLocalPosFromEvent);
703
704     points[0].state = Qt::TouchPointReleased;
705     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
706     QCoreApplication::processEvents();
707     delete item;
708     delete canvas;
709 }
710
711 void tst_qquickcanvas::mouseFromTouch_basic()
712 {
713     // Turn off accepting touch events with acceptTouchEvents. This
714     // should result in sending mouse events generated from the touch
715     // with the new event propagation system.
716
717     TestTouchItem::clearMousePressCounter();
718     QQuickCanvas *canvas = new QQuickCanvas;
719     canvas->resize(250, 250);
720     canvas->setPos(100, 100);
721     canvas->show();
722     QTest::qWaitForWindowShown(canvas);
723     QTest::qWait(10);
724
725     TestTouchItem *item = new TestTouchItem(canvas->rootItem());
726     item->setPos(QPointF(50, 50));
727     item->setSize(QSizeF(150, 150));
728     item->acceptTouchEvents = false;
729
730     QList<QWindowSystemInterface::TouchPoint> points;
731     QWindowSystemInterface::TouchPoint tp;
732     tp.id = 1;
733     tp.state = Qt::TouchPointPressed;
734     QPoint pos = canvas->mapToGlobal(item->mapToScene(QPointF(10, 10)).toPoint());
735     tp.area = QRectF(pos, QSizeF(4, 4));
736     points << tp;
737     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
738     points[0].state = Qt::TouchPointMoved;
739     points[0].area.adjust(5, 5, 5, 5);
740     QVector2D velocity(1.5, 2.5);
741     points[0].velocity = velocity;
742     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
743     points[0].state = Qt::TouchPointReleased;
744     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
745     QCoreApplication::processEvents();
746
747     // The item should have received a mouse press, move, and release.
748     QCOMPARE(item->mousePressNum, 1);
749     QCOMPARE(item->mouseMoveNum, 1);
750     QCOMPARE(item->mouseReleaseNum, 1);
751     QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(canvas->mapFromGlobal(points[0].area.center().toPoint())).toPoint());
752     QCOMPARE(item->lastVelocityFromMouseMove, velocity);
753     QVERIFY((item->lastMouseCapabilityFlags & QTouchDevice::Velocity) != 0);
754
755     // Now the same with a transformation.
756     item->setRotation(90); // clockwise
757     QMatrix4x4 transformMatrix;
758     transformMatrix.rotate(-90, 0, 0, 1); // counterclockwise
759     QVector2D transformedVelocity = transformMatrix.mapVector(velocity).toVector2D();
760     points[0].state = Qt::TouchPointPressed;
761     points[0].velocity = velocity;
762     points[0].area = QRectF(pos, QSizeF(4, 4));
763     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
764     points[0].state = Qt::TouchPointMoved;
765     points[0].area.adjust(5, 5, 5, 5);
766     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
767     QCoreApplication::processEvents();
768     QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(canvas->mapFromGlobal(points[0].area.center().toPoint())).toPoint());
769     QCOMPARE(item->lastVelocityFromMouseMove, transformedVelocity);
770
771     points[0].state = Qt::TouchPointReleased;
772     QWindowSystemInterface::handleTouchEvent(canvas, touchDeviceWithVelocity, points);
773     QCoreApplication::processEvents();
774     delete item;
775     delete canvas;
776 }
777
778 void tst_qquickcanvas::clearCanvas()
779 {
780     QQuickCanvas *canvas = new QQuickCanvas;
781     QQuickItem *item = new QQuickItem;
782     item->setParentItem(canvas->rootItem());
783
784     QVERIFY(item->canvas() == canvas);
785
786     delete canvas;
787
788     QVERIFY(item->canvas() == 0);
789
790     delete item;
791 }
792
793 void tst_qquickcanvas::mouseFiltering()
794 {
795     TestTouchItem::clearMousePressCounter();
796
797     QQuickCanvas *canvas = new QQuickCanvas;
798     canvas->resize(250, 250);
799     canvas->setPos(100, 100);
800     canvas->show();
801     QTest::qWaitForWindowShown(canvas);
802
803     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
804     bottomItem->setObjectName("Bottom Item");
805     bottomItem->setSize(QSizeF(150, 150));
806
807     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
808     middleItem->setObjectName("Middle Item");
809     middleItem->setPos(QPointF(50, 50));
810     middleItem->setSize(QSizeF(150, 150));
811
812     TestTouchItem *topItem = new TestTouchItem(middleItem);
813     topItem->setObjectName("Top Item");
814     topItem->setPos(QPointF(50, 50));
815     topItem->setSize(QSizeF(150, 150));
816
817     QPoint pos(100, 100);
818
819     QTest::mousePress(canvas, Qt::LeftButton, 0, pos);
820
821     // Mouse filtering propagates down the stack, so the
822     // correct order is
823     // 1. middleItem filters event
824     // 2. bottomItem filters event
825     // 3. topItem receives event
826     QTRY_COMPARE(middleItem->mousePressId, 1);
827     QTRY_COMPARE(bottomItem->mousePressId, 2);
828     QTRY_COMPARE(topItem->mousePressId, 3);
829
830     delete canvas;
831 }
832
833 void tst_qquickcanvas::qmlCreation()
834 {
835     QQmlEngine engine;
836     QQmlComponent component(&engine);
837     component.loadUrl(testFileUrl("window.qml"));
838     QObject* created = component.create();
839     QVERIFY(created);
840
841     QQuickCanvas* canvas = qobject_cast<QQuickCanvas*>(created);
842     QVERIFY(canvas);
843     QCOMPARE(canvas->clearColor(), QColor(Qt::green));
844
845     QQuickItem* item = canvas->findChild<QQuickItem*>("item");
846     QVERIFY(item);
847     QCOMPARE(item->canvas(), canvas);
848
849     delete canvas;
850 }
851
852 void tst_qquickcanvas::clearColor()
853 {
854     //### Can we examine rendering to make sure it is really blue?
855     QQuickCanvas *canvas = new QQuickCanvas;
856     canvas->resize(250, 250);
857     canvas->setPos(100, 100);
858     canvas->setClearColor(Qt::blue);
859     canvas->show();
860     QTest::qWaitForWindowShown(canvas);
861     QCOMPARE(canvas->clearColor(), QColor(Qt::blue));
862     delete canvas;
863 }
864
865 void tst_qquickcanvas::grab()
866 {
867     QQuickCanvas canvas;
868     canvas.setClearColor(Qt::red);
869
870     canvas.resize(250, 250);
871     canvas.show();
872
873     QTest::qWaitForWindowShown(&canvas);
874
875     QImage content = canvas.grabFrameBuffer();
876     QCOMPARE(content.width(), canvas.width());
877     QCOMPARE(content.height(), canvas.height());
878     QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
879 }
880
881 void tst_qquickcanvas::multipleWindows()
882 {
883     QList<QQuickCanvas *> windows;
884     for (int i=0; i<6; ++i) {
885         QQuickCanvas *c = new QQuickCanvas();
886         c->setClearColor(Qt::GlobalColor(Qt::red + i));
887         c->resize(300, 200);
888         c->setPos(100 + i * 30, 100 + i * 20);
889         c->show();
890         windows << c;
891         QVERIFY(c->isVisible());
892     }
893
894     // move them
895     for (int i=0; i<windows.size(); ++i) {
896         QQuickCanvas *c = windows.at(i);
897         c->setPos(c->x() - 10, c->y() - 10);
898     }
899
900     // resize them
901     for (int i=0; i<windows.size(); ++i) {
902         QQuickCanvas *c = windows.at(i);
903         c->resize(200, 150);
904     }
905
906     qDeleteAll(windows);
907 }
908
909 void tst_qquickcanvas::animationsWhileHidden()
910 {
911     QQmlEngine engine;
912     QQmlComponent component(&engine);
913     component.loadUrl(testFileUrl("AnimationsWhileHidden.qml"));
914     QObject* created = component.create();
915
916     QQuickCanvas* canvas = qobject_cast<QQuickCanvas*>(created);
917     QVERIFY(canvas);
918     QVERIFY(canvas->isVisible());
919
920     // Now hide the window and verify that it went off screen
921     canvas->hide();
922     QTest::qWait(10);
923     QVERIFY(!canvas->isVisible());
924
925     // Running animaiton should cause it to become visible again shortly.
926     QTRY_VERIFY(canvas->isVisible());
927
928     delete canvas;
929 }
930
931
932 void tst_qquickcanvas::headless()
933 {
934     QQmlEngine engine;
935     QQmlComponent component(&engine);
936     component.loadUrl(testFileUrl("Headless.qml"));
937     QObject* created = component.create();
938
939     QQuickCanvas* canvas = qobject_cast<QQuickCanvas*>(created);
940     QVERIFY(canvas);
941
942     QTest::qWaitForWindowShown(canvas);
943     QVERIFY(canvas->isVisible());
944
945     QSignalSpy initialized(canvas, SIGNAL(sceneGraphInitialized()));
946     QSignalSpy invalidated(canvas, SIGNAL(sceneGraphInvalidated()));
947
948     // Verify that the canvas is alive and kicking
949     QVERIFY(canvas->openglContext() != 0);
950
951     // Store the visual result
952     QImage originalContent = canvas->grabFrameBuffer();
953
954     // Hide the canvas and verify signal emittion and GL context deletion
955     canvas->hide();
956     canvas->releaseResources();
957
958     QTRY_COMPARE(invalidated.size(), 1);
959     QVERIFY(canvas->openglContext() == 0);
960
961     // Destroy the native windowing system buffers
962     canvas->destroy();
963     QVERIFY(canvas->handle() == 0);
964
965     // Show and verify that we are back and running
966     canvas->show();
967     QTest::qWaitForWindowShown(canvas);
968
969     QTRY_COMPARE(initialized.size(), 1);
970     QVERIFY(canvas->openglContext() != 0);
971
972     // Verify that the visual output is the same
973     QImage newContent = canvas->grabFrameBuffer();
974
975     QCOMPARE(originalContent, newContent);
976
977     delete canvas;
978 }
979
980 void tst_qquickcanvas::focusObject()
981 {
982     QQmlEngine engine;
983     QQmlComponent component(&engine);
984     component.loadUrl(testFileUrl("focus.qml"));
985     QObject *created = component.create();
986     QVERIFY(created);
987
988     QQuickCanvas *canvas = qobject_cast<QQuickCanvas*>(created);
989     QVERIFY(canvas);
990
991     QQuickItem *item1 = canvas->findChild<QQuickItem*>("item1");
992     QVERIFY(item1);
993     item1->setFocus(true);
994     QCOMPARE(item1, canvas->focusObject());
995
996     QQuickItem *item2 = canvas->findChild<QQuickItem*>("item2");
997     QVERIFY(item2);
998     item2->setFocus(true);
999     QCOMPARE(item2, canvas->focusObject());
1000
1001     delete canvas;
1002 }
1003
1004 void tst_qquickcanvas::ignoreUnhandledMouseEvents()
1005 {
1006     QQuickCanvas* canvas = new QQuickCanvas;
1007     canvas->resize(100, 100);
1008     canvas->show();
1009     QTest::qWaitForWindowShown(canvas);
1010
1011     QQuickItem* item = new QQuickItem;
1012     item->setSize(QSizeF(100, 100));
1013     item->setParentItem(canvas->rootItem());
1014
1015     {
1016         QMouseEvent me(QEvent::MouseButtonPress, QPointF(50, 50), Qt::LeftButton, Qt::LeftButton,
1017                        Qt::NoModifier);
1018         me.setAccepted(true);
1019         QVERIFY(QCoreApplication::sendEvent(canvas, &me));
1020         QVERIFY(!me.isAccepted());
1021     }
1022
1023     {
1024         QMouseEvent me(QEvent::MouseMove, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton,
1025                        Qt::NoModifier);
1026         me.setAccepted(true);
1027         QVERIFY(QCoreApplication::sendEvent(canvas, &me));
1028         QVERIFY(!me.isAccepted());
1029     }
1030
1031     {
1032         QMouseEvent me(QEvent::MouseButtonRelease, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton,
1033                        Qt::NoModifier);
1034         me.setAccepted(true);
1035         QVERIFY(QCoreApplication::sendEvent(canvas, &me));
1036         QVERIFY(!me.isAccepted());
1037     }
1038
1039     delete canvas;
1040 }
1041
1042
1043 void tst_qquickcanvas::ownershipRootItem()
1044 {
1045     qmlRegisterType<RootItemAccessor>("QtQuick", 2, 0, "RootItemAccessor");
1046
1047     QQmlEngine engine;
1048     QQmlComponent component(&engine);
1049     component.loadUrl(testFileUrl("ownershipRootItem.qml"));
1050     QObject* created = component.create();
1051
1052     QQuickCanvas* canvas = qobject_cast<QQuickCanvas*>(created);
1053     QVERIFY(canvas);
1054     QTest::qWaitForWindowShown(canvas);
1055
1056     RootItemAccessor* accessor = canvas->findChild<RootItemAccessor*>("accessor");
1057     QVERIFY(accessor);
1058     engine.collectGarbage();
1059
1060     QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
1061     QCoreApplication::processEvents();
1062     QVERIFY(!accessor->isRootItemDestroyed());
1063 }
1064 QTEST_MAIN(tst_qquickcanvas)
1065
1066 #include "tst_qquickcanvas.moc"