diff options
Diffstat (limited to 'tests/auto/widgets/kernel')
62 files changed, 5500 insertions, 1707 deletions
diff --git a/tests/auto/widgets/kernel/CMakeLists.txt b/tests/auto/widgets/kernel/CMakeLists.txt index c94c48aaa4..2d4880ea3c 100644 --- a/tests/auto/widgets/kernel/CMakeLists.txt +++ b/tests/auto/widgets/kernel/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from kernel.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause add_subdirectory(qapplication) add_subdirectory(qboxlayout) @@ -9,16 +10,14 @@ add_subdirectory(qstackedlayout) add_subdirectory(qtooltip) add_subdirectory(qwidget_window) add_subdirectory(qwidgetmetatype) +add_subdirectory(qwidgetrepaintmanager) add_subdirectory(qwidgetsvariant) add_subdirectory(qwindowcontainer) add_subdirectory(qsizepolicy) if(NOT APPLE) add_subdirectory(qgesturerecognizer) endif() -# QTBUG-87668 # special case -if(NOT ANDROID) - add_subdirectory(qwidget) -endif() +add_subdirectory(qwidget) if(QT_FEATURE_shortcut) add_subdirectory(qshortcut) endif() diff --git a/tests/auto/widgets/kernel/qaction/CMakeLists.txt b/tests/auto/widgets/kernel/qaction/CMakeLists.txt index dd70d5448f..9d1985da0b 100644 --- a/tests/auto/widgets/kernel/qaction/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qaction/CMakeLists.txt @@ -1,15 +1,23 @@ -# Generated from qaction.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qaction Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qaction LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qaction SOURCES tst_qaction.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/kernel/qaction/tst_qaction.cpp b/tests/auto/widgets/kernel/qaction/tst_qaction.cpp index 63d49e1216..2820fd710b 100644 --- a/tests/auto/widgets/kernel/qaction/tst_qaction.cpp +++ b/tests/auto/widgets/kernel/qaction/tst_qaction.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QDialog> #include <QMainWindow> @@ -36,10 +11,14 @@ #include <qaction.h> #include <qactiongroup.h> #include <qmenu.h> +#include <qmenubar.h> +#include <qtoolbar.h> #include <qpa/qplatformtheme.h> #include <qpa/qplatformintegration.h> #include <private/qguiapplication_p.h> +#include <QtWidgets/private/qapplication_p.h> + class tst_QAction : public QObject { Q_OBJECT @@ -65,6 +44,8 @@ private slots: void disableShortcutsWithBlockedWidgets_data(); void disableShortcutsWithBlockedWidgets(); void shortcutFromKeyEvent(); // QTBUG-48325 + void disableShortcutInMenuAction_data(); + void disableShortcutInMenuAction(); #endif private: @@ -132,7 +113,6 @@ void tst_QAction::actionEvent() // add action MyWidget testWidget(this); testWidget.show(); - QApplication::setActiveWindow(&testWidget); testWidget.addAction(&a); qApp->processEvents(); @@ -162,7 +142,7 @@ void tst_QAction::alternateShortcuts() MyWidget testWidget(this); testWidget.show(); - QApplication::setActiveWindow(&testWidget); + QApplicationPrivate::setActiveWindow(&testWidget); { QAction act(&testWidget); @@ -174,11 +154,11 @@ void tst_QAction::alternateShortcuts() act.setAutoRepeat(true); QTest::keyClick(&testWidget, Qt::Key_A, Qt::ControlModifier); - QCOMPARE(spy.count(), 1); //act should have been triggered + QCOMPARE(spy.size(), 1); //act should have been triggered act.setAutoRepeat(false); QTest::keyClick(&testWidget, Qt::Key_A, Qt::ControlModifier); - QCOMPARE(spy.count(), 2); //act should have been triggered a 2nd time + QCOMPARE(spy.size(), 2); //act should have been triggered a 2nd time //end of the scope of the action, it will be destroyed and removed from wid //This action should also unregister its shortcuts @@ -193,7 +173,7 @@ void tst_QAction::keysequence() { MyWidget testWidget(this); testWidget.show(); - QApplication::setActiveWindow(&testWidget); + QApplicationPrivate::setActiveWindow(&testWidget); { QAction act(&testWidget); @@ -208,12 +188,12 @@ void tst_QAction::keysequence() act.setAutoRepeat(true); QTest::keySequence(&testWidget, ks); QCoreApplication::processEvents(); - QCOMPARE(spy.count(), 1); // act should have been triggered + QCOMPARE(spy.size(), 1); // act should have been triggered act.setAutoRepeat(false); QTest::keySequence(&testWidget, ks); QCoreApplication::processEvents(); - QCOMPARE(spy.count(), 2); //act should have been triggered a 2nd time + QCOMPARE(spy.size(), 2); //act should have been triggered a 2nd time // end of the scope of the action, it will be destroyed and removed from widget // This action should also unregister its shortcuts @@ -227,7 +207,7 @@ void tst_QAction::enabledVisibleInteraction() { MyWidget testWidget(this); testWidget.show(); - QApplication::setActiveWindow(&testWidget); + QApplicationPrivate::setActiveWindow(&testWidget); QAction act(nullptr); // check defaults @@ -249,15 +229,15 @@ void tst_QAction::enabledVisibleInteraction() act.setEnabled(true); act.setVisible(false); QTest::keyClick(&testWidget, Qt::Key_T, Qt::ControlModifier); - QCOMPARE(spy.count(), 0); //act is not visible, so don't trigger + QCOMPARE(spy.size(), 0); //act is not visible, so don't trigger act.setVisible(false); act.setEnabled(true); QTest::keyClick(&testWidget, Qt::Key_T, Qt::ControlModifier); - QCOMPARE(spy.count(), 0); //act is not visible, so don't trigger + QCOMPARE(spy.size(), 0); //act is not visible, so don't trigger act.setVisible(true); act.setEnabled(true); QTest::keyClick(&testWidget, Qt::Key_T, Qt::ControlModifier); - QCOMPARE(spy.count(), 1); //act is visible and enabled, so trigger + QCOMPARE(spy.size(), 1); //act is visible and enabled, so trigger } #endif // QT_CONFIG(shortcut) @@ -275,12 +255,12 @@ void tst_QAction::task229128TriggeredSignalWhenInActiongroup() QSignalSpy actionSpy(checkedAction, QOverload<bool>::of(&QAction::triggered)); QSignalSpy actionGroupSpy(&ag, QOverload<QAction*>::of(&QActionGroup::triggered)); - QCOMPARE(actionGroupSpy.count(), 0); - QCOMPARE(actionSpy.count(), 0); + QCOMPARE(actionGroupSpy.size(), 0); + QCOMPARE(actionSpy.size(), 0); checkedAction->trigger(); // check that both the group and the action have emitted the signal - QCOMPARE(actionGroupSpy.count(), 1); - QCOMPARE(actionSpy.count(), 1); + QCOMPARE(actionGroupSpy.size(), 1); + QCOMPARE(actionSpy.size(), 1); } #if QT_CONFIG(shortcut) @@ -292,7 +272,6 @@ void tst_QAction::repeat() MyWidget testWidget(this); testWidget.show(); - QApplication::setActiveWindow(&testWidget); QVERIFY(QTest::qWaitForWindowActive(&testWidget)); QAction act(&testWidget); @@ -303,7 +282,7 @@ void tst_QAction::repeat() act.setAutoRepeat(true); QTest::keyPress(&testWidget, Qt::Key_F); QTest::keyRelease(&testWidget, Qt::Key_F); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); QTest::keyPress(&testWidget, Qt::Key_F); @@ -311,7 +290,7 @@ void tst_QAction::repeat() QTest::simulateEvent(&testWidget, true, Qt::Key_F, Qt::NoModifier, QString("f"), true); QTest::simulateEvent(&testWidget, true, Qt::Key_F, Qt::NoModifier, QString("f"), true); QTest::keyRelease(&testWidget, Qt::Key_F); - QCOMPARE(spy.count(), 3); + QCOMPARE(spy.size(), 3); spy.clear(); act.setAutoRepeat(false); @@ -319,14 +298,14 @@ void tst_QAction::repeat() QTest::simulateEvent(&testWidget, true, Qt::Key_F, Qt::NoModifier, QString("f"), true); QTest::simulateEvent(&testWidget, true, Qt::Key_F, Qt::NoModifier, QString("f"), true); QTest::keyRelease(&testWidget, Qt::Key_F); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); act.setAutoRepeat(true); QTest::keyPress(&testWidget, Qt::Key_F); QTest::simulateEvent(&testWidget, true, Qt::Key_F, Qt::NoModifier, QString("f"), true); QTest::keyRelease(&testWidget, Qt::Key_F); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); } void tst_QAction::disableShortcutsWithBlockedWidgets_data() @@ -371,12 +350,11 @@ void tst_QAction::disableShortcutsWithBlockedWidgets() dialog.show(); QVERIFY(QTest::qWaitForWindowExposed(&dialog)); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QSignalSpy spy(&action, &QAction::triggered); QTest::keyPress(&window, Qt::Key_1); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); } class ShortcutOverrideWidget : public QWidget @@ -414,10 +392,83 @@ void tst_QAction::shortcutFromKeyEvent() // shortcut route for us QKeyEvent e(QEvent::KeyPress, Qt::Key_1, Qt::NoModifier); QApplication::sendEvent(&testWidget, &e); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(testWidget.shortcutOverrideCount, 1); } +/* + Ignore actions in menus whose menu action has been hidden or disabled. + The menu entry will not be in the menu bar or parent menu, so the action + is not reachable through interactive means. QTBUG-25743 +*/ +void tst_QAction::disableShortcutInMenuAction_data() +{ + QTest::addColumn<QByteArray>("property"); + + QTest::addRow("visible") << QByteArray("visible"); + QTest::addRow("enabled") << QByteArray("enabled"); +} + +void tst_QAction::disableShortcutInMenuAction() +{ + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QFETCH(QByteArray, property); + + QMainWindow mw; + QMenu *testMenu = mw.menuBar()->addMenu("Test"); + QAction *testAction = testMenu->addAction("Test Action"); + testAction->setShortcut(Qt::ControlModifier | Qt::Key_A); + QToolBar *toolBar = new QToolBar; + mw.addToolBar(toolBar); + + mw.show(); + QVERIFY(QTest::qWaitForWindowActive(&mw)); + + int expectedTriggerCount = 0; + QSignalSpy spy(testAction, &QAction::triggered); + + QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::ControlModifier); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), ++expectedTriggerCount); + + testMenu->menuAction()->setProperty(property, false); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), expectedTriggerCount); + + testMenu->menuAction()->setProperty(property, true); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), ++expectedTriggerCount); + + // If the action lives somewhere else, then keep firing even + // if the menu has been hidden or disabled. + toolBar->addAction(testAction); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), ++expectedTriggerCount); + + testMenu->menuAction()->setProperty(property, false); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), ++expectedTriggerCount); + + // unless all other widgets in which the action lives have + // been hidden... + toolBar->hide(); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), expectedTriggerCount); + + // ... or disabled + toolBar->show(); + toolBar->setEnabled(false); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), expectedTriggerCount); + + // back to normal + toolBar->setEnabled(true); + QApplication::sendEvent(&mw, &event); + QCOMPARE(spy.size(), ++expectedTriggerCount); +} + #endif // QT_CONFIG(shortcut) QTEST_MAIN(tst_QAction) diff --git a/tests/auto/widgets/kernel/qactiongroup/CMakeLists.txt b/tests/auto/widgets/kernel/qactiongroup/CMakeLists.txt index 927802103c..e26ee75bc1 100644 --- a/tests/auto/widgets/kernel/qactiongroup/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qactiongroup/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qactiongroup.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qactiongroup Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qactiongroup LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qactiongroup SOURCES tst_qactiongroup.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qactiongroup/tst_qactiongroup.cpp b/tests/auto/widgets/kernel/qactiongroup/tst_qactiongroup.cpp index 7462fd8bbf..0d42340bfb 100644 --- a/tests/auto/widgets/kernel/qactiongroup/tst_qactiongroup.cpp +++ b/tests/auto/widgets/kernel/qactiongroup/tst_qactiongroup.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> diff --git a/tests/auto/widgets/kernel/qapplication/BLACKLIST b/tests/auto/widgets/kernel/qapplication/BLACKLIST index cce10602e4..c68c7d6b14 100644 --- a/tests/auto/widgets/kernel/qapplication/BLACKLIST +++ b/tests/auto/widgets/kernel/qapplication/BLACKLIST @@ -1,5 +1,3 @@ -[sendEventsOnProcessEvents] -ubuntu-20.04 [touchEventPropagation] # QTBUG-66745 opensuse-leap diff --git a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt index 3bb7e31691..524db06560 100644 --- a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt @@ -1,5 +1,17 @@ -# Generated from qapplication.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qapplication LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() add_subdirectory(desktopsettingsaware) add_subdirectory(modal) add_subdirectory(test) + +add_dependencies(tst_qapplication + desktopsettingsaware_helper + modal_helper +) diff --git a/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/CMakeLists.txt index a391d5b13d..f094a451d2 100644 --- a/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/CMakeLists.txt @@ -1,15 +1,16 @@ -# Generated from desktopsettingsaware.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## desktopsettingsaware Binary: ##################################################################### -qt_internal_add_executable(desktopsettingsaware_helper # special case +qt_internal_add_executable(desktopsettingsaware_helper SOURCES main.cpp - OUTPUT_DIRECTORY # special case - ${CMAKE_CURRENT_BINARY_DIR}/.. # special case - PUBLIC_LIBRARIES + OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/.. + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/main.cpp b/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/main.cpp index 5e86bcb529..1bf3eef55c 100644 --- a/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/main.cpp +++ b/tests/auto/widgets/kernel/qapplication/desktopsettingsaware/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QApplication> diff --git a/tests/auto/widgets/kernel/qapplication/modal/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/modal/CMakeLists.txt index 95c429f936..79c5660650 100644 --- a/tests/auto/widgets/kernel/qapplication/modal/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/modal/CMakeLists.txt @@ -1,16 +1,17 @@ -# Generated from modal.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## modal Binary: ##################################################################### -qt_internal_add_executable(modal_helper # special case +qt_internal_add_executable(modal_helper SOURCES base.cpp base.h main.cpp - OUTPUT_DIRECTORY # special case - ${CMAKE_CURRENT_BINARY_DIR}/.. # special case - PUBLIC_LIBRARIES + OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/.. + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qapplication/modal/base.cpp b/tests/auto/widgets/kernel/qapplication/modal/base.cpp index 249d402f2e..49a90723dd 100644 --- a/tests/auto/widgets/kernel/qapplication/modal/base.cpp +++ b/tests/auto/widgets/kernel/qapplication/modal/base.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "base.h" diff --git a/tests/auto/widgets/kernel/qapplication/modal/base.h b/tests/auto/widgets/kernel/qapplication/modal/base.h index 153d9ca420..168da92f97 100644 --- a/tests/auto/widgets/kernel/qapplication/modal/base.h +++ b/tests/auto/widgets/kernel/qapplication/modal/base.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef BASE_H #define BASE_H diff --git a/tests/auto/widgets/kernel/qapplication/modal/main.cpp b/tests/auto/widgets/kernel/qapplication/modal/main.cpp index 9dcb6732fa..3f3496834d 100644 --- a/tests/auto/widgets/kernel/qapplication/modal/main.cpp +++ b/tests/auto/widgets/kernel/qapplication/modal/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QApplication> #include "base.h" diff --git a/tests/auto/widgets/kernel/qapplication/test/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/test/CMakeLists.txt index ad5209f9bf..fc80b8af7e 100644 --- a/tests/auto/widgets/kernel/qapplication/test/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/test/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from test.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## test Test: @@ -8,35 +9,23 @@ list(APPEND test_data "../tmp/README") list(APPEND test_data "../modal") -qt_internal_add_test(tst_qapplication # special case +qt_internal_add_test(tst_qapplication SOURCES ../tst_qapplication.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::Widgets Qt::WidgetsPrivate TESTDATA ${test_data} - OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." # special case + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." ) ## Scopes: ##################################################################### -qt_internal_extend_target(tst_qapplication CONDITION builtin_testdata # special case +qt_internal_extend_target(tst_qapplication CONDITION builtin_testdata DEFINES BUILTIN_TESTDATA ) - -#### Keys ignored in scope 3:.:.:test.pro:NOT ANDROID: -# SUBPROGRAMS = "desktopsettingsaware" "modal" - -#### Keys ignored in scope 6:.:.:test.pro:NOT ANDROID: -# TEST_HELPER_INSTALLS = "../debug/helper" - -#### Keys ignored in scope 8:.:.:test.pro:NOT ANDROID: -# TEST_HELPER_INSTALLS = "../release/helper" - -#### Keys ignored in scope 10:.:.:test.pro:NOT ANDROID: -# TEST_HELPER_INSTALLS = "../helper" diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp index 8216cfbab0..2eb2d84504 100644 --- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp +++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #define QT_STATICPLUGIN #include <QtWidgets/qstyleplugin.h> @@ -48,6 +23,7 @@ #include <QtGui/QFontDatabase> #include <QtGui/QClipboard> +#include <QtGui/QStyleHints> #include <QtWidgets/QApplication> #include <QtWidgets/QMessageBox> @@ -119,12 +95,15 @@ private slots: void libraryPaths_qt_plugin_path_2(); #endif +#ifdef QT_BUILD_INTERNAL void sendPostedEvents(); +#endif // ifdef QT_BUILD_INTERNAL void thread(); void desktopSettingsAware(); void setActiveWindow(); + void activateDeactivateEvent(); void focusWidget(); void focusChanged(); @@ -141,6 +120,7 @@ private slots: void style(); void applicationPalettePolish(); + void setColorScheme(); void allWidgets(); void topLevelWidgets(); @@ -152,6 +132,7 @@ private slots: void wheelEventPropagation(); void qtbug_12673(); + void qtbug_103611(); void noQuitOnHide(); void globalStaticObjectDestruction(); // run this last @@ -187,6 +168,21 @@ void tst_QApplication::sendEventsOnProcessEvents() QCoreApplication::postEvent(&app, new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); + +#ifdef Q_OS_LINUX + if ((QSysInfo::productType() == "rhel" && QSysInfo::productVersion().startsWith(u'9')) + || (QSysInfo::productType() == "ubuntu" && QSysInfo::productVersion().startsWith(u'2'))) + { + QFile f("/proc/self/maps"); + QVERIFY(f.open(QIODevice::ReadOnly)); + + QByteArray libs = f.readAll(); + if (libs.contains("libqgtk3.") || libs.contains("libqgtk3TestInfix.")) { + QEXPECT_FAIL("", "Fails if qgtk3 (Glib) is loaded, see QTBUG-87137", Abort); + } + } +#endif + QVERIFY(spy.recordedEvents.contains(QEvent::User + 1)); } @@ -239,10 +235,12 @@ void tst_QApplication::staticSetup() EventWatcher() { qApp->installEventFilter(this); +#if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QObject::connect(qApp, &QApplication::paletteChanged, [&]{ ++palette_changed; }); QObject::connect(qApp, &QApplication::fontChanged, [&]{ ++font_changed; }); QT_WARNING_POP +#endif } protected: @@ -272,8 +270,13 @@ QT_WARNING_POP font.setBold(!font.bold()); qApp->setFont(font); QApplication::processEvents(); +#if QT_DEPRECATED_SINCE(6, 0) QCOMPARE(watcher.palette_changed, 2); QCOMPARE(watcher.font_changed, 2); +#else + QCOMPARE(watcher.palette_changed, 1); + QCOMPARE(watcher.font_changed, 1); +#endif } @@ -310,10 +313,8 @@ void tst_QApplication::alert() QApplication::alert(&widget, -1); QApplication::alert(&widget, 250); widget2.activateWindow(); - QApplication::setActiveWindow(&widget2); QApplication::alert(&widget, 0); widget.activateWindow(); - QApplication::setActiveWindow(&widget); QApplication::alert(&widget, 200); } @@ -502,7 +503,7 @@ static char **QString2cstrings(const QString &args) static QByteArrayList cache; const auto &list = QStringView{ args }.split(' '); - auto argarray = new char*[list.count() + 1]; + auto argarray = new char*[list.size() + 1]; int i = 0; for (; i < list.size(); ++i ) { @@ -591,7 +592,7 @@ void tst_QApplication::lastWindowClosed() QTimer::singleShot(1000, dialog.data(), &QDialog::accept); dialog->exec(); QVERIFY(dialog); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QPointer<CloseWidget>widget = new CloseWidget; widget->setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("CloseWidget")); @@ -600,7 +601,7 @@ void tst_QApplication::lastWindowClosed() QObject::connect(&app, &QGuiApplication::lastWindowClosed, widget.data(), &QObject::deleteLater); QCoreApplication::exec(); QVERIFY(!widget); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); delete dialog; @@ -618,7 +619,7 @@ void tst_QApplication::lastWindowClosed() QTimer::singleShot(1000, &app, &QApplication::closeAllWindows); QCoreApplication::exec(); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); } class QuitOnLastWindowClosedDialog : public QDialog @@ -651,8 +652,8 @@ public slots: other.exec(); // verify that the eventloop ran and let the timer fire - QCOMPARE(spy.count(), 1); - QCOMPARE(appSpy.count(), 1); + QCOMPARE(spy.size(), 1); + QCOMPARE(appSpy.size(), 1); } private: @@ -677,7 +678,7 @@ public slots: timer1.setSingleShot(true); timer1.start(1000); dialog.exec(); - QCOMPARE(spy1.count(), 1); + QCOMPARE(spy1.size(), 1); show(); } @@ -698,7 +699,7 @@ void tst_QApplication::quitOnLastWindowClosed() QCoreApplication::exec(); // lastWindowClosed() signal should only be sent after the last dialog is closed - QCOMPARE(appSpy.count(), 2); + QCOMPARE(appSpy.size(), 2); } { int argc = 0; @@ -713,8 +714,8 @@ void tst_QApplication::quitOnLastWindowClosed() timer1.setSingleShot(true); timer1.start(1000); dialog.exec(); - QCOMPARE(spy1.count(), 1); - QCOMPARE(appSpy.count(), 0); + QCOMPARE(spy1.size(), 1); + QCOMPARE(appSpy.size(), 0); QTimer timer2; connect(&timer2, &QTimer::timeout, &app, &QCoreApplication::quit); @@ -723,8 +724,8 @@ void tst_QApplication::quitOnLastWindowClosed() timer2.start(1000); int returnValue = QCoreApplication::exec(); QCOMPARE(returnValue, 0); - QCOMPARE(spy2.count(), 1); - QCOMPARE(appSpy.count(), 0); + QCOMPARE(spy2.size(), 1); + QCOMPARE(appSpy.size(), 0); } { int argc = 0; @@ -755,8 +756,8 @@ void tst_QApplication::quitOnLastWindowClosed() QCoreApplication::exec(); - QCOMPARE(spy.count(), 1); - QVERIFY(spy2.count() < 15); // Should be around 10 if closing caused the quit + QCOMPARE(spy.size(), 1); + QVERIFY(spy2.size() < 15); // Should be around 10 if closing caused the quit } bool quitApplicationTriggered = false; @@ -786,13 +787,13 @@ void tst_QApplication::quitOnLastWindowClosed() QCoreApplication::exec(); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QVERIFY(quitApplicationTriggered); } { int argc = 0; QApplication app(argc, nullptr); - QSignalSpy appSpy(&app, &QApplication::lastWindowClosed); + QSignalSpy appSpy(&app, &QGuiApplication::lastWindowClosed); // exec a dialog for 1 second, then show the window QuitOnLastWindowClosedWindow window; @@ -808,8 +809,8 @@ void tst_QApplication::quitOnLastWindowClosed() QCOMPARE(returnValue, 0); // failure here means the timer above didn't fire, and the // quit was caused the dialog being closed (not the window) - QCOMPARE(timerSpy.count(), 1); - QCOMPARE(appSpy.count(), 2); + QCOMPARE(timerSpy.size(), 1); + QCOMPARE(appSpy.size(), 2); } { int argc = 0; @@ -858,7 +859,7 @@ void tst_QApplication::quitOnLastWindowClosed() QTimer::singleShot(100, &w1, &QWidget::close); QCoreApplication::exec(); - QVERIFY(timerSpy.count() < 10); + QVERIFY(timerSpy.size() < 10); } } @@ -902,6 +903,7 @@ void tst_QApplication::closeAllWindows() { int argc = 0; QApplication app(argc, nullptr); + app.setAttribute(Qt::AA_DontUseNativeDialogs, true); // create some windows new QWidget; @@ -910,7 +912,7 @@ void tst_QApplication::closeAllWindows() // show all windows auto topLevels = QApplication::topLevelWidgets(); - for (QWidget *w : qAsConst(topLevels)) { + for (QWidget *w : std::as_const(topLevels)) { w->show(); QVERIFY(QTest::qWaitForWindowExposed(w)); } @@ -927,14 +929,14 @@ void tst_QApplication::closeAllWindows() PromptOnCloseWidget *promptOnCloseWidget = new PromptOnCloseWidget; // show all windows topLevels = QApplication::topLevelWidgets(); - for (QWidget *w : qAsConst(topLevels)) { + for (QWidget *w : std::as_const(topLevels)) { w->show(); QVERIFY(QTest::qWaitForWindowExposed(w)); } // close the last window to open the prompt (eventloop recurses) promptOnCloseWidget->close(); // all windows should not be visible, except the one that opened the prompt - for (QWidget *w : qAsConst(topLevels)) { + for (QWidget *w : std::as_const(topLevels)) { if (w == promptOnCloseWidget) QVERIFY(w->isVisible()); else @@ -946,8 +948,8 @@ void tst_QApplication::closeAllWindows() bool isPathListIncluded(const QStringList &l, const QStringList &r) { - int size = r.count(); - if (size > l.count()) + int size = r.size(); + if (size > l.size()) return false; #if defined (Q_OS_WIN) Qt::CaseSensitivity cs = Qt::CaseInsensitive; @@ -955,13 +957,13 @@ bool isPathListIncluded(const QStringList &l, const QStringList &r) Qt::CaseSensitivity cs = Qt::CaseSensitive; #endif int i = 0, j = 0; - for ( ; i < l.count() && j < r.count(); ++i) { + for ( ; i < l.size() && j < r.size(); ++i) { if (QDir::toNativeSeparators(l[i]).compare(QDir::toNativeSeparators(r[j]), cs) == 0) { ++j; i = -1; } } - return j == r.count(); + return j == r.size(); } #if QT_CONFIG(library) @@ -1021,7 +1023,7 @@ void tst_QApplication::libraryPaths() { qCDebug(lcTests) << "Initial library path:" << QApplication::libraryPaths(); - int count = QApplication::libraryPaths().count(); + int count = QApplication::libraryPaths().size(); #if 0 // this test doesn't work if KDE 4 is installed QCOMPARE(count, 1); // before creating QApplication, only the PluginsPath is in the libraryPaths() @@ -1030,9 +1032,9 @@ void tst_QApplication::libraryPaths() QApplication::addLibraryPath(installPathPlugins); qCDebug(lcTests) << "installPathPlugins" << installPathPlugins; qCDebug(lcTests) << "After adding plugins path:" << QApplication::libraryPaths(); - QCOMPARE(QApplication::libraryPaths().count(), count); + QCOMPARE(QApplication::libraryPaths().size(), count); QApplication::addLibraryPath(testDir); - QCOMPARE(QApplication::libraryPaths().count(), count + 1); + QCOMPARE(QApplication::libraryPaths().size(), count + 1); // creating QApplication adds the applicationDirPath to the libraryPath int argc = 1; @@ -1042,19 +1044,19 @@ void tst_QApplication::libraryPaths() // On Windows CE these are identical and might also be the case for other // systems too if (appDirPath != installPathPlugins) - QCOMPARE(QApplication::libraryPaths().count(), count + 2); + QCOMPARE(QApplication::libraryPaths().size(), count + 2); } { int argc = 1; QApplication app(argc, &argv0); qCDebug(lcTests) << "Initial library path:" << QCoreApplication::libraryPaths(); - int count = QCoreApplication::libraryPaths().count(); + int count = QCoreApplication::libraryPaths().size(); QString installPathPlugins = QLibraryInfo::path(QLibraryInfo::PluginsPath); QCoreApplication::addLibraryPath(installPathPlugins); qCDebug(lcTests) << "installPathPlugins" << installPathPlugins; qCDebug(lcTests) << "After adding plugins path:" << QCoreApplication::libraryPaths(); - QCOMPARE(QCoreApplication::libraryPaths().count(), count); + QCOMPARE(QCoreApplication::libraryPaths().size(), count); QString appDirPath = QCoreApplication::applicationDirPath(); @@ -1062,14 +1064,14 @@ void tst_QApplication::libraryPaths() QCoreApplication::addLibraryPath(appDirPath + "/.."); qCDebug(lcTests) << "appDirPath" << appDirPath; qCDebug(lcTests) << "After adding appDirPath && appDirPath + /..:" << QCoreApplication::libraryPaths(); - QCOMPARE(QCoreApplication::libraryPaths().count(), count + 1); + QCOMPARE(QCoreApplication::libraryPaths().size(), count + 1); #ifdef Q_OS_MACOS QCoreApplication::addLibraryPath(appDirPath + "/../MacOS"); #else QCoreApplication::addLibraryPath(appDirPath + "/tmp/.."); #endif qCDebug(lcTests) << "After adding appDirPath + /tmp/..:" << QCoreApplication::libraryPaths(); - QCOMPARE(QCoreApplication::libraryPaths().count(), count + 1); + QCOMPARE(QCoreApplication::libraryPaths().size(), count + 1); } } @@ -1136,11 +1138,12 @@ void tst_QApplication::libraryPaths_qt_plugin_path_2() << QCoreApplication::applicationDirPath(); QVERIFY(isPathListIncluded(QCoreApplication::libraryPaths(), expected)); - qputenv("QT_PLUGIN_PATH", QByteArray()); + qputenv("QT_PLUGIN_PATH", nullptr); } } #endif +#ifdef QT_BUILD_INTERNAL class SendPostedEventsTester : public QObject { Q_OBJECT @@ -1162,14 +1165,14 @@ void SendPostedEventsTester::doTest() QPointer<SendPostedEventsTester> p = this; QApplication::postEvent(this, new QEvent(QEvent::User)); // DeferredDelete should not be delivered until returning from this function - QApplication::postEvent(this, new QDeferredDeleteEvent()); + deleteLater(); QEventLoop eventLoop; QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection); eventLoop.exec(); QVERIFY(p != nullptr); - QCOMPARE(eventSpy.count(), 2); + QCOMPARE(eventSpy.size(), 2); QCOMPARE(eventSpy.at(0), int(QEvent::MetaCall)); QCOMPARE(eventSpy.at(1), int(QEvent::User)); eventSpy.clear(); @@ -1186,6 +1189,7 @@ void tst_QApplication::sendPostedEvents() (void) QCoreApplication::exec(); QVERIFY(p.isNull()); } +#endif void tst_QApplication::thread() { @@ -1330,12 +1334,9 @@ void DeleteLaterWidget::checkDeleteLater() void tst_QApplication::testDeleteLater() { -#ifdef Q_OS_MAC - QSKIP("This test fails and then hangs on OS X, see QTBUG-24318"); -#endif int argc = 0; QApplication app(argc, nullptr); - connect(&app, &QApplication::lastWindowClosed, &app, &QCoreApplication::quit); + connect(&app, &QGuiApplication::lastWindowClosed, &app, &QCoreApplication::quit); DeleteLaterWidget *wgt = new DeleteLaterWidget(&app); QTimer::singleShot(500, wgt, &DeleteLaterWidget::runTest); @@ -1530,7 +1531,7 @@ void tst_QApplication::desktopSettingsAware() environment += QLatin1String("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM=1"); testProcess.setEnvironment(environment); #endif - testProcess.start("desktopsettingsaware_helper"); + testProcess.start("./desktopsettingsaware_helper"); QVERIFY2(testProcess.waitForStarted(), qPrintable(QString::fromLatin1("Cannot start 'desktopsettingsaware_helper': %1").arg(testProcess.errorString()))); QVERIFY(testProcess.waitForFinished(10000)); @@ -1558,11 +1559,61 @@ void tst_QApplication::setActiveWindow() delete pb2; w->show(); - QApplication::setActiveWindow(w); // needs this on twm (focus follows mouse) + QApplicationPrivate::setActiveWindow(w); // needs this on twm (focus follows mouse) QVERIFY(pb1->hasFocus()); delete w; } +void tst_QApplication::activateDeactivateEvent() +{ + // Ensure that QWindows (other than QWidgetWindow) + // are activated / deactivated. + class Window : public QWindow + { + public: + using QWindow::QWindow; + + int activateCount = 0; + int deactivateCount = 0; + protected: + bool event(QEvent *e) override + { + switch (e->type()) { + case QEvent::WindowActivate: + ++activateCount; + break; + case QEvent::WindowDeactivate: + ++deactivateCount; + break; + default: + break; + } + return QWindow::event(e); + } + }; + + int argc = 0; + QApplication app(argc, nullptr); + + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + Window w1; + Window w2; + + w1.show(); + w1.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&w1)); + QCOMPARE(w1.activateCount, 1); + QCOMPARE(w1.deactivateCount, 0); + + w2.show(); + w2.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&w2)); + QCOMPARE(w1.deactivateCount, 1); + QCOMPARE(w2.activateCount, 1); +} + void tst_QApplication::focusWidget() { int argc = 0; @@ -1573,7 +1624,6 @@ void tst_QApplication::focusWidget() QTextEdit te; te.show(); - QApplication::setActiveWindow(&te); QVERIFY(QTest::qWaitForWindowActive(&te)); const auto focusWidget = QApplication::focusWidget(); @@ -1589,7 +1639,6 @@ void tst_QApplication::focusWidget() QTextEdit te(&w); w.show(); - QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); const auto focusWidget = QApplication::focusWidget(); @@ -1622,22 +1671,22 @@ void tst_QApplication::focusChanged() hbox1.addWidget(&le1); hbox1.addWidget(&pb1); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); parent1.show(); - QApplication::setActiveWindow(&parent1); // needs this on twm (focus follows mouse) - QCOMPARE(spy.count(), 1); - QCOMPARE(spy.at(0).count(), 2); + QApplicationPrivate::setActiveWindow(&parent1); // needs this on twm (focus follows mouse) + QCOMPARE(spy.size(), 1); + QCOMPARE(spy.at(0).size(), 2); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &le1); QCOMPARE(now, QApplication::focusWidget()); QVERIFY(!old); spy.clear(); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); pb1.setFocus(); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &pb1); @@ -1646,7 +1695,7 @@ void tst_QApplication::focusChanged() spy.clear(); lb1.setFocus(); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &lb1); @@ -1655,7 +1704,7 @@ void tst_QApplication::focusChanged() spy.clear(); lb1.clearFocus(); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QVERIFY(!now); @@ -1674,10 +1723,10 @@ void tst_QApplication::focusChanged() hbox2.addWidget(&pb2); parent2.show(); - QApplication::setActiveWindow(&parent2); // needs this on twm (focus follows mouse) - QVERIFY(spy.count() > 0); // one for deactivation, one for activation on Windows - old = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(0)); - now = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(1)); + QApplicationPrivate::setActiveWindow(&parent2); // needs this on twm (focus follows mouse) + QVERIFY(spy.size() > 0); // one for deactivation, one for activation on Windows + old = qvariant_cast<QWidget*>(spy.at(spy.size()-1).at(0)); + now = qvariant_cast<QWidget*>(spy.at(spy.size()-1).at(1)); QCOMPARE(now, &le2); QCOMPARE(now, QApplication::focusWidget()); QVERIFY(!old); @@ -1702,10 +1751,10 @@ void tst_QApplication::focusChanged() tab.simulate(now); if (!tabAllControls) { - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(now, QApplication::focusWidget()); } else { - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &pb2); @@ -1715,11 +1764,11 @@ void tst_QApplication::focusChanged() } if (!tabAllControls) { - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(now, QApplication::focusWidget()); } else { tab.simulate(now); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &le2); @@ -1729,11 +1778,11 @@ void tst_QApplication::focusChanged() } if (!tabAllControls) { - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(now, QApplication::focusWidget()); } else { backtab.simulate(now); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &pb2); @@ -1744,12 +1793,12 @@ void tst_QApplication::focusChanged() if (!tabAllControls) { - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(now, QApplication::focusWidget()); old = &pb2; } else { backtab.simulate(now); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &le2); @@ -1760,10 +1809,10 @@ void tst_QApplication::focusChanged() click.simulate(old); if (!(pb2.focusPolicy() & Qt::ClickFocus)) { - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); QCOMPARE(now, QApplication::focusWidget()); } else { - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &pb2); @@ -1772,7 +1821,7 @@ void tst_QApplication::focusChanged() spy.clear(); click.simulate(old); - QVERIFY(spy.count() > 0); + QVERIFY(spy.size() > 0); old = qvariant_cast<QWidget*>(spy.at(0).at(0)); now = qvariant_cast<QWidget*>(spy.at(0).at(1)); QCOMPARE(now, &le2); @@ -1782,16 +1831,16 @@ void tst_QApplication::focusChanged() } parent1.activateWindow(); - QApplication::setActiveWindow(&parent1); // needs this on twm (focus follows mouse) - QVERIFY(spy.count() == 1 || spy.count() == 2); // one for deactivation, one for activation on Windows + QApplicationPrivate::setActiveWindow(&parent1); // needs this on twm (focus follows mouse) + QVERIFY(spy.size() == 1 || spy.size() == 2); // one for deactivation, one for activation on Windows //on windows, the change of focus is made in 2 steps //(the focusChanged SIGNAL is emitted twice) - if (spy.count()==1) - old = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(0)); + if (spy.size()==1) + old = qvariant_cast<QWidget*>(spy.at(spy.size()-1).at(0)); else - old = qvariant_cast<QWidget*>(spy.at(spy.count()-2).at(0)); - now = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(1)); + old = qvariant_cast<QWidget*>(spy.at(spy.size()-2).at(0)); + now = qvariant_cast<QWidget*>(spy.at(spy.size()-1).at(1)); QCOMPARE(now, &le1); QCOMPARE(now, QApplication::focusWidget()); QCOMPARE(old, &le2); @@ -1865,7 +1914,7 @@ void tst_QApplication::focusMouseClick() // front most widget has Qt::TabFocus, parent widget accepts clicks as well // now send a mouse button press event and check what happens with the focus // it should be given to the parent widget - QMouseEvent ev(QEvent::MouseButtonPress, QPointF(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QMouseEvent ev(QEvent::MouseButtonPress, QPointF(), w.mapToGlobal(QPointF()), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QSpontaneKeyEvent::setSpontaneous(&ev); QVERIFY(ev.spontaneous()); qApp->notify(&w2, &ev); @@ -2004,6 +2053,122 @@ void tst_QApplication::applicationPalettePolish() } } +void tst_QApplication::setColorScheme() +{ + int argc = 1; + QApplication app(argc, &argv0); + + if (QStringList{"minimal", "offscreen", "wayland", "xcb", "wasm", "webassembly"} + .contains(QGuiApplication::platformName(), Qt::CaseInsensitive)) { + QSKIP("Setting the colorScheme is not implemented on this platform."); + } + qDebug() << "Testing setColorScheme on platform" << QGuiApplication::platformName(); + + if (QByteArrayView(app.style()->metaObject()->className()) == "QWindowsVistaStyle") + QSKIP("Setting the colorScheme is not supported with the Windows Vista style."); + + const Qt::ColorScheme defaultColorScheme = QApplication::styleHints()->colorScheme(); + // if we implement setColorScheme, then we must be able to read it + QVERIFY(defaultColorScheme != Qt::ColorScheme::Unknown); + const Qt::ColorScheme newColorScheme = defaultColorScheme == Qt::ColorScheme::Light + ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; + + class TopLevelWidget : public QWidget + { + QList<QEvent::Type> events; + public: + TopLevelWidget() + { + setObjectName("colorScheme TopLevelWidget"); + } + + void clearEvents() + { + events.clear(); + } + qsizetype eventCount(QEvent::Type type) const + { + return events.count(type); + } + protected: + bool event(QEvent *event) override + { + switch (event->type()) { + case QEvent::ApplicationPaletteChange: + case QEvent::PaletteChange: + case QEvent::ThemeChange: + events << event->type(); + break; + default: + break; + } + + return QWidget::event(event); + } + } topLevelWidget; + topLevelWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevelWidget)); + + QSignalSpy colorSchemeChangedSpy(app.styleHints(), &QStyleHints::colorSchemeChanged); + + // always start with a clean list + topLevelWidget.clearEvents(); + const QPalette defaultPalette = topLevelWidget.palette(); + + bool oldPaletteWhenSchemeChanged = false; + connect(app.styleHints(), &QStyleHints::colorSchemeChanged, this, + [defaultPalette, &topLevelWidget, &oldPaletteWhenSchemeChanged]{ + oldPaletteWhenSchemeChanged = defaultPalette == topLevelWidget.palette(); + }); + + app.styleHints()->setColorScheme(newColorScheme); + QTRY_COMPARE(colorSchemeChangedSpy.count(), 1); + // We have not yet updated the palette when we emit the colorSchemeChanged + // signal, so the toplevel widget should still use the previous palette + QVERIFY(oldPaletteWhenSchemeChanged); + QCOMPARE(topLevelWidget.eventCount(QEvent::ThemeChange), 1); + // We can't guarantee that there is only one ApplicationPaletteChange, + // and they might arrive asynchronously in response to ThemeChange + QTRY_COMPARE_GE(topLevelWidget.eventCount(QEvent::ApplicationPaletteChange), 1); + // But we can guarantee a single PaletteChange event for the widget + QCOMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1); + // The palette should have changed + QCOMPARE_NE(topLevelWidget.palette(), defaultPalette); + + topLevelWidget.clearEvents(); + colorSchemeChangedSpy.clear(); + + // verify that a widget shown with a color scheme override in place respect that + QWidget newWidget; + newWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&newWidget)); + QCOMPARE(newWidget.palette(), topLevelWidget.palette()); + + // Setting to Unknown should follow the system preference again + app.styleHints()->setColorScheme(Qt::ColorScheme::Unknown); + QTRY_COMPARE(colorSchemeChangedSpy.count(), 1); + QCOMPARE(app.styleHints()->colorScheme(), defaultColorScheme); + QTRY_COMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1); + + auto debugPalette = qScopeGuard([defaultPalette, &topLevelWidget]{ + qDebug() << "Inspecting palettes for differences"; + const QPalette palette = topLevelWidget.palette(); + for (int g = 0; g < QPalette::NColorGroups; ++g) { + for (int r = 0; r < QPalette::NColorRoles; ++r) { + const auto group = static_cast<QPalette::ColorGroup>(g); + const auto role = static_cast<QPalette::ColorRole>(r); + qDebug() << "...Checking" << group << role; + const auto actualBrush = palette.brush(group, role); + const auto expectedBrush = defaultPalette.brush(group, role); + if (palette.brush(group, role) != defaultPalette.brush(group, role)) + qWarning() << "...Difference in" << group << role << actualBrush << expectedBrush; + } + } + }); + QCOMPARE(topLevelWidget.palette(), defaultPalette); + debugPalette.dismiss(); +} + void tst_QApplication::allWidgets() { int argc = 1; @@ -2026,11 +2191,11 @@ void tst_QApplication::topLevelWidgets() #endif QCoreApplication::processEvents(); QVERIFY(QApplication::topLevelWidgets().contains(w)); - QCOMPARE(QApplication::topLevelWidgets().count(), 1); + QCOMPARE(QApplication::topLevelWidgets().size(), 1); delete w; w = nullptr; QCoreApplication::processEvents(); - QCOMPARE(QApplication::topLevelWidgets().count(), 0); + QCOMPARE(QApplication::topLevelWidgets().size(), 0); } @@ -2447,7 +2612,7 @@ void tst_QApplication::wheelEventPropagation() int vcount = 0; int hcount = 0; - for (const auto &event : qAsConst(events)) { + for (const auto &event : std::as_const(events)) { const QPoint pixelDelta = event.orientation == Qt::Vertical ? QPoint(0, -scrollStep) : QPoint(-scrollStep, 0); const QPoint angleDelta = event.orientation == Qt::Vertical ? QPoint(0, -120) : QPoint(-120, 0); QWindowSystemInterface::handleWheelEvent(outerArea.windowHandle(), center, global, @@ -2458,10 +2623,10 @@ void tst_QApplication::wheelEventPropagation() else ++hcount; QCoreApplication::processEvents(); - QCOMPARE(innerVSpy.count(), innerScrolls ? vcount : 0); - QCOMPARE(innerHSpy.count(), innerScrolls ? hcount : 0); - QCOMPARE(outerVSpy.count(), innerScrolls ? 0 : vcount); - QCOMPARE(outerHSpy.count(), innerScrolls ? 0 : hcount); + QCOMPARE(innerVSpy.size(), innerScrolls ? vcount : 0); + QCOMPARE(innerHSpy.size(), innerScrolls ? hcount : 0); + QCOMPARE(outerVSpy.size(), innerScrolls ? 0 : vcount); + QCOMPARE(outerHSpy.size(), innerScrolls ? 0 : hcount); } } @@ -2470,7 +2635,7 @@ void tst_QApplication::qtbug_12673() #if QT_CONFIG(process) QProcess testProcess; QStringList arguments; - testProcess.start("modal_helper", arguments); + testProcess.start("./modal_helper", arguments); QVERIFY2(testProcess.waitForStarted(), qPrintable(QString::fromLatin1("Cannot start 'modal_helper': %1").arg(testProcess.errorString()))); QVERIFY(testProcess.waitForFinished(20000)); @@ -2480,6 +2645,20 @@ void tst_QApplication::qtbug_12673() #endif } +void tst_QApplication::qtbug_103611() +{ + { + int argc = 0; + QApplication app(argc, nullptr); + auto ll = QLocale().uiLanguages(); + } + { + int argc = 0; + QApplication app(argc, nullptr); + auto ll = QLocale().uiLanguages(); + } +} + class NoQuitOnHideWidget : public QWidget { Q_OBJECT @@ -2509,8 +2688,26 @@ public: explicit ShowCloseShowWidget(bool showAgain, QWidget *parent = nullptr) : QWidget(parent), showAgain(showAgain) { + int timeout = 500; +#ifdef Q_OS_ANDROID + // On Android, CI Android emulator is not running HW accelerated graphics and can be slow, + // use a longer timeout to avoid flaky failures + timeout = 1000; +#endif + QTimer::singleShot(timeout, this, [] () { QCoreApplication::exit(1); }); + } + + bool shown = false; + +protected: + void showEvent(QShowEvent *) override + { QTimer::singleShot(0, this, &ShowCloseShowWidget::doClose); - QTimer::singleShot(500, this, [] () { QCoreApplication::exit(1); }); + shown = true; + } + void hideEvent(QHideEvent *) override + { + shown = false; } private slots: @@ -2526,16 +2723,21 @@ private: void tst_QApplication::abortQuitOnShow() { + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("Wayland: This crash, see QTBUG-123172."); + int argc = 0; QApplication app(argc, nullptr); ShowCloseShowWidget window1(false); window1.setWindowTitle(QLatin1String(QTest::currentTestFunction())); window1.show(); + QVERIFY(QTest::qWaitFor([&window1](){ return window1.shown; })); QCOMPARE(QCoreApplication::exec(), 0); ShowCloseShowWidget window2(true); window2.setWindowTitle(QLatin1String(QTest::currentTestFunction())); window2.show(); + QVERIFY(QTest::qWaitFor([&window2](){ return window2.shown; })); QCOMPARE(QCoreApplication::exec(), 1); } @@ -2550,7 +2752,7 @@ void tst_QApplication::staticFunctions() QApplication::activeModalWidget(); QApplication::focusWidget(); QApplication::activeWindow(); - QApplication::setActiveWindow(nullptr); + QApplicationPrivate::setActiveWindow(nullptr); QApplication::widgetAt(QPoint(0, 0)); QApplication::topLevelAt(QPoint(0, 0)); QTest::ignoreMessage(QtWarningMsg, "Must construct a QApplication first."); diff --git a/tests/auto/widgets/kernel/qboxlayout/CMakeLists.txt b/tests/auto/widgets/kernel/qboxlayout/CMakeLists.txt index 8fdc0d21a9..5b60382fba 100644 --- a/tests/auto/widgets/kernel/qboxlayout/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qboxlayout/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qboxlayout.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qboxlayout Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qboxlayout LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qboxlayout SOURCES tst_qboxlayout.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::TestPrivate Qt::Widgets diff --git a/tests/auto/widgets/kernel/qboxlayout/tst_qboxlayout.cpp b/tests/auto/widgets/kernel/qboxlayout/tst_qboxlayout.cpp index a410a7bf00..4313d9891c 100644 --- a/tests/auto/widgets/kernel/qboxlayout/tst_qboxlayout.cpp +++ b/tests/auto/widgets/kernel/qboxlayout/tst_qboxlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -141,7 +116,7 @@ void tst_QBoxLayout::insertLayout() QCOMPARE(dummyParentLayout->count(), 1); // add subLayout to another layout - QTest::ignoreMessage(QtWarningMsg, "QLayout::addChildLayout: layout \"\" already has a parent"); + QTest::ignoreMessage(QtWarningMsg, "QLayout::addChildLayout: layout QHBoxLayout \"\" already has a parent"); vbox->addLayout(subLayout); QCOMPARE((subLayout->parent() == vbox), (vbox->count() == 1)); } @@ -217,7 +192,7 @@ void tst_QBoxLayout::setStyleShouldChangeSpacing() window.setWindowTitle(QTest::currentTestFunction()); QHBoxLayout *hbox = new QHBoxLayout(&window); QPushButton *pb1 = new QPushButton(tr("The spacing between this")); - QPushButton *pb2 = new QPushButton(tr("and this button should depend on the style of the parent widget"));; + QPushButton *pb2 = new QPushButton(tr("and this button should depend on the style of the parent widget")); pb1->setAttribute(Qt::WA_LayoutUsesWidgetRect); pb2->setAttribute(Qt::WA_LayoutUsesWidgetRect); hbox->addWidget(pb1); @@ -542,14 +517,14 @@ void tst_QBoxLayout::testLayoutEngine() QHBoxLayout box; box.setSpacing(spacing); int i; - for (i = 0; i < itemDescriptions.count(); ++i) { + for (i = 0; i < itemDescriptions.size(); ++i) { Descr descr = itemDescriptions.at(i); LayoutItem *li = new LayoutItem(descr); box.addItem(li); box.setStretch(i, descr.stretch); } box.setGeometry(QRect(0,0,size,100)); - for (i = 0; i < expectedSizes.count(); ++i) { + for (i = 0; i < expectedSizes.size(); ++i) { int xSize = expectedSizes.at(i); int xPos = expectedPositions.at(i); QLayoutItem *item = box.itemAt(i); diff --git a/tests/auto/widgets/kernel/qformlayout/BLACKLIST b/tests/auto/widgets/kernel/qformlayout/BLACKLIST deleted file mode 100644 index 375682e788..0000000000 --- a/tests/auto/widgets/kernel/qformlayout/BLACKLIST +++ /dev/null @@ -1,3 +0,0 @@ -# QTBUG-87401 -[wrapping] -android diff --git a/tests/auto/widgets/kernel/qformlayout/CMakeLists.txt b/tests/auto/widgets/kernel/qformlayout/CMakeLists.txt index b084fe1634..9e1da4c6a3 100644 --- a/tests/auto/widgets/kernel/qformlayout/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qformlayout/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qformlayout.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qformlayout Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qformlayout LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qformlayout SOURCES tst_qformlayout.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::TestPrivate Qt::Widgets diff --git a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp index 4aa9f8ac2d..9638823538 100644 --- a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp +++ b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -135,6 +110,9 @@ private slots: void takeRow_QLayout(); void setWidget(); void setLayout(); + void hideShowRow(); + void showWithHiddenRow(); + void hiddenRowAndStretch(); /* QLayoutItem *itemAt(int row, ItemRole role) const; @@ -274,7 +252,9 @@ void tst_QFormLayout::wrapping() fl->setRowWrapPolicy(QFormLayout::WrapLongRows); QLineEdit *le = new QLineEdit; - QLabel *lbl = new QLabel("A long label"); + QLabel *lbl = new QLabel("A long label which is actually long enough to trigger wrapping," + " even on Android and even if it is executed on a tiling window" + " manager which forces the window into fullscreen mode."); le->setMinimumWidth(200); fl->addRow(lbl, le); @@ -814,6 +794,7 @@ void tst_QFormLayout::removeRow_QWidget() QCOMPARE(layout->rowCount(), 0); QWidget *w3 = new QWidget; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid widget"); layout->removeRow(w3); delete w3; } @@ -854,6 +835,7 @@ void tst_QFormLayout::removeRow_QLayout() QCOMPARE(layout->rowCount(), 0); QHBoxLayout *l3 = new QHBoxLayout; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid layout"); layout->removeRow(l3); delete l3; } @@ -893,6 +875,7 @@ void tst_QFormLayout::takeRow() QCOMPARE(layout->rowCount(), 0); QCOMPARE(result.fieldItem->widget(), w1.data()); + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid row 0"); result = layout->takeRow(0); QVERIFY(!result.fieldItem); @@ -933,6 +916,7 @@ void tst_QFormLayout::takeRow_QWidget() QCOMPARE(layout->rowCount(), 0); QWidget *w3 = new QWidget; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid widget"); result = layout->takeRow(w3); delete w3; @@ -980,6 +964,7 @@ void tst_QFormLayout::takeRow_QLayout() QCOMPARE(layout->rowCount(), 0); QHBoxLayout *l3 = new QHBoxLayout; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid layout"); result = layout->takeRow(l3); delete l3; @@ -1009,7 +994,9 @@ void tst_QFormLayout::setWidget() QCOMPARE(layout.rowCount(), 6); // should be ignored and generate warnings + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Cell (3, 1) already occupied"); layout.setWidget(3, QFormLayout::FieldRole, &w4); + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Invalid cell (-1, 1)"); layout.setWidget(-1, QFormLayout::FieldRole, &w4); { @@ -1077,7 +1064,9 @@ void tst_QFormLayout::setLayout() QCOMPARE(layout.rowCount(), 6); // should be ignored and generate warnings + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Cell (3, 1) already occupied"); layout.setLayout(3, QFormLayout::FieldRole, &l4); + QTest::ignoreMessage(QtWarningMsg, "QLayout::addChildLayout: layout QHBoxLayout \"\" already has a parent"); layout.setLayout(-1, QFormLayout::FieldRole, &l4); QCOMPARE(layout.count(), 3); QCOMPARE(layout.rowCount(), 6); @@ -1123,6 +1112,191 @@ void tst_QFormLayout::setLayout() } } +void tst_QFormLayout::hideShowRow() +{ + QWidget topLevel; + QFormLayout layout; + + const auto makeComplex = []{ + QHBoxLayout *hboxField = new QHBoxLayout; + hboxField->addWidget(new QLineEdit("Left")); + hboxField->addWidget(new QLineEdit("Right")); + return hboxField; + }; + + layout.addRow("Label", new QLineEdit("one")); + layout.addRow("Label", new QLineEdit("two")); + layout.addRow("Label", new QLineEdit("three")); + layout.addRow("Label", makeComplex()); + layout.addRow(new QLineEdit("five")); // spanning widget + layout.addRow(makeComplex()); // spanning layout + + topLevel.setLayout(&layout); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + // returns the top-left position of the items in a row + const auto rowPosition = [&layout](int row) { + QRect rect; + if (QLayoutItem *spanningItem = layout.itemAt(row, QFormLayout::SpanningRole)) { + rect = spanningItem->geometry(); + } else { + if (QLayoutItem *labelItem = layout.itemAt(row, QFormLayout::LabelRole)) { + rect = labelItem->geometry(); + } + if (QLayoutItem *fieldItem = layout.itemAt(row, QFormLayout::FieldRole)) { + rect |= fieldItem->geometry(); + } + } + return rect.topLeft(); + }; + + // returns the first widget in a row, even if that row is taken by a layout + const auto rowInputWidget = [&layout](int row) -> QWidget* { + auto fieldItem = layout.itemAt(row, QFormLayout::FieldRole); + if (!fieldItem) + return nullptr; + QWidget *fieldWidget = fieldItem->widget(); + // we happen to know our layout structure + if (!fieldWidget) + fieldWidget = fieldItem->layout()->itemAt(0)->widget(); + return fieldWidget; + }; + + // record the reference positions for all rows + QList<QPoint> rowPositions(layout.rowCount()); + for (int row = 0; row < layout.rowCount(); ++row) + rowPositions[row] = rowPosition(row); + + // hide each row in turn, the next row should take the space of the hidden row + for (int row = 0; row < layout.rowCount(); ++ row) { + layout.setRowVisible(row, false); + QVERIFY(!layout.isRowVisible(row)); + if (row < layout.rowCount() - 1) + QTRY_COMPARE(rowPosition(row + 1), rowPositions[row]); + layout.setRowVisible(row, true); + QVERIFY(layout.isRowVisible(row)); + } + + // Hiding only the label or only the field doesn't hide the row. + for (int row = 0; row < layout.rowCount() - 1; ++row) { + const auto labelItem = layout.itemAt(0, QFormLayout::LabelRole); + if (labelItem) { + labelItem->widget()->hide(); + QVERIFY(layout.isRowVisible(row)); + QCOMPARE(rowPosition(row), rowPositions[row]); + layout.itemAt(0, QFormLayout::LabelRole)->widget()->show(); + } + const auto fieldItem = layout.itemAt(0, QFormLayout::FieldRole); + if (fieldItem) { + fieldItem->widget()->hide(); + QVERIFY(layout.isRowVisible(row)); + QCOMPARE(rowPosition(row), rowPositions[row]); + layout.itemAt(0, QFormLayout::FieldRole)->widget()->show(); + } + } + + // If we hide both label and field, then the row should be considered hidden and the + // following row should move up into the space of the hidden row. We can only test + // this if both label and field are widgets, or if there is a spanning widget. + for (int row = 0; row < layout.rowCount() - 1; ++row) { + QWidget *labelWidget = nullptr; + if (auto labelItem = layout.itemAt(row, QFormLayout::LabelRole)) + labelWidget = labelItem->widget(); + QWidget *fieldWidget = nullptr; + if (auto fieldItem = layout.itemAt(row, QFormLayout::FieldRole)) + fieldWidget = fieldItem->widget(); + + if (!fieldWidget) + continue; + if (labelWidget) + labelWidget->hide(); + fieldWidget->hide(); + QVERIFY(!layout.isRowVisible(row)); + QVERIFY(!layout.isRowVisible(fieldWidget)); + if (labelWidget) + QVERIFY(!layout.isRowVisible(labelWidget)); + QTRY_COMPARE(rowPosition(row + 1), rowPositions[row]); + if (labelWidget) + labelWidget->show(); + fieldWidget->show(); + } + + // hiding a row where a widget has focus must move focus to a widget in the next row + for (int row = 0; row < layout.rowCount(); ++row) { + QWidget *inputWidget = rowInputWidget(row); + QVERIFY(inputWidget); + inputWidget->setFocus(); + layout.setRowVisible(row, false); + QVERIFY(!inputWidget->hasFocus()); + } + + // Now hide all rows, hide the toplevel widget, and show the toplevel widget again. + // None of the widgets inside must be visible. + for (int row = 0; row < layout.rowCount(); ++row) + layout.setRowVisible(row, false); + topLevel.hide(); + topLevel.show(); + for (int row = 0; row < layout.rowCount(); ++row) + QVERIFY(rowInputWidget(row)->isHidden()); +} + +void tst_QFormLayout::showWithHiddenRow() +{ + QWidget topLevel; + QFormLayout layout; + + for (int row = 0; row < 3; ++row) + layout.addRow(QString("Label %1").arg(row), new QLineEdit); + layout.setRowVisible(1, false); + + topLevel.setLayout(&layout); + topLevel.show(); +} + +/* + Test that hiding rows does not leave outdated layout data behind + in hidden items that results in out-of-bounds array access. See + QTBUG-109237. +*/ +void tst_QFormLayout::hiddenRowAndStretch() +{ + QWidget topLevel; + QFormLayout layout; + layout.setRowWrapPolicy(QFormLayout::WrapAllRows); + + // We need our own stretcher item so that QFormLayout doesn't insert + // it's own, as that would grow the size of the layout data array again. + QSpacerItem *stretch = new QSpacerItem(100, 100, QSizePolicy::Expanding, QSizePolicy::Expanding); + layout.setItem(0, QFormLayout::FieldRole, stretch); + + QLabel *lastLabel = nullptr; + QLineEdit *lastField = nullptr; + for (int row = 1; row < 4; ++row) { + QLabel *label = new QLabel(QString("Label %1").arg(row)); + label->setWordWrap(true); + QLineEdit *field = new QLineEdit; + layout.setWidget(row, QFormLayout::LabelRole, label); + layout.setWidget(row, QFormLayout::FieldRole, field); + if (row == 3) { + lastLabel = label; + lastField = field; + } + } + + Q_ASSERT(lastLabel); + Q_ASSERT(lastField); + + topLevel.setLayout(&layout); + topLevel.sizeHint(); + + lastLabel->setVisible(false); + lastField->setVisible(false); + + // should not assert here + topLevel.show(); +} + void tst_QFormLayout::itemAt() { QWidget topLevel; diff --git a/tests/auto/widgets/kernel/qgesturerecognizer/CMakeLists.txt b/tests/auto/widgets/kernel/qgesturerecognizer/CMakeLists.txt index 9dfd6cb20c..ffa54992d3 100644 --- a/tests/auto/widgets/kernel/qgesturerecognizer/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qgesturerecognizer/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qgesturerecognizer.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qgesturerecognizer Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qgesturerecognizer LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qgesturerecognizer SOURCES tst_qgesturerecognizer.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate diff --git a/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp b/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp index b65f6a5ff6..cdab480d84 100644 --- a/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp +++ b/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QTest> @@ -53,6 +28,7 @@ private Q_SLOTS: void pinchGesture(); void swipeGesture_data(); void swipeGesture(); + void touchReplay(); #endif // !QT_NO_GESTURES private: @@ -95,7 +71,7 @@ TestWidget::TestWidget(const GestureTypeVector &gestureTypes) { setAttribute(Qt::WA_AcceptTouchEvents); - foreach (Qt::GestureType gestureType, gestureTypes) { + for (Qt::GestureType gestureType : gestureTypes) { grabGesture(gestureType); m_receivedGestures.insert(gestureType, false); } @@ -293,7 +269,7 @@ void tst_QGestureRecognizer::swipeGesture() // Press point #3 points.append(points.last() + fingerDistance); - swipeSequence.press(points.size() - 1, points.last(), &widget); + swipeSequence.stationary(0).stationary(1).press(points.size() - 1, points.last(), &widget); swipeSequence.commit(); Q_ASSERT(points.size() == swipePoints); @@ -326,6 +302,25 @@ void tst_QGestureRecognizer::swipeGesture() } } +void tst_QGestureRecognizer::touchReplay() +{ + const Qt::GestureType gestureType = Qt::TapGesture; + QWidget parent; + TestWidget widget(GestureTypeVector(1, gestureType)); + widget.setParent(&parent); + widget.setGeometry(0, 0, 100, 100); + parent.adjustSize(); + parent.show(); + QVERIFY(QTest::qWaitForWindowActive(&parent)); + + QWindow* windowHandle = parent.window()->windowHandle(); + const QPoint globalPos = QPoint(42, 16); + QTest::touchEvent(windowHandle, m_touchDevice).press(1, globalPos); + QTest::touchEvent(windowHandle, m_touchDevice).release(1, globalPos); + + QVERIFY(widget.gestureReceived(gestureType)); +} + #endif // !QT_NO_GESTURES QTEST_MAIN(tst_QGestureRecognizer) diff --git a/tests/auto/widgets/kernel/qgridlayout/CMakeLists.txt b/tests/auto/widgets/kernel/qgridlayout/CMakeLists.txt index c9ac563f01..bf72bc0ae6 100644 --- a/tests/auto/widgets/kernel/qgridlayout/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qgridlayout/CMakeLists.txt @@ -1,14 +1,21 @@ -# Generated from qgridlayout.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qgridlayout Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qgridlayout LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qgridlayout SOURCES sortdialog.ui tst_qgridlayout.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate diff --git a/tests/auto/widgets/kernel/qgridlayout/tst_qgridlayout.cpp b/tests/auto/widgets/kernel/qgridlayout/tst_qgridlayout.cpp index 2cf2462fd5..3c325699a7 100644 --- a/tests/auto/widgets/kernel/qgridlayout/tst_qgridlayout.cpp +++ b/tests/auto/widgets/kernel/qgridlayout/tst_qgridlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -83,7 +58,8 @@ private slots: static inline int visibleTopLevelWidgetCount() { int result= 0; - foreach (const QWidget *topLevel, QApplication::topLevelWidgets()) { + const auto topLevels = QApplication::topLevelWidgets(); + for (const QWidget *topLevel : topLevels) { if (topLevel->isVisible()) ++result; } @@ -235,6 +211,9 @@ void tst_QGridLayout::badDistributionBug() void tst_QGridLayout::setMinAndMaxSize() { + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("This test crashes on Wayland, see also QTBUG-107184"); + QWidget widget; setFrameless(&widget); QGridLayout layout(&widget); @@ -683,7 +662,7 @@ void tst_QGridLayout::spacingsAndMargins() QSKIP("The screen is too small to run this test case"); // We are relying on the order here... - for (int pi = 0; pi < sizehinters.count(); ++pi) { + for (int pi = 0; pi < sizehinters.size(); ++pi) { QPoint pt = sizehinters.at(pi)->mapTo(&toplevel, QPoint(0, 0)); QCOMPARE(pt, expectedpositions.at(pi)); } @@ -853,7 +832,7 @@ void tst_QGridLayout::minMaxSize() QList<QPointer<SizeHinterFrame> > sizehinters; for (int i = 0; i < rows; ++i) { for (int j = 0; j < columns; ++j) { - SizeInfo si = sizeinfos.at(sizehinters.count()); + SizeInfo si = sizeinfos.at(sizehinters.size()); int numpixels = si.hfwNumPixels; if (pass == 1 && numpixels == -1) numpixels = -2; //### yuk, (and don't fake it if it already tests sizehint) @@ -882,7 +861,7 @@ void tst_QGridLayout::minMaxSize() QTRY_COMPARE(toplevel.size(), toplevel.sizeHint()); } // We are relying on the order here... - for (int pi = 0; pi < sizehinters.count(); ++pi) { + for (int pi = 0; pi < sizehinters.size(); ++pi) { QPoint pt = sizehinters.at(pi)->mapTo(&toplevel, QPoint(0, 0)); QCOMPARE(pt, sizeinfos.at(pi).expectedPos); } @@ -1052,7 +1031,7 @@ void tst_QGridLayout::styleDependentSpacingsAndMargins() widget.adjustSize(); QApplication::processEvents(); - for (int pi = 0; pi < expectedpositions.count(); ++pi) { + for (int pi = 0; pi < expectedpositions.size(); ++pi) { QCOMPARE(sizehinters.at(pi)->pos(), expectedpositions.at(pi)); } } @@ -1442,7 +1421,7 @@ void tst_QGridLayout::layoutSpacing() QLayout *layout = widget->layout(); QVERIFY(layout); - for (int pi = 0; pi < expectedpositions.count(); ++pi) { + for (int pi = 0; pi < expectedpositions.size(); ++pi) { QLayoutItem *item = layout->itemAt(pi); //qDebug() << item->widget()->pos(); QCOMPARE(item->widget()->pos(), expectedpositions.at(pi)); diff --git a/tests/auto/widgets/kernel/qlayout/CMakeLists.txt b/tests/auto/widgets/kernel/qlayout/CMakeLists.txt index 0ee2ce145e..6bda750c0f 100644 --- a/tests/auto/widgets/kernel/qlayout/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qlayout/CMakeLists.txt @@ -1,9 +1,16 @@ -# Generated from qlayout.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qlayout Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qlayout LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -13,27 +20,10 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qlayout SOURCES tst_qlayout.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::TestPrivate Qt::Widgets Qt::WidgetsPrivate TESTDATA ${test_data} ) - -## Scopes: -##################################################################### - -if(ANDROID AND NOT ANDROID_EMBEDDED) - # Resources: - set(testdata_resource_files - "baseline/smartmaxsize" - ) - - qt_internal_add_resource(tst_qlayout "testdata" - PREFIX - "/" - FILES - ${testdata_resource_files} - ) -endif() diff --git a/tests/auto/widgets/kernel/qlayout/testdata.qrc b/tests/auto/widgets/kernel/qlayout/testdata.qrc deleted file mode 100644 index 24e8e56263..0000000000 --- a/tests/auto/widgets/kernel/qlayout/testdata.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>baseline/smartmaxsize</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/kernel/qlayout/tst_qlayout.cpp b/tests/auto/widgets/kernel/qlayout/tst_qlayout.cpp index deb473ab8c..bd170ca8ab 100644 --- a/tests/auto/widgets/kernel/qlayout/tst_qlayout.cpp +++ b/tests/auto/widgets/kernel/qlayout/tst_qlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -355,7 +330,7 @@ void tst_QLayout::adjustSizeShouldMakeSureLayoutIsActivated() void tst_QLayout::testRetainSizeWhenHidden() { -#if (defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)) +#ifdef Q_OS_ANDROID QSKIP("Test does not work on platforms which default to showMaximized()"); #endif @@ -403,10 +378,10 @@ void tst_QLayout::removeWidget() { QHBoxLayout layout; QCOMPARE(layout.count(), 0); - QWidget w; - layout.addWidget(&w); + std::unique_ptr<QWidget> w(new QWidget); + layout.addWidget(w.get()); QCOMPARE(layout.count(), 1); - layout.removeWidget(&w); + layout.removeWidget(w.get()); QCOMPARE(layout.count(), 0); QPointer<QLayout> childLayout(new QHBoxLayout); @@ -420,6 +395,12 @@ void tst_QLayout::removeWidget() QCOMPARE(layout.count(), 0); QVERIFY(!childLayout.isNull()); + + // Test inactive layout consumes ChildRemoved event (QTBUG-124151) + layout.addWidget(w.get()); + layout.setEnabled(false); + w.reset(); + layout.setEnabled(true); } QTEST_MAIN(tst_QLayout) diff --git a/tests/auto/widgets/kernel/qshortcut/CMakeLists.txt b/tests/auto/widgets/kernel/qshortcut/CMakeLists.txt index fd97a436d0..517286f324 100644 --- a/tests/auto/widgets/kernel/qshortcut/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qshortcut/CMakeLists.txt @@ -1,16 +1,38 @@ -# Generated from qshortcut.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qshortcut Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qshortcut LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qshortcut SOURCES tst_qshortcut.cpp INCLUDE_DIRECTORIES .. - PUBLIC_LIBRARIES + LIBRARIES + Qt::Gui + Qt::GuiPrivate + Qt::Widgets + Qt::WidgetsPrivate +) + +qt_internal_add_test(tst_qguishortcut_with_qapplication + SOURCES + ../../../gui/kernel/qshortcut/tst_qshortcut.cpp + DEFINES + tst_QShortcut=tst_QGuiShortcutWithQApplication + INCLUDE_DIRECTORIES + .. + LIBRARIES Qt::Gui Qt::GuiPrivate Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp b/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp index 1266660cea..d34df43b01 100644 --- a/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp +++ b/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -48,11 +23,69 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformintegration.h> +#include <QtWidgets/private/qapplication_p.h> + QT_BEGIN_NAMESPACE class QMainWindow; class QTextEdit; QT_END_NAMESPACE +class TestEdit : public QTextEdit +{ + Q_OBJECT +public: + TestEdit(QWidget *parent, const char *name) + : QTextEdit(parent) + { + setObjectName(name); + } + +protected: + bool event(QEvent *e) override + { + // Make testedit allow any Ctrl+Key as shortcut + if (e->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if (ke->modifiers() == Qt::ControlModifier + && ke->key() > Qt::Key_Any + && ke->key() < Qt::Key_ydiaeresis) { + ke->ignore(); + return true; + } + } + + // If keypress not processed as normal, check for + // Ctrl+Key event, and input custom string for + // result comparison. + if (e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if (ke->modifiers() && ke->key() > Qt::Key_Any + && ke->key() < Qt::Key_ydiaeresis) { + const QChar c = QLatin1Char(char(ke->key())); + if (ke->modifiers() == Qt::ControlModifier) + insertPlainText(QLatin1String("<Ctrl+") + c + QLatin1Char('>')); + else if (ke->modifiers() == Qt::AltModifier) + insertPlainText(QLatin1String("<Alt+") + c + QLatin1Char('>')); + else if (ke->modifiers() == Qt::ShiftModifier) + insertPlainText(QLatin1String("<Shift+") + c + QLatin1Char('>')); + return true; + } + } + return QTextEdit::event(e); + } +}; + +class MainWindow : public QMainWindow +{ +public: + MainWindow(); + + TestEdit *testEdit() const { return m_testEdit; } + +private: + TestEdit *m_testEdit; +}; + class tst_QShortcut : public QObject { Q_OBJECT @@ -143,62 +176,7 @@ protected: void testElement(); Result ambigResult; -}; - -class TestEdit : public QTextEdit -{ - Q_OBJECT -public: - TestEdit(QWidget *parent, const char *name) - : QTextEdit(parent) - { - setObjectName(name); - } - -protected: - bool event(QEvent *e) override - { - // Make testedit allow any Ctrl+Key as shortcut - if (e->type() == QEvent::ShortcutOverride) { - QKeyEvent *ke = static_cast<QKeyEvent*>(e); - if (ke->modifiers() == Qt::ControlModifier - && ke->key() > Qt::Key_Any - && ke->key() < Qt::Key_ydiaeresis) { - ke->ignore(); - return true; - } - } - - // If keypress not processed as normal, check for - // Ctrl+Key event, and input custom string for - // result comparison. - if (e->type() == QEvent::KeyPress) { - QKeyEvent *ke = static_cast<QKeyEvent*>(e); - if (ke->modifiers() && ke->key() > Qt::Key_Any - && ke->key() < Qt::Key_ydiaeresis) { - const QChar c = QLatin1Char(char(ke->key())); - if (ke->modifiers() == Qt::ControlModifier) - insertPlainText(QLatin1String("<Ctrl+") + c + QLatin1Char('>')); - else if (ke->modifiers() == Qt::AltModifier) - insertPlainText(QLatin1String("<Alt+") + c + QLatin1Char('>')); - else if (ke->modifiers() == Qt::ShiftModifier) - insertPlainText(QLatin1String("<Shift+") + c + QLatin1Char('>')); - return true; - } - } - return QTextEdit::event(e); - } -}; - -class MainWindow : public QMainWindow -{ -public: - MainWindow(); - - TestEdit *testEdit() const { return m_testEdit; } - -private: - TestEdit *m_testEdit; + QScopedPointer<MainWindow> mainWindow; }; MainWindow::MainWindow() @@ -337,11 +315,11 @@ void tst_QShortcut::number_data() Shift + Qt::Key_Plus on Shift + Qt::Key_Pluss Qt::Key_Plus on Shift + Qt::Key_Pluss */ - QTest::newRow("N002 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::SHIFT | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N002:Shift+M - [Shift+M]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_M) << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N002 - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N002:Shift+M - [Shift+M]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N002:M - [Shift+M]") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N002 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::SHIFT | Qt::Key_Plus) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N002:Shift++ [Shift++]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("N002 - slot2") << SetupAccel << TriggerSlot2 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Plus).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N002:Shift++ [Shift++]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Plus).toCombined() << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("N002:+ [Shift++]") << TestAccel << NoWidget << QString() << int(Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N002 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all @@ -359,8 +337,8 @@ void tst_QShortcut::number_data() Qt::Key_F1 on Shift + Qt::Key_F1 */ - QTest::newRow("N004 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N004:Shift+F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N004 - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N004:Shift+F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N004:F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << int(Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N004 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all @@ -376,7 +354,7 @@ void tst_QShortcut::number_data() //QTest::newRow("N005a:Shift+Tab - [Tab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT + Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; // (Shift+)BackTab != Tab, but Shift+BackTab == Shift+Tab QTest::newRow("N005a:Backtab - [Tab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N005a:Shift+Backtab - [Tab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N005a:Shift+Backtab - [Tab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Backtab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N005a - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all /* Testing Single Sequences @@ -385,11 +363,11 @@ void tst_QShortcut::number_data() Qt::Key_Backtab on Shift + Qt::Key_Tab Shift + Qt::Key_Backtab on Shift + Qt::Key_Tab */ - QTest::newRow("N005b - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::SHIFT | Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N005b - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Tab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N005b:Tab - [Shift+Tab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N005b:Shift+Tab - [Shift+Tab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N005b:Shift+Tab - [Shift+Tab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Tab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N005b:BackTab - [Shift+Tab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N005b:Shift+BackTab - [Shift+Tab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N005b:Shift+BackTab - [Shift+Tab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Backtab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N005b - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all /* Testing Single Sequences @@ -402,10 +380,10 @@ void tst_QShortcut::number_data() QTest::newRow("N006a:Tab - [BackTab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // This should work, since platform dependent code will transform the // Shift+Tab into a Shift+BackTab, which should trigger the shortcut - QTest::newRow("N006a:Shift+Tab - [BackTab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; //XFAIL + QTest::newRow("N006a:Shift+Tab - [BackTab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Tab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; //XFAIL QTest::newRow("N006a:BackTab - [BackTab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; //commented out because the behaviour changed, those tests should be updated - //QTest::newRow("N006a:Shift+BackTab - [BackTab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT + Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + //QTest::newRow("N006a:Shift+BackTab - [BackTab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Backtab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N006a - clear") << ClearAll << NoWidget<< QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all /* Testing Single Sequences @@ -414,11 +392,11 @@ void tst_QShortcut::number_data() Qt::Key_Backtab on Shift + Qt::Key_Backtab Shift + Qt::Key_Backtab on Shift + Qt::Key_Backtab */ - QTest::newRow("N006b - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::SHIFT | Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N006b - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Backtab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N006b:Tab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N006b:Shift+Tab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Tab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N006b:Shift+Tab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Tab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N006b:BackTab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << int(Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N006b:Shift+BackTab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Backtab) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; //XFAIL + QTest::newRow("N006b:Shift+BackTab - [Shift+BackTab]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Backtab).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; //XFAIL QTest::newRow("N006b - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all //=========================================== @@ -431,9 +409,9 @@ void tst_QShortcut::number_data() Shift + Qt::Key_F1 */ QTest::newRow("N007 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N007 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N007 - slot2") << SetupAccel << TriggerSlot2 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N007:F1") << TestAccel << NoWidget << QString() << int(Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("N007:Shift + F1") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("N007:Shift + F1") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("N007 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all /* Testing Single Sequences @@ -443,13 +421,13 @@ void tst_QShortcut::number_data() Alt + Qt::Key_M */ QTest::newRow("N01 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N02 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::SHIFT | Qt::Key_M) << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N03 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::CTRL | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N04 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::ALT | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N02 - slot2") << SetupAccel << TriggerSlot2 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N03 - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::CTRL, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N04 - slot2") << SetupAccel << TriggerSlot2 << QString() << QKeyCombination(Qt::ALT, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N:Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("N:Shift+Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_M) << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; - QTest::newRow("N:Ctrl+Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::CTRL | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("N:Alt+Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::ALT | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("N:Shift+Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("N:Ctrl+Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::CTRL, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N:Alt+Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::ALT, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; /* Testing Single Sequence Ambiguity Qt::Key_M on shortcut2 @@ -464,11 +442,11 @@ void tst_QShortcut::number_data() Qt::Key_K */ QTest::newRow("N06 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N07 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::SHIFT | Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N07 - slot2") << SetupAccel << TriggerSlot2 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Aring).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N08 - slot2") << SetupAccel << TriggerSlot1 << QString() << int(Qt::Key_K) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N:Qt::Key_aring") << TestAccel << NoWidget << QString() << int(Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("N:Qt::Key_Aring") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("N:Qt::Key_Aring") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Aring).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("N:Qt::Key_aring - Text Form") << TestAccel << NoWidget << QString() << 0 << 0xC5 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N:Qt::Key_Aring - Text Form") << TestAccel << NoWidget << QString() << int(Qt::SHIFT) << 0xC5 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("N:Qt::Qt::Key_K") << TestAccel << NoWidget << QString() << int(Qt::Key_K) << int('k') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; @@ -481,11 +459,11 @@ void tst_QShortcut::number_data() */ QTest::newRow("N10 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N11 - slot2") << SetupAccel << TriggerSlot2 << QString() << int(Qt::Key_I) << 0 << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("N12 - slot1") << SetupAccel << TriggerSlot1 << QString() << int(Qt::SHIFT | Qt::Key_I) << 0 << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << NoResult; + QTest::newRow("N12 - slot1") << SetupAccel << TriggerSlot1 << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_I).toCombined() << 0 << int(Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("N:Qt::Key_M (2)") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N:Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::Key_I) << int('i') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot2Triggered; - QTest::newRow("N:Shift+Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_I) << int('I') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("N:Shift+Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_I).toCombined() << int('I') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("N:end") << TestEnd << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; } @@ -527,10 +505,10 @@ void tst_QShortcut::text_data() Ctrl + Qt::Key_Plus on Ctrl + Qt::Key_Pluss */ QTest::newRow("T002 - slot1") << SetupAccel << TriggerSlot1 << QString("Shift+M") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("T002:Shift+M - [Shift+M]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_M) << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("T002:Shift+M - [Shift+M]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T002:M - [Shift+M]") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T002 - slot2") << SetupAccel << TriggerSlot2 << QString("Shift++") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("T002:Shift++ [Shift++]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("T002:Shift++ [Shift++]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Plus).toCombined() << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("T002:+ [Shift++]") << TestAccel << NoWidget << QString() << int(Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T002 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all @@ -542,7 +520,7 @@ void tst_QShortcut::text_data() QTest::newRow("T002b - slot1") << SetupAccel << TriggerSlot1 << QString("Ctrl++") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; //commented out because the behaviour changed, those tests should be updated //QTest::newRow("T002b:Shift+Ctrl++ [Ctrl++]")<< TestAccel << NoWidget << QString() << int(Qt::SHIFT + Qt::CTRL + Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("T002b:Ctrl++ [Ctrl++]") << TestAccel << NoWidget << QString() << int(Qt::CTRL | Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("T002b:Ctrl++ [Ctrl++]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::CTRL, Qt::Key_Plus).toCombined() << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T002b:+ [Ctrl++]") << TestAccel << NoWidget << QString() << int(Qt::Key_Plus) << int('+') << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T002b - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all @@ -561,7 +539,7 @@ void tst_QShortcut::text_data() Qt::Key_F1 on Shift + Qt::Key_F1 */ QTest::newRow("T004 - slot1") << SetupAccel << TriggerSlot1 << QString("Shift+F1") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; - QTest::newRow("T004:Shift+F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("T004:Shift+F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T004:F1 - [Shift+F1]") << TestAccel << NoWidget << QString() << int(Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T004 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all @@ -577,7 +555,7 @@ void tst_QShortcut::text_data() QTest::newRow("T007 - slot1") << SetupAccel << TriggerSlot1 << QString("F1") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T007 - slot2") << SetupAccel << TriggerSlot2 << QString("Shift+F1") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T007:F1") << TestAccel << NoWidget << QString() << int(Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("T007:Shift + F1") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_F1) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("T007:Shift + F1") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_F1).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("T007 - clear") << ClearAll << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; // Clear all /* Testing Single Sequences @@ -592,9 +570,9 @@ void tst_QShortcut::text_data() QTest::newRow("T04 - slot2") << SetupAccel << TriggerSlot2 << QString("Alt+M") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T:Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("T:Shift + Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_M) << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; - QTest::newRow("T:Ctrl + Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::CTRL | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("T:Alt + Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::ALT | Qt::Key_M) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("T:Shift + Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_M).toCombined() << int('M') << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("T:Ctrl + Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::CTRL, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("T:Alt + Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::ALT, Qt::Key_M).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; /* Testing Single Sequence Ambiguity Qt::Key_M on shortcut2 @@ -613,7 +591,7 @@ void tst_QShortcut::text_data() QTest::newRow("T07 - slot2") << SetupAccel << TriggerSlot2 << QString::fromLatin1("Shift+\x0C5")<< 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T08 - slot2") << SetupAccel << TriggerSlot1 << QString("K") << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T:Qt::Key_aring") << TestAccel << NoWidget << QString() << int(Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; - QTest::newRow("T:Qt::Key_Aring") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_Aring) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; + QTest::newRow("T:Qt::Key_Aring") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_Aring).toCombined() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("T:Qt::Key_aring - Text Form") << TestAccel << NoWidget << QString() << 0 << 0xC5 << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T:Qt::Key_Aring - Text Form") << TestAccel << NoWidget << QString() << int(Qt::SHIFT) << 0xC5 << 0 << 0 << 0 << 0 << 0 << 0 << Slot2Triggered; QTest::newRow("T:Qt::Key_K") << TestAccel << NoWidget << QString() << int(Qt::Key_K) << int('k') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; @@ -629,7 +607,7 @@ void tst_QShortcut::text_data() QTest::newRow("T12 - slot1") << SetupAccel << TriggerSlot1 << QString("Shift+I, M")<< 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; QTest::newRow("T:Qt::Key_M (2)") << TestAccel << NoWidget << QString() << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T:Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::Key_I) << int('i') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot2Triggered; - QTest::newRow("T:Shift+Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << int(Qt::SHIFT | Qt::Key_I) << int('I') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot1Triggered; + QTest::newRow("T:Shift+Qt::Key_I, Qt::Key_M") << TestAccel << NoWidget << QString() << QKeyCombination(Qt::SHIFT, Qt::Key_I).toCombined() << int('I') << int(Qt::Key_M) << int('m') << 0 << 0 << 0 << 0 << Slot1Triggered; QTest::newRow("T:end") << TestEnd << NoWidget << QString() << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << NoResult; } @@ -1086,7 +1064,6 @@ void tst_QShortcut::context() // Focus on 'other1' edit, so Active Window context should trigger other1->activateWindow(); // <--- - QApplication::setActiveWindow(other1); QCOMPARE(QApplication::activeWindow(), other1->window()); QCOMPARE(QApplication::focusWidget(), static_cast<QWidget *>(other1)); @@ -1178,7 +1155,6 @@ void tst_QShortcut::duplicatedShortcutOverride() w.resize(200, 200); w.move(QGuiApplication::primaryScreen()->availableGeometry().center() - QPoint(100, 100)); w.show(); - QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); QTest::keyPress(w.windowHandle(), Qt::Key_A); QCoreApplication::processEvents(); @@ -1276,8 +1252,6 @@ void tst_QShortcut::testElement() if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); - static QScopedPointer<MainWindow> mainW; - currentResult = NoResult; QFETCH(tst_QShortcut::Action, action); QFETCH(tst_QShortcut::Widget, testWidget); @@ -1292,28 +1266,35 @@ void tst_QShortcut::testElement() QFETCH(int, c4); QFETCH(tst_QShortcut::Result, result); - if (mainW.isNull()) { - mainW.reset(new MainWindow); - mainW->setWindowTitle(QTest::currentTestFunction()); - mainW->show(); - mainW->activateWindow(); - QVERIFY(QTest::qWaitForWindowActive(mainW.data())); - } + auto mainWindowDeleter = qScopeGuard([&]{ + if (action == TestEnd) + mainWindow.reset(); + }); + + if (mainWindow.isNull()) + mainWindow.reset(new MainWindow); + mainWindow->setWindowTitle(QTest::currentTestFunction()); + mainWindow->show(); + mainWindow->activateWindow(); + // Don't use QVERIFY here; the data function uses QEXPECT_FAIL, + // which would result in an XPASS failure. + if (!QTest::qWaitForWindowActive(mainWindow.data())) + QVERIFY(false); switch (action) { case ClearAll: - qDeleteAll(mainW->findChildren<QShortcut *>()); + qDeleteAll(mainWindow->findChildren<QShortcut *>()); break; case SetupAccel: - setupShortcut(mainW.data(), txt, testWidget, txt.isEmpty() + setupShortcut(mainWindow.data(), txt, testWidget, txt.isEmpty() ? QKeySequence(k1, k2, k3, k4) : QKeySequence::fromString(txt)); break; case TestAccel: - sendKeyEvents(mainW.data(), k1, char16_t(c1), k2, char16_t(c2), k3, char16_t(c3), k4, char16_t(c4)); + sendKeyEvents(mainWindow.data(), k1, char16_t(c1), k2, char16_t(c2), k3, char16_t(c3), k4, char16_t(c4)); QCOMPARE(currentResult, result); break; case TestEnd: - mainW.reset(); + // taken care of by the mainWindowDeleter break; } } @@ -1369,10 +1350,10 @@ void tst_QShortcut::keys() QCOMPARE(QApplication::focusWidget(), &le); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Enter); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Return); - QTRY_COMPARE(spy.count(), 2); + QTRY_COMPARE(spy.size(), 2); } QTEST_MAIN(tst_QShortcut) diff --git a/tests/auto/widgets/kernel/qsizepolicy/CMakeLists.txt b/tests/auto/widgets/kernel/qsizepolicy/CMakeLists.txt index 36450d4f80..efdd72db73 100644 --- a/tests/auto/widgets/kernel/qsizepolicy/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qsizepolicy/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qsizepolicy.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qsizepolicy Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsizepolicy LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qsizepolicy SOURCES tst_qsizepolicy.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets Qt::WidgetsPrivate diff --git a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp index d6a5ec218d..19d267f7e5 100644 --- a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp +++ b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp @@ -1,32 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <QTest> #include <qsizepolicy.h> Q_DECLARE_METATYPE(Qt::Orientations) @@ -34,6 +8,8 @@ Q_DECLARE_METATYPE(QSizePolicy) Q_DECLARE_METATYPE(QSizePolicy::Policy) Q_DECLARE_METATYPE(QSizePolicy::ControlType) +#include <QTest> + class tst_QSizePolicy : public QObject { Q_OBJECT @@ -109,7 +85,7 @@ void tst_QSizePolicy::constExpr() { /* gcc < 4.8.0 has problems with init'ing variant members in constexpr ctors */ /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54922 */ -#if !defined(Q_CC_GNU) || defined(Q_CC_INTEL) || defined(Q_CC_CLANG) || Q_CC_GNU >= 408 +#if !defined(Q_CC_GNU) || defined(Q_CC_CLANG) || Q_CC_GNU >= 408 // check that certain ctors are constexpr (compile-only): { constexpr QSizePolicy sp; Q_UNUSED(sp); } { constexpr QSizePolicy sp = QSizePolicy(); Q_UNUSED(sp); } @@ -119,7 +95,7 @@ void tst_QSizePolicy::constExpr() { // QTBUG-69983: For ControlType != QSizePolicy::DefaultType, qCountTrailingZeroBits() // is used, which MSVC 15.8.1 does not consider constexpr due to built-ins -# if defined(QT_HAS_CONSTEXPR_BUILTINS) && (!defined(Q_CC_MSVC) || _MSC_VER < 1915) +# if defined(QT_HAS_CONSTEXPR_BITOPS) constexpr auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::CheckBox); # else constexpr auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding, QSizePolicy::DefaultType); diff --git a/tests/auto/widgets/kernel/qstackedlayout/CMakeLists.txt b/tests/auto/widgets/kernel/qstackedlayout/CMakeLists.txt index 2c1c3da277..5701f455b5 100644 --- a/tests/auto/widgets/kernel/qstackedlayout/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qstackedlayout/CMakeLists.txt @@ -1,13 +1,21 @@ -# Generated from qstackedlayout.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qstackedlayout Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qstackedlayout LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qstackedlayout SOURCES tst_qstackedlayout.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/kernel/qstackedlayout/tst_qstackedlayout.cpp b/tests/auto/widgets/kernel/qstackedlayout/tst_qstackedlayout.cpp index 711ffeda03..c73b9725b2 100644 --- a/tests/auto/widgets/kernel/qstackedlayout/tst_qstackedlayout.cpp +++ b/tests/auto/widgets/kernel/qstackedlayout/tst_qstackedlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -36,6 +11,8 @@ #include <QPushButton> #include <QSignalSpy> +#include <QtWidgets/private/qapplication_p.h> + class tst_QStackedLayout : public QObject { Q_OBJECT @@ -132,7 +109,7 @@ void tst_QStackedLayout::testCase() // One widget added to layout QWidget *w1 = new QWidget(testWidget); testLayout->addWidget(w1); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(0).toInt(), 0); spy.clear(); QCOMPARE(testLayout->currentIndex(), 0); @@ -149,7 +126,7 @@ void tst_QStackedLayout::testCase() // Change the current index testLayout->setCurrentIndex(1); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(0).toInt(), 1); spy.clear(); QCOMPARE(testLayout->currentIndex(), 1); @@ -163,7 +140,7 @@ void tst_QStackedLayout::testCase() // Second widget removed from layout; back to nothing testLayout->removeWidget(w2); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).at(0).toInt(), -1); spy.clear(); QCOMPARE(testLayout->currentIndex(), -1); @@ -312,7 +289,6 @@ void tst_QStackedLayout::keepFocusAfterSetCurrent() stackLayout->setCurrentIndex(0); testWidget->show(); - QApplication::setActiveWindow(testWidget); QVERIFY(QTest::qWaitForWindowActive(testWidget)); edit1->setFocus(); diff --git a/tests/auto/widgets/kernel/qtooltip/CMakeLists.txt b/tests/auto/widgets/kernel/qtooltip/CMakeLists.txt index a07cd1aa65..af2de80e24 100644 --- a/tests/auto/widgets/kernel/qtooltip/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qtooltip/CMakeLists.txt @@ -1,13 +1,21 @@ -# Generated from qtooltip.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qtooltip Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qtooltip LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qtooltip SOURCES tst_qtooltip.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/kernel/qtooltip/tst_qtooltip.cpp b/tests/auto/widgets/kernel/qtooltip/tst_qtooltip.cpp index 092adc58af..bc0624c9ab 100644 --- a/tests/auto/widgets/kernel/qtooltip/tst_qtooltip.cpp +++ b/tests/auto/widgets/kernel/qtooltip/tst_qtooltip.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -36,6 +11,8 @@ #include <qwhatsthis.h> #include <qscreen.h> +#include <QtWidgets/private/qapplication_p.h> + class tst_QToolTip : public QObject { Q_OBJECT @@ -43,12 +20,13 @@ class tst_QToolTip : public QObject private slots: void init(); void cleanup(); - void task183679_data(); - void task183679(); + void keyEvent_data(); + void keyEvent(); void whatsThis(); void setPalette(); void qtbug64550_stylesheet(); void dontCrashOutsideScreenGeometry(); + void marginSetWithStyleSheet(); }; void tst_QToolTip::init() @@ -62,11 +40,11 @@ void tst_QToolTip::cleanup() qApp->setStyleSheet(QString()); } -class Widget_task183679 : public QWidget +class Widget : public QWidget { Q_OBJECT public: - Widget_task183679(QWidget *parent = nullptr) : QWidget(parent) {} + Widget(QWidget *parent = nullptr) : QWidget(parent) {} void showDelayedToolTip(int msecs) { @@ -78,25 +56,32 @@ public: private slots: void showToolTip() { - QToolTip::showText(mapToGlobal(QPoint(0, 0)), Widget_task183679::toolTipText(), this); + QToolTip::showText(mapToGlobal(QPoint(0, 0)), Widget::toolTipText(), this); } }; Q_DECLARE_METATYPE(Qt::Key) -void tst_QToolTip::task183679_data() +void tst_QToolTip::keyEvent_data() { QTest::addColumn<Qt::Key>("key"); QTest::addColumn<bool>("visible"); - QTest::newRow("non-modifier") << Qt::Key_A << true; + QTest::newRow("non-modifier") << Qt::Key_A << +#if defined(Q_OS_MACOS) + // macOS natively hides tooltips on non-modifier key events, + // so QTipLabel::eventFilter does the same. Match that here. + false; +#else + true; +#endif QTest::newRow("Shift") << Qt::Key_Shift << true; QTest::newRow("Control") << Qt::Key_Control << true; QTest::newRow("Alt") << Qt::Key_Alt << true; QTest::newRow("Meta") << Qt::Key_Meta << true; } -void tst_QToolTip::task183679() +void tst_QToolTip::keyEvent() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); @@ -104,11 +89,7 @@ void tst_QToolTip::task183679() QFETCH(Qt::Key, key); QFETCH(bool, visible); -#ifdef Q_OS_MAC - QSKIP("This test fails in the CI system, QTBUG-30040"); -#endif - - Widget_task183679 widget; + Widget widget; widget.move(QGuiApplication::primaryScreen()->availableGeometry().topLeft() + QPoint(50, 50)); // Ensure cursor is not over tooltip, which causes it to hide #ifndef QT_NO_CURSOR @@ -117,7 +98,6 @@ void tst_QToolTip::task183679() widget.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag())); widget.show(); - QApplication::setActiveWindow(&widget); QVERIFY(QTest::qWaitForWindowActive(&widget)); widget.showDelayedToolTip(100); @@ -137,7 +117,8 @@ void tst_QToolTip::task183679() static QWidget *findWhatsThat() { - foreach (QWidget *widget, QApplication::topLevelWidgets()) { + const auto widgets = QApplication::topLevelWidgets(); + for (QWidget *widget : widgets) { if (widget->inherits("QWhatsThat")) return widget; } @@ -208,10 +189,9 @@ void tst_QToolTip::qtbug64550_stylesheet() if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); - Widget_task183679 widget; + Widget widget; widget.setStyleSheet(QStringLiteral("* { font-size: 48pt; }\n")); widget.show(); - QApplication::setActiveWindow(&widget); QVERIFY(QTest::qWaitForWindowActive(&widget)); widget.showDelayedToolTip(100); @@ -220,7 +200,7 @@ void tst_QToolTip::qtbug64550_stylesheet() QVERIFY(toolTip); QTRY_VERIFY(toolTip->isVisible()); - const QRect boundingRect = QFontMetrics(widget.font()).boundingRect(Widget_task183679::toolTipText()); + const QRect boundingRect = QFontMetrics(widget.font()).boundingRect(Widget::toolTipText()); const QSize toolTipSize = toolTip->size(); QVERIFY2(toolTipSize.width() >= boundingRect.width() && toolTipSize.height() >= boundingRect.height(), @@ -233,5 +213,30 @@ void tst_QToolTip::dontCrashOutsideScreenGeometry() { QToolTip::hideText(); } +void tst_QToolTip::marginSetWithStyleSheet() +{ + const char *toolTipText = "Test Tool Tip"; + + qApp->setStyleSheet("QToolTip {font-size: 8px; margin: 5px;}"); + QToolTip::showText(QGuiApplication::primaryScreen()->availableGeometry().topLeft(), toolTipText); + QTRY_VERIFY(QToolTip::isVisible()); + QWidget *toolTip = findToolTip(); + QVERIFY(toolTip); + QTRY_VERIFY(toolTip->isVisible()); + int toolTipHeight = toolTip->size().height(); + qApp->setStyleSheet(QString()); + QToolTip::hideText(); + + qApp->setStyleSheet("QToolTip {font-size: 8px; margin: 10px;}"); + QToolTip::showText(QGuiApplication::primaryScreen()->availableGeometry().topLeft(), toolTipText); + QTRY_VERIFY(QToolTip::isVisible()); + toolTip = findToolTip(); + QVERIFY(toolTip); + QTRY_VERIFY(toolTip->isVisible()); + QCOMPARE_LE(toolTip->size().height(), toolTipHeight + 10); + qApp->setStyleSheet(QString()); + QToolTip::hideText(); +} + QTEST_MAIN(tst_QToolTip) #include "tst_qtooltip.moc" diff --git a/tests/auto/widgets/kernel/qwidget/BLACKLIST b/tests/auto/widgets/kernel/qwidget/BLACKLIST index 254ca784ab..4c17af245b 100644 --- a/tests/auto/widgets/kernel/qwidget/BLACKLIST +++ b/tests/auto/widgets/kernel/qwidget/BLACKLIST @@ -1,74 +1,49 @@ -# OSX QTBUG-25300 QTBUG-45502 -[normalGeometry] -ubuntu-16.04 -[restoreVersion1Geometry] -ubuntu-16.04 -[focusProxyAndInputMethods] -rhel-7.6 -centos -opensuse-leap -ubuntu [raise] opensuse-leap -# QTBUG-68175 -opensuse-42.3 -[childEvents] -macos -[renderInvisible] -macos -[optimizedResizeMove] -osx [optimizedResize_topLevel] osx [render_windowOpacity] macos arm [render_systemClip] osx -[showMinimizedKeepsFocus] -macos -[maskedUpdate] -opensuse -opensuse-leap -[moveInResizeEvent] -ubuntu-16.04 [multipleToplevelFocusCheck] -rhel-7.6 centos opensuse-leap ubuntu sles-15 - -[syntheticEnterLeave] -macos # Can't move cursor (QTBUG-76312) -[taskQTBUG_4055_sendSyntheticEnterLeave] -macos # Can't move cursor (QTBUG-76312) - # QTBUG-87668 -[reparent] -android -[windowState] -android [showMinimizedKeepsFocus] android +macos-13 ci +macos-14 ci [normalGeometry] android [saveRestoreGeometry] android [optimizedResizeMove] android -[resizeEvent] -android [update] android [scroll] android -[setWindowGeometry] +[moveChild] android -[windowMoveResize] +[multipleToplevelFocusCheck] android -[moveChild] +[renderInvisible] android -[showAndMoveChild] +[updateWhileMinimized] android -[multipleToplevelFocusCheck] +[doubleRepaint] +android +[setMaskInResizeEvent] +android +[activateWindow] +android +[optimizedResize_topLevel] +android +[hoverPosition] +macos-14 x86 +# QTBUG-124291 +[setParentChangesFocus:make dialog parentless, after] android diff --git a/tests/auto/widgets/kernel/qwidget/CMakeLists.txt b/tests/auto/widgets/kernel/qwidget/CMakeLists.txt index d04db40e87..f18bc98bb3 100644 --- a/tests/auto/widgets/kernel/qwidget/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwidget/CMakeLists.txt @@ -1,20 +1,15 @@ -# Generated from qwidget.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwidget Test: ##################################################################### -qt_internal_add_test(tst_qwidget - SOURCES - tst_qwidget.cpp - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::Gui - Qt::GuiPrivate - Qt::TestPrivate - Qt::Widgets - Qt::WidgetsPrivate -) +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidget LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() # Resources: set(qwidget_resource_files @@ -24,17 +19,20 @@ set(qwidget_resource_files "hellotr_la.qm" ) -qt_internal_add_resource(tst_qwidget "qwidget" - PREFIX - "/" - FILES - ${qwidget_resource_files} +qt_internal_add_test(tst_qwidget + SOURCES + tst_qwidget.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::TestPrivate + Qt::Widgets + Qt::WidgetsPrivate + TESTDATA ${qwidget_resource_files} + BUILTIN_TESTDATA ) - -#### Keys ignored in scope 1:.:.:qwidget.pro:<TRUE>: -# testcase.timeout = "600" - ## Scopes: ##################################################################### @@ -43,16 +41,8 @@ qt_internal_extend_target(tst_qwidget CONDITION AIX -fpermissive ) -qt_internal_extend_target(tst_qwidget CONDITION APPLE - SOURCES - tst_qwidget_mac_helpers.mm - PUBLIC_LIBRARIES - ${FWAppKit} - ${FWSecurity} -) - qt_internal_extend_target(tst_qwidget CONDITION WIN32 - PUBLIC_LIBRARIES + LIBRARIES gdi32 user32 ) diff --git a/tests/auto/widgets/kernel/qwidget/qwidget.qrc b/tests/auto/widgets/kernel/qwidget/qwidget.qrc deleted file mode 100644 index 597ea872a5..0000000000 --- a/tests/auto/widgets/kernel/qwidget/qwidget.qrc +++ /dev/null @@ -1,8 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> - <qresource> - <file>geometry.dat</file> - <file>geometry-maximized.dat</file> - <file>geometry-fullscreen.dat</file> - <file>hellotr_la.qm</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data0.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data0.qsnap Binary files differdeleted file mode 100644 index b3473cdefe..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data0.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data1.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data1.qsnap Binary files differdeleted file mode 100644 index 10007733ca..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data1.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data2.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data2.qsnap Binary files differdeleted file mode 100644 index cde5964a30..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data2.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data3.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data3.qsnap Binary files differdeleted file mode 100644 index 23ea1410e4..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Motif_data3.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data0.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data0.qsnap Binary files differdeleted file mode 100644 index a8918c1d1b..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data0.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data1.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data1.qsnap Binary files differdeleted file mode 100644 index 0981cf5dd1..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data1.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data2.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data2.qsnap Binary files differdeleted file mode 100644 index 75d09136cf..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data2.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data3.qsnap b/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data3.qsnap Binary files differdeleted file mode 100644 index f58f74d030..0000000000 --- a/tests/auto/widgets/kernel/qwidget/testdata/paintEvent/res_Windows_data3.qsnap +++ /dev/null diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index 7ca08993ab..cac968c5dd 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "../../../shared/highdpi.h" @@ -38,6 +13,7 @@ #include <qlineedit.h> #include <qlistview.h> #include <qmessagebox.h> +#include <qmimedata.h> #include <qpainter.h> #include <qpoint.h> #include <qpushbutton.h> @@ -52,6 +28,7 @@ #include <qmainwindow.h> #include <qdockwidget.h> #include <qrandom.h> +#include <qsignalspy.h> #include <qstylehints.h> #include <qtoolbar.h> #include <qtoolbutton.h> @@ -61,6 +38,7 @@ #include <QtGui/qbackingstore.h> #include <QtGui/qguiapplication.h> #include <QtGui/qpa/qplatformwindow.h> +#include <QtGui/qpa/qplatformdrag.h> #include <QtGui/qscreen.h> #include <qmenubar.h> #include <qcompleter.h> @@ -73,15 +51,13 @@ #include <QtGui/qwindow.h> #include <qtimer.h> #include <QtWidgets/QDoubleSpinBox> - -#if defined(Q_OS_MACOS) -#include "tst_qwidget_mac_helpers.h" // Abstract the ObjC stuff out so not everyone must run an ObjC++ compile. -#endif +#include <QtWidgets/QComboBox> #include <QtTest/QTest> #include <QtTest/private/qtesthelpers_p.h> using namespace QTestPrivate; +using namespace Qt::StringLiterals; #if defined(Q_OS_WIN) # include <QtCore/qt_windows.h> @@ -146,6 +122,34 @@ static QByteArray msgComparisonFailed(T v1, const char *op, T v2) return s.toLocal8Bit(); } +template<class T> class EventSpy : public QObject +{ +public: + EventSpy(T *widget, QEvent::Type event) + : m_widget(widget), eventToSpy(event) + { + if (m_widget) + m_widget->installEventFilter(this); + } + + T *widget() const { return m_widget; } + int count() const { return m_count; } + void clear() { m_count = 0; } + +protected: + bool eventFilter(QObject *object, QEvent *event) override + { + if (event->type() == eventToSpy) + ++m_count; + return QObject::eventFilter(object, event); + } + +private: + T *m_widget; + const QEvent::Type eventToSpy; + int m_count = 0; +}; + Q_LOGGING_CATEGORY(lcTests, "qt.widgets.tests") class tst_QWidget : public QObject @@ -159,7 +163,10 @@ public: public slots: void initTestCase(); void cleanup(); + private slots: + void nativeWindowAttribute(); + void addActionOverloads(); void getSetCheck(); void fontPropagation(); void fontPropagation2(); @@ -184,10 +191,15 @@ private slots: void mapFromAndTo(); void focusChainOnHide(); void focusChainOnReparent(); + void focusAbstraction(); void defaultTabOrder(); void reverseTabOrder(); void tabOrderWithProxy(); + void tabOrderWithProxyDisabled(); + void tabOrderWithProxyOutOfOrder(); void tabOrderWithCompoundWidgets(); + void tabOrderWithCompoundWidgetsInflection_data(); + void tabOrderWithCompoundWidgetsInflection(); void tabOrderWithCompoundWidgetsNoFocusPolicy(); void tabOrderNoChange(); void tabOrderNoChange2(); @@ -195,12 +207,16 @@ private slots: void appFocusWidgetWhenLosingFocusProxy(); void explicitTabOrderWithComplexWidget(); void explicitTabOrderWithSpinBox_QTBUG81097(); + void tabOrderList(); + void tabOrderComboBox_data(); + void tabOrderComboBox(); #if defined(Q_OS_WIN) void activation(); #endif void reparent(); void setScreen(); void windowState(); + void resizePropagation(); void showMaximized(); void showFullScreen(); void showMinimized(); @@ -218,6 +234,8 @@ private slots: void saveRestoreGeometry(); void restoreVersion1Geometry_data(); void restoreVersion1Geometry(); + void restoreGeometryAfterScreenChange_data(); + void restoreGeometryAfterScreenChange(); void widgetAt(); #ifdef Q_OS_MACOS @@ -238,11 +256,14 @@ private slots: void ensureCreated(); void createAndDestroy(); + void eventsAndAttributesOnDestroy(); void winIdChangeEvent(); void persistentWinId(); void showNativeChild(); + void closeAndShowNativeChild(); + void closeAndShowWithNativeChild(); void transientParent(); - void qobject_castInDestroyedSlot(); + void qobject_castOnDestruction(); void showHideEvent_data(); void showHideEvent(); @@ -294,8 +315,11 @@ private slots: void childEvents(); void render(); + void renderChildFillsBackground(); + void renderTargetOffset(); void renderInvisible(); void renderWithPainter(); + void renderRTL(); void render_task188133(); void render_task211796(); void render_task217815(); @@ -328,6 +352,8 @@ private slots: void resizeInPaintEvent(); void opaqueChildren(); + void dumpObjectTree(); + void setMaskInResizeEvent(); void moveInResizeEvent(); @@ -356,7 +382,10 @@ private slots: void maskedUpdate(); #ifndef QT_NO_CURSOR void syntheticEnterLeave(); + void enterLeaveOnWindowShowHide_data(); + void enterLeaveOnWindowShowHide(); void taskQTBUG_4055_sendSyntheticEnterLeave(); + void hoverPosition(); void underMouse(); void taskQTBUG_27643_enterEvents(); #endif @@ -368,6 +397,8 @@ private slots: void focusWidget_task254563(); void rectOutsideCoordinatesLimit_task144779(); void setGraphicsEffect(); + void render_graphicsEffect_data(); + void render_graphicsEffect(); #ifdef QT_BUILD_INTERNAL void destroyBackingStore(); @@ -378,7 +409,8 @@ private slots: void openModal_taskQTBUG_5804(); void focusProxy(); - void focusProxyAndInputMethods(); + void imEnabledNotImplemented(); + #ifdef QT_BUILD_INTERNAL void scrollWithoutBackingStore(); #endif @@ -400,6 +432,7 @@ private slots: void touchUpdateOnNewTouch(); void touchCancel(); void touchEventsForGesturePendingWidgets(); + void synthMouseDoubleClick(); void styleSheetPropagation(); @@ -425,10 +458,31 @@ private slots: void receivesApplicationFontChangeEvent(); void receivesApplicationPaletteChangeEvent(); void deleteWindowInCloseEvent(); + void quitOnClose(); -private: - bool ensureScreenSize(int width, int height); + void setParentChangesFocus_data(); + void setParentChangesFocus(); + + void activateWhileModalHidden(); + +#ifdef Q_OS_ANDROID + void showFullscreenAndroid(); +#endif + void setVisibleDuringDestruction(); + + void explicitShowHide(); + + void dragEnterLeaveSymmetry(); + + void reparentWindowHandles_data(); + void reparentWindowHandles(); + +#ifndef QT_NO_CONTEXTMENU + void contextMenuTrigger(); +#endif + +private: const QString m_platform; QSize m_testWidgetSize; QPoint m_availableTopLeft; @@ -436,13 +490,17 @@ private: const bool m_windowsAnimationsEnabled; QPointingDevice *m_touchScreen; const int m_fuzz; -}; + QPalette simplePalette(); -bool tst_QWidget::ensureScreenSize(int width, int height) -{ - const QSize available = QGuiApplication::primaryScreen()->availableGeometry().size(); - return (available.width() >= width && available.height() >= height); -} +private: + enum class ScreenPosition { + OffAbove, + OffLeft, + OffBelow, + OffRight, + Contained + }; +}; // Testing get/set functions void tst_QWidget::getSetCheck() @@ -458,18 +516,24 @@ void tst_QWidget::getSetCheck() QVERIFY(var1.data() != obj1.style()); QVERIFY(obj1.style() != nullptr); // style can never be 0 for a widget + const QRegularExpression negativeNotPossible(u"^.*Negative sizes \\(.*\\) are not possible$"_s); + const QRegularExpression largestAllowedSize(u"^.*The largest allowed size is \\(.*\\)$"_s); // int QWidget::minimumWidth() // void QWidget::setMinimumWidth(int) obj1.setMinimumWidth(0); QCOMPARE(obj1.minimumWidth(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); obj1.setMinimumWidth(INT_MIN); QCOMPARE(obj1.minimumWidth(), 0); // A widgets width can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); obj1.setMinimumWidth(INT_MAX); child1.setMinimumWidth(0); QCOMPARE(child1.minimumWidth(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); child1.setMinimumWidth(INT_MIN); QCOMPARE(child1.minimumWidth(), 0); // A widgets width can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); child1.setMinimumWidth(INT_MAX); QCOMPARE(child1.minimumWidth(), QWIDGETSIZE_MAX); // The largest minimum size should only be as big as the maximium @@ -477,14 +541,18 @@ void tst_QWidget::getSetCheck() // void QWidget::setMinimumHeight(int) obj1.setMinimumHeight(0); QCOMPARE(obj1.minimumHeight(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); obj1.setMinimumHeight(INT_MIN); QCOMPARE(obj1.minimumHeight(), 0); // A widgets height can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); obj1.setMinimumHeight(INT_MAX); child1.setMinimumHeight(0); QCOMPARE(child1.minimumHeight(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); child1.setMinimumHeight(INT_MIN); QCOMPARE(child1.minimumHeight(), 0); // A widgets height can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); child1.setMinimumHeight(INT_MAX); QCOMPARE(child1.minimumHeight(), QWIDGETSIZE_MAX); // The largest minimum size should only be as big as the maximium @@ -492,8 +560,10 @@ void tst_QWidget::getSetCheck() // void QWidget::setMaximumWidth(int) obj1.setMaximumWidth(0); QCOMPARE(obj1.maximumWidth(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); obj1.setMaximumWidth(INT_MIN); QCOMPARE(obj1.maximumWidth(), 0); // A widgets width can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); obj1.setMaximumWidth(INT_MAX); QCOMPARE(obj1.maximumWidth(), QWIDGETSIZE_MAX); // QWIDGETSIZE_MAX is the abs max, not INT_MAX @@ -501,8 +571,10 @@ void tst_QWidget::getSetCheck() // void QWidget::setMaximumHeight(int) obj1.setMaximumHeight(0); QCOMPARE(obj1.maximumHeight(), 0); + QTest::ignoreMessage(QtWarningMsg, negativeNotPossible); obj1.setMaximumHeight(INT_MIN); QCOMPARE(obj1.maximumHeight(), 0); // A widgets height can never be less than 0 + QTest::ignoreMessage(QtWarningMsg, largestAllowedSize); obj1.setMaximumHeight(INT_MAX); QCOMPARE(obj1.maximumHeight(), QWIDGETSIZE_MAX); // QWIDGETSIZE_MAX is the abs max, not INT_MAX @@ -562,6 +634,7 @@ void tst_QWidget::getSetCheck() QBoxLayout *var11 = new QBoxLayout(QBoxLayout::LeftToRight); obj1.setLayout(var11); QCOMPARE(static_cast<QLayout *>(var11), obj1.layout()); + QTest::ignoreMessage(QtWarningMsg, "QWidget::setLayout: Cannot set layout to 0"); obj1.setLayout(nullptr); QCOMPARE(static_cast<QLayout *>(var11), obj1.layout()); // You cannot set a 0-pointer layout, that keeps the current delete var11; // This will remove the layout from the widget @@ -585,7 +658,7 @@ void tst_QWidget::getSetCheck() #if defined (Q_OS_WIN) obj1.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); const HWND handle = reinterpret_cast<HWND>(obj1.winId()); // explicitly create window handle - QVERIFY(GetWindowLong(handle, GWL_STYLE) & LONG(WS_POPUP)); + QVERIFY(GetWindowLongPtr(handle, GWL_STYLE) & LONG_PTR(WS_POPUP)); #endif } @@ -622,6 +695,10 @@ tst_QWidget::~tst_QWidget() void tst_QWidget::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 widget, 200 for < 2000, scale up for larger screens // to avoid Windows warnings about minimum size for decorated windows. int width = 200; @@ -644,6 +721,90 @@ void tst_QWidget::cleanup() QTRY_VERIFY(QApplication::topLevelWidgets().isEmpty()); } +template <typename T> +struct ImplicitlyConvertibleTo { + T t; + operator const T() const { return t; } + operator T() { return t; } +}; + +void testFunction0() {} +void testFunction1(bool) {} + +void tst_QWidget::nativeWindowAttribute() +{ + QWidget parent; + QWidget child(&parent); + + QCOMPARE(parent.windowHandle(), nullptr); + QCOMPARE(child.windowHandle(), nullptr); + + // Setting WA_NativeWindow should create window handle + parent.setAttribute(Qt::WA_NativeWindow); + QVERIFY(parent.windowHandle() != nullptr); + // But not its child's window handle + QCOMPARE(child.windowHandle(), nullptr); + // Until the child also gains WA_NativeWindow + child.setAttribute(Qt::WA_NativeWindow); + QVERIFY(child.windowHandle() != nullptr); +} + +void tst_QWidget::addActionOverloads() +{ + // almost exhaustive check of addAction() overloads: + // (text), (icon, text), (icon, text, shortcut), (text, shortcut) + // each with a good sample of ways to QObject::connect() to + // QAction::triggered(bool) + QWidget w; + + // don't just pass QString etc - that'd be too easy (think QStringBuilder) + ImplicitlyConvertibleTo<QString> text = {QStringLiteral("foo")}; + ImplicitlyConvertibleTo<QIcon> icon; + + const auto check = [&](auto &...args) { // don't need to perfectly-forward, only lvalues passed + w.addAction(args...); + + w.addAction(args..., &w, SLOT(deleteLater())); + w.addAction(args..., &w, &QObject::deleteLater); + w.addAction(args..., testFunction0); + w.addAction(args..., &w, testFunction0); + w.addAction(args..., testFunction1); + w.addAction(args..., &w, testFunction1); + w.addAction(args..., [&](bool b) { w.setEnabled(b); }); + w.addAction(args..., &w, [&](bool b) { w.setEnabled(b); }); + + w.addAction(args..., &w, SLOT(deleteLater()), Qt::QueuedConnection); + w.addAction(args..., &w, &QObject::deleteLater, Qt::QueuedConnection); + // doesn't exist: w.addAction(args..., testFunction0, Qt::QueuedConnection); + w.addAction(args..., &w, testFunction0, Qt::QueuedConnection); + // doesn't exist: w.addAction(args..., testFunction1, Qt::QueuedConnection); + w.addAction(args..., &w, testFunction1, Qt::QueuedConnection); + // doesn't exist: w.addAction(args..., [&](bool b) { w.setEnabled(b); }, Qt::QueuedConnection); + w.addAction(args..., &w, [&](bool b) { w.setEnabled(b); }, Qt::QueuedConnection); + }; + const auto check1 = [&](auto &arg, auto &...args) { + check(arg, args...); + check(std::as_const(arg), args...); + }; + const auto check2 = [&](auto &arg1, auto &arg2, auto &...args) { + check1(arg1, arg2, args...); + check1(arg1, std::as_const(arg2), args...); + }; + [[maybe_unused]] + const auto check3 = [&](auto &arg1, auto &arg2, auto &arg3) { + check2(arg1, arg2, arg3); + check2(arg1, arg2, std::as_const(arg3)); + }; + + check1(text); + check2(icon, text); +#ifndef QT_NO_SHORTCUT + ImplicitlyConvertibleTo<QKeySequence> keySequence = {Qt::CTRL | Qt::Key_C}; + check2(text, keySequence); + check3(icon, text, keySequence); +#endif +} + void tst_QWidget::fontPropagation() { QScopedPointer<QWidget> testWidget(new QWidget); @@ -1166,6 +1327,8 @@ void tst_QWidget::ignoreKeyEventsWhenDisabled_QTBUG27417() centerOnScreen(&lineEdit); lineEdit.setDisabled(true); lineEdit.show(); + QTest::ignoreMessage(QtWarningMsg, "Keyboard event not accepted by receiving widget"); + QTest::ignoreMessage(QtWarningMsg, "Keyboard event not accepted by receiving widget"); QTest::keyClick(&lineEdit, Qt::Key_A); QTRY_VERIFY(lineEdit.text().isEmpty()); } @@ -1740,6 +1903,7 @@ void tst_QWidget::focusChainOnReparent() } QWidget window2; + child22->setParent(child21); child2->setParent(&window2); QWidget *expectedNewChain[5] = {&window2, child2, child21, child22, &window2}; @@ -1780,8 +1944,6 @@ void tst_QWidget::focusChainOnHide() QWidget::setTabOrder(child, parent.data()); parent->show(); - QApplication::setActiveWindow(parent->window()); - child->activateWindow(); child->setFocus(); QTRY_VERIFY(child->hasFocus()); @@ -1822,8 +1984,11 @@ public: setObjectName(name); lineEdit1 = new QLineEdit; + lineEdit1->setObjectName(name + "/lineEdit1"); lineEdit2 = new QLineEdit; + lineEdit2->setObjectName(name + "/lineEdit2"); lineEdit3 = new QLineEdit; + lineEdit3->setObjectName(name + "/lineEdit3"); lineEdit3->setEnabled(false); QHBoxLayout* hbox = new QHBoxLayout(this); @@ -1838,6 +2003,112 @@ public: QLineEdit *lineEdit3; }; +static QList<QWidget *> getFocusChain(QWidget *start, bool bForward) +{ + QList<QWidget *> ret; + QWidget *cur = start; + // detect infinite loop + int count = 100; + auto loopGuard = qScopeGuard([]{ + QFAIL("Inifinite loop detected in focus chain"); + }); + do { + ret += cur; + cur = bForward ? cur->nextInFocusChain() : cur->previousInFocusChain(); + if (!--count) + return ret; + } while (cur != start); + loopGuard.dismiss(); + return ret; +} + +void tst_QWidget::focusAbstraction() +{ + QLoggingCategory::setFilterRules("qt.widgets.focus=true"); + QWidget *widget1 = new QWidget; + widget1->setObjectName("Widget 1"); + QWidget *widget2 = new QWidget; + widget2->setObjectName("Widget 2"); + QWidget *widget3 = new QWidget; + widget3->setObjectName("Widget 3"); + QWidgetPrivate *priv1 = QWidgetPrivate::get(widget1); + QWidgetPrivate *priv2 = QWidgetPrivate::get(widget2); + QWidgetPrivate *priv3 = QWidgetPrivate::get(widget3); + + // Verify initialization + QVERIFY(!priv1->isInFocusChain()); + QVERIFY(!priv2->isInFocusChain()); + QVERIFY(!priv3->isInFocusChain()); + + // Verify, that parenting builds a focus chain. + QWidget parent; + parent.setObjectName("Parent"); + widget1->setParent(&parent); + widget2->setParent(&parent); + widget3->setParent(&parent); + QVERIFY(priv1->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + QVERIFY(priv3->isInFocusChain()); + QWidgetList expected{widget1, widget2, widget3, &parent}; + QCOMPARE(getFocusChain(widget1, true), expected); + + // Verify, that reparented focus children end up behind parent. + widget1->setParent(widget2); + priv2->insertIntoFocusChainAfter(widget3); + priv2->reparentFocusChildren(QWidgetPrivate::FocusDirection::Next); + expected = {widget1, &parent, widget3, widget2}; + QCOMPARE(getFocusChain(widget1, true), expected); + QVERIFY(priv1->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + QVERIFY(priv3->isInFocusChain()); + + // Check removal + priv3->removeFromFocusChain(QWidgetPrivate::FocusChainRemovalRule::AssertConsistency); + expected.removeOne(widget3); + QCOMPARE(getFocusChain(widget1, true), expected); + QVERIFY(priv1->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + QVERIFY(!priv3->isInFocusChain()); + + // Check insert + priv3->insertIntoFocusChain(QWidgetPrivate::FocusDirection::Previous, widget1); + expected = {widget3, widget1, &parent, widget2}; + QCOMPARE(getFocusChain(widget3, true), expected); + + // Verify, that take doesn't break + const QWidgetList taken = QWidgetPrivate::takeFromFocusChain(widget1, widget2); + QVERIFY(priv1->isFocusChainConsistent()); + expected = {widget1, &parent, widget2}; + QCOMPARE(taken, expected); + QVERIFY(priv1->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + QVERIFY(!priv3->isInFocusChain()); + + // Verify insertion of multiple widgets + QWidgetPrivate::insertIntoFocusChain(taken, QWidgetPrivate::FocusDirection::Next, widget3); + expected = {widget3, widget1, &parent, widget2}; + QCOMPARE(getFocusChain(widget3, true), expected); + QVERIFY(priv1->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + QVERIFY(priv2->isInFocusChain()); + + // Verify broken chain identification + // d'tor asserts chain consistency => repair before going out of scope + auto guard = qScopeGuard([priv2, widget3]{ priv2->focus_next = widget3; }); + + // Nullptr is not allowed + priv2->focus_next = nullptr; + QVERIFY(!priv1->isFocusChainConsistent()); + + // Chain looping back in the middle + priv2->focus_next = widget1; + QVERIFY(!priv1->isFocusChainConsistent()); + + // "last" element pointing to itself + priv2->focus_next = widget2; + QVERIFY(!priv1->isFocusChainConsistent()); +} + void tst_QWidget::defaultTabOrder() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) @@ -1861,7 +2132,6 @@ void tst_QWidget::defaultTabOrder() container.setWindowTitle(QLatin1String(QTest::currentTestFunction())); container.show(); container.activateWindow(); - QApplication::setActiveWindow(&container); QVERIFY(QTest::qWaitForWindowActive(&container)); QTRY_VERIFY(firstEdit->hasFocus()); @@ -1897,23 +2167,29 @@ void tst_QWidget::defaultTabOrder() void tst_QWidget::reverseTabOrder() { - if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + if (QGuiApplication::platformName().startsWith(QLatin1StringView("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); const int compositeCount = 2; Container container; - container.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + container.setObjectName(QLatin1StringView("Container")); + container.setWindowTitle(QLatin1StringView(QTest::currentTestFunction())); Composite* composite[compositeCount]; QLineEdit *firstEdit = new QLineEdit(); + firstEdit->setObjectName(QLatin1StringView("FirstEdit")); container.box->addWidget(firstEdit); + static constexpr QLatin1StringView comp("Composite-%1"); for (int i = 0; i < compositeCount; i++) { - composite[i] = new Composite(); + const QString name = QString(comp).arg(i); + composite[i] = new Composite(nullptr, name); + composite[i]->setObjectName(name); container.box->addWidget(composite[i]); } QLineEdit *lastEdit = new QLineEdit(); + lastEdit->setObjectName(QLatin1StringView("LastEdit")); container.box->addWidget(lastEdit); // Reverse tab order inside each composite @@ -1922,7 +2198,6 @@ void tst_QWidget::reverseTabOrder() container.show(); container.activateWindow(); - QApplication::setActiveWindow(&container); QVERIFY(QTest::qWaitForWindowActive(&container)); QTRY_VERIFY(firstEdit->hasFocus()); @@ -1957,6 +2232,139 @@ void tst_QWidget::reverseTabOrder() QVERIFY(firstEdit->hasFocus()); } +void tst_QWidget::tabOrderList() +{ + Composite c; + QCOMPARE(getFocusChain(&c, true), + QList<QWidget *>({&c, c.lineEdit1, c.lineEdit2, c.lineEdit3})); + QWidget::setTabOrder({c.lineEdit3, c.lineEdit2, c.lineEdit1}); + // not starting with 3 like one would maybe expect, but still 3, 2, 1 + QCOMPARE(getFocusChain(&c, true), + QList<QWidget *>({&c, c.lineEdit1, c.lineEdit3, c.lineEdit2})); +} + +void tst_QWidget::tabOrderComboBox_data() +{ + QTest::addColumn<const bool>("editableAtBeginning"); + QTest::addColumn<const QList<int>>("firstTabOrder"); + QTest::addColumn<const QList<int>>("secondTabOrder"); + + QTest::addRow("3 not editable") << false << QList<int>{2, 1, 0} << QList<int>{0, 1, 2}; + QTest::addRow("4 editable") << true << QList<int>{2, 1, 0, 3} << QList<int>{3, 0, 2, 1}; +} + +QWidgetList expectedFocusChain(const QList<QComboBox *> &boxes, const QList<int> &sequence) +{ + Q_ASSERT(boxes.count() == sequence.count()); + QWidgetList widgets; + for (int i : sequence) { + Q_ASSERT(i >= 0); + Q_ASSERT(i < boxes.count()); + QComboBox *box = boxes.at(i); + widgets.append(box); + if (box->lineEdit()) + widgets.append(box->lineEdit()); + } + + return widgets; +} + +QWidgetList realFocusChain(const QList<QComboBox *> &boxes, const QList<int> &sequence) +{ + const QWidgetList all = getFocusChain(boxes.at(sequence.at(0)), true); + QWidgetList chain; + // Filter everything with NoFocus + for (auto *widget : all) { + if (widget->focusPolicy() != Qt::NoFocus) + chain << widget; + } + return chain; +} + +void setTabOrder(const QList<QComboBox *> &boxes, const QList<int> &sequence) +{ + Q_ASSERT(boxes.count() == sequence.count()); + QWidget *previous = nullptr; + for (int i : sequence) { + Q_ASSERT(i >= 0); + Q_ASSERT(i < boxes.count()); + QWidget *box = boxes.at(i); + if (!previous) { + previous = box; + } else { + QWidget::setTabOrder(previous, box); + previous = box; + } + } +} + +void tst_QWidget::tabOrderComboBox() +{ + QFETCH(const bool, editableAtBeginning); + QFETCH(const QList<int>, firstTabOrder); + QFETCH(const QList<int>, secondTabOrder); + const int count = firstTabOrder.count(); + Q_ASSERT(count == secondTabOrder.count()); + Q_ASSERT(count > 1); + + QWidget w; + w.setObjectName("MainWidget"); + QVBoxLayout* layout = new QVBoxLayout(); + w.setLayout(layout); + + QList<QComboBox *> boxes; + for (int i = 0; i < count; ++i) { + auto box = new QComboBox; + box->setObjectName("ComboBox " + QString::number(i)); + if (editableAtBeginning) { + box->setEditable(true); + box->lineEdit()->setObjectName("LineEdit " + QString::number(i)); + } + boxes.append(box); + layout->addWidget(box); + } + layout->addStretch(); + +#define COMPARE(seq)\ + setTabOrder(boxes, seq);\ + QCOMPARE(realFocusChain(boxes, seq), expectedFocusChain(boxes, seq)) + + COMPARE(firstTabOrder); + + if (!editableAtBeginning) { + for (auto *box : boxes) + box->setEditable(box); + } + + COMPARE(secondTabOrder); + + // Remove the focus proxy of the first combobox's line edit. + QComboBox *box = boxes.at(0); + QLineEdit *lineEdit = box->lineEdit(); + const QWidget *prev = lineEdit->previousInFocusChain(); + const QWidget *next = lineEdit->nextInFocusChain(); + const QWidget *proxy = lineEdit->focusProxy(); + QCOMPARE(proxy, box); + lineEdit->setFocusProxy(nullptr); + QCOMPARE(lineEdit->focusProxy(), nullptr); + QCOMPARE(lineEdit->previousInFocusChain(), prev); + QCOMPARE(lineEdit->nextInFocusChain(), next); + + // Remove first item and check chain consistency + boxes.removeFirst(); + delete box; + + // Create new list with 0 removed and other indexes updated + QList<int> thirdTabOrder(secondTabOrder); + thirdTabOrder.removeIf([](int i){ return i == 0; }); + for (int &i : thirdTabOrder) + --i; + + COMPARE(thirdTabOrder); + +#undef COMPARE +} + void tst_QWidget::tabOrderWithProxy() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) @@ -1984,7 +2392,6 @@ void tst_QWidget::tabOrderWithProxy() container.show(); container.activateWindow(); - QApplication::setActiveWindow(&container); QVERIFY(QTest::qWaitForWindowActive(&container)); QTRY_VERIFY(firstEdit->hasFocus()); @@ -2018,6 +2425,81 @@ void tst_QWidget::tabOrderWithProxy() QVERIFY(firstEdit->hasFocus()); } +static QString focusWidgetName() +{ + return QApplication::focusWidget() != nullptr + ? QApplication::focusWidget()->objectName() + : QStringLiteral("No focus widget"); +} + +void tst_QWidget::tabOrderWithProxyDisabled() +{ + Container container; + container.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + + QLineEdit lineEdit1; + lineEdit1.setObjectName("lineEdit1"); + + QWidget containingWidget; + containingWidget.setFocusPolicy(Qt::StrongFocus); + auto *containingLayout = new QVBoxLayout; + QLineEdit lineEdit2; + lineEdit2.setObjectName("lineEdit2"); + QLineEdit lineEdit3; + lineEdit3.setObjectName("lineEdit3"); + containingLayout->addWidget(&lineEdit2); + containingLayout->addWidget(&lineEdit3); + containingWidget.setLayout(containingLayout); + containingWidget.setFocusProxy(&lineEdit2); + lineEdit2.setEnabled(false); + + container.box->addWidget(&lineEdit1); + container.box->addWidget(&containingWidget); + + container.show(); + container.activateWindow(); + + if (!QTest::qWaitForWindowActive(&container)) + QSKIP("Window failed to activate, skipping test"); + + QVERIFY2(lineEdit1.hasFocus(), + qPrintable(focusWidgetName())); + container.tab(); + QVERIFY2(!lineEdit2.hasFocus(), + qPrintable(focusWidgetName())); + QVERIFY2(lineEdit3.hasFocus(), + qPrintable(focusWidgetName())); + container.tab(); + QVERIFY2(lineEdit1.hasFocus(), + qPrintable(focusWidgetName())); + container.backTab(); + QVERIFY2(lineEdit3.hasFocus(), + qPrintable(focusWidgetName())); + container.backTab(); + QVERIFY2(!lineEdit2.hasFocus(), + qPrintable(focusWidgetName())); + QVERIFY2(lineEdit1.hasFocus(), + qPrintable(focusWidgetName())); +} + +//#define DEBUG_FOCUS_CHAIN +static void dumpFocusChain(QWidget *start, bool bForward, const char *desc = nullptr) +{ +#ifdef DEBUG_FOCUS_CHAIN + qDebug() << "Dump focus chain, start:" << start << "isForward:" << bForward << desc; + QWidget *cur = start; + do { + qDebug() << "-" << cur; + auto widgetPrivate = static_cast<QWidgetPrivate *>(qt_widget_private(cur)); + cur = bForward ? widgetPrivate->focus_next : widgetPrivate->focus_prev; + } while (cur != start); +#else + Q_UNUSED(start); + Q_UNUSED(bForward); + Q_UNUSED(desc); +#endif +} + void tst_QWidget::tabOrderWithCompoundWidgets() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) @@ -2058,7 +2540,6 @@ void tst_QWidget::tabOrderWithCompoundWidgets() container.show(); container.activateWindow(); - QApplication::setActiveWindow(&container); QVERIFY(QTest::qWaitForWindowActive(&container)); lastEdit->setFocus(); @@ -2109,34 +2590,180 @@ void tst_QWidget::tabOrderWithCompoundWidgets() QVERIFY(lastEdit->hasFocus()); } -static QList<QWidget *> getFocusChain(QWidget *start, bool bForward) +void tst_QWidget::tabOrderWithProxyOutOfOrder() { - QList<QWidget *> ret; - QWidget *cur = start; - do { - ret += cur; - auto widgetPrivate = static_cast<QWidgetPrivate *>(qt_widget_private(cur)); - cur = bForward ? widgetPrivate->focus_next : widgetPrivate->focus_prev; - } while (cur != start); - return ret; + Container container; + container.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + container.setObjectName(QLatin1StringView("Container")); + + // important to create the widgets with parent so that they are + // added to the focus chain already now, and with the buttonBox + // before the outsideButton. + QWidget buttonBox(&container); + buttonBox.setObjectName(QLatin1StringView("buttonBox")); + QPushButton outsideButton(&container); + outsideButton.setObjectName(QLatin1StringView("outsideButton")); + + container.box->addWidget(&outsideButton); + container.box->addWidget(&buttonBox); + QCOMPARE(getFocusChain(&container, true), + QList<QWidget*>({&container, &buttonBox, &outsideButton})); + + // this now adds okButon and cancelButton to the focus chain, + // after the outsideButton - so the outsideButton is in between + // the buttonBox and the children of the buttonBox! + QPushButton okButton(&buttonBox); + okButton.setObjectName("okButton"); + QPushButton cancelButton(&buttonBox); + cancelButton.setObjectName("cancelButton"); + QCOMPARE(getFocusChain(&container, true), + QList<QWidget*>({&container, &buttonBox, &outsideButton, &okButton, &cancelButton})); + + // by setting the okButton as the focusProxy, the outsideButton becomes + // unreachable when navigating the focus chain as the buttonBox is in front + // of, and proxies to the okButton behind the outsideButton. setFocusProxy + // must fix that by moving the buttonBox in front of the first sibling of + // the proxy. + buttonBox.setFocusProxy(&okButton); + QCOMPARE(getFocusChain(&container, true), + QList<QWidget*>({&container, &outsideButton, &buttonBox, &okButton, &cancelButton})); + + container.show(); + container.activateWindow(); + if (!QTest::qWaitForWindowActive(&container)) + QSKIP("Window failed to activate, skipping test"); + + QCOMPARE(QApplication::focusWidget(), &outsideButton); + container.tab(); + QCOMPARE(QApplication::focusWidget(), &okButton); + container.tab(); + QCOMPARE(QApplication::focusWidget(), &cancelButton); + container.tab(); + QCOMPARE(QApplication::focusWidget(), &outsideButton); + + container.backTab(); + QCOMPARE(QApplication::focusWidget(), &cancelButton); + container.backTab(); + QCOMPARE(QApplication::focusWidget(), &okButton); + container.backTab(); + QCOMPARE(QApplication::focusWidget(), &outsideButton); + container.backTab(); + QCOMPARE(QApplication::focusWidget(), &cancelButton); } -//#define DEBUG_FOCUS_CHAIN -static void dumpFocusChain(QWidget *start, bool bForward, const char *desc = nullptr) +static bool isFocusChainConsistent(QWidget *widget) { -#ifdef DEBUG_FOCUS_CHAIN - qDebug() << "Dump focus chain, start:" << start << "isForward:" << bForward << desc; - QWidget *cur = start; - do { - qDebug() << cur; - auto widgetPrivate = static_cast<QWidgetPrivate *>(qt_widget_private(cur)); - cur = bForward ? widgetPrivate->focus_next : widgetPrivate->focus_prev; - } while (cur != start); -#else - Q_UNUSED(start); - Q_UNUSED(bForward); - Q_UNUSED(desc); -#endif + auto forward = getFocusChain(widget, true); + auto backward = getFocusChain(widget, false); + auto logger = qScopeGuard([=]{ + qCritical("Focus chain is not consistent!"); + qWarning() << forward.size() << "forwards: " << forward; + qWarning() << backward.size() << "backwards:" << backward; + }); + // both lists start with the same, the widget + if (forward.takeFirst() != backward.takeFirst()) + return false; + const qsizetype chainLength = forward.size(); + if (backward.size() != chainLength) + return false; + for (qsizetype i = 0; i < chainLength; ++i) { + if (forward.at(i) != backward.at(chainLength - i - 1)) + return false; + } + logger.dismiss(); + return true; +} + +/* + This tests that we end up with consistent and complete chains when we set + the tab order from a widget (the lineEdit) inside a compound (the tabWidget) + to the compound, or visa versa. In that case, QWidget::setTabOrder will walk + the focus chain to the focus child inside the compound to replace the compound + itself when manipulating the tab order. If that last focus child is then + however also the lineEdit, then we must not create an inconsistent or + incomplete loop. + + The tabWidget is seen as a compound because QTabWidget sets the tab bar as + the focus proxy, and it has more widgets inside, like pages, toolbuttons etc. +*/ +void tst_QWidget::tabOrderWithCompoundWidgetsInflection_data() +{ + QTest::addColumn<QByteArrayList>("tabOrder"); + + QTest::addRow("forward") + << QByteArrayList{"dialog", "tabWidget", "lineEdit", "compound", "okButton", "cancelButton"}; + QTest::addRow("backward") + << QByteArrayList{"dialog", "cancelButton", "okButton", "compound", "lineEdit", "tabWidget"}; +} + +void tst_QWidget::tabOrderWithCompoundWidgetsInflection() +{ + QFETCH(const QByteArrayList, tabOrder); + + QDialog dialog; + dialog.setObjectName("dialog"); + QTabWidget *tabWidget = new QTabWidget; + tabWidget->setObjectName("tabWidget"); + tabWidget->setFocusPolicy(Qt::TabFocus); + QWidget *page = new QWidget; + page->setObjectName("page"); + QLineEdit *lineEdit = new QLineEdit; + lineEdit->setObjectName("lineEdit"); + QWidget *compound = new QWidget; + compound->setObjectName("compound"); + compound->setFocusPolicy(Qt::TabFocus); + QPushButton *okButton = new QPushButton("Ok"); + okButton->setObjectName("okButton"); + okButton->setFocusPolicy(Qt::TabFocus); + QPushButton *cancelButton = new QPushButton("Cancel"); + cancelButton->setObjectName("cancelButton"); + cancelButton->setFocusPolicy(Qt::TabFocus); + + QVBoxLayout *pageLayout = new QVBoxLayout; + pageLayout->addWidget(lineEdit); + page->setLayout(pageLayout); + tabWidget->addTab(page, "Tab"); + + QHBoxLayout *compoundLayout = new QHBoxLayout; + compoundLayout->addStretch(); + compoundLayout->addWidget(cancelButton); + compoundLayout->addWidget(okButton); + compound->setFocusProxy(okButton); + compound->setLayout(compoundLayout); + + QVBoxLayout *dialogLayout = new QVBoxLayout; + dialogLayout->addWidget(tabWidget); + dialogLayout->addWidget(compound); + dialog.setLayout(dialogLayout); + + QVERIFY(isFocusChainConsistent(&dialog)); + + QList<QWidget *> expectedFocusChain; + for (qsizetype i = 0; i < tabOrder.size() - 1; ++i) { + QWidget *first = dialog.findChild<QWidget *>(tabOrder.at(i)); + if (!first && tabOrder.at(i) == dialog.objectName()) + first = &dialog; + QVERIFY(first); + if (i == 0) + expectedFocusChain.append(first); + QWidget *second = dialog.findChild<QWidget *>(tabOrder.at(i + 1)); + QVERIFY(second); + expectedFocusChain.append(second); + QWidget::setTabOrder(first, second); + QVERIFY(isFocusChainConsistent(&dialog)); + } + + const auto forwardChain = getFocusChain(&dialog, true); + auto logger = qScopeGuard([=]{ + qCritical("Order of widgets in focus chain not matching:"); + qCritical() << " Actual :" << forwardChain; + qCritical() << " Expected:" << expectedFocusChain; + }); + for (qsizetype i = 0; i < expectedFocusChain.size() - 2; ++i) { + QCOMPARE_LT(forwardChain.indexOf(expectedFocusChain.at(i)), + forwardChain.indexOf(expectedFocusChain.at(i + 1))); + } + logger.dismiss(); } void tst_QWidget::tabOrderWithCompoundWidgetsNoFocusPolicy() @@ -2160,28 +2787,27 @@ void tst_QWidget::tabOrderWithCompoundWidgetsNoFocusPolicy() container.show(); container.activateWindow(); - QApplication::setActiveWindow(&container); if (!QTest::qWaitForWindowActive(&container)) QSKIP("Window failed to activate, skipping test"); QVERIFY2(spinbox1.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); container.tab(); QVERIFY2(!spinbox2.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); QVERIFY2(spinbox3.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); container.tab(); QVERIFY2(spinbox1.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); container.backTab(); QVERIFY2(spinbox3.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); container.backTab(); QVERIFY2(!spinbox2.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); QVERIFY2(spinbox1.hasFocus(), - qPrintable(QApplication::focusWidget()->objectName())); + qPrintable(focusWidgetName())); } void tst_QWidget::tabOrderNoChange() @@ -2266,7 +2892,6 @@ void tst_QWidget::appFocusWidgetWithFocusProxyLater() QLineEdit *lineEdit = new QLineEdit(&window); lineEdit->setFocus(); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QCOMPARE(QApplication::focusWidget(), lineEdit); @@ -2294,7 +2919,6 @@ void tst_QWidget::appFocusWidgetWhenLosingFocusProxy() lineEdit->setFocusProxy(lineEditFocusProxy); lineEdit->setFocus(); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QCOMPARE(QApplication::focusWidget(), lineEditFocusProxy); QVERIFY(lineEdit->hasFocus()); @@ -2321,7 +2945,6 @@ void tst_QWidget::explicitTabOrderWithComplexWidget() QWidget::setTabOrder(lineEditOne, lineEditTwo); lineEditOne->setFocus(); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QTRY_COMPARE(QApplication::focusWidget(), lineEditOne); @@ -2350,7 +2973,6 @@ void tst_QWidget::explicitTabOrderWithSpinBox_QTBUG81097() QWidget::setTabOrder(spinBoxTwo, lineEdit); spinBoxOne->setFocus(); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QTRY_COMPARE(QApplication::focusWidget(), spinBoxOne); @@ -2397,6 +3019,24 @@ void tst_QWidget::activation() } #endif // Q_OS_WIN +struct WindowStateChangeWatcher : public QObject +{ + WindowStateChangeWatcher(QWidget *widget) + { + Q_ASSERT(widget->window()->windowHandle()); + widget->window()->windowHandle()->installEventFilter(this); + lastWindowStates = widget->window()->windowHandle()->windowState(); + } + Qt::WindowStates lastWindowStates; +protected: + bool eventFilter(QObject *receiver, QEvent *event) override + { + if (event->type() == QEvent::WindowStateChange) + lastWindowStates = static_cast<QWindow *>(receiver)->windowState(); + return QObject::eventFilter(receiver, event); + } +}; + void tst_QWidget::windowState() { #ifdef Q_OS_MACOS @@ -2410,12 +3050,14 @@ void tst_QWidget::windowState() QPoint pos; QSize size = m_testWidgetSize; - if (QGuiApplicationPrivate::platformIntegration()->defaultWindowState(Qt::Widget) - == Qt::WindowFullScreen) { + const Qt::WindowState defaultWidgetState = + QGuiApplicationPrivate::platformIntegration()->defaultWindowState(Qt::Widget); + if (defaultWidgetState == Qt::WindowFullScreen) size = QGuiApplication::primaryScreen()->size(); - } else { + else if (defaultWidgetState == Qt::WindowMaximized) + size = QGuiApplication::primaryScreen()->availableSize(); + else pos = QPoint(10, 10); - } QWidget widget1; widget1.move(pos); @@ -2530,6 +3172,119 @@ void tst_QWidget::windowState() QTRY_COMPARE(widget1.size(), size); } +// Test propagation of size and state from platform window to QWidget +// Windows and linux/XCB only +void tst_QWidget::resizePropagation() +{ +#if !defined(Q_OS_LINUX) && !defined(Q_OS_WIN) + QSKIP("resizePropagation test is designed for Linux/XCB and Windows only"); +#endif + const bool xcb = (m_platform == QStringLiteral("xcb")); +#ifdef Q_OS_LINUX + if (!xcb) + QSKIP("resizePropagation test is designed for XCB only"); +#endif + + // Windows: + // When a widget is maximized after it has been resized, the widget retains its original 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); + int count = 0; + + const QSize screenSize = QGuiApplication::primaryScreen()->size(); + const QSize size1 = QSize(screenSize.width() * 0.5, screenSize.height() * 0.5); + const QSize size2 = QSize(screenSize.width() * 0.625, screenSize.height() * 0.833); + + 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 == Equal) + count = spy.count(); + + // Resize if required + if (size.isValid()) + widget.resize(size); + + // Wait for the widget anyway + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + // Check signal count and qDebug output for fail analysis + 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 + QTRY_COMPARE(widget.windowState(), windowState); + QTRY_COMPARE(window->windowState(), windowState); + + // Check target size with fail or warning + switch (checkTargetSize) { + case Fail: + QCOMPARE(widget.size(), 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, Equal, Fail); + if (QTest::currentTestFailed()) + return; + + // test state transition, state and size consistency after resize + 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, Equal, xcb ? Warn : Fail); + if (QTest::currentTestFailed()) + return; + + // test state transition, state and size consistency after maximize + widget.showMaximized(); + verifyResize(QSize(), Qt::WindowMaximized, Greater, xcb ? Fail : Warn); + if (QTest::currentTestFailed()) + return; + +#ifdef Q_OS_WIN + QCOMPARE(widget.size(), size2); +#endif +} + void tst_QWidget::showMaximized() { QWidget plain; @@ -2719,7 +3474,7 @@ void tst_QWidget::resizeEvent() wParent.setWindowTitle(QLatin1String(QTest::currentTestFunction())); wParent.resize(m_testWidgetSize); ResizeWidget wChild(&wParent); - wParent.show(); + QTestPrivate::androidCompatibleShow(&wParent); QVERIFY(QTest::qWaitForWindowExposed(&wParent)); QCOMPARE (wChild.m_resizeEventCount, 1); // initial resize event before paint wParent.hide(); @@ -2728,7 +3483,7 @@ void tst_QWidget::resizeEvent() safeSize.setWidth(639); wChild.resize(safeSize); QCOMPARE (wChild.m_resizeEventCount, 1); - wParent.show(); + QTestPrivate::androidCompatibleShow(&wParent); QCOMPARE (wChild.m_resizeEventCount, 2); } @@ -2736,7 +3491,7 @@ void tst_QWidget::resizeEvent() ResizeWidget wTopLevel; wTopLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction())); wTopLevel.resize(m_testWidgetSize); - wTopLevel.show(); + QTestPrivate::androidCompatibleShow(&wTopLevel); QVERIFY(QTest::qWaitForWindowExposed(&wTopLevel)); QCOMPARE (wTopLevel.m_resizeEventCount, 1); // initial resize event before paint for toplevels wTopLevel.hide(); @@ -2745,7 +3500,7 @@ void tst_QWidget::resizeEvent() safeSize.setWidth(639); wTopLevel.resize(safeSize); QCOMPARE (wTopLevel.m_resizeEventCount, 1); - wTopLevel.show(); + QTestPrivate::androidCompatibleShow(&wTopLevel); QVERIFY(QTest::qWaitForWindowExposed(&wTopLevel)); QCOMPARE (wTopLevel.m_resizeEventCount, 2); } @@ -2821,7 +3576,7 @@ void tst_QWidget::showMinimizedKeepsFocus() child1.setFocusPolicy(Qt::StrongFocus); child2.setFocusPolicy(Qt::StrongFocus); window.show(); - QApplication::setActiveWindow(&window); + QApplicationPrivate::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); child2.setFocus(); @@ -2845,7 +3600,6 @@ void tst_QWidget::showMinimizedKeepsFocus() QWidget *child = new QWidget(&window); child->setFocusPolicy(Qt::StrongFocus); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); child->setFocus(); QTRY_COMPARE(window.focusWidget(), child); @@ -2864,7 +3618,6 @@ void tst_QWidget::showMinimizedKeepsFocus() QWidget *child = new QWidget(&window); child->setFocusPolicy(Qt::StrongFocus); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); child->setFocus(); QTRY_COMPARE(window.focusWidget(), child); @@ -2884,7 +3637,6 @@ void tst_QWidget::showMinimizedKeepsFocus() QWidget *child = new QWidget(&window); child->setFocusPolicy(Qt::StrongFocus); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); child->setFocus(); QTRY_COMPARE(window.focusWidget(), child); @@ -2905,7 +3657,6 @@ void tst_QWidget::showMinimizedKeepsFocus() QWidget *child = new QWidget(&window); child->setFocusPolicy(Qt::StrongFocus); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); child->setFocus(); QTRY_COMPARE(window.focusWidget(), child); @@ -2922,7 +3673,6 @@ void tst_QWidget::showMinimizedKeepsFocus() QTRY_COMPARE(QApplication::focusWidget(), nullptr); window.showNormal(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); #ifdef Q_OS_MACOS if (!macHasAccessToWindowsServer()) @@ -2960,8 +3710,8 @@ void tst_QWidget::reparent() pal2.setColor(childTLW.backgroundRole(), Qt::yellow); childTLW.setPalette(pal2); - parent.show(); - childTLW.show(); + QTestPrivate::androidCompatibleShow(&parent); + QTestPrivate::androidCompatibleShow(&childTLW); QVERIFY(QTest::qWaitForWindowExposed(&parent)); parent.move(parentPosition); @@ -2971,7 +3721,7 @@ void tst_QWidget::reparent() child.setParent(nullptr, child.windowFlags() & ~Qt::WindowType_Mask); child.setGeometry(childPos.x(), childPos.y(), child.width(), child.height()); - child.show(); + QTestPrivate::androidCompatibleShow(&child); #if 0 // QTBUG-26424 if (m_platform == QStringLiteral("xcb")) @@ -2987,7 +3737,7 @@ void tst_QWidget::reparent() void tst_QWidget::setScreen() { const auto screens = QApplication::screens(); - if (screens.count() < 2) + if (screens.size() < 2) QSKIP("This test tests nothing on a machine with a single screen."); QScreen *screen0 = screens.at(0); @@ -3076,109 +3826,111 @@ void tst_QWidget::hideWhenFocusWidgetIsChild() void tst_QWidget::normalGeometry() { -#ifdef Q_OS_MACOS - QSKIP("QTBUG-52974"); -#endif - if (m_platform == QStringLiteral("wayland")) QSKIP("Wayland: This fails. Figure out why."); QWidget parent; + QCOMPARE(parent.normalGeometry(), parent.geometry()); parent.setWindowTitle("NormalGeometry parent"); QWidget *child = new QWidget(&parent); QCOMPARE(parent.normalGeometry(), parent.geometry()); QCOMPARE(child->normalGeometry(), QRect()); - const QRect testGeom = QRect(m_availableTopLeft + QPoint(100 ,100), m_testWidgetSize); - parent.setGeometry(testGeom); + parent.setGeometry(QRect(m_availableTopLeft + QPoint(100 ,100), m_testWidgetSize)); parent.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&parent)); - QApplication::processEvents(); + WindowStateChangeWatcher stateChangeWatcher(&parent); - QRect geom = parent.geometry(); - // ### the window manager places the top-left corner at - // ### 100,100... making geom something like 102,124 (offset by - // ### the frame/frame)... this indicates a rather large different - // ### between how X11 and Windows works - // QCOMPARE(geom, QRect(100, 100, 200, 200)); - QCOMPARE(parent.normalGeometry(), geom); + const QRect normalGeometry = parent.geometry(); + // We can't make any assumptions about the actual geometry compared to the + // requested geometry. In this test, we only care about normalGeometry. + QCOMPARE(parent.normalGeometry(), normalGeometry); parent.setWindowState(parent.windowState() ^ Qt::WindowMaximized); QTRY_VERIFY(parent.windowState() & Qt::WindowMaximized); - QTRY_VERIFY(parent.geometry() != geom); - QTRY_COMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_VERIFY(parent.geometry() != normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); parent.setWindowState(parent.windowState() ^ Qt::WindowMaximized); QTRY_VERIFY(!(parent.windowState() & Qt::WindowMaximized)); - QTRY_COMPARE(parent.geometry(), geom); - QTRY_COMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_COMPARE(parent.geometry(), normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); parent.showMaximized(); - QTRY_VERIFY(parent.windowState() & Qt::WindowMaximized); - QTRY_VERIFY(parent.geometry() != geom); - QCOMPARE(parent.normalGeometry(), geom); + QTRY_VERIFY(parent.windowHandle()->windowState() & Qt::WindowMaximized); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_VERIFY(parent.geometry() != normalGeometry); + QCOMPARE(parent.normalGeometry(), normalGeometry); parent.showNormal(); QTRY_VERIFY(!(parent.windowState() & Qt::WindowMaximized)); - QTRY_COMPARE(parent.geometry(), geom); - QCOMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_COMPARE(parent.geometry(), normalGeometry); + QCOMPARE(parent.normalGeometry(), normalGeometry); + + parent.setWindowState(parent.windowState() ^ Qt::WindowFullScreen); + QTRY_VERIFY(parent.windowState() & Qt::WindowFullScreen); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_VERIFY(parent.geometry() != normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); + + parent.setWindowState(Qt::WindowNoState); + QTRY_VERIFY(!(parent.windowState() & Qt::WindowFullScreen)); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_COMPARE(parent.geometry(), normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); + + parent.showFullScreen(); + QTRY_VERIFY(parent.window()->windowState() & Qt::WindowFullScreen); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_VERIFY(parent.geometry() != normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); + + parent.showNormal(); + QTRY_VERIFY(!(parent.windowHandle()->windowState() & Qt::WindowFullScreen)); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_COMPARE(parent.geometry(), normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); - parent.setWindowState(parent.windowState() ^ Qt::WindowMinimized); - QTest::qWait(10); - parent.setWindowState(parent.windowState() ^ Qt::WindowMaximized); - QTest::qWait(10); if (m_platform == QStringLiteral("xcb")) QSKIP("QTBUG-26424"); - QTRY_VERIFY(parent.windowState() & (Qt::WindowMinimized|Qt::WindowMaximized)); + + parent.setWindowState(parent.windowState() ^ Qt::WindowMaximized); + QTRY_VERIFY(stateChangeWatcher.lastWindowStates & Qt::WindowMaximized); + parent.setWindowState(parent.windowState() ^ Qt::WindowMinimized); + QTRY_VERIFY(stateChangeWatcher.lastWindowStates & Qt::WindowMinimized); + + QTRY_COMPARE(parent.windowState() & (Qt::WindowMinimized|Qt::WindowMaximized), Qt::WindowMinimized|Qt::WindowMaximized); + QTRY_VERIFY(stateChangeWatcher.lastWindowStates & (Qt::WindowMinimized|Qt::WindowMaximized)); // ### when minimized and maximized at the same time, the geometry // ### does *NOT* have to be the normal geometry, it could be the // ### maximized geometry. // QCOMPARE(parent.geometry(), geom); - QTRY_COMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); parent.setWindowState(parent.windowState() ^ Qt::WindowMinimized); - QTest::qWait(10); QTRY_VERIFY(!(parent.windowState() & Qt::WindowMinimized)); QTRY_VERIFY(parent.windowState() & Qt::WindowMaximized); - QTRY_VERIFY(parent.geometry() != geom); - QTRY_COMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_VERIFY(parent.geometry() != normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); parent.setWindowState(parent.windowState() ^ Qt::WindowMaximized); - QTest::qWait(10); QTRY_VERIFY(!(parent.windowState() & Qt::WindowMaximized)); - QTRY_COMPARE(parent.geometry(), geom); - QTRY_COMPARE(parent.normalGeometry(), geom); - - parent.setWindowState(parent.windowState() ^ Qt::WindowFullScreen); - QTest::qWait(10); - QTRY_VERIFY(parent.windowState() & Qt::WindowFullScreen); - QTRY_VERIFY(parent.geometry() != geom); - QTRY_COMPARE(parent.normalGeometry(), geom); - - parent.setWindowState(parent.windowState() ^ Qt::WindowFullScreen); - QTest::qWait(10); - QVERIFY(!(parent.windowState() & Qt::WindowFullScreen)); - QTRY_COMPARE(parent.geometry(), geom); - QTRY_COMPARE(parent.normalGeometry(), geom); - - parent.showFullScreen(); - QTest::qWait(10); - QTRY_VERIFY(parent.windowState() & Qt::WindowFullScreen); - QTRY_VERIFY(parent.geometry() != geom); - QTRY_COMPARE(parent.normalGeometry(), geom); - - parent.showNormal(); - QTest::qWait(10); - QTRY_VERIFY(!(parent.windowState() & Qt::WindowFullScreen)); - QTRY_COMPARE(parent.geometry(), geom); - QTRY_COMPARE(parent.normalGeometry(), geom); + QTRY_COMPARE(stateChangeWatcher.lastWindowStates, parent.windowState()); + QTRY_COMPARE(parent.geometry(), normalGeometry); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); parent.showNormal(); + stateChangeWatcher.lastWindowStates = {}; parent.setWindowState(Qt:: WindowFullScreen | Qt::WindowMaximized); parent.setWindowState(Qt::WindowMinimized | Qt:: WindowFullScreen | Qt::WindowMaximized); parent.setWindowState(Qt:: WindowFullScreen | Qt::WindowMaximized); - QTest::qWait(10); - QTRY_COMPARE(parent.normalGeometry(), geom); + // the actual window will be either fullscreen or maximized + QTRY_VERIFY(stateChangeWatcher.lastWindowStates & (Qt:: WindowFullScreen | Qt::WindowMaximized)); + QTRY_COMPARE(parent.normalGeometry(), normalGeometry); } void tst_QWidget::setGeometry() @@ -3187,9 +3939,8 @@ void tst_QWidget::setGeometry() tlw.setWindowTitle(QLatin1String(QTest::currentTestFunction())); QWidget child(&tlw); - const QPoint topLeft = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); const QSize initialSize = 2 * m_testWidgetSize; - QRect tr(topLeft + QPoint(100,100), initialSize); + QRect tr(m_availableTopLeft + QPoint(100,100), initialSize); QRect cr(50,50,50,50); tlw.setGeometry(tr); child.setGeometry(cr); @@ -3198,15 +3949,14 @@ void tst_QWidget::setGeometry() QCOMPARE(child.geometry(), cr); tlw.setParent(nullptr, Qt::Window|Qt::FramelessWindowHint); - tr = QRect(topLeft, initialSize / 2); + tr = QRect(m_availableTopLeft, initialSize / 2); tlw.setGeometry(tr); QCOMPARE(tlw.geometry(), tr); tlw.showNormal(); - QTest::qWait(50); - if (tlw.frameGeometry() != tlw.geometry()) + if (!QTest::qWaitFor([&tlw]{ return tlw.frameGeometry() == tlw.geometry(); })) QSKIP("Your window manager is too broken for this test"); - if (m_platform == QStringLiteral("xcb")) - QSKIP("QTBUG-26424"); + if (m_platform == QStringLiteral("xcb") && tlw.geometry() != tr) + QEXPECT_FAIL("", "QTBUG-26424", Continue); QCOMPARE(tlw.geometry(), tr); } @@ -3381,9 +4131,9 @@ void tst_QWidget::raise() QObjectList list1{child1, child2, child3, child4}; QCOMPARE(parentPtr->children(), list1); - QCOMPARE(allChildren.count(), list1.count()); + QCOMPARE(allChildren.size(), list1.size()); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child4 ? 1 : 0; if (expectedPaintEvents == 0) { QCOMPARE(child->numPaintEvents, 0); @@ -3397,9 +4147,10 @@ void tst_QWidget::raise() for (int i = 0; i < 5; ++i) child2->raise(); - QTest::qWait(50); + QVERIFY(QTest::qWaitForWindowExposed(child2)); + QApplication::processEvents(); // process events that could be triggered by raise(); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child2 ? 1 : 0; int expectedZOrderChangeEvents = child == child2 ? 1 : 0; QTRY_COMPARE(child->numPaintEvents, expectedPaintEvents); @@ -3426,15 +4177,17 @@ void tst_QWidget::raise() onTop->show(); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); QTRY_VERIFY(onTop->numPaintEvents > 0); + QApplication::processEvents(); // process remaining paint events if there's more than one onTop->reset(); // Reset all the children. - for (UpdateWidget *child : qAsConst(allChildren)) + for (UpdateWidget *child : std::as_const(allChildren)) child->reset(); for (int i = 0; i < 5; ++i) child3->raise(); - QTest::qWait(50); + QVERIFY(QTest::qWaitForWindowExposed(child3)); + QApplication::processEvents(); // process events that could be triggered by raise(); QCOMPARE(onTop->numPaintEvents, 0); QCOMPARE(onTop->numZOrderChangeEvents, 0); @@ -3442,7 +4195,7 @@ void tst_QWidget::raise() QObjectList list3{child1, child4, child2, child3}; QCOMPARE(parent->children(), list3); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = 0; int expectedZOrderChangeEvents = child == child3 ? 1 : 0; QTRY_COMPARE(child->numPaintEvents, expectedPaintEvents); @@ -3480,9 +4233,9 @@ void tst_QWidget::lower() QObjectList list1{child1, child2, child3, child4}; QCOMPARE(parent->children(), list1); - QCOMPARE(allChildren.count(), list1.count()); + QCOMPARE(allChildren.size(), list1.size()); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child4 ? 1 : 0; if (expectedPaintEvents == 0) { QCOMPARE(child->numPaintEvents, 0); @@ -3499,7 +4252,7 @@ void tst_QWidget::lower() QTest::qWait(100); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child3 ? 1 : 0; int expectedZOrderChangeEvents = child == child4 ? 1 : 0; QTRY_COMPARE(child->numZOrderChangeEvents, expectedZOrderChangeEvents); @@ -3545,7 +4298,7 @@ void tst_QWidget::stackUnder() QObjectList list1{child1, child2, child3, child4}; QCOMPARE(parent->children(), list1); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child4 ? 1 : 0; #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) if (expectedPaintEvents == 1 && child->numPaintEvents == 2) @@ -3563,7 +4316,7 @@ void tst_QWidget::stackUnder() QObjectList list2{child1, child4, child2, child3}; QCOMPARE(parent->children(), list2); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedPaintEvents = child == child3 ? 1 : 0; int expectedZOrderChangeEvents = child == child4 ? 1 : 0; QTRY_COMPARE(child->numPaintEvents, expectedPaintEvents); @@ -3578,7 +4331,7 @@ void tst_QWidget::stackUnder() QObjectList list3{child4, child2, child1, child3}; QCOMPARE(parent->children(), list3); - for (UpdateWidget *child : qAsConst(allChildren)) { + for (UpdateWidget *child : std::as_const(allChildren)) { int expectedZOrderChangeEvents = child == child1 ? 1 : 0; if (child == child3) { #ifndef Q_OS_MACOS @@ -3700,6 +4453,13 @@ void tst_QWidget::saveRestoreGeometry() QVERIFY(QTest::qWaitForWindowExposed(&widget)); QApplication::processEvents(); + + /* --------------------------------------------------------------------- + * This test function is likely to flake when debugged with Qt Creator. + * (29px offset making the following QTRY_VERIFY2 fail) + * --------------------------------------------------------------------- + */ + QTRY_VERIFY2(HighDpi::fuzzyCompare(widget.pos(), position, m_fuzz), qPrintable(HighDpi::msgPointMismatch(widget.pos(), position))); QCOMPARE(widget.size(), size); @@ -3855,10 +4615,9 @@ void tst_QWidget::restoreVersion1Geometry() const Qt::WindowStates WindowStateMask = Qt::WindowFullScreen | Qt::WindowMaximized | Qt::WindowMinimized; QFile f(fileName); - QVERIFY(f.exists()); - f.open(QIODevice::ReadOnly); + QVERIFY(f.open(QIODevice::ReadOnly)); const QByteArray savedGeometry = f.readAll(); - QCOMPARE(savedGeometry.count(), 46); + QCOMPARE(savedGeometry.size(), 46); f.close(); QWidget widget; @@ -3912,6 +4671,68 @@ void tst_QWidget::restoreVersion1Geometry() #endif } +void tst_QWidget::restoreGeometryAfterScreenChange_data() +{ + QTest::addColumn<ScreenPosition>("screenPosition"); + QTest::addColumn<int>("deltaWidth"); + QTest::addColumn<int>("deltaHeight"); + QTest::addColumn<int>("frameMargin"); + QTest::addColumn<bool>("outside"); + + QTest::newRow("offAboveLarge") << ScreenPosition::OffAbove << 200 << 250 << 20 << true; + QTest::newRow("fitting") << ScreenPosition::Contained << 80 << 80 << 20 << false; + QTest::newRow("offRightWide") << ScreenPosition::OffRight << 150 << 80 << 20 << false; + QTest::newRow("offLeftFitting") << ScreenPosition::OffLeft << 70 << 70 << 20 << true; + QTest::newRow("offBelowHigh") << ScreenPosition::OffBelow << 80 << 200 << 20 << false; +} + +void tst_QWidget::restoreGeometryAfterScreenChange() +{ + const QList<QScreen *> &screens = QApplication::screens(); + QVERIFY2(!screens.isEmpty(), "No screens found."); + const QRect screenGeometry = screens.at(0)->geometry(); + + QFETCH(ScreenPosition, screenPosition); + QFETCH(int, deltaWidth); + QFETCH(int, deltaHeight); + QFETCH(int, frameMargin); + QFETCH(bool, outside); + + QRect restoredGeometry = screenGeometry; + restoredGeometry.setHeight(screenGeometry.height() * deltaHeight / 100); + restoredGeometry.setWidth(screenGeometry.width() * deltaWidth / 100); + const float moveMargin = outside ? 1.2 : 0.75; + + switch (screenPosition) { + case ScreenPosition::OffLeft: + restoredGeometry.setLeft(restoredGeometry.width() * (-moveMargin)); + break; + case ScreenPosition::OffAbove: + restoredGeometry.setTop(restoredGeometry.height() * (-moveMargin)); + break; + case ScreenPosition::OffRight: + restoredGeometry.setRight(restoredGeometry.width() * moveMargin); + break; + case ScreenPosition::OffBelow: + restoredGeometry.setBottom(restoredGeometry.height() * moveMargin); + break; + case ScreenPosition::Contained: + break; + } + + // If restored geometry fits into screen and has not been moved, + // it is changed only by frame margin plus one pixel at each edge + const QRect originalGeometry = restoredGeometry.adjusted(1, frameMargin + 1, 1, frameMargin + 1); + + QWidgetPrivate::checkRestoredGeometry(screenGeometry, &restoredGeometry, frameMargin); + + if (deltaHeight < 100 && deltaWidth < 100 && screenPosition == ScreenPosition::Contained) + QCOMPARE(originalGeometry, restoredGeometry); + + // new geometry has to fit on the screen + QVERIFY(screenGeometry.contains(restoredGeometry)); +} + void tst_QWidget::widgetAt() { #ifdef Q_OS_MACOS @@ -4070,7 +4891,8 @@ void tst_QWidget::testDeletionInEventHandlers() w = new Widget; w->show(); w->deleteThis = true; - QMouseEvent me(QEvent::MouseButtonRelease, QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::KeyboardModifiers()); + QMouseEvent me(QEvent::MouseButtonRelease, QPoint(1, 1), w->mapToGlobal(QPoint(1, 1)), + Qt::LeftButton, Qt::LeftButton, Qt::KeyboardModifiers()); qApp->notify(w, &me); QVERIFY(w.isNull()); delete w; @@ -4109,7 +4931,8 @@ void tst_QWidget::testDeletionInEventHandlers() w->setMouseTracking(true); w->show(); w->deleteThis = true; - QMouseEvent me2 = QMouseEvent(QEvent::MouseMove, QPoint(0, 0), Qt::NoButton, Qt::NoButton, Qt::NoModifier); + QMouseEvent me2 = QMouseEvent(QEvent::MouseMove, QPoint(0, 0), w->mapToGlobal(QPoint(0, 0)), + Qt::NoButton, Qt::NoButton, Qt::NoModifier); QApplication::sendEvent(w, &me2); QVERIFY(w.isNull()); delete w; @@ -4175,22 +4998,20 @@ class StaticWidget : public QWidget Q_OBJECT public: bool partial = false; - bool gotPaintEvent = false; QRegion paintedRegion; - explicit StaticWidget(QWidget *parent = nullptr) : QWidget(parent) + explicit StaticWidget(const QPalette &palette, QWidget *parent = nullptr) : QWidget(parent) { setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_OpaquePaintEvent); - setPalette(Qt::red); // Make sure we have an opaque palette. + setPalette(palette); setAutoFillBackground(true); } void paintEvent(QPaintEvent *e) override { paintedRegion += e->region(); - gotPaintEvent = true; -// qDebug() << "paint" << e->region(); + ++paintEvents; // Look for a full update, set partial to false if found. for (QRect r : e->region()) { partial = (r != rect()); @@ -4198,107 +5019,114 @@ public: break; } } + + // Wait timeout ms until at least one paint event has been consumed + // and the counter is no longer increasing. + // => making sure to consume multiple paint events relating to one operation + // before returning true. + bool waitForPaintEvent(int timeout = 100) + { + QDeadlineTimer deadline(timeout); + int count = -1; + while (!deadline.hasExpired() && count != paintEvents) { + count = paintEvents; + QCoreApplication::processEvents(); + if (count == paintEvents && count > 0) { + paintEvents = 0; + return true; + } + } + paintEvents = 0; + return false; + } +private: + int paintEvents = 0; }; /* Test that widget resizes and moves can be done with minimal repaints when WA_StaticContents - and WA_OpaquePaintEvent is set. Test is mac-only for now. + and WA_OpaquePaintEvent is set. */ void tst_QWidget::optimizedResizeMove() { - if (m_platform == QStringLiteral("wayland")) - QSKIP("Wayland: This fails. Figure out why."); + const bool wayland = QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive); + QWidget parent; - parent.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + parent.setPalette(simplePalette()); + parent.setWindowTitle(QTest::currentTestFunction()); parent.resize(400, 400); - StaticWidget staticWidget(&parent); - staticWidget.gotPaintEvent = false; + StaticWidget staticWidget(simplePalette(), &parent); staticWidget.move(150, 150); staticWidget.resize(150, 150); parent.show(); QVERIFY(QTest::qWaitForWindowExposed(&parent)); - QTRY_VERIFY(staticWidget.gotPaintEvent); + QVERIFY(staticWidget.waitForPaintEvent()); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(10, 10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + if (!wayland) { + QVERIFY(!staticWidget.waitForPaintEvent()); + } else { + if (staticWidget.waitForPaintEvent()) + QSKIP("Wayland is not optimising paint events. Skipping test."); + } - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(-10, -10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(-10, 10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); - staticWidget.gotPaintEvent = false; staticWidget.resize(staticWidget.size() + QSize(10, 10)); - QTRY_VERIFY(staticWidget.gotPaintEvent); + QVERIFY(staticWidget.waitForPaintEvent()); QCOMPARE(staticWidget.partial, true); - staticWidget.gotPaintEvent = false; staticWidget.resize(staticWidget.size() + QSize(-10, -10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); - staticWidget.gotPaintEvent = false; staticWidget.resize(staticWidget.size() + QSize(10, -10)); - QTRY_VERIFY(staticWidget.gotPaintEvent); + QVERIFY(staticWidget.waitForPaintEvent()); QCOMPARE(staticWidget.partial, true); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(10, 10)); staticWidget.resize(staticWidget.size() + QSize(-10, -10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(10, 10)); staticWidget.resize(staticWidget.size() + QSize(10, 10)); - QTRY_VERIFY(staticWidget.gotPaintEvent); + QVERIFY(staticWidget.waitForPaintEvent()); QCOMPARE(staticWidget.partial, true); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(-10, -10)); staticWidget.resize(staticWidget.size() + QSize(-10, -10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); staticWidget.setAttribute(Qt::WA_StaticContents, false); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(-10, -10)); staticWidget.resize(staticWidget.size() + QSize(-10, -10)); - QTRY_VERIFY(staticWidget.gotPaintEvent); + QVERIFY(staticWidget.waitForPaintEvent()); QCOMPARE(staticWidget.partial, false); staticWidget.setAttribute(Qt::WA_StaticContents, true); staticWidget.setAttribute(Qt::WA_StaticContents, false); - staticWidget.gotPaintEvent = false; staticWidget.move(staticWidget.pos() + QPoint(10, 10)); - QTest::qWait(20); - QCOMPARE(staticWidget.gotPaintEvent, false); + QVERIFY(!staticWidget.waitForPaintEvent()); staticWidget.setAttribute(Qt::WA_StaticContents, true); } void tst_QWidget::optimizedResize_topLevel() { - if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) - QSKIP("Wayland: This fails. Figure out why."); + const bool wayland = QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive); if (QHighDpiScaling::isActive()) QSKIP("Skip due to rounding errors in the regions."); - StaticWidget topLevel; + StaticWidget topLevel(simplePalette()); + topLevel.setPalette(simplePalette()); topLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - topLevel.gotPaintEvent = false; topLevel.show(); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - QTRY_VERIFY(topLevel.gotPaintEvent); + QVERIFY(topLevel.waitForPaintEvent()); - topLevel.gotPaintEvent = false; topLevel.partial = false; topLevel.paintedRegion = QRegion(); @@ -4323,10 +5151,15 @@ void tst_QWidget::optimizedResize_topLevel() QRegion expectedUpdateRegion(topLevel.rect()); expectedUpdateRegion -= QRect(QPoint(), topLevel.size() - QSize(10, 10)); - QTRY_VERIFY(topLevel.gotPaintEvent); + QVERIFY(topLevel.waitForPaintEvent()); if (m_platform == QStringLiteral("xcb") || m_platform == QStringLiteral("offscreen")) QSKIP("QTBUG-26424"); - QCOMPARE(topLevel.partial, true); + if (!wayland) { + QCOMPARE(topLevel.partial, true); + } else { + if (!topLevel.partial) + QSKIP("Wayland does repaint partially. Skipping test."); + } QCOMPARE(topLevel.paintedRegion, expectedUpdateRegion); } @@ -4496,7 +5329,7 @@ protected: } public: QList<WId> m_winIdList; - int winIdChangeEventCount() const { return m_winIdList.count(); } + int winIdChangeEventCount() const { return m_winIdList.size(); } }; class CreateDestroyWidget : public WinIdChangeWidget @@ -4551,6 +5384,84 @@ void tst_QWidget::createAndDestroy() QVERIFY(widget.internalWinId()); } +void tst_QWidget::eventsAndAttributesOnDestroy() +{ + // The events and attributes when destroying a widget should + // include those of hiding the widget. + + CreateDestroyWidget widget; + EventSpy<QWidget> showEventSpy(&widget, QEvent::Show); + EventSpy<QWidget> hideEventSpy(&widget, QEvent::Hide); + + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + + widget.show(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 1); + QCOMPARE(hideEventSpy.count(), 0); + + widget.hide(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 1); + QCOMPARE(hideEventSpy.count(), 1); + + widget.show(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), 1); + + widget.destroy(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), 2); + + const int hideEventsAfterDestroy = hideEventSpy.count(); + + widget.create(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), hideEventsAfterDestroy); + + QWidgetPrivate::get(&widget)->setVisible(true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 3); + QCOMPARE(hideEventSpy.count(), hideEventsAfterDestroy); + + // Make sure the destroy that happens when a top level + // is moved to being a child does not prevent the child + // being shown again. + + QWidget parent; + QWidget child; + parent.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parent)); + child.show(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(&parent); + QCOMPARE(child.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + + child.show(); + QCOMPARE(child.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), true); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + void tst_QWidget::winIdChangeEvent() { { @@ -4690,6 +5601,75 @@ void tst_QWidget::showNativeChild() QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); } +void tst_QWidget::closeAndShowNativeChild() +{ + QWidget topLevel; + QWidget *nativeChild = new QWidget; + nativeChild->winId(); + nativeChild->setFixedSize(200, 200); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(nativeChild); + topLevel.setLayout(layout); + + topLevel.show(); + QVERIFY(!nativeChild->isHidden()); + nativeChild->close(); + QVERIFY(nativeChild->isHidden()); + nativeChild->show(); + QVERIFY(!nativeChild->isHidden()); +} + +void tst_QWidget::closeAndShowWithNativeChild() +{ + bool dontCreateNativeWidgetSiblings = QApplication::testAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + auto resetAttribute = qScopeGuard([&]{ + QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, dontCreateNativeWidgetSiblings); + }); + QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + + QWidget topLevel; + topLevel.setObjectName("TopLevel"); + QWidget *nativeChild = new QWidget; + nativeChild->setObjectName("NativeChild"); + nativeChild->setFixedSize(200, 200); + QWidget *normalChild = new QWidget; + normalChild->setObjectName("NormalChild"); + normalChild->setFixedSize(200, 200); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(nativeChild); + layout->addWidget(normalChild); + topLevel.setLayout(layout); + + nativeChild->setAttribute(Qt::WA_NativeWindow); + + QCOMPARE(normalChild->testAttribute(Qt::WA_WState_Hidden), false); + QCOMPARE(normalChild->testAttribute(Qt::WA_WState_ExplicitShowHide), false); + + QCOMPARE(nativeChild->testAttribute(Qt::WA_WState_Hidden), false); + QCOMPARE(nativeChild->testAttribute(Qt::WA_WState_ExplicitShowHide), false); + + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + const QSize originalSize = topLevel.size(); + topLevel.close(); + + // all children must have the same state + QCOMPARE(nativeChild->isHidden(), normalChild->isHidden()); + QCOMPARE(nativeChild->isVisible(), normalChild->isVisible()); + QCOMPARE(nativeChild->testAttribute(Qt::WA_WState_Visible), + normalChild->testAttribute(Qt::WA_WState_Visible)); + QCOMPARE(nativeChild->testAttribute(Qt::WA_WState_Hidden), + normalChild->testAttribute(Qt::WA_WState_Hidden)); + QCOMPARE(nativeChild->testAttribute(Qt::WA_WState_ExplicitShowHide), + normalChild->testAttribute(Qt::WA_WState_ExplicitShowHide)); + + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(topLevel.size(), originalSize); +} + class ShowHideEventWidget : public QWidget { public: @@ -4839,6 +5819,7 @@ void tst_QWidget::update() Q_CHECK_PAINTEVENTS UpdateWidget w; + w.setPalette(simplePalette()); w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); w.resize(100, 100); centerOnScreen(&w); @@ -4852,6 +5833,7 @@ void tst_QWidget::update() w.reset(); UpdateWidget child(&w); + child.setPalette(simplePalette()); child.setGeometry(10, 10, 80, 80); child.show(); @@ -4923,6 +5905,7 @@ void tst_QWidget::update() // overlapping sibling UpdateWidget sibling(&w); + sibling.setPalette(simplePalette()); child.setGeometry(10, 10, 20, 20); sibling.setGeometry(15, 15, 20, 20); sibling.show(); @@ -5002,9 +5985,11 @@ void tst_QWidget::isOpaque() { #ifndef Q_OS_MACOS QWidget w; + w.setPalette(simplePalette()); QVERIFY(::isOpaque(&w)); QWidget child(&w); + child.setPalette(simplePalette()); QVERIFY(!::isOpaque(&child)); child.setAutoFillBackground(true); @@ -5086,11 +6071,11 @@ void tst_QWidget::scroll() const int h = qMin(500, screen->availableGeometry().height() / 2); UpdateWidget updateWidget; + updateWidget.setPalette(simplePalette()); updateWidget.resize(w, h); updateWidget.reset(); - updateWidget.move(QGuiApplication::primaryScreen()->geometry().center() - QPoint(250, 250)); + updateWidget.move(m_availableTopLeft); updateWidget.showNormal(); - QApplication::setActiveWindow(&updateWidget); QVERIFY(QTest::qWaitForWindowActive(&updateWidget)); QVERIFY(updateWidget.numPaintEvents > 0); @@ -5165,34 +6150,74 @@ void tst_QWidget::scrollNativeChildren() #endif // Mac OS -class DestroyedSlotChecker : public QObject +/* + This class is used as a slot object to test two different steps of + QWidget destruction. + + The first step is connecting the destroyed() signal to an object of + this class (through its operator()). In widgets, destroyed() is + emitted by ~QWidget, and not by ~QObject. This means that in our + operator() we expect the sender of the signal to still be a + QWidget. + + The connection realized at the first step means that now there's + an instance of this class owned by the sender object. That instance + is destroyed when the signal/slot connections are destroyed. + That happens in ~QObject, not in ~QWidget. Therefore, in the + destructor of this class, check that indeed the target is no longer + a QWidget but just a QObject. +*/ +class QObjectCastChecker { - Q_OBJECT - public: - bool wasQWidget = false; + explicit QObjectCastChecker(QWidget *target) + : m_target(target) + { + } -public slots: - void destroyedSlot(QObject *object) + ~QObjectCastChecker() { - wasQWidget = (qobject_cast<QWidget *>(object) != nullptr || object->isWidgetType()); + if (!m_target) + return; + + // When ~QObject is reached, check that indeed the object is no + // longer a QWidget. This relies on slots being disconnected in + // ~QObject (and this "slot object" being destroyed there). + QVERIFY(!qobject_cast<QWidget *>(m_target)); + QVERIFY(!dynamic_cast<QWidget *>(m_target)); + QVERIFY(!m_target->isWidgetType()); } -}; -/* - Test that qobject_cast<QWidget*> returns 0 in a slot - connected to QObject::destroyed. -*/ -void tst_QWidget::qobject_castInDestroyedSlot() -{ - DestroyedSlotChecker checker; + QObjectCastChecker(QObjectCastChecker &&other) noexcept + : m_target(std::exchange(other.m_target, nullptr)) + {} - QWidget *widget = new QWidget(); + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QObjectCastChecker) - QObject::connect(widget, &QObject::destroyed, &checker, &DestroyedSlotChecker::destroyedSlot); - delete widget; + void swap(QObjectCastChecker &other) noexcept + { + qSwap(m_target, other.m_target); + } + + void operator()(QObject *object) const + { + // Test that in a slot connected to destroyed() the emitter is + // still a QWidget. This is because ~QWidget() itself emits the + // signal. + QVERIFY(qobject_cast<QWidget *>(object)); + QVERIFY(dynamic_cast<QWidget *>(object)); + QVERIFY(object->isWidgetType()); + } + +private: + Q_DISABLE_COPY(QObjectCastChecker) + QObject *m_target; +}; - QVERIFY(checker.wasQWidget); +void tst_QWidget::qobject_castOnDestruction() +{ + QWidget widget; + QObject::connect(&widget, &QObject::destroyed, QObjectCastChecker(&widget)); } // Since X11 WindowManager operations are all async, and we have no way to know if the window @@ -5238,7 +6263,7 @@ void tst_QWidget::setWindowGeometry_data() const Qt::WindowFlags windowFlags[] = {Qt::WindowFlags(), Qt::FramelessWindowHint}; const bool skipEmptyRects = (m_platform == QStringLiteral("windows")); - for (Rects l : qAsConst(rects)) { + for (Rects l : std::as_const(rects)) { if (skipEmptyRects) l.removeIf([] (const QRect &r) { return r.isEmpty(); }); const QRect &rect = l.constFirst(); @@ -5257,8 +6282,8 @@ void tst_QWidget::setWindowGeometry_data() void tst_QWidget::setWindowGeometry() { - if (m_platform == QStringLiteral("xcb")) - QSKIP("X11: Skip this test due to Window manager positioning issues."); + if (m_platform == QStringLiteral("xcb") || m_platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("X11/Wayland: Skip this test due to Window manager positioning issues."); QFETCH(Rects, rects); QFETCH(int, windowFlags); @@ -5275,7 +6300,7 @@ void tst_QWidget::setWindowGeometry() QCOMPARE(widget.geometry(), rect); // setGeometry() without showing - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.setGeometry(r); QTest::qWait(100); QCOMPARE(widget.geometry(), r); @@ -5301,7 +6326,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // setGeometry() while shown - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.setGeometry(r); QTest::qWait(10); QTRY_COMPARE(widget.geometry(), r); @@ -5316,7 +6341,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // setGeometry() after hide() - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.setGeometry(r); QTest::qWait(10); QTRY_COMPARE(widget.geometry(), r); @@ -5326,7 +6351,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // show() again, geometry() should still be the same - widget.show(); + QTestPrivate::androidCompatibleShow(&widget); if (rect.isValid()) QVERIFY(QTest::qWaitForWindowExposed(&widget)); QTRY_COMPARE(widget.geometry(), rect); @@ -5352,7 +6377,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // setGeometry() while shown - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.setGeometry(r); QTest::qWait(10); QTRY_COMPARE(widget.geometry(), r); @@ -5367,7 +6392,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // setGeometry() after hide() - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.setGeometry(r); QTest::qWait(10); QTRY_COMPARE(widget.geometry(), r); @@ -5377,7 +6402,7 @@ void tst_QWidget::setWindowGeometry() QTRY_COMPARE(widget.geometry(), rect); // show() again, geometry() should still be the same - widget.show(); + QTestPrivate::androidCompatibleShow(&widget); if (rect.isValid()) QVERIFY(QTest::qWaitForWindowExposed(&widget)); QTest::qWait(10); @@ -5446,7 +6471,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // move() without showing - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.move(r.topLeft()); widget.resize(r.size()); QApplication::processEvents(); @@ -5476,7 +6501,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // move() while shown - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { // XCB: First resize after show of zero-sized gets wrong win_gravity. const bool expectMoveFail = !windowFlags && ((widget.width() == 0 || widget.height() == 0) && r.width() != 0 && r.height() != 0) @@ -5505,7 +6530,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // move() after hide() - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.move(r.topLeft()); widget.resize(r.size()); QApplication::processEvents(); @@ -5525,7 +6550,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // show() again, pos() should be the same - widget.show(); + QTestPrivate::androidCompatibleShow(&widget); if (rect.isValid()) QVERIFY(QTest::qWaitForWindowExposed(&widget)); QApplication::processEvents(); @@ -5556,7 +6581,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // move() while shown - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.move(r.topLeft()); widget.resize(r.size()); QApplication::processEvents(); @@ -5576,7 +6601,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // move() after hide() - for (const QRect &r : qAsConst(rects)) { + for (const QRect &r : std::as_const(rects)) { widget.move(r.topLeft()); widget.resize(r.size()); QApplication::processEvents(); @@ -5596,7 +6621,7 @@ void tst_QWidget::windowMoveResize() QTRY_COMPARE(widget.size(), rect.size()); // show() again, pos() should be the same - widget.show(); + QTestPrivate::androidCompatibleShow(&widget); if (rect.isValid()) QVERIFY(QTest::qWaitForWindowExposed(&widget)); QTest::qWait(10); @@ -5738,8 +6763,7 @@ void tst_QWidget::moveChild() parent.setStyle(style.data()); ColorWidget child(&parent, Qt::Widget, Qt::blue); - parent.setGeometry(QRect(parent.screen()->availableGeometry().topLeft() + QPoint(50, 50), - QSize(200, 200))); + parent.setGeometry(QRect(m_availableTopLeft + QPoint(50, 50), QSize(200, 200))); child.setGeometry(25, 25, 50, 50); #ifndef QT_NO_CURSOR // Try to make sure the cursor is not in a taskbar area to prevent tooltips or window highlighting QCursor::setPos(parent.geometry().topRight() + QPoint(50 , 50)); @@ -5775,16 +6799,18 @@ void tst_QWidget::moveChild() QTRY_COMPARE(pos, child.pos()); QTRY_COMPARE(parent.r, QRegion(oldGeometry) - child.geometry()); -#if !defined(Q_OS_MACOS) + // should be scrolled in backingstore QCOMPARE(child.r, QRegion()); -#endif VERIFY_COLOR(child, child.rect(), child.color); VERIFY_COLOR(parent, QRegion(parent.rect()) - child.geometry(), parent.color); } void tst_QWidget::showAndMoveChild() { +#ifdef ANDROID + QSKIP("Fails on Android due to removed grabWindow(): QTBUG-118849"); +#endif if (m_platform == QStringLiteral("wayland")) QSKIP("Wayland: This fails. Figure out why."); QWidget parent(nullptr, Qt::Window | Qt::WindowStaysOnTopHint); @@ -5802,7 +6828,6 @@ void tst_QWidget::showAndMoveChild() parent.setGeometry(desktopDimensions); parent.setPalette(Qt::red); parent.show(); - QApplication::setActiveWindow(&parent); QVERIFY(QTest::qWaitForWindowActive(&parent)); QWidget child(&parent); @@ -5921,17 +6946,17 @@ void tst_QWidget::multipleToplevelFocusCheck() w2.show(); QVERIFY(QTest::qWaitForWindowExposed(&w2)); - QApplication::setActiveWindow(&w1); w1.activateWindow(); + QApplicationPrivate::setActiveWindow(&w1); QVERIFY(QTest::qWaitForWindowActive(&w1)); - QCOMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w1)); + QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w1)); QTest::mouseDClick(&w1, Qt::LeftButton); QTRY_COMPARE(QApplication::focusWidget(), static_cast<QWidget *>(w1.edit)); w2.activateWindow(); - QApplication::setActiveWindow(&w2); + QApplicationPrivate::setActiveWindow(&w2); QVERIFY(QTest::qWaitForWindowActive(&w2)); - QCOMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w2)); + QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w2)); QTest::mouseClick(&w2, Qt::LeftButton); QTRY_COMPARE(QApplication::focusWidget(), nullptr); @@ -5939,16 +6964,16 @@ void tst_QWidget::multipleToplevelFocusCheck() QTRY_COMPARE(QApplication::focusWidget(), static_cast<QWidget *>(w2.edit)); w1.activateWindow(); - QApplication::setActiveWindow(&w1); + QApplicationPrivate::setActiveWindow(&w1); QVERIFY(QTest::qWaitForWindowActive(&w1)); - QCOMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w1)); + QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w1)); QTest::mouseDClick(&w1, Qt::LeftButton); QTRY_COMPARE(QApplication::focusWidget(), static_cast<QWidget *>(w1.edit)); w2.activateWindow(); - QApplication::setActiveWindow(&w2); + QApplicationPrivate::setActiveWindow(&w2); QVERIFY(QTest::qWaitForWindowActive(&w2)); - QCOMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w2)); + QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&w2)); QTest::mouseClick(&w2, Qt::LeftButton); QTRY_COMPARE(QApplication::focusWidget(), nullptr); } @@ -6011,7 +7036,7 @@ void tst_QWidget::setFocus() { // move focus to another window testWidget->activateWindow(); - QApplication::setActiveWindow(testWidget.data()); + QApplicationPrivate::setActiveWindow(testWidget.data()); if (testWidget->focusWidget()) testWidget->focusWidget()->clearFocus(); else @@ -6057,7 +7082,7 @@ void tst_QWidget::setFocus() // note: window may be active, but we don't want it to be testWidget->activateWindow(); - QApplication::setActiveWindow(testWidget.data()); + QApplicationPrivate::setActiveWindow(testWidget.data()); if (testWidget->focusWidget()) testWidget->focusWidget()->clearFocus(); else @@ -6233,34 +7258,6 @@ void tst_QWidget::setFocus() } } -template<class T> class EventSpy : public QObject -{ -public: - EventSpy(T *widget, QEvent::Type event) - : m_widget(widget), eventToSpy(event) - { - if (m_widget) - m_widget->installEventFilter(this); - } - - T *widget() const { return m_widget; } - int count() const { return m_count; } - void clear() { m_count = 0; } - -protected: - bool eventFilter(QObject *object, QEvent *event) override - { - if (event->type() == eventToSpy) - ++m_count; - return QObject::eventFilter(object, event); - } - -private: - T *m_widget; - const QEvent::Type eventToSpy; - int m_count = 0; -}; - #ifndef QT_NO_CURSOR void tst_QWidget::setCursor() { @@ -6410,6 +7407,9 @@ void tst_QWidget::setToolTip() QCOMPARE(widget.toolTip(), QString()); QCOMPARE(spy.count(), 2); + const int wakeUpDelay = widget.style()->styleHint(QStyle::SH_ToolTip_WakeUpDelay); + const int fallAsleepDelay = widget.style()->styleHint(QStyle::SH_ToolTip_FallAsleepDelay); + for (int pass = 0; pass < 2; ++pass) { QCursor::setPos(m_safeCursorPos); QScopedPointer<QWidget> popup(new QWidget(nullptr, Qt::Popup)); @@ -6429,12 +7429,12 @@ void tst_QWidget::setToolTip() QWindow *popupWindow = popup->windowHandle(); QTest::qWait(10); QTest::mouseMove(popupWindow, QPoint(25, 25)); - QTest::qWait(900); // delay is 700 + QTest::qWait(wakeUpDelay + 200); QCOMPARE(spy1.count(), 1); QCOMPARE(spy2.count(), 0); if (pass == 0) - QTest::qWait(2200); // delay is 2000 + QTest::qWait(fallAsleepDelay + 200); QTest::mouseMove(popupWindow); } @@ -6461,7 +7461,7 @@ void tst_QWidget::testWindowIconChangeEventPropagation() QWidgetList widgets; widgets << &topLevelWidget << &topLevelChild << &dialog << &dialogChild; - QCOMPARE(widgets.count(), 4); + QCOMPARE(widgets.size(), 4); topLevelWidget.show(); dialog.show(); @@ -6475,13 +7475,13 @@ void tst_QWidget::testWindowIconChangeEventPropagation() // Create spy lists. QList <EventSpyPtr> applicationEventSpies; QList <EventSpyPtr> widgetEventSpies; - for (QWidget *widget : qAsConst(widgets)) { + for (QWidget *widget : std::as_const(widgets)) { applicationEventSpies.append(EventSpyPtr::create(widget, QEvent::ApplicationWindowIconChange)); widgetEventSpies.append(EventSpyPtr::create(widget, QEvent::WindowIconChange)); } QList <WindowEventSpyPtr> appWindowEventSpies; QList <WindowEventSpyPtr> windowEventSpies; - for (QWindow *window : qAsConst(windows)) { + for (QWindow *window : std::as_const(windows)) { appWindowEventSpies.append(WindowEventSpyPtr::create(window, QEvent::ApplicationWindowIconChange)); windowEventSpies.append(WindowEventSpyPtr::create(window, QEvent::WindowIconChange)); } @@ -6490,7 +7490,7 @@ void tst_QWidget::testWindowIconChangeEventPropagation() const QIcon windowIcon = qApp->style()->standardIcon(QStyle::SP_TitleBarMenuButton); qApp->setWindowIcon(windowIcon); - for (int i = 0; i < widgets.count(); ++i) { + for (int i = 0; i < widgets.size(); ++i) { // Check QEvent::ApplicationWindowIconChange EventSpyPtr spy = applicationEventSpies.at(i); QWidget *widget = spy->widget(); @@ -6507,7 +7507,7 @@ void tst_QWidget::testWindowIconChangeEventPropagation() QCOMPARE(spy->count(), 1); spy->clear(); } - for (int i = 0; i < windows.count(); ++i) { + for (int i = 0; i < windows.size(); ++i) { // Check QEvent::ApplicationWindowIconChange (sent to QWindow) // QWidgetWindows don't get this event, since the widget takes care of changing the icon WindowEventSpyPtr spy = appWindowEventSpies.at(i); @@ -6525,7 +7525,7 @@ void tst_QWidget::testWindowIconChangeEventPropagation() // Set icon on a top-level widget. topLevelWidget.setWindowIcon(QIcon()); - for (int i = 0; i < widgets.count(); ++i) { + for (int i = 0; i < widgets.size(); ++i) { // Check QEvent::ApplicationWindowIconChange EventSpyPtr spy = applicationEventSpies.at(i); QCOMPARE(spy->count(), 0); @@ -6682,14 +7682,12 @@ void tst_QWidget::clean_qt_x11_enforce_cursor() child->setAttribute(Qt::WA_SetCursor, true); window.show(); - QApplication::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowActive(&window)); QTest::qWait(100); QCursor::setPos(window.geometry().center()); QTest::qWait(100); child->setFocus(); - QApplication::processEvents(); QTest::qWait(100); delete w; @@ -6725,8 +7723,19 @@ public: bool eventFilter(QObject *object, QEvent *event) override { QWidget *widget = qobject_cast<QWidget *>(object); - if (widget && !event->spontaneous()) - events.append(qMakePair(widget, event->type())); + if (widget && !event->spontaneous()) { + switch (event->type()) { + // we might get those events if we couldn't move the cursor + case QEvent::Enter: + case QEvent::Leave: + // we might get this on systems that have an input method installed + case QEvent::InputMethodQuery: + break; + default: + events.append(qMakePair(widget, event->type())); + break; + } + } return false; } @@ -6768,11 +7777,6 @@ void tst_QWidget::childEvents() { EventRecorder::EventList expected; - // Move away the cursor; otherwise it might result in an enter event if it's - // inside the widget when the widget is shown. - QCursor::setPos(m_safeCursorPos); - QTest::qWait(100); - { // no children created, not shown QWidget widget; @@ -6812,7 +7816,9 @@ void tst_QWidget::childEvents() << qMakePair(&widget, QEvent::Move) << qMakePair(&widget, QEvent::Resize) << qMakePair(&widget, QEvent::Show) +#ifndef Q_OS_ANDROID << qMakePair(&widget, QEvent::CursorChange) +#endif << qMakePair(&widget, QEvent::ShowToParent); QVERIFY2(spy.eventList() == expected, @@ -6901,7 +7907,9 @@ void tst_QWidget::childEvents() << qMakePair(&widget, QEvent::Move) << qMakePair(&widget, QEvent::Resize) << qMakePair(&widget, QEvent::Show) +#ifndef Q_OS_ANDROID << qMakePair(&widget, QEvent::CursorChange) +#endif << qMakePair(&widget, QEvent::ShowToParent); QVERIFY2(spy.eventList() == expected, @@ -6993,7 +8001,9 @@ void tst_QWidget::childEvents() << qMakePair(&widget, QEvent::Move) << qMakePair(&widget, QEvent::Resize) << qMakePair(&widget, QEvent::Show) +#ifndef Q_OS_ANDROID << qMakePair(&widget, QEvent::CursorChange) +#endif << qMakePair(&widget, QEvent::ShowToParent); QVERIFY2(spy.eventList() == expected, @@ -7048,7 +8058,9 @@ private: void tst_QWidget::render() { - return; +#ifdef Q_OS_ANDROID + QSKIP("QTBUG-118984: crashes on Android."); +#endif QCalendarWidget source; source.setWindowTitle(QLatin1String(QTest::currentTestFunction())); // disable anti-aliasing to eliminate potential differences when subpixel antialiasing @@ -7063,16 +8075,11 @@ void tst_QWidget::render() RenderWidget target(&source); target.resize(source.size()); target.show(); - - QCoreApplication::processEvents(); - QCoreApplication::sendPostedEvents(); - QTest::qWait(250); + QVERIFY(QTest::qWaitForWindowExposed(&target)); const QImage sourceImage = source.grab(QRect(QPoint(0, 0), QSize(-1, -1))).toImage(); - QCoreApplication::processEvents(); QImage targetImage = target.grab(QRect(QPoint(0, 0), QSize(-1, -1))).toImage(); - QCoreApplication::processEvents(); - QCOMPARE(sourceImage, targetImage); + QTRY_COMPARE(sourceImage, targetImage); // Fill target.rect() will Qt::red and render // QRegion(0, 0, source->width(), source->height() / 2, QRegion::Ellipse) @@ -7085,66 +8092,73 @@ void tst_QWidget::render() QVERIFY(sourceImage != targetImage); QCOMPARE(targetImage.pixel(target.width() / 2, 29), QColor(Qt::red).rgb()); + if (targetImage.devicePixelRatioF() > 1) + QEXPECT_FAIL("", "This test fails on high-DPI displays", Continue); QCOMPARE(targetImage.pixel(target.width() / 2, 30), sourceImage.pixel(source.width() / 2, 0)); +} - // Test that a child widget properly fills its background - { - QWidget window; - window.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - window.resize(100, 100); - // prevent custom styles - window.setStyle(QStyleFactory::create(QLatin1String("Windows"))); - window.show(); - QVERIFY(QTest::qWaitForWindowExposed(&window)); - QWidget child(&window); - child.resize(window.size()); - child.show(); +// Test that a child widget properly fills its background +void tst_QWidget::renderChildFillsBackground() +{ + QWidget window; + window.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + window.resize(100, 100); + // prevent custom styles + window.setStyle(QStyleFactory::create(QLatin1String("Windows"))); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + QWidget child(&window); + child.resize(window.size()); + child.show(); - QCoreApplication::processEvents(); - const QPixmap childPixmap = child.grab(QRect(QPoint(0, 0), QSize(-1, -1))); - const QPixmap windowPixmap = window.grab(QRect(QPoint(0, 0), QSize(-1, -1))); - QCOMPARE(childPixmap, windowPixmap); - } + QCoreApplication::processEvents(); + const QPixmap childPixmap = child.grab(QRect(QPoint(0, 0), QSize(-1, -1))); + const QPixmap windowPixmap = window.grab(QRect(QPoint(0, 0), QSize(-1, -1))); +#ifndef Q_OS_ANDROID + // On Android all widgets are shown maximized, so the pixmaps + // will be similar + if (!m_platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QEXPECT_FAIL("", "This test fails on all platforms", Continue); +#endif + QCOMPARE(childPixmap, windowPixmap); +} - { // Check that the target offset is correct. - QWidget widget; - widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - widget.resize(200, 200); - widget.setAutoFillBackground(true); - widget.setPalette(Qt::red); - // prevent custom styles - widget.setStyle(QStyleFactory::create(QLatin1String("Windows"))); - widget.show(); - QVERIFY(QTest::qWaitForWindowExposed(&widget)); - QImage image(widget.size(), QImage::Format_RGB32); - image.fill(QColor(Qt::blue).rgb()); - - // Target offset (0, 0) - widget.render(&image, QPoint(), QRect(20, 20, 100, 100)); - QCOMPARE(image.pixel(0, 0), QColor(Qt::red).rgb()); - QCOMPARE(image.pixel(99, 99), QColor(Qt::red).rgb()); - QCOMPARE(image.pixel(100, 100), QColor(Qt::blue).rgb()); - - // Target offset (20, 20). - image.fill(QColor(Qt::blue).rgb()); - widget.render(&image, QPoint(20, 20), QRect(20, 20, 100, 100)); - QCOMPARE(image.pixel(0, 0), QColor(Qt::blue).rgb()); - QCOMPARE(image.pixel(19, 19), QColor(Qt::blue).rgb()); - QCOMPARE(image.pixel(20, 20), QColor(Qt::red).rgb()); - QCOMPARE(image.pixel(119, 119), QColor(Qt::red).rgb()); - QCOMPARE(image.pixel(120, 120), QColor(Qt::blue).rgb()); - } -} - -// On Windows the active palette is used instead of the inactive palette even -// though the widget is invisible. This is probably related to task 178507/168682, -// but for the renderInvisible test it doesn't matter, we're mostly interested -// in testing the geometry so just workaround the palette issue for now. +void tst_QWidget::renderTargetOffset() +{ // Check that the target offset is correct. + QWidget widget; + widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + widget.resize(200, 200); + widget.setAutoFillBackground(true); + widget.setPalette(Qt::red); + // prevent custom styles + widget.setStyle(QStyleFactory::create(QLatin1String("Windows"))); + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + QImage image(widget.size(), QImage::Format_RGB32); + image.fill(QColor(Qt::blue).rgb()); + + // Target offset (0, 0) + widget.render(&image, QPoint(), QRect(20, 20, 100, 100)); + QCOMPARE(image.pixel(0, 0), QColor(Qt::red).rgb()); + QCOMPARE(image.pixel(99, 99), QColor(Qt::red).rgb()); + QCOMPARE(image.pixel(100, 100), QColor(Qt::blue).rgb()); + + // Target offset (20, 20). + image.fill(QColor(Qt::blue).rgb()); + widget.render(&image, QPoint(20, 20), QRect(20, 20, 100, 100)); + QCOMPARE(image.pixel(0, 0), QColor(Qt::blue).rgb()); + QCOMPARE(image.pixel(19, 19), QColor(Qt::blue).rgb()); + QCOMPARE(image.pixel(20, 20), QColor(Qt::red).rgb()); + QCOMPARE(image.pixel(119, 119), QColor(Qt::red).rgb()); + QCOMPARE(image.pixel(120, 120), QColor(Qt::blue).rgb()); +} + +// On some platforms the active palette is used instead of the inactive palette even +// though the widget is invisible, but for the renderInvisible test it doesn't matter, +// as we're mostly interested in testing the geometry, so just workaround the palette +// issue for now. static void workaroundPaletteIssue(QWidget *widget) { -#ifndef Q_OS_WIN - return; -#endif if (!widget) return; @@ -7165,6 +8179,9 @@ void tst_QWidget::renderInvisible() if (m_platform == QStringLiteral("xcb")) QSKIP("QTBUG-26424"); + if (m_platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("Wayland: Skip this test, see also QTBUG-107157"); + QScopedPointer<QCalendarWidget> calendar(new QCalendarWidget); calendar->move(m_availableTopLeft + QPoint(100, 100)); calendar->setWindowTitle(QLatin1String(QTest::currentTestFunction())); @@ -7182,12 +8199,10 @@ void tst_QWidget::renderInvisible() dummyFocusWidget.move(calendar->geometry().bottomLeft() + QPoint(0, 100)); dummyFocusWidget.show(); QVERIFY(QTest::qWaitForWindowExposed(&dummyFocusWidget)); - QCoreApplication::processEvents(); - QTest::qWait(120); // Create normal reference image. const QSize calendarSize = calendar->size(); - QImage referenceImage(calendarSize, QImage::Format_ARGB32); + QImage referenceImage(calendarSize, QImage::Format_ARGB32_Premultiplied); calendar->render(&referenceImage); #ifdef RENDER_DEBUG referenceImage.save("referenceImage.png"); @@ -7197,9 +8212,8 @@ void tst_QWidget::renderInvisible() // Create resized reference image. const QSize calendarSizeResized = calendar->size() + QSize(50, 50); calendar->resize(calendarSizeResized); - QCoreApplication::processEvents(); QTest::qWait(30); - QImage referenceImageResized(calendarSizeResized, QImage::Format_ARGB32); + QImage referenceImageResized(calendarSizeResized, QImage::Format_ARGB32_Premultiplied); calendar->render(&referenceImageResized); #ifdef RENDER_DEBUG referenceImageResized.save("referenceImageResized.png"); @@ -7208,12 +8222,11 @@ void tst_QWidget::renderInvisible() // Explicitly hide the calendar. calendar->hide(); - QCoreApplication::processEvents(); QTest::qWait(30); workaroundPaletteIssue(calendar.data()); { // Make sure we get the same image when the calendar is explicitly hidden. - QImage testImage(calendarSizeResized, QImage::Format_ARGB32); + QImage testImage(calendarSizeResized, QImage::Format_ARGB32_Premultiplied); calendar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("explicitlyHiddenCalendarResized.png"); @@ -7229,7 +8242,7 @@ void tst_QWidget::renderInvisible() workaroundPaletteIssue(calendar.data()); { // Never been visible, created or laid out. - QImage testImage(calendarSize, QImage::Format_ARGB32); + QImage testImage(calendarSize, QImage::Format_ARGB32_Premultiplied); calendar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("neverBeenVisibleCreatedOrLaidOut.png"); @@ -7238,11 +8251,10 @@ void tst_QWidget::renderInvisible() } calendar->hide(); - QCoreApplication::processEvents(); QTest::qWait(30); { // Calendar explicitly hidden. - QImage testImage(calendarSize, QImage::Format_ARGB32); + QImage testImage(calendarSize, QImage::Format_ARGB32_Premultiplied); calendar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("explicitlyHiddenCalendar.png"); @@ -7256,7 +8268,7 @@ void tst_QWidget::renderInvisible() navigationBar->hide(); { // Check that the navigation bar isn't drawn when rendering the entire calendar. - QImage testImage(calendarSize, QImage::Format_ARGB32); + QImage testImage(calendarSize, QImage::Format_ARGB32_Premultiplied); calendar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("calendarWithoutNavigationBar.png"); @@ -7265,7 +8277,7 @@ void tst_QWidget::renderInvisible() } { // Make sure the navigation bar renders correctly even though it's hidden. - QImage testImage(navigationBar->size(), QImage::Format_ARGB32); + QImage testImage(navigationBar->size(), QImage::Format_ARGB32_Premultiplied); navigationBar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("explicitlyHiddenNavigationBar.png"); @@ -7279,7 +8291,7 @@ void tst_QWidget::renderInvisible() { // Render next month button. // Fill test image with correct background color. - QImage testImage(nextMonthButton->size(), QImage::Format_ARGB32); + QImage testImage(nextMonthButton->size(), QImage::Format_ARGB32_Premultiplied); navigationBar->render(&testImage, QPoint(), QRegion(), QWidget::RenderFlags()); #ifdef RENDER_DEBUG testImage.save("nextMonthButtonBackground.png"); @@ -7306,7 +8318,6 @@ void tst_QWidget::renderInvisible() // Navigation bar isn't explicitly hidden anymore. navigationBar->show(); - QCoreApplication::processEvents(); QTest::qWait(30); QVERIFY(!calendar->isVisible()); @@ -7318,7 +8329,7 @@ void tst_QWidget::renderInvisible() QCoreApplication::processEvents(); { // Make sure we get an image equal to the resized reference image. - QImage testImage(calendarSizeResized, QImage::Format_ARGB32); + QImage testImage(calendarSizeResized, QImage::Format_ARGB32_Premultiplied); calendar->render(&testImage); #ifdef RENDER_DEBUG testImage.save("calendarResized.png"); @@ -7330,7 +8341,7 @@ void tst_QWidget::renderInvisible() QCalendarWidget calendar; const QSize calendarSize = calendar.sizeHint(); - QImage image(2 * calendarSize, QImage::Format_ARGB32); + QImage image(2 * calendarSize, QImage::Format_ARGB32_Premultiplied); image.fill(QColor(Qt::red).rgb()); calendar.render(&image); @@ -7470,6 +8481,47 @@ void tst_QWidget::renderWithPainter() QCOMPARE(painter.renderHints(), oldRenderHints); } +void tst_QWidget::renderRTL() +{ + QFont f; + f.setStyleStrategy(QFont::NoAntialias); + const QScopedPointer<QStyle> style(QStyleFactory::create(QLatin1String("Windows"))); + + QMenu menu; + menu.setMinimumWidth(200); + menu.setFont(f); + menu.setStyle(style.data()); + menu.addAction("I"); + menu.show(); + menu.setLayoutDirection(Qt::LeftToRight); + QVERIFY(QTest::qWaitForWindowExposed(&menu)); + + QImage imageLTR(menu.size(), QImage::Format_ARGB32); + menu.render(&imageLTR); + //imageLTR.save("/tmp/rendered_1.png"); + + menu.setLayoutDirection(Qt::RightToLeft); + QImage imageRTL(menu.size(), QImage::Format_ARGB32); + menu.render(&imageRTL); + imageRTL = imageRTL.mirrored(true, false); + //imageRTL.save("/tmp/rendered_2.png"); + + QCOMPARE(imageLTR.height(), imageRTL.height()); + QCOMPARE(imageLTR.width(), imageRTL.width()); + static constexpr auto border = 4; + for (int h = border; h < imageRTL.height() - border; ++h) { + // there should be no difference on the right (aka no text) + for (int w = imageRTL.width() / 2; w < imageRTL.width() - border; ++w) { + auto pixLTR = imageLTR.pixel(w, h); + auto pixRTL = imageRTL.pixel(w, h); + if (pixLTR != pixRTL) + qDebug() << "Pixel do not match at" << w << h << ":" + << Qt::hex << pixLTR << "<->" << pixRTL; + QCOMPARE(pixLTR, pixRTL); + } + } +} + void tst_QWidget::render_task188133() { QMainWindow mainWindow; @@ -8017,7 +9069,7 @@ void tst_QWidget::moveWindowInShowEvent_data() QTest::addColumn<QPoint>("initial"); QTest::addColumn<QPoint>("position"); - QPoint p = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); + QPoint p = m_availableTopLeft; QTest::newRow("1") << p << (p + QPoint(10, 10)); QTest::newRow("2") << (p + QPoint(10,10)) << p; @@ -8054,21 +9106,15 @@ void tst_QWidget::moveWindowInShowEvent() // show it widget.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); - QTest::qWait(100); // it should have moved QCOMPARE(widget.pos(), position); } void tst_QWidget::repaintWhenChildDeleted() { -#ifdef Q_OS_WIN - QTest::qWait(1000); -#endif ColorWidget w(nullptr, Qt::FramelessWindowHint, Qt::red); w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - QPoint startPoint = w.screen()->availableGeometry().topLeft(); - startPoint.rx() += 50; - startPoint.ry() += 50; + const QPoint startPoint = m_availableTopLeft + QPoint(50, 50); w.setGeometry(QRect(startPoint, QSize(100, 100))); w.show(); QVERIFY(QTest::qWaitForWindowExposed(&w)); @@ -8091,9 +9137,7 @@ void tst_QWidget::hideOpaqueChildWhileHidden() { ColorWidget w(nullptr, Qt::FramelessWindowHint, Qt::red); w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - QPoint startPoint = w.screen()->availableGeometry().topLeft(); - startPoint.rx() += 50; - startPoint.ry() += 50; + const QPoint startPoint = m_availableTopLeft + QPoint(50, 50); w.setGeometry(QRect(startPoint, QSize(100, 100))); ColorWidget child(&w, Qt::Widget, Qt::blue); @@ -8142,6 +9186,7 @@ void tst_QWidget::updateWhileMinimized() QSKIP("Platform does not support showMinimized()"); #endif UpdateWidget widget; + widget.setPalette(simplePalette()); widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); // Filter out activation change and focus events to avoid update() calls in QWidget. widget.updateOnActivationChangeAndFocusIn = false; @@ -8169,7 +9214,8 @@ void tst_QWidget::updateWhileMinimized() qDebug() << "xcb: XDG_CURRENT_DESKTOP=" << desktop; if (desktop == QStringLiteral("ubuntu:GNOME") || desktop == QStringLiteral("GNOME-Classic:GNOME") - || desktop == QStringLiteral("GNOME")) + || desktop == QStringLiteral("GNOME") + || desktop.isEmpty()) // on local VMs count = 1; } QCOMPARE(widget.numPaintEvents, count); @@ -8714,6 +9760,7 @@ void tst_QWidget::doubleRepaint() QSKIP("Not having window server access causes the wrong number of repaints to be issues"); #endif UpdateWidget widget; + widget.setPalette(simplePalette()); widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); centerOnScreen(&widget); widget.setFocusPolicy(Qt::StrongFocus); @@ -8730,9 +9777,6 @@ void tst_QWidget::doubleRepaint() // Minmize: Should not trigger a repaint. widget.showMinimized(); QTest::qWait(10); -#if defined(Q_OS_QNX) - QEXPECT_FAIL("", "Platform does not support showMinimized()", Continue); -#endif QCOMPARE(widget.numPaintEvents, 0); widget.numPaintEvents = 0; @@ -8748,9 +9792,10 @@ void tst_QWidget::resizeInPaintEvent() QWidget window; window.setWindowTitle(QLatin1String(QTest::currentTestFunction())); UpdateWidget widget(&window); + widget.setPalette(simplePalette()); window.resize(200, 200); window.show(); - QApplication::setActiveWindow(&window); + QApplicationPrivate::setActiveWindow(&window); QVERIFY(QTest::qWaitForWindowExposed(&window)); QTRY_VERIFY(widget.numPaintEvents > 0); @@ -8804,6 +9849,46 @@ void tst_QWidget::opaqueChildren() QCOMPARE(qt_widget_private(&grandChild)->getOpaqueChildren(), QRegion()); } +void tst_QWidget::dumpObjectTree() +{ + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QWidget w; + w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + Q_SET_OBJECT_NAME(w); + w.move(100, 100); + w.resize(200, 200); + + QLineEdit le(&w); + Q_SET_OBJECT_NAME(le); + le.resize(200, 200); + + { + const char * const expected[] = { + "QWidget::w I", + " QLineEdit::le I", + " QWidgetLineControl:: ", + }; + for (const char *line : expected) + QTest::ignoreMessage(QtDebugMsg, line); + w.dumpObjectTree(); + } + + QTestPrivate::androidCompatibleShow(&w); + QVERIFY(QTest::qWaitForWindowActive(&w)); + + { + const char * const expected[] = { + "QWidget::w <200x200+100+100>", + " QLineEdit::le F<200x200+0+0>", + " QWidgetLineControl:: ", + }; + for (const char *line : expected) + QTest::ignoreMessage(QtDebugMsg, line); + w.dumpObjectTree(); + } +} class MaskSetWidget : public QWidget { @@ -8818,6 +9903,8 @@ public: paintedRegion += event->region(); for (const QRect &r : event->region()) p.fillRect(r, Qt::red); + + repainted = true; } void resizeEvent(QResizeEvent *) override @@ -8826,6 +9913,7 @@ public: } QRegion paintedRegion; + bool repainted = false; public slots: void resizeDown() { setGeometry(QRect(0, 50, 50, 50)); } @@ -8835,6 +9923,7 @@ public slots: void tst_QWidget::setMaskInResizeEvent() { UpdateWidget w; + w.setPalette(simplePalette()); w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); w.reset(); w.resize(200, 200); @@ -8852,19 +9941,19 @@ void tst_QWidget::setMaskInResizeEvent() w.reset(); testWidget.paintedRegion = QRegion(); - QTimer::singleShot(0, &testWidget, SLOT(resizeDown())); - QTest::qWait(100); + testWidget.resizeDown(); QRegion expectedParentUpdate(0, 0, 100, 10); // Old testWidget area. expectedParentUpdate += testWidget.geometry(); // New testWidget area. + QTRY_VERIFY(testWidget.repainted); QTRY_COMPARE(w.paintedRegion, expectedParentUpdate); QTRY_COMPARE(testWidget.paintedRegion, testWidget.mask()); testWidget.paintedRegion = QRegion(); - // Now resize the widget again, but in the oposite direction - QTimer::singleShot(0, &testWidget, SLOT(resizeUp())); - QTest::qWait(100); - + testWidget.repainted = false; + // Now resize the widget again, but in the opposite direction + testWidget.resizeUp(); + QTRY_VERIFY(testWidget.repainted); QTRY_COMPARE(testWidget.paintedRegion, testWidget.mask()); } @@ -8914,6 +10003,7 @@ void tst_QWidget::immediateRepaintAfterInvalidateBackingStore() QSKIP("We don't support immediate repaint right after show on other platforms."); QScopedPointer<UpdateWidget> widget(new UpdateWidget); + widget->setPalette(simplePalette()); widget->setWindowTitle(QLatin1String(QTest::currentTestFunction())); centerOnScreen(widget.data()); widget->show(); @@ -9298,20 +10388,19 @@ void tst_QWidget::translucentWidget() label.setWindowTitle(QLatin1String(QTest::currentTestFunction())); label.setFixedSize(16,16); label.setAttribute(Qt::WA_TranslucentBackground); - const QPoint labelPos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); - label.move(labelPos); + label.move(m_availableTopLeft); label.show(); QVERIFY(QTest::qWaitForWindowExposed(&label)); QPixmap widgetSnapshot = -#ifdef Q_OS_WIN - QGuiApplication::primaryScreen()->grabWindow(0, labelPos.x(), labelPos.y(), label.width(), label.height()); -#else label.grab(QRect(QPoint(0, 0), label.size())); -#endif const QImage actual = widgetSnapshot.toImage().convertToFormat(QImage::Format_RGB32); QImage expected = pm.toImage().scaled(label.devicePixelRatio() * pm.size()); expected.setDevicePixelRatio(label.devicePixelRatio()); +#ifdef Q_OS_ANDROID + // Android uses Format_ARGB32_Premultiplied by default + expected = expected.convertToFormat(QImage::Format_RGB32); +#endif QCOMPARE(actual.size(),expected.size()); QCOMPARE(actual,expected); @@ -9366,11 +10455,12 @@ void tst_QWidget::setClearAndResizeMask() QSKIP("Wayland: This fails. Figure out why."); UpdateWidget topLevel; + topLevel.setPalette(simplePalette()); topLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction())); topLevel.resize(160, 160); centerOnScreen(&topLevel); topLevel.show(); - QApplication::setActiveWindow(&topLevel); + QApplicationPrivate::setActiveWindow(&topLevel); QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); QTRY_VERIFY(topLevel.numPaintEvents > 0); topLevel.reset(); @@ -9400,6 +10490,7 @@ void tst_QWidget::setClearAndResizeMask() } UpdateWidget child(&topLevel); + child.setPalette(simplePalette()); child.setAutoFillBackground(true); // NB! Opaque child. child.setPalette(Qt::red); child.resize(100, 100); @@ -9679,6 +10770,8 @@ void tst_QWidget::syntheticEnterLeave() }; QCursor::setPos(m_safeCursorPos); + if (!QTest::qWaitFor([this]{ return QCursor::pos() == m_safeCursorPos; })) + QSKIP("Can't move cursor"); MyWidget window; window.setWindowTitle(QLatin1String(QTest::currentTestFunction())); @@ -9719,7 +10812,8 @@ void tst_QWidget::syntheticEnterLeave() // Position the cursor in the middle of the window. const QPoint globalPos = window.mapToGlobal(QPoint(100, 100)); QCursor::setPos(globalPos); // Enter child2 and grandChild. - QTest::qWait(300); + if (!QTest::qWaitFor([globalPos]{ return QCursor::pos() == globalPos; })) + QSKIP("Can't move cursor"); QCOMPARE(window.numLeaveEvents, 0); QCOMPARE(child2->numLeaveEvents, 0); @@ -9770,6 +10864,129 @@ void tst_QWidget::syntheticEnterLeave() #endif #ifndef QT_NO_CURSOR +void tst_QWidget::enterLeaveOnWindowShowHide_data() +{ + QTest::addColumn<Qt::WindowType>("windowType"); + QTest::addRow("dialog") << Qt::Dialog; + QTest::addRow("popup") << Qt::Popup; +} + + +/*! + Verify that a window that has the mouse gets a leave event + when a dialog or popup opens (even if that dialog or popup is + not under the mouse), and an enter event when the secondary window + closes again (while the mouse is still over the original widget. + + Since mouse grabbing might cause some event interaction, simulate + the opening of the secondary window from a mouse press, like we would with + a button or context menu. See QTBUG-78970. +*/ +void tst_QWidget::enterLeaveOnWindowShowHide() +{ + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QFETCH(Qt::WindowType, windowType); + class Widget : public QWidget + { + public: + int numEnterEvents = 0; + int numLeaveEvents = 0; + QPoint enterPosition; + Qt::WindowType secondaryWindowType = {}; + protected: + void enterEvent(QEnterEvent *e) override + { + enterPosition = e->position().toPoint(); + ++numEnterEvents; + } + void leaveEvent(QEvent *) override + { + enterPosition = {}; + ++numLeaveEvents; + } + void mousePressEvent(QMouseEvent *e) override + { + QWidget *secondary = nullptr; + switch (secondaryWindowType) { + case Qt::Dialog: { + QDialog *dialog = new QDialog(this); + dialog->setModal(true); + dialog->setWindowModality(Qt::ApplicationModal); + secondary = dialog; + break; + } + case Qt::Popup: { + QMenu *menu = new QMenu(this); + menu->addAction("Action 1"); + menu->addAction("Action 2"); + secondary = menu; + break; + } + default: + QVERIFY2(false, "Test case not implemented for window type"); + break; + } + + QPoint secondaryPos = e->globalPosition().toPoint(); + if (e->button() == Qt::LeftButton) + secondaryPos += QPoint(10, 10); // cursor outside secondary + else + secondaryPos -= QPoint(10, 10); // cursor inside secondary + secondary->move(secondaryPos); + secondary->show(); + if (!QTest::qWaitForWindowExposed(secondary)) + QEXPECT_FAIL("", "Secondary window failed to show, test will fail", Abort); + if (secondaryWindowType == Qt::Dialog && QGuiApplication::platformName() == "windows") + QTest::qWait(1000); // on Windows, we have to wait for fade-in effects + } + }; + + int expectedEnter = 0; + int expectedLeave = 0; + + Widget widget; + widget.secondaryWindowType = windowType; + const QRect screenGeometry = widget.screen()->availableGeometry(); + const QPoint cursorPos = screenGeometry.topLeft() + QPoint(50, 50); + widget.setGeometry(QRect(cursorPos - QPoint(50, 50), screenGeometry.size() / 4)); + QCursor::setPos(cursorPos); + + if (!QTest::qWaitFor([&]{ return widget.geometry().contains(QCursor::pos()); })) + QSKIP("We can't move the cursor"); + widget.show(); + QVERIFY(QTest::qWaitForWindowActive(&widget)); + + ++expectedEnter; + QTRY_COMPARE_WITH_TIMEOUT(widget.numEnterEvents, expectedEnter, 1000); + QCOMPARE(widget.enterPosition, widget.mapFromGlobal(cursorPos)); + QVERIFY(widget.underMouse()); + + QTest::mouseClick(&widget, Qt::LeftButton, {}, widget.mapFromGlobal(cursorPos)); + ++expectedLeave; + QTRY_COMPARE_WITH_TIMEOUT(widget.numLeaveEvents, expectedLeave, 1000); + QVERIFY(!widget.underMouse()); + QTRY_VERIFY(QApplication::activeModalWidget() || QApplication::activePopupWidget()); + if (QApplication::activeModalWidget()) + QApplication::activeModalWidget()->close(); + else if (QApplication::activePopupWidget()) + QApplication::activePopupWidget()->close(); + ++expectedEnter; + // Use default timeout, the test is flaky on Windows otherwise. + QTRY_VERIFY(widget.numEnterEvents >= expectedEnter); + // When a modal dialog closes we might get more than one enter event on macOS. + // This seems to depend on timing, so we tolerate that flakiness for now. + if (widget.numEnterEvents > expectedEnter && QGuiApplication::platformName() == "cocoa") + QEXPECT_FAIL("dialog", "On macOS, we might get more than one Enter event", Continue); + + QCOMPARE(widget.numEnterEvents, expectedEnter); + QCOMPARE(widget.enterPosition, widget.mapFromGlobal(cursorPos)); + QVERIFY(widget.underMouse()); +} +#endif + +#ifndef QT_NO_CURSOR void tst_QWidget::taskQTBUG_4055_sendSyntheticEnterLeave() { if (m_platform == QStringLiteral("wayland")) @@ -9799,69 +11016,179 @@ void tst_QWidget::taskQTBUG_4055_sendSyntheticEnterLeave() int numEnterEvents = 0, numMouseMoveEvents = 0; }; - QCursor::setPos(m_safeCursorPos); - - SELParent parent; - parent.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - parent.move(200, 200); - parent.resize(200, 200); - SELChild child(&parent); - child.resize(200, 200); - parent.show(); - QVERIFY(QTest::qWaitForWindowActive(&parent)); - - QCursor::setPos(child.mapToGlobal(QPoint(100, 100))); - // Make sure the cursor has entered the child. - QTRY_VERIFY(child.numEnterEvents > 0); - - child.hide(); - child.reset(); - child.show(); - - // Make sure the child gets enter event and no mouse move event. - QTRY_COMPARE(child.numEnterEvents, 1); - QCOMPARE(child.numMouseMoveEvents, 0); - - child.hide(); - child.reset(); - child.setMouseTracking(true); - child.show(); - - // Make sure the child gets enter event. - // Note that we verify event->button() and event->buttons() - // in SELChild::mouseMoveEvent(). - QTRY_COMPARE(child.numEnterEvents, 1); - QCOMPARE(child.numMouseMoveEvents, 0); - - // Sending synthetic enter/leave trough the parent's mousePressEvent handler. - parent.child = &child; - - child.hide(); - child.reset(); - QTest::mouseClick(&parent, Qt::LeftButton); - - // Make sure the child gets enter event. - QTRY_COMPARE(child.numEnterEvents, 1); - QCOMPARE(child.numMouseMoveEvents, 0); - - child.hide(); - child.reset(); - QTest::keyPress(&parent, Qt::Key_Shift); - QTest::mouseClick(&parent, Qt::LeftButton); - - // Make sure the child gets enter event - QTRY_COMPARE(child.numEnterEvents, 1); - QCOMPARE(child.numMouseMoveEvents, 0); - QTest::keyRelease(&child, Qt::Key_Shift); - child.hide(); - child.reset(); - child.setMouseTracking(false); - QTest::mouseClick(&parent, Qt::LeftButton); - - // Make sure the child gets enter event and no mouse move event. - QTRY_COMPARE(child.numEnterEvents, 1); - QCOMPARE(child.numMouseMoveEvents, 0); + QCursor::setPos(m_safeCursorPos); + if (!QTest::qWaitFor([this]{ return QCursor::pos() == m_safeCursorPos; })) + QSKIP("Can't move cursor"); + + SELParent parent; + parent.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + parent.move(200, 200); + parent.resize(200, 200); + SELChild child(&parent); + child.resize(200, 200); + parent.show(); + QVERIFY(QTest::qWaitForWindowActive(&parent)); + + const QPoint childPos = child.mapToGlobal(QPoint(100, 100)); + QCursor::setPos(childPos); + if (!QTest::qWaitFor([childPos]{ return QCursor::pos() == childPos; })) + QSKIP("Can't move cursor"); + + // Make sure the cursor has entered the child. + QTRY_VERIFY(child.numEnterEvents > 0); + + child.hide(); + child.reset(); + child.show(); + + // Make sure the child gets enter event and no mouse move event. + QTRY_COMPARE(child.numEnterEvents, 1); + QCOMPARE(child.numMouseMoveEvents, 0); + + child.hide(); + child.reset(); + child.setMouseTracking(true); + child.show(); + + // Make sure the child gets enter event. + // Note that we verify event->button() and event->buttons() + // in SELChild::mouseMoveEvent(). + QTRY_COMPARE(child.numEnterEvents, 1); + QCOMPARE(child.numMouseMoveEvents, 0); + + // Sending synthetic enter/leave through the parent's mousePressEvent handler. + parent.child = &child; + + child.hide(); + child.reset(); + QTest::mouseClick(&parent, Qt::LeftButton); + + // Make sure the child gets enter event. + QTRY_COMPARE(child.numEnterEvents, 1); + QCOMPARE(child.numMouseMoveEvents, 0); + + child.hide(); + child.reset(); + QTest::keyPress(&parent, Qt::Key_Shift); + QTest::mouseClick(&parent, Qt::LeftButton); + + // Make sure the child gets enter event + QTRY_COMPARE(child.numEnterEvents, 1); + QCOMPARE(child.numMouseMoveEvents, 0); + QTest::keyRelease(&child, Qt::Key_Shift); + child.hide(); + child.reset(); + child.setMouseTracking(false); + QTest::mouseClick(&parent, Qt::LeftButton); + + // Make sure the child gets enter event and no mouse move event. + QTRY_COMPARE(child.numEnterEvents, 1); + QCOMPARE(child.numMouseMoveEvents, 0); } + +void tst_QWidget::hoverPosition() +{ + if (m_platform == QStringLiteral("wayland")) + QSKIP("Wayland: Clients can't set cursor position on wayland."); + + class HoverWidget : public QWidget + { + public: + HoverWidget(QWidget *parent = nullptr) : QWidget(parent) { + setMouseTracking(true); + setAttribute(Qt::WA_Hover); + } + bool event(QEvent *ev) override { + switch (ev->type()) { + case QEvent::HoverMove: + // The docs say that WA_Hover will cause a paint event on enter and leave, but not on move. + update(); + Q_FALLTHROUGH(); + case QEvent::HoverEnter: + case QEvent::HoverLeave: { + qCDebug(lcTests) << ev; + lastHoverType = ev->type(); + ++hoverEventCount; + QHoverEvent *hov = static_cast<QHoverEvent *>(ev); + mousePos = hov->position().toPoint(); + mouseScenePos = hov->scenePosition().toPoint(); + if (ev->type() == QEvent::HoverEnter) + mouseEnterScenePos = hov->scenePosition().toPoint(); + break; + } + default: + break; + } + return QWidget::event(ev); + } + void paintEvent(QPaintEvent *) override { + ++paintEventCount; + QPainter painter(this); + if (mousePos.x() > 0) + painter.setPen(Qt::red); + painter.drawRect(0, 0, width(), height()); + painter.setPen(Qt::darkGreen); + painter.drawLine(mousePos - QPoint(crossHalfWidth, 0), mousePos + QPoint(crossHalfWidth, 0)); + painter.drawLine(mousePos - QPoint(0, crossHalfWidth), mousePos + QPoint(0, crossHalfWidth)); + } + + QEvent::Type lastHoverType = QEvent::None; + int hoverEventCount = 0; + int paintEventCount = 0; + QPoint mousePos; + QPoint mouseScenePos; + QPoint mouseEnterScenePos; + + private: + const int crossHalfWidth = 5; + }; + + QCursor::setPos(m_safeCursorPos); + if (!QTest::qWaitFor([this]{ return QCursor::pos() == m_safeCursorPos; })) + QSKIP("Can't move cursor"); + + QWidget root; + root.resize(300, 300); + HoverWidget h(&root); + h.setGeometry(100, 100, 100, 100); + root.show(); + QVERIFY(QTest::qWaitForWindowExposed(&root)); + + const QPoint middle(50, 50); + // wait until we got the correct global pos + QPoint expectedGlobalPos = root.geometry().topLeft() + QPoint(100, 100) + middle; + if (!QTest::qWaitFor([&]{ return expectedGlobalPos == h.mapToGlobal(middle); })) + QSKIP("Can't move cursor"); + QPoint curpos = h.mapToGlobal(middle); + QCursor::setPos(curpos); + if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; })) + QSKIP("Can't move cursor"); + QTRY_COMPARE_GE(h.hoverEventCount, 1); // HoverEnter and then probably HoverMove, so usually 2 + QTRY_COMPARE_GE(h.paintEventCount, 2); + const int enterHoverEventCount = h.hoverEventCount; + qCDebug(lcTests) << "hover enter events:" << enterHoverEventCount << "last was" << h.lastHoverType + << "; paint events:" << h.paintEventCount; + QCOMPARE(h.mousePos, middle); + QCOMPARE(h.mouseEnterScenePos, h.mapToParent(middle)); + QCOMPARE(h.mouseScenePos, h.mapToParent(middle)); + QCOMPARE(h.lastHoverType, enterHoverEventCount == 1 ? QEvent::HoverEnter : QEvent::HoverMove); + + curpos += {10, 10}; + QCursor::setPos(curpos); + if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; })) + QSKIP("Can't move cursor"); + QTRY_COMPARE(h.hoverEventCount, enterHoverEventCount + 1); + QCOMPARE(h.lastHoverType, QEvent::HoverMove); + QTRY_COMPARE_GE(h.paintEventCount, 3); + + curpos += {50, 50}; // in the outer widget, but leaving the inner widget + QCursor::setPos(curpos); + if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; })) + QSKIP("Can't move cursor"); + QTRY_COMPARE(h.lastHoverType, QEvent::HoverLeave); + QCOMPARE_GE(h.hoverEventCount, enterHoverEventCount + 2); + QTRY_COMPARE_GE(h.paintEventCount, 4); +} #endif void tst_QWidget::windowFlags() @@ -9967,6 +11294,7 @@ void tst_QWidget::focusWidget_task254563() void tst_QWidget::destroyBackingStore() { UpdateWidget w; + w.setPalette(simplePalette()); w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); centerOnScreen(&w); w.reset(); @@ -10091,6 +11419,157 @@ void tst_QWidget::setGraphicsEffect() QVERIFY(!blurEffect); } + +class TestGraphicsEffect : public QGraphicsEffect +{ +public: + TestGraphicsEffect(QObject *parent = nullptr) + : QGraphicsEffect(parent) + { + m_pattern = QPixmap(10, 10); + m_pattern.fill(Qt::lightGray); + QPainter p(&m_pattern); + p.fillRect(QRectF(0, 0, 5, 5), QBrush(Qt::darkGray)); + p.fillRect(QRectF(5, 5, 5, 5), QBrush(Qt::darkGray)); + } + void setExtent(int extent) + { + m_extent = extent; + } + QRectF boundingRectFor(const QRectF &sr) const override + { + return QRectF(sr.x() - m_extent, sr.y() - m_extent, + sr.width() + 2 * m_extent, sr.height() + 2 * m_extent); + } +protected: + void draw(QPainter *painter) override + { + QBrush brush; + brush.setTexture(m_pattern); + brush.setStyle(Qt::TexturePattern); + QPaintDevice *p = painter->device(); + painter->fillRect(QRect(-m_extent, -m_extent, + p->width() + m_extent, p->height() + m_extent), brush); + } + QPixmap m_pattern; + int m_extent = 0; +}; + +static QImage fillExpected1() +{ + QImage expected(QSize(40, 40), QImage::Format_RGB32); + QPainter p(&expected); + p.fillRect(QRect{{0, 0}, expected.size()}, QBrush(Qt::gray)); + p.fillRect(QRect(10, 10, 10, 10), QBrush(Qt::red)); + p.fillRect(QRect(20, 20, 10, 10), QBrush(Qt::blue)); + return expected; +} +static QImage fillExpected2() +{ + QImage expected = fillExpected1(); + QPainter p(&expected); + p.fillRect(QRect(10, 10, 5, 5), QBrush(Qt::darkGray)); + p.fillRect(QRect(15, 15, 5, 5), QBrush(Qt::darkGray)); + p.fillRect(QRect(15, 10, 5, 5), QBrush(Qt::lightGray)); + p.fillRect(QRect(10, 15, 5, 5), QBrush(Qt::lightGray)); + return expected; +} +static QImage fillExpected3() +{ + QImage expected(QSize(40, 40), QImage::Format_RGB32); + QPixmap pattern; + pattern = QPixmap(10, 10); + pattern.fill(Qt::lightGray); + QPainter p(&pattern); + p.fillRect(QRectF(0, 0, 5, 5), QBrush(Qt::darkGray)); + p.fillRect(QRectF(5, 5, 5, 5), QBrush(Qt::darkGray)); + QBrush brush; + brush.setTexture(pattern); + brush.setStyle(Qt::TexturePattern); + QPainter p2(&expected); + p2.fillRect(QRect{{0, 0}, expected.size()}, brush); + return expected; +} +static QImage fillExpected4() +{ + QImage expected = fillExpected1(); + QPixmap pattern; + pattern = QPixmap(10, 10); + pattern.fill(Qt::lightGray); + QPainter p(&pattern); + p.fillRect(QRectF(0, 0, 5, 5), QBrush(Qt::darkGray)); + p.fillRect(QRectF(5, 5, 5, 5), QBrush(Qt::darkGray)); + QBrush brush; + brush.setTexture(pattern); + brush.setStyle(Qt::TexturePattern); + QPainter p2(&expected); + p2.fillRect(QRect{{15, 15}, QSize{20, 20}}, brush); + return expected; +} + +void tst_QWidget::render_graphicsEffect_data() +{ + QTest::addColumn<QImage>("expected"); + QTest::addColumn<bool>("topLevelEffect"); + QTest::addColumn<bool>("child1Effect"); + QTest::addColumn<bool>("child2Effect"); + QTest::addColumn<int>("extent"); + + QTest::addRow("no_effect") << fillExpected1() << false << false << false << 0; + QTest::addRow("first_child_effect") << fillExpected2() << false << true << false << 0; + QTest::addRow("top_level_effect") << fillExpected3() << true << false << false << 0; + QTest::addRow("effect_with_extent") << fillExpected4() << false << false << true << 5; +} + +void tst_QWidget::render_graphicsEffect() +{ + QFETCH(QImage, expected); + QFETCH(bool, topLevelEffect); + QFETCH(bool, child1Effect); + QFETCH(bool, child2Effect); + QFETCH(int, extent); + + QScopedPointer<QWidget> topLevel(new QWidget); + topLevel->setPalette(Qt::gray); + topLevel->resize(40, 40); + topLevel->setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::") + + QLatin1String(QTest::currentDataTag())); + + // Render widget with 2 child widgets + QImage image(topLevel->size(), QImage::Format_RGB32); + image.fill(QColor(Qt::gray).rgb()); + + QPainter painter(&image); + + QWidget *childWidget1(new QWidget(topLevel.data())); + childWidget1->setAutoFillBackground(true); + childWidget1->setPalette(Qt::red); + childWidget1->resize(10, 10); + childWidget1->move(10, 10); + QWidget *childWidget2(new QWidget(topLevel.data())); + childWidget2->setAutoFillBackground(true); + childWidget2->setPalette(Qt::blue); + childWidget2->resize(10, 10); + childWidget2->move(20, 20); + + TestGraphicsEffect *graphicsEffect(new TestGraphicsEffect(topLevel.data())); + if (topLevelEffect) + topLevel->setGraphicsEffect(graphicsEffect); + if (child1Effect) + childWidget1->setGraphicsEffect(graphicsEffect); + if (child2Effect) + childWidget2->setGraphicsEffect(graphicsEffect); + graphicsEffect->setExtent(extent); + + // Render without effect + topLevel->render(&painter); +#ifdef RENDER_DEBUG + image.save("render_GraphicsEffect" + QTest::currentDataTag() + ".png"); + expected.save("render_GraphicsEffect_expected" + QTest::currentDataTag() + ".png"); +#endif + QCOMPARE(image, expected); +} + void tst_QWidget::activateWindow() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) @@ -10136,6 +11615,9 @@ void tst_QWidget::activateWindow() void tst_QWidget::openModal_taskQTBUG_5804() { +#ifdef Q_OS_ANDROID + QSKIP("This test hangs on Android"); +#endif class Widget : public QWidget { public: @@ -10283,32 +11765,42 @@ void tst_QWidget::focusProxy() QCOMPARE(container2->focusOutCount, 1); } -void tst_QWidget::focusProxyAndInputMethods() +void tst_QWidget::imEnabledNotImplemented() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) - QSKIP("Window activation is not supported."); - QScopedPointer<QWidget> toplevel(new QWidget(nullptr, Qt::X11BypassWindowManagerHint)); - toplevel->setWindowTitle(QLatin1String(QTest::currentTestFunction())); - toplevel->resize(200, 200); - toplevel->setAttribute(Qt::WA_InputMethodEnabled, true); - - QWidget *child = new QWidget(toplevel.data()); - child->setFocusProxy(toplevel.data()); - child->setAttribute(Qt::WA_InputMethodEnabled, true); + QSKIP("QWindow::requestActivate() is not supported."); - toplevel->setFocusPolicy(Qt::WheelFocus); - child->setFocusPolicy(Qt::WheelFocus); - - QVERIFY(!child->hasFocus()); - QVERIFY(!toplevel->hasFocus()); + // Check that a plain widget doesn't report that it supports IM. Only + // widgets that implements either Qt::ImEnabled, or the Qt4 backup + // solution, Qt::ImSurroundingText, should do so. + QWidget topLevel; + QWidget plain(&topLevel); + QLineEdit edit(&topLevel); + topLevel.show(); - toplevel->show(); - QVERIFY(QTest::qWaitForWindowExposed(toplevel.data())); - QApplication::setActiveWindow(toplevel.data()); - QVERIFY(QTest::qWaitForWindowActive(toplevel.data())); - QVERIFY(toplevel->hasFocus()); - QVERIFY(child->hasFocus()); - QCOMPARE(qApp->focusObject(), toplevel.data()); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QVERIFY(QTest::qWaitForWindowActive(&topLevel)); + + // A plain widget should return false for ImEnabled + plain.setFocus(Qt::OtherFocusReason); + QCOMPARE(QApplication::focusWidget(), &plain); + QVariant imEnabled = QApplication::inputMethod()->queryFocusObject(Qt::ImEnabled, QVariant()); + QVERIFY(imEnabled.isValid()); + QVERIFY(!imEnabled.toBool()); + + // But a lineedit should return true + edit.setFocus(Qt::OtherFocusReason); + QCOMPARE(QApplication::focusWidget(), &edit); + imEnabled = QApplication::inputMethod()->queryFocusObject(Qt::ImEnabled, QVariant()); + QVERIFY(imEnabled.isValid()); + QVERIFY(imEnabled.toBool()); + + // ImEnabled should be false when a lineedit is read-only since + // ImEnabled indicates the widget accepts input method _input_. + edit.setReadOnly(true); + imEnabled = QApplication::inputMethod()->queryFocusObject(Qt::ImEnabled, QVariant()); + QVERIFY(imEnabled.isValid()); + QVERIFY(!imEnabled.toBool()); } #ifdef QT_BUILD_INTERNAL @@ -10367,7 +11859,7 @@ void tst_QWidget::taskQTBUG_7532_tabOrderWithFocusProxy() void tst_QWidget::movedAndResizedAttributes() { // Use Qt::Tool as fully decorated windows have a minimum width of 160 on - QWidget w(nullptr, Qt::Tool); + QWidget w; w.setWindowTitle(QLatin1String(QTest::currentTestFunction())); w.show(); @@ -10405,11 +11897,11 @@ void tst_QWidget::movedAndResizedAttributes() QVERIFY(!w.testAttribute(Qt::WA_Resized)); w.showNormal(); - w.move(10,10); + w.move(m_availableTopLeft); QVERIFY(w.testAttribute(Qt::WA_Moved)); QVERIFY(!w.testAttribute(Qt::WA_Resized)); - w.resize(100, 100); + w.resize(m_testWidgetSize); QVERIFY(w.testAttribute(Qt::WA_Moved)); QVERIFY(w.testAttribute(Qt::WA_Resized)); } @@ -10667,7 +12159,6 @@ void tst_QWidget::grabMouse() layout->addWidget(grabber); centerOnScreen(&w); w.show(); - QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); QStringList expectedLog; @@ -10704,7 +12195,6 @@ void tst_QWidget::grabKeyboard() layout->addWidget(nonGrabber); centerOnScreen(&w); w.show(); - QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); nonGrabber->setFocus(); grabber->grabKeyboard(); @@ -10768,7 +12258,24 @@ protected: case QEvent::MouseMove: case QEvent::MouseButtonRelease: ++m_mouseEventCount; - m_lastMouseEventPos = static_cast<QMouseEvent *>(e)->position(); + { + QMouseEvent *me = static_cast<QMouseEvent *>(e); + m_lastMouseEventPos = me->position(); + m_lastMouseTimestamp = me->timestamp(); + } + if (m_acceptMouse) + e->accept(); + else + e->ignore(); + return true; + + case QEvent::MouseButtonDblClick: + ++m_mouseEventCount; + { + QMouseEvent *me = static_cast<QMouseEvent *>(e); + m_lastMouseEventPos = me->position(); + m_doubleClickTimestamp = me->timestamp(); + } if (m_acceptMouse) e->accept(); else @@ -10794,10 +12301,15 @@ public: int m_mouseEventCount = 0; bool m_acceptMouse = true; QPointF m_lastMouseEventPos; + quint64 m_lastMouseTimestamp = 0; + quint64 m_doubleClickTimestamp = 0; }; void tst_QWidget::touchEventSynthesizedMouseEvent() { + if (m_platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("This test failed on Wayland. See also QTBUG-107157."); + { // Simple case, we ignore the touch events, we get mouse events instead TouchMouseWidget widget; @@ -11032,6 +12544,50 @@ void tst_QWidget::touchEventsForGesturePendingWidgets() QVERIFY(parent.m_gestureEventCount > 0); } +void tst_QWidget::synthMouseDoubleClick() +{ + TouchMouseWidget widget; + widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); + widget.show(); + QWindow* window = widget.windowHandle(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + // tap once; move slightly from press to release + QPoint p(20, 20); + int expectedMouseEventCount = 0; + QTest::touchEvent(window, m_touchScreen).press(1, p, window); + QCOMPARE(widget.m_touchBeginCount, 0); + QCOMPARE(widget.m_mouseEventCount, ++expectedMouseEventCount); + QCOMPARE(widget.m_lastMouseEventPos.toPoint(), p); + quint64 mouseTimestamp = widget.m_lastMouseTimestamp; + p += {1, 0}; + QTest::touchEvent(window, m_touchScreen).move(1, p, window); + QCOMPARE(widget.m_mouseEventCount, ++expectedMouseEventCount); + QCOMPARE(widget.m_lastMouseEventPos.toPoint(), p); + QCOMPARE_GT(widget.m_lastMouseTimestamp, mouseTimestamp); + mouseTimestamp = widget.m_lastMouseTimestamp; + QTest::touchEvent(window, m_touchScreen).release(1, p, window); + QCOMPARE(widget.m_mouseEventCount, ++expectedMouseEventCount); + QCOMPARE(widget.m_lastMouseEventPos.toPoint(), p); + QCOMPARE_GT(widget.m_lastMouseTimestamp, mouseTimestamp); + mouseTimestamp = widget.m_lastMouseTimestamp; + + // tap again nearby: a double-click event should be synthesized + p += {0, 1}; + QTest::touchEvent(window, m_touchScreen).press(2, p, window); + QCOMPARE(widget.m_touchBeginCount, 0); + QCOMPARE(widget.m_mouseEventCount, ++expectedMouseEventCount); + QCOMPARE(widget.m_lastMouseEventPos.toPoint(), p); + QCOMPARE_GT(widget.m_doubleClickTimestamp, mouseTimestamp); + mouseTimestamp = widget.m_doubleClickTimestamp; + + QTest::touchEvent(window, m_touchScreen).release(2, p, window); + QCOMPARE(widget.m_mouseEventCount, ++expectedMouseEventCount); + QCOMPARE(widget.m_lastMouseEventPos.toPoint(), p); + QCOMPARE_GT(widget.m_lastMouseTimestamp, mouseTimestamp); + mouseTimestamp = widget.m_lastMouseTimestamp; +} + void tst_QWidget::styleSheetPropagation() { QTableView tw; @@ -11212,9 +12768,10 @@ void tst_QWidget::underMouse() QCOMPARE(QApplication::activePopupWidget(), &popupWidget); // Send an artificial leave event for window, as it won't get generated automatically - // due to cursor not actually being over the window. - QWindowSystemInterface::handleLeaveEvent(window); - QApplication::processEvents(); + // due to cursor not actually being over the window. The Cocoa and offscreen plugins + // do this for us. + if (QGuiApplication::platformName() != "cocoa" && QGuiApplication::platformName() != "offscreen") + QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>(window); // If there is an active popup, undermouse should not be reported (QTBUG-27478), // but opening a popup causes leave for widgets under mouse. @@ -11316,10 +12873,6 @@ void tst_QWidget::underMouse() // Mouse leaves popup and enters topLevelWidget, should cause leave for popup // but no enter to topLevelWidget. -#ifdef Q_OS_DARWIN - // Artificial leave event needed for Cocoa. - QWindowSystemInterface::handleLeaveEvent(popupWindow); -#endif QTest::mouseMove(popupWindow, popupWindow->mapFromGlobal(window->mapToGlobal(inWindowPoint))); QApplication::processEvents(); QVERIFY(!topLevelWidget.underMouse()); @@ -11403,9 +12956,6 @@ public: // when mousing over it. void tst_QWidget::taskQTBUG_27643_enterEvents() { -#ifdef Q_OS_MACOS - QSKIP("QTBUG-52974: this test can crash!"); -#endif // Move the mouse cursor to a safe location so it won't interfere QCursor::setPos(m_safeCursorPos); @@ -11512,6 +13062,7 @@ void tst_QWidget::resizeStaticContentsChildWidget_QTBUG35282() widget.resize(200,200); UpdateWidget childWidget(&widget); + childWidget.setPalette(simplePalette()); childWidget.setAttribute(Qt::WA_StaticContents); childWidget.setAttribute(Qt::WA_OpaquePaintEvent); childWidget.setGeometry(250, 250, 500, 500); @@ -11610,60 +13161,54 @@ void tst_QWidget::testForOutsideWSRangeFlag() } } -class TabletWidget : public QWidget +void tst_QWidget::tabletTracking() { -public: - TabletWidget(QWidget *parent) : QWidget(parent) { } + class TabletWidget : public QWidget + { + public: + using QWidget::QWidget; - int tabletEventCount = 0; - int pressEventCount = 0; - int moveEventCount = 0; - int releaseEventCount = 0; - int trackingChangeEventCount = 0; - qint64 uid = -1; + int tabletEventCount = 0; + int pressEventCount = 0; + int moveEventCount = 0; + int releaseEventCount = 0; + int trackingChangeEventCount = 0; + qint64 uid = -1; -protected: - void tabletEvent(QTabletEvent *event) override { - ++tabletEventCount; - uid = event->pointingDevice()->uniqueId().numericId(); - switch (event->type()) { - case QEvent::TabletMove: - ++moveEventCount; - break; - case QEvent::TabletPress: - ++pressEventCount; - break; - case QEvent::TabletRelease: - ++releaseEventCount; - break; - default: - break; + protected: + void tabletEvent(QTabletEvent *event) override { + ++tabletEventCount; + uid = event->pointingDevice()->uniqueId().numericId(); + switch (event->type()) { + case QEvent::TabletMove: + ++moveEventCount; + break; + case QEvent::TabletPress: + ++pressEventCount; + break; + case QEvent::TabletRelease: + ++releaseEventCount; + break; + default: + break; + } } - } - bool event(QEvent *ev) override { - if (ev->type() == QEvent::TabletTrackingChange) - ++trackingChangeEventCount; - return QWidget::event(ev); - } -}; - -void tst_QWidget::tabletTracking() -{ - QWidget parent; - parent.setWindowTitle(QLatin1String(QTest::currentTestFunction())); - parent.resize(200,200); - // QWidgetWindow::handleTabletEvent doesn't deliver tablet events to the window's widget, only to a child. - // So it doesn't do any good to show a TabletWidget directly: it needs a parent. - TabletWidget widget(&parent); + bool event(QEvent *ev) override { + if (ev->type() == QEvent::TabletTrackingChange) + ++trackingChangeEventCount; + return QWidget::event(ev); + } + } widget; + widget.setWindowTitle(QLatin1String(QTest::currentTestFunction())); widget.resize(200,200); - parent.showNormal(); - QVERIFY(QTest::qWaitForWindowExposed(&parent)); + widget.showNormal(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); widget.setAttribute(Qt::WA_TabletTracking); QTRY_COMPARE(widget.trackingChangeEventCount, 1); QVERIFY(widget.hasTabletTracking()); - QWindow *window = parent.windowHandle(); + QWindow *window = widget.windowHandle(); QPointF local(10, 10); QPointF global = window->mapToGlobal(local.toPoint()); QPointF deviceLocal = QHighDpi::toNativeLocalPosition(local, window); @@ -11739,6 +13284,36 @@ void tst_QWidget::closeEvent() widget.windowHandle()->close(); widget.windowHandle()->close(); QCOMPARE(widget.closeCount, 1); + + CloseCountingWidget widget2; + widget2.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget2)); + widget2.close(); + widget2.close(); + QCOMPARE(widget2.closeCount, 1); + widget2.closeCount = 0; + + widget2.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget2)); + widget2.close(); + QCOMPARE(widget2.closeCount, 1); + + CloseCountingWidget widget3; + widget3.close(); + widget3.close(); + QEXPECT_FAIL("", "Closing a widget without a window will unconditionally send close events", Continue); + QCOMPARE(widget3.closeCount, 0); + + QWidget parent; + CloseCountingWidget child; + child.setParent(&parent); + parent.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parent)); + child.close(); + QCOMPARE(child.closeCount, 1); + child.close(); + QEXPECT_FAIL("", "Closing a widget without a window will unconditionally send close events", Continue); + QCOMPARE(child.closeCount, 1); } void tst_QWidget::closeWithChildWindow() @@ -11940,11 +13515,639 @@ protected: void tst_QWidget::deleteWindowInCloseEvent() { - // Just checking if closing this widget causes a crash +#ifdef Q_OS_ANDROID + QSKIP("This test crashes on Android"); +#endif + QSignalSpy quitSpy(qApp, &QGuiApplication::lastWindowClosed); + + // Closing this widget should not cause a crash auto widget = new DeleteOnCloseEventWidget; - widget->close(); - QVERIFY(true); + widget->show(); + QVERIFY(QTest::qWaitForWindowExposed(widget)); + QTimer::singleShot(0, widget, [&]{ + widget->close(); + }); + QApplication::exec(); + + // It should still result in a single lastWindowClosed emit + QCOMPARE(quitSpy.size(), 1); } +/*! + Verify that both closing and deleting the last (only) window-widget + exits the application event loop. +*/ +void tst_QWidget::quitOnClose() +{ + QSignalSpy quitSpy(qApp, &QGuiApplication::lastWindowClosed); + + std::unique_ptr<QWidget>widget(new QWidget); + widget->show(); + QVERIFY(QTest::qWaitForWindowExposed(widget.get())); + + // QGuiApplication::lastWindowClosed is documented to only be emitted + // when we are in exec() + QTimer::singleShot(0, widget.get(), [&]{ + widget->close(); + }); + QApplication::exec(); + QCOMPARE(quitSpy.size(), 1); + + widget->show(); + QVERIFY(QTest::qWaitForWindowExposed(widget.get())); + QTimer::singleShot(0, widget.get(), [&]{ + widget.reset(); + }); + QApplication::exec(); + QCOMPARE(quitSpy.size(), 2); +} + +void tst_QWidget::setParentChangesFocus_data() +{ + QTest::addColumn<Qt::WindowType>("initialType"); + QTest::addColumn<bool>("initialParent"); + QTest::addColumn<Qt::WindowType>("targetType"); + QTest::addColumn<bool>("targetParent"); + QTest::addColumn<bool>("reparentBeforeShow"); + QTest::addColumn<QString>("focusWidget"); + + for (const bool before : {true, false}) { + const char *tag = before ? "before" : "after"; + QTest::addRow("give dialog parent, %s", tag) + << Qt::Dialog << false << Qt::Dialog << true << before << "lineEdit"; + QTest::addRow("make dialog parentless, %s", tag) + << Qt::Dialog << true << Qt::Dialog << false << before << "lineEdit"; + QTest::addRow("dialog to sheet, %s", tag) + << Qt::Dialog << true << Qt::Sheet << true << before << "lineEdit"; + QTest::addRow("window to widget, %s", tag) + << Qt::Window << true << Qt::Widget << true << before << "windowEdit"; + QTest::addRow("widget to window, %s", tag) + << Qt::Widget << true << Qt::Window << true << before << "lineEdit"; + } +} + +void tst_QWidget::setParentChangesFocus() +{ + QFETCH(Qt::WindowType, initialType); + QFETCH(bool, initialParent); + QFETCH(Qt::WindowType, targetType); + QFETCH(bool, targetParent); + QFETCH(bool, reparentBeforeShow); + QFETCH(QString, focusWidget); + + QWidget window; + window.setObjectName("window"); + QLineEdit *windowEdit = new QLineEdit(&window); + windowEdit->setObjectName("windowEdit"); + windowEdit->setFocus(); + + std::unique_ptr<QWidget> secondary(new QWidget(initialParent ? &window : nullptr, initialType)); + secondary->setObjectName("secondary"); + QLineEdit *lineEdit = new QLineEdit(secondary.get()); + lineEdit->setObjectName("lineEdit"); + QPushButton *pushButton = new QPushButton(secondary.get()); + pushButton->setObjectName("pushButton"); + lineEdit->setFocus(); + + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + + if (reparentBeforeShow) { + secondary->setParent(targetParent ? &window : nullptr, targetType); + // making a widget into a window doesn't set a focusWidget until shown + if (secondary->focusWidget()) + QCOMPARE(secondary->focusWidget()->objectName(), focusWidget); + } + secondary->show(); + QApplicationPrivate::setActiveWindow(secondary.get()); + QVERIFY(QTest::qWaitForWindowActive(secondary.get())); + + if (!reparentBeforeShow) { + secondary->setParent(targetParent ? &window : nullptr, targetType); + secondary->show(); // reparenting hides, so show again + QApplicationPrivate::setActiveWindow(secondary.get()); + QVERIFY(QTest::qWaitForWindowActive(secondary.get())); + } + QVERIFY(QTest::qWaitFor([]{ return QApplication::focusWidget(); })); + QCOMPARE(QApplication::focusWidget()->objectName(), focusWidget); +} + +void tst_QWidget::activateWhileModalHidden() +{ + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QDialog dialog; + dialog.setWindowModality(Qt::ApplicationModal); + dialog.show(); + QVERIFY(QTest::qWaitForWindowActive(&dialog)); + QVERIFY(dialog.isActiveWindow()); + QCOMPARE(QApplication::activeWindow(), &dialog); + + dialog.hide(); + QTRY_VERIFY(!dialog.isVisible()); + + QMainWindow window; + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + QVERIFY(window.isActiveWindow()); + QCOMPARE(QApplication::activeWindow(), &window); +} + +// Create a simple palette to prevent multiple paint events +QPalette tst_QWidget::simplePalette() +{ + static QPalette simplePalette = []{ + const QColor windowText = Qt::black; + const QColor backGround = QColor(239, 239, 239); + const QColor light = backGround.lighter(150); + const QColor mid = (backGround.darker(130)); + const QColor midLight = mid.lighter(110); + const QColor base = Qt::white; + const QColor dark = backGround.darker(150); + const QColor text = Qt::black; + const QColor highlight = QColor(48, 140, 198); + const QColor hightlightedText = Qt::white; + const QColor button = backGround; + const QColor shadow = dark.darker(135); + + QPalette defaultPalette(windowText, backGround, light, dark, mid, text, base); + defaultPalette.setBrush(QPalette::Midlight, midLight); + defaultPalette.setBrush(QPalette::Button, button); + defaultPalette.setBrush(QPalette::Shadow, shadow); + defaultPalette.setBrush(QPalette::HighlightedText, hightlightedText); + defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight); + return defaultPalette; + }(); + + return simplePalette; +} + +#ifdef Q_OS_ANDROID +void tst_QWidget::showFullscreenAndroid() +{ + QWidget w; + w.setAutoFillBackground(true); + QPalette p = w.palette(); + p.setColor(QPalette::Window, Qt::red); + w.setPalette(p); + + // Need to toggle showFullScreen() twice, see QTBUG-101968 + w.showFullScreen(); + QVERIFY(QTest::qWaitForWindowExposed(&w)); + w.show(); + QVERIFY(QTest::qWaitForWindowExposed(&w)); + w.showFullScreen(); + QVERIFY(QTest::qWaitForWindowExposed(&w)); + + // Make sure that the lower part of the screen contains the red widget, not + // the buttons. + + const QRect fullGeometry = w.screen()->geometry(); + // Take a rect of (20 x 20) from the bottom area + const QRect grabArea(10, fullGeometry.height() - 30, 20, 20); + const QImage img = grabFromWidget(&w, grabArea).toImage().convertedTo(QImage::Format_RGB32); + + QPixmap expectedPix(20, 20); + expectedPix.fill(Qt::red); + const QImage expectedImg = expectedPix.toImage().convertedTo(QImage::Format_RGB32); + + QCOMPARE(img, expectedImg); +} +#endif // Q_OS_ANDROID + +void tst_QWidget::setVisibleDuringDestruction() +{ + CreateDestroyWidget widget; + widget.create(); + QVERIFY(widget.windowHandle()); + + QSignalSpy signalSpy(widget.windowHandle(), &QWindow::visibleChanged); + EventSpy<QWindow> showEventSpy(widget.windowHandle(), QEvent::Show); + widget.show(); + QTRY_COMPARE(showEventSpy.count(), 1); + QTRY_COMPARE(signalSpy.count(), 1); + + EventSpy<QWindow> hideEventSpy(widget.windowHandle(), QEvent::Hide); + widget.hide(); + QTRY_COMPARE(hideEventSpy.count(), 1); + QTRY_COMPARE(signalSpy.count(), 2); + + widget.show(); + QTRY_COMPARE(showEventSpy.count(), 2); + QTRY_COMPARE(signalSpy.count(), 3); + + widget.destroy(); + QTRY_COMPARE(hideEventSpy.count(), 2); + QTRY_COMPARE(signalSpy.count(), 4); +} + +void tst_QWidget::explicitShowHide() +{ + { + QWidget parent; + parent.setObjectName("Parent"); + QWidget child(&parent); + child.setObjectName("Child"); + + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + parent.show(); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + // Fix up earlier expected failure + child.setAttribute(Qt::WA_WState_ExplicitShowHide, false); + + parent.hide(); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + } + + { + // Test what happens when a child is reparented after showing it + + QWidget parent; + parent.setObjectName("Parent"); + QWidget child; + child.setObjectName("Child"); + + child.show(); + QCOMPARE(child.isVisible(), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + child.setParent(&parent); + // As documented, a widget becomes invisible as part of changing + // its parent, even if it was previously visible. The user must call + // show() to make the widget visible again. + QCOMPARE(child.isVisible(), false); + + // However, the widget does not end up with Qt::WA_WState_Hidden, + // as QWidget::setParent treats it as a child, which normally will + // not get Qt::WA_WState_Hidden out of the box. + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + // For some reason we reset WA_WState_ExplicitShowHide, and it's + // not clear whether this is correct or not See QWidget::setParent() + // for a comment with more details. + QEXPECT_FAIL("", "We reset WA_WState_ExplicitShowHide on widget re-parent", Continue); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + + // The fact that the child doesn't have Qt::WA_WState_Hidden means + // it's sufficient to show the parent widget. We don't need to + // explicitly show the child. + parent.show(); + QCOMPARE(child.isVisible(), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + } + + { + QWidget parent; + parent.setObjectName("Parent"); + QWidget child(&parent); + child.setObjectName("Child"); + + parent.show(); + + // If a non-native child ends up being closed, we will hide the + // widget, but do so via QWidget::hide(), which marks the widget + // as explicitly hidden. + + child.setAttribute(Qt::WA_WState_ExplicitShowHide, false); + QCOMPARE(child.close(), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true); + + child.show(); + child.setAttribute(Qt::WA_NativeWindow); + child.setAttribute(Qt::WA_WState_ExplicitShowHide, false); + QCOMPARE(child.close(), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true); + + child.show(); + child.setAttribute(Qt::WA_WState_ExplicitShowHide, false); + QCOMPARE(child.windowHandle()->close(), false); // Can't close non-top level QWindows + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + // If we end up in QWidgetPrivate::handleClose via QWidgetWindow::closeEvent, + // either through QWindow::close(), or via QWSI::handleCloseEvent, we'll still + // do the explicit hide. + + child.show(); + child.setAttribute(Qt::WA_WState_ExplicitShowHide, false); + QCOMPARE(QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>( + child.windowHandle()), true); + QEXPECT_FAIL("", "Closing a native child via QWSI is treated as an explicit hide", Continue); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true); + } + + { + QWidget widget; + widget.show(); + widget.hide(); + + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Hidden), true); + + // The widget is now explicitly hidden. Showing it again, via QWindow, + // should make the widget visible, and it should not stay hidden, as + // that's an invalid state for a widget. + + widget.windowHandle()->setVisible(true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_ExplicitShowHide), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Hidden), false); + } +} + +/*! + Verify that we deliver DragEnter/Leave events symmetrically, even if the + widget entered didn't accept the DragEnter event. +*/ +void tst_QWidget::dragEnterLeaveSymmetry() +{ + QWidget widget; + widget.setAcceptDrops(true); + QLineEdit lineEdit; + QLabel label("Hello world"); + label.setAcceptDrops(true); + + struct EventFilter : QObject + { + bool eventFilter(QObject *receiver, QEvent *event) override + { + switch (event->type()) { + case QEvent::DragEnter: + case QEvent::DragLeave: + receivers[event->type()] << receiver; + break; + + default: + break; + } + + return false; + } + + QMap<QEvent::Type, QList<QObject *>> receivers; + + void clear() { receivers.clear(); } + bool hasEntered(QWidget *widget) const + { + return receivers.value(QEvent::DragEnter).contains(widget); + } + bool hasLeft(QWidget *widget) const + { + return receivers.value(QEvent::DragLeave).contains(widget); + } + } filter; + + widget.installEventFilter(&filter); + lineEdit.installEventFilter(&filter); + label.installEventFilter(&filter); + + QVBoxLayout vbox; + vbox.setContentsMargins(10, 10, 10, 10); + vbox.addWidget(&lineEdit); + vbox.addWidget(&label); + widget.setLayout(&vbox); + + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + QMimeData data; + data.setColorData(QVariant::fromValue(Qt::red)); + QWindowSystemInterface::handleDrag(widget.windowHandle(), &data, QPoint(1, 1), + Qt::ActionMask, Qt::LeftButton, {}); + QVERIFY(filter.hasEntered(&widget)); + QVERIFY(!filter.hasEntered(&lineEdit)); + QVERIFY(!filter.hasEntered(&label)); + QVERIFY(widget.underMouse()); + QVERIFY(!lineEdit.underMouse()); + filter.clear(); + + QWindowSystemInterface::handleDrag(widget.windowHandle(), &data, lineEdit.geometry().center(), + Qt::ActionMask, Qt::LeftButton, {}); + // DragEnter propagates as the lineEdit doesn't want it, so the widget + // sees both a Leave and an Enter event + QVERIFY(filter.hasLeft(&widget)); + QVERIFY(filter.hasEntered(&widget)); + QVERIFY(filter.hasEntered(&widget)); + // both have the UnderMouse attribute set + QVERIFY(lineEdit.underMouse()); + QVERIFY(widget.underMouse()); + + // The lineEdit didn't accept the DragEnter, but it should still has to + // get the DragLeave so that UnderMouse is cleared; the widget gets both + // Leave and Enter through propagation. + QWindowSystemInterface::handleDrag(widget.windowHandle(), &data, label.geometry().center(), + Qt::ActionMask, Qt::LeftButton, {}); + QVERIFY(filter.hasLeft(&lineEdit)); + QVERIFY(filter.hasLeft(&widget)); + QVERIFY(filter.hasEntered(&label)); + QVERIFY(filter.hasEntered(&widget)); + + QVERIFY(!lineEdit.underMouse()); + QVERIFY(label.underMouse()); + QVERIFY(widget.underMouse()); +} + +void tst_QWidget::reparentWindowHandles_data() +{ + QTest::addColumn<int>("stage"); + QTest::addRow("reparent child") << 1; + QTest::addRow("top level to child") << 2; + QTest::addRow("transient parent") << 3; + QTest::addRow("window container") << 4; +} + +void tst_QWidget::reparentWindowHandles() +{ + const bool nativeSiblingsOriginal = qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); + auto nativeSiblingGuard = qScopeGuard([&]{ + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, nativeSiblingsOriginal); + }); + + QFETCH(int, stage); + + switch (stage) { + case 1: { + // Reparent child widget + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + QPointer<QWidget> child = new QWidget(&topLevel); + child->setAttribute(Qt::WA_DontCreateNativeAncestors); + child->setAttribute(Qt::WA_NativeWindow); + QVERIFY(child->windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + QPointer<QWidget> intermediate = new QWidget(&anotherTopLevel); + QPointer<QWidget> leaf = new QWidget(intermediate); + leaf->setAttribute(Qt::WA_DontCreateNativeAncestors); + leaf->setAttribute(Qt::WA_NativeWindow); + QVERIFY(leaf->windowHandle()); + QVERIFY(!intermediate->windowHandle()); + + // Reparenting a native widget should reparent the QWindow + child->setParent(leaf); + QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle()); + QCOMPARE(child->windowHandle()->transientParent(), nullptr); + QVERIFY(!intermediate->windowHandle()); + + // So should reparenting a non-native widget with native children + intermediate->setParent(&topLevel); + QVERIFY(!intermediate->windowHandle()); + QCOMPARE(leaf->windowHandle()->parent(), topLevel.windowHandle()); + QCOMPARE(leaf->windowHandle()->transientParent(), nullptr); + QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle()); + QCOMPARE(child->windowHandle()->transientParent(), nullptr); + } + break; + case 2: { + // Top level to child + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + + // A regular top level loses its nativeness + QPointer<QWidget> regularToplevel = new QWidget; + regularToplevel->show(); + QVERIFY(QTest::qWaitForWindowExposed(regularToplevel)); + QVERIFY(regularToplevel->windowHandle()); + regularToplevel->setParent(&topLevel); + QVERIFY(!regularToplevel->windowHandle()); + + // A regular top level loses its nativeness + QPointer<QWidget> regularToplevelWithNativeChildren = new QWidget; + QPointer<QWidget> nativeChild = new QWidget(regularToplevelWithNativeChildren); + nativeChild->setAttribute(Qt::WA_DontCreateNativeAncestors); + nativeChild->setAttribute(Qt::WA_NativeWindow); + QVERIFY(nativeChild->windowHandle()); + regularToplevelWithNativeChildren->show(); + QVERIFY(QTest::qWaitForWindowExposed(regularToplevelWithNativeChildren)); + QVERIFY(regularToplevelWithNativeChildren->windowHandle()); + regularToplevelWithNativeChildren->setParent(&topLevel); + QVERIFY(!regularToplevelWithNativeChildren->windowHandle()); + // But the native child does not + QVERIFY(nativeChild->windowHandle()); + QCOMPARE(nativeChild->windowHandle()->parent(), topLevel.windowHandle()); + + // An explicitly native top level keeps its nativeness, and the window handle moves + QPointer<QWidget> nativeTopLevel = new QWidget; + nativeTopLevel->setAttribute(Qt::WA_NativeWindow); + QVERIFY(nativeTopLevel->windowHandle()); + nativeTopLevel->setParent(&topLevel); + QVERIFY(nativeTopLevel->windowHandle()); + QCOMPARE(nativeTopLevel->windowHandle()->parent(), topLevel.windowHandle()); + } + break; + case 3: { + // Transient parent + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + QPointer<QWidget> child = new QWidget(&topLevel); + child->setAttribute(Qt::WA_NativeWindow); + QVERIFY(child->windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + + // Make transient child of top level + anotherTopLevel.setParent(&topLevel, Qt::Window); + QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr); + QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle()); + + // Make transient child of child + anotherTopLevel.setParent(child, Qt::Window); + QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr); + QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle()); + } + break; + case 4: { + // Window container + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + + QPointer<QWidget> child = new QWidget(&topLevel); + QVERIFY(!child->windowHandle()); + + QWindow *window = new QWindow; + QWidget *container = QWidget::createWindowContainer(window); + container->setParent(child); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(window->parent(), topLevel.windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + + child->setParent(&anotherTopLevel); + QCOMPARE(window->parent(), anotherTopLevel.windowHandle()); + } + break; + default: + Q_UNREACHABLE(); + } +} + +#ifndef QT_NO_CONTEXTMENU +void tst_QWidget::contextMenuTrigger() +{ + class ContextMenuWidget : public QWidget + { + public: + int events = 0; + + protected: + void contextMenuEvent(QContextMenuEvent *) override { ++events; } + }; + + const Qt::ContextMenuTrigger wasTrigger = QGuiApplication::styleHints()->contextMenuTrigger(); + auto restoreTriggerGuard = qScopeGuard([wasTrigger]{ + QGuiApplication::styleHints()->setContextMenuTrigger(wasTrigger); + }); + + ContextMenuWidget widget; + widget.show(); + QVERIFY(!qApp->topLevelWindows().empty()); + auto *window = qApp->topLevelWindows()[0]; + QVERIFY(window); + QCOMPARE(widget.events, 0); + QGuiApplication::styleHints()->setContextMenuTrigger(Qt::ContextMenuTrigger::Press); + QTest::mousePress(window, Qt::RightButton); + QCOMPARE(widget.events, 1); + QTest::mouseRelease(window, Qt::RightButton); + QCOMPARE(widget.events, 1); + QGuiApplication::styleHints()->setContextMenuTrigger(Qt::ContextMenuTrigger::Release); + QTest::mousePress(window, Qt::RightButton); + QCOMPARE(widget.events, 1); + QTest::mouseRelease(window, Qt::RightButton); + QCOMPARE(widget.events, 2); +} +#endif + QTEST_MAIN(tst_QWidget) #include "tst_qwidget.moc" diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.h b/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.h deleted file mode 100644 index 87de300da9..0000000000 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.h +++ /dev/null @@ -1,35 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include <QtCore/QString> -#include <QtCore/QPair> -#include <QtWidgets/QWidget> - -#pragma once // Yeah, it's deprecated in general, but it's standard practice for Mac OS X. - -QString nativeWindowTitle(QWidget *widget, Qt::WindowState state); -bool nativeWindowModified(QWidget *widget); diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.mm b/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.mm deleted file mode 100644 index e2d00aa25b..0000000000 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget_mac_helpers.mm +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -// some versions of CALayer.h use 'slots' as an identifier -#define QT_NO_KEYWORDS - -#include "tst_qwidget_mac_helpers.h" -#include <QApplication> -#include <qpa/qplatformnativeinterface.h> -#include <private/qcore_mac_p.h> - -#include <AppKit/AppKit.h> - -QString nativeWindowTitle(QWidget *window, Qt::WindowState state) -{ - QWindow *qwindow = window->windowHandle(); - NSWindow *nswindow = (NSWindow *) qApp->platformNativeInterface()->nativeResourceForWindow("nswindow", qwindow); - QCFString macTitle; - if (state == Qt::WindowMinimized) { - macTitle = reinterpret_cast<CFStringRef>([[nswindow miniwindowTitle] retain]); - } else { - macTitle = reinterpret_cast<CFStringRef>([[nswindow title] retain]); - } - return macTitle; -} - -bool nativeWindowModified(QWidget *widget) -{ - QWindow *qwindow = widget->windowHandle(); - NSWindow *nswindow = (NSWindow *) qApp->platformNativeInterface()->nativeResourceForWindow("nswindow", qwindow); - return [nswindow isDocumentEdited]; -} diff --git a/tests/auto/widgets/kernel/qwidget_window/BLACKLIST b/tests/auto/widgets/kernel/qwidget_window/BLACKLIST index 7eae8254ac..77853a3e8c 100644 --- a/tests/auto/widgets/kernel/qwidget_window/BLACKLIST +++ b/tests/auto/widgets/kernel/qwidget_window/BLACKLIST @@ -1,7 +1,3 @@ -[tst_resize_count] -# QTBUG-66345 -opensuse-42.3 -ubuntu-16.04 # QTBUG-87412 [tst_move_show] android @@ -17,3 +13,6 @@ android android [mouseMoveWithPopup] android +# QTBUG-96270 +[tst_paintEventOnSecondShow] +opensuse diff --git a/tests/auto/widgets/kernel/qwidget_window/CMakeLists.txt b/tests/auto/widgets/kernel/qwidget_window/CMakeLists.txt index 9bae267970..af60c92cbf 100644 --- a/tests/auto/widgets/kernel/qwidget_window/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwidget_window/CMakeLists.txt @@ -1,16 +1,24 @@ -# Generated from qwidget_window.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwidget_window Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidget_window LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwidget_window SOURCES tst_qwidget_window.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::TestPrivate Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp index dbd541c736..1fab69fdcc 100644 --- a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp +++ b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -43,6 +18,7 @@ #include <qlabel.h> #include <qmainwindow.h> #include <qtoolbar.h> +#include <qsignalspy.h> #include <private/qwindow_p.h> #include <private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> @@ -52,6 +28,8 @@ #include <QtTest/private/qtesthelpers_p.h> +#include <QtWidgets/private/qapplication_p.h> + using namespace QTestPrivate; // Compare a window position that may go through scaling in the platform plugin with fuzz. @@ -75,6 +53,7 @@ public: tst_QWidget_window(); public slots: + void init(); void initTestCase(); void cleanupTestCase(); void cleanup(); @@ -90,6 +69,8 @@ private slots: void tst_show_resize(); void tst_show_resize_hide_show(); + void close(); + void tst_windowFilePathAndwindowTitle_data(); void tst_windowFilePathAndwindowTitle(); void tst_windowFilePath_data(); @@ -104,6 +85,7 @@ private slots: void tst_dnd(); void tst_dnd_events(); void tst_dnd_propagation(); + void tst_dnd_destroyOnDrop(); #endif void tst_qtbug35600(); @@ -129,6 +111,13 @@ private slots: void mouseMoveWithPopup_data(); void mouseMoveWithPopup(); + void showHideWindowHandle_data(); + void showHideWindowHandle(); + + void resetFocusObjectOnDestruction(); + + void cleanupOnDestruction(); + private: QSize m_testWidgetSize; const int m_fuzz; @@ -146,6 +135,11 @@ void tst_QWidget_window::initTestCase() { } +void tst_QWidget_window::init() +{ + QTest::failOnWarning(QRegularExpression(".*No such slot.*")); +} + void tst_QWidget_window::cleanupTestCase() { } @@ -247,6 +241,70 @@ void tst_QWidget_window::tst_show_resize_hide_show() QCOMPARE(w.size(), m_testWidgetSize); } +void tst_QWidget_window::close() +{ + // Verfy that closing a QWidgetWindow deletes its platform window, + // as expected of a QWindow subclass. This must be done also + // if QWidget API is used to close. The QCloseEvent must not be + // spontaneous if the close is triggered by a Qt API that the application + // would call in response to an event, and spontaneous if it is directly + // caused by user interaction, such as clicking the (x) in the titlebar. + // We can simulate this only by generating a WindowSystemEvent. + // Children of the window should get a hide event (never spontaneous when + // caused by closing the window). + + struct Widget : public QWidget + { + using QWidget::QWidget; + int spontClose = -1; + int spontHide = -1; + protected: + void hideEvent(QHideEvent *e) override + { spontHide = e->spontaneous() ? 1 : 0; } + void closeEvent(QCloseEvent *e) override + { spontClose = e->spontaneous() ? 1 : 0; } + }; + + // QWindow::close() + { + Widget w; + Widget child(&w); + w.winId(); + QVERIFY(w.windowHandle()); + QVERIFY(w.windowHandle()->handle()); + w.windowHandle()->close(); + QCOMPARE(w.spontClose, 0); + QCOMPARE(child.spontHide, -1); // was never shown + QVERIFY(w.windowHandle()); + QVERIFY(!w.windowHandle()->handle()); + } + + // QWidget::close() + { + Widget w; + Widget child(&w); + w.show(); + QVERIFY(w.windowHandle()); + QVERIFY(w.windowHandle()->handle()); + w.close(); + QCOMPARE(w.spontClose, 0); + QCOMPARE(child.spontHide, 0); + QVERIFY(w.windowHandle()); + QVERIFY(!w.windowHandle()->handle()); + } + + // User-initiated close + { + Widget w; + Widget child(&w); + w.show(); + QWindowSystemInterface::handleCloseEvent(w.windowHandle()); + QApplication::processEvents(); + QCOMPARE(w.spontClose, 1); + QCOMPARE(child.spontHide, 0); + } +} + class PaintTestWidget : public QWidget { public: @@ -368,14 +426,14 @@ void tst_QWidget_window::tst_windowFilePath() void tst_QWidget_window::tst_showWithoutActivating() { QString platformName = QGuiApplication::platformName().toLower(); - if (platformName == "cocoa") - QSKIP("Cocoa: This fails. Figure out why."); - else if (platformName != QStringLiteral("xcb") - && platformName != QStringLiteral("windows") - && platformName != QStringLiteral("ios") - && platformName != QStringLiteral("tvos") - && platformName != QStringLiteral("watchos")) - QSKIP("Qt::WA_ShowWithoutActivating is currently supported only on xcb, windows, and ios/tvos/watchos platforms."); + if (platformName != QStringLiteral("xcb") + && platformName != QStringLiteral("windows") + && platformName != QStringLiteral("cocoa") + && platformName != QStringLiteral("ios") + && platformName != QStringLiteral("tvos") + && platformName != QStringLiteral("watchos")) + QSKIP("Qt::WA_ShowWithoutActivating is currently supported only on xcb, " \ + "windows, and macos/ios/tvos/watchos platforms."); QWidget w1; w1.setAttribute(Qt::WA_ShowWithoutActivating); @@ -399,6 +457,7 @@ void tst_QWidget_window::tst_paintEventOnSecondShow() { PaintTestWidget w; w.show(); + QVERIFY(QTest::qWaitForWindowExposed(&w)); w.hide(); w.paintEventCount = 0; @@ -632,7 +691,6 @@ void tst_QWidget_window::tst_dnd() dndTestWidget.show(); QVERIFY(QTest::qWaitForWindowExposed(&dndTestWidget)); - qApp->setActiveWindow(&dndTestWidget); QVERIFY(QTest::qWaitForWindowActive(&dndTestWidget)); QMimeData mimeData; @@ -872,6 +930,78 @@ void tst_QWidget_window::tst_dnd_propagation() QCOMPARE(target.mDndEvents, "enter leave enter drop "); } + +class ReparentSelfOnDropWidget : public QWidget +{ +public: + ReparentSelfOnDropWidget(QWidget *newFutureParent) + : m_newFutureParent(newFutureParent) + { + setAcceptDrops(true); + + const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry(); + auto width = availableGeometry.width() / 6; + auto height = availableGeometry.height() / 4; + + setGeometry(availableGeometry.x() + 200, availableGeometry.y() + 200, width, height); + + QLabel *label = new QLabel(QStringLiteral("Test"), this); + label->setGeometry(40, 40, 60, 60); + label->setAcceptDrops(true); + } + + void dragEnterEvent(QDragEnterEvent *event) override + { + event->accept(); + } + + void dragMoveEvent(QDragMoveEvent *event) override + { + event->acceptProposedAction(); + } + + void dropEvent(QDropEvent *event) override + { + event->accept(); + // Turn 'this' from a top-level widget to a child widget. + // This destroys the QWidgetWindow since the widget is no longer top-level. + setParent(m_newFutureParent); + } + +private: + QWidget *m_newFutureParent; +}; + +void tst_QWidget_window::tst_dnd_destroyOnDrop() +{ + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("Wayland: This fails. Figure out why."); + + QMimeData mimeData; + mimeData.setText(QLatin1String("testmimetext")); + + QWidget newParent; + newParent.resize(400, 400); + newParent.show(); + QVERIFY(QTest::qWaitForWindowActive(&newParent)); + + ReparentSelfOnDropWidget *target = new ReparentSelfOnDropWidget(&newParent); + target->show(); + QVERIFY(QTest::qWaitForWindowActive(target)); + + Qt::DropActions supportedActions = Qt::DropAction::CopyAction; + QWindow *window = target->windowHandle(); + + auto posInsideDropTarget = QHighDpi::toNativePixels(QPoint(20, 20), window->screen()); + auto posInsideLabel = QHighDpi::toNativePixels(QPoint(60, 60), window->screen()); + + QWindowSystemInterface::handleDrag(window, &mimeData, posInsideDropTarget, supportedActions, {}, {}); + QWindowSystemInterface::handleDrag(window, &mimeData, posInsideLabel, supportedActions, {}, {}); + QWindowSystemInterface::handleDrop(window, &mimeData, posInsideLabel, supportedActions, {}, {}); + + QGuiApplication::processEvents(); +} + #endif void tst_QWidget_window::tst_qtbug35600() @@ -1332,6 +1462,9 @@ void tst_QWidget_window::mouseMoveWithPopup_data() void tst_QWidget_window::mouseMoveWithPopup() { + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) + QSKIP("Wayland: Skip this test, see also QTBUG-107154"); + QFETCH(Qt::WindowType, windowType); class Window : public QWidget @@ -1501,11 +1634,219 @@ void tst_QWidget_window::mouseMoveWithPopup() // but the release event will still be delivered to the first popup - dialogs might not get it QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonRelease); - if (topLevel.popup->mouseReleaseCount != 1 - && !QGuiApplication::platformName().startsWith(QLatin1String("windows"), Qt::CaseInsensitive)) + if (topLevel.popup->mouseReleaseCount != 1) QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue); QCOMPARE(topLevel.popup->mouseReleaseCount, 1); } +struct ShowHideEntry { + QEvent::Type action; + Qt::WindowType target; + using List = QList<ShowHideEntry>; +}; + +void tst_QWidget_window::showHideWindowHandle_data() +{ + QTest::addColumn<ShowHideEntry::List>("entries"); + + QTest::addRow("show/hide widget") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Hide, Qt::Widget } + }; + QTest::addRow("show/hide window") << ShowHideEntry::List{ + { QEvent::Show, Qt::Window }, { QEvent::Hide, Qt::Window } + }; + QTest::addRow("show widget, hide window") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Hide, Qt::Window } + }; + QTest::addRow("show window, hide widget") << ShowHideEntry::List{ + { QEvent::Show, Qt::Window }, { QEvent::Hide, Qt::Widget } + }; + QTest::addRow("show/hide widget, then show window, hide widget") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Hide, Qt::Widget }, + { QEvent::Show, Qt::Window }, { QEvent::Hide, Qt::Widget } + }; + QTest::addRow("show widget, close widget, show widget") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Close, Qt::Widget }, { QEvent::Show, Qt::Widget } + }; + QTest::addRow("show widget, close widget, show window") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Close, Qt::Widget }, { QEvent::Show, Qt::Window } + }; + QTest::addRow("show widget, close window, show widget") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Close, Qt::Window }, { QEvent::Show, Qt::Widget } + }; + QTest::addRow("show widget, close window, show window") << ShowHideEntry::List{ + { QEvent::Show, Qt::Widget }, { QEvent::Close, Qt::Window }, { QEvent::Show, Qt::Window } + }; +} + +void tst_QWidget_window::showHideWindowHandle() +{ + QWidget parent; + parent.setObjectName("Parent"); + QCOMPARE(parent.isVisible(), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + + QWidget child; + child.setObjectName("Child"); + QCOMPARE(child.isVisible(), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true); + + child.setParent(&parent); + QCOMPARE(child.isVisible(), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + QFETCH(QList<ShowHideEntry>, entries); + for (const auto entry : entries) { + + if (entry.action == QEvent::Show) { + if (entry.target == Qt::Window && !parent.windowHandle()) { + parent.setAttribute(Qt::WA_NativeWindow); + QVERIFY(parent.windowHandle()); + + QCOMPARE(parent.isVisible(), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + } + + bool wasExplicitShowHide = parent.testAttribute(Qt::WA_WState_ExplicitShowHide); + + if (entry.target == Qt::Widget) + parent.show(); + else + parent.windowHandle()->show(); + + QVERIFY(QTest::qWaitForWindowActive(&parent)); + + QCOMPARE(parent.isVisible(), true); + QVERIFY(parent.windowHandle()); + QCOMPARE(parent.windowHandle()->isVisible(), true); + + QCOMPARE(parent.testAttribute(Qt::WA_WState_Visible), true); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), + entry.target == Qt::Widget || wasExplicitShowHide); + + QCOMPARE(child.isVisible(), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + } else if (entry.action == QEvent::Hide) { + + bool wasExplicitShowHide = parent.testAttribute(Qt::WA_WState_ExplicitShowHide); + + if (entry.target == Qt::Widget) + parent.hide(); + else + parent.windowHandle()->hide(); + + QCOMPARE(parent.isVisible(), false); + QVERIFY(parent.windowHandle()); + QCOMPARE(parent.windowHandle()->isVisible(), false); + + QCOMPARE(parent.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), + entry.target == Qt::Widget || wasExplicitShowHide); + + QCOMPARE(child.isVisible(), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + + } else if (entry.action == QEvent::Close) { + + bool wasExplicitShowHide = parent.testAttribute(Qt::WA_WState_ExplicitShowHide); + + if (entry.target == Qt::Widget) + parent.close(); + else + parent.windowHandle()->close(); + + QCOMPARE(parent.isVisible(), false); + QVERIFY(parent.windowHandle()); + QCOMPARE(parent.windowHandle()->isVisible(), false); + + QCOMPARE(parent.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true); + QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), + entry.target == Qt::Widget || wasExplicitShowHide); + + QCOMPARE(child.isVisible(), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false); + } + } +} + +void tst_QWidget_window::resetFocusObjectOnDestruction() +{ + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) + QSKIP("QWindow::requestActivate() is not supported."); + + QSignalSpy focusObjectChangedSpy(qApp, &QGuiApplication::focusObjectChanged); + + // single top level widget that has focus + std::unique_ptr<QWidget> widget(new QWidget); + widget->setObjectName("Widget 1"); + widget->setFocus(); + widget->show(); + QVERIFY(QTest::qWaitForWindowActive(widget.get())); + + int activeCount = focusObjectChangedSpy.size(); + widget.reset(); + QVERIFY(focusObjectChangedSpy.size() > activeCount); + QCOMPARE(focusObjectChangedSpy.last().last().value<QObject*>(), nullptr); + focusObjectChangedSpy.clear(); + + // top level widget with focused child + widget.reset(new QWidget); + widget->setObjectName("Widget 2"); + QWidget *child = new QWidget(widget.get()); + child->setObjectName("Child widget"); + child->setFocus(); + widget->show(); + QVERIFY(QTest::qWaitForWindowActive(widget.get())); + + activeCount = focusObjectChangedSpy.size(); + widget.reset(); + // we might get more than one signal emission + QVERIFY(focusObjectChangedSpy.size() > activeCount); + QCOMPARE(focusObjectChangedSpy.last().last().value<QObject*>(), nullptr); +} + +class CreateDestroyWidget : public QWidget +{ +public: + using QWidget::create; + using QWidget::destroy; +}; + +void tst_QWidget_window::cleanupOnDestruction() +{ + CreateDestroyWidget widget; + QWidget child(&widget); + + QWidget grandChild(&child); + // Ensure there's not a 1:1 native window hierarhcy that we could + // recurse during QWidget::destroy(), triggering the issue that + // we were failing to clean up when not destroyed via QWidget. + grandChild.setAttribute(Qt::WA_DontCreateNativeAncestors); + grandChild.winId(); + + widget.destroy(); + widget.create(); + + widget.show(); +} + QTEST_MAIN(tst_QWidget_window) #include "tst_qwidget_window.moc" diff --git a/tests/auto/widgets/kernel/qwidgetaction/CMakeLists.txt b/tests/auto/widgets/kernel/qwidgetaction/CMakeLists.txt index 58dae2d076..fb5409464d 100644 --- a/tests/auto/widgets/kernel/qwidgetaction/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwidgetaction/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qwidgetaction.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwidgetaction Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidgetaction LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwidgetaction SOURCES tst_qwidgetaction.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::TestPrivate Qt::Widgets diff --git a/tests/auto/widgets/kernel/qwidgetaction/tst_qwidgetaction.cpp b/tests/auto/widgets/kernel/qwidgetaction/tst_qwidgetaction.cpp index 1b43961182..a06e072b71 100644 --- a/tests/auto/widgets/kernel/qwidgetaction/tst_qwidgetaction.cpp +++ b/tests/auto/widgets/kernel/qwidgetaction/tst_qwidgetaction.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -247,7 +222,7 @@ void tst_QWidgetAction::customWidget() tb1.addAction(action); QList<QWidget *> combos = action->createdWidgets(); - QCOMPARE(combos.count(), 1); + QCOMPARE(combos.size(), 1); QPointer<QComboBox> combo1 = qobject_cast<QComboBox *>(combos.at(0)); QVERIFY(combo1); @@ -255,7 +230,7 @@ void tst_QWidgetAction::customWidget() tb2.addAction(action); combos = action->createdWidgets(); - QCOMPARE(combos.count(), 2); + QCOMPARE(combos.size(), 2); QCOMPARE(combos.at(0), combo1.data()); QPointer<QComboBox> combo2 = qobject_cast<QComboBox *>(combos.at(1)); diff --git a/tests/auto/widgets/kernel/qwidgetmetatype/CMakeLists.txt b/tests/auto/widgets/kernel/qwidgetmetatype/CMakeLists.txt index 3d8c3bbf4b..431a584a60 100644 --- a/tests/auto/widgets/kernel/qwidgetmetatype/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwidgetmetatype/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qwidgetmetatype.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwidgetmetatype Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidgetmetatype LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwidgetmetatype SOURCES tst_qwidgetmetatype.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp b/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp index 425380a99e..885c26a128 100644 --- a/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp +++ b/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Stephen Kelly <stephen.kelly@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Stephen Kelly <stephen.kelly@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt b/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt new file mode 100644 index 0000000000..ae91af064c --- /dev/null +++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidgetrepaintmanager LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qwidgetrepaintmanager + SOURCES + tst_qwidgetrepaintmanager.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::TestPrivate + Qt::Widgets + Qt::WidgetsPrivate +) diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp new file mode 100644 index 0000000000..64ebeb08b0 --- /dev/null +++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp @@ -0,0 +1,1048 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> +#include <QPainter> +#include <QScrollArea> +#include <QScrollBar> +#include <QApplication> + +#include <private/qhighdpiscaling_p.h> +#include <private/qwidget_p.h> +#include <private/qwidgetrepaintmanager_p.h> +#include <qpa/qplatformintegration.h> +#include <qpa/qplatformbackingstore.h> +#include <private/qguiapplication_p.h> + +//#define MANUAL_DEBUG + +class TestWidget : public QWidget +{ +public: + TestWidget(QWidget *parent = nullptr) + : QWidget(parent) + { + } + + QSize sizeHint() const override + { + const int screenWidth = QGuiApplication::primaryScreen()->geometry().width(); + const int width = qMax(200, 100 * ((screenWidth + 500) / 1000)); + return isWindow() ? QSize(width, width) : QSize(width - 40, width - 40); + } + + void initialShow() + { + show(); + if (isWindow()) { + QVERIFY(QTest::qWaitForWindowExposed(this)); + QVERIFY(waitForPainted()); + } + paintedRegions = {}; + } + + bool waitForPainted(int timeout = 5000) + { + int remaining = timeout; + QDeadlineTimer deadline(remaining, Qt::PreciseTimer); + if (!QTest::qWaitFor([this]{ return !paintedRegions.isEmpty(); }, timeout)) + return false; + + // In case of multiple paint events: + // Process events and wait until all have been consumed, + // i.e. paintedRegions no longer changes. + QRegion reg; + while (remaining > 0 && reg != paintedRegions) { + reg = paintedRegions; + QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); + if (reg == paintedRegions) + return true; + + remaining = int(deadline.remainingTime()); + } + return false; + } + + QRegion takePaintedRegions() + { + QRegion result = paintedRegions; + paintedRegions = {}; + return result; + } + QRegion paintedRegions; + + bool event(QEvent *event) override + { + const auto type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::WindowDeactivate) + return true; + if (type == QEvent::UpdateRequest) + ++updateRequests; + return QWidget::event(event); + } + int updateRequests = 0; + +protected: + void paintEvent(QPaintEvent *event) override + { + paintedRegions += event->region(); + QPainter painter(this); + const QBrush patternBrush = isWindow() ? QBrush(Qt::blue, Qt::VerPattern) + : QBrush(Qt::red, Qt::HorPattern); + painter.fillRect(rect(), patternBrush); + } +}; + +class OpaqueWidget : public QWidget +{ +public: + OpaqueWidget(const QColor &col, QWidget *parent = nullptr) + : QWidget(parent), fillColor(col) + { + setAttribute(Qt::WA_OpaquePaintEvent); + } + + bool event(QEvent *event) override + { + const auto type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::WindowDeactivate) + return true; + return QWidget::event(event); + } + +protected: + void paintEvent(QPaintEvent *e) override + { + Q_UNUSED(e); + QPainter painter(this); + fillColor.setBlue(paintCount % 255); + painter.fillRect(e->rect(), fillColor); +#ifdef MANUAL_DEBUG + ++paintCount; + painter.drawText(rect(), Qt::AlignCenter, QString::number(paintCount)); +#endif + } + +private: + QColor fillColor; + int paintCount = 0; +}; + +class Draggable : public OpaqueWidget +{ +public: + Draggable(QWidget *parent = nullptr) + : OpaqueWidget(Qt::white, parent) + { + } + + Draggable(const QColor &col, QWidget *parent = nullptr) + : OpaqueWidget(col, parent) + { + left = new OpaqueWidget(Qt::gray, this); + top = new OpaqueWidget(Qt::gray, this); + right = new OpaqueWidget(Qt::gray, this); + bottom = new OpaqueWidget(Qt::gray, this); + } + + QSize sizeHint() const override { + return QSize(100, 100); + } + +protected: + void resizeEvent(QResizeEvent *) override + { + if (!left) + return; + left->setGeometry(0, 0, 10, height()); + top->setGeometry(10, 0, width() - 10, 10); + right->setGeometry(width() - 10, 10, 10, height() - 10); + bottom->setGeometry(10, height() - 10, width() - 10, 10); + } + + void mousePressEvent(QMouseEvent *e) override + { + lastPos = e->position().toPoint(); + } + void mouseMoveEvent(QMouseEvent *e) override + { + QPoint pos = geometry().topLeft(); + pos += e->position().toPoint() - lastPos; + move(pos); + } + void mouseReleaseEvent(QMouseEvent *) override + { + lastPos = {}; + } + +private: + OpaqueWidget *left = nullptr; + OpaqueWidget *top = nullptr; + OpaqueWidget *right = nullptr; + OpaqueWidget *bottom = nullptr; + QPoint lastPos; +}; + +class TestScene : public QWidget +{ +public: + TestScene() + { + setObjectName("scene"); + + // opaque because it has an opaque background color and autoFillBackground is set + area = new QWidget(this); + area->setObjectName("area"); + area->setAutoFillBackground(true); + QPalette palette; + palette.setColor(QPalette::Window, QColor::fromRgb(0, 0, 0)); + area->setPalette(palette); + + // all these children set WA_OpaquePaintEvent + redChild = new Draggable(Qt::red, area); + redChild->setObjectName("redChild"); + + greenChild = new Draggable(Qt::green, area); + greenChild->setObjectName("greenChild"); + + yellowChild = new Draggable(Qt::yellow, this); + yellowChild->setObjectName("yellowChild"); + + nakedChild = new Draggable(this); + nakedChild->move(300, 0); + nakedChild->setObjectName("nakedChild"); + + bar = new OpaqueWidget(Qt::darkGray, this); + bar->setObjectName("bar"); + } + + QWidget *area; + QWidget *redChild; + QWidget *greenChild; + QWidget *yellowChild; + QWidget *nakedChild; + QWidget *bar; + + QSize sizeHint() const override { return QSize(400, 400); } + + bool event(QEvent *event) override + { + const auto type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::WindowDeactivate) + return true; + return QWidget::event(event); + } + +protected: + void resizeEvent(QResizeEvent *) override + { + area->setGeometry(50, 50, width() - 100, height() - 100); + bar->setGeometry(width() / 2 - 25, height() / 2, 50, height() / 2); + } +}; + +class tst_QWidgetRepaintManager : public QObject +{ + Q_OBJECT + +public: + tst_QWidgetRepaintManager(); + +public slots: + void initTestCase(); + void cleanup(); + +private slots: + void basic(); + void children(); + void opaqueChildren(); + void staticContents(); + void scroll(); + void paintOnScreenUpdates(); + void evaluateRhi(); + +#if defined(QT_BUILD_INTERNAL) + void scrollWithOverlap(); + void overlappedRegion(); + void fastMove(); + void moveAccross(); + void moveInOutOverlapped(); + +protected: + /* + This helper compares the widget as rendered into the backingstore with the widget + as rendered via QWidget::grab. The latter always produces a fully rendered image, + so differences indicate bugs in QWidgetRepaintManager's or QWidget's painting code. + */ + bool compareWidget(QWidget *w) + { + QBackingStore *backingStore = w->window()->backingStore(); + Q_ASSERT(backingStore && backingStore->handle()); + QPlatformBackingStore *platformBackingStore = backingStore->handle(); + + if (!waitForFlush(w)) { + qWarning() << "Widget" << w << "failed to flush"; + return false; + } + + QImage backingstoreContent = platformBackingStore->toImage(); + if (!w->isWindow()) { + const qreal dpr = w->devicePixelRatioF(); + const QPointF offset = w->mapTo(w->window(), QPointF(0, 0)) * dpr; + backingstoreContent = backingstoreContent.copy(offset.x(), offset.y(), w->width() * dpr, w->height() * dpr); + } + const QImage widgetRender = w->grab().toImage().convertToFormat(backingstoreContent.format()); + + const bool result = backingstoreContent == widgetRender; + +#ifdef MANUAL_DEBUG + if (!result) { + backingstoreContent.save(QString("/tmp/backingstore_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); + widgetRender.save(QString("/tmp/grab_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); + } +#endif + return result; + }; + + QRegion dirtyRegion(QWidget *widget) const + { + return QWidgetPrivate::get(widget)->dirty; + } + bool waitForFlush(QWidget *widget) const + { + if (!widget) + return true; + + auto *repaintManager = QWidgetPrivate::get(widget->window())->maybeRepaintManager(); + + if (!repaintManager) + return true; + + return QTest::qWaitFor([repaintManager]{ return !repaintManager->isDirty(); } ); + }; +#endif // QT_BUILD_INTERNAL + + +private: + const int m_fuzz; + bool m_implementsScroll = false; +}; + +tst_QWidgetRepaintManager::tst_QWidgetRepaintManager() : + m_fuzz(int(QHighDpiScaling::factor(QGuiApplication::primaryScreen()))) +{ +} + +void tst_QWidgetRepaintManager::initTestCase() +{ + QWidget widget; + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + m_implementsScroll = widget.backingStore()->handle()->scroll(QRegion(widget.rect()), 1, 1); + qInfo() << QGuiApplication::platformName() << "QPA backend implements scroll:" << m_implementsScroll; +} + +void tst_QWidgetRepaintManager::cleanup() +{ + QVERIFY(QApplication::topLevelWidgets().isEmpty()); +} + +void tst_QWidgetRepaintManager::basic() +{ + TestWidget widget; + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + QCOMPARE(widget.takePaintedRegions(), QRegion(0, 0, widget.width(), widget.height())); + + widget.update(); + QVERIFY(widget.waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion(0, 0, widget.width(), widget.height())); + + widget.repaint(); + QCOMPARE(widget.takePaintedRegions(), QRegion(0, 0, widget.width(), widget.height())); +} + +/*! + Children cannot assumed to be fully opaque, so the parent will repaint when the + child repaints. +*/ +void tst_QWidgetRepaintManager::children() +{ + if (QStringList{"android"}.contains(QGuiApplication::platformName())) + QSKIP("This test fails on Android"); + + TestWidget widget; + widget.initialShow(); + + TestWidget *child1 = new TestWidget(&widget); + child1->move(20, 20); + child1->show(); + QVERIFY(QTest::qWaitForWindowExposed(child1)); + QVERIFY(child1->waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion(child1->geometry())); + QCOMPARE(child1->takePaintedRegions(), QRegion(child1->rect())); + + child1->move(20, 30); + QVERIFY(widget.waitForPainted()); + // both the old and the new area covered by child1 need to be repainted + QCOMPARE(widget.takePaintedRegions(), QRegion(20, 20, child1->width(), child1->height() + 10)); + QCOMPARE(child1->takePaintedRegions(), QRegion(child1->rect())); + + TestWidget *child2 = new TestWidget(&widget); + child2->move(30, 30); + child2->raise(); + child2->show(); + + QVERIFY(child2->waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion(child2->geometry())); + QCOMPARE(child1->takePaintedRegions(), QRegion(10, 0, child2->width() - 10, child2->height())); + QCOMPARE(child2->takePaintedRegions(), QRegion(child2->rect())); + + child1->hide(); + QVERIFY(widget.waitForPainted()); + QCOMPARE(widget.paintedRegions, QRegion(child1->geometry())); +} + +void tst_QWidgetRepaintManager::opaqueChildren() +{ + if (QStringList{"android"}.contains(QGuiApplication::platformName())) + QSKIP("This test fails on Android"); + + TestWidget widget; + widget.initialShow(); + + TestWidget *child1 = new TestWidget(&widget); + child1->move(20, 20); + child1->setAttribute(Qt::WA_OpaquePaintEvent); + child1->show(); + + QVERIFY(child1->waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion()); + QCOMPARE(child1->takePaintedRegions(), child1->rect()); + + child1->move(20, 30); + QVERIFY(widget.waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion(20, 20, child1->width(), 10)); + if (!m_implementsScroll) + QEXPECT_FAIL("", "child1 shouldn't get painted, we can just move the area of the backingstore", Continue); + QCOMPARE(child1->takePaintedRegions(), QRegion()); +} + +/*! + When resizing to be larger, a widget with Qt::WA_StaticContents set + should only repaint the newly revealed areas. +*/ +void tst_QWidgetRepaintManager::staticContents() +{ + const auto *integration = QGuiApplicationPrivate::platformIntegration(); + if (!integration->hasCapability(QPlatformIntegration::BackingStoreStaticContents)) + QSKIP("Platform does not support static backingstore content"); + + TestWidget widget; + widget.setAttribute(Qt::WA_StaticContents); + widget.initialShow(); + + // Trigger resize via QWindow (similar to QWSI code path) + QVERIFY(widget.windowHandle()); + QSize oldSize = widget.size(); + widget.windowHandle()->resize(widget.width(), widget.height() + 10); + QVERIFY(widget.waitForPainted()); + QCOMPARE(widget.takePaintedRegions(), QRegion(0, oldSize.width(), widget.width(), 10)); + + // Trigger resize via QWidget + oldSize = widget.size(); + widget.resize(widget.width() + 10, widget.height()); + QVERIFY(widget.waitForPainted()); + QEXPECT_FAIL("", "QWidgetPrivate::setGeometry_sys wrongly triggers full update", Continue); + QCOMPARE(widget.takePaintedRegions(), QRegion(oldSize.width(), 0, 10, widget.height())); +} + +/*! + Scrolling a widget. +*/ +void tst_QWidgetRepaintManager::scroll() +{ + if (QStringList{"android"}.contains(QGuiApplication::platformName())) + QSKIP("This test fails on Android"); + + TestWidget widget; + widget.initialShow(); + + widget.scroll(10, 0); + QVERIFY(widget.waitForPainted()); + if (!m_implementsScroll) + QEXPECT_FAIL("", "This should just repaint the newly exposed region", Continue); + QCOMPARE(widget.takePaintedRegions(), QRegion(0, 0, 10, widget.height())); + + TestWidget *child = new TestWidget(&widget); + child->move(20, 20); + child->initialShow(); + + // a potentially semi-transparent child scrolling needs a full repaint + child->scroll(10, 0); + QVERIFY(child->waitForPainted()); + QCOMPARE(child->takePaintedRegions(), child->rect()); + QCOMPARE(widget.takePaintedRegions(), child->geometry()); + + // a explicitly opaque child scrolling only needs the child to repaint newly exposed regions + child->setAttribute(Qt::WA_OpaquePaintEvent); + child->scroll(10, 0); + QVERIFY(child->waitForPainted()); + if (!m_implementsScroll) + QEXPECT_FAIL("", "This should just repaint the newly exposed region", Continue); + QCOMPARE(child->takePaintedRegions(), QRegion(0, 0, 10, child->height())); + QCOMPARE(widget.takePaintedRegions(), QRegion()); +} + +class PaintOnScreenWidget : public TestWidget +{ +public: + using TestWidget::TestWidget; + + // Explicit override to prevent noPaintOnScreen on Windows + QPaintEngine *paintEngine() const override + { + return nullptr; + } +}; + +void tst_QWidgetRepaintManager::paintOnScreenUpdates() +{ + { + TestWidget topLevel; + topLevel.setObjectName("TopLevel"); + topLevel.resize(500, 500); + TestWidget renderToTextureWidget(&topLevel); + renderToTextureWidget.setObjectName("RenderToTexture"); + renderToTextureWidget.setGeometry(0, 0, 200, 200); + QWidgetPrivate::get(&renderToTextureWidget)->setRenderToTexture(); + + PaintOnScreenWidget paintOnScreenWidget(&topLevel); + paintOnScreenWidget.setObjectName("PaintOnScreen"); + paintOnScreenWidget.setGeometry(200, 200, 300, 300); + + topLevel.initialShow(); + + // Updating before toggling WA_PaintOnScreen should work fine + paintOnScreenWidget.update(); + paintOnScreenWidget.waitForPainted(); + QVERIFY(paintOnScreenWidget.waitForPainted()); + +#ifdef Q_OS_ANDROID + QEXPECT_FAIL("", "This test fails on Android", Abort); +#endif + QCOMPARE(paintOnScreenWidget.takePaintedRegions(), paintOnScreenWidget.rect()); + + renderToTextureWidget.update(); + QVERIFY(renderToTextureWidget.waitForPainted()); + QCOMPARE(renderToTextureWidget.takePaintedRegions(), renderToTextureWidget.rect()); + + // Then toggle WA_PaintOnScreen + paintOnScreenWidget.setAttribute(Qt::WA_PaintOnScreen); + + // The render-to-texture widget updates fine + renderToTextureWidget.update(); + QVERIFY(renderToTextureWidget.waitForPainted()); + QCOMPARE(renderToTextureWidget.takePaintedRegions(), renderToTextureWidget.rect()); + + // Updating the paint-on-screen texture widget will not result + // in a paint event, but should result in an update request. + paintOnScreenWidget.updateRequests = 0; + paintOnScreenWidget.update(); + QVERIFY(QTest::qWaitFor([&]{ return paintOnScreenWidget.updateRequests > 0; })); + + // And should not prevent the render-to-texture widget from receiving updates + renderToTextureWidget.update(); + QVERIFY(renderToTextureWidget.waitForPainted()); + QCOMPARE(renderToTextureWidget.takePaintedRegions(), renderToTextureWidget.rect()); + } + + { + TestWidget paintOnScreenTopLevel; + paintOnScreenTopLevel.setObjectName("PaintOnScreenTopLevel"); + paintOnScreenTopLevel.setAttribute(Qt::WA_PaintOnScreen); + + paintOnScreenTopLevel.initialShow(); + + paintOnScreenTopLevel.updateRequests = 0; + paintOnScreenTopLevel.update(); + QVERIFY(QTest::qWaitFor([&]{ return paintOnScreenTopLevel.updateRequests > 0; })); + + // Turn off paint on screen and make it a render-to-texture widget. + // This will lead us into a QWidgetRepaintManager::markDirty() code + // path that checks updateRequestSent, which is still set from the + // update above since paint-on-screen handling doesn't reset it. + paintOnScreenTopLevel.setAttribute(Qt::WA_PaintOnScreen, false); + QWidgetPrivate::get(&paintOnScreenTopLevel)->setRenderToTexture(); + paintOnScreenTopLevel.update(); + QVERIFY(QTest::qWaitFor([&]{ return paintOnScreenTopLevel.updateRequests > 1; })); + } +} + +class RhiWidgetPrivate : public QWidgetPrivate +{ +public: + RhiWidgetPrivate(const QPlatformBackingStoreRhiConfig &config) + : config(config) + { + } + + QPlatformBackingStoreRhiConfig rhiConfig() const override + { + return config; + } + + QPlatformBackingStoreRhiConfig config = QPlatformBackingStoreRhiConfig::Null; +}; + +class RhiWidget : public QWidget +{ +public: + RhiWidget(const QPlatformBackingStoreRhiConfig &config = QPlatformBackingStoreRhiConfig::Null, QWidget *parent = nullptr) + : QWidget(*new RhiWidgetPrivate(config), parent, {}) + { + } +}; + +void tst_QWidgetRepaintManager::evaluateRhi() +{ + const auto *integration = QGuiApplicationPrivate::platformIntegration(); + if (!integration->hasCapability(QPlatformIntegration::RhiBasedRendering)) + QSKIP("Platform does not support RHI based rendering"); + + // We need full control over whether widgets are native or not + const bool nativeSiblingsOriginal = qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); + auto nativeSiblingGuard = qScopeGuard([&]{ + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, nativeSiblingsOriginal); + }); + + auto defaultSurfaceType = QSurface::RasterSurface; + bool usesRhiBackingStore = false; + + { + // Plain QWidget doesn't enable RHI + QWidget regularWidget; + regularWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(®ularWidget)); + QVERIFY(!QWidgetPrivate::get(®ularWidget)->usesRhiFlush); + + // The platform might use a non-raster surface type if it uses + // an RHI backingstore by default (e.g. Android, iOS, QNX). + defaultSurfaceType = regularWidget.windowHandle()->surfaceType(); + + // Record whether the platform uses an RHI backingstore, + // so we can opt out of some tests further down. + if (defaultSurfaceType != QSurface::RasterSurface) + usesRhiBackingStore = QWidgetPrivate::get(®ularWidget)->rhi(); + else + QVERIFY(!QWidgetPrivate::get(®ularWidget)->rhi()); + } + + { + // But a top level RHI widget does + RhiWidget rhiWidget; + rhiWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&rhiWidget)); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + } + +#if QT_CONFIG(opengl) + { + // Non-native child RHI widget enables RHI for top level regular widget + QWidget topLevel; + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + // Only the native widget that actually flushes will report usesRhiFlush + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + // But it should have an RHI it can use + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + } + + { + // Native child RHI widget does not enable RHI for top level + QWidget topLevel; + RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); + QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); + + if (!usesRhiBackingStore) + QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); + } + + { + // Non-native RHI child of native child enables RHI for native child, + // but not top level. + QWidget topLevel; + QWidget nativeChild(&topLevel); + nativeChild.setAttribute(Qt::WA_NativeWindow); + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); + QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); + if (!usesRhiBackingStore) + QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); + } + + { + // Native child RHI widget does not prevent RHI for top level + // if non-native RHI child widget is also present. + QWidget topLevel; + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + } + + { + // Reparenting into a window that already matches the required + // surface type should still mark the parent as flushing with RHI. + QWidget topLevel; + + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::Null); + rhiWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&rhiWidget)); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + rhiWidget.setParent(&topLevel); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + } + + { + // Non-native RHI child of native child enables RHI for native child, + // but does not prevent top level from flushing with RHI. + QWidget topLevel; + QWidget nativeChild(&topLevel); + nativeChild.setAttribute(Qt::WA_NativeWindow); + RhiWidget rhiGranchild(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); + RhiWidget rhiChild(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiGranchild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiGranchild)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiChild)->rhi()); + } + +#if QT_CONFIG(metal) + QRhiMetalInitParams metalParams; + if (QRhi::probe(QRhi::Metal, &metalParams)) { + // Native RHI childen allows mixing RHI backends + QWidget topLevel; + RhiWidget openglWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + openglWidget.setAttribute(Qt::WA_NativeWindow); + RhiWidget metalWidget(QPlatformBackingStoreRhiConfig::Metal, &topLevel); + metalWidget.setAttribute(Qt::WA_NativeWindow); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); + QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); + if (!usesRhiBackingStore) + QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); + + QCOMPARE(openglWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&openglWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&openglWidget)->rhi()); + + QCOMPARE(metalWidget.windowHandle()->surfaceType(), QSurface::MetalSurface); + QVERIFY(QWidgetPrivate::get(&metalWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&metalWidget)->rhi()); + + QVERIFY(QWidgetPrivate::get(&openglWidget)->rhi() != QWidgetPrivate::get(&metalWidget)->rhi()); + } +#endif // QT_CONFIG(metal) + +#endif // QT_CONFIG(opengl) +} + +#if defined(QT_BUILD_INTERNAL) + +/*! + Verify that overlapping children are repainted correctly when + a widget is moved (via a scroll area) for such a distance that + none of the old area is still visible. QTBUG-26269 +*/ +void tst_QWidgetRepaintManager::scrollWithOverlap() +{ + if (QStringList{"android"}.contains(QGuiApplication::platformName())) + QSKIP("This test fails on Android"); + + class MainWindow : public QWidget + { + public: + MainWindow(QWidget *parent = 0) + : QWidget(parent, Qt::WindowStaysOnTopHint) + { + m_scrollArea = new QScrollArea(this); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QWidget *w = new QWidget; + w->setPalette(QPalette(Qt::gray)); + w->setAutoFillBackground(true); + m_scrollArea->setWidget(w); + m_scrollArea->resize(500, 100); + w->resize(5000, 600); + + m_topWidget = new QWidget(this); + m_topWidget->setPalette(QPalette(Qt::red)); + m_topWidget->setAutoFillBackground(true); + m_topWidget->resize(300, 200); + + resize(600, 300); + } + + void resizeEvent(QResizeEvent *e) override + { + QWidget::resizeEvent(e); + // move scroll area and top widget to the center of the main window + scrollArea()->move((width() - scrollArea()->width()) / 2, (height() - scrollArea()->height()) / 2); + topWidget()->move((width() - topWidget()->width()) / 2, (height() - topWidget()->height()) / 2); + } + + + inline QScrollArea *scrollArea() const { return m_scrollArea; } + inline QWidget *topWidget() const { return m_topWidget; } + + private: + QScrollArea *m_scrollArea; + QWidget *m_topWidget; + }; + + MainWindow w; + w.show(); + + QVERIFY(QTest::qWaitForWindowActive(&w)); + + bool result = compareWidget(w.topWidget()); + // if this fails already, then the system we test on can't compare screenshots from grabbed widgets, + // and we have to skip this test. Possible reasons are differences in surface formats or DPI, or + // unrelated bugs in QPlatformBackingStore::toImage or QWidget::grab. + if (!result) + QSKIP("Cannot compare QWidget::grab with QScreen::grabWindow on this machine"); + + // scroll the horizontal slider to the right side + { + w.scrollArea()->horizontalScrollBar()->setValue(w.scrollArea()->horizontalScrollBar()->maximum()); + QVERIFY(compareWidget(w.topWidget())); + } + + // scroll the vertical slider down + { + w.scrollArea()->verticalScrollBar()->setValue(w.scrollArea()->verticalScrollBar()->maximum()); + QVERIFY(compareWidget(w.topWidget())); + } + + // hide the top widget + { + w.topWidget()->hide(); + QVERIFY(compareWidget(w.scrollArea()->viewport())); + } + + // scroll the horizontal slider to the left side + { + w.scrollArea()->horizontalScrollBar()->setValue(w.scrollArea()->horizontalScrollBar()->minimum()); + QVERIFY(compareWidget(w.scrollArea()->viewport())); + } + + // scroll the vertical slider up + { + w.scrollArea()->verticalScrollBar()->setValue(w.scrollArea()->verticalScrollBar()->minimum()); + QVERIFY(compareWidget(w.scrollArea()->viewport())); + } +} + +/*! + This tests QWidgetPrivate::overlappedRegion, which however is only used in the + QWidgetRepaintManager, so the test is here. +*/ +void tst_QWidgetRepaintManager::overlappedRegion() +{ + TestScene scene; + + if (scene.screen()->availableSize().width() < scene.sizeHint().width() + || scene.screen()->availableSize().height() < scene.sizeHint().height()) { + QSKIP("The screen on this system is too small for this test"); + } + + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + auto overlappedRegion = [](QWidget *widget, bool breakAfterFirst = false){ + auto *priv = QWidgetPrivate::get(widget); + // overlappedRegion works on parent coordinates (crect, i.e. QWidget::geometry) + return priv->overlappedRegion(widget->geometry(), breakAfterFirst); + }; + + // the yellow child is not overlapped + QVERIFY(overlappedRegion(scene.yellowChild).isEmpty()); + // the green child is partially overlapped by the yellow child, which + // is at position -50, -50 relative to the green child (and 100x100 large) + QRegion overlap = overlappedRegion(scene.greenChild); + QVERIFY(!overlap.isEmpty()); + QCOMPARE(overlap, QRegion(QRect(-50, -50, 100, 100))); + // the red child is completely obscured by the green child, and partially + // obscured by the yellow child. How exactly this is divided into rects is + // irrelevant for the test. + overlap = overlappedRegion(scene.redChild); + QVERIFY(!overlap.isEmpty()); + QCOMPARE(overlap.boundingRect(), QRect(-50, -50, 150, 150)); + + // moving the red child out of obscurity + scene.redChild->move(100, 0); + overlap = overlappedRegion(scene.redChild); + QTRY_VERIFY(overlap.isEmpty()); + + // moving the red child down so it's partially behind the bar + scene.redChild->move(100, 100); + overlap = overlappedRegion(scene.redChild); + QTRY_VERIFY(!overlap.isEmpty()); + + // moving the yellow child so it is partially overlapped by the bar + scene.yellowChild->move(200, 200); + overlap = overlappedRegion(scene.yellowChild); + QTRY_VERIFY(!overlap.isEmpty()); +} + +void tst_QWidgetRepaintManager::fastMove() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + // moving yellow; nothing obscured + scene.yellowChild->move(QPoint(25, 0)); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); // fast move + if (m_implementsScroll) { + QCOMPARE(repaintManager->dirtyWidgetList(), QList<QWidget *>() << &scene); + QVERIFY(dirtyRegion(scene.yellowChild).isEmpty()); + } else { + QCOMPARE(repaintManager->dirtyWidgetList(), QList<QWidget *>() << scene.yellowChild << &scene); + QCOMPARE(dirtyRegion(scene.yellowChild), QRect(0, 0, 100, 100)); + } + QCOMPARE(dirtyRegion(&scene), QRect(0, 0, 25, 100)); + QTRY_VERIFY(dirtyRegion(&scene).isEmpty()); + QVERIFY(compareWidget(&scene)); +} + +void tst_QWidgetRepaintManager::moveAccross() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + for (int i = 0; i < 4; ++i) { + scene.greenChild->move(scene.greenChild->pos() + QPoint(25, 0)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); + + for (int i = 0; i < 16; ++i) { + scene.redChild->move(scene.redChild->pos() + QPoint(25, 0)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); + + for (int i = 0; i < qMin(scene.area->width(), scene.area->height()); i += 25) { + scene.yellowChild->move(scene.yellowChild->pos() + QPoint(25, 25)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); +} + +void tst_QWidgetRepaintManager::moveInOutOverlapped() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + // yellow out + scene.yellowChild->move(QPoint(-100, 0)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid dest rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // yellow in, obscured by bar + scene.yellowChild->move(QPoint(scene.width() / 2, scene.height() / 2)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid source rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // green out + scene.greenChild->move(QPoint(-100, 0)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid dest rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // green back in, obscured by bar + scene.greenChild->move(QPoint(scene.area->width() / 2 - 50, scene.area->height() / 2 - 50)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid source rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // red back under green + scene.redChild->move(scene.greenChild->pos()); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // destination rect obscured + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); +} +#endif //# defined(QT_BUILD_INTERNAL) + +QTEST_MAIN(tst_QWidgetRepaintManager) +#include "tst_qwidgetrepaintmanager.moc" diff --git a/tests/auto/widgets/kernel/qwidgetsvariant/CMakeLists.txt b/tests/auto/widgets/kernel/qwidgetsvariant/CMakeLists.txt index f387be7306..bb3c1e2ad6 100644 --- a/tests/auto/widgets/kernel/qwidgetsvariant/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwidgetsvariant/CMakeLists.txt @@ -1,15 +1,22 @@ -# Generated from qwidgetsvariant.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwidgetsvariant Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwidgetsvariant LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwidgetsvariant SOURCES tst_qwidgetsvariant.cpp INCLUDE_DIRECTORIES ../../../other/qvariant_common - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qwidgetsvariant/tst_qwidgetsvariant.cpp b/tests/auto/widgets/kernel/qwidgetsvariant/tst_qwidgetsvariant.cpp index d33f734af0..0a03fb9e1d 100644 --- a/tests/auto/widgets/kernel/qwidgetsvariant/tst_qwidgetsvariant.cpp +++ b/tests/auto/widgets/kernel/qwidgetsvariant/tst_qwidgetsvariant.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> diff --git a/tests/auto/widgets/kernel/qwindowcontainer/CMakeLists.txt b/tests/auto/widgets/kernel/qwindowcontainer/CMakeLists.txt index 63a3f9632d..787505972f 100644 --- a/tests/auto/widgets/kernel/qwindowcontainer/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qwindowcontainer/CMakeLists.txt @@ -1,13 +1,20 @@ -# Generated from qwindowcontainer.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwindowcontainer Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwindowcontainer LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwindowcontainer SOURCES tst_qwindowcontainer.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Widgets ) diff --git a/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp b/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp index 9dead3d3ea..52aaf094b4 100644 --- a/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp +++ b/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -32,12 +7,14 @@ #include <qapplication.h> #include <qwindow.h> #include <qwidget.h> +#include <qlineedit.h> #include <qdockwidget.h> #include <qmainwindow.h> #include <qscreen.h> #include <qscopedpointer.h> #include <qevent.h> +#include <qboxlayout.h> class Window : public QWindow @@ -71,16 +48,20 @@ public: private slots: void testShow(); void testPositionAndSize(); + void testSizeHints(); void testExposeObscure(); void testOwnership(); void testBehindTheScenesDeletion(); void testUnparenting(); + void testReparenting(); void testUnparentReparent(); void testActivation(); void testAncestorChange(); void testDockWidget(); void testNativeContainerParent(); void testPlatformSurfaceEvent(); + void embedWidgetWindow(); + void testFocus(); void cleanup(); private: @@ -132,7 +113,29 @@ void tst_QWindowContainer::testPositionAndSize() QCOMPARE(window->height(), container->height()); } +void tst_QWindowContainer::testSizeHints() +{ + QScopedPointer<QWidget> tlw(new QWidget); + QWindow *window = new QWindow(); + window->setMinimumSize(QSize(200, 200)); + window->setGeometry(m_availableGeometry.x() + 300, m_availableGeometry.y() + 400, 500, 600); + + QScopedPointer<QWidget> container(QWidget::createWindowContainer(window)); + container->setWindowTitle(QTest::currentTestFunction()); + + QVBoxLayout *vbox = new QVBoxLayout(tlw.data()); + vbox->addWidget(container.data()); + vbox->setContentsMargins(0, 0, 0, 0); + // Size hints should work regardless of visibility + QCOMPARE(container->minimumSizeHint(), window->minimumSize()); + QCOMPARE(vbox->minimumSize(), window->minimumSize()); + + // Respect dynamic updates + window->setMinimumSize(QSize(210, 210)); + QCOMPARE(container->minimumSizeHint(), window->minimumSize()); + QCOMPARE(vbox->minimumSize(), window->minimumSize()); +} void tst_QWindowContainer::testExposeObscure() { @@ -231,12 +234,12 @@ void tst_QWindowContainer::testActivation() void tst_QWindowContainer::testUnparenting() { - QWindow *window = new QWindow(); + QPointer<QWindow> window(new QWindow()); QScopedPointer<QWidget> container(QWidget::createWindowContainer(window)); container->setWindowTitle(QTest::currentTestFunction()); container->setGeometry(m_availableGeometry.x() + 100, m_availableGeometry.y() + 100, 200, 100); - window->setParent(0); + window->setParent(nullptr); container->show(); @@ -244,6 +247,26 @@ void tst_QWindowContainer::testUnparenting() // Window should not be made visible by container.. QVERIFY(!window->isVisible()); + + container.reset(); + QVERIFY(window); + delete window; +} + +void tst_QWindowContainer::testReparenting() +{ + QPointer<QWindow> window1(new QWindow()); + QScopedPointer<QWindow> window2(new QWindow()); + QScopedPointer<QWidget> container(QWidget::createWindowContainer(window1)); + + window1->setParent(window2.data()); + + // Not deleted with container + container.reset(); + QVERIFY(window1); + // but deleted with new parent + window2.reset(); + QVERIFY(!window1); } void tst_QWindowContainer::testUnparentReparent() @@ -414,6 +437,99 @@ void tst_QWindowContainer::testPlatformSurfaceEvent() QVERIFY(ok); } +void tst_QWindowContainer::embedWidgetWindow() +{ + { + QWidget parent; + QWidget *widget = new QWidget; + widget->show(); + QVERIFY(QTest::qWaitForWindowExposed(widget)); + QVERIFY(widget->windowHandle()); + QPointer<QWindow> widgetWindow = widget->windowHandle(); + auto *container = QWidget::createWindowContainer(widgetWindow, &parent); + QCOMPARE(container, widget); + QCOMPARE(widget->parent(), &parent); + delete widget; + QTRY_VERIFY(widgetWindow.isNull()); + } + + QPointer<QWidget> widget = new QWidget; + QPointer<QWindow> widgetWindow; + { + QWidget parent; + widget->show(); + QVERIFY(QTest::qWaitForWindowExposed(widget)); + QVERIFY(widget->windowHandle()); + widgetWindow = widget->windowHandle(); + auto *container = QWidget::createWindowContainer(widgetWindow, &parent); + QCOMPARE(container, widget); + QCOMPARE(widget->parent(), &parent); + } + QTRY_VERIFY(widget.isNull()); + QTRY_VERIFY(widgetWindow.isNull()); + +} + +void tst_QWindowContainer::testFocus() +{ + QWidget root; + root.setGeometry(m_availableGeometry); + + QLineEdit *lineEdit = new QLineEdit(&root); + lineEdit->setGeometry(0, 0, m_availableGeometry.width() * 0.1, 17); + lineEdit->setFocusPolicy(Qt::FocusPolicy::StrongFocus); + + QWindow *embedded = new QWindow(); + QWidget *container = QWidget::createWindowContainer(embedded, &root); + container->setGeometry(0, lineEdit->height() + 10, m_availableGeometry.width() * 0.2, m_availableGeometry.height() - (lineEdit->height() + 10)); + container->setFocusPolicy(Qt::StrongFocus); + + root.show(); + QVERIFY(QTest::qWaitForWindowExposed(&root)); + lineEdit->setFocus(); + QTRY_VERIFY(lineEdit->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + + // embedded window gets focused because of mouse click + QPoint embeddedCenter = container->rect().center(); + QTest::mousePress(root.windowHandle(), Qt::LeftButton, {}, embeddedCenter); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); + + QTest::mouseClick(lineEdit, Qt::LeftButton, {}); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + QVERIFY(lineEdit->hasFocus()); + + // embedded window gets focused because of Tab key event + QTest::keyClick(root.windowHandle(), Qt::Key_Tab); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); + // A key tab event sent to the root window should cause + // the nextInFocusChain of the window container to get focused + QTest::keyClick(root.windowHandle(), Qt::Key_Tab); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + QVERIFY(lineEdit->hasFocus()); + + // embedded window gets focused programmatically + embedded->requestActivate(); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); +} + QTEST_MAIN(tst_QWindowContainer) #include "tst_qwindowcontainer.moc" |