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 | |
51 | Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests" ) |
52 | |
53 | struct TouchEventData { |
54 | QEvent::Type type; |
55 | QWidget *widget; |
56 | QWindow *window; |
57 | Qt::TouchPointStates states; |
58 | QList<QTouchEvent::TouchPoint> touchPoints; |
59 | }; |
60 | |
61 | static 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 | |
76 | static 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 | } |
82 | static 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 | |
111 | class RootItemAccessor : public QQuickItem |
112 | { |
113 | Q_OBJECT |
114 | public: |
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;} |
130 | public slots: |
131 | void rootItemDestroyed() { |
132 | m_rootItemDestroyed = true; |
133 | } |
134 | |
135 | private: |
136 | bool m_rootItemDestroyed; |
137 | QQuickItem *m_rootItem; |
138 | }; |
139 | |
140 | class TestTouchItem : public QQuickRectangle |
141 | { |
142 | Q_OBJECT |
143 | public: |
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 | |
269 | int TestTouchItem::mousePressNum = 0; |
270 | int TestTouchItem::mouseMoveNum = 0; |
271 | int TestTouchItem::mouseReleaseNum = 0; |
272 | |
273 | class EventFilter : public QObject |
274 | { |
275 | public: |
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 | |
285 | class ConstantUpdateItem : public QQuickItem |
286 | { |
287 | Q_OBJECT |
288 | public: |
289 | ConstantUpdateItem(QQuickItem *parent = nullptr) : QQuickItem(parent), iterations(0) {setFlag(flag: ItemHasContents);} |
290 | |
291 | int iterations; |
292 | protected: |
293 | QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *){ |
294 | iterations++; |
295 | update(); |
296 | return nullptr; |
297 | } |
298 | }; |
299 | |
300 | class MouseRecordingWindow : public QQuickWindow |
301 | { |
302 | public: |
303 | explicit MouseRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { } |
304 | |
305 | protected: |
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 | |
322 | public: |
323 | QList<QMouseEvent> m_mouseEvents; |
324 | }; |
325 | |
326 | class MouseRecordingItem : public QQuickItem |
327 | { |
328 | public: |
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 | |
337 | protected: |
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 | |
361 | public: |
362 | QList<QMouseEvent> m_mouseEvents; |
363 | QList<QTouchEvent> m_touchEvents; |
364 | |
365 | private: |
366 | bool m_acceptTouch; |
367 | }; |
368 | |
369 | class tst_qquickwindow : public QQmlDataTest |
370 | { |
371 | Q_OBJECT |
372 | public: |
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 | |
381 | private 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 | |
489 | private: |
490 | QTouchDevice *touchDevice; |
491 | QTouchDevice *touchDeviceWithVelocity; |
492 | }; |
493 | #if QT_CONFIG(opengl) |
494 | Q_DECLARE_METATYPE(QOpenGLContext *); |
495 | #endif |
496 | void tst_qquickwindow::cleanup() |
497 | { |
498 | QVERIFY(QGuiApplication::topLevelWindows().isEmpty()); |
499 | } |
500 | #if QT_CONFIG(opengl) |
501 | void 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 |
521 | void 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 |
536 | void 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 | |
552 | void 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 | |
573 | class FrameCounter : public QObject |
574 | { |
575 | Q_OBJECT |
576 | public slots: |
577 | void incr() { QMutexLocker locker(&m_mutex); ++m_counter; } |
578 | public: |
579 | FrameCounter() : m_counter(0) {} |
580 | int count() { QMutexLocker locker(&m_mutex); int x = m_counter; return x; } |
581 | private: |
582 | int m_counter; |
583 | QMutex m_mutex; |
584 | }; |
585 | |
586 | void 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 | |
617 | void 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 | |
747 | void 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 | |
879 | void 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 | |
891 | void 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 | |
925 | void 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 | |
957 | void 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 | |
996 | void 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 | |
1066 | void 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 | |
1115 | void 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 | |
1127 | void 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 | |
1216 | void 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 | |
1227 | void 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 | |
1255 | void 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 | |
1268 | void 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 | |
1304 | void 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 | |
1320 | void 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 | |
1400 | void 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 | |
1418 | void 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 | |
1436 | void 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 | |
1450 | void 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 | |
1470 | void 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 | |
1480 | void 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 | |
1517 | void 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 | |
1547 | void 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 | |
1569 | void 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 | |
1627 | void 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 | |
1654 | void 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 | |
1699 | void 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 | |
1727 | void 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 | |
1766 | void 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) |
1792 | void 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 | |
1934 | void 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 | |
1945 | void 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 | |
2003 | void 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 | |
2026 | void 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 | |
2049 | void 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 | |
2100 | void 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 | |
2142 | void 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 | |
2162 | void 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 | |
2182 | void 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 |
2202 | void 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 |
2223 | void 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 |
2245 | void 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 |
2271 | void 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 |
2300 | void 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 | |
2326 | void 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 |
2341 | void 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 | |
2373 | void 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 | |
2425 | void 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 | |
2459 | class RenderJob : public QRunnable |
2460 | { |
2461 | public: |
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) |
2472 | class GlRenderJob : public QRunnable |
2473 | { |
2474 | public: |
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 |
2494 | int RenderJob::deleted = 0; |
2495 | |
2496 | void 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 | |
2583 | class EventCounter : public QQuickRectangle |
2584 | { |
2585 | public: |
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 | } |
2610 | protected: |
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 | |
2624 | private: |
2625 | QList<QEvent::Type> m_returnTrueForType; |
2626 | QMap<QEvent::Type, int> m_childMouseEventFilterEventCount; |
2627 | QMap<QEvent::Type, int> m_eventCount; |
2628 | }; |
2629 | |
2630 | void 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 | |
2690 | class HoverTimestampConsumer : public QQuickItem |
2691 | { |
2692 | Q_OBJECT |
2693 | public: |
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 |
2709 | void 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 | |
2774 | class CircleItem : public QQuickRectangle |
2775 | { |
2776 | public: |
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 | |
2808 | void 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 | |
2852 | void 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 | |
2902 | void 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 | |
2919 | class TestDropTarget : public QQuickItem |
2920 | { |
2921 | Q_OBJECT |
2922 | public: |
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 | |
2971 | class DragEventTester { |
2972 | public: |
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 | |
3037 | void 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 | |
3174 | void 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 | |
3196 | class DeliveryRecord : public QPair<QString, QString> |
3197 | { |
3198 | public: |
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 | |
3212 | Q_DECLARE_METATYPE(DeliveryRecord) |
3213 | |
3214 | QDebug operator<<(QDebug dbg, const DeliveryRecord &pair) |
3215 | { |
3216 | dbg << pair.toString(); |
3217 | return dbg; |
3218 | } |
3219 | |
3220 | typedef QVector<DeliveryRecord> DeliveryRecordVector; |
3221 | |
3222 | class EventItem : public QQuickRectangle |
3223 | { |
3224 | Q_OBJECT |
3225 | public: |
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 | |
3260 | protected: |
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 | |
3291 | private: |
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 | |
3315 | DeliveryRecordVector EventItem::m_expectedDeliveryList; |
3316 | DeliveryRecordVector EventItem::m_deliveryList; |
3317 | QSet<QEvent::Type> EventItem::m_includedEventTypes; |
3318 | |
3319 | typedef QVector<const char*> CharStarVector; |
3320 | |
3321 | Q_DECLARE_METATYPE(CharStarVector) |
3322 | |
3323 | struct 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 | |
3334 | Q_DECLARE_METATYPE(InputState) |
3335 | |
3336 | void 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 | |
3486 | void 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 | |
3554 | void 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) |
3591 | void 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 | |
3614 | QTEST_MAIN(tst_qquickwindow) |
3615 | |
3616 | #include "tst_qquickwindow.moc" |
3617 | |