1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <qtest.h>
30#include <QDebug>
31#include <QMimeData>
32#include <QTouchEvent>
33#include <QtQuick/QQuickItem>
34#include <QtQuick/QQuickView>
35#include <QtQuick/QQuickWindow>
36#include <QtQml/QQmlEngine>
37#include <QtQml/QQmlComponent>
38#include <QtQuick/private/qquickrectangle_p.h>
39#include <QtQuick/private/qquickloader_p.h>
40#include <QtQuick/private/qquickmousearea_p.h>
41#include "../../shared/util.h"
42#include "../shared/visualtestutil.h"
43#include "../shared/viewtestutil.h"
44#include <QSignalSpy>
45#include <private/qquickwindow_p.h>
46#include <private/qguiapplication_p.h>
47#include <QRunnable>
48#include <QOpenGLFunctions>
49#include <QSGRendererInterface>
50
51Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
52
53struct TouchEventData {
54 QEvent::Type type;
55 QWidget *widget;
56 QWindow *window;
57 Qt::TouchPointStates states;
58 QList<QTouchEvent::TouchPoint> touchPoints;
59};
60
61static QTouchEvent::TouchPoint makeTouchPoint(QQuickItem *item, const QPointF &p, const QPointF &lastPoint = QPointF())
62{
63 QPointF last = lastPoint.isNull() ? p : lastPoint;
64
65 QTouchEvent::TouchPoint tp;
66
67 tp.setPos(p);
68 tp.setLastPos(last);
69 tp.setScenePos(item->mapToScene(point: p));
70 tp.setLastScenePos(item->mapToScene(point: last));
71 tp.setScreenPos(item->window()->mapToGlobal(pos: tp.scenePos().toPoint()));
72 tp.setLastScreenPos(item->window()->mapToGlobal(pos: tp.lastScenePos().toPoint()));
73 return tp;
74}
75
76static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states = {},
77 const QList<QTouchEvent::TouchPoint>& touchPoints = QList<QTouchEvent::TouchPoint>())
78{
79 TouchEventData d = { .type: type, .widget: nullptr, .window: w, .states: states, .touchPoints: touchPoints };
80 return d;
81}
82static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states, const QTouchEvent::TouchPoint &touchPoint)
83{
84 QList<QTouchEvent::TouchPoint> points;
85 points << touchPoint;
86 return makeTouchData(type, w, states, touchPoints: points);
87}
88
89#define COMPARE_TOUCH_POINTS(tp1, tp2) \
90{ \
91 QCOMPARE(tp1.pos(), tp2.pos()); \
92 QCOMPARE(tp1.lastPos(), tp2.lastPos()); \
93 QCOMPARE(tp1.scenePos(), tp2.scenePos()); \
94 QCOMPARE(tp1.lastScenePos(), tp2.lastScenePos()); \
95 QCOMPARE(tp1.screenPos(), tp2.screenPos()); \
96 QCOMPARE(tp1.lastScreenPos(), tp2.lastScreenPos()); \
97}
98
99#define COMPARE_TOUCH_DATA(d1, d2) \
100{ \
101 QCOMPARE((int)d1.type, (int)d2.type); \
102 QCOMPARE(d1.widget, d2.widget); \
103 QCOMPARE((int)d1.states, (int)d2.states); \
104 QCOMPARE(d1.touchPoints.count(), d2.touchPoints.count()); \
105 for (int i=0; i<d1.touchPoints.count(); i++) { \
106 COMPARE_TOUCH_POINTS(d1.touchPoints[i], d2.touchPoints[i]); \
107 } \
108}
109
110
111class RootItemAccessor : public QQuickItem
112{
113 Q_OBJECT
114public:
115 RootItemAccessor()
116 : m_rootItemDestroyed(false)
117 , m_rootItem(nullptr)
118 {
119 }
120 Q_INVOKABLE QQuickItem *contentItem()
121 {
122 if (!m_rootItem) {
123 QQuickWindowPrivate *c = QQuickWindowPrivate::get(c: window());
124 m_rootItem = c->contentItem;
125 QObject::connect(sender: m_rootItem, SIGNAL(destroyed()), receiver: this, SLOT(rootItemDestroyed()));
126 }
127 return m_rootItem;
128 }
129 bool isRootItemDestroyed() {return m_rootItemDestroyed;}
130public slots:
131 void rootItemDestroyed() {
132 m_rootItemDestroyed = true;
133 }
134
135private:
136 bool m_rootItemDestroyed;
137 QQuickItem *m_rootItem;
138};
139
140class TestTouchItem : public QQuickRectangle
141{
142 Q_OBJECT
143public:
144 TestTouchItem(QQuickItem *parent = nullptr)
145 : QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true),
146 mousePressCount(0), mouseMoveCount(0),
147 spinLoopWhenPressed(false), touchEventCount(0),
148 mouseUngrabEventCount(0)
149 {
150 border()->setWidth(1);
151 setAcceptedMouseButtons(Qt::LeftButton);
152 setFiltersChildMouseEvents(true);
153 }
154
155 void reset() {
156 acceptTouchEvents = acceptMouseEvents = true;
157 setEnabled(true);
158 setVisible(true);
159
160 lastEvent = makeTouchData(type: QEvent::None, w: window(), states: {}, touchPoints: QList<QTouchEvent::TouchPoint>());//CHECK_VALID
161
162 lastVelocity = lastVelocityFromMouseMove = QVector2D();
163 lastMousePos = QPointF();
164 lastMouseCapabilityFlags = 0;
165 touchEventCount = 0;
166 mouseMoveCount = 0;
167 mouseUngrabEventCount = 0;
168 }
169
170 static void clearMouseEventCounters()
171 {
172 mousePressNum = mouseMoveNum = mouseReleaseNum = 0;
173 }
174
175 void clearTouchEventCounter()
176 {
177 touchEventCount = 0;
178 }
179
180 bool acceptTouchEvents;
181 bool acceptMouseEvents;
182 bool grabOnRelease = false;
183 TouchEventData lastEvent;
184 int mousePressCount;
185 int mouseMoveCount;
186 bool spinLoopWhenPressed;
187 int touchEventCount;
188 int mouseUngrabEventCount;
189 QVector2D lastVelocity;
190 QVector2D lastVelocityFromMouseMove;
191 QPointF lastMousePos;
192 int lastMouseCapabilityFlags;
193
194 void touchEvent(QTouchEvent *event) {
195 if (!acceptTouchEvents) {
196 event->ignore();
197 return;
198 }
199 ++touchEventCount;
200 lastEvent = makeTouchData(type: event->type(), w: event->window(), states: event->touchPointStates(), touchPoints: event->touchPoints());
201 if (event->device()->capabilities().testFlag(flag: QTouchDevice::Velocity) && !event->touchPoints().isEmpty()) {
202 lastVelocity = event->touchPoints().first().velocity();
203 } else {
204 lastVelocity = QVector2D();
205 }
206 if (spinLoopWhenPressed && event->touchPointStates().testFlag(flag: Qt::TouchPointPressed)) {
207 QCoreApplication::processEvents();
208 }
209 }
210
211 void mousePressEvent(QMouseEvent *e) {
212 if (!acceptMouseEvents) {
213 e->ignore();
214 return;
215 }
216 mousePressCount = ++mousePressNum;
217 lastMousePos = e->pos();
218 lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(event: e);
219 }
220
221 void mouseMoveEvent(QMouseEvent *e) {
222 if (!acceptMouseEvents) {
223 e->ignore();
224 return;
225 }
226 mouseMoveCount = ++mouseMoveNum;
227 lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(event: e);
228 lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(event: e);
229 lastMousePos = e->pos();
230 }
231
232 void mouseReleaseEvent(QMouseEvent *e) {
233 if (!acceptMouseEvents) {
234 e->ignore();
235 return;
236 }
237 ++mouseReleaseNum;
238 lastMousePos = e->pos();
239 lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(event: e);
240 }
241
242 void mouseUngrabEvent() {
243 ++mouseUngrabEventCount;
244 }
245
246 bool childMouseEventFilter(QQuickItem *item, QEvent *e) {
247 qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName();
248 switch (e->type()) {
249 case QEvent::MouseButtonPress:
250 mousePressCount = ++mousePressNum;
251 break;
252 case QEvent::MouseButtonRelease:
253 if (grabOnRelease)
254 grabMouse();
255 break;
256 case QEvent::MouseMove:
257 mouseMoveCount = ++mouseMoveNum;
258 break;
259 default:
260 break;
261 }
262
263 return false;
264 }
265
266 static int mousePressNum, mouseMoveNum, mouseReleaseNum;
267};
268
269int TestTouchItem::mousePressNum = 0;
270int TestTouchItem::mouseMoveNum = 0;
271int TestTouchItem::mouseReleaseNum = 0;
272
273class EventFilter : public QObject
274{
275public:
276 bool eventFilter(QObject *watched, QEvent *event) {
277 Q_UNUSED(watched);
278 events.append(t: event->type());
279 return false;
280 }
281
282 QList<int> events;
283};
284
285class ConstantUpdateItem : public QQuickItem
286{
287Q_OBJECT
288public:
289 ConstantUpdateItem(QQuickItem *parent = nullptr) : QQuickItem(parent), iterations(0) {setFlag(flag: ItemHasContents);}
290
291 int iterations;
292protected:
293 QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *){
294 iterations++;
295 update();
296 return nullptr;
297 }
298};
299
300class MouseRecordingWindow : public QQuickWindow
301{
302public:
303 explicit MouseRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { }
304
305protected:
306 void mousePressEvent(QMouseEvent *event) override {
307 qCDebug(lcTests) << event;
308 m_mouseEvents << *event;
309 QQuickWindow::mousePressEvent(event);
310 }
311 void mouseMoveEvent(QMouseEvent *event) override {
312 qCDebug(lcTests) << event;
313 m_mouseEvents << *event;
314 QQuickWindow::mouseMoveEvent(event);
315 }
316 void mouseReleaseEvent(QMouseEvent *event) override {
317 qCDebug(lcTests) << event;
318 m_mouseEvents << *event;
319 QQuickWindow::mouseReleaseEvent(event);
320 }
321
322public:
323 QList<QMouseEvent> m_mouseEvents;
324};
325
326class MouseRecordingItem : public QQuickItem
327{
328public:
329 MouseRecordingItem(bool acceptTouch, QQuickItem *parent = nullptr)
330 : QQuickItem(parent)
331 , m_acceptTouch(acceptTouch)
332 {
333 setSize(QSizeF(300, 300));
334 setAcceptedMouseButtons(Qt::LeftButton);
335 }
336
337protected:
338 void touchEvent(QTouchEvent* event) override {
339 event->setAccepted(m_acceptTouch);
340 m_touchEvents << *event;
341 qCDebug(lcTests) << "accepted?" << event->isAccepted() << event;
342 }
343 void mousePressEvent(QMouseEvent *event) override {
344 qCDebug(lcTests) << event;
345 m_mouseEvents << *event;
346 }
347 void mouseMoveEvent(QMouseEvent *event) override {
348 qCDebug(lcTests) << event;
349 m_mouseEvents << *event;
350 }
351 void mouseReleaseEvent(QMouseEvent *event) override {
352 qCDebug(lcTests) << event;
353 m_mouseEvents << *event;
354 }
355
356 void mouseDoubleClickEvent(QMouseEvent *event) override {
357 qCDebug(lcTests) << event;
358 m_mouseEvents << *event;
359 }
360
361public:
362 QList<QMouseEvent> m_mouseEvents;
363 QList<QTouchEvent> m_touchEvents;
364
365private:
366 bool m_acceptTouch;
367};
368
369class tst_qquickwindow : public QQmlDataTest
370{
371 Q_OBJECT
372public:
373 tst_qquickwindow()
374 : touchDevice(QTest::createTouchDevice())
375 , touchDeviceWithVelocity(QTest::createTouchDevice())
376 {
377 QQuickWindow::setDefaultAlphaBuffer(true);
378 touchDeviceWithVelocity->setCapabilities(QTouchDevice::Position | QTouchDevice::Velocity);
379 }
380
381private slots:
382 void cleanup();
383#if QT_CONFIG(opengl)
384 void openglContextCreatedSignal();
385#endif
386 void aboutToStopSignal();
387
388 void constantUpdates();
389 void constantUpdatesOnWindow_data();
390 void constantUpdatesOnWindow();
391 void mouseFiltering();
392 void headless();
393 void noUpdateWhenNothingChanges();
394
395 void touchEvent_basic();
396 void touchEvent_propagation();
397 void touchEvent_propagation_data();
398 void touchEvent_cancel();
399 void touchEvent_cancelClearsMouseGrab();
400 void touchEvent_reentrant();
401 void touchEvent_velocity();
402
403 void mergeTouchPointLists_data();
404 void mergeTouchPointLists();
405
406 void mouseFromTouch_basic();
407 void synthMouseFromTouch_data();
408 void synthMouseFromTouch();
409 void synthMouseDoubleClickFromTouch_data();
410 void synthMouseDoubleClickFromTouch();
411
412 void clearWindow();
413
414 void qmlCreation();
415 void qmlCreationWithScreen();
416 void clearColor();
417 void defaultState();
418
419 void grab_data();
420 void grab();
421 void multipleWindows();
422
423 void animationsWhileHidden();
424
425 void focusObject();
426 void focusReason();
427
428 void ignoreUnhandledMouseEvents();
429
430 void ownershipRootItem();
431
432 void hideThenDelete_data();
433 void hideThenDelete();
434
435 void showHideAnimate();
436
437 void testExpose();
438
439 void requestActivate();
440
441 void testWindowVisibilityOrder();
442
443 void blockClosing();
444 void blockCloseMethod();
445
446 void crashWhenHoverItemDeleted();
447
448 void unloadSubWindow();
449 void changeVisibilityInCompleted();
450
451 void qobjectEventFilter_touch();
452 void qobjectEventFilter_key();
453 void qobjectEventFilter_mouse();
454
455#if QT_CONFIG(cursor)
456 void cursor();
457#endif
458
459 void animatingSignal();
460
461 void contentItemSize();
462
463 void defaultSurfaceFormat();
464
465 void attachedProperty();
466
467 void testRenderJob();
468
469 void testHoverChildMouseEventFilter();
470 void testHoverTimestamp();
471 void test_circleMapItem();
472
473 void pointerEventTypeAndPointCount();
474
475 void grabContentItemToImage();
476
477 void testDragEventPropertyPropagation();
478
479 void findChild();
480
481 void testChildMouseEventFilter();
482 void testChildMouseEventFilter_data();
483 void cleanupGrabsOnRelease();
484
485#if QT_CONFIG(shortcut)
486 void testShortCut();
487#endif
488
489private:
490 QTouchDevice *touchDevice;
491 QTouchDevice *touchDeviceWithVelocity;
492};
493#if QT_CONFIG(opengl)
494Q_DECLARE_METATYPE(QOpenGLContext *);
495#endif
496void tst_qquickwindow::cleanup()
497{
498 QVERIFY(QGuiApplication::topLevelWindows().isEmpty());
499}
500#if QT_CONFIG(opengl)
501void tst_qquickwindow::openglContextCreatedSignal()
502{
503 qRegisterMetaType<QOpenGLContext *>();
504
505 QQuickWindow window;
506 QSignalSpy spy(&window, SIGNAL(openglContextCreated(QOpenGLContext*)));
507
508 window.setTitle(QTest::currentTestFunction());
509 window.show();
510 QVERIFY(QTest::qWaitForWindowExposed(&window));
511
512 if (window.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL)
513 QSKIP("Skipping OpenGL context test due to not running with OpenGL");
514
515 QTRY_VERIFY(spy.size() > 0);
516
517 QVariant ctx = spy.at(i: 0).at(i: 0);
518 QCOMPARE(qvariant_cast<QOpenGLContext *>(ctx), window.openglContext());
519}
520#endif
521void tst_qquickwindow::aboutToStopSignal()
522{
523 QQuickWindow window;
524 window.setTitle(QTest::currentTestFunction());
525 window.show();
526 QVERIFY(QTest::qWaitForWindowExposed(&window));
527
528 QSignalSpy spy(&window, SIGNAL(sceneGraphAboutToStop()));
529
530 window.hide();
531
532 QTRY_VERIFY(spy.count() > 0);
533}
534
535//If the item calls update inside updatePaintNode, it should schedule another sync pass
536void tst_qquickwindow::constantUpdates()
537{
538 QQuickWindow window;
539 window.resize(w: 250, h: 250);
540 ConstantUpdateItem item(window.contentItem());
541 window.setTitle(QTest::currentTestFunction());
542 window.show();
543
544 QSignalSpy beforeSpy(&window, SIGNAL(beforeSynchronizing()));
545 QSignalSpy afterSpy(&window, SIGNAL(afterSynchronizing()));
546
547 QTRY_VERIFY(item.iterations > 10);
548 QTRY_VERIFY(beforeSpy.count() > 10);
549 QTRY_VERIFY(afterSpy.count() > 10);
550}
551
552void tst_qquickwindow::constantUpdatesOnWindow_data()
553{
554 QTest::addColumn<bool>(name: "blockedGui");
555 QTest::addColumn<QByteArray>(name: "signal");
556
557 QQuickWindow window;
558 window.setTitle(QTest::currentTestFunction());
559 window.setGeometry(posx: 100, posy: 100, w: 300, h: 200);
560 window.show();
561 QVERIFY(QTest::qWaitForWindowExposed(&window));
562 const bool threaded = QQuickWindowPrivate::get(c: &window)->context->thread() != QGuiApplication::instance()->thread();
563 if (threaded) {
564 QTest::newRow(dataTag: "blocked, beforeRender") << true << QByteArray(SIGNAL(beforeRendering()));
565 QTest::newRow(dataTag: "blocked, afterRender") << true << QByteArray(SIGNAL(afterRendering()));
566 QTest::newRow(dataTag: "blocked, swapped") << true << QByteArray(SIGNAL(frameSwapped()));
567 }
568 QTest::newRow(dataTag: "unblocked, beforeRender") << false << QByteArray(SIGNAL(beforeRendering()));
569 QTest::newRow(dataTag: "unblocked, afterRender") << false << QByteArray(SIGNAL(afterRendering()));
570 QTest::newRow(dataTag: "unblocked, swapped") << false << QByteArray(SIGNAL(frameSwapped()));
571}
572
573class FrameCounter : public QObject
574{
575 Q_OBJECT
576public slots:
577 void incr() { QMutexLocker locker(&m_mutex); ++m_counter; }
578public:
579 FrameCounter() : m_counter(0) {}
580 int count() { QMutexLocker locker(&m_mutex); int x = m_counter; return x; }
581private:
582 int m_counter;
583 QMutex m_mutex;
584};
585
586void tst_qquickwindow::constantUpdatesOnWindow()
587{
588 QFETCH(bool, blockedGui);
589 QFETCH(QByteArray, signal);
590
591 QQuickWindow window;
592 window.setTitle(QTest::currentTestFunction());
593 window.setGeometry(posx: 100, posy: 100, w: 300, h: 200);
594
595 bool ok = connect(sender: &window, signal: signal.constData(), receiver: &window, SLOT(update()), Qt::DirectConnection);
596 Q_ASSERT(ok);
597 window.show();
598 QVERIFY(QTest::qWaitForWindowExposed(&window));
599
600 FrameCounter counter;
601 connect(sender: &window, SIGNAL(frameSwapped()), receiver: &counter, SLOT(incr()), Qt::DirectConnection);
602
603 int frameCount = 10;
604 QElapsedTimer timer;
605 timer.start();
606 if (blockedGui) {
607 while (counter.count() < frameCount)
608 QTest::qSleep(ms: 100);
609 QVERIFY(counter.count() >= frameCount);
610 } else {
611 window.update();
612 QTRY_VERIFY(counter.count() > frameCount);
613 }
614 window.hide();
615}
616
617void tst_qquickwindow::touchEvent_basic()
618{
619 TestTouchItem::clearMouseEventCounters();
620
621 QQuickWindow *window = new QQuickWindow;
622 QScopedPointer<QQuickWindow> cleanup(window);
623 window->setTitle(QTest::currentTestFunction());
624
625 window->resize(w: 250, h: 250);
626 window->setPosition(posx: 100, posy: 100);
627 window->show();
628 QVERIFY(QTest::qWaitForWindowActive(window));
629
630 TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
631 bottomItem->setObjectName("Bottom Item");
632 bottomItem->setSize(QSizeF(150, 150));
633
634 TestTouchItem *middleItem = new TestTouchItem(bottomItem);
635 middleItem->setObjectName("Middle Item");
636 middleItem->setPosition(QPointF(50, 50));
637 middleItem->setSize(QSizeF(150, 150));
638
639 TestTouchItem *topItem = new TestTouchItem(middleItem);
640 topItem->setObjectName("Top Item");
641 topItem->setPosition(QPointF(50, 50));
642 topItem->setSize(QSizeF(150, 150));
643
644 QPointF pos(10, 10);
645 QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, device: touchDevice, autoCommit: false);
646
647 // press single point
648 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(),window).commit();
649 QQuickTouchUtils::flush(window);
650 QTRY_COMPARE(topItem->lastEvent.touchPoints.count(), 1);
651
652 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
653 QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
654 // At one point this was failing with kwin (KDE window manager) because window->setPosition(100, 100)
655 // would put the decorated window at that position rather than the window itself.
656 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
657 topItem->reset();
658 touchSeq.release(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window).commit();
659
660 // press multiple points
661 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window)
662 .press(touchId: 1, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
663 QQuickTouchUtils::flush(window);
664 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
665 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
666 QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
667 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
668 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
669 topItem->reset();
670 bottomItem->reset();
671 touchSeq.release(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window).release(touchId: 1, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
672
673 // touch point on top item moves to bottom item, but top item should still receive the event
674 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window).commit();
675 QQuickTouchUtils::flush(window);
676 touchSeq.move(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
677 QQuickTouchUtils::flush(window);
678 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
679 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, Qt::TouchPointMoved,
680 makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
681 topItem->reset();
682 touchSeq.release(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
683
684 // touch point on bottom item moves to top item, but bottom item should still receive the event
685 touchSeq.press(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
686 QQuickTouchUtils::flush(window);
687 touchSeq.move(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window).commit();
688 QQuickTouchUtils::flush(window);
689 QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
690 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, Qt::TouchPointMoved,
691 makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos), pos)));
692 bottomItem->reset();
693 touchSeq.release(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
694
695 // a single stationary press on an item shouldn't cause an event
696 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(), window).commit();
697 QQuickTouchUtils::flush(window);
698 touchSeq.stationary(touchId: 0)
699 .press(touchId: 1, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
700 QQuickTouchUtils::flush(window);
701 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); // received press only, not stationary
702 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
703 QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
704 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos)));
705 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
706 topItem->reset();
707 bottomItem->reset();
708 // cleanup: what is pressed must be released
709 // Otherwise you will get an assertion failure:
710 // ASSERT: "itemForTouchPointId.isEmpty()" in file items/qquickwindow.cpp
711 touchSeq.release(touchId: 0, pt: pos.toPoint(), window).release(touchId: 1, pt: pos.toPoint(), window).commit();
712 QQuickTouchUtils::flush(window);
713
714 // move touch point from top item to bottom, and release
715 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(),window).commit();
716 QQuickTouchUtils::flush(window);
717 touchSeq.release(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(),window).commit();
718 QQuickTouchUtils::flush(window);
719 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
720 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, Qt::TouchPointReleased,
721 makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
722 topItem->reset();
723
724 // release while another point is pressed
725 touchSeq.press(touchId: 0, pt: topItem->mapToScene(point: pos).toPoint(),window)
726 .press(touchId: 1, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
727 QQuickTouchUtils::flush(window);
728 touchSeq.move(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window).commit();
729 QQuickTouchUtils::flush(window);
730 touchSeq.release(touchId: 0, pt: bottomItem->mapToScene(point: pos).toPoint(), window)
731 .stationary(touchId: 1).commit();
732 QQuickTouchUtils::flush(window);
733 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
734 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
735 QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
736 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, Qt::TouchPointReleased,
737 makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos))));
738 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos)));
739 topItem->reset();
740 bottomItem->reset();
741
742 delete topItem;
743 delete middleItem;
744 delete bottomItem;
745}
746
747void tst_qquickwindow::touchEvent_propagation()
748{
749 TestTouchItem::clearMouseEventCounters();
750
751 QFETCH(bool, acceptTouchEvents);
752 QFETCH(bool, acceptMouseEvents);
753 QFETCH(bool, enableItem);
754 QFETCH(bool, showItem);
755
756 QQuickWindow *window = new QQuickWindow;
757 QScopedPointer<QQuickWindow> cleanup(window);
758
759 window->resize(w: 250, h: 250);
760 window->setPosition(posx: 100, posy: 100);
761 window->setTitle(QTest::currentTestFunction());
762 window->show();
763 QVERIFY(QTest::qWaitForWindowActive(window));
764
765 TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
766 bottomItem->setObjectName("Bottom Item");
767 bottomItem->setSize(QSizeF(150, 150));
768
769 TestTouchItem *middleItem = new TestTouchItem(bottomItem);
770 middleItem->setObjectName("Middle Item");
771 middleItem->setPosition(QPointF(50, 50));
772 middleItem->setSize(QSizeF(150, 150));
773
774 TestTouchItem *topItem = new TestTouchItem(middleItem);
775 topItem->setObjectName("Top Item");
776 topItem->setPosition(QPointF(50, 50));
777 topItem->setSize(QSizeF(150, 150));
778
779 QPointF pos(10, 10);
780 QPoint pointInBottomItem = bottomItem->mapToScene(point: pos).toPoint(); // (10, 10)
781 QPoint pointInMiddleItem = middleItem->mapToScene(point: pos).toPoint(); // (60, 60) overlaps with bottomItem
782 QPoint pointInTopItem = topItem->mapToScene(point: pos).toPoint(); // (110, 110) overlaps with bottom & top items
783
784 // disable topItem
785 topItem->acceptTouchEvents = acceptTouchEvents;
786 topItem->acceptMouseEvents = acceptMouseEvents;
787 topItem->setEnabled(enableItem);
788 topItem->setVisible(showItem);
789
790 // single touch to top item, should be received by middle item
791 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: pointInTopItem, window);
792 QTRY_COMPARE(middleItem->lastEvent.touchPoints.count(), 1);
793 QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
794 QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
795 COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed,
796 makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))));
797 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: pointInTopItem, window);
798
799 // touch top and middle items, middle item should get both events
800 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: pointInTopItem, window)
801 .press(touchId: 1, pt: pointInMiddleItem, window);
802 QTRY_COMPARE(middleItem->lastEvent.touchPoints.count(), 2);
803 QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
804 QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
805 COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed,
806 (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))
807 << makeTouchPoint(middleItem, pos) )));
808 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: pointInTopItem, window)
809 .release(touchId: 1, pt: pointInMiddleItem, window);
810 middleItem->reset();
811
812 // disable middleItem as well
813 middleItem->acceptTouchEvents = acceptTouchEvents;
814 middleItem->acceptMouseEvents = acceptMouseEvents;
815 middleItem->setEnabled(enableItem);
816 middleItem->setVisible(showItem);
817
818 // touch top and middle items, bottom item should get all events
819 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: pointInTopItem, window)
820 .press(touchId: 1, pt: pointInMiddleItem, window);
821 QTRY_COMPARE(bottomItem->lastEvent.touchPoints.count(), 2);
822 QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
823 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
824 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed,
825 (QList<QTouchEvent::TouchPoint>() << makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))
826 << makeTouchPoint(bottomItem, bottomItem->mapFromItem(middleItem, pos)) )));
827 bottomItem->reset();
828
829 // disable bottom item as well
830 bottomItem->acceptTouchEvents = acceptTouchEvents;
831 bottomItem->setEnabled(enableItem);
832 bottomItem->setVisible(showItem);
833 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: pointInTopItem, window)
834 .release(touchId: 1, pt: pointInMiddleItem, window);
835
836 // no events should be received
837 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: pointInTopItem, window)
838 .press(touchId: 1, pt: pointInMiddleItem, window)
839 .press(touchId: 2, pt: pointInBottomItem, window);
840 QTest::qWait(ms: 50);
841 QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
842 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
843 QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
844 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: pointInTopItem, window)
845 .release(touchId: 1, pt: pointInMiddleItem, window)
846 .release(touchId: 2, pt: pointInBottomItem, window);
847 topItem->reset();
848 middleItem->reset();
849 bottomItem->reset();
850
851 // disable middle item, touch on top item
852 middleItem->acceptTouchEvents = acceptTouchEvents;
853 middleItem->setEnabled(enableItem);
854 middleItem->setVisible(showItem);
855 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: pointInTopItem, window);
856 QTest::qWait(ms: 50);
857 if (!enableItem || !showItem) {
858 // middle item is disabled or has 0 opacity, bottom item receives the event
859 QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
860 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
861 QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1);
862 COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed,
863 makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))));
864 } else {
865 // middle item ignores event, sends it to the top item (top-most child)
866 QCOMPARE(topItem->lastEvent.touchPoints.count(), 1);
867 QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
868 QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
869 COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed,
870 makeTouchPoint(topItem, pos)));
871 }
872 QTest::touchEvent(window, device: touchDevice).release(touchId: 0, pt: pointInTopItem, window);
873
874 delete topItem;
875 delete middleItem;
876 delete bottomItem;
877}
878
879void tst_qquickwindow::touchEvent_propagation_data()
880{
881 QTest::addColumn<bool>(name: "acceptTouchEvents");
882 QTest::addColumn<bool>(name: "acceptMouseEvents");
883 QTest::addColumn<bool>(name: "enableItem");
884 QTest::addColumn<bool>(name: "showItem");
885
886 QTest::newRow(dataTag: "disable events") << false << false << true << true;
887 QTest::newRow(dataTag: "disable item") << true << true << false << true;
888 QTest::newRow(dataTag: "hide item") << true << true << true << false;
889}
890
891void tst_qquickwindow::touchEvent_cancel()
892{
893 TestTouchItem::clearMouseEventCounters();
894
895 QQuickWindow *window = new QQuickWindow;
896 QScopedPointer<QQuickWindow> cleanup(window);
897
898 window->resize(w: 250, h: 250);
899 window->setPosition(posx: 100, posy: 100);
900 window->setTitle(QTest::currentTestFunction());
901 window->show();
902 QVERIFY(QTest::qWaitForWindowActive(window));
903
904 TestTouchItem *item = new TestTouchItem(window->contentItem());
905 item->setPosition(QPointF(50, 50));
906 item->setSize(QSizeF(150, 150));
907
908 QPointF pos(50, 50);
909 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: item->mapToScene(point: pos).toPoint(), window);
910 QCoreApplication::processEvents();
911
912 QTRY_COMPARE(item->lastEvent.touchPoints.count(), 1);
913 TouchEventData d = makeTouchData(type: QEvent::TouchBegin, w: window, states: Qt::TouchPointPressed, touchPoint: makeTouchPoint(item, p: pos));
914 COMPARE_TOUCH_DATA(item->lastEvent, d);
915 item->reset();
916
917 QWindowSystemInterface::handleTouchCancelEvent(window: nullptr, device: touchDevice);
918 QCoreApplication::processEvents();
919 d = makeTouchData(type: QEvent::TouchCancel, w: window);
920 COMPARE_TOUCH_DATA(item->lastEvent, d);
921
922 delete item;
923}
924
925void tst_qquickwindow::touchEvent_cancelClearsMouseGrab()
926{
927 TestTouchItem::clearMouseEventCounters();
928
929 QQuickWindow *window = new QQuickWindow;
930 QScopedPointer<QQuickWindow> cleanup(window);
931
932 window->resize(w: 250, h: 250);
933 window->setPosition(posx: 100, posy: 100);
934 window->setTitle(QTest::currentTestFunction());
935 window->show();
936 QVERIFY(QTest::qWaitForWindowActive(window));
937
938 TestTouchItem *item = new TestTouchItem(window->contentItem());
939 item->setPosition(QPointF(50, 50));
940 item->setSize(QSizeF(150, 150));
941 item->acceptMouseEvents = true;
942 item->acceptTouchEvents = false;
943
944 QPointF pos(50, 50);
945 QTest::touchEvent(window, device: touchDevice).press(touchId: 0, pt: item->mapToScene(point: pos).toPoint(), window);
946 QCoreApplication::processEvents();
947
948 QTRY_COMPARE(item->mousePressCount, 1);
949 QTRY_COMPARE(item->mouseUngrabEventCount, 0);
950
951 QWindowSystemInterface::handleTouchCancelEvent(window: nullptr, device: touchDevice);
952 QCoreApplication::processEvents();
953
954 QTRY_COMPARE(item->mouseUngrabEventCount, 1);
955}
956
957void tst_qquickwindow::touchEvent_reentrant()
958{
959 TestTouchItem::clearMouseEventCounters();
960
961 QQuickWindow *window = new QQuickWindow;
962 QScopedPointer<QQuickWindow> cleanup(window);
963
964 window->resize(w: 250, h: 250);
965 window->setPosition(posx: 100, posy: 100);
966 window->setTitle(QTest::currentTestFunction());
967 window->show();
968 QVERIFY(QTest::qWaitForWindowActive(window));
969
970 TestTouchItem *item = new TestTouchItem(window->contentItem());
971
972 item->spinLoopWhenPressed = true; // will call processEvents() from the touch handler
973
974 item->setPosition(QPointF(50, 50));
975 item->setSize(QSizeF(150, 150));
976 QPointF pos(60, 60);
977
978 // None of these should commit from the dtor.
979 QTest::QTouchEventSequence press = QTest::touchEvent(window, device: touchDevice, autoCommit: false).press(touchId: 0, pt: pos.toPoint(), window);
980 pos += QPointF(2, 2);
981 QTest::QTouchEventSequence move = QTest::touchEvent(window, device: touchDevice, autoCommit: false).move(touchId: 0, pt: pos.toPoint(), window);
982 QTest::QTouchEventSequence release = QTest::touchEvent(window, device: touchDevice, autoCommit: false).release(touchId: 0, pt: pos.toPoint(), window);
983
984 // Now commit (i.e. call QWindowSystemInterface::handleTouchEvent), but do not process the events yet.
985 press.commit(processEvents: false);
986 move.commit(processEvents: false);
987 release.commit(processEvents: false);
988
989 QCoreApplication::processEvents();
990
991 QTRY_COMPARE(item->touchEventCount, 3);
992
993 delete item;
994}
995
996void tst_qquickwindow::touchEvent_velocity()
997{
998 TestTouchItem::clearMouseEventCounters();
999
1000 QQuickWindow *window = new QQuickWindow;
1001 QScopedPointer<QQuickWindow> cleanup(window);
1002 window->resize(w: 250, h: 250);
1003 window->setPosition(posx: 100, posy: 100);
1004 window->setTitle(QTest::currentTestFunction());
1005 window->show();
1006 QVERIFY(QTest::qWaitForWindowExposed(window));
1007 QTest::qWait(ms: 10);
1008
1009 TestTouchItem *item = new TestTouchItem(window->contentItem());
1010 item->setPosition(QPointF(50, 50));
1011 item->setSize(QSizeF(150, 150));
1012
1013 QList<QTouchEvent::TouchPoint> points;
1014 QTouchEvent::TouchPoint tp;
1015 tp.setId(1);
1016 tp.setState(Qt::TouchPointPressed);
1017 const QPointF localPos = item->mapToScene(point: QPointF(10, 10));
1018 const QPointF screenPos = window->mapToGlobal(pos: localPos.toPoint());
1019 tp.setPos(localPos);
1020 tp.setScreenPos(screenPos);
1021 tp.setEllipseDiameters(QSizeF(4, 4));
1022 points << tp;
1023 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1024 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1025 QGuiApplication::processEvents();
1026 QQuickTouchUtils::flush(window);
1027 QCOMPARE(item->touchEventCount, 1);
1028
1029 points[0].setState(Qt::TouchPointMoved);
1030 points[0].setPos(localPos + QPointF(5, 5));
1031 points[0].setScreenPos(screenPos + QPointF(5, 5));
1032 QVector2D velocity(1.5, 2.5);
1033 points[0].setVelocity(velocity);
1034 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1035 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1036 QGuiApplication::processEvents();
1037 QQuickTouchUtils::flush(window);
1038 QCOMPARE(item->touchEventCount, 2);
1039 QCOMPARE(item->lastEvent.touchPoints.count(), 1);
1040 QCOMPARE(item->lastVelocity, velocity);
1041
1042 // Now have a transformation on the item and check if velocity and position are transformed accordingly.
1043 item->setRotation(90); // clockwise
1044 QMatrix4x4 transformMatrix;
1045 transformMatrix.rotate(angle: -90, x: 0, y: 0, z: 1); // counterclockwise
1046 QVector2D transformedVelocity = transformMatrix.mapVector(vector: velocity).toVector2D();
1047 points[0].setPos(points[0].pos() + QPointF(5, 5));
1048 points[0].setScreenPos(points[0].screenPos() + QPointF(5, 5));
1049 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1050 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1051 QGuiApplication::processEvents();
1052 QQuickTouchUtils::flush(window);
1053 QCOMPARE(item->lastVelocity, transformedVelocity);
1054 QPoint itemLocalPos = item->mapFromScene(point: points[0].pos()).toPoint();
1055 QPoint itemLocalPosFromEvent = item->lastEvent.touchPoints[0].pos().toPoint();
1056 QCOMPARE(itemLocalPos, itemLocalPosFromEvent);
1057
1058 points[0].setState(Qt::TouchPointReleased);
1059 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1060 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1061 QGuiApplication::processEvents();
1062 QQuickTouchUtils::flush(window);
1063 delete item;
1064}
1065
1066void tst_qquickwindow::mergeTouchPointLists_data()
1067{
1068 QTest::addColumn<QVector<QQuickItem*>>(name: "list1");
1069 QTest::addColumn<QVector<QQuickItem*>>(name: "list2");
1070 QTest::addColumn<QVector<QQuickItem*>>(name: "expected");
1071 QTest::addColumn<bool>(name: "showItem");
1072
1073 // FIXME: do not leak all these items
1074 auto item1 = new QQuickItem();
1075 auto item2 = new QQuickItem();
1076 auto item3 = new QQuickItem();
1077 auto item4 = new QQuickItem();
1078 auto item5 = new QQuickItem();
1079
1080 QTest::newRow(dataTag: "empty") << QVector<QQuickItem*>() << QVector<QQuickItem*>() << QVector<QQuickItem*>();
1081 QTest::newRow(dataTag: "single list left")
1082 << (QVector<QQuickItem*>() << item1 << item2 << item3)
1083 << QVector<QQuickItem*>()
1084 << (QVector<QQuickItem*>() << item1 << item2 << item3);
1085 QTest::newRow(dataTag: "single list right")
1086 << QVector<QQuickItem*>()
1087 << (QVector<QQuickItem*>() << item1 << item2 << item3)
1088 << (QVector<QQuickItem*>() << item1 << item2 << item3);
1089 QTest::newRow(dataTag: "two lists identical")
1090 << (QVector<QQuickItem*>() << item1 << item2 << item3)
1091 << (QVector<QQuickItem*>() << item1 << item2 << item3)
1092 << (QVector<QQuickItem*>() << item1 << item2 << item3);
1093 QTest::newRow(dataTag: "two lists 1")
1094 << (QVector<QQuickItem*>() << item1 << item2 << item5)
1095 << (QVector<QQuickItem*>() << item3 << item4 << item5)
1096 << (QVector<QQuickItem*>() << item1 << item2 << item3 << item4 << item5);
1097 QTest::newRow(dataTag: "two lists 2")
1098 << (QVector<QQuickItem*>() << item1 << item2 << item5)
1099 << (QVector<QQuickItem*>() << item3 << item4 << item5)
1100 << (QVector<QQuickItem*>() << item1 << item2 << item3 << item4 << item5);
1101 QTest::newRow(dataTag: "two lists 3")
1102 << (QVector<QQuickItem*>() << item1 << item2 << item3)
1103 << (QVector<QQuickItem*>() << item1 << item4 << item5)
1104 << (QVector<QQuickItem*>() << item1 << item2 << item3 << item4 << item5);
1105 QTest::newRow(dataTag: "two lists 4")
1106 << (QVector<QQuickItem*>() << item1 << item3 << item4)
1107 << (QVector<QQuickItem*>() << item2 << item3 << item5)
1108 << (QVector<QQuickItem*>() << item1 << item2 << item3 << item4 << item5);
1109 QTest::newRow(dataTag: "two lists 5")
1110 << (QVector<QQuickItem*>() << item1 << item2 << item4)
1111 << (QVector<QQuickItem*>() << item1 << item3 << item4)
1112 << (QVector<QQuickItem*>() << item1 << item2 << item3 << item4);
1113}
1114
1115void tst_qquickwindow::mergeTouchPointLists()
1116{
1117 QFETCH(QVector<QQuickItem*>, list1);
1118 QFETCH(QVector<QQuickItem*>, list2);
1119 QFETCH(QVector<QQuickItem*>, expected);
1120
1121 QQuickWindow win;
1122 auto windowPrivate = QQuickWindowPrivate::get(c: &win);
1123 auto targetList = windowPrivate->mergePointerTargets(list1, list2);
1124 QCOMPARE(targetList, expected);
1125}
1126
1127void tst_qquickwindow::mouseFromTouch_basic()
1128{
1129 // Turn off accepting touch events with acceptTouchEvents. This
1130 // should result in sending mouse events generated from the touch
1131 // with the new event propagation system.
1132
1133 TestTouchItem::clearMouseEventCounters();
1134 QQuickWindow *window = new QQuickWindow;
1135 QScopedPointer<QQuickWindow> cleanup(window);
1136 window->resize(w: 250, h: 250);
1137 window->setPosition(posx: 100, posy: 100);
1138 window->setTitle(QTest::currentTestFunction());
1139 window->show();
1140 QVERIFY(QTest::qWaitForWindowExposed(window));
1141 QTest::qWait(ms: 10);
1142
1143 TestTouchItem *item = new TestTouchItem(window->contentItem());
1144 item->setPosition(QPointF(50, 50));
1145 item->setSize(QSizeF(150, 150));
1146 item->acceptTouchEvents = false;
1147
1148 QList<QTouchEvent::TouchPoint> points;
1149 QTouchEvent::TouchPoint tp;
1150 tp.setId(1);
1151 tp.setState(Qt::TouchPointPressed);
1152 const QPointF localPos = item->mapToScene(point: QPointF(10, 10));
1153 const QPointF screenPos = window->mapToGlobal(pos: localPos.toPoint());
1154 tp.setPos(localPos);
1155 tp.setScreenPos(screenPos);
1156 tp.setEllipseDiameters(QSizeF(4, 4));
1157 points << tp;
1158 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1159 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1160 QGuiApplication::processEvents();
1161 QQuickTouchUtils::flush(window);
1162 points[0].setState(Qt::TouchPointMoved);
1163 points[0].setPos(localPos + QPointF(5, 5));
1164 points[0].setScreenPos(screenPos + QPointF(5, 5));
1165 QVector2D velocity(1.5, 2.5);
1166 points[0].setVelocity(velocity);
1167 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1168 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1169 QGuiApplication::processEvents();
1170 QQuickTouchUtils::flush(window);
1171 points[0].setState(Qt::TouchPointReleased);
1172 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1173 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1174 QGuiApplication::processEvents();
1175 QQuickTouchUtils::flush(window);
1176
1177 // The item should have received a mouse press, move, and release.
1178 QCOMPARE(item->mousePressNum, 1);
1179 QCOMPARE(item->mouseMoveNum, 1);
1180 QCOMPARE(item->mouseReleaseNum, 1);
1181 QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].pos()).toPoint());
1182 QCOMPARE(item->lastVelocityFromMouseMove, velocity);
1183 QVERIFY((item->lastMouseCapabilityFlags & QTouchDevice::Velocity) != 0);
1184
1185 // Now the same with a transformation.
1186 item->setRotation(90); // clockwise
1187 QMatrix4x4 transformMatrix;
1188 transformMatrix.rotate(angle: -90, x: 0, y: 0, z: 1); // counterclockwise
1189 QVector2D transformedVelocity = transformMatrix.mapVector(vector: velocity).toVector2D();
1190 points[0].setState(Qt::TouchPointPressed);
1191 points[0].setVelocity(velocity);
1192 tp.setPos(localPos);
1193 tp.setScreenPos(screenPos);
1194 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1195 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1196 QGuiApplication::processEvents();
1197 QQuickTouchUtils::flush(window);
1198 points[0].setState(Qt::TouchPointMoved);
1199 points[0].setPos(localPos + QPointF(5, 5));
1200 points[0].setScreenPos(screenPos + QPointF(5, 5));
1201 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1202 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1203 QGuiApplication::processEvents();
1204 QQuickTouchUtils::flush(window);
1205 QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].pos()).toPoint());
1206 QCOMPARE(item->lastVelocityFromMouseMove, transformedVelocity);
1207
1208 points[0].setState(Qt::TouchPointReleased);
1209 QWindowSystemInterface::handleTouchEvent(window, device: touchDeviceWithVelocity,
1210 points: QWindowSystemInterfacePrivate::toNativeTouchPoints(pointList: points, window));
1211 QCoreApplication::processEvents();
1212 QQuickTouchUtils::flush(window);
1213 delete item;
1214}
1215
1216void tst_qquickwindow::synthMouseFromTouch_data()
1217{
1218 QTest::addColumn<bool>(name: "synthMouse"); // AA_SynthesizeMouseForUnhandledTouchEvents
1219 QTest::addColumn<bool>(name: "acceptTouch"); // QQuickItem::touchEvent: setAccepted()
1220
1221 QTest::newRow(dataTag: "no synth, accept") << false << true; // suitable for touch-capable UIs
1222 QTest::newRow(dataTag: "no synth, don't accept") << false << false;
1223 QTest::newRow(dataTag: "synth and accept") << true << true;
1224 QTest::newRow(dataTag: "synth, don't accept") << true << false; // the default
1225}
1226
1227void tst_qquickwindow::synthMouseFromTouch()
1228{
1229 QFETCH(bool, synthMouse);
1230 QFETCH(bool, acceptTouch);
1231
1232 QCoreApplication::setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTouchEvents, on: synthMouse);
1233 QScopedPointer<MouseRecordingWindow> window(new MouseRecordingWindow);
1234 QScopedPointer<MouseRecordingItem> item(new MouseRecordingItem(acceptTouch, nullptr));
1235 item->setParentItem(window->contentItem());
1236 window->resize(w: 250, h: 250);
1237 window->setPosition(posx: 100, posy: 100);
1238 window->setTitle(QTest::currentTestFunction());
1239 window->show();
1240 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1241
1242 QPoint p1 = QPoint(20, 20);
1243 QPoint p2 = QPoint(30, 30);
1244 QTest::touchEvent(window: window.data(), device: touchDevice).press(touchId: 0, pt: p1, window: window.data());
1245 QTest::touchEvent(window: window.data(), device: touchDevice).move(touchId: 0, pt: p2, window: window.data());
1246 QTest::touchEvent(window: window.data(), device: touchDevice).release(touchId: 0, pt: p2, window: window.data());
1247
1248 QCOMPARE(item->m_touchEvents.count(), !synthMouse && !acceptTouch ? 1 : 3);
1249 QCOMPARE(item->m_mouseEvents.count(), (acceptTouch || !synthMouse) ? 0 : 3);
1250 QCOMPARE(window->m_mouseEvents.count(), 0);
1251 for (const QMouseEvent &ev : item->m_mouseEvents)
1252 QCOMPARE(ev.source(), Qt::MouseEventSynthesizedByQt);
1253}
1254
1255void tst_qquickwindow::synthMouseDoubleClickFromTouch_data()
1256{
1257 QTest::addColumn<QPoint>(name: "movement");
1258 QTest::addColumn<QPoint>(name: "distanceBetweenPresses");
1259 QTest::addColumn<bool>(name: "expectedSynthesizedDoubleClickEvent");
1260
1261 QTest::newRow(dataTag: "normal") << QPoint(0, 0) << QPoint(0, 0) << true;
1262 QTest::newRow(dataTag: "with 1 pixel wiggle") << QPoint(1, 1) << QPoint(1, 1) << true;
1263 QTest::newRow(dataTag: "too much distance to second tap") << QPoint(0, 0) << QPoint(50, 0) << false;
1264 QTest::newRow(dataTag: "too much drag") << QPoint(50, 0) << QPoint(0, 0) << false;
1265 QTest::newRow(dataTag: "too much drag and too much distance to second tap") << QPoint(50, 0) << QPoint(50, 0) << false;
1266}
1267
1268void tst_qquickwindow::synthMouseDoubleClickFromTouch()
1269{
1270 QFETCH(QPoint, movement);
1271 QFETCH(QPoint, distanceBetweenPresses);
1272 QFETCH(bool, expectedSynthesizedDoubleClickEvent);
1273
1274 QCoreApplication::setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTouchEvents, on: true);
1275 QScopedPointer<MouseRecordingWindow> window(new MouseRecordingWindow);
1276 QScopedPointer<MouseRecordingItem> item(new MouseRecordingItem(false, nullptr));
1277 item->setParentItem(window->contentItem());
1278 window->resize(w: 250, h: 250);
1279 window->setPosition(posx: 100, posy: 100);
1280 window->setTitle(QTest::currentTestFunction());
1281 window->show();
1282 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1283 QTest::qWait(ms: 100);
1284
1285 QPoint p1 = item->mapToScene(point: item->clipRect().center()).toPoint();
1286 QTest::touchEvent(window: window.data(), device: touchDevice).press(touchId: 0, pt: p1, window: window.data());
1287 QTest::touchEvent(window: window.data(), device: touchDevice).move(touchId: 0, pt: p1 + movement, window: window.data());
1288 QTest::touchEvent(window: window.data(), device: touchDevice).release(touchId: 0, pt: p1 + movement, window: window.data());
1289
1290 QPoint p2 = p1 + distanceBetweenPresses;
1291 QTest::touchEvent(window: window.data(), device: touchDevice).press(touchId: 1, pt: p2, window: window.data());
1292 QTest::touchEvent(window: window.data(), device: touchDevice).move(touchId: 1, pt: p2 + movement, window: window.data());
1293 QTest::touchEvent(window: window.data(), device: touchDevice).release(touchId: 1, pt: p2 + movement, window: window.data());
1294
1295 const int eventCount = item->m_mouseEvents.count();
1296 QVERIFY(eventCount >= 2);
1297
1298 const int nDoubleClicks = std::count_if(first: item->m_mouseEvents.constBegin(), last: item->m_mouseEvents.constEnd(), pred: [](const QMouseEvent &ev) { return (ev.type() == QEvent::MouseButtonDblClick); } );
1299 const bool foundDoubleClick = (nDoubleClicks == 1);
1300 QCOMPARE(foundDoubleClick, expectedSynthesizedDoubleClickEvent);
1301
1302}
1303
1304void tst_qquickwindow::clearWindow()
1305{
1306 QQuickWindow *window = new QQuickWindow;
1307 window->setTitle(QTest::currentTestFunction());
1308 QQuickItem *item = new QQuickItem;
1309 item->setParentItem(window->contentItem());
1310
1311 QCOMPARE(item->window(), window);
1312
1313 delete window;
1314
1315 QVERIFY(!item->window());
1316
1317 delete item;
1318}
1319
1320void tst_qquickwindow::mouseFiltering()
1321{
1322 TestTouchItem::clearMouseEventCounters();
1323
1324 QQuickWindow *window = new QQuickWindow;
1325 QScopedPointer<QQuickWindow> cleanup(window);
1326 window->resize(w: 250, h: 250);
1327 window->setPosition(posx: 100, posy: 100);
1328 window->setTitle(QTest::currentTestFunction());
1329 window->show();
1330 QVERIFY(QTest::qWaitForWindowActive(window));
1331
1332 TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
1333 bottomItem->setObjectName("Bottom Item");
1334 bottomItem->setSize(QSizeF(150, 150));
1335
1336 TestTouchItem *siblingItem = new TestTouchItem(bottomItem);
1337 siblingItem->setObjectName("Sibling of Middle Item");
1338 siblingItem->setPosition(QPointF(90, 25));
1339 siblingItem->setSize(QSizeF(150, 150));
1340
1341 TestTouchItem *middleItem = new TestTouchItem(bottomItem);
1342 middleItem->setObjectName("Middle Item");
1343 middleItem->setPosition(QPointF(50, 50));
1344 middleItem->setSize(QSizeF(150, 150));
1345
1346 TestTouchItem *topItem = new TestTouchItem(middleItem);
1347 topItem->setObjectName("Top Item");
1348 topItem->setPosition(QPointF(50, 50));
1349 topItem->setSize(QSizeF(150, 150));
1350
1351 QPoint pos(100, 100);
1352
1353 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1354
1355 // Mouse filtering propagates down the stack, so the
1356 // correct order is
1357 // 1. middleItem filters event
1358 // 2. bottomItem filters event
1359 // 3. topItem receives event
1360 QTRY_COMPARE(middleItem->mousePressCount, 1);
1361 QTRY_COMPARE(bottomItem->mousePressCount, 2);
1362 QTRY_COMPARE(topItem->mousePressCount, 3);
1363 QCOMPARE(siblingItem->mousePressCount, 0);
1364
1365 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1366 topItem->clearMouseEventCounters();
1367 middleItem->clearMouseEventCounters();
1368 bottomItem->clearMouseEventCounters();
1369 siblingItem->clearMouseEventCounters();
1370
1371 // Repeat, but this time have the top item accept the press
1372 topItem->acceptMouseEvents = true;
1373
1374 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1375
1376 // Mouse filtering propagates down the stack, so the
1377 // correct order is
1378 // 1. middleItem filters event
1379 // 2. bottomItem filters event
1380 // 3. topItem receives event
1381 QTRY_COMPARE(middleItem->mousePressCount, 1);
1382 QTRY_COMPARE(bottomItem->mousePressCount, 2);
1383 QTRY_COMPARE(topItem->mousePressCount, 3);
1384 QCOMPARE(siblingItem->mousePressCount, 0);
1385
1386 pos += QPoint(50, 50);
1387 QTest::mouseMove(window, pos);
1388
1389 // The top item has grabbed, so the move goes there, but again
1390 // all the ancestors can filter, even when the mouse is outside their bounds
1391 QTRY_COMPARE(middleItem->mouseMoveCount, 1);
1392 QTRY_COMPARE(bottomItem->mouseMoveCount, 2);
1393 QTRY_COMPARE(topItem->mouseMoveCount, 3);
1394 QCOMPARE(siblingItem->mouseMoveCount, 0);
1395
1396 // clean up mouse press state for the next tests
1397 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
1398}
1399
1400void tst_qquickwindow::qmlCreation()
1401{
1402 QQmlEngine engine;
1403 QQmlComponent component(&engine);
1404 component.loadUrl(url: testFileUrl(fileName: "window.qml"));
1405 QObject *created = component.create();
1406 QScopedPointer<QObject> cleanup(created);
1407 QVERIFY(created);
1408
1409 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1410 QVERIFY(window);
1411 QCOMPARE(window->color(), QColor(Qt::green));
1412
1413 QQuickItem *item = window->findChild<QQuickItem*>(aName: "item");
1414 QVERIFY(item);
1415 QCOMPARE(item->window(), window);
1416}
1417
1418void tst_qquickwindow::qmlCreationWithScreen()
1419{
1420 QQmlEngine engine;
1421 QQmlComponent component(&engine);
1422 component.loadUrl(url: testFileUrl(fileName: "windowWithScreen.qml"));
1423 QObject *created = component.create();
1424 QScopedPointer<QObject> cleanup(created);
1425 QVERIFY(created);
1426
1427 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1428 QVERIFY(window);
1429 QCOMPARE(window->color(), QColor(Qt::green));
1430
1431 QQuickItem *item = window->findChild<QQuickItem*>(aName: "item");
1432 QVERIFY(item);
1433 QCOMPARE(item->window(), window);
1434}
1435
1436void tst_qquickwindow::clearColor()
1437{
1438 //::grab examines rendering to make sure it works visually
1439 QQuickWindow *window = new QQuickWindow;
1440 QScopedPointer<QQuickWindow> cleanup(window);
1441 window->resize(w: 250, h: 250);
1442 window->setPosition(posx: 100, posy: 100);
1443 window->setColor(Qt::blue);
1444 window->setTitle(QTest::currentTestFunction());
1445 window->show();
1446 QVERIFY(QTest::qWaitForWindowExposed(window));
1447 QCOMPARE(window->color(), QColor(Qt::blue));
1448}
1449
1450void tst_qquickwindow::defaultState()
1451{
1452 QQmlEngine engine;
1453 QQmlComponent component(&engine);
1454 component.setData("import QtQuick 2.0; import QtQuick.Window 2.1; Window { }", baseUrl: QUrl());
1455 QObject *created = component.create();
1456 QScopedPointer<QObject> cleanup(created);
1457 QVERIFY(created);
1458
1459 QQuickWindow *qmlWindow = qobject_cast<QQuickWindow*>(object: created);
1460 QVERIFY(qmlWindow);
1461 qmlWindow->setTitle(QTest::currentTestFunction());
1462
1463 QQuickWindow cppWindow;
1464 cppWindow.show();
1465 QVERIFY(QTest::qWaitForWindowExposed(&cppWindow));
1466
1467 QCOMPARE(qmlWindow->windowState(), cppWindow.windowState());
1468}
1469
1470void tst_qquickwindow::grab_data()
1471{
1472 QTest::addColumn<bool>(name: "visible");
1473 QTest::addColumn<bool>(name: "alpha");
1474 QTest::newRow(dataTag: "visible,opaque") << true << false;
1475 QTest::newRow(dataTag: "invisible,opaque") << false << false;
1476 QTest::newRow(dataTag: "visible,transparent") << true << true;
1477 QTest::newRow(dataTag: "invisible,transparent") << false << true;
1478}
1479
1480void tst_qquickwindow::grab()
1481{
1482 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1483 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1484 QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
1485
1486 QFETCH(bool, visible);
1487 QFETCH(bool, alpha);
1488
1489 QQuickWindow window;
1490 window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag()));
1491 if (alpha) {
1492 window.setColor(QColor(0, 0, 0, 0));
1493 } else {
1494 window.setColor(Qt::red);
1495 }
1496
1497 window.resize(w: 250, h: 250);
1498
1499 if (visible) {
1500 window.show();
1501 QVERIFY(QTest::qWaitForWindowExposed(&window));
1502 } else {
1503 window.create();
1504 }
1505
1506 QImage content = window.grabWindow();
1507 QCOMPARE(content.width(), int(window.width() * window.devicePixelRatio()));
1508 QCOMPARE(content.height(), int(window.height() * window.devicePixelRatio()));
1509
1510 if (alpha) {
1511 QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
1512 } else {
1513 QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
1514 }
1515}
1516
1517void tst_qquickwindow::multipleWindows()
1518{
1519 QList<QQuickWindow *> windows;
1520 QScopedPointer<QQuickWindow> cleanup[6];
1521
1522 for (int i=0; i<6; ++i) {
1523 QQuickWindow *c = new QQuickWindow();
1524 c->setTitle(QLatin1String(QTest::currentTestFunction()) + QString::number(i));
1525 c->setColor(Qt::GlobalColor(Qt::red + i));
1526 c->resize(w: 300, h: 200);
1527 c->setPosition(posx: 100 + i * 30, posy: 100 + i * 20);
1528 c->show();
1529 windows << c;
1530 cleanup[i].reset(other: c);
1531 QVERIFY(QTest::qWaitForWindowExposed(c));
1532 }
1533
1534 // move them
1535 for (int i=0; i<windows.size(); ++i) {
1536 QQuickWindow *c = windows.at(i);
1537 c->setPosition(posx: 100 + i * 30, posy: 100 + i * 20 + 100);
1538 }
1539
1540 // resize them
1541 for (int i=0; i<windows.size(); ++i) {
1542 QQuickWindow *c = windows.at(i);
1543 c->resize(w: 200, h: 150);
1544 }
1545}
1546
1547void tst_qquickwindow::animationsWhileHidden()
1548{
1549 QQmlEngine engine;
1550 QQmlComponent component(&engine);
1551 component.loadUrl(url: testFileUrl(fileName: "AnimationsWhileHidden.qml"));
1552 QObject *created = component.create();
1553 QScopedPointer<QObject> cleanup(created);
1554
1555 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1556 QVERIFY(window);
1557 window->setTitle(QTest::currentTestFunction());
1558 QVERIFY(window->isVisible());
1559
1560 // Now hide the window and verify that it went off screen
1561 window->hide();
1562 QTest::qWait(ms: 10);
1563 QVERIFY(!window->isVisible());
1564
1565 // Running animaiton should cause it to become visible again shortly.
1566 QTRY_VERIFY(window->isVisible());
1567}
1568
1569void tst_qquickwindow::headless()
1570{
1571 QQmlEngine engine;
1572 QQmlComponent component(&engine);
1573 component.loadUrl(url: testFileUrl(fileName: "Headless.qml"));
1574 QObject *created = component.create();
1575 QScopedPointer<QObject> cleanup(created);
1576
1577 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1578 window->setPersistentOpenGLContext(false);
1579 window->setPersistentSceneGraph(false);
1580 QVERIFY(window);
1581 window->setTitle(QTest::currentTestFunction());
1582 window->show();
1583
1584 QVERIFY(QTest::qWaitForWindowExposed(window));
1585 QVERIFY(window->isVisible());
1586 const bool threaded = QQuickWindowPrivate::get(c: window)->context->thread() != QThread::currentThread();
1587 QSignalSpy initialized(window, SIGNAL(sceneGraphInitialized()));
1588 QSignalSpy invalidated(window, SIGNAL(sceneGraphInvalidated()));
1589
1590 // Verify that the window is alive and kicking
1591 QVERIFY(window->isSceneGraphInitialized());
1592
1593 const bool isGL = window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL;
1594
1595 // Store the visual result
1596 QImage originalContent = window->grabWindow();
1597
1598 // Hide the window and verify signal emittion and GL context deletion
1599 window->hide();
1600 window->releaseResources();
1601
1602 if (threaded) {
1603 QTRY_VERIFY(invalidated.size() >= 1);
1604 if (isGL)
1605 QVERIFY(!window->isSceneGraphInitialized());
1606 }
1607 // Destroy the native windowing system buffers
1608 window->destroy();
1609 QVERIFY(!window->handle());
1610
1611 // Show and verify that we are back and running
1612 window->show();
1613 QVERIFY(QTest::qWaitForWindowExposed(window));
1614
1615 if (threaded)
1616 QTRY_COMPARE(initialized.size(), 1);
1617
1618 QVERIFY(window->isSceneGraphInitialized());
1619
1620 // Verify that the visual output is the same
1621 QImage newContent = window->grabWindow();
1622 QString errorMessage;
1623 QVERIFY2(QQuickVisualTestUtil::compareImages(newContent, originalContent, &errorMessage),
1624 qPrintable(errorMessage));
1625}
1626
1627void tst_qquickwindow::noUpdateWhenNothingChanges()
1628{
1629 QQuickWindow window;
1630 window.setTitle(QTest::currentTestFunction());
1631 window.setGeometry(posx: 100, posy: 100, w: 300, h: 200);
1632
1633 QQuickRectangle rect(window.contentItem());
1634
1635 window.showNormal();
1636 QVERIFY(QTest::qWaitForWindowExposed(&window));
1637 // Many platforms are broken in the sense that that they follow up
1638 // the initial expose with a second expose or more. Let these go
1639 // through before we let the test continue.
1640 QTest::qWait(ms: 100);
1641 if (QQuickWindowPrivate::get(c: &window)->context->thread() == QGuiApplication::instance()->thread()) {
1642 QSKIP("Only threaded renderloop implements this feature");
1643 return;
1644 }
1645
1646 QSignalSpy spy(&window, SIGNAL(frameSwapped()));
1647 rect.update();
1648 // Wait a while and verify that no more frameSwapped come our way.
1649 QTest::qWait(ms: 100);
1650
1651 QCOMPARE(spy.size(), 0);
1652}
1653
1654void tst_qquickwindow::focusObject()
1655{
1656 QQmlEngine engine;
1657 QQmlComponent component(&engine);
1658 component.loadUrl(url: testFileUrl(fileName: "focus.qml"));
1659 QObject *created = component.create();
1660 QScopedPointer<QObject> cleanup(created);
1661
1662 QVERIFY(created);
1663
1664 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1665 QVERIFY(window);
1666 window->setTitle(QTest::currentTestFunction());
1667
1668 QSignalSpy focusObjectSpy(window, SIGNAL(focusObjectChanged(QObject*)));
1669
1670 window->show();
1671 QVERIFY(QTest::qWaitForWindowExposed(window));
1672 window->requestActivate();
1673 QVERIFY(QTest::qWaitForWindowActive(window));
1674
1675 QCOMPARE(window->contentItem(), window->focusObject());
1676 QCOMPARE(focusObjectSpy.count(), 1);
1677
1678 QQuickItem *item1 = window->findChild<QQuickItem*>(aName: "item1");
1679 QVERIFY(item1);
1680 item1->setFocus(true);
1681 QCOMPARE(item1, window->focusObject());
1682 QCOMPARE(focusObjectSpy.count(), 2);
1683
1684 QQuickItem *item2 = window->findChild<QQuickItem*>(aName: "item2");
1685 QVERIFY(item2);
1686 item2->setFocus(true);
1687 QCOMPARE(item2, window->focusObject());
1688 QCOMPARE(focusObjectSpy.count(), 3);
1689
1690 // set focus for item in non-focused focus scope and
1691 // ensure focusObject does not change and signal is not emitted
1692 QQuickItem *item3 = window->findChild<QQuickItem*>(aName: "item3");
1693 QVERIFY(item3);
1694 item3->setFocus(true);
1695 QCOMPARE(item2, window->focusObject());
1696 QCOMPARE(focusObjectSpy.count(), 3);
1697}
1698
1699void tst_qquickwindow::focusReason()
1700{
1701 QQuickWindow *window = new QQuickWindow;
1702 QScopedPointer<QQuickWindow> cleanup(window);
1703 window->resize(w: 200, h: 200);
1704 window->show();
1705 window->setTitle(QTest::currentTestFunction());
1706 QVERIFY(QTest::qWaitForWindowExposed(window));
1707
1708 QScopedPointer<QQuickItem> firstItem(new QQuickItem);
1709 firstItem->setSize(QSizeF(100, 100));
1710 firstItem->setParentItem(window->contentItem());
1711
1712 QScopedPointer<QQuickItem> secondItem(new QQuickItem);
1713 secondItem->setSize(QSizeF(100, 100));
1714 secondItem->setParentItem(window->contentItem());
1715
1716 firstItem->forceActiveFocus(reason: Qt::OtherFocusReason);
1717 QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::OtherFocusReason);
1718
1719 secondItem->forceActiveFocus(reason: Qt::TabFocusReason);
1720 QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::TabFocusReason);
1721
1722 firstItem->forceActiveFocus(reason: Qt::BacktabFocusReason);
1723 QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::BacktabFocusReason);
1724
1725}
1726
1727void tst_qquickwindow::ignoreUnhandledMouseEvents()
1728{
1729 QQuickWindow *window = new QQuickWindow;
1730 QScopedPointer<QQuickWindow> cleanup(window);
1731 window->setTitle(QTest::currentTestFunction());
1732 window->resize(w: 100, h: 100);
1733 window->show();
1734 QVERIFY(QTest::qWaitForWindowExposed(window));
1735
1736 QScopedPointer<QQuickItem> item(new QQuickItem);
1737 item->setSize(QSizeF(100, 100));
1738 item->setParentItem(window->contentItem());
1739
1740 {
1741 QMouseEvent me(QEvent::MouseButtonPress, QPointF(50, 50), Qt::LeftButton, Qt::LeftButton,
1742 Qt::NoModifier);
1743 me.setAccepted(true);
1744 QVERIFY(QCoreApplication::sendEvent(window, &me));
1745 QVERIFY(!me.isAccepted());
1746 }
1747
1748 {
1749 QMouseEvent me(QEvent::MouseMove, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton,
1750 Qt::NoModifier);
1751 me.setAccepted(true);
1752 QVERIFY(QCoreApplication::sendEvent(window, &me));
1753 QVERIFY(!me.isAccepted());
1754 }
1755
1756 {
1757 QMouseEvent me(QEvent::MouseButtonRelease, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton,
1758 Qt::NoModifier);
1759 me.setAccepted(true);
1760 QVERIFY(QCoreApplication::sendEvent(window, &me));
1761 QVERIFY(!me.isAccepted());
1762 }
1763}
1764
1765
1766void tst_qquickwindow::ownershipRootItem()
1767{
1768 qmlRegisterType<RootItemAccessor>(uri: "Test", versionMajor: 1, versionMinor: 0, qmlName: "RootItemAccessor");
1769
1770 QQmlEngine engine;
1771 QQmlComponent component(&engine);
1772 component.loadUrl(url: testFileUrl(fileName: "ownershipRootItem.qml"));
1773 QObject *created = component.create();
1774 QScopedPointer<QObject> cleanup(created);
1775
1776 QQuickWindow *window = qobject_cast<QQuickWindow*>(object: created);
1777 QVERIFY(window);
1778 window->setTitle(QTest::currentTestFunction());
1779 window->show();
1780 QVERIFY(QTest::qWaitForWindowExposed(window));
1781
1782 RootItemAccessor* accessor = window->findChild<RootItemAccessor*>(aName: "accessor");
1783 QVERIFY(accessor);
1784 engine.collectGarbage();
1785
1786 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1787 QCoreApplication::processEvents();
1788 QVERIFY(!accessor->isRootItemDestroyed());
1789}
1790
1791#if QT_CONFIG(cursor)
1792void tst_qquickwindow::cursor()
1793{
1794 QQuickWindow window;
1795 window.setTitle(QTest::currentTestFunction());
1796 window.setFramePosition(QGuiApplication::primaryScreen()->availableGeometry().topLeft() + QPoint(50, 50));
1797 window.resize(w: 320, h: 290);
1798
1799 QQuickItem parentItem;
1800 parentItem.setObjectName("parentItem");
1801 parentItem.setPosition(QPointF(0, 0));
1802 parentItem.setSize(QSizeF(180, 180));
1803 parentItem.setParentItem(window.contentItem());
1804
1805 QQuickItem childItem;
1806 childItem.setObjectName("childItem");
1807 childItem.setPosition(QPointF(60, 90));
1808 childItem.setSize(QSizeF(120, 120));
1809 childItem.setParentItem(&parentItem);
1810
1811 QQuickItem clippingItem;
1812 clippingItem.setObjectName("clippingItem");
1813 clippingItem.setPosition(QPointF(120, 120));
1814 clippingItem.setSize(QSizeF(180, 180));
1815 clippingItem.setClip(true);
1816 clippingItem.setParentItem(window.contentItem());
1817
1818 QQuickItem clippedItem;
1819 clippedItem.setObjectName("clippedItem");
1820 clippedItem.setPosition(QPointF(-30, -30));
1821 clippedItem.setSize(QSizeF(120, 120));
1822 clippedItem.setParentItem(&clippingItem);
1823
1824 window.show();
1825 QVERIFY(QTest::qWaitForWindowActive(&window));
1826
1827 // Position the cursor over the parent and child item and the clipped section of clippedItem.
1828 QTest::mouseMove(window: &window, pos: QPoint(100, 100));
1829
1830 // No items cursors, window cursor is the default arrow.
1831 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1832
1833 // The section of clippedItem under the cursor is clipped, and so doesn't affect the window cursor.
1834 clippedItem.setCursor(Qt::ForbiddenCursor);
1835 QCOMPARE(clippedItem.cursor().shape(), Qt::ForbiddenCursor);
1836 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1837
1838 // parentItem is under the cursor, so the window cursor is changed.
1839 parentItem.setCursor(Qt::IBeamCursor);
1840 QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor);
1841 QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
1842
1843 // childItem is under the cursor and is in front of its parent, so the window cursor is changed.
1844 childItem.setCursor(Qt::WaitCursor);
1845 QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor);
1846 QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
1847
1848 childItem.setCursor(Qt::PointingHandCursor);
1849 QCOMPARE(childItem.cursor().shape(), Qt::PointingHandCursor);
1850 QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
1851
1852 // childItem is the current cursor item, so this has no effect on the window cursor.
1853 parentItem.unsetCursor();
1854 QCOMPARE(parentItem.cursor().shape(), Qt::ArrowCursor);
1855 QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
1856
1857 parentItem.setCursor(Qt::IBeamCursor);
1858 QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor);
1859 QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
1860
1861 // With the childItem cursor cleared, parentItem is now foremost.
1862 childItem.unsetCursor();
1863 QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
1864 QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
1865
1866 // Setting the childItem cursor to the default still takes precedence over parentItem.
1867 childItem.setCursor(Qt::ArrowCursor);
1868 QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
1869 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1870
1871 childItem.setCursor(Qt::WaitCursor);
1872 QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor);
1873 QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
1874
1875 // Move the cursor so it is over just parentItem.
1876 QTest::mouseMove(window: &window, pos: QPoint(20, 20));
1877 QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
1878
1879 // Move the cursor so that is over all items, clippedItem wins because its a child of
1880 // clippingItem which is in from of parentItem in painting order.
1881 QTest::mouseMove(window: &window, pos: QPoint(125, 125));
1882 QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor);
1883
1884 // Over clippingItem only, so no cursor.
1885 QTest::mouseMove(window: &window, pos: QPoint(200, 280));
1886 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1887
1888 // Over no item, so no cursor.
1889 QTest::mouseMove(window: &window, pos: QPoint(10, 280));
1890 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1891
1892 // back to the start.
1893 QTest::mouseMove(window: &window, pos: QPoint(100, 100));
1894 QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
1895
1896 // Try with the mouse pressed.
1897 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(100, 100));
1898 QTest::mouseMove(window: &window, pos: QPoint(20, 20));
1899 QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
1900 QTest::mouseMove(window: &window, pos: QPoint(125, 125));
1901 QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor);
1902 QTest::mouseMove(window: &window, pos: QPoint(200, 280));
1903 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1904 QTest::mouseMove(window: &window, pos: QPoint(10, 280));
1905 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1906 QTest::mouseMove(window: &window, pos: QPoint(100, 100));
1907 QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
1908 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(100, 100));
1909
1910 // Remove the cursor item from the scene. Theoretically this should make parentItem the
1911 // cursorItem, but given the situation will correct itself after the next mouse move it
1912 // simply unsets the window cursor for now.
1913 childItem.setParentItem(nullptr);
1914 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1915
1916 parentItem.setCursor(Qt::SizeAllCursor);
1917 QCOMPARE(parentItem.cursor().shape(), Qt::SizeAllCursor);
1918 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1919
1920 // Changing the cursor of an un-parented item doesn't affect the window's cursor.
1921 childItem.setCursor(Qt::ClosedHandCursor);
1922 QCOMPARE(childItem.cursor().shape(), Qt::ClosedHandCursor);
1923 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1924
1925 childItem.unsetCursor();
1926 QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
1927 QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
1928
1929 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(100, 101));
1930 QCOMPARE(window.cursor().shape(), Qt::SizeAllCursor);
1931}
1932#endif
1933
1934void tst_qquickwindow::hideThenDelete_data()
1935{
1936 QTest::addColumn<bool>(name: "persistentSG");
1937 QTest::addColumn<bool>(name: "persistentGL");
1938
1939 QTest::newRow(dataTag: "persistent:SG=false,GL=false") << false << false;
1940 QTest::newRow(dataTag: "persistent:SG=true,GL=false") << true << false;
1941 QTest::newRow(dataTag: "persistent:SG=false,GL=true") << false << true;
1942 QTest::newRow(dataTag: "persistent:SG=true,GL=true") << true << true;
1943}
1944
1945void tst_qquickwindow::hideThenDelete()
1946{
1947 QFETCH(bool, persistentSG);
1948 QFETCH(bool, persistentGL);
1949
1950 QScopedPointer<QSignalSpy> openglDestroyed;
1951 QScopedPointer<QSignalSpy> sgInvalidated;
1952
1953 {
1954 QQuickWindow window;
1955 window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ')
1956 + QLatin1String(QTest::currentDataTag()));
1957 window.setColor(Qt::red);
1958
1959 window.setPersistentSceneGraph(persistentSG);
1960 window.setPersistentOpenGLContext(persistentGL);
1961
1962 window.resize(w: 400, h: 300);
1963 window.show();
1964
1965 QVERIFY(QTest::qWaitForWindowExposed(&window));
1966 const bool threaded = QQuickWindowPrivate::get(c: &window)->context->thread() != QGuiApplication::instance()->thread();
1967 const bool isGL = window.rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL;
1968#if QT_CONFIG(opengl)
1969 if (isGL)
1970 openglDestroyed.reset(other: new QSignalSpy(window.openglContext(), SIGNAL(aboutToBeDestroyed())));
1971#endif
1972
1973 sgInvalidated.reset(other: new QSignalSpy(&window, SIGNAL(sceneGraphInvalidated())));
1974
1975 window.hide();
1976
1977 QTRY_VERIFY(!window.isExposed());
1978
1979 if (threaded) {
1980 if (!isGL)
1981 QSKIP("Skipping persistency verification due to not running with OpenGL");
1982
1983 if (!persistentSG) {
1984 QVERIFY(sgInvalidated->size() > 0);
1985 if (!persistentGL)
1986 QVERIFY(openglDestroyed->size() > 0);
1987 else
1988 QCOMPARE(openglDestroyed->size(), 0);
1989 } else {
1990 QCOMPARE(sgInvalidated->size(), 0);
1991 QCOMPARE(openglDestroyed->size(), 0);
1992 }
1993 }
1994 }
1995
1996 QVERIFY(sgInvalidated->size() > 0);
1997#if QT_CONFIG(opengl)
1998 if (openglDestroyed)
1999 QVERIFY(openglDestroyed->size() > 0);
2000#endif
2001}
2002
2003void tst_qquickwindow::showHideAnimate()
2004{
2005 // This test tries to mimick a bug triggered in the qquickanimatedimage test
2006 // A window is shown, then removed again before it is exposed. This left
2007 // traces in the render loop which prevent other animations from running
2008 // later on.
2009 {
2010 QQuickWindow window;
2011 window.resize(w: 400, h: 300);
2012 window.show();
2013 }
2014
2015 QQmlEngine engine;
2016 QQmlComponent component(&engine);
2017 component.loadUrl(url: testFileUrl(fileName: "showHideAnimate.qml"));
2018 QScopedPointer<QQuickItem> created(qobject_cast<QQuickItem *>(object: component.create()));
2019
2020 QVERIFY(created);
2021
2022 QTRY_VERIFY(created->opacity() > 0.5);
2023 QTRY_VERIFY(created->opacity() < 0.5);
2024}
2025
2026void tst_qquickwindow::testExpose()
2027{
2028 QQuickWindow window;
2029 window.setTitle(QTest::currentTestFunction());
2030 window.setGeometry(posx: 100, posy: 100, w: 300, h: 200);
2031
2032 window.show();
2033 QTRY_VERIFY(window.isExposed());
2034
2035 QSignalSpy swapSpy(&window, SIGNAL(frameSwapped()));
2036
2037 // exhaust pending exposes, as some platforms send us plenty
2038 // while showing the first time
2039 QTest::qWait(ms: 1000);
2040 while (swapSpy.size() != 0) {
2041 swapSpy.clear();
2042 QTest::qWait(ms: 100);
2043 }
2044
2045 QWindowSystemInterface::handleExposeEvent(window: &window, region: QRegion(10, 10, 20, 20));
2046 QTRY_COMPARE(swapSpy.size(), 1);
2047}
2048
2049void tst_qquickwindow::requestActivate()
2050{
2051 QQmlEngine engine;
2052 QQmlComponent component(&engine);
2053 component.loadUrl(url: testFileUrl(fileName: "active.qml"));
2054 QScopedPointer<QQuickWindow> window1(qobject_cast<QQuickWindow *>(object: component.create()));
2055 QVERIFY(!window1.isNull());
2056 window1->setTitle(QTest::currentTestFunction());
2057
2058 QWindowList windows = QGuiApplication::topLevelWindows();
2059 QCOMPARE(windows.size(), 2);
2060
2061 for (int i = 0; i < windows.size(); ++i) {
2062 if (windows.at(i)->objectName() == window1->objectName()) {
2063 windows.removeAt(i);
2064 break;
2065 }
2066 }
2067 QCOMPARE(windows.size(), 1);
2068 QCOMPARE(windows.at(0)->objectName(), QLatin1String("window2"));
2069
2070 window1->show();
2071 QVERIFY(QTest::qWaitForWindowExposed(windows.at(0))); //We wait till window 2 comes up
2072 window1->requestActivate(); // and then transfer the focus to window1
2073
2074 QTRY_COMPARE(QGuiApplication::focusWindow(), window1.data());
2075 QVERIFY(window1->isActive());
2076
2077 QQuickItem *item = QQuickVisualTestUtil::findItem<QQuickItem>(parent: window1->contentItem(), objectName: "item1");
2078 QVERIFY(item);
2079
2080 //copied from src/qmltest/quicktestevent.cpp
2081 QPoint pos = item->mapToScene(point: QPointF(item->width()/2, item->height()/2)).toPoint();
2082
2083 QMouseEvent me(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
2084 QSpontaneKeyEvent::setSpontaneous(&me);
2085 if (!qApp->notify(window1.data(), &me)) {
2086 QString warning = QString::fromLatin1(str: "Mouse event MousePress not accepted by receiving window");
2087 QWARN(warning.toLatin1().data());
2088 }
2089 me = QMouseEvent(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, {}, Qt::NoModifier);
2090 QSpontaneKeyEvent::setSpontaneous(&me);
2091 if (!qApp->notify(window1.data(), &me)) {
2092 QString warning = QString::fromLatin1(str: "Mouse event MouseRelease not accepted by receiving window");
2093 QWARN(warning.toLatin1().data());
2094 }
2095
2096 QTRY_COMPARE(QGuiApplication::focusWindow(), windows.at(0));
2097 QVERIFY(windows.at(0)->isActive());
2098}
2099
2100void tst_qquickwindow::testWindowVisibilityOrder()
2101{
2102 QQmlEngine engine;
2103 QQmlComponent component(&engine);
2104 component.loadUrl(url: testFileUrl(fileName: "windoworder.qml"));
2105 QScopedPointer<QQuickWindow> window1(qobject_cast<QQuickWindow *>(object: component.create()));
2106 QVERIFY(!window1.isNull());
2107 window1->setTitle(QTest::currentTestFunction());
2108 QQuickWindow *window2 = window1->property(name: "win2").value<QQuickWindow*>();
2109 QQuickWindow *window3 = window1->property(name: "win3").value<QQuickWindow*>();
2110 QQuickWindow *window4 = window1->property(name: "win4").value<QQuickWindow*>();
2111 QQuickWindow *window5 = window1->property(name: "win5").value<QQuickWindow*>();
2112 QVERIFY(window2);
2113 QVERIFY(window3);
2114
2115 QVERIFY(QTest::qWaitForWindowExposed(window3));
2116
2117 QWindowList windows = QGuiApplication::topLevelWindows();
2118 QTRY_COMPARE(windows.size(), 5);
2119
2120 if (qgetenv(varName: "XDG_CURRENT_DESKTOP") == "Unity" && QGuiApplication::focusWindow() != window3) {
2121 qDebug() << "Unity (flaky QTBUG-62604): expected window3 to have focus; actual focusWindow:"
2122 << QGuiApplication::focusWindow();
2123 } else {
2124 QCOMPARE(window3, QGuiApplication::focusWindow());
2125 QVERIFY(window1->isActive());
2126 QVERIFY(window2->isActive());
2127 QVERIFY(window3->isActive());
2128 }
2129
2130 //Test if window4 is shown 2 seconds after the application startup
2131 //with window4 visible window5 (transient child) should also become visible
2132 QVERIFY(!window4->isVisible());
2133 QVERIFY(!window5->isVisible());
2134
2135 window4->setVisible(true);
2136
2137 QVERIFY(QTest::qWaitForWindowExposed(window5));
2138 QVERIFY(window4->isVisible());
2139 QVERIFY(window5->isVisible());
2140}
2141
2142void tst_qquickwindow::blockClosing()
2143{
2144 QQmlEngine engine;
2145 QQmlComponent component(&engine);
2146 component.loadUrl(url: testFileUrl(fileName: "ucantclosethis.qml"));
2147 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
2148 QVERIFY(!window.isNull());
2149 window->setTitle(QTest::currentTestFunction());
2150 window->show();
2151 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2152 QVERIFY(window->isVisible());
2153 QWindowSystemInterface::handleCloseEvent(window: window.data());
2154 QVERIFY(window->isVisible());
2155 QWindowSystemInterface::handleCloseEvent(window: window.data());
2156 QVERIFY(window->isVisible());
2157 window->setProperty(name: "canCloseThis", value: true);
2158 QWindowSystemInterface::handleCloseEvent(window: window.data());
2159 QTRY_VERIFY(!window->isVisible());
2160}
2161
2162void tst_qquickwindow::blockCloseMethod()
2163{
2164 QQmlEngine engine;
2165 QQmlComponent component(&engine);
2166 component.loadUrl(url: testFileUrl(fileName: "ucantclosethis.qml"));
2167 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
2168 QVERIFY(!window.isNull());
2169 window->setTitle(QTest::currentTestFunction());
2170 window->show();
2171 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2172 QVERIFY(window->isVisible());
2173 QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
2174 QVERIFY(window->isVisible());
2175 QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
2176 QVERIFY(window->isVisible());
2177 window->setProperty(name: "canCloseThis", value: true);
2178 QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
2179 QTRY_VERIFY(!window->isVisible());
2180}
2181
2182void tst_qquickwindow::crashWhenHoverItemDeleted()
2183{
2184 // QTBUG-32771
2185 QQmlEngine engine;
2186 QQmlComponent component(&engine);
2187 component.loadUrl(url: testFileUrl(fileName: "hoverCrash.qml"));
2188 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
2189 QVERIFY(!window.isNull());
2190 window->setTitle(QTest::currentTestFunction());
2191 window->show();
2192 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2193
2194 // Simulate a move from the first rectangle to the second. Crash will happen in here
2195 // Moving instantaneously from (0, 99) to (0, 102) does not cause the crash
2196 for (int i = 99; i < 102; ++i) {
2197 QTest::mouseMove(window: window.data(), pos: QPoint(0, i));
2198 }
2199}
2200
2201// QTBUG-33436
2202void tst_qquickwindow::unloadSubWindow()
2203{
2204 QQmlEngine engine;
2205 QQmlComponent component(&engine);
2206 component.loadUrl(url: testFileUrl(fileName: "unloadSubWindow.qml"));
2207 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
2208 QVERIFY(!window.isNull());
2209 window->setTitle(QTest::currentTestFunction());
2210 window->show();
2211 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2212 QPointer<QQuickWindow> transient;
2213 QTRY_VERIFY(transient = window->property("transientWindow").value<QQuickWindow*>());
2214 QVERIFY(QTest::qWaitForWindowExposed(transient));
2215
2216 // Unload the inner window (in nested Loaders) and make sure it doesn't crash
2217 QQuickLoader *loader = window->property(name: "loader1").value<QQuickLoader*>();
2218 loader->setActive(false);
2219 QTRY_VERIFY(transient.isNull() || !transient->isVisible());
2220}
2221
2222// QTBUG-52573
2223void tst_qquickwindow::changeVisibilityInCompleted()
2224{
2225 QQmlEngine engine;
2226 QQmlComponent component(&engine);
2227 component.loadUrl(url: testFileUrl(fileName: "changeVisibilityInCompleted.qml"));
2228 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
2229 QVERIFY(!window.isNull());
2230 window->setTitle(QTest::currentTestFunction());
2231 window->show();
2232 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2233 QPointer<QQuickWindow> winVisible;
2234 QTRY_VERIFY(winVisible = window->property("winVisible").value<QQuickWindow*>());
2235 QPointer<QQuickWindow> winVisibility;
2236 QTRY_VERIFY(winVisibility = window->property("winVisibility").value<QQuickWindow*>());
2237 QVERIFY(QTest::qWaitForWindowExposed(winVisible));
2238 QVERIFY(QTest::qWaitForWindowExposed(winVisibility));
2239
2240 QVERIFY(winVisible->isVisible());
2241 QCOMPARE(winVisibility->visibility(), QWindow::Windowed);
2242}
2243
2244// QTBUG-32004
2245void tst_qquickwindow::qobjectEventFilter_touch()
2246{
2247 QQuickWindow window;
2248
2249 window.resize(w: 250, h: 250);
2250 window.setPosition(posx: 100, posy: 100);
2251 window.setTitle(QTest::currentTestFunction());
2252 window.show();
2253 QVERIFY(QTest::qWaitForWindowActive(&window));
2254
2255 TestTouchItem *item = new TestTouchItem(window.contentItem());
2256 item->setSize(QSizeF(150, 150));
2257
2258 EventFilter eventFilter;
2259 item->installEventFilter(filterObj: &eventFilter);
2260
2261 QPointF pos(10, 10);
2262
2263 // press single point
2264 QTest::touchEvent(window: &window, device: touchDevice).press(touchId: 0, pt: item->mapToScene(point: pos).toPoint(), window: &window);
2265
2266 QCOMPARE(eventFilter.events.count(), 1);
2267 QCOMPARE(eventFilter.events.first(), (int)QEvent::TouchBegin);
2268}
2269
2270// QTBUG-32004
2271void tst_qquickwindow::qobjectEventFilter_key()
2272{
2273 QQuickWindow window;
2274
2275 window.resize(w: 250, h: 250);
2276 window.setPosition(posx: 100, posy: 100);
2277 window.setTitle(QTest::currentTestFunction());
2278 window.show();
2279 QVERIFY(QTest::qWaitForWindowActive(&window));
2280
2281 TestTouchItem *item = new TestTouchItem(window.contentItem());
2282 item->setSize(QSizeF(150, 150));
2283 item->setFocus(true);
2284
2285 EventFilter eventFilter;
2286 item->installEventFilter(filterObj: &eventFilter);
2287
2288 QTest::keyPress(window: &window, key: Qt::Key_A);
2289
2290 // NB: It may also receive some QKeyEvent(ShortcutOverride) which we're not interested in
2291 QVERIFY(eventFilter.events.contains((int)QEvent::KeyPress));
2292 eventFilter.events.clear();
2293
2294 QTest::keyRelease(window: &window, key: Qt::Key_A);
2295
2296 QVERIFY(eventFilter.events.contains((int)QEvent::KeyRelease));
2297}
2298
2299// QTBUG-32004
2300void tst_qquickwindow::qobjectEventFilter_mouse()
2301{
2302 QQuickWindow window;
2303
2304 window.resize(w: 250, h: 250);
2305 window.setPosition(posx: 100, posy: 100);
2306 window.setTitle(QTest::currentTestFunction());
2307 window.show();
2308 QVERIFY(QTest::qWaitForWindowActive(&window));
2309
2310 TestTouchItem *item = new TestTouchItem(window.contentItem());
2311 item->setSize(QSizeF(150, 150));
2312
2313 EventFilter eventFilter;
2314 item->installEventFilter(filterObj: &eventFilter);
2315
2316 QPoint point = item->mapToScene(point: QPointF(10, 10)).toPoint();
2317 QTest::mouseMove(window: &window, pos: point);
2318 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: point);
2319
2320 QVERIFY(eventFilter.events.contains((int)QEvent::MouseButtonPress));
2321
2322 // clean up mouse press state for the next tests
2323 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: point);
2324}
2325
2326void tst_qquickwindow::animatingSignal()
2327{
2328 QQuickWindow window;
2329 window.setTitle(QTest::currentTestFunction());
2330 window.setGeometry(posx: 100, posy: 100, w: 300, h: 200);
2331
2332 QSignalSpy spy(&window, SIGNAL(afterAnimating()));
2333
2334 window.show();
2335 QTRY_VERIFY(window.isExposed());
2336
2337 QTRY_VERIFY(spy.count() > 1);
2338}
2339
2340// QTBUG-36938
2341void tst_qquickwindow::contentItemSize()
2342{
2343 QQuickWindow window;
2344 window.setTitle(QTest::currentTestFunction());
2345 QQuickItem *contentItem = window.contentItem();
2346 QVERIFY(contentItem);
2347 QCOMPARE(QSize(contentItem->width(), contentItem->height()), window.size());
2348
2349 QSizeF size(300, 200);
2350 window.resize(newSize: size.toSize());
2351 window.show();
2352 QVERIFY(QTest::qWaitForWindowExposed(&window));
2353
2354 QCOMPARE(window.size(), size.toSize());
2355 QCOMPARE(QSizeF(contentItem->width(), contentItem->height()), size);
2356
2357 QQmlEngine engine;
2358 QQmlComponent component(&engine);
2359 component.setData(QByteArray("import QtQuick 2.1\n Rectangle { anchors.fill: parent }"), baseUrl: QUrl());
2360 QScopedPointer<QQuickItem> rect(qobject_cast<QQuickItem *>(object: component.create()));
2361 QVERIFY(rect);
2362 rect->setParentItem(window.contentItem());
2363 QCOMPARE(QSizeF(rect->width(), rect->height()), size);
2364
2365 size.transpose();
2366 window.resize(newSize: size.toSize());
2367 QCOMPARE(window.size(), size.toSize());
2368 // wait for resize event
2369 QTRY_COMPARE(QSizeF(contentItem->width(), contentItem->height()), size);
2370 QCOMPARE(QSizeF(rect->width(), rect->height()), size);
2371}
2372
2373void tst_qquickwindow::defaultSurfaceFormat()
2374{
2375 // It is quite difficult to verify anything for real since the resulting format after
2376 // surface/context creation can be anything, depending on the platform and drivers,
2377 // and many options and settings may fail in various configurations, but test at
2378 // least using some harmless settings to check that the global, static format is
2379 // taken into account in the requested format.
2380
2381 QSurfaceFormat savedDefaultFormat = QSurfaceFormat::defaultFormat();
2382
2383 // Verify that depth and stencil are set, as they should be, unless they are disabled
2384 // via environment variables.
2385
2386 QSurfaceFormat format = savedDefaultFormat;
2387 format.setSwapInterval(0);
2388 format.setRedBufferSize(8);
2389 format.setGreenBufferSize(8);
2390 format.setBlueBufferSize(8);
2391 format.setProfile(QSurfaceFormat::CompatibilityProfile);
2392 format.setOption(option: QSurfaceFormat::DebugContext);
2393 // Will not set depth and stencil. That should be added automatically,
2394 // unless the are disabled (but they aren't).
2395 QSurfaceFormat::setDefaultFormat(format);
2396
2397 QQuickWindow window;
2398 window.setTitle(QTest::currentTestFunction());
2399 window.show();
2400 QVERIFY(QTest::qWaitForWindowExposed(&window));
2401
2402 if (window.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL)
2403 QSKIP("Skipping OpenGL context test due to not running with OpenGL");
2404
2405 const QSurfaceFormat reqFmt = window.requestedFormat();
2406 QCOMPARE(format.swapInterval(), reqFmt.swapInterval());
2407 QCOMPARE(format.redBufferSize(), reqFmt.redBufferSize());
2408 QCOMPARE(format.greenBufferSize(), reqFmt.greenBufferSize());
2409 QCOMPARE(format.blueBufferSize(), reqFmt.blueBufferSize());
2410 QCOMPARE(format.profile(), reqFmt.profile());
2411 QCOMPARE(int(format.options()), int(reqFmt.options()));
2412
2413#if QT_CONFIG(opengl)
2414 // Depth and stencil should be >= what has been requested. For real. But use
2415 // the context since the window's surface format is only partially updated
2416 // on most platforms.
2417 const QOpenGLContext *openglContext = nullptr;
2418 QTRY_VERIFY((openglContext = window.openglContext()) != nullptr);
2419 QVERIFY(openglContext->format().depthBufferSize() >= 16);
2420 QVERIFY(openglContext->format().stencilBufferSize() >= 8);
2421#endif
2422 QSurfaceFormat::setDefaultFormat(savedDefaultFormat);
2423}
2424
2425void tst_qquickwindow::attachedProperty()
2426{
2427 QQuickView view(testFileUrl(fileName: "windowattached.qml"));
2428 view.setTitle(QTest::currentTestFunction());
2429 view.show();
2430 view.requestActivate();
2431 QVERIFY(QTest::qWaitForWindowActive(&view));
2432 QVERIFY(view.rootObject()->property("windowActive").toBool());
2433 QCOMPARE(view.rootObject()->property("contentItem").value<QQuickItem*>(), view.contentItem());
2434 QCOMPARE(view.rootObject()->property("windowWidth").toInt(), view.width());
2435 QCOMPARE(view.rootObject()->property("windowHeight").toInt(), view.height());
2436 QCOMPARE(view.rootObject()->property("window").value<QQuickView*>(), &view);
2437
2438 QQuickWindow *innerWindow = view.rootObject()->findChild<QQuickWindow*>(aName: "extraWindow");
2439 QVERIFY(innerWindow);
2440 innerWindow->show();
2441 innerWindow->requestActivate();
2442 QVERIFY(QTest::qWaitForWindowActive(innerWindow));
2443
2444 QQuickText *text = view.rootObject()->findChild<QQuickText*>(aName: "extraWindowText");
2445 QVERIFY(text);
2446 QCOMPARE(text->text(), QLatin1String("active\nvisibility: 2"));
2447 QCOMPARE(text->property("contentItem").value<QQuickItem*>(), innerWindow->contentItem());
2448 QCOMPARE(text->property("windowWidth").toInt(), innerWindow->width());
2449 QCOMPARE(text->property("windowHeight").toInt(), innerWindow->height());
2450 QCOMPARE(text->property("window").value<QQuickWindow*>(), innerWindow);
2451
2452 text->setParentItem(nullptr);
2453 QVERIFY(!text->property("contentItem").value<QQuickItem*>());
2454 QCOMPARE(text->property("windowWidth").toInt(), 0);
2455 QCOMPARE(text->property("windowHeight").toInt(), 0);
2456 QVERIFY(!text->property("window").value<QQuickWindow*>());
2457}
2458
2459class RenderJob : public QRunnable
2460{
2461public:
2462 RenderJob(QQuickWindow::RenderStage s, QList<QQuickWindow::RenderStage> *l) : stage(s), list(l) { }
2463 ~RenderJob() { ++deleted; }
2464 QQuickWindow::RenderStage stage;
2465 QList<QQuickWindow::RenderStage> *list;
2466 void run() {
2467 list->append(t: stage);
2468 }
2469 static int deleted;
2470};
2471#if QT_CONFIG(opengl)
2472class GlRenderJob : public QRunnable
2473{
2474public:
2475 GlRenderJob(GLubyte *buf) : readPixel(buf), mutex(nullptr), condition(nullptr) {}
2476 ~GlRenderJob() {}
2477 void run() {
2478 QOpenGLContext::currentContext()->functions()->glClearColor(red: 1.0f, green: 0, blue: 0, alpha: 1.0f);
2479 QOpenGLContext::currentContext()->functions()->glClear(GL_COLOR_BUFFER_BIT);
2480 QOpenGLContext::currentContext()->functions()->glReadPixels(x: 0, y: 0, width: 1, height: 1, GL_RGBA,
2481 GL_UNSIGNED_BYTE,
2482 pixels: (void *)readPixel);
2483 if (mutex) {
2484 mutex->lock();
2485 condition->wakeOne();
2486 mutex->unlock();
2487 }
2488 }
2489 GLubyte *readPixel;
2490 QMutex *mutex;
2491 QWaitCondition *condition;
2492};
2493#endif
2494int RenderJob::deleted = 0;
2495
2496void tst_qquickwindow::testRenderJob()
2497{
2498 QList<QQuickWindow::RenderStage> completedJobs;
2499
2500 QQuickWindow::RenderStage stages[] = {
2501 QQuickWindow::BeforeSynchronizingStage,
2502 QQuickWindow::AfterSynchronizingStage,
2503 QQuickWindow::BeforeRenderingStage,
2504 QQuickWindow::AfterRenderingStage,
2505 QQuickWindow::AfterSwapStage,
2506 QQuickWindow::NoStage
2507 };
2508
2509 const int numJobs = 6;
2510
2511 {
2512 QQuickWindow window;
2513 window.setTitle(QTest::currentTestFunction());
2514 RenderJob::deleted = 0;
2515
2516 // Schedule the jobs
2517 for (int i = 0; i < numJobs; ++i)
2518 window.scheduleRenderJob(job: new RenderJob(stages[i], &completedJobs), schedule: stages[i]);
2519 window.show();
2520 QVERIFY(QTest::qWaitForWindowExposed(&window));
2521
2522 // All jobs should be deleted
2523 QTRY_COMPARE(RenderJob::deleted, numJobs);
2524
2525 // The NoStage job is not completed, if it is issued when there is no context,
2526 // but the rest will be queued and completed once relevant render stage is hit.
2527 QCOMPARE(completedJobs.size(), numJobs - 1);
2528
2529 // Verify jobs were completed in correct order
2530 for (int i = 0; i < numJobs - 1; ++i)
2531 QCOMPARE(completedJobs.at(i), stages[i]);
2532
2533
2534 // Check that NoStage job gets executed if it is scheduled when window is exposed
2535 completedJobs.clear();
2536 RenderJob::deleted = 0;
2537 window.scheduleRenderJob(job: new RenderJob(QQuickWindow::NoStage, &completedJobs),
2538 schedule: QQuickWindow::NoStage);
2539 QTRY_COMPARE(RenderJob::deleted, 1);
2540 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
2541 || (QGuiApplication::platformName() == QLatin1String("minimal")))
2542 QEXPECT_FAIL("", "NoStage job fails on offscreen/minimal platforms", Continue);
2543 QCOMPARE(completedJobs.size(), 1);
2544
2545#if QT_CONFIG(opengl)
2546 if (window.rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) {
2547 // Do a synchronized GL job.
2548 GLubyte readPixel[4] = {0, 0, 0, 0};
2549 GlRenderJob *glJob = new GlRenderJob(readPixel);
2550 if (window.openglContext()->thread() != QThread::currentThread()) {
2551 QMutex mutex;
2552 QWaitCondition condition;
2553 glJob->mutex = &mutex;
2554 glJob->condition = &condition;
2555 mutex.lock();
2556 window.scheduleRenderJob(job: glJob, schedule: QQuickWindow::NoStage);
2557 condition.wait(lockedMutex: &mutex);
2558 mutex.unlock();
2559 } else {
2560 window.scheduleRenderJob(job: glJob, schedule: QQuickWindow::NoStage);
2561 }
2562 QCOMPARE(int(readPixel[0]), 255);
2563 QCOMPARE(int(readPixel[1]), 0);
2564 QCOMPARE(int(readPixel[2]), 0);
2565 QCOMPARE(int(readPixel[3]), 255);
2566 }
2567#endif
2568 }
2569
2570 // Verify that jobs are deleted when window is not rendered at all
2571 completedJobs.clear();
2572 RenderJob::deleted = 0;
2573 {
2574 QQuickWindow window2;
2575 for (int i = 0; i < numJobs; ++i) {
2576 window2.scheduleRenderJob(job: new RenderJob(stages[i], &completedJobs), schedule: stages[i]);
2577 }
2578 }
2579 QTRY_COMPARE(RenderJob::deleted, numJobs);
2580 QCOMPARE(completedJobs.size(), 0);
2581}
2582
2583class EventCounter : public QQuickRectangle
2584{
2585public:
2586 EventCounter(QQuickItem *parent = nullptr)
2587 : QQuickRectangle(parent)
2588 { }
2589
2590 void addFilterEvent(QEvent::Type type)
2591 {
2592 m_returnTrueForType.append(t: type);
2593 }
2594
2595 int childMouseEventFilterEventCount(QEvent::Type type)
2596 {
2597 return m_childMouseEventFilterEventCount.value(key: type, defaultValue: 0);
2598 }
2599
2600 int eventCount(QEvent::Type type)
2601 {
2602 return m_eventCount.value(key: type, defaultValue: 0);
2603 }
2604
2605 void reset()
2606 {
2607 m_eventCount.clear();
2608 m_childMouseEventFilterEventCount.clear();
2609 }
2610protected:
2611 bool childMouseEventFilter(QQuickItem *, QEvent *event) override
2612 {
2613 m_childMouseEventFilterEventCount[event->type()]++;
2614 return m_returnTrueForType.contains(t: event->type());
2615 }
2616
2617 bool event(QEvent *event) override
2618 {
2619 m_eventCount[event->type()]++;
2620 return QQuickRectangle::event(event);
2621 }
2622
2623
2624private:
2625 QList<QEvent::Type> m_returnTrueForType;
2626 QMap<QEvent::Type, int> m_childMouseEventFilterEventCount;
2627 QMap<QEvent::Type, int> m_eventCount;
2628};
2629
2630void tst_qquickwindow::testHoverChildMouseEventFilter()
2631{
2632 QQuickWindow window;
2633
2634 window.resize(w: 250, h: 250);
2635 window.setPosition(posx: 100, posy: 100);
2636 window.setTitle(QTest::currentTestFunction());
2637 window.show();
2638 QVERIFY(QTest::qWaitForWindowActive(&window));
2639
2640 EventCounter *bottomItem = new EventCounter(window.contentItem());
2641 bottomItem->setObjectName("Bottom Item");
2642 bottomItem->setSize(QSizeF(150, 150));
2643 bottomItem->setAcceptHoverEvents(true);
2644
2645 EventCounter *middleItem = new EventCounter(bottomItem);
2646 middleItem->setObjectName("Middle Item");
2647 middleItem->setPosition(QPointF(50, 50));
2648 middleItem->setSize(QSizeF(150, 150));
2649 middleItem->setAcceptHoverEvents(true);
2650
2651 EventCounter *topItem = new EventCounter(middleItem);
2652 topItem->setObjectName("Top Item");
2653 topItem->setPosition(QPointF(50, 50));
2654 topItem->setSize(QSizeF(150, 150));
2655 topItem->setAcceptHoverEvents(true);
2656
2657 QPoint pos(10, 10);
2658
2659 QTest::mouseMove(window: &window, pos);
2660
2661 QTRY_VERIFY(bottomItem->eventCount(QEvent::HoverEnter) > 0);
2662 QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0);
2663 QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0);
2664 QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
2665 bottomItem->reset();
2666
2667 pos = QPoint(60, 60);
2668 QTest::mouseMove(window: &window, pos);
2669 QTRY_VERIFY(middleItem->eventCount(QEvent::HoverEnter) > 0);
2670 QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0);
2671 middleItem->reset();
2672
2673 pos = QPoint(70,70);
2674 bottomItem->setFiltersChildMouseEvents(true);
2675 QTest::mouseMove(window: &window, pos);
2676 QTRY_VERIFY(middleItem->eventCount(QEvent::HoverMove) > 0);
2677 QVERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverMove) > 0);
2678 QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
2679 bottomItem->reset();
2680 middleItem->reset();
2681
2682 pos = QPoint(110,110);
2683 bottomItem->addFilterEvent(type: QEvent::HoverEnter);
2684 QTest::mouseMove(window: &window, pos);
2685 QTRY_VERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter) > 0);
2686 QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
2687 QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0);
2688}
2689
2690class HoverTimestampConsumer : public QQuickItem
2691{
2692 Q_OBJECT
2693public:
2694 HoverTimestampConsumer(QQuickItem *parent = nullptr)
2695 : QQuickItem(parent)
2696 {
2697 setAcceptHoverEvents(true);
2698 }
2699
2700 void hoverEnterEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); }
2701 void hoverLeaveEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); }
2702 void hoverMoveEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); }
2703
2704 QList<ulong> hoverTimestamps;
2705};
2706
2707// Checks that a QHoverEvent carries the timestamp of the QMouseEvent that caused it.
2708// QTBUG-54600
2709void tst_qquickwindow::testHoverTimestamp()
2710{
2711 QQuickWindow window;
2712
2713 window.resize(w: 200, h: 200);
2714 window.setPosition(posx: 100, posy: 100);
2715 window.setTitle(QTest::currentTestFunction());
2716 window.show();
2717 QVERIFY(QTest::qWaitForWindowActive(&window));
2718
2719 HoverTimestampConsumer *hoverConsumer = new HoverTimestampConsumer(window.contentItem());
2720 hoverConsumer->setWidth(100);
2721 hoverConsumer->setHeight(100);
2722 hoverConsumer->setX(50);
2723 hoverConsumer->setY(50);
2724
2725 // First position, outside
2726 {
2727 QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(40, 40), QPointF(40, 40), QPointF(140, 140),
2728 Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
2729 mouseEvent.setTimestamp(10);
2730 QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
2731 }
2732
2733 // Enter
2734 {
2735 QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(50, 50), QPointF(50, 50), QPointF(150, 150),
2736 Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
2737 mouseEvent.setTimestamp(20);
2738 QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
2739 }
2740 QCOMPARE(hoverConsumer->hoverTimestamps.size(), 1);
2741 QCOMPARE(hoverConsumer->hoverTimestamps.last(), 20UL);
2742
2743 // Move
2744 {
2745 QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(60, 60), QPointF(60, 60), QPointF(160, 160),
2746 Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
2747 mouseEvent.setTimestamp(30);
2748 QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
2749 }
2750 QCOMPARE(hoverConsumer->hoverTimestamps.size(), 2);
2751 QCOMPARE(hoverConsumer->hoverTimestamps.last(), 30UL);
2752
2753 // Move
2754 {
2755 QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(100, 100), QPointF(100, 100), QPointF(200, 200),
2756 Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
2757 mouseEvent.setTimestamp(40);
2758 QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
2759 }
2760 QCOMPARE(hoverConsumer->hoverTimestamps.size(), 3);
2761 QCOMPARE(hoverConsumer->hoverTimestamps.last(), 40UL);
2762
2763 // Leave
2764 {
2765 QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(160, 160), QPointF(160, 160), QPointF(260, 260),
2766 Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
2767 mouseEvent.setTimestamp(5);
2768 QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
2769 }
2770 QCOMPARE(hoverConsumer->hoverTimestamps.size(), 4);
2771 QCOMPARE(hoverConsumer->hoverTimestamps.last(), 5UL);
2772}
2773
2774class CircleItem : public QQuickRectangle
2775{
2776public:
2777 CircleItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent) { }
2778
2779 void setRadius(qreal radius) {
2780 const qreal diameter = radius*2;
2781 setWidth(diameter);
2782 setHeight(diameter);
2783 }
2784
2785 bool childMouseEventFilter(QQuickItem *item, QEvent *event) override
2786 {
2787 Q_UNUSED(item)
2788 if (event->type() == QEvent::MouseButtonPress && !contains(pos: static_cast<QMouseEvent*>(event)->pos())) {
2789 // This is an evil hack: in case of items that are not rectangles, we never accept the event.
2790 // Instead the events are now delivered to QDeclarativeGeoMapItemBase which doesn't to anything with them.
2791 // The map below it still works since it filters events and steals the events at some point.
2792 event->setAccepted(false);
2793 return true;
2794 }
2795 return false;
2796 }
2797
2798 bool contains(const QPointF &pos) const override {
2799 // returns true if the point is inside the the embedded circle inside the (square) rect
2800 const float radius = (float)width()/2;
2801 const QVector2D center(radius, radius);
2802 const QVector2D dx = QVector2D(pos) - center;
2803 const bool ret = dx.lengthSquared() < radius*radius;
2804 return ret;
2805 }
2806};
2807
2808void tst_qquickwindow::test_circleMapItem()
2809{
2810 QQuickWindow window;
2811
2812 window.resize(w: 250, h: 250);
2813 window.setPosition(posx: 100, posy: 100);
2814 window.setTitle(QTest::currentTestFunction());
2815
2816 QQuickItem *root = window.contentItem();
2817 QQuickMouseArea *mab = new QQuickMouseArea(root);
2818 mab->setObjectName("Bottom MouseArea");
2819 mab->setSize(QSizeF(100, 100));
2820
2821 CircleItem *topItem = new CircleItem(root);
2822 topItem->setFiltersChildMouseEvents(true);
2823 topItem->setColor(Qt::green);
2824 topItem->setObjectName("Top Item");
2825 topItem->setPosition(QPointF(30, 30));
2826 topItem->setRadius(20);
2827 QQuickMouseArea *mat = new QQuickMouseArea(topItem);
2828 mat->setObjectName("Top Item/MouseArea");
2829 mat->setSize(QSizeF(40, 40));
2830
2831 QSignalSpy bottomSpy(mab, SIGNAL(clicked(QQuickMouseEvent *)));
2832 QSignalSpy topSpy(mat, SIGNAL(clicked(QQuickMouseEvent *)));
2833
2834 window.show();
2835 QVERIFY(QTest::qWaitForWindowExposed(&window));
2836 QTest::qWait(ms: 1000);
2837
2838 QPoint pos(50, 50);
2839 QTest::mouseClick(window: &window, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos);
2840
2841 QCOMPARE(topSpy.count(), 1);
2842 QCOMPARE(bottomSpy.count(), 0);
2843
2844 // Outside the "Circles" "input area", but on top of the bottomItem rectangle
2845 pos = QPoint(66, 66);
2846 QTest::mouseClick(window: &window, button: Qt::LeftButton, stateKey: Qt::KeyboardModifiers(), pos);
2847
2848 QCOMPARE(bottomSpy.count(), 1);
2849 QCOMPARE(topSpy.count(), 1);
2850}
2851
2852void tst_qquickwindow::pointerEventTypeAndPointCount()
2853{
2854 QPointF localPosition(33, 66);
2855 QPointF scenePosition(133, 166);
2856 QPointF screenPosition(333, 366);
2857 QMouseEvent me(QEvent::MouseButtonPress, localPosition, scenePosition, screenPosition,
2858 Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
2859 QTouchEvent te(QEvent::TouchBegin, touchDevice, Qt::NoModifier, Qt::TouchPointPressed,
2860 QList<QTouchEvent::TouchPoint>() << QTouchEvent::TouchPoint(1));
2861
2862
2863 QQuickPointerMouseEvent pme(nullptr, QQuickPointerDevice::genericMouseDevice());
2864 pme.reset(&me);
2865 QCOMPARE(pme.asMouseEvent(localPosition), &me);
2866 QVERIFY(pme.asPointerMouseEvent());
2867 QVERIFY(!pme.asPointerTouchEvent());
2868 QVERIFY(!pme.asPointerTabletEvent());
2869// QVERIFY(!pe->asTabletEvent()); // TODO
2870 QCOMPARE(pme.pointCount(), 1);
2871 QCOMPARE(pme.point(0)->scenePosition(), scenePosition);
2872 QCOMPARE(pme.asMouseEvent(localPosition)->localPos(), localPosition);
2873 QCOMPARE(pme.asMouseEvent(localPosition)->screenPos(), screenPosition);
2874
2875 QQuickPointerTouchEvent pte(nullptr, QQuickPointerDevice::touchDevice(d: touchDevice));
2876 pte.reset(&te);
2877 QCOMPARE(pte.asTouchEvent(), &te);
2878 QVERIFY(!pte.asPointerMouseEvent());
2879 QVERIFY(pte.asPointerTouchEvent());
2880 QVERIFY(!pte.asPointerTabletEvent());
2881 QVERIFY(pte.asTouchEvent());
2882// QVERIFY(!pte.asTabletEvent()); // TODO
2883 QCOMPARE(pte.pointCount(), 1);
2884 QCOMPARE(pte.touchPointById(1)->id(), 1);
2885 QVERIFY(!pte.touchPointById(0));
2886
2887 te.setTouchPoints(QList<QTouchEvent::TouchPoint>() << QTouchEvent::TouchPoint(1) << QTouchEvent::TouchPoint(2));
2888 pte.reset(&te);
2889 QCOMPARE(pte.pointCount(), 2);
2890 QCOMPARE(pte.touchPointById(1)->id(), 1);
2891 QCOMPARE(pte.touchPointById(2)->id(), 2);
2892 QVERIFY(!pte.touchPointById(0));
2893
2894 te.setTouchPoints(QList<QTouchEvent::TouchPoint>() << QTouchEvent::TouchPoint(2));
2895 pte.reset(&te);
2896 QCOMPARE(pte.pointCount(), 1);
2897 QCOMPARE(pte.touchPointById(2)->id(), 2);
2898 QVERIFY(!pte.touchPointById(1));
2899 QVERIFY(!pte.touchPointById(0));
2900}
2901
2902void tst_qquickwindow::grabContentItemToImage()
2903{
2904 QQmlEngine engine;
2905 QQmlComponent component(&engine);
2906 component.loadUrl(url: testFileUrl(fileName: "grabContentItemToImage.qml"));
2907
2908 QObject *created = component.create();
2909 QScopedPointer<QObject> cleanup(created);
2910 QVERIFY(created);
2911
2912 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: created);
2913 QVERIFY(QTest::qWaitForWindowActive(window));
2914
2915 QMetaObject::invokeMethod(obj: window, member: "grabContentItemToImage");
2916 QTRY_COMPARE(created->property("success").toInt(), 1);
2917}
2918
2919class TestDropTarget : public QQuickItem
2920{
2921 Q_OBJECT
2922public:
2923 TestDropTarget(QQuickItem *parent = nullptr)
2924 : QQuickItem(parent)
2925 , enterDropAction(Qt::CopyAction)
2926 , moveDropAction(Qt::CopyAction)
2927 , dropDropAction(Qt::CopyAction)
2928 , enterAccept(true)
2929 , moveAccept(true)
2930 , dropAccept(true)
2931 {
2932 setFlags(ItemAcceptsDrops);
2933 }
2934
2935 void reset()
2936 {
2937 enterDropAction = Qt::CopyAction;
2938 moveDropAction = Qt::CopyAction;
2939 dropDropAction = Qt::CopyAction;
2940 enterAccept = true;
2941 moveAccept = true;
2942 dropAccept = true;
2943 }
2944
2945 void dragEnterEvent(QDragEnterEvent *event)
2946 {
2947 event->setAccepted(enterAccept);
2948 event->setDropAction(enterDropAction);
2949 }
2950
2951 void dragMoveEvent(QDragMoveEvent *event)
2952 {
2953 event->setAccepted(moveAccept);
2954 event->setDropAction(moveDropAction);
2955 }
2956
2957 void dropEvent(QDropEvent *event)
2958 {
2959 event->setAccepted(dropAccept);
2960 event->setDropAction(dropDropAction);
2961 }
2962
2963 Qt::DropAction enterDropAction;
2964 Qt::DropAction moveDropAction;
2965 Qt::DropAction dropDropAction;
2966 bool enterAccept;
2967 bool moveAccept;
2968 bool dropAccept;
2969};
2970
2971class DragEventTester {
2972public:
2973 DragEventTester()
2974 : pos(60, 60)
2975 , actions(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction)
2976 , buttons(Qt::LeftButton)
2977 , modifiers(Qt::NoModifier)
2978 {
2979 }
2980
2981 ~DragEventTester() {
2982 qDeleteAll(c: events);
2983 events.clear();
2984 enterEvent = nullptr;
2985 moveEvent = nullptr;
2986 dropEvent = nullptr;
2987 leaveEvent = nullptr;
2988 }
2989
2990 void addEnterEvent()
2991 {
2992 enterEvent = new QDragEnterEvent(pos, actions, &data, buttons, modifiers);
2993 events.append(t: enterEvent);
2994 }
2995
2996 void addMoveEvent()
2997 {
2998 moveEvent = new QDragMoveEvent(pos, actions, &data, buttons, modifiers, QEvent::DragMove);
2999 events.append(t: moveEvent);
3000 }
3001
3002 void addDropEvent()
3003 {
3004 dropEvent = new QDropEvent(pos, actions, &data, buttons, modifiers, QEvent::Drop);
3005 events.append(t: dropEvent);
3006 }
3007
3008 void addLeaveEvent()
3009 {
3010 leaveEvent = new QDragLeaveEvent();
3011 events.append(t: leaveEvent);
3012 }
3013
3014 void sendDragEventSequence(QQuickWindow *window) const {
3015 for (int i = 0; i < events.size(); ++i) {
3016 QCoreApplication::sendEvent(receiver: window, event: events[i]);
3017 }
3018 }
3019
3020 // Used for building events.
3021 QMimeData data;
3022 QPoint pos;
3023 Qt::DropActions actions;
3024 Qt::MouseButtons buttons;
3025 Qt::KeyboardModifiers modifiers;
3026
3027 // Owns events.
3028 QList<QEvent *> events;
3029
3030 // Non-owner pointers for easy acccess.
3031 QDragEnterEvent *enterEvent;
3032 QDragMoveEvent *moveEvent;
3033 QDropEvent *dropEvent;
3034 QDragLeaveEvent *leaveEvent;
3035};
3036
3037void tst_qquickwindow::testDragEventPropertyPropagation()
3038{
3039 QQuickWindow window;
3040 TestDropTarget dropTarget(window.contentItem());
3041
3042 // Setting the size is important because the QQuickWindow checks if the drag happened inside
3043 // the drop target.
3044 dropTarget.setSize(QSizeF(100, 100));
3045
3046 // Test enter events property propagation.
3047 // For enter events, only isAccepted gets propagated.
3048 {
3049 DragEventTester builder;
3050 dropTarget.enterAccept = false;
3051 dropTarget.enterDropAction = Qt::IgnoreAction;
3052 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3053 builder.sendDragEventSequence(window: &window);
3054 QDragEnterEvent* enterEvent = builder.enterEvent;
3055 QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
3056 }
3057 {
3058 DragEventTester builder;
3059 dropTarget.enterAccept = false;
3060 dropTarget.enterDropAction = Qt::CopyAction;
3061 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3062 builder.sendDragEventSequence(window: &window);
3063 QDragEnterEvent* enterEvent = builder.enterEvent;
3064 QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
3065 }
3066 {
3067 DragEventTester builder;
3068 dropTarget.enterAccept = true;
3069 dropTarget.enterDropAction = Qt::IgnoreAction;
3070 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3071 builder.sendDragEventSequence(window: &window);
3072 QDragEnterEvent* enterEvent = builder.enterEvent;
3073 QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
3074 }
3075 {
3076 DragEventTester builder;
3077 dropTarget.enterAccept = true;
3078 dropTarget.enterDropAction = Qt::CopyAction;
3079 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3080 builder.sendDragEventSequence(window: &window);
3081 QDragEnterEvent* enterEvent = builder.enterEvent;
3082 QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
3083 }
3084
3085 // Test move events property propagation.
3086 // For move events, both isAccepted and dropAction get propagated.
3087 dropTarget.reset();
3088 {
3089 DragEventTester builder;
3090 dropTarget.moveAccept = false;
3091 dropTarget.moveDropAction = Qt::IgnoreAction;
3092 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3093 builder.sendDragEventSequence(window: &window);
3094 QDragMoveEvent* moveEvent = builder.moveEvent;
3095 QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
3096 QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
3097 }
3098 {
3099 DragEventTester builder;
3100 dropTarget.moveAccept = false;
3101 dropTarget.moveDropAction = Qt::CopyAction;
3102 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3103 builder.sendDragEventSequence(window: &window);
3104 QDragMoveEvent* moveEvent = builder.moveEvent;
3105 QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
3106 QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
3107 }
3108 {
3109 DragEventTester builder;
3110 dropTarget.moveAccept = true;
3111 dropTarget.moveDropAction = Qt::IgnoreAction;
3112 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3113 builder.sendDragEventSequence(window: &window);
3114 QDragMoveEvent* moveEvent = builder.moveEvent;
3115 QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
3116 QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
3117 }
3118 {
3119 DragEventTester builder;
3120 dropTarget.moveAccept = true;
3121 dropTarget.moveDropAction = Qt::CopyAction;
3122 builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
3123 builder.sendDragEventSequence(window: &window);
3124 QDragMoveEvent* moveEvent = builder.moveEvent;
3125 QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
3126 QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
3127 }
3128
3129 // Test drop events property propagation.
3130 // For drop events, both isAccepted and dropAction get propagated.
3131 dropTarget.reset();
3132 {
3133 DragEventTester builder;
3134 dropTarget.dropAccept = false;
3135 dropTarget.dropDropAction = Qt::IgnoreAction;
3136 builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
3137 builder.sendDragEventSequence(window: &window);
3138 QDropEvent* dropEvent = builder.dropEvent;
3139 QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
3140 QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
3141 }
3142 {
3143 DragEventTester builder;
3144 dropTarget.dropAccept = false;
3145 dropTarget.dropDropAction = Qt::CopyAction;
3146 builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
3147 builder.sendDragEventSequence(window: &window);
3148 QDropEvent* dropEvent = builder.dropEvent;
3149 QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
3150 QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
3151 }
3152 {
3153 DragEventTester builder;
3154 dropTarget.dropAccept = true;
3155 dropTarget.dropDropAction = Qt::IgnoreAction;
3156 builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
3157 builder.sendDragEventSequence(window: &window);
3158 QDropEvent* dropEvent = builder.dropEvent;
3159 QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
3160 QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
3161 }
3162 {
3163 DragEventTester builder;
3164 dropTarget.dropAccept = true;
3165 dropTarget.dropDropAction = Qt::CopyAction;
3166 builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
3167 builder.sendDragEventSequence(window: &window);
3168 QDropEvent* dropEvent = builder.dropEvent;
3169 QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
3170 QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
3171 }
3172}
3173
3174void tst_qquickwindow::findChild()
3175{
3176 QQuickWindow window;
3177
3178 // QQuickWindow
3179 // |_ QQuickWindow::contentItem
3180 // | |_ QObject("contentItemChild")
3181 // |_ QObject("viewChild")
3182
3183 QObject *windowChild = new QObject(&window);
3184 windowChild->setObjectName("windowChild");
3185
3186 QObject *contentItemChild = new QObject(window.contentItem());
3187 contentItemChild->setObjectName("contentItemChild");
3188
3189 QCOMPARE(window.findChild<QObject *>("windowChild"), windowChild);
3190 QCOMPARE(window.findChild<QObject *>("contentItemChild"), contentItemChild);
3191
3192 QVERIFY(!window.contentItem()->findChild<QObject *>("viewChild")); // sibling
3193 QCOMPARE(window.contentItem()->findChild<QObject *>("contentItemChild"), contentItemChild);
3194}
3195
3196class DeliveryRecord : public QPair<QString, QString>
3197{
3198public:
3199 DeliveryRecord(const QString &filter, const QString &receiver) : QPair(filter, receiver) { }
3200 DeliveryRecord(const QString &receiver) : QPair(QString(), receiver) { }
3201 DeliveryRecord() : QPair() { }
3202 QString toString() const {
3203 if (second.isEmpty())
3204 return QLatin1String("Delivery(no receiver)");
3205 else if (first.isEmpty())
3206 return QString(QLatin1String("Delivery(to '%1')")).arg(a: second);
3207 else
3208 return QString(QLatin1String("Delivery('%1' filtering for '%2')")).arg(a: first).arg(a: second);
3209 }
3210};
3211
3212Q_DECLARE_METATYPE(DeliveryRecord)
3213
3214QDebug operator<<(QDebug dbg, const DeliveryRecord &pair)
3215{
3216 dbg << pair.toString();
3217 return dbg;
3218}
3219
3220typedef QVector<DeliveryRecord> DeliveryRecordVector;
3221
3222class EventItem : public QQuickRectangle
3223{
3224 Q_OBJECT
3225public:
3226 EventItem(QQuickItem *parent)
3227 : QQuickRectangle(parent)
3228 , m_eventAccepts(true)
3229 , m_filterReturns(true)
3230 , m_filterAccepts(true)
3231 , m_filterNotPreAccepted(false)
3232 {
3233 QSizeF psize(parent->width(), parent->height());
3234 psize -= QSizeF(20, 20);
3235 setWidth(psize.width());
3236 setHeight(psize.height());
3237 setPosition(QPointF(10, 10));
3238 }
3239
3240 void setFilterReturns(bool filterReturns) { m_filterReturns = filterReturns; }
3241 void setFilterAccepts(bool accepts) { m_filterAccepts = accepts; }
3242 void setEventAccepts(bool accepts) { m_eventAccepts = accepts; }
3243
3244 /*!
3245 * \internal
3246 *
3247 * returns false if any of the calls to childMouseEventFilter had the wrong
3248 * preconditions. If all calls had the expected precondition, returns true.
3249 */
3250 bool testFilterPreConditions() const { return !m_filterNotPreAccepted; }
3251 static QVector<DeliveryRecord> &deliveryList() { return m_deliveryList; }
3252 static QSet<QEvent::Type> &includedEventTypes()
3253 {
3254 if (m_includedEventTypes.isEmpty())
3255 m_includedEventTypes << QEvent::MouseButtonPress;
3256 return m_includedEventTypes;
3257 }
3258 static void setExpectedDeliveryList(const QVector<DeliveryRecord> &v) { m_expectedDeliveryList = v; }
3259
3260protected:
3261 bool childMouseEventFilter(QQuickItem *i, QEvent *e) override
3262 {
3263 appendEvent(filter: this, receiver: i, event: e);
3264 switch (e->type()) {
3265 case QEvent::MouseButtonPress:
3266 if (!e->isAccepted())
3267 m_filterNotPreAccepted = true;
3268 e->setAccepted(m_filterAccepts);
3269 // qCDebug(lcTests) << objectName() << i->objectName();
3270 return m_filterReturns;
3271 default:
3272 break;
3273 }
3274 return QQuickRectangle::childMouseEventFilter(i, e);
3275 }
3276
3277 bool event(QEvent *e) override
3278 {
3279 appendEvent(filter: nullptr, receiver: this, event: e);
3280 switch (e->type()) {
3281 case QEvent::MouseButtonPress:
3282 // qCDebug(lcTests) << objectName();
3283 e->setAccepted(m_eventAccepts);
3284 return true;
3285 default:
3286 break;
3287 }
3288 return QQuickRectangle::event(e);
3289 }
3290
3291private:
3292 static void appendEvent(QQuickItem *filter, QQuickItem *receiver, QEvent *event) {
3293 if (includedEventTypes().contains(value: event->type())) {
3294 auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString());
3295 int i = m_deliveryList.count();
3296 if (m_expectedDeliveryList.count() > i && m_expectedDeliveryList[i] == record)
3297 qCDebug(lcTests).noquote().nospace() << i << ": " << record;
3298 else
3299 qCDebug(lcTests).noquote().nospace() << i << ": " << record
3300 << ", expected " << (m_expectedDeliveryList.count() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---";
3301 m_deliveryList << record;
3302 }
3303 }
3304 bool m_eventAccepts;
3305 bool m_filterReturns;
3306 bool m_filterAccepts;
3307 bool m_filterNotPreAccepted;
3308
3309 // list of (filtering-parent . receiver) pairs
3310 static DeliveryRecordVector m_expectedDeliveryList;
3311 static DeliveryRecordVector m_deliveryList;
3312 static QSet<QEvent::Type> m_includedEventTypes;
3313};
3314
3315DeliveryRecordVector EventItem::m_expectedDeliveryList;
3316DeliveryRecordVector EventItem::m_deliveryList;
3317QSet<QEvent::Type> EventItem::m_includedEventTypes;
3318
3319typedef QVector<const char*> CharStarVector;
3320
3321Q_DECLARE_METATYPE(CharStarVector)
3322
3323struct InputState {
3324 struct {
3325 // event() behavior
3326 bool eventAccepts;
3327 // filterChildMouse behavior
3328 bool returns;
3329 bool accepts;
3330 bool filtersChildMouseEvent;
3331 } r[4];
3332};
3333
3334Q_DECLARE_METATYPE(InputState)
3335
3336void tst_qquickwindow::testChildMouseEventFilter_data()
3337{
3338 // HIERARCHY:
3339 // r0->r1->r2->r3
3340 //
3341 QTest::addColumn<QPoint>(name: "mousePos");
3342 QTest::addColumn<InputState>(name: "inputState");
3343 QTest::addColumn<DeliveryRecordVector>(name: "expectedDeliveryOrder");
3344
3345 QTest::newRow(dataTag: "if filtered and rejected, do not deliver it to the item that filtered it")
3346 << QPoint(100, 100)
3347 << InputState({
3348 // | event() | child mouse filter
3349 // +---------+---------+---------+---------
3350 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3351 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3352 { .eventAccepts: true, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3353 { .eventAccepts: false, .returns: true, .accepts: false, .filtersChildMouseEvent: true},
3354 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false}
3355 }
3356 })
3357 << (DeliveryRecordVector()
3358 << DeliveryRecord("r2", "r3")
3359 //<< DeliveryRecord("r3") // it got filtered -> do not deliver
3360 // DeliveryRecord("r2") // r2 filtered it -> do not deliver
3361 << DeliveryRecord("r1")
3362 );
3363
3364 QTest::newRow(dataTag: "no filtering, no accepting")
3365 << QPoint(100, 100)
3366 << InputState({
3367 // | event() | child mouse filter
3368 // +---------+---------+---------+---------
3369 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3370 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3371 { .eventAccepts: false , .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3372 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3373 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false}
3374 }
3375 })
3376 << (DeliveryRecordVector()
3377 << DeliveryRecord("r3")
3378 << DeliveryRecord("r2")
3379 << DeliveryRecord("r1")
3380 << DeliveryRecord("r0")
3381 << DeliveryRecord("root")
3382 );
3383
3384 QTest::newRow(dataTag: "all filtering, no accepting")
3385 << QPoint(100, 100)
3386 << InputState({
3387 // | event() | child mouse filter
3388 // +---------+---------+---------+---------
3389 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3390 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3391 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3392 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3393 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true}
3394 }
3395 })
3396 << (DeliveryRecordVector()
3397 << DeliveryRecord("r2", "r3")
3398 << DeliveryRecord("r1", "r3")
3399 << DeliveryRecord("r0", "r3")
3400 << DeliveryRecord("r3")
3401 << DeliveryRecord("r1", "r2")
3402 << DeliveryRecord("r0", "r2")
3403 << DeliveryRecord("r2")
3404 << DeliveryRecord("r0", "r1")
3405 << DeliveryRecord("r1")
3406 << DeliveryRecord("r0")
3407 << DeliveryRecord("root")
3408 );
3409
3410
3411 QTest::newRow(dataTag: "some filtering, no accepting")
3412 << QPoint(100, 100)
3413 << InputState({
3414 // | event() | child mouse filter
3415 // +---------+---------+---------+---------
3416 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3417 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3418 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3419 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3420 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false}
3421 }
3422 })
3423 << (DeliveryRecordVector()
3424 << DeliveryRecord("r1", "r3")
3425 << DeliveryRecord("r0", "r3")
3426 << DeliveryRecord("r3")
3427 << DeliveryRecord("r1", "r2")
3428 << DeliveryRecord("r0", "r2")
3429 << DeliveryRecord("r2")
3430 << DeliveryRecord("r0", "r1")
3431 << DeliveryRecord("r1")
3432 << DeliveryRecord("r0")
3433 << DeliveryRecord("root")
3434 );
3435
3436 QTest::newRow(dataTag: "r1 accepts")
3437 << QPoint(100, 100)
3438 << InputState({
3439 // | event() | child mouse filter
3440 // +---------+---------+---------+---------
3441 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3442 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3443 { .eventAccepts: true , .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3444 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3445 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false}
3446 }
3447 })
3448 << (DeliveryRecordVector()
3449 << DeliveryRecord("r1", "r3")
3450 << DeliveryRecord("r0", "r3")
3451 << DeliveryRecord("r3")
3452 << DeliveryRecord("r1", "r2")
3453 << DeliveryRecord("r0", "r2")
3454 << DeliveryRecord("r2")
3455 << DeliveryRecord("r0", "r1")
3456 << DeliveryRecord("r1")
3457 );
3458
3459 QTest::newRow(dataTag: "r1 rejects and filters")
3460 << QPoint(100, 100)
3461 << InputState({
3462 // | event() | child mouse filter
3463 // +---------+---------+---------+---------
3464 .r: { // | accepts | returns | accepts | filtersChildMouseEvent
3465 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: true},
3466 { .eventAccepts: false , .returns: true, .accepts: false, .filtersChildMouseEvent: true},
3467 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false},
3468 { .eventAccepts: false, .returns: false, .accepts: false, .filtersChildMouseEvent: false}
3469 }
3470 })
3471 << (DeliveryRecordVector()
3472 << DeliveryRecord("r1", "r3")
3473 << DeliveryRecord("r0", "r3")
3474// << DeliveryRecord("r3") // since it got filtered we don't deliver to r3
3475 << DeliveryRecord("r1", "r2")
3476 << DeliveryRecord("r0", "r2")
3477// << DeliveryRecord("r2" // since it got filtered we don't deliver to r2
3478 << DeliveryRecord("r0", "r1")
3479// << DeliveryRecord("r1") // since it acted as a filter and returned true, we don't deliver to r1
3480 << DeliveryRecord("r0")
3481 << DeliveryRecord("root")
3482 );
3483
3484}
3485
3486void tst_qquickwindow::testChildMouseEventFilter()
3487{
3488 QFETCH(QPoint, mousePos);
3489 QFETCH(InputState, inputState);
3490 QFETCH(DeliveryRecordVector, expectedDeliveryOrder);
3491
3492 EventItem::setExpectedDeliveryList(expectedDeliveryOrder);
3493
3494 QQuickWindow window;
3495 window.resize(w: 500, h: 809);
3496 QQuickItem *root = window.contentItem();
3497 root->setAcceptedMouseButtons(Qt::LeftButton);
3498
3499 root->setObjectName("root");
3500 EventFilter *rootFilter = new EventFilter;
3501 root->installEventFilter(filterObj: rootFilter);
3502
3503 // Create 4 items; each item a child of the previous item.
3504 EventItem *r[4];
3505 r[0] = new EventItem(root);
3506 r[0]->setColor(QColor(0x404040));
3507 r[0]->setWidth(200);
3508 r[0]->setHeight(200);
3509
3510 r[1] = new EventItem(r[0]);
3511 r[1]->setColor(QColor(0x606060));
3512
3513 r[2] = new EventItem(r[1]);
3514 r[2]->setColor(Qt::red);
3515
3516 r[3] = new EventItem(r[2]);
3517 r[3]->setColor(Qt::green);
3518
3519 for (uint i = 0; i < sizeof(r)/sizeof(EventItem*); ++i) {
3520 r[i]->setEventAccepts(inputState.r[i].eventAccepts);
3521 r[i]->setFilterReturns(inputState.r[i].returns);
3522 r[i]->setFilterAccepts(inputState.r[i].accepts);
3523 r[i]->setFiltersChildMouseEvents(inputState.r[i].filtersChildMouseEvent);
3524 r[i]->setObjectName(QString::fromLatin1(str: "r%1").arg(a: i));
3525 r[i]->setAcceptedMouseButtons(Qt::LeftButton);
3526 }
3527
3528 window.show();
3529 window.requestActivate();
3530 QVERIFY(QTest::qWaitForWindowActive(&window));
3531
3532 DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList();
3533 actualDeliveryOrder.clear();
3534 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: mousePos);
3535
3536 // Check if event got delivered to the root item. If so, append it to the list of items the event got delivered to
3537 if (rootFilter->events.contains(t: QEvent::MouseButtonPress))
3538 actualDeliveryOrder.append(t: DeliveryRecord("root"));
3539
3540 for (int i = 0; i < qMax(a: actualDeliveryOrder.count(), b: expectedDeliveryOrder.count()); ++i) {
3541 const DeliveryRecord expectedNames = expectedDeliveryOrder.value(i);
3542 const DeliveryRecord actualNames = actualDeliveryOrder.value(i);
3543 QCOMPARE(actualNames.toString(), expectedNames.toString());
3544 }
3545
3546 for (EventItem *item : r) {
3547 QVERIFY(item->testFilterPreConditions());
3548 }
3549
3550 // "restore" mouse state
3551 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: mousePos);
3552}
3553
3554void tst_qquickwindow::cleanupGrabsOnRelease()
3555{
3556 TestTouchItem::clearMouseEventCounters();
3557
3558 QQuickWindow *window = new QQuickWindow;
3559 QScopedPointer<QQuickWindow> cleanup(window);
3560 window->resize(w: 250, h: 250);
3561 window->setPosition(posx: 100, posy: 100);
3562 window->setTitle(QTest::currentTestFunction());
3563 window->show();
3564 QVERIFY(QTest::qWaitForWindowActive(window));
3565
3566 TestTouchItem *parent = new TestTouchItem(window->contentItem());
3567 parent->setObjectName("parent");
3568 parent->setSize(QSizeF(150, 150));
3569 parent->acceptMouseEvents = true;
3570 parent->grabOnRelease = true;
3571
3572 TestTouchItem *child = new TestTouchItem(parent);
3573 child->setObjectName("child");
3574 child->setSize(QSizeF(100, 100));
3575 child->acceptMouseEvents = true;
3576
3577 QPoint pos(80, 80);
3578
3579 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
3580 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos);
3581 // There is an explicit parent->grabMouse on release(!). This means grab changes from child
3582 // to parent:
3583 // This will emit two ungrab events:
3584 // 1. One for the child (due to the explicit call to parent->grabMouse())
3585 // 2. One for the parent (since the mouse button was finally released)
3586 QCOMPARE(child->mouseUngrabEventCount, 1);
3587 QCOMPARE(parent->mouseUngrabEventCount, 1);
3588}
3589
3590#if QT_CONFIG(shortcut)
3591void tst_qquickwindow::testShortCut()
3592{
3593 QQmlEngine engine;
3594 QQmlComponent component(&engine);
3595 component.loadUrl(url: testFileUrl(fileName: "shortcut.qml"));
3596
3597 QObject *created = component.create();
3598 QScopedPointer<QObject> cleanup(created);
3599 QVERIFY(created);
3600
3601 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: created);
3602 QVERIFY(QTest::qWaitForWindowActive(window));
3603
3604 EventFilter eventFilter;
3605 window->activeFocusItem()->installEventFilter(filterObj: &eventFilter);
3606 //Send non-spontaneous key press event
3607 QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_B, Qt::NoModifier);
3608 QCoreApplication::sendEvent(receiver: window, event: &keyEvent);
3609 QVERIFY(eventFilter.events.contains(int(QEvent::ShortcutOverride)));
3610 QVERIFY(window->property("received").value<bool>());
3611}
3612#endif
3613
3614QTEST_MAIN(tst_qquickwindow)
3615
3616#include "tst_qquickwindow.moc"
3617

source code of qtdeclarative/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp