From 823026646c62c7e3b42be1549fcb61debb468e60 Mon Sep 17 00:00:00 2001 From: Oliver Eftevaag Date: Tue, 28 May 2024 00:01:20 +0200 Subject: Replace AA_DontUsePopupWindows with Popup::popupType The application attribute approach has some weaknesses which makes it less suitable, than simply having a property which can be set per instance of Popup. In addition, given just how many of our own tests that are failing, when changing the default behavior of popup, it`s a much safer approach to introduce this feature as an opt-in, in the beginning. We can instead potentially modify the behavior of PopupType::Default in the future, when we feel that it's less risky to do so. To give more context to why the AA approach is bad: - The AA is too grandiose of a solution, which adds an arbitrary limitation of not allowing mixing of in-scene popups and popup windows. This also affects 3rd party libraries. - It causes inconveniences when styling the various popup types. Dialog, for instance, has a title property, which should be shown in the window title, and not inside the popup window. - It also makes it less flexible for potential future changes. We've learned that this change is riskier, than initially expected, since the qobject and visual hierarchy of popup objects are different when using popup windows, and tests that simulate events, will need to send the events to a different window. The introduction of the PopupType::Default value, allows us to, for instance, add a property later in ApplicationWindow, or somewhere else, to change the behavior of PopupType::Default. Meaning that we can still add a more grandiose API, that affects the behavior of multiple popup instances, if we think it's too cumbersome for developers to add `popupType: Popup.Window` to all popup instances. Task-number: QTBUG-121363 Change-Id: I544da820261607621a9b9ad5c4c9679e676e44a0 Reviewed-by: Shawn Rutledge --- src/quicktemplates/qquickmenu.cpp | 2 +- src/quicktemplates/qquickpopup.cpp | 61 ++++++++++++++++++---- src/quicktemplates/qquickpopup_p.h | 14 +++++ src/quicktemplates/qquickpopup_p_p.h | 1 + .../qquickmenu/data/nativeDynamicSubmenus.qml | 2 + .../qquickmenu/data/nativeEmptyMenu.qml | 1 + .../qquickmenu/data/nativeMenuSeparator.qml | 2 + .../qquickmenu/data/nativeMixedItems.qml | 3 ++ .../quickcontrols/qquickmenu/data/nativeStatic.qml | 2 + .../quickcontrols/qquickmenu/tst_qquickmenu.cpp | 8 ++- tests/auto/quickcontrols/qquickpopup/BLACKLIST | 3 ++ .../qquickpopup/data/popupCenterIn.qml | 1 + .../data/popupWithButtonInBackground.qml | 1 + .../qquickpopup/data/reparentingPopup.qml | 1 + .../quickcontrols/qquickpopup/data/simplepopup.qml | 2 +- .../quickcontrols/qquickpopup/tst_qquickpopup.cpp | 36 +++++++++++-- tests/manual/quickcontrols/menus/Main.qml | 3 ++ 17 files changed, 123 insertions(+), 20 deletions(-) diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index 6cf039a710..d5f1a72f5b 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -309,7 +309,7 @@ bool QQuickMenuPrivate::useNativeMenu() const QQuickMenu *root = rootMenu(); if (auto menuBar = QQuickMenuPrivate::get(root)->menuBar.get()) return QQuickMenuBarPrivate::get(menuBar)->useNativeMenu(q_func()); - return !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuWindows); + return m_popupType == QQuickPopup::Native; } QPlatformMenu *QQuickMenuPrivate::nativeHandle() diff --git a/src/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index 33013caaf4..29e550ca44 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -123,16 +123,20 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") \section1 Popup Windows Popup can behave in two different ways. Depending on the platform, - and whether the \l Qt::AA_DontUsePopupWindows application attribute is set or not. + and what the value of the \l popupType property is. - By default, on desktop systems, the popup will create a special popup window, + Showing a popup as a separate top-level window is currently under tech-preview, + and therefore disabled by default. You can enable popup windows explicitly by + setting \l popupType to \c Popup.Window. + + This will cause a separate popup window to be created, which contains the \l contentItem and \l background items. \section1 Popup Items - If the \l Qt::AA_DontUsePopupWindows application attribute is set, + If the \l popupType property is set to \c Item, or the platform doesn't support multiple windows, - the popup will instead create a special item, which gets parented to the + the popup will instead create a item, which gets parented to the \l{Overlay::overlay}{overlay} in the scene of the existing window. In order to ensure that a popup is displayed above other items in the @@ -897,11 +901,9 @@ QPalette QQuickPopupPrivate::defaultPalette() const bool QQuickPopupPrivate::usePopupWindow() const { - // TODO: fix later - return false; - // return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows) - // && !QCoreApplication::testAttribute(Qt::AA_DontUsePopupWindows) - // && popupWindowType() != Qt::Widget; // We use Qt::Widget here, to allow some popup derived types, like drawer, to opt out of using separate windows. + return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows) + && m_popupType == QQuickPopup::PopupType::Window + && popupWindowType() != Qt::Widget; // We use Qt::Widget here, to allow some popup derived types, like drawer, to opt out of using separate windows. } void QQuickPopupPrivate::adjustPopupItemParentAndWindow() @@ -2727,6 +2729,47 @@ void QQuickPopup::resetBottomInset() d->popupItem->resetBottomInset(); } + +/*! + \qmlproperty enumeration QtQuick.Controls::Popup::popupType + \since 6.8 + \preliminary + + This property determines the type of popup that will be created. + + Available options: + \value Default Let Qt decide the optimal popup type, depending on the system. This is the default value. + While \c Popup.Window is in tech-preview, \c Popup.Default will be equal to \c Popup.Item. + But this is likely to change in a future release. + \value Item The popup will be embedded into the \l{Popup Items}{same scene as the parent}, without the use of a separate window. + \value Window The popup will be presented in a \l {Popup Windows}{separate window}. If the platform doesn't support multiple windows, + \c Popup.Item will be used instead. This option is currently under tech-preview. + \value Native The popup will be native to the platform. If the platform doesn't support native popups, + \c Popup.Window will be used instead. This option is currently under tech-preview. + \sa {Popup Windows}, {Popup Items} +*/ +QQuickPopup::PopupType QQuickPopup::popupType() const +{ + Q_D(const QQuickPopup); + return d->m_popupType; +} + +void QQuickPopup::setPopupType(PopupType popupType) +{ + Q_D(QQuickPopup); + if (d->m_popupType == popupType) + return; + + d->m_popupType = popupType; + + emit popupTypeChanged(); +} + +void QQuickPopup::resetPopupType() +{ + setPopupType(PopupType::Default); +} + /*! \since QtQuick.Controls 2.3 (Qt 5.10) \qmlproperty palette QtQuick.Controls::Popup::palette diff --git a/src/quicktemplates/qquickpopup_p.h b/src/quicktemplates/qquickpopup_p.h index f81ec5027d..56287d4a03 100644 --- a/src/quicktemplates/qquickpopup_p.h +++ b/src/quicktemplates/qquickpopup_p.h @@ -101,6 +101,7 @@ class Q_QUICKTEMPLATES2_EXPORT QQuickPopup : public QObject, public QQmlParserSt Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(PopupType popupType READ popupType WRITE setPopupType RESET resetPopupType NOTIFY popupTypeChanged FINAL REVISION(6, 8)) Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") Q_CLASSINFO("DefaultProperty", "contentData") QML_NAMED_ELEMENT(Popup) @@ -311,6 +312,18 @@ public: void setBottomInset(qreal inset); void resetBottomInset(); + enum PopupType { + Default, + Item, + Window, + Native + }; + Q_ENUM(PopupType) + + PopupType popupType() const; + void setPopupType(PopupType); + void resetPopupType(); + public Q_SLOTS: void open(); void close(); @@ -378,6 +391,7 @@ Q_SIGNALS: Q_REVISION(2, 5) void leftInsetChanged(); Q_REVISION(2, 5) void rightInsetChanged(); Q_REVISION(2, 5) void bottomInsetChanged(); + Q_REVISION(6, 8) void popupTypeChanged(); protected: QQuickPopup(QQuickPopupPrivate &dd, QObject *parent); diff --git a/src/quicktemplates/qquickpopup_p_p.h b/src/quicktemplates/qquickpopup_p_p.h index 1c6d5283f5..ff364489fb 100644 --- a/src/quicktemplates/qquickpopup_p_p.h +++ b/src/quicktemplates/qquickpopup_p_p.h @@ -193,6 +193,7 @@ public: qreal prevOpacity = 0; qreal prevScale = 0; QString m_title; + QQuickPopup::PopupType m_popupType = QQuickPopup::Default; friend class QQuickPopupTransitionManager; }; diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml index c0326fb511..951cb7cb6c 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml @@ -37,6 +37,7 @@ ApplicationWindow { Menu { id: subMenu objectName: title + popupType: Popup.Native Action { text: subMenu.objectName + "Action1" @@ -47,5 +48,6 @@ ApplicationWindow { Menu { id: contextMenu objectName: "menu" + popupType: Popup.Native } } diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml index f1d4f33625..0ae2c5dc66 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml @@ -46,5 +46,6 @@ ApplicationWindow { Menu { id: contextMenu objectName: "menu" + popupType: Popup.Native } } diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml index a32d94bb6b..54195af349 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml @@ -13,6 +13,7 @@ ApplicationWindow { Menu { id: contextMenu objectName: "menu" + popupType: Popup.Native Action { objectName: text @@ -24,6 +25,7 @@ ApplicationWindow { Menu { id: subMenu objectName: "subMenu" + popupType: Popup.Native Action { objectName: text diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml index 3e6e851bb3..119d8debec 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml @@ -30,12 +30,14 @@ ApplicationWindow { Menu { objectName: title + popupType: contextMenu.popupType } } Menu { id: contextMenu objectName: "menu" + popupType: contextMenu.popupType Action { objectName: text @@ -51,6 +53,7 @@ ApplicationWindow { id: subMenu title: "subMenu" objectName: title + popupType: contextMenu.popupType Action { objectName: text diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml index f9ff438811..32ba1f1829 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml @@ -14,6 +14,7 @@ ApplicationWindow { Menu { id: contextMenu objectName: "menu" + popupType: Popup.Native Action { objectName: text @@ -34,6 +35,7 @@ ApplicationWindow { id: subMenu title: "subMenu" objectName: title + popupType: Popup.Native // TODO: remove me when the defaults are true Action { diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index 232b8e935e..e652168002 100644 --- a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -2191,7 +2191,6 @@ void tst_QQuickMenu::invalidUrlInImgTag() void tst_QQuickMenu::nativeStatic() { - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); QQuickControlsApplicationHelper helper(this, QLatin1String("nativeStatic.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; @@ -2235,7 +2234,6 @@ void tst_QQuickMenu::nativeStatic() void tst_QQuickMenu::nativeDynamicActions() { - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); QQuickControlsApplicationHelper helper(this, QLatin1String("nativeEmptyMenu.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; @@ -2295,7 +2293,6 @@ void tst_QQuickMenu::nativeDynamicActions() void tst_QQuickMenu::nativeDynamicSubmenus() { - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); QQuickControlsApplicationHelper helper(this, QLatin1String("nativeDynamicSubmenus.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; @@ -2403,7 +2400,6 @@ void tst_QQuickMenu::nativeDynamicSubmenus() void tst_QQuickMenu::nativeMenuSeparator() { - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); QQuickControlsApplicationHelper helper(this, QLatin1String("nativeMenuSeparator.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; @@ -2443,6 +2439,8 @@ void tst_QQuickMenu::nativeMenuSeparator() void tst_QQuickMenu::dontUseNativeMenuWindowsChanges() { + QSKIP("QTBUG-125967 This test will need to be fixed, by using popupType: Popup.Native instead of AA_DontUseNativeMenuWindows."); + if (QSysInfo::productType() == QLatin1String("b2qt")) QSKIP("b2qt doesn't support native menus"); @@ -2630,7 +2628,6 @@ void tst_QQuickMenu::textPadding() // Check that you can set implicitTextPadding on each MenuItem, and that // textPadding will end up as the maximum implicitTextPadding among all the // MenuItems in the same Menu. - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickControlsApplicationHelper helper(this, QLatin1String("nativeMixedItems.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -2640,6 +2637,7 @@ void tst_QQuickMenu::textPadding() QQuickMenu *contextMenu = window->property("contextMenu").value(); QVERIFY(contextMenu); + contextMenu->setPopupType(QQuickPopup::Item); contextMenu->setVisible(true); diff --git a/tests/auto/quickcontrols/qquickpopup/BLACKLIST b/tests/auto/quickcontrols/qquickpopup/BLACKLIST index bd2185328f..9a05aad150 100644 --- a/tests/auto/quickcontrols/qquickpopup/BLACKLIST +++ b/tests/auto/quickcontrols/qquickpopup/BLACKLIST @@ -23,3 +23,6 @@ opensuse-leap [cursorShape] opensuse-leap + +[popupWindowFocus] +* # QTBUG-121363 diff --git a/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml b/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml index 969cda091a..6a67af30fc 100644 --- a/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml +++ b/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml @@ -13,6 +13,7 @@ Window { Popup { id: simplepopup anchors.centerIn: parent + popupType: Popup.Window Text { text: "I am a centered popup" diff --git a/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml b/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml index d54c3a5d88..b265a80df7 100644 --- a/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml +++ b/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml @@ -16,6 +16,7 @@ Window { Popup { id: simplepopup + popupType: Popup.Window x: 50 y: 50 diff --git a/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml b/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml index d92e6893dd..e747704e4b 100644 --- a/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml +++ b/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml @@ -16,6 +16,7 @@ Window { Popup { id: simplepopup visible: true + popupType: Popup.Window x: 10 y: 10 width: 200 diff --git a/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml b/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml index ca6daefcc6..60371d20d4 100644 --- a/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml +++ b/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml @@ -12,7 +12,7 @@ Window { Popup { id: simplepopup - + popupType: Popup.Window x: 50 y: 50 diff --git a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index f5a4212d91..a10c0aa53a 100644 --- a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -121,6 +121,7 @@ private slots: void popupWindowClosesOnParentWindowClosing(); void popupWindowChangingParent(); void popupWindowFocus(); + void popupWindowChangeFromWindowToInScene(); private: QScopedPointer touchScreen = QScopedPointer(QTest::createTouchDevice()); @@ -2394,7 +2395,6 @@ void tst_QQuickPopup::noDimmer() void tst_QQuickPopup::popupWindowPositioning() { - QSKIP("Enable test later"); QQuickApplicationHelper helper(this, "simplepopup.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -2466,7 +2466,6 @@ void tst_QQuickPopup::popupWindowAnchorsCenterIn_data() void tst_QQuickPopup::popupWindowAnchorsCenterIn() { - QSKIP("Enable test later"); QFETCH(bool, centerInParent); QQuickApplicationHelper helper(this, "popupCenterIn.qml"); @@ -2587,7 +2586,6 @@ void tst_QQuickPopup::popupWindowClosesOnParentWindowClosing() void tst_QQuickPopup::popupWindowChangingParent() { - QSKIP("Enable test later"); QQuickApplicationHelper helper(this, "reparentingPopup.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -2642,7 +2640,6 @@ void tst_QQuickPopup::popupWindowChangingParent() void tst_QQuickPopup::popupWindowFocus() { - QSKIP("Enable this test once the PopupType property has been merged"); QQuickApplicationHelper helper(this, "popupWindowFocusHandling.qml"); QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; @@ -2679,6 +2676,37 @@ void tst_QQuickPopup::popupWindowFocus() QVERIFY(QGuiApplication::focusObject() == textField1); } +void tst_QQuickPopup::popupWindowChangeFromWindowToInScene() +{ + QQuickApplicationHelper helper(this, "simplepopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + auto *popup = window->contentItem()->findChild(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + const QWindow *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + popup->close(); + QTRY_VERIFY(!popupWindow->isVisible()); + QVERIFY(!popup->isVisible()); + popup->setPopupType(QQuickPopup::Item); + popup->open(); + QTRY_VERIFY(popup->isVisible()); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + QVERIFY(overlay); + QVERIFY(overlay->childItems().contains(popup->popupItem())); + popup->close(); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" diff --git a/tests/manual/quickcontrols/menus/Main.qml b/tests/manual/quickcontrols/menus/Main.qml index ac04ca5364..a75f2afd6c 100644 --- a/tests/manual/quickcontrols/menus/Main.qml +++ b/tests/manual/quickcontrols/menus/Main.qml @@ -35,6 +35,7 @@ ApplicationWindow { id: fileMenu objectName: "file" title: qsTr("&File") + popupType: popupTypeCombo.currentIndex ContextAction { text: qsTr("&New...") } ContextMenuItem { text: "menuItem" } ContextAction { text: qsTr("&Open...") } @@ -60,6 +61,7 @@ ApplicationWindow { id: editMenu objectName: "edit" title: qsTr("&Edit") + popupType: popupTypeCombo.currentIndex ContextAction { id: cutAction text: qsTr("Cut") @@ -104,6 +106,7 @@ ApplicationWindow { id: menuBarItemMenu objectName: "MenuBarItem" title: "MenuBarItem" + popupType: popupTypeCombo.currentIndex ContextAction { text: qsTr("Action") } Action { text: qsTr("Remove menu") -- cgit v1.2.3