| 1 | // Copyright (C) 2017 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <QtCore/private/qfileselector_p.h> |
| 5 | #include <QtCore/qloggingcategory.h> |
| 6 | #include <QtQml/qqmlengine.h> |
| 7 | #include <QtQml/qqmlextensionplugin.h> |
| 8 | #include <QtQuickTemplates2/private/qquicktheme_p_p.h> |
| 9 | #include <QtQuickControls2/private/qquickstyle_p.h> |
| 10 | #include <QtQuickControls2/private/qquickstyleplugin_p.h> |
| 11 | #include <QtQuickControls2/qquickstyle.h> |
| 12 | #include <QtQuickControls2/qtquickcontrols2global.h> |
| 13 | |
| 14 | QT_BEGIN_NAMESPACE |
| 15 | |
| 16 | Q_GHS_KEEP_REFERENCE(qml_register_types_QtQuick_Controls); |
| 17 | |
| 18 | Q_LOGGING_CATEGORY(lcQtQuickControls2Plugin, "qt.quick.controls.qtquickcontrols2plugin" ) |
| 19 | |
| 20 | class QtQuickControls2Plugin : public QQmlExtensionPlugin |
| 21 | { |
| 22 | Q_OBJECT |
| 23 | Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) |
| 24 | |
| 25 | public: |
| 26 | QtQuickControls2Plugin(QObject *parent = nullptr); |
| 27 | ~QtQuickControls2Plugin(); |
| 28 | |
| 29 | void registerTypes(const char *uri) override; |
| 30 | void unregisterTypes() override; |
| 31 | |
| 32 | private: |
| 33 | // We store these because the style plugins can be unregistered before |
| 34 | // QtQuickControls2Plugin, and since QQuickStylePlugin calls QQuickStylePrivate::reset(), |
| 35 | // the style information can be lost when it comes time to call qmlUnregisterModuleImport(). |
| 36 | // It also avoids unnecessarily resolving the style after resetting it just to get the style |
| 37 | // name in unregisterTypes(). |
| 38 | bool customStyle = false; |
| 39 | QString registeredStyleUri; |
| 40 | QString registeredFallbackStyleUri; |
| 41 | QString rawFallbackStyleName; |
| 42 | }; |
| 43 | |
| 44 | static const char *qtQuickControlsUri = "QtQuick.Controls" ; |
| 45 | |
| 46 | QString styleUri() |
| 47 | { |
| 48 | const QString style = QQuickStyle::name(); |
| 49 | if (!QQuickStylePrivate::isCustomStyle()) { |
| 50 | // The style set is a built-in style. |
| 51 | const QString styleName = QQuickStylePrivate::effectiveStyleName(styleName: style); |
| 52 | return QString::fromLatin1(ba: "QtQuick.Controls.%1" ).arg(a: styleName); |
| 53 | } |
| 54 | |
| 55 | // This is a custom style, so just use the name as the import uri. |
| 56 | QString styleName = style; |
| 57 | if (styleName.startsWith(s: QLatin1String(":/" ))) |
| 58 | styleName.remove(i: 0, len: 2); |
| 59 | return styleName; |
| 60 | } |
| 61 | |
| 62 | QString fallbackStyleUri() |
| 63 | { |
| 64 | // The fallback style must be a built-in style, so we don't need to check for custom styles here. |
| 65 | const QString fallbackStyle = QQuickStylePrivate::fallbackStyle(); |
| 66 | const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(styleName: fallbackStyle); |
| 67 | return QString::fromLatin1(ba: "QtQuick.Controls.%1" ).arg(a: fallbackStyleName); |
| 68 | } |
| 69 | |
| 70 | QtQuickControls2Plugin::QtQuickControls2Plugin(QObject *parent) : QQmlExtensionPlugin(parent) |
| 71 | { |
| 72 | volatile auto registration = &qml_register_types_QtQuick_Controls; |
| 73 | Q_UNUSED(registration); |
| 74 | } |
| 75 | |
| 76 | QtQuickControls2Plugin::~QtQuickControls2Plugin() |
| 77 | { |
| 78 | // Intentionally empty: we use register/unregisterTypes() to do |
| 79 | // initialization and cleanup, as plugins are not unloaded on macOS. |
| 80 | } |
| 81 | |
| 82 | /*! |
| 83 | \internal |
| 84 | |
| 85 | If this function is called, it means QtQuick.Controls was imported, |
| 86 | and we're doing runtime style selection. |
| 87 | |
| 88 | For example, where: |
| 89 | \list |
| 90 | \li styleName="Material" |
| 91 | \li rawFallbackStyleName="" |
| 92 | \li fallbackStyleName="Basic" |
| 93 | \li registeredStyleUri="QtQuick.Controls.Material" |
| 94 | \li rawFallbackStyleName is empty => parentModule="QtQuick.Controls.Material" |
| 95 | \li registeredFallbackStyleUri="QtQuick.Controls.Basic" |
| 96 | \endlist |
| 97 | |
| 98 | The following registrations would be made: |
| 99 | |
| 100 | qmlRegisterModuleImport("QtQuick.Controls.Material", "QtQuick.Controls.Basic") |
| 101 | qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Material") |
| 102 | |
| 103 | As another example, where: |
| 104 | \list |
| 105 | \li styleName="Material" |
| 106 | \li rawFallbackStyleName="Fusion" |
| 107 | \li fallbackStyleName="Fusion" |
| 108 | \li registeredStyleUri="QtQuick.Controls.Material" |
| 109 | \li rawFallbackStyleName is not empty => parentModule="QtQuick.Controls" |
| 110 | \li registeredFallbackStyleUri="QtQuick.Controls.Fusion" |
| 111 | \endlist |
| 112 | |
| 113 | The following registrations would be made: |
| 114 | |
| 115 | qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Fusion") |
| 116 | qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.Material") |
| 117 | |
| 118 | In this case, the Material style imports a fallback (Basic) via the IMPORTS |
| 119 | section in its CMakeLists.txt, \e and the user specifies a different fallback |
| 120 | using an env var/.conf/C++. We want the user's fallback to take priority, |
| 121 | which means we have to place the user-specified fallback at a more immediate place, |
| 122 | and that place is as an import of QtQuick.Controls itself rather than as an |
| 123 | import of the current style, Material (as we did in the first example). |
| 124 | |
| 125 | If the style to be imported is a custom style and no specific fallback was |
| 126 | selected, we need to indirectly import Basic, but we cannot import Basic through |
| 127 | the custom style since the versions don't match. For that case we have a |
| 128 | "QtQuick.Controls.IndirectBasic" which does nothing but import |
| 129 | QtQuick.Controls.Basic. Instead of QtQuick.Controls.Basic we import that one: |
| 130 | |
| 131 | qmlRegisterModuleImport("QtQuick.Controls", "Some.Custom.Style") |
| 132 | qmlRegisterModuleImport("QtQuick.Controls", "QtQuick.Controls.IndirectBasic") |
| 133 | */ |
| 134 | void QtQuickControls2Plugin::registerTypes(const char *uri) |
| 135 | { |
| 136 | qCDebug(lcQtQuickControls2Plugin) << "registerTypes() called with uri" << uri; |
| 137 | |
| 138 | // It's OK that the style is resolved more than once; some accessors like name() cause it to be called, for example. |
| 139 | QQuickStylePrivate::init(); |
| 140 | |
| 141 | // The fallback style that was set via env var/.conf/C++. |
| 142 | rawFallbackStyleName = QQuickStylePrivate::fallbackStyle(); |
| 143 | // The style that was set via env var/.conf/C++, or Basic if none was set. |
| 144 | const QString styleName = QQuickStylePrivate::effectiveStyleName(styleName: QQuickStyle::name()); |
| 145 | // The effective fallback style: rawFallbackStyleName, or Basic if empty. |
| 146 | const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(styleName: rawFallbackStyleName); |
| 147 | qCDebug(lcQtQuickControls2Plugin) << "style:" << QQuickStyle::name() << "effective style:" << styleName |
| 148 | << "fallback style:" << rawFallbackStyleName << "effective fallback style:" << fallbackStyleName; |
| 149 | |
| 150 | customStyle = QQuickStylePrivate::isCustomStyle(); |
| 151 | // The URI of the current style. For built-in styles, the style name is appended to "QtQuick.Controls.". |
| 152 | // For custom styles that are embedded in resources, we need to remove the ":/" prefix. |
| 153 | registeredStyleUri = ::styleUri(); |
| 154 | |
| 155 | // If the style is Basic, we don't need to register the fallback because the Basic style |
| 156 | // provides all controls. Also, if we didn't return early here, we can get an infinite import loop |
| 157 | // when the style is set to Basic. |
| 158 | if (styleName != fallbackStyleName && styleName != QLatin1String("Basic" )) { |
| 159 | // If no specific fallback is given, the fallback is of lower precedence than recursive |
| 160 | // imports of the main style (i.e. IMPORTS in a style's CMakeLists.txt). |
| 161 | // If a specific fallback is given, it is of higher precedence. |
| 162 | |
| 163 | QString parentModule; |
| 164 | QString fallbackModule; |
| 165 | |
| 166 | // The fallback style has to be a built-in style, so it will become "QtQuick.Controls.<fallback>". |
| 167 | registeredFallbackStyleUri = ::fallbackStyleUri(); |
| 168 | |
| 169 | if (!rawFallbackStyleName.isEmpty()) { |
| 170 | parentModule = qtQuickControlsUri; |
| 171 | fallbackModule = registeredFallbackStyleUri; |
| 172 | } else if (customStyle) { |
| 173 | // Since we don't know the versioning scheme of custom styles, but we want the |
| 174 | // version of QtQuick.Controls to be propagated, we need to do our own indirection. |
| 175 | // QtQuick.Controls.IndirectBasic indirectly imports QtQuick.Controls.Basic |
| 176 | Q_ASSERT(registeredFallbackStyleUri == QLatin1String("QtQuick.Controls.Basic" )); |
| 177 | parentModule = qtQuickControlsUri; |
| 178 | fallbackModule = QLatin1String("QtQuick.Controls.IndirectBasic" ); |
| 179 | } else { |
| 180 | parentModule = registeredStyleUri; |
| 181 | fallbackModule = registeredFallbackStyleUri; |
| 182 | } |
| 183 | |
| 184 | qCDebug(lcQtQuickControls2Plugin) |
| 185 | << "calling qmlRegisterModuleImport() to register fallback style with" |
| 186 | << " uri \"" << parentModule << "\" moduleMajor" << QQmlModuleImportModuleAny |
| 187 | << "import" << fallbackModule << "importMajor" << QQmlModuleImportAuto; |
| 188 | // Whenever parentModule is imported, registeredFallbackStyleUri will be imported too. |
| 189 | // The fallback style must be a built-in style, so we match the version number. |
| 190 | qmlRegisterModuleImport(uri: parentModule.toUtf8().constData(), moduleMajor: QQmlModuleImportModuleAny, |
| 191 | import: fallbackModule.toUtf8().constData(), |
| 192 | importMajor: QQmlModuleImportAuto, importMinor: QQmlModuleImportAuto); |
| 193 | } |
| 194 | |
| 195 | // If the user imports QtQuick.Controls 2.15, and they're using the Material style, we should import version 2.15. |
| 196 | // However, if they import QtQuick.Controls 2.15, but are using a custom style, we want to use the latest version |
| 197 | // number of their style. |
| 198 | const int importMajor = customStyle ? QQmlModuleImportLatest : QQmlModuleImportAuto; |
| 199 | qCDebug(lcQtQuickControls2Plugin).nospace() |
| 200 | << "calling qmlRegisterModuleImport() to register primary style with" |
| 201 | << " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor |
| 202 | << " import " << registeredStyleUri << " importMajor " << importMajor; |
| 203 | // When QtQuick.Controls is imported, the selected style will be imported too. |
| 204 | qmlRegisterModuleImport(uri: qtQuickControlsUri, moduleMajor: QQmlModuleImportModuleAny, |
| 205 | import: registeredStyleUri.toUtf8().constData(), importMajor); |
| 206 | |
| 207 | if (customStyle) |
| 208 | QFileSelectorPrivate::addStatics(QStringList() << styleName); |
| 209 | } |
| 210 | |
| 211 | void QtQuickControls2Plugin::unregisterTypes() |
| 212 | { |
| 213 | qCDebug(lcQtQuickControls2Plugin) << "unregisterTypes() called" ; |
| 214 | |
| 215 | const int importMajor = customStyle ? QQmlModuleImportLatest : QQmlModuleImportAuto; |
| 216 | qCDebug(lcQtQuickControls2Plugin).nospace() |
| 217 | << "calling qmlUnregisterModuleImport() to unregister primary style with" |
| 218 | << " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor |
| 219 | << " import " << registeredStyleUri << " importMajor " << importMajor; |
| 220 | qmlUnregisterModuleImport(uri: qtQuickControlsUri, moduleMajor: QQmlModuleImportModuleAny, |
| 221 | import: registeredStyleUri.toUtf8().constData(), importMajor); |
| 222 | |
| 223 | if (!registeredFallbackStyleUri.isEmpty()) { |
| 224 | QString parentModule; |
| 225 | QString fallbackModule; |
| 226 | |
| 227 | if (!rawFallbackStyleName.isEmpty()) { |
| 228 | parentModule = qtQuickControlsUri; |
| 229 | fallbackModule = registeredFallbackStyleUri; |
| 230 | rawFallbackStyleName.clear(); |
| 231 | } else if (customStyle) { |
| 232 | parentModule = qtQuickControlsUri; |
| 233 | fallbackModule = QLatin1String("QtQuick.Controls.IndirectBasic" ); |
| 234 | } else { |
| 235 | parentModule = registeredStyleUri; |
| 236 | fallbackModule = registeredFallbackStyleUri; |
| 237 | } |
| 238 | |
| 239 | qCDebug(lcQtQuickControls2Plugin) |
| 240 | << "calling qmlUnregisterModuleImport() to unregister fallback style with" |
| 241 | << " uri \"" << parentModule << "\" moduleMajor" << QQmlModuleImportModuleAny |
| 242 | << "import" << fallbackModule << "importMajor" << QQmlModuleImportAuto; |
| 243 | qmlUnregisterModuleImport(uri: parentModule.toUtf8().constData(), moduleMajor: QQmlModuleImportModuleAny, |
| 244 | import: fallbackModule.toUtf8().constData(), |
| 245 | importMajor: QQmlModuleImportAuto, importMinor: QQmlModuleImportAuto); |
| 246 | |
| 247 | registeredFallbackStyleUri.clear(); |
| 248 | } |
| 249 | |
| 250 | customStyle = false; |
| 251 | registeredStyleUri.clear(); |
| 252 | } |
| 253 | |
| 254 | QT_END_NAMESPACE |
| 255 | |
| 256 | #include "qtquickcontrols2plugin.moc" |
| 257 | |