diff options
author | Axel Spoerl <axel.spoerl@qt.io> | 2022-10-28 15:23:41 +0200 |
---|---|---|
committer | Axel Spoerl <axel.spoerl@qt.io> | 2022-11-11 11:21:32 +0100 |
commit | 52dcd47850138ac257e3ad7e6eeae205bcff4aa6 (patch) | |
tree | 9956dc42b22bfff88db21ece78acb29ed168723f /tests | |
parent | b58876c296a5a87f50d5e554afc277e5bc752a16 (diff) |
emit QWindow::windowStateChanged only when state has changed
Upon programmatic window state changes, windowStateChange was fired
once in QWindow::setWindowStates and once when the visual state had
been changed by the window interface.
This patch adds if guards to ensure that the singal is fired only once.
It adds a corresponding autotest to tst_QWindow.
tst_QWidget::resizePropagation() is adapted to no longer expect double
signal emission.
Fixes: QTBUG-102478
Pick-to: 6.4 6.2
Change-Id: If093c0a883d76d8a676e4fab90db6b0676452267
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 98 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp | 65 |
2 files changed, 139 insertions, 24 deletions
diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index d2d89c0ebf..1cac1820d9 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -88,6 +88,7 @@ private slots: void activateDeactivateEvent(); void qobject_castOnDestruction(); void touchToMouseTranslationByPopup(); + void stateChangeSignal(); private: QPoint m_availableTopLeft; @@ -2778,6 +2779,103 @@ void tst_QWindow::touchToMouseTranslationByPopup() QTRY_COMPARE(window.mouseReleaseButton, int(Qt::LeftButton)); } +// Test that windowStateChanged is not emitted on noop change (QTBUG-102478) +void tst_QWindow::stateChangeSignal() +{ + // Test only for Windows, Linux and macOS +#if !defined(Q_OS_LINUX) && !defined(Q_OS_WINDOWS) && !defined(Q_OS_DARWIN) + QSKIP("Singular windowStateChanged signal emission is guaranteed for Linux, Windows and macOS only.\n" + "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;})); + 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) + { effectiveStates.append(state); })); + // Part 1: + // => test signal emission on programmatic state changes + QCOMPARE(w.windowState(), Qt::WindowNoState); + // - wait for target state to be set + // - wait for signal spy to have reached target count + // - extract state from signal and compare to target +#define CHECK_STATE(State)\ + QTRY_VERIFY(QTest::qWaitFor([&w](){return (w.windowState() == State); }));\ + CHECK_SIGNAL(State) +#define CHECK_SIGNAL(State)\ + QTRY_COMPARE(spy.count(), signalCount);\ + if (signalCount > 0) {\ + QVariantList list = spy.at(signalCount - 1).toList();\ + QCOMPARE(list.count(), 1);\ + bool ok;\ + const int stateInt = list.at(0).toInt(&ok);\ + QVERIFY(ok);\ + const Qt::WindowState newState = static_cast<Qt::WindowState>(stateInt);\ + QCOMPARE(newState, State);\ + } + // Check initialization + CHECK_STATE(Qt::WindowNoState); + // showMaximized after init + // expected behavior: signal emitted once with state == WindowMaximized + ++signalCount; + w.showMaximized(); + CHECK_STATE(Qt::WindowMaximized); + // setWindowState to normal + // expected behavior: signal emitted once with state == WindowNoState + ++signalCount; + w.setWindowState(Qt::WindowNoState); + CHECK_STATE(Qt::WindowNoState); + // redundant setWindowState to normal - except windows, where the no-op is counted + // expected behavior: No emits. + // On Windows, a no-op state change causes a no-op resize and repaint, leading to a + // no-op state change and singal emission. +#ifdef Q_OS_WINDOWS + ++signalCount; + ++signalCount; +#endif + w.setWindowState(Qt::WindowNoState); + CHECK_STATE(Qt::WindowNoState); + // setWindowState to minimized + // expected behavior: signal emitted once with state == WindowMinimized + ++signalCount; + w.showMinimized(); + CHECK_STATE(Qt::WindowMinimized); + // setWindowState to Normal + // expected behavior: signal emitted once with state == WindowNoState + ++signalCount; + w.showNormal(); + CHECK_STATE(Qt::WindowNoState); + /* + - Testcase showFullScreen is omitted: Depending on window manager, + WindowFullScreen can be mapped to WindowMaximized + - Transition from WindowMinimized to WindowMaximized is omitted: + WindowNoState to WindowMaximized + */ + // Part 2: + // => test signal emission on simulated user interaction + // To test the code path, inject state change events into the QPA event queue. + // Test the signal emission only, not the window's actual visible state. + + // Flush pending events and clear + QCoreApplication::processEvents(); + spy.clear(); + effectiveStates.clear(); + signalCount = 0; + // Maximize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowMaximized, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowMaximized); + // Normalize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowNoState, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowNoState); + // Minimize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowMinimized, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowMinimized); +} + #include <tst_qwindow.moc> QTEST_MAIN(tst_QWindow) diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index abc939ce93..1e58b35e88 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -2752,18 +2752,15 @@ void tst_QWidget::resizePropagation() QSKIP("resizePropagation test is designed for XCB only"); #endif - // Platform specific notes: - // Linux/XCB: - // - Unless maximized, a widget and its corresponding window can have different sizes - // - windowStateChanged can be fired multiple times (QTBUG-102478) when widget is maximized - // // Windows: // When a widget is maximized after it has been resized, the widget retains its original size, - // while the window shows maximum size + // while the window shows maximum size. + // windowStateChanged signal gets fired on a no-op change from/to WindowNoState // Initialize widget and signal spy for window handle QWidget widget; widget.showMaximized(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); QWindow *window = widget.windowHandle(); QTRY_VERIFY(window); QSignalSpy spy(window, &QWindow::windowStateChanged); @@ -2773,11 +2770,15 @@ void tst_QWidget::resizePropagation() const QSize size1 = QSize(screenSize.width() * 0.5, screenSize.height() * 0.5); const QSize size2 = QSize(screenSize.width() * 0.625, screenSize.height() * 0.833); - auto verifyResize = [&](const QSize &size, Qt::WindowState windowState, bool checkCountIncrement, bool checkTargetSize) + enum CountIncrementCheck {Equal, Greater}; + enum TargetSizeCheck {Fail, Warn}; + auto verifyResize = [&](const QSize &size, Qt::WindowState windowState, + CountIncrementCheck checkCountIncrement, + TargetSizeCheck checkTargetSize) { // Capture count of latest async signals - if (!checkCountIncrement) - count = spy.size(); + if (checkCountIncrement == Equal) + count = spy.count(); // Resize if required if (size.isValid()) @@ -2787,13 +2788,24 @@ void tst_QWidget::resizePropagation() QVERIFY(QTest::qWaitForWindowExposed(&widget)); // Check signal count and qDebug output for fail analysis - if (checkCountIncrement) { - QTRY_VERIFY(spy.size() > count); - qDebug() << "spy count:" << spy.size() << "previous count:" << count; - count = spy.size(); - } else { - qDebug() << spy << widget.windowState() << window->windowState(); - QCOMPARE(spy.size(), count); + switch (checkCountIncrement) { + case Greater: { + auto logger = qScopeGuard([&](){ + qDebug() << "spy count:" << spy.count() << "previous count:" << count; + }); + QTRY_VERIFY(spy.count() > count); + logger.dismiss(); + count = spy.count(); + } + break; + case Equal: { + auto logger = qScopeGuard([&](){ + qDebug() << spy << widget.windowState() << window->windowState(); + }); + QCOMPARE(spy.count(), count); + logger.dismiss(); + } + break; } // QTRY necessary because state changes are propagated async @@ -2801,32 +2813,37 @@ void tst_QWidget::resizePropagation() QTRY_COMPARE(window->windowState(), windowState); // Check target size with fail or warning - if (checkTargetSize) { + switch (checkTargetSize) { + case Fail: QCOMPARE(widget.size(), window->size()); - } else if (widget.size() != window->size()) { - qWarning() << m_platform << "size mismtach tolerated. Widget:" - << widget.size() << "Window:" << window->size(); + break; + case Warn: + if (widget.size() != window->size()) { + qWarning() << m_platform << "size mismtach tolerated. Widget:" + << widget.size() << "Window:" << window->size(); + } + break; } }; // test state and size consistency of maximized window - verifyResize(QSize(), Qt::WindowMaximized, true, true); + verifyResize(QSize(), Qt::WindowMaximized, Equal, Fail); if (QTest::currentTestFailed()) return; // test state transition, state and size consistency after resize - verifyResize(size1, Qt::WindowNoState, true, !xcb ); + verifyResize(size1, Qt::WindowNoState, Greater, xcb ? Warn : Fail ); if (QTest::currentTestFailed()) return; // test unchanged state, state and size consistency after resize - verifyResize(size2, Qt::WindowNoState, false, !xcb); + verifyResize(size2, Qt::WindowNoState, Equal, xcb ? Warn : Fail); if (QTest::currentTestFailed()) return; // test state transition, state and size consistency after maximize widget.showMaximized(); - verifyResize(QSize(), Qt::WindowMaximized, true, xcb); + verifyResize(QSize(), Qt::WindowMaximized, Greater, xcb ? Fail : Warn); if (QTest::currentTestFailed()) return; |