Fix order of QSGItem mouse filtering.
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qsgcanvas / tst_qsgcanvas.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <qtest.h>
43 #include <QDebug>
44 #include <QTouchEvent>
45 #include <QtDeclarative/QSGItem>
46 #include <QtDeclarative/QSGCanvas>
47 #include <QtDeclarative/private/qsgrectangle_p.h>
48 #include "../../../shared/util.h"
49 #include <QtGui/QWindowSystemInterface>
50
51 struct TouchEventData {
52     QEvent::Type type;
53     QWidget *widget;
54     QWindow *window;
55     Qt::TouchPointStates states;
56     QList<QTouchEvent::TouchPoint> touchPoints;
57 };
58
59 static QTouchEvent::TouchPoint makeTouchPoint(QSGItem *item, const QPointF &p, const QPointF &lastPoint = QPointF())
60 {
61     QPointF last = lastPoint.isNull() ? p : lastPoint;
62
63     QTouchEvent::TouchPoint tp;
64
65     tp.setPos(p);
66     tp.setLastPos(last);
67     tp.setScenePos(item->mapToScene(p));
68     tp.setLastScenePos(item->mapToScene(last));
69     tp.setScreenPos(item->canvas()->mapToGlobal(tp.scenePos().toPoint()));
70     tp.setLastScreenPos(item->canvas()->mapToGlobal(tp.lastScenePos().toPoint()));
71     return tp;
72 }
73
74 static TouchEventData makeTouchData(QEvent::Type type, QWidget *w, Qt::TouchPointStates states, const QList<QTouchEvent::TouchPoint> &touchPoints)
75 {
76     TouchEventData d = { type, w, 0, states, touchPoints };
77     return d;
78 }
79
80 static TouchEventData makeTouchData(QEvent::Type type, QWidget *w, Qt::TouchPointStates states, const QTouchEvent::TouchPoint &touchPoint)
81 {
82     QList<QTouchEvent::TouchPoint> points;
83     points << touchPoint;
84     return makeTouchData(type, w, states, points);
85 }
86 static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states, const QList<QTouchEvent::TouchPoint>& touchPoints)
87 {
88     TouchEventData d = { type, 0, w, states, touchPoints };
89     return d;
90 }
91 static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states, const QTouchEvent::TouchPoint &touchPoint)
92 {
93     QList<QTouchEvent::TouchPoint> points;
94     points << touchPoint;
95     return makeTouchData(type, w, states, points);
96 }
97
98 #define COMPARE_TOUCH_POINTS(tp1, tp2) \
99 { \
100     QCOMPARE(tp1.pos(), tp2.pos()); \
101     QCOMPARE(tp1.lastPos(), tp2.lastPos()); \
102     QCOMPARE(tp1.scenePos(), tp2.scenePos()); \
103     QCOMPARE(tp1.lastScenePos(), tp2.lastScenePos()); \
104     QCOMPARE(tp1.screenPos(), tp2.screenPos()); \
105     QCOMPARE(tp1.lastScreenPos(), tp2.lastScreenPos()); \
106 }
107
108 #define COMPARE_TOUCH_DATA(d1, d2) \
109 { \
110     QCOMPARE((int)d1.type, (int)d2.type); \
111     QCOMPARE(d1.widget, d2.widget); \
112     QCOMPARE((int)d1.states, (int)d2.states); \
113     QCOMPARE(d1.touchPoints.count(), d2.touchPoints.count()); \
114     for (int i=0; i<d1.touchPoints.count(); i++) { \
115         COMPARE_TOUCH_POINTS(d1.touchPoints[i], d2.touchPoints[i]); \
116     } \
117 }
118
119 class TestTouchItem : public QSGRectangle
120 {
121     Q_OBJECT
122 public:
123     TestTouchItem(QSGItem *parent = 0)
124         : QSGRectangle(parent), acceptEvents(true), mousePressId(0)
125     {
126         border()->setWidth(1);
127         setAcceptedMouseButtons(Qt::LeftButton);
128         setFiltersChildMouseEvents(true);
129     }
130
131     void reset() {
132         acceptEvents = true;
133         setEnabled(true);
134         setOpacity(1.0);
135
136         lastEvent = makeTouchData(QEvent::None, canvas(), 0, QList<QTouchEvent::TouchPoint>());//CHECK_VALID
137     }
138
139     bool acceptEvents;
140     TouchEventData lastEvent;
141     int mousePressId;
142
143 protected:
144     virtual void touchEvent(QTouchEvent *event) {
145         if (!acceptEvents) {
146             event->ignore();
147             return;
148         }
149         lastEvent = makeTouchData(event->type(), event->widget(), event->touchPointStates(), event->touchPoints());
150         event->accept();
151     }
152
153     virtual void mousePressEvent(QMouseEvent *event) {
154         mousePressId = ++mousePressNum;
155     }
156
157     bool childMouseEventFilter(QSGItem *, QEvent *) {
158         mousePressId = ++mousePressNum;
159         return false;
160     }
161
162     static int mousePressNum;
163 };
164
165 int TestTouchItem::mousePressNum = 0;
166
167 class ConstantUpdateItem : public QSGItem
168 {
169 Q_OBJECT
170 public:
171     ConstantUpdateItem(QSGItem *parent = 0) : QSGItem(parent), iterations(0) {setFlag(ItemHasContents);}
172
173     int iterations;
174 protected:
175     QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *){
176         iterations++;
177         update();
178         return 0;
179     }
180 };
181
182 class tst_qsgcanvas : public QObject
183 {
184     Q_OBJECT
185 public:
186     tst_qsgcanvas();
187
188 private slots:
189     void initTestCase();
190     void cleanupTestCase();
191
192     void constantUpdates();
193
194     void touchEvent_basic();
195     void touchEvent_propagation();
196     void touchEvent_propagation_data();
197
198     void clearCanvas();
199     void mouseFiltering();
200 };
201
202 tst_qsgcanvas::tst_qsgcanvas()
203 {
204 }
205
206 void tst_qsgcanvas::initTestCase()
207 {
208 }
209
210 void tst_qsgcanvas::cleanupTestCase()
211 {
212 }
213
214 //If the item calls update inside updatePaintNode, it should schedule another update
215 void tst_qsgcanvas::constantUpdates()
216 {
217     QSGCanvas canvas;
218     ConstantUpdateItem item(canvas.rootItem());
219     canvas.show();
220     QTRY_VERIFY(item.iterations > 60);
221 }
222
223 void tst_qsgcanvas::touchEvent_basic()
224 {
225     QSGCanvas *canvas = new QSGCanvas;
226     canvas->resize(250, 250);
227     canvas->move(100, 100);
228     canvas->show();
229
230     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
231     bottomItem->setObjectName("Bottom Item");
232     bottomItem->setSize(QSizeF(150, 150));
233
234     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
235     middleItem->setObjectName("Middle Item");
236     middleItem->setPos(QPointF(50, 50));
237     middleItem->setSize(QSizeF(150, 150));
238
239     TestTouchItem *topItem = new TestTouchItem(middleItem);
240     topItem->setObjectName("Top Item");
241     topItem->setPos(QPointF(50, 50));
242     topItem->setSize(QSizeF(150, 150));
243
244     QPointF pos(10, 10);
245
246     // press single point
247     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(),canvas);
248     QTest::qWait(50);
249
250     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
251
252     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
253     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
254     TouchEventData d = makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem,pos));
255     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
256     topItem->reset();
257
258     // press multiple points
259     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(),canvas)
260             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
261     QTest::qWait(50);
262     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
263     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
264     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
265     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
266     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
267     topItem->reset();
268     bottomItem->reset();
269
270     // touch point on top item moves to bottom item, but top item should still receive the event
271     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(), canvas);
272     QTest::qWait(50);
273     QTest::touchEvent(canvas).move(0, bottomItem->mapToScene(pos).toPoint(), canvas);
274     QTest::qWait(50);
275     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
276     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, canvas, Qt::TouchPointMoved,
277             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
278     topItem->reset();
279
280     // touch point on bottom item moves to top item, but bottom item should still receive the event
281     QTest::touchEvent(canvas).press(0, bottomItem->mapToScene(pos).toPoint(), canvas);
282     QTest::qWait(50);
283     QTest::touchEvent(canvas).move(0, topItem->mapToScene(pos).toPoint(), canvas);
284     QTest::qWait(50);
285     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
286     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, canvas, Qt::TouchPointMoved,
287             makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos), pos)));
288     bottomItem->reset();
289
290     // a single stationary press on an item shouldn't cause an event
291     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(), canvas);
292     QTest::qWait(50);
293     QTest::touchEvent(canvas).stationary(0)
294             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
295     QTest::qWait(50);
296     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);    // received press only, not stationary
297     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
298     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
299     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
300     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
301     topItem->reset();
302     bottomItem->reset();
303
304     // move touch point from top item to bottom, and release
305     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(),canvas);
306     QTest::qWait(50);
307     QTest::touchEvent(canvas).release(0, bottomItem->mapToScene(pos).toPoint(),canvas);
308     QTest::qWait(50);
309     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
310     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, canvas, Qt::TouchPointReleased,
311             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
312     topItem->reset();
313
314     // release while another point is pressed
315     QTest::touchEvent(canvas).press(0, topItem->mapToScene(pos).toPoint(),canvas)
316             .press(1, bottomItem->mapToScene(pos).toPoint(), canvas);
317     QTest::qWait(50);
318     QTest::touchEvent(canvas).move(0, bottomItem->mapToScene(pos).toPoint(), canvas);
319     QTest::qWait(50);
320     QTest::touchEvent(canvas).release(0, bottomItem->mapToScene(pos).toPoint(), canvas)
321                              .stationary(1);
322     QTest::qWait(50);
323     QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
324     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
325     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
326     COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, canvas, Qt::TouchPointReleased,
327             makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos))));
328     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
329     topItem->reset();
330     bottomItem->reset();
331
332     delete topItem;
333     delete middleItem;
334     delete bottomItem;
335     delete canvas;
336 }
337
338 void tst_qsgcanvas::touchEvent_propagation()
339 {
340     QFETCH(bool, acceptEvents);
341     QFETCH(bool, enableItem);
342     QFETCH(qreal, itemOpacity);
343
344     QSGCanvas *canvas = new QSGCanvas;
345     canvas->resize(250, 250);
346     canvas->move(100, 100);
347     canvas->show();
348
349     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
350     bottomItem->setObjectName("Bottom Item");
351     bottomItem->setSize(QSizeF(150, 150));
352
353     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
354     middleItem->setObjectName("Middle Item");
355     middleItem->setPos(QPointF(50, 50));
356     middleItem->setSize(QSizeF(150, 150));
357
358     TestTouchItem *topItem = new TestTouchItem(middleItem);
359     topItem->setObjectName("Top Item");
360     topItem->setPos(QPointF(50, 50));
361     topItem->setSize(QSizeF(150, 150));
362
363     QPointF pos(10, 10);
364     QPoint pointInBottomItem = bottomItem->mapToScene(pos).toPoint();  // (10, 10)
365     QPoint pointInMiddleItem = middleItem->mapToScene(pos).toPoint();  // (60, 60) overlaps with bottomItem
366     QPoint pointInTopItem = topItem->mapToScene(pos).toPoint();  // (110, 110) overlaps with bottom & top items
367
368     // disable topItem
369     topItem->acceptEvents = acceptEvents;
370     topItem->setEnabled(enableItem);
371     topItem->setOpacity(itemOpacity);
372
373     // single touch to top item, should be received by middle item
374     QTest::touchEvent(canvas).press(0, pointInTopItem, canvas);
375     QTest::qWait(50);
376     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
377     QCOMPARE(middleItem->lastEvent.touchPoints.count(), 1);
378     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
379     COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
380             makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))));
381
382     // touch top and middle items, middle item should get both events
383     QTest::touchEvent(canvas).press(0, pointInTopItem, canvas)
384             .press(1, pointInMiddleItem, canvas);
385     QTest::qWait(50);
386     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
387     QCOMPARE(middleItem->lastEvent.touchPoints.count(), 2);
388     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
389     COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
390            (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))
391                                               << makeTouchPoint(middleItem, pos) )));
392     middleItem->reset();
393
394     // disable middleItem as well
395     middleItem->acceptEvents = acceptEvents;
396     middleItem->setEnabled(enableItem);
397     middleItem->setOpacity(itemOpacity);
398
399     // touch top and middle items, bottom item should get all events
400     QTest::touchEvent(canvas).press(0, pointInTopItem, canvas)
401             .press(1, pointInMiddleItem, canvas);
402     QTest::qWait(50);
403     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
404     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
405     QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 2);
406     COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
407             (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))
408                                               << makeTouchPoint(bottomItem, bottomItem->mapFromItem(middleItem, pos)) )));
409     bottomItem->reset();
410
411     // disable bottom item as well
412     bottomItem->acceptEvents = acceptEvents;
413     bottomItem->setEnabled(enableItem);
414     bottomItem->setOpacity(itemOpacity);
415
416     // no events should be received
417     QTest::touchEvent(canvas).press(0, pointInTopItem, canvas)
418             .press(1, pointInMiddleItem, canvas)
419             .press(2, pointInBottomItem, canvas);
420     QTest::qWait(50);
421     QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
422     QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
423     QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
424
425     topItem->reset();
426     middleItem->reset();
427     bottomItem->reset();
428
429     // disable middle item, touch on top item
430     middleItem->acceptEvents = acceptEvents;
431     middleItem->setEnabled(enableItem);
432     middleItem->setOpacity(itemOpacity);
433     QTest::touchEvent(canvas).press(0, pointInTopItem, canvas);
434     QTest::qWait(50);
435     if (!enableItem || itemOpacity == 0) {
436         // middle item is disabled or has 0 opacity, bottom item receives the event
437         QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
438         QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
439         QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
440         COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
441                 makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))));
442     } else {
443         // middle item ignores event, sends it to the top item (top-most child)
444         QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
445         QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
446         QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
447         COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, canvas, Qt::TouchPointPressed,
448                 makeTouchPoint(topItem, pos)));
449     }
450
451     delete topItem;
452     delete middleItem;
453     delete bottomItem;
454     delete canvas;
455 }
456
457 void tst_qsgcanvas::touchEvent_propagation_data()
458 {
459     QTest::addColumn<bool>("acceptEvents");
460     QTest::addColumn<bool>("enableItem");
461     QTest::addColumn<qreal>("itemOpacity");
462
463     QTest::newRow("disable events") << false << true << 1.0;
464     QTest::newRow("disable item") << true << false << 1.0;
465     QTest::newRow("opacity of 0") << true << true << 0.0;
466 }
467
468 void tst_qsgcanvas::clearCanvas()
469 {
470     QSGCanvas *canvas = new QSGCanvas;
471     QSGItem *item = new QSGItem;
472     item->setParentItem(canvas->rootItem());
473
474     QVERIFY(item->canvas() == canvas);
475
476     delete canvas;
477
478     QVERIFY(item->canvas() == 0);
479
480     delete item;
481 }
482
483 void tst_qsgcanvas::mouseFiltering()
484 {
485     QSGCanvas *canvas = new QSGCanvas;
486     canvas->resize(250, 250);
487     canvas->move(100, 100);
488     canvas->show();
489
490     TestTouchItem *bottomItem = new TestTouchItem(canvas->rootItem());
491     bottomItem->setObjectName("Bottom Item");
492     bottomItem->setSize(QSizeF(150, 150));
493
494     TestTouchItem *middleItem = new TestTouchItem(bottomItem);
495     middleItem->setObjectName("Middle Item");
496     middleItem->setPos(QPointF(50, 50));
497     middleItem->setSize(QSizeF(150, 150));
498
499     TestTouchItem *topItem = new TestTouchItem(middleItem);
500     topItem->setObjectName("Top Item");
501     topItem->setPos(QPointF(50, 50));
502     topItem->setSize(QSizeF(150, 150));
503
504     QPoint pos(100, 100);
505
506     QTest::mousePress(canvas, Qt::LeftButton, 0, pos);
507     QTest::qWait(50);
508
509     // Mouse filtering propagates down the stack, so the
510     // correct order is
511     // 1. middleItem filters event
512     // 2. bottomItem filters event
513     // 3. topItem receives event
514     QCOMPARE(middleItem->mousePressId, 1);
515     QCOMPARE(bottomItem->mousePressId, 2);
516     QCOMPARE(topItem->mousePressId, 3);
517 }
518
519
520 QTEST_MAIN(tst_qsgcanvas)
521
522 #include "tst_qsgcanvas.moc"