diff options
Diffstat (limited to 'tests/auto/gui/kernel/qwindow')
-rw-r--r-- | tests/auto/gui/kernel/qwindow/BLACKLIST | 7 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/CMakeLists.txt | 16 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp | 229 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 355 |
4 files changed, 520 insertions, 87 deletions
diff --git a/tests/auto/gui/kernel/qwindow/BLACKLIST b/tests/auto/gui/kernel/qwindow/BLACKLIST index 328d3f51b1..69df8883c8 100644 --- a/tests/auto/gui/kernel/qwindow/BLACKLIST +++ b/tests/auto/gui/kernel/qwindow/BLACKLIST @@ -11,8 +11,6 @@ windows # QTBUG-69162 windows-10 android -[testInputEvents] -rhel-7.4 [modalWindowPosition] # QTBUG-69161 android @@ -23,3 +21,8 @@ android android [modalWindowModallity] android +[enterLeaveOnWindowShowHide] +windows-10 +windows-11 +android +rhel diff --git a/tests/auto/gui/kernel/qwindow/CMakeLists.txt b/tests/auto/gui/kernel/qwindow/CMakeLists.txt index 605761622f..5824989ac3 100644 --- a/tests/auto/gui/kernel/qwindow/CMakeLists.txt +++ b/tests/auto/gui/kernel/qwindow/CMakeLists.txt @@ -5,6 +5,12 @@ ## tst_qwindow Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwindow LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwindow SOURCES tst_qwindow.cpp @@ -14,8 +20,9 @@ qt_internal_add_test(tst_qwindow Qt::GuiPrivate ) -if(APPLE OR WIN32) +if(APPLE OR WIN32 OR QT_FEATURE_xcb) qt_internal_add_test(tst_foreignwindow + LOWDPI SOURCES tst_foreignwindow.cpp LIBRARIES @@ -25,9 +32,14 @@ if(APPLE OR WIN32) ) if(APPLE) - target_compile_options(tst_foreignwindow PRIVATE -x objective-c++) + enable_language(OBJCXX) + set_source_files_properties(tst_foreignwindow.cpp PROPERTIES LANGUAGE OBJCXX) set_property(TARGET tst_foreignwindow PROPERTY PROPERTY MACOSX_BUNDLE TRUE) endif() + + if(QT_FEATURE_xcb) + target_link_libraries(tst_foreignwindow PRIVATE XCB::XCB) + endif() endif() ## Scopes: diff --git a/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp index 256564f6bc..fdb1b333ef 100644 --- a/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2023 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 <QTest> @@ -7,114 +7,189 @@ #include <private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> -#if defined(Q_OS_MACOS) -# include <AppKit/AppKit.h> -#elif defined(Q_OS_WIN) -# include <winuser.h> -#endif - -Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests") +#include "../../../../shared/nativewindow.h" -class NativeWindow +class tst_ForeignWindow: public QObject { - Q_DISABLE_COPY(NativeWindow) -public: - NativeWindow(); - ~NativeWindow(); - - operator WId() const { return reinterpret_cast<WId>(m_handle); } - -private: -#if defined(Q_OS_MACOS) - NSView *m_handle = nullptr; -#elif defined(Q_OS_WIN) - HWND m_handle = nullptr; -#endif -}; + Q_OBJECT -#if defined(Q_OS_MACOS) +private slots: + void initTestCase() + { + auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + if (!platformIntegration->hasCapability(QPlatformIntegration::ForeignWindows)) + QSKIP("This platform does not support foreign windows"); + } -@interface View : NSView -@end + void fromWinId(); + void initialState(); -@implementation View -- (instancetype)init -{ - if ((self = [super init])) { - qCDebug(lcTests) << "Initialized" << self; - } - return self; -} + void embedForeignWindow(); + void embedInForeignWindow(); -- (void)dealloc -{ - qCDebug(lcTests) << "Deallocating" << self; - [super dealloc]; -} -@end + void destroyExplicitly(); + void destroyWhenParentIsDestroyed(); +}; -NativeWindow::NativeWindow() - : m_handle([View new]) +void tst_ForeignWindow::fromWinId() { + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + QVERIFY(foreignWindow); + QVERIFY(foreignWindow->flags().testFlag(Qt::ForeignWindow)); + QVERIFY(foreignWindow->handle()); + + // fromWinId does not take (exclusive) ownership of the native window, + // so deleting the foreign window should not be a problem/cause crashes. + foreignWindow.reset(); } -NativeWindow::~NativeWindow() +void tst_ForeignWindow::initialState() { - [m_handle release]; -} + NativeWindow nativeWindow; + QVERIFY(nativeWindow); -#elif defined(Q_OS_WIN) + // A foreign window can be used to embed a Qt UI in a foreign window hierarchy, + // in which case the foreign window merely acts as a parent and should not be + // modified, or to embed a foreign window in a Qt UI, in which case the foreign + // window must to be able to re-parent, move, resize, show, etc, so that the + // containing Qt UI can treat it as any other window. -NativeWindow::NativeWindow() -{ - static const LPCWSTR className = []{ - WNDCLASS wc = {}; - wc.lpfnWndProc = DefWindowProc; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpszClassName = L"Native Window"; - RegisterClass(&wc); - return wc.lpszClassName; - }(); - m_handle = CreateWindowEx(0, className, nullptr, WS_POPUP, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + // At the point of creation though, we don't know what the foreign window + // will be used for, so the platform should not assume it can modify the + // window. Any properties set on the native window should persist past + // creation of the foreign window. + + const QRect initialGeometry(123, 456, 321, 654); + nativeWindow.setGeometry(initialGeometry); + QTRY_COMPARE(nativeWindow.geometry(), initialGeometry); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + QCOMPARE(nativeWindow.geometry(), initialGeometry); + + // For extra bonus points, the foreign window should actually + // reflect the state of the native window. + QCOMPARE(foreignWindow->geometry(), initialGeometry); } -NativeWindow::~NativeWindow() +void tst_ForeignWindow::embedForeignWindow() { - DestroyWindow(m_handle); -} + // A foreign window embedded into a Qt UI requires that the rest of Qt + // is to be able to treat the foreign child window as any other window + // that it can show, hide, stack, and move around. + + QWindow parentWindow; + + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + // As a prerequisite to that, we must be able to reparent the foreign window + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + foreignWindow->setParent(&parentWindow); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + // FIXME: This test is flakey on Linux. Figure out why +#if !defined(Q_OS_LINUX) + foreignWindow->setParent(nullptr); + QTRY_VERIFY(nativeWindow.parentWinId() != parentWindow.winId()); #endif +} -class tst_ForeignWindow: public QObject +void tst_ForeignWindow::embedInForeignWindow() { - Q_OBJECT + // When a foreign window is used as a container to embed a Qt UI + // in a foreign window hierarchy, the foreign window merely acts + // as a parent, and should not be modified. -private slots: - void initTestCase() { - auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); - if (!platformIntegration->hasCapability(QPlatformIntegration::ForeignWindows)) - QSKIP("This platform does not support foreign windows"); + // At a minimum, we must be able to reparent into the window + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(nativeWindow.isParentOf(embeddedWindow.winId())); } - void fromWinId(); -}; + { + // The foreign window's native window should not be reparent as a + // result of creating the foreign window, adding and removing children, + // or destroying the foreign window. -void tst_ForeignWindow::fromWinId() + NativeWindow topLevelNativeWindow; + NativeWindow childNativeWindow; + childNativeWindow.setParent(topLevelNativeWindow); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(childNativeWindow)); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(childNativeWindow.isParentOf(embeddedWindow.winId())); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + embeddedWindow.setParent(nullptr); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + foreignWindow.reset(); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + } +} + +void tst_ForeignWindow::destroyExplicitly() { NativeWindow nativeWindow; QVERIFY(nativeWindow); std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); - QVERIFY(foreignWindow); - QVERIFY(foreignWindow->flags().testFlag(Qt::ForeignWindow)); QVERIFY(foreignWindow->handle()); - // fromWinId does not take (exclusive) ownership of the native window, - // so deleting the foreign window should not be a problem/cause crashes. - foreignWindow.reset(); + // Explicitly destroying a foreign window is a no-op, as + // the documentation claims that it "releases the native + // platform resources associated with this window.", which + // is not technically true for foreign windows. + auto *windowHandleBeforeDestroy = foreignWindow->handle(); + foreignWindow->destroy(); + QCOMPARE(foreignWindow->handle(), windowHandleBeforeDestroy); +} + +void tst_ForeignWindow::destroyWhenParentIsDestroyed() +{ + QWindow parentWindow; + + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + foreignWindow->setParent(&parentWindow); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + + // Reparenting into a window will result in creating it + QVERIFY(parentWindow.handle()); + + parentWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parentWindow)); + + // Destroying the parent window of the foreign window results + // in destroying the foreign window as well, as the foreign + // window no longer has a parent it can be embedded in. + QVERIFY(foreignWindow->handle()); + parentWindow.destroy(); + QVERIFY(!foreignWindow->handle()); + + // But the foreign window can be recreated again, and will + // continue to be a native child of the parent window. + foreignWindow->create(); + QVERIFY(foreignWindow->handle()); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + + parentWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parentWindow)); } #include <tst_foreignwindow.moc> diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index 1cac1820d9..227fd77e1a 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); @@ -1449,6 +1509,7 @@ void tst_QWindow::touchCancelWithTouchToMouse() void tst_QWindow::touchInterruptedByPopup() { InputTestWindow window; + window.setObjectName("main"); window.setTitle(QLatin1String(QTest::currentTestFunction())); window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.show(); @@ -1469,6 +1530,7 @@ void tst_QWindow::touchInterruptedByPopup() // Launch a popup window InputTestWindow popup; + window.setObjectName("popup"); popup.setFlags(Qt::Popup); popup.setModality(Qt::WindowModal); popup.resize(m_testWindowSize / 2); @@ -1491,9 +1553,6 @@ void tst_QWindow::touchInterruptedByPopup() QWindowSystemInterface::handleTouchEvent(&window, touchDevice, points); QCoreApplication::processEvents(); QTRY_COMPARE(window.touchReleasedCount, 0); - - // Due to temporary fix for QTBUG-37371: the original window should receive a TouchCancel - QTRY_COMPARE(window.touchEventType, QEvent::TouchCancel); } void tst_QWindow::orientation() @@ -1576,6 +1635,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 +2146,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 +2161,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 +2338,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 +2370,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 +2553,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 +2869,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 +2957,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) |