diff options
author | Ulf Hermann <[email protected]> | 2023-01-17 12:01:02 +0100 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2023-01-20 20:39:11 +0100 |
commit | aa7f0b9731f4650ad57a4023374e7ddaeb168a7f (patch) | |
tree | 140550f556ad11138a8c54df76e7aa7ed7f7ef98 | |
parent | ec58c0ddb7fe1ebf33c80335ab9435e53fd00274 (diff) |
Controls: Fix module imports of fallback styles
If you specify an explicit fallback, it should take precedence over
any implicit fallback the style might specify. If you don't, the
implicit fallback should take precedence.
If, however, the main style is a custom one, we cannot indirectly import
QtQuick.Controls.Basic through it as the versions may not match. For
that case, add a QtQuick.Controls.IndirectBasic module that does nothing
but import QtQuick.Controls.Basic.
Also, add a test that checks whether the expected fallback was imported
and fix some mistaken test data along the way.
In Qt 6.4 and earlier you could not override the implicit fallback since
the precedence between imports was wrong. After that was fixed in commit
a0290dcc53a14b67c0179c43ce8dc21b43cfc83b the fallback style set from
QtQuick.Controls ("Basic" if not requested explicitly) would always
override the implicit fallback style.
Both behaviors are equally wrong. We need to actually check whether the
fallback is explicitly set or not.
Pick-to: 6.5
Fixes: QTBUG-110247
Change-Id: I1aeee05c1c720bebf8b1b55361fd035a5565fb1b
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Fabian Kosmale <[email protected]>
6 files changed, 170 insertions, 27 deletions
diff --git a/src/quickcontrols/CMakeLists.txt b/src/quickcontrols/CMakeLists.txt index e5d8e68e39..6eff9b10d3 100644 --- a/src/quickcontrols/CMakeLists.txt +++ b/src/quickcontrols/CMakeLists.txt @@ -55,6 +55,13 @@ qt_internal_extend_target(qtquickcontrols2plugin Qt::QuickTemplates2Private ) +qt_internal_add_resource(qtquickcontrols2plugin "indirectBasic" + PREFIX + "/qt-project.org/imports/QtQuick/Controls/IndirectBasic" + FILES + qmldir +) + if(QT_FEATURE_quick_designer) add_subdirectory(designer) endif() diff --git a/src/quickcontrols/qmldir b/src/quickcontrols/qmldir new file mode 100644 index 0000000000..02967c0b0f --- /dev/null +++ b/src/quickcontrols/qmldir @@ -0,0 +1,2 @@ +module QtQuick.Controls.IndirectBasic +import QtQuick.Controls.Basic auto diff --git a/src/quickcontrols/qtquickcontrols2plugin.cpp b/src/quickcontrols/qtquickcontrols2plugin.cpp index 8e30e8428b..9a29401841 100644 --- a/src/quickcontrols/qtquickcontrols2plugin.cpp +++ b/src/quickcontrols/qtquickcontrols2plugin.cpp @@ -38,6 +38,7 @@ private: bool customStyle = false; QString registeredStyleUri; QString registeredFallbackStyleUri; + QString rawFallbackStyleName; }; static const char *qtQuickControlsUri = "QtQuick.Controls"; @@ -78,6 +79,58 @@ QtQuickControls2Plugin::~QtQuickControls2Plugin() // initialization and cleanup, as plugins are not unloaded on macOS. } +/*! + \internal + + If this function is called, it means QtQuick.Controls was imported, + and we're doing runtime style selection. + + For example, where: + \list + \li styleName="Material" + \li rawFallbackStyleName="" + \li fallbackStyleName="Basic" + \li registeredStyleUri="QtQuick.Controls.Material" + \li rawFallbackStyleName is empty => parentModule="QtQuick.Controls.Material" + \li registeredFallbackStyleUri="QtQuick.Controls.Basic" + \endlist + + The following registrations would be made: + + qmlRegisterModuleImport("QtQuick.Controls.Material", "QtQuick.Controls.Basic") + qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Material") + + As another example, where: + \list + \li styleName="Material" + \li rawFallbackStyleName="Fusion" + \li fallbackStyleName="Fusion" + \li registeredStyleUri="QtQuick.Controls.Material" + \li rawFallbackStyleName is not empty => parentModule="QtQuick.Controls" + \li registeredFallbackStyleUri="QtQuick.Controls.Fusion" + \endlist + + The following registrations would be made: + + qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Fusion") + qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Material") + + In this case, the Material style imports a fallback (Basic) via the IMPORTS + section in its CMakeLists.txt, \e and the user specifies a different fallback + using an env var/.conf/C++. We want the user's fallback to take priority, + which means we have to place the user-specified fallback at a more immediate place, + and that place is as an import of QtQuick.Controls itself rather than as an + import of the current style, Material (as we did in the first example). + + If the style to be imported is a custom style and no specific fallback was + selected, we need to indirectly import Basic, but we cannot import Basic through + the custom style since the versions don't match. For that case we have a + "QtQuick.Controls.IndirectBasic" which does nothing but import + QtQuick.Controls.Basic. Instead of QtQuick.Controls.Basic we import that one: + + qmlRegisterModuleImport("QtQuick.Controls", "Some.Custom.Style") + qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.IndirectBasic") +*/ void QtQuickControls2Plugin::registerTypes(const char *uri) { qCDebug(lcQtQuickControls2Plugin) << "registerTypes() called with uri" << uri; @@ -85,34 +138,71 @@ void QtQuickControls2Plugin::registerTypes(const char *uri) // It's OK that the style is resolved more than once; some accessors like name() cause it to be called, for example. QQuickStylePrivate::init(); + // The fallback style that was set via env var/.conf/C++. + rawFallbackStyleName = QQuickStylePrivate::fallbackStyle(); + // The style that was set via env var/.conf/C++, or Basic if none was set. const QString styleName = QQuickStylePrivate::effectiveStyleName(QQuickStyle::name()); - const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(QQuickStylePrivate::fallbackStyle()); + // The effective fallback style: rawFallbackStyleName, or Basic if empty. + const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(rawFallbackStyleName); qCDebug(lcQtQuickControls2Plugin) << "style:" << QQuickStyle::name() << "effective style:" << styleName - << "fallback style:" << QQuickStylePrivate::fallbackStyle() << "effective fallback style:" << fallbackStyleName; + << "fallback style:" << rawFallbackStyleName << "effective fallback style:" << fallbackStyleName; + + customStyle = QQuickStylePrivate::isCustomStyle(); + // The URI of the current style. For built-in styles, the style name is appended to "QtQuick.Controls.". + // For custom styles that are embedded in resources, we need to remove the ":/" prefix. + registeredStyleUri = ::styleUri(); // If the style is Basic, we don't need to register the fallback because the Basic style // provides all controls. Also, if we didn't return early here, we can get an infinite import loop // when the style is set to Basic. if (styleName != fallbackStyleName && styleName != QLatin1String("Basic")) { + // If no specific fallback is given, the fallback is of lower precedence than recursive + // imports of the main style (i.e. IMPORTS in a style's CMakeLists.txt). + // If a specific fallback is given, it is of higher precedence. + + QString parentModule; + QString fallbackModule; + + // The fallback style has to be a built-in style, so it will become "QtQuick.Controls.<fallback>". registeredFallbackStyleUri = ::fallbackStyleUri(); - qCDebug(lcQtQuickControls2Plugin) << "calling qmlRegisterModuleImport() to register fallback style with" - << " uri \"" << qtQuickControlsUri << "\" moduleMajor" << QQmlModuleImportModuleAny - << "import" << registeredFallbackStyleUri << "importMajor" << QQmlModuleImportAuto; + + if (!rawFallbackStyleName.isEmpty()) { + parentModule = qtQuickControlsUri; + fallbackModule = registeredFallbackStyleUri; + } else if (customStyle) { + // Since we don't know the versioning scheme of custom styles, but we want the + // version of QtQuick.Controls to be propagated, we need to do our own indirection. + // QtQuick.Controls.IndirectBasic indirectly imports QtQuick.Controls.Basic + Q_ASSERT(registeredFallbackStyleUri == QLatin1String("QtQuick.Controls.Basic")); + parentModule = qtQuickControlsUri; + fallbackModule = QLatin1String("QtQuick.Controls.IndirectBasic"); + } else { + parentModule = registeredStyleUri; + fallbackModule = registeredFallbackStyleUri; + } + + qCDebug(lcQtQuickControls2Plugin) + << "calling qmlRegisterModuleImport() to register fallback style with" + << " uri \"" << parentModule << "\" moduleMajor" << QQmlModuleImportModuleAny + << "import" << fallbackModule << "importMajor" << QQmlModuleImportAuto; + // Whenever parentModule is imported, registeredFallbackStyleUri will be imported too. // The fallback style must be a built-in style, so we match the version number. - qmlRegisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, registeredFallbackStyleUri.toUtf8().constData(), - QQmlModuleImportAuto, QQmlModuleImportAuto); + qmlRegisterModuleImport(parentModule.toUtf8().constData(), QQmlModuleImportModuleAny, + fallbackModule.toUtf8().constData(), + QQmlModuleImportAuto, QQmlModuleImportAuto); } // If the user imports QtQuick.Controls 2.15, and they're using the Material style, we should import version 2.15. // However, if they import QtQuick.Controls 2.15, but are using a custom style, we want to use the latest version // number of their style. - customStyle = QQuickStylePrivate::isCustomStyle(); - registeredStyleUri = ::styleUri(); - const int importMajor = !customStyle ? QQmlModuleImportAuto : QQmlModuleImportLatest; - qCDebug(lcQtQuickControls2Plugin).nospace() << "calling qmlRegisterModuleImport() to register primary style with" - << " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor - << " import " << registeredStyleUri << " importMajor " << importMajor; - qmlRegisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, registeredStyleUri.toUtf8().constData(), importMajor); + const int importMajor = customStyle ? QQmlModuleImportLatest : QQmlModuleImportAuto; + qCDebug(lcQtQuickControls2Plugin).nospace() + << "calling qmlRegisterModuleImport() to register primary style with" + << " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor + << " import " << registeredStyleUri << " importMajor " << importMajor; + // When QtQuick.Controls is imported, the selected style will be imported too. + qmlRegisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, + registeredStyleUri.toUtf8().constData(), importMajor); if (customStyle) QFileSelectorPrivate::addStatics(QStringList() << styleName); @@ -122,15 +212,41 @@ void QtQuickControls2Plugin::unregisterTypes() { qCDebug(lcQtQuickControls2Plugin) << "unregisterTypes() called"; + const int importMajor = customStyle ? QQmlModuleImportLatest : QQmlModuleImportAuto; + qCDebug(lcQtQuickControls2Plugin).nospace() + << "calling qmlUnregisterModuleImport() to unregister primary style with" + << " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor + << " import " << registeredStyleUri << " importMajor " << importMajor; + qmlUnregisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, + registeredStyleUri.toUtf8().constData(), importMajor); + if (!registeredFallbackStyleUri.isEmpty()) { - // We registered a fallback style, so now we need to unregister it. - qmlUnregisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, registeredFallbackStyleUri.toUtf8().constData(), - QQmlModuleImportAuto, QQmlModuleImportAuto); + QString parentModule; + QString fallbackModule; + + if (!rawFallbackStyleName.isEmpty()) { + parentModule = qtQuickControlsUri; + fallbackModule = registeredFallbackStyleUri; + rawFallbackStyleName.clear(); + } else if (customStyle) { + parentModule = qtQuickControlsUri; + fallbackModule = QLatin1String("QtQuick.Controls.IndirectBasic"); + } else { + parentModule = registeredStyleUri; + fallbackModule = registeredFallbackStyleUri; + } + + qCDebug(lcQtQuickControls2Plugin) + << "calling qmlUnregisterModuleImport() to unregister fallback style with" + << " uri \"" << parentModule << "\" moduleMajor" << QQmlModuleImportModuleAny + << "import" << fallbackModule << "importMajor" << QQmlModuleImportAuto; + qmlUnregisterModuleImport(parentModule.toUtf8().constData(), QQmlModuleImportModuleAny, + fallbackModule.toUtf8().constData(), + QQmlModuleImportAuto, QQmlModuleImportAuto); + registeredFallbackStyleUri.clear(); } - const int importMajor = !customStyle ? QQmlModuleImportAuto : QQmlModuleImportLatest; - qmlUnregisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, registeredStyleUri.toUtf8().constData(), importMajor); customStyle = false; registeredStyleUri.clear(); } diff --git a/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/Action.qml b/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/Action.qml new file mode 100644 index 0000000000..f0a1459891 --- /dev/null +++ b/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/Action.qml @@ -0,0 +1,4 @@ +import QtQuick.Templates as T +T.Action { + objectName: "StyleThatImportsFusion" +} diff --git a/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/qmldir b/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/qmldir new file mode 100644 index 0000000000..824792a4e5 --- /dev/null +++ b/tests/auto/quickcontrols/styleimports/data/styles/StyleThatImportsFusion/qmldir @@ -0,0 +1,3 @@ +module StyleThatImportsFusion +Action 6.0 Action.qml +import QtQuick.Controls.Fusion auto diff --git a/tests/auto/quickcontrols/styleimports/tst_styleimports.cpp b/tests/auto/quickcontrols/styleimports/tst_styleimports.cpp index dbc56e1022..175541296a 100644 --- a/tests/auto/quickcontrols/styleimports/tst_styleimports.cpp +++ b/tests/auto/quickcontrols/styleimports/tst_styleimports.cpp @@ -69,44 +69,52 @@ void tst_StyleImports::select_data() QTest::newRow("control=Action,style=fs,fallback=empty") << "Action.qml" << "FileSystemStyle" << "" << "FileSystemStyle"; QTest::newRow("control=Action,style=qrc,fallback=empty") << "Action.qml" << "ResourceStyle" << "" << "Basic"; QTest::newRow("control=Action,style=nosuch,fallback=empty") << "Action.qml" << "NoSuchStyle" << "" << "Basic"; + QTest::newRow("control=Action,style=import,fallback=empty") << "Action.qml" << "StyleThatImportsFusion" << "" << "StyleThatImportsFusion"; QTest::newRow("control=Action,style=basic,fallback=mat") << "Action.qml" << "Basic" << "Material" << ""; QTest::newRow("control=Action,style=fs,fallback=mat") << "Action.qml" << "FileSystemStyle" << "Material" << "FileSystemStyle"; QTest::newRow("control=Action,style=qrc,fallback=mat") << "Action.qml" << "ResourceStyle" << "Material" << "Basic"; QTest::newRow("control=Action,style=nosuch,fallback=mat") << "Action.qml" << "NoSuchStyle" << "Material" << "Basic"; + QTest::newRow("control=Action,style=import,fallback=mat") << "Action.qml" << "StyleThatImportsFusion" << "Material" << "StyleThatImportsFusion"; // Amongst the styles we're testing here, ScrollView.qml only exists in the Basic style. QTest::newRow("control=ScrollView,style=basic,fallback=empty") << "ScrollView.qml" << "Basic" << "" << "Basic"; QTest::newRow("control=ScrollView,style=fs,fallback=empty") << "ScrollView.qml" << "FileSystemStyle" << "" << "Basic"; QTest::newRow("control=ScrollView,style=qrc,fallback=empty") << "ScrollView.qml" << "ResourceStyle" << "" << "Basic"; QTest::newRow("control=ScrollView,style=nosuch,fallback=empty") << "ScrollView.qml" << "NoSuchStyle" << "" << "Basic"; + QTest::newRow("control=ScrollView,style=import,fallback=empty") << "ScrollView.qml" << "StyleThatImportsFusion" << "" << "Fusion"; QTest::newRow("control=ScrollView,style=basic,fallback=mat") << "ScrollView.qml" << "Basic" << "Material" << "Basic"; - QTest::newRow("control=ScrollView,style=fs,fallback=mat") << "ScrollView.qml" << "FileSystemStyle" << "Material" << "Basic"; - QTest::newRow("control=ScrollView,style=qrc,fallback=mat") << "ScrollView.qml" << "ResourceStyle" << "Material" << "Basic"; + QTest::newRow("control=ScrollView,style=fs,fallback=mat") << "ScrollView.qml" << "FileSystemStyle" << "Material" << "Material"; + QTest::newRow("control=ScrollView,style=qrc,fallback=mat") << "ScrollView.qml" << "ResourceStyle" << "Material" << "Material"; QTest::newRow("control=ScrollView,style=nosuch,fallback=mat") << "ScrollView.qml" << "NoSuchStyle" << "Material" << "Basic"; + QTest::newRow("control=ScrollView,style=import,fallback=mat") << "ScrollView.qml" << "StyleThatImportsFusion" << "Material" << "Material"; // Label.qml exists in the FileSystemStyle, Basic and Material styles. QTest::newRow("control=Label,style=basic,fallback=empty") << "Label.qml" << "Basic" << "" << "Basic"; QTest::newRow("control=Label,style=fs,fallback=empty") << "Label.qml" << "FileSystemStyle" << "" << "FileSystemStyle"; QTest::newRow("control=Label,style=qrc,fallback=empty") << "Label.qml" << "ResourceStyle" << "" << "Basic"; QTest::newRow("control=Label,style=nosuch,fallback=empty") << "Label.qml" << "NoSuchStyle" << "" << "Basic"; + QTest::newRow("control=Label,style=import,fallback=empty") << "Label.qml" << "StyleThatImportsFusion" << "" << "Fusion"; QTest::newRow("control=Label,style=basic,fallback=mat") << "Label.qml" << "Basic" << "Material" << "Basic"; QTest::newRow("control=Label,style=fs,fallback=mat") << "Label.qml" << "FileSystemStyle" << "Material" << "FileSystemStyle"; - QTest::newRow("control=Label,style=qrc,fallback=mat") << "Label.qml" << "ResourceStyle" << "Material" << "Basic"; + QTest::newRow("control=Label,style=qrc,fallback=mat") << "Label.qml" << "ResourceStyle" << "Material" << "Material"; QTest::newRow("control=Label,style=nosuch,fallback=mat") << "Label.qml" << "NoSuchStyle" << "Material" << "Basic"; + QTest::newRow("control=Label,style=import,fallback=mat") << "Label.qml" << "StyleThatImportsFusion" << "Material" << "Material"; // Button.qml exists in all styles including the fs and qrc styles QTest::newRow("control=Button,style=basic,fallback=empty") << "Button.qml" << "Basic" << "" << "Basic"; QTest::newRow("control=Button,style=fs,fallback=empty") << "Button.qml" << "FileSystemStyle" << "" << "FileSystemStyle"; QTest::newRow("control=Button,style=qrc,fallback=empty") << "Button.qml" << "ResourceStyle" << "" << "ResourceStyle"; QTest::newRow("control=Button,style=nosuch,fallback=empty") << "Button.qml" << "NoSuchStyle" << "" << "Basic"; + QTest::newRow("control=Button,style=import,fallback=empty") << "Button.qml" << "StyleThatImportsFusion" << "" << "Fusion"; QTest::newRow("control=Button,style=basic,fallback=mat") << "Button.qml" << "Basic" << "Material" << "Basic"; QTest::newRow("control=Button,style=fs,fallback=mat") << "Button.qml" << "FileSystemStyle" << "Material" << "FileSystemStyle"; QTest::newRow("control=Button,style=qrc,fallback=mat") << "Button.qml" << "ResourceStyle" << "Material" << "ResourceStyle"; QTest::newRow("control=Button,style=nosuch,fallback=mat") << "Button.qml" << "NoSuchStyle" << "Material" << "Basic"; + QTest::newRow("control=Button,style=import,fallback=mat") << "Button.qml" << "StyleThatImportsFusion" << "Material" << "Material"; } void tst_StyleImports::select() @@ -144,14 +152,17 @@ void tst_StyleImports::select() QVERIFY2(!object.isNull(), qPrintable(component.errorString())); - // TODO: test built-in styles below too - // We can't check for the attached style object since that API is in a plugin, - // and it's not possible to use e.g. the baseUrl of the QQmlContext - // nor the metaObject to test it either. - if (!QQuickStylePrivate::builtInStyles().contains(expected)) { // We're expecting a custom style. QCOMPARE(object->objectName(), expected); + } else { + const QUrl expectedUrl( + QLatin1String("qrc:///qt-project.org/imports/QtQuick/Controls/%1/%2") + .arg(expected, file)); + QQmlComponent c2(&engine, expectedUrl); + QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + QScopedPointer<QObject> o2(c2.create()); + QCOMPARE(object->metaObject(), o2->metaObject()); } } |