diff options
Diffstat (limited to 'tests/auto/gui/kernel/qwindow/tst_qwindow.cpp')
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 350 |
1 files changed, 347 insertions, 3 deletions
diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index 1cac1820d9..a9e2c5f882 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qrasterwindow.h> #include <qpa/qwindowsysteminterface.h> @@ -7,6 +7,7 @@ #include <qpa/qplatformwindow.h> #include <private/qguiapplication_p.h> #include <private/qhighdpiscaling_p.h> +#include <private/qwindow_p.h> #include <QtGui/QPainter> #include <QTest> @@ -22,6 +23,12 @@ Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests") +static bool isPlatformEglFS() +{ + static const bool isEglFS = !QGuiApplication::platformName().compare(QLatin1String("eglfs"), Qt::CaseInsensitive); + return isEglFS; +} + class tst_QWindow: public QObject { Q_OBJECT @@ -30,6 +37,7 @@ private slots: void create(); void setParent(); void setVisible(); + void setVisibleThenCreate(); void setVisibleFalseDoesNotCreateWindow(); void eventOrderOnShow(); void paintEvent(); @@ -89,6 +97,13 @@ private slots: void qobject_castOnDestruction(); void touchToMouseTranslationByPopup(); void stateChangeSignal(); +#ifndef QT_NO_CURSOR + void enterLeaveOnWindowShowHide_data(); + void enterLeaveOnWindowShowHide(); +#endif + void windowExposedAfterReparent(); + void childEvents(); + void parentEvents(); private: QPoint m_availableTopLeft; @@ -106,6 +121,10 @@ static bool isPlatformWayland() void tst_QWindow::initTestCase() { +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() == 33) + QSKIP("Is flaky on Android 13 / RHEL 8.6 and 8.8 (QTQAINFRA-5606)"); +#endif // Size of reference window, 200 for < 2000, scale up for larger screens // to avoid Windows warnings about minimum size for decorated windows. int width = 200; @@ -231,6 +250,40 @@ void tst_QWindow::setVisible() QVERIFY(QTest::qWaitForWindowExposed(&i)); } +class SurfaceCreatedWindow : public QWindow +{ + Q_OBJECT +public: + using QWindow::QWindow; + + bool eventFilter(QObject *o, QEvent *e) override + { + if (e->type() == QEvent::PlatformSurface) { + auto type = static_cast<QPlatformSurfaceEvent*>(e)->surfaceEventType(); + if (type == QPlatformSurfaceEvent::SurfaceCreated) + ++surfaceCreatedEvents; + } + return QWindow::eventFilter(o, e); + } + + int surfaceCreatedEvents = 0; +}; + +void tst_QWindow::setVisibleThenCreate() +{ + QWindow parent; + parent.setObjectName("Parent"); + SurfaceCreatedWindow child(&parent); + child.installEventFilter(&child); + child.setObjectName("Child"); + child.setVisible(true); + child.create(); + QCOMPARE(child.surfaceCreatedEvents, 1); + parent.setVisible(true); + QCOMPARE(child.surfaceCreatedEvents, 1); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + void tst_QWindow::setVisibleFalseDoesNotCreateWindow() { QWindow w; @@ -429,11 +482,16 @@ void tst_QWindow::resizeEventAfterResize() // Make sure we get a resizeEvent after calling resize window.resize(m_testWindowSize); + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QTRY_COMPARE(window.received(QEvent::Resize), 2); } void tst_QWindow::exposeEventOnShrink_QTBUG54040() { + if (isPlatformEglFS()) + QSKIP("", "eglfs windows are fullscreen by default.", Continue); Window window; window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.setTitle(QTest::currentTestFunction()); @@ -616,6 +674,8 @@ void tst_QWindow::childWindowPositioning_data() void tst_QWindow::childWindowPositioning() { + if (isPlatformEglFS()) + QSKIP("eglfs does not support child windows."); const QPoint topLeftOrigin(0, 0); ColoredWindow topLevelWindowFirst(Qt::green); @@ -1576,6 +1636,13 @@ void tst_QWindow::sizes() window.resize(80, 80); window.setMaximumSize(QSize(70, 70)); QCOMPARE(window.size(), QSize(70, 70)); + + // QTBUG-113233 + // test for an invalid min/max pair + window.setMinimumSize(QSize(80, 80)); // current maximumSize = QSize(70, 70) + QCOMPARE(window.size(), QSize(70, 70)); + window.setMaximumSize(QSize(90, 90)); + QCOMPARE(window.size(), QSize(80, 80)); } class CloseOnCloseEventWindow : public QWindow @@ -2080,6 +2147,10 @@ void tst_QWindow::initialSize() w.setTitle(QLatin1String(QTest::currentTestFunction())); w.setWidth(m_testWindowSize.width()); w.showNormal(); + + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QTRY_COMPARE(w.width(), m_testWindowSize.width()); QTRY_VERIFY(w.height() > 0); } @@ -2091,6 +2162,8 @@ void tst_QWindow::initialSize() w.showNormal(); const QSize expectedSize = testSize; + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); QTRY_COMPARE(w.size(), expectedSize); } } @@ -2266,6 +2339,10 @@ void tst_QWindow::modalWindowPosition() window.setModality(Qt::WindowModal); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); + + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QCOMPARE(window.geometry(), origGeo); } @@ -2294,6 +2371,9 @@ void tst_QWindow::modalWindowEnterEventOnHide_QTBUG35109() if (isPlatformOffscreenOrMinimal()) QSKIP("Can't test window focusing on offscreen/minimal"); + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); + const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center(); const int childOffset = 16; @@ -2474,6 +2554,8 @@ void tst_QWindow::spuriousMouseMove() QSKIP("No enter events sent"); if (platformName == QLatin1String("wayland")) QSKIP("Setting mouse cursor position is not possible on Wayland"); + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); const QPoint center = screenGeometry.center(); QCursor::setPos(center); @@ -2788,11 +2870,11 @@ void tst_QWindow::stateChangeSignal() "On other operating systems, the signal may be emitted twice."); #endif QWindow w; - Q_ASSUME(connect (&w, &QWindow::windowStateChanged, [](Qt::WindowState s){qCDebug(lcTests) << "State change to" << s;})); + Q_ASSERT(connect (&w, &QWindow::windowStateChanged, [](Qt::WindowState s){qCDebug(lcTests) << "State change to" << s;})); QSignalSpy spy(&w, SIGNAL(windowStateChanged(Qt::WindowState))); unsigned short signalCount = 0; QList<Qt::WindowState> effectiveStates; - Q_ASSUME(connect(&w, &QWindow::windowStateChanged, [&effectiveStates](Qt::WindowState state) + Q_ASSERT(connect(&w, &QWindow::windowStateChanged, [&effectiveStates](Qt::WindowState state) { effectiveStates.append(state); })); // Part 1: // => test signal emission on programmatic state changes @@ -2876,6 +2958,268 @@ void tst_QWindow::stateChangeSignal() CHECK_SIGNAL(Qt::WindowMinimized); } +#ifndef QT_NO_CURSOR +void tst_QWindow::enterLeaveOnWindowShowHide_data() +{ + QTest::addColumn<Qt::WindowType>("windowType"); + QTest::addRow("dialog") << Qt::Dialog; + QTest::addRow("popup") << Qt::Popup; +} + +/*! + Verify that we get enter and leave events if the window under the mouse + opens and closes a modal dialog or popup. QWindow might get multiple + events in a row, as the various QPA plugins need to use different techniques + to synthesize events if the native platform doesn't provide them for us. +*/ +void tst_QWindow::enterLeaveOnWindowShowHide() +{ + if (isPlatformWayland()) + QSKIP("Can't set cursor position and qWaitForWindowActive on Wayland"); + + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); + + QFETCH(Qt::WindowType, windowType); + + class Window : public QWindow + { + public: + int numEnterEvents = 0; + int numLeaveEvents = 0; + QPoint enterPosition; + protected: + bool event(QEvent *e) override + { + switch (e->type()) { + case QEvent::Enter: + ++numEnterEvents; + enterPosition = static_cast<QEnterEvent*>(e)->position().toPoint(); + break; + case QEvent::Leave: + ++numLeaveEvents; + break; + default: + break; + } + return QWindow::event(e); + } + }; + + int expectedEnter = 0; + int expectedLeave = 0; + + Window window; + const QRect screenGeometry = window.screen()->availableGeometry(); + const QPoint cursorPos = screenGeometry.topLeft() + QPoint(50, 50); + window.setGeometry(QRect(cursorPos - QPoint(50, 50), screenGeometry.size() / 4)); + QCursor::setPos(cursorPos); + + if (!QTest::qWaitFor([&]{ return window.geometry().contains(QCursor::pos()); })) + QSKIP("We can't move the cursor"); + + window.show(); + window.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + + ++expectedEnter; + QTRY_COMPARE_WITH_TIMEOUT(window.numEnterEvents, expectedEnter, 250); + QCOMPARE(window.enterPosition, window.mapFromGlobal(QCursor::pos())); + + QWindow secondary; + secondary.setFlag(windowType); + secondary.setModality(Qt::WindowModal); + secondary.setTransientParent(&window); + secondary.setPosition(cursorPos + QPoint(50, 50)); + secondary.show(); + QVERIFY(QTest::qWaitForWindowExposed(&secondary)); + ++expectedLeave; + QTRY_VERIFY(window.numLeaveEvents >= expectedLeave); + secondary.close(); + ++expectedEnter; + QTRY_VERIFY(window.numEnterEvents >= expectedEnter); + QCOMPARE(window.enterPosition, window.mapFromGlobal(QCursor::pos())); +} +#endif + +void tst_QWindow::windowExposedAfterReparent() +{ + QWindow parent; + QWindow child(&parent); + child.show(); + parent.show(); + + QVERIFY(QTest::qWaitForWindowExposed(&parent)); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(nullptr); + QCoreApplication::processEvents(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(&parent); + QCoreApplication::processEvents(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + +struct ParentWindow : public QWindow +{ + bool event(QEvent *event) override + { + [&]() -> void { + if (event->type() == QEvent::ChildWindowAdded + || event->type() == QEvent::ChildWindowRemoved) { + // We should not receive child events after the window has been destructed + QVERIFY(this->isWindowType()); + + auto *parentWindow = this; + auto *childEvent = static_cast<QChildWindowEvent*>(event); + auto *childWindow = childEvent->child(); + + if (event->type() == QEvent::ChildWindowAdded) { + QVERIFY(childWindow->parent()); + QVERIFY(parentWindow->isAncestorOf(childWindow)); + if (childWindow->handle()) + QVERIFY(childWindow->handle()->parent() == parentWindow->handle()); + + } else { + QVERIFY(!childWindow->parent()); + QVERIFY(!parentWindow->isAncestorOf(childWindow)); + if (childWindow->handle()) + QVERIFY(childWindow->handle()->parent() != parentWindow->handle()); + } + } + }(); + + return QWindow::event(event); + } +}; + +void tst_QWindow::childEvents() +{ + ParentWindow parent; + + { + // ChildAdded via constructor + QWindow constructorChild(&parent); + if (QTest::currentTestFailed()) return; + // ChildRemoved via destructor + } + + if (QTest::currentTestFailed()) return; + + // ChildAdded and ChildRemoved via setParent + QWindow child; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; + + parent.create(); + child.create(); + + // ChildAdded and ChildRemoved after creation + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; +} + +struct ChildWindowPrivate; +struct ChildWindow : public QWindow +{ + ChildWindow(QWindow *parent = nullptr); +}; + +struct ChildWindowPrivate : public QWindowPrivate +{ + ChildWindowPrivate() : QWindowPrivate() + { + receiveParentEvents = true; + } +}; + +ChildWindow::ChildWindow(QWindow *parent) + : QWindow(*new ChildWindowPrivate, parent) +{} + +struct ParentEventTester : public QObject +{ + bool eventFilter(QObject *object, QEvent *event) override + { + [&]() -> void { + if (event->type() == QEvent::ParentWindowAboutToChange + || event->type() == QEvent::ParentWindowChange) { + // We should not receive parent events after the window has been destructed + QVERIFY(object->isWindowType()); + auto *window = static_cast<QWindow*>(object); + + if (event->type() == QEvent::ParentWindowAboutToChange) { + QVERIFY(window->parent() != nextExpectedParent); + if (window->handle()) { + QVERIFY(window->handle()->parent() != + (nextExpectedParent ? nextExpectedParent->handle() : nullptr)); + } + } else { + QVERIFY(window->parent() == nextExpectedParent); + if (window->handle()) { + QVERIFY(window->handle()->parent() == + (nextExpectedParent ? nextExpectedParent->handle() : nullptr)); + } + } + } + }(); + + return QObject::eventFilter(object, event); + } + + QWindow *nextExpectedParent = nullptr; +}; + + + +void tst_QWindow::parentEvents() +{ + QWindow parent; + + { + ParentEventTester tester; + + { + // We can't hook in early enough to get the parent change during + // QObject construction. + ChildWindow child(&parent); + + // But we can observe the one during destruction + child.installEventFilter(&tester); + tester.nextExpectedParent = nullptr; + } + } + if (QTest::currentTestFailed()) return; + + ParentEventTester tester; + ChildWindow child; + child.installEventFilter(&tester); + + tester.nextExpectedParent = &parent; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + + tester.nextExpectedParent = nullptr; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; + + parent.create(); + child.create(); + + tester.nextExpectedParent = &parent; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + + tester.nextExpectedParent = nullptr; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; +} + #include <tst_qwindow.moc> QTEST_MAIN(tst_QWindow) |