diff options
author | Santhosh Kumar <[email protected]> | 2024-08-21 18:42:55 +0200 |
---|---|---|
committer | Santhosh Kumar <[email protected]> | 2025-07-09 22:31:35 +0200 |
commit | 931d634c469babe7835d8486cafc4bfe17bcb21f (patch) | |
tree | 2f7a17e41fed020da636f3ab57db5105e7625b14 | |
parent | 8aeb0b3236a98458a7310e8f2817aab0bb60558a (diff) |
The non-native quick dialogs don't block input to the windows even after
setting the modality as ApplicationModal/WindowModal. This is because
non-native platform dialogs (QuickPlaform*Dialogs) didn't set modal to
its respective window (QQuickPopupWindow).
This patch fixes that issue by setting the modality to the non-native
platform dialogs.
Task-number: QTBUG-127605
Pick-to: 6.10 6.9 6.8
Change-Id: Idb3374e881766ae92adc0360c9b9af5c498dd6df
Reviewed-by: Oliver Eftevaag <[email protected]>
21 files changed, 543 insertions, 26 deletions
diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickplatformcolordialog.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickplatformcolordialog.cpp index 8e1413863c..c62573b3be 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickplatformcolordialog.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickplatformcolordialog.cpp @@ -10,6 +10,7 @@ #include <QtQml/qqmlcomponent.h> #include <QtQuick/qquickwindow.h> #include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquickpopupanchors_p.h> @@ -109,7 +110,7 @@ bool QQuickPlatformColorDialog::show(Qt::WindowFlags flags, Qt::WindowModality m m_dialog->setTitle(options->windowTitle()); m_dialog->setOptions(options); - + m_dialog->setWindowModality(modality); m_dialog->open(); return true; } diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfiledialog.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfiledialog.cpp index 25919aa691..1334a8ccdf 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfiledialog.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfiledialog.cpp @@ -11,6 +11,7 @@ #include <QtQuick/qquickwindow.h> #include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> #include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquickpopupanchors_p.h> @@ -195,7 +196,7 @@ bool QQuickPlatformFileDialog::show(Qt::WindowFlags flags, Qt::WindowModality mo m_dialog->setCurrentFolder(QUrl::fromLocalFile(QDir().absolutePath())); } } - + m_dialog->setWindowModality(modality); m_dialog->open(); return true; } diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfolderdialog.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfolderdialog.cpp index 2f4cf630c7..849fe4b042 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfolderdialog.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfolderdialog.cpp @@ -10,6 +10,7 @@ #include <QtQml/qqmlinfo.h> #include <QtQuick/qquickwindow.h> #include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquickpopupanchors_p.h> @@ -154,7 +155,7 @@ bool QQuickPlatformFolderDialog::show(Qt::WindowFlags flags, Qt::WindowModality ? options->labelText(QFileDialogOptions::Accept) : QString()); m_dialog->setRejectLabel(options->isLabelExplicitlySet(QFileDialogOptions::Reject) ? options->labelText(QFileDialogOptions::Reject) : QString()); - + m_dialog->setWindowModality(modality); m_dialog->open(); return true; } diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfontdialog.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfontdialog.cpp index 6a68a28525..ba391f44f8 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickplatformfontdialog.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickplatformfontdialog.cpp @@ -10,6 +10,7 @@ #include <QtQml/qqmlcomponent.h> #include <QtQuick/qquickwindow.h> #include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquickpopupanchors_p.h> @@ -125,7 +126,7 @@ bool QQuickPlatformFontDialog::show(Qt::WindowFlags flags, Qt::WindowModality mo m_dialog->setOptions(options); m_dialog->init(); - + m_dialog->setWindowModality(modality); m_dialog->open(); return true; } diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickplatformmessagedialog.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickplatformmessagedialog.cpp index 0875a21458..f0524d14f9 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickplatformmessagedialog.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickplatformmessagedialog.cpp @@ -4,6 +4,7 @@ #include <QtQml/qqmlcomponent.h> #include "qquickplatformmessagedialog_p.h" +#include <QtQuickTemplates2/private/qquickdialog_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquickpopupanchors_p.h> @@ -87,7 +88,7 @@ bool QQuickPlatformMessageDialog::show(Qt::WindowFlags flags, Qt::WindowModality QSharedPointer<QMessageDialogOptions> options = QPlatformMessageDialogHelper::options(); m_dialog->setTitle(options->windowTitle()); m_dialog->setOptions(options); - + m_dialog->setWindowModality(modality); m_dialog->open(); return true; } diff --git a/src/quicktemplates/qquickdialog.cpp b/src/quicktemplates/qquickdialog.cpp index 9ad6161c69..7d9d6b4d87 100644 --- a/src/quicktemplates/qquickdialog.cpp +++ b/src/quicktemplates/qquickdialog.cpp @@ -7,6 +7,8 @@ #include "qquickabstractbutton_p.h" #include "qquickpopupitem_p_p.h" #include "qquickpopupwindow_p_p.h" +#include <qpa/qplatformintegration.h> +#include <private/qguiapplication_p.h> QT_BEGIN_NAMESPACE diff --git a/src/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index 6f80c1171d..c27937d0a0 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -1121,15 +1121,21 @@ void QQuickPopupPrivate::adjustPopupItemParentAndWindow() const qreal initialHeight = popupItem->height() + windowInsets().top() + windowInsets().bottom(); popupItem->setParentItem(popupWindow->contentItem()); popupWindow->resize(qCeil(initialWidth), qCeil(initialHeight)); - popupWindow->setModality(modal ? Qt::ApplicationModal : Qt::NonModal); + if (popupWndModality != Qt::NonModal) + popupWindow->setModality(popupWndModality); + else + popupWindow->setModality(modal ? Qt::ApplicationModal : Qt::NonModal); popupItem->resetTitle(); popupWindow->setTitle(title); } popupItem->setParentItem(popupWindow->contentItem()); popupItem->forceActiveFocus(Qt::PopupFocusReason); } - if (popupWindow) - popupWindow->setVisible(visible); + if (popupWindow && popupWindow->transientParent()) { + auto *transientParentPriv = QQuickWindowPrivate::get(qobject_cast<QQuickWindow *>(popupWindow->transientParent())); + if (!transientParentPriv->inDestructor) + popupWindow->setVisible(visible); + } } else { if (visible) { popupItem->setParentItem(overlay); @@ -1152,6 +1158,7 @@ void QQuickPopupPrivate::adjustPopupItemParentAndWindow() if (!hasZ) popupItem->setZ(qMax(topPopupItem->z(), popupItem->z())); } + q->setModal((popupWndModality != Qt::NonModal) || modal); } popupItem->setTitle(title); @@ -3425,6 +3432,12 @@ bool QQuickPopup::setAccessibleProperty(const char *propertyName, const QVariant return d->popupItem->setAccessibleProperty(propertyName, value); } +void QQuickPopup::setWindowModality(const Qt::WindowModality modality) +{ + Q_D(QQuickPopup); + d->popupWndModality = modality; +} + QQuickItem *QQuickPopup::safeAreaAttachmentItem() { return popupItem(); diff --git a/src/quicktemplates/qquickpopup_p.h b/src/quicktemplates/qquickpopup_p.h index ebc9ada842..ebcf62073e 100644 --- a/src/quicktemplates/qquickpopup_p.h +++ b/src/quicktemplates/qquickpopup_p.h @@ -314,6 +314,8 @@ public: void setBottomInset(qreal inset); void resetBottomInset(); + void setWindowModality(const Qt::WindowModality modality); + enum PopupType { Item, Window, diff --git a/src/quicktemplates/qquickpopup_p_p.h b/src/quicktemplates/qquickpopup_p_p.h index 4c03929eea..fa55b460e4 100644 --- a/src/quicktemplates/qquickpopup_p_p.h +++ b/src/quicktemplates/qquickpopup_p_p.h @@ -203,6 +203,7 @@ public: qreal prevScale = 0; QString title; QQuickPopup::PopupType popupType = QQuickPopup::Item; + Qt::WindowModality popupWndModality = Qt::NonModal; friend class QQuickPopupTransitionManager; }; diff --git a/src/quicktemplates/qquickpopupwindow.cpp b/src/quicktemplates/qquickpopupwindow.cpp index 2f306176e1..7e21b8ec8d 100644 --- a/src/quicktemplates/qquickpopupwindow.cpp +++ b/src/quicktemplates/qquickpopupwindow.cpp @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcPopupWindow, "qt.quick.controls.popup.window") static bool s_popupGrabOk = false; -static QWindow *s_grabbedWindow = nullptr; +static QPointer<QWindow> s_grabbedWindow; class QQuickPopupWindowPrivate : public QQuickWindowQmlImplPrivate { @@ -132,7 +132,10 @@ void QQuickPopupWindow::resizeEvent(QResizeEvent *e) void QQuickPopupWindowPrivate::setVisible(bool visible) { - if (m_inHideEvent) + Q_Q(QQuickPopupWindow); + const bool isTransientParentDestroyed = !q->transientParent() ? true : + QQuickWindowPrivate::get(qobject_cast<QQuickWindow *>(q->transientParent()))->inDestructor; + if (m_inHideEvent || isTransientParentDestroyed) return; const bool visibleChanged = QWindowPrivate::visible != visible; diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/data/checkModality.qml b/tests/auto/quickdialogs/qquickcolordialogimpl/data/checkModality.qml new file mode 100644 index 0000000000..9cf79058f5 --- /dev/null +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/data/checkModality.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: rootWindow + width: 100 + height: 100 + + property alias dialog: dialog + + property alias rootMArea: rootMouseArea + property alias childMArea: childMouseArea + property alias childWindow: childWindow + + MouseArea { + id: rootMouseArea + anchors.fill: parent + } + + ApplicationWindow { + id: childWindow + width: 100 + height: 100 + MouseArea { + id: childMouseArea + anchors.fill: parent + } + } + + ColorDialog { + id: dialog + objectName: "ColorDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp b/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp index 155cea0370..8c4bc9693b 100644 --- a/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp @@ -6,6 +6,7 @@ #include <QtTest/qsignalspy.h> #include <QtQuick/qquickview.h> #include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuickControlsTestUtils/private/dialogstestutils_p.h> #include <QtQuickDialogs2/private/qquickcolordialog_p.h> @@ -59,6 +60,8 @@ private slots: void workingInsideQQuickViewer_data(); void workingInsideQQuickViewer(); void dialogCanMoveBetweenWindows(); + void checkModality_data(); + void checkModality(); private: bool closePopup(DialogTestHelper<QQuickColorDialog, QQuickColorDialogImpl> *dialogHelper, @@ -658,6 +661,62 @@ void tst_QQuickColorDialogImpl::dialogCanMoveBetweenWindows() CLOSE_DIALOG("Ok"); } +void tst_QQuickColorDialogImpl::checkModality_data() +{ + QTest::addColumn<Qt::WindowModality>("modality"); + QTest::addColumn<QColorDialogOptions::ColorDialogOption>("options"); + QTest::addColumn<int>("expectedRootWindowClickCount"); + QTest::addColumn<int>("expectedChildWindowClickCount"); + + QTest::newRow("nonModal") << Qt::NonModal << QColorDialogOptions::DontUseNativeDialog << 1 << 1; + QTest::newRow("windowModal") << Qt::WindowModal << QColorDialogOptions::DontUseNativeDialog << 0 << 1; + // Verify application modal case only for the platform which supports multiple windows + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + QTest::newRow("applicationModal") << Qt::ApplicationModal << QColorDialogOptions::DontUseNativeDialog << 0 << 0; +} + +void tst_QQuickColorDialogImpl::checkModality() +{ + QFETCH(Qt::WindowModality, modality); + QFETCH(QColorDialogOptions::ColorDialogOption, options); + QFETCH(int, expectedRootWindowClickCount); + QFETCH(int, expectedChildWindowClickCount); + + DialogTestHelper<QQuickColorDialog, QQuickColorDialogImpl> dialogHelper(this, "checkModality.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + dialogHelper.dialog->setModality(modality); + QCOMPARE(dialogHelper.dialog->modality(), modality); + + dialogHelper.dialog->setOptions(options); + QCOMPARE(dialogHelper.dialog->options(), options); + + OPEN_QUICK_DIALOG(); + QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); + + auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>(); + QVERIFY(childWindow); + + // Setting the child window transient parent as root window won't allow it to receive mouse + // events, causing WindowModal case not to be tested. Thus, resetting the transient parent. + if (modality == Qt::WindowModal) + childWindow->setTransientParent(nullptr); + + const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>(); + QVERIFY(rootMouseArea); + QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(rmaMouseSpy.size(), expectedRootWindowClickCount); + + const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>(); + QVERIFY(childMouseArea); + QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount); +} + + QTEST_MAIN(tst_QQuickColorDialogImpl) #include "tst_qquickcolordialogimpl.moc" diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/data/checkModality.qml b/tests/auto/quickdialogs/qquickfiledialogimpl/data/checkModality.qml new file mode 100644 index 0000000000..2857b890d7 --- /dev/null +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/data/checkModality.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: rootWindow + width: 100 + height: 100 + + property alias dialog: dialog + + property alias rootMArea: rootMouseArea + property alias childMArea: childMouseArea + property alias childWindow: childWindow + + MouseArea { + id: rootMouseArea + anchors.fill: parent + } + + ApplicationWindow { + id: childWindow + width: 100 + height: 100 + MouseArea { + id: childMouseArea + anchors.fill: parent + } + } + + FileDialog { + id: dialog + objectName: "FileDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp index 0dcfd0e2c8..a485da93d2 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -6,6 +6,7 @@ #include <QtTest/qsignalspy.h> #include <QtQml/qqmlfile.h> #include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuickTest/quicktest.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuickControlsTestUtils/private/dialogstestutils_p.h> @@ -98,6 +99,8 @@ private slots: void sidebarStandardPaths(); void popupType(); void reentrantFolder(); + void checkModality_data(); + void checkModality(); private: enum DelegateOrderPolicy @@ -1814,7 +1817,7 @@ void tst_QQuickFileDialogImpl::popupType() OPEN_QUICK_DIALOG(); QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); - // Window should be the default value + // Window should be the default value QCOMPARE(dialogHelper.quickDialog->popupType(), QQuickPopup::Window); QCOMPARE(dialogHelper.dialog->popupType(), QQuickPopup::Window); @@ -1893,6 +1896,59 @@ void tst_QQuickFileDialogImpl::reentrantFolder() QTRY_COMPARE(dialogHelper.fileDialogListView->count(), 2); // { NotReachable1, NotReachable2 } } +void tst_QQuickFileDialogImpl::checkModality_data() +{ + QTest::addColumn<Qt::WindowModality>("modality"); + QTest::addColumn<QFileDialogOptions::FileDialogOption>("options"); + QTest::addColumn<int>("expectedRootWindowChildCount"); + QTest::addColumn<int>("expectedChildWindowClickCount"); + + QTest::newRow("nonModal") << Qt::NonModal << QFileDialogOptions::DontUseNativeDialog << 1 << 1; + QTest::newRow("windowModal") << Qt::WindowModal << QFileDialogOptions::DontUseNativeDialog << 0 << 1; + // Verify application modal case only for the platform which supports multiple windows + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + QTest::newRow("applicationModal") << Qt::ApplicationModal << QFileDialogOptions::DontUseNativeDialog << 0 << 0; +} + +void tst_QQuickFileDialogImpl::checkModality() +{ + QFETCH(Qt::WindowModality, modality); + QFETCH(QFileDialogOptions::FileDialogOption, options); + QFETCH(int, expectedRootWindowChildCount); + QFETCH(int, expectedChildWindowClickCount); + + FileDialogTestHelper dialogHelper(this, "checkModality.qml"); + + dialogHelper.dialog->setModality(modality); + QCOMPARE(dialogHelper.dialog->modality(), modality); + + dialogHelper.dialog->setOptions(options); + QCOMPARE(dialogHelper.dialog->options(), options); + + OPEN_QUICK_DIALOG(); + QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); + + auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>(); + QVERIFY(childWindow); + + // Setting the child window transient parent as root window won't allow it to receive mouse + // events, causing WindowModal case not to be tested. Thus, resetting the transient parent. + if (modality == Qt::WindowModal) + childWindow->setTransientParent(nullptr); + + const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>(); + QVERIFY(rootMouseArea); + QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(rmaMouseSpy.size(), expectedRootWindowChildCount); + + const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>(); + QVERIFY(childMouseArea); + QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount); +} + QTEST_MAIN(tst_QQuickFileDialogImpl) #include "tst_qquickfiledialogimpl.moc" diff --git a/tests/auto/quickdialogs/qquickfolderdialogimpl/data/checkModality.qml b/tests/auto/quickdialogs/qquickfolderdialogimpl/data/checkModality.qml new file mode 100644 index 0000000000..fbe974ab97 --- /dev/null +++ b/tests/auto/quickdialogs/qquickfolderdialogimpl/data/checkModality.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: rootWindow + width: 100 + height: 100 + + property alias dialog: dialog + + property alias rootMArea: rootMouseArea + property alias childMArea: childMouseArea + property alias childWindow: childWindow + + MouseArea { + id: rootMouseArea + anchors.fill: parent + } + + ApplicationWindow { + id: childWindow + width: 100 + height: 100 + MouseArea { + id: childMouseArea + anchors.fill: parent + } + } + + FolderDialog { + id: dialog + objectName: "FolderDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp index d593b112cb..16f42bd76a 100644 --- a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp @@ -5,6 +5,7 @@ #include <QtTest/qsignalspy.h> #include <QtQml/qqmlfile.h> #include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuickTest/quicktest.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuickControlsTestUtils/private/dialogstestutils_p.h> @@ -65,6 +66,8 @@ private slots: void itemsDisabledWhenNecessary(); void done_data(); void done(); + void checkModality_data(); + void checkModality(); private: QTemporaryDir tempDir; @@ -872,6 +875,59 @@ void tst_QQuickFolderDialogImpl::done() QCOMPARE(dialogHelper.dialog->result(), result); } +void tst_QQuickFolderDialogImpl::checkModality_data() +{ + QTest::addColumn<Qt::WindowModality>("modality"); + QTest::addColumn<QFileDialogOptions::FileDialogOption>("options"); + QTest::addColumn<int>("expectedRootWindowChildCount"); + QTest::addColumn<int>("expectedChildWindowClickCount"); + + QTest::newRow("nonModal") << Qt::NonModal << QFileDialogOptions::DontUseNativeDialog << 1 << 1; + QTest::newRow("windowModal") << Qt::WindowModal << QFileDialogOptions::DontUseNativeDialog << 0 << 1; + // Verify application modal case only for the platform which supports multiple windows + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + QTest::newRow("applicationModal") << Qt::ApplicationModal << QFileDialogOptions::DontUseNativeDialog << 0 << 0; +} + +void tst_QQuickFolderDialogImpl::checkModality() +{ + QFETCH(Qt::WindowModality, modality); + QFETCH(QFileDialogOptions::FileDialogOption, options); + QFETCH(int, expectedRootWindowChildCount); + QFETCH(int, expectedChildWindowClickCount); + + FolderDialogTestHelper dialogHelper(this, "checkModality.qml"); + + dialogHelper.dialog->setModality(modality); + QCOMPARE(dialogHelper.dialog->modality(), modality); + + dialogHelper.dialog->setOptions(options); + QCOMPARE(dialogHelper.dialog->options(), options); + + OPEN_QUICK_DIALOG(); + QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); + + auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>(); + QVERIFY(childWindow); + + // Setting the child window transient parent as root window won't allow it to receive mouse + // events, causing WindowModal case not to be tested. Thus, resetting the transient parent. + if (modality == Qt::WindowModal) + childWindow->setTransientParent(nullptr); + + const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>(); + QVERIFY(rootMouseArea); + QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(rmaMouseSpy.size(), expectedRootWindowChildCount); + + const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>(); + QVERIFY(childMouseArea); + QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount); +} + QTEST_MAIN(tst_QQuickFolderDialogImpl) #include "tst_qquickfolderdialogimpl.moc" diff --git a/tests/auto/quickdialogs/qquickfontdialogimpl/data/checkModality.qml b/tests/auto/quickdialogs/qquickfontdialogimpl/data/checkModality.qml new file mode 100644 index 0000000000..71dffec6dc --- /dev/null +++ b/tests/auto/quickdialogs/qquickfontdialogimpl/data/checkModality.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: rootWindow + width: 100 + height: 100 + + property alias dialog: dialog + + property alias rootMArea: rootMouseArea + property alias childMArea: childMouseArea + property alias childWindow: childWindow + + MouseArea { + id: rootMouseArea + anchors.fill: parent + } + + ApplicationWindow { + id: childWindow + width: 100 + height: 100 + MouseArea { + id: childMouseArea + anchors.fill: parent + } + } + + FontDialog { + id: dialog + objectName: "FontDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp b/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp index e6a4e349de..44d4b7b9aa 100644 --- a/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp @@ -5,6 +5,7 @@ #include <QtTest/qsignalspy.h> #include <QtQml/qqmlfile.h> #include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuickTemplates2/private/qquickitemdelegate_p.h> #include <QtQuickDialogs2/private/qquickfontdialog_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickfontdialogimpl_p.h> @@ -50,6 +51,8 @@ private slots: void changeDialogTitle(); void searchFamily(); void setCurrentFontFromApi(); + void checkModality_data(); + void checkModality(); private: QQuickAbstractButton *findDialogButton(QQuickDialogButtonBox *box, const QString &buttonText) @@ -601,6 +604,61 @@ void tst_QQuickFontDialogImpl::setCurrentFontFromApi() CLOSE_DIALOG("Ok"); } +void tst_QQuickFontDialogImpl::checkModality_data() +{ + QTest::addColumn<Qt::WindowModality>("modality"); + QTest::addColumn<QFontDialogOptions::FontDialogOption>("options"); + QTest::addColumn<int>("expectedRootWindowChildCount"); + QTest::addColumn<int>("expectedChildWindowClickCount"); + + QTest::newRow("nonModal") << Qt::NonModal << QFontDialogOptions::DontUseNativeDialog << 1 << 1; + QTest::newRow("windowModal") << Qt::WindowModal << QFontDialogOptions::DontUseNativeDialog << 0 << 1; + // Verify application modal case only for the platform which supports multiple windows + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + QTest::newRow("applicationModal") << Qt::ApplicationModal << QFontDialogOptions::DontUseNativeDialog << 0 << 0; +} + +void tst_QQuickFontDialogImpl::checkModality() +{ + QFETCH(Qt::WindowModality, modality); + QFETCH(QFontDialogOptions::FontDialogOption, options); + QFETCH(int, expectedRootWindowChildCount); + QFETCH(int, expectedChildWindowClickCount); + + DialogTestHelper<QQuickFontDialog, QQuickFontDialogImpl> dialogHelper(this, "checkModality.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + dialogHelper.dialog->setModality(modality); + QCOMPARE(dialogHelper.dialog->modality(), modality); + + dialogHelper.dialog->setOptions(options); + QCOMPARE(dialogHelper.dialog->options(), options); + + OPEN_QUICK_DIALOG(); + QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); + + auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>(); + QVERIFY(childWindow); + + // Setting the child window transient parent as root window won't allow it to receive mouse + // events, causing WindowModal case not to be tested. Thus, resetting the transient parent. + if (modality == Qt::WindowModal) + childWindow->setTransientParent(nullptr); + + const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>(); + QVERIFY(rootMouseArea); + QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(rmaMouseSpy.size(), expectedRootWindowChildCount); + + const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>(); + QVERIFY(childMouseArea); + QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); + QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount); +} + QTEST_MAIN(tst_QQuickFontDialogImpl) #include "tst_qquickfontdialogimpl.moc" diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/CMakeLists.txt b/tests/auto/quickdialogs/qquickmessagedialogimpl/CMakeLists.txt index 536a44be6e..333ffc9cea 100644 --- a/tests/auto/quickdialogs/qquickmessagedialogimpl/CMakeLists.txt +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/CMakeLists.txt @@ -18,6 +18,7 @@ qt_internal_add_test(tst_qquickmessagedialogimpl tst_qquickmessagedialogimpl.cpp DEFINES QQC2_IMPORT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols" + QTEST_THROW_ON_FAIL LIBRARIES Qt::CorePrivate Qt::Gui diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/data/checkModality.qml b/tests/auto/quickdialogs/qquickmessagedialogimpl/data/checkModality.qml new file mode 100644 index 0000000000..56e58543d9 --- /dev/null +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/data/checkModality.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: rootWindow + width: 100 + height: 100 + + property alias dialog: dialog + + property alias rootMArea: rootMouseArea + property alias childMArea: childMouseArea + property alias childWindow: childWindow + + MouseArea { + id: rootMouseArea + anchors.fill: parent + } + + ApplicationWindow { + id: childWindow + width: 100 + height: 100 + MouseArea { + id: childMouseArea + anchors.fill: parent + } + } + + MessageDialog { + id: dialog + objectName: "MessageDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp index 70a7e72e7f..a1bb937f4e 100644 --- a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp @@ -2,8 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/qtest.h> + +#ifndef QTEST_THROW_ON_FAIL +# error This test requires QTEST_THROW_ON_FAIL being active. +#endif + +#include <bitset> #include <QtTest/qsignalspy.h> #include <QtQml/qqmlfile.h> +#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuickDialogs2/private/qquickmessagedialog_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickmessagedialogimpl_p.h> #include <QtQuickTest/quicktest.h> @@ -41,6 +48,8 @@ private slots: void changeStandardButtons(); void detailedText(); void resultReflectsLastStandardButtonPressed(); + void checkModality_data(); + void checkModality(); }; // We don't want to fail on warnings until QTBUG-98964 is fixed, @@ -163,6 +172,18 @@ void tst_QQuickMessageDialogImpl::changeStandardButtons() auto buttonBox = dialogHelper.quickDialog->findChild<QQuickDialogButtonBox *>("buttonBox"); QVERIFY(buttonBox); + + std::bitset<QPlatformDialogHelper::NRoles> availableButtonRoles; + auto verifyButtonBox = [&]() { + availableButtonRoles.reset(); + for (int i = 0; i < buttonBox->count(); ++i) { + const auto button = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(i)); + const auto role = QQuickDialogPrivate::buttonRole(button); + QVERIFY(role > QPlatformDialogHelper::InvalidRole && role < QPlatformDialogHelper::NRoles); + availableButtonRoles.set(role); + } + }; + QCOMPARE(buttonBox->count(), 1); QVERIFY2(dialogHelper.dialog->buttons() & QPlatformDialogHelper::StandardButton::Ok, "The QMessageDialogOptionsPrivate constructor assures that the Ok button will be " @@ -178,15 +199,10 @@ void tst_QQuickMessageDialogImpl::changeStandardButtons() dialogHelper.dialog->open(); QCOMPARE(buttonBox->count(), 3); - auto saveButton = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(0)); - QVERIFY(saveButton); - QCOMPARE(saveButton, buttonBox->standardButton(QPlatformDialogHelper::StandardButton::Save)); - auto applyButton = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(1)); - QVERIFY(applyButton); - QCOMPARE(applyButton, buttonBox->standardButton(QPlatformDialogHelper::StandardButton::Cancel)); - auto cancelButton = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(2)); - QVERIFY(cancelButton); - QCOMPARE(cancelButton, buttonBox->standardButton(QPlatformDialogHelper::StandardButton::Apply)); + verifyButtonBox(); + QVERIFY(availableButtonRoles.test(QPlatformDialogHelper::AcceptRole)); + QVERIFY(availableButtonRoles.test(QPlatformDialogHelper::RejectRole)); + QVERIFY(availableButtonRoles.test(QPlatformDialogHelper::ApplyRole)); // change buttons when the dialog is closed dialogHelper.dialog->close(); @@ -198,12 +214,9 @@ void tst_QQuickMessageDialogImpl::changeStandardButtons() dialogHelper.dialog->open(); QCOMPARE(buttonBox->count(), 2); - auto okButton = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(0)); - QVERIFY(okButton); - QCOMPARE(okButton, buttonBox->standardButton(QPlatformDialogHelper::StandardButton::Ok)); - auto closeButton = qobject_cast<QQuickAbstractButton *>(buttonBox->itemAt(1)); - QVERIFY(closeButton); - QCOMPARE(closeButton, buttonBox->standardButton(QPlatformDialogHelper::StandardButton::Close)); + verifyButtonBox(); + QVERIFY(availableButtonRoles.test(QPlatformDialogHelper::AcceptRole)); + QVERIFY(availableButtonRoles.test(QPlatformDialogHelper::RejectRole)); dialogHelper.dialog->close(); } @@ -367,6 +380,64 @@ void tst_QQuickMessageDialogImpl::resultReflectsLastStandardButtonPressed() dialogHelper.dialog->close(); } +void tst_QQuickMessageDialogImpl::checkModality_data() +{ + QTest::addColumn<Qt::WindowModality>("modality"); + QTest::addColumn<int>("expectedRootWindowChildCount"); + QTest::addColumn<int>("expectedChildWindowClickCount"); + + QTest::newRow("nonModal") << Qt::NonModal << 1 << 1; + QTest::newRow("windowModal") << Qt::WindowModal << 0 << 1; + // Verify application modal case only for the platform which supports multiple windows + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + QTest::newRow("applicationModal") << Qt::ApplicationModal << 0 << 0; +} + +void tst_QQuickMessageDialogImpl::checkModality() +{ + QFETCH(Qt::WindowModality, modality); + QFETCH(int, expectedRootWindowChildCount); + QFETCH(int, expectedChildWindowClickCount); + + DialogTestHelper<QQuickMessageDialog, QQuickMessageDialogImpl> dialogHelper(this, "checkModality.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + dialogHelper.dialog->setModality(modality); + QCOMPARE(dialogHelper.dialog->modality(), modality); + + OPEN_QUICK_DIALOG(); + QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished()); + + auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>(); + QVERIFY(childWindow); + + // Setting the child window transient parent as root window won't allow it to receive mouse + // events, causing WindowModal case not to be tested. Thus, resetting the transient parent. + if (modality == Qt::WindowModal) + childWindow->setTransientParent(nullptr); + + // Platforms such as Android have system bars obscuring the content item on + // top and bottom, which can lead to a mouse event being delivered to the + // system bar instead of the content item. To avoid this, safe area margin + // has been considered. + const QMargins safeAreaMargin = dialogHelper.window()->safeAreaMargins(); + const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>(); + QVERIFY(rootMouseArea); + QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, + QPoint(5 + safeAreaMargin.left(), 5 + safeAreaMargin.top())); + QCOMPARE(rmaMouseSpy.size(), expectedRootWindowChildCount); + + const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>(); + QVERIFY(childMouseArea); + QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked); + QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, + QPoint(5 + safeAreaMargin.left(), 5 + safeAreaMargin.top())); + QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount); +} + + QTEST_MAIN(tst_QQuickMessageDialogImpl) #include "tst_qquickmessagedialogimpl.moc" |