| 1 | // Copyright (C) 2016 Research In Motion. |
| 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 <QtQml/qqmlfile.h> |
| 5 | #include <QtCore/QCoreApplication> |
| 6 | #include <QtCore/QTranslator> |
| 7 | #include <QQmlComponent> |
| 8 | #include "qqmlapplicationengine.h" |
| 9 | #include "qqmlapplicationengine_p.h" |
| 10 | #include <QtQml/private/qqmlcomponent_p.h> |
| 11 | #include <QtQml/private/qqmldirdata_p.h> |
| 12 | #include <QtQml/private/qqmlfileselector_p.h> |
| 13 | |
| 14 | using namespace Qt::Literals::StringLiterals; |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | QQmlApplicationEnginePrivate::QQmlApplicationEnginePrivate(QQmlEngine *e) |
| 19 | : QQmlEnginePrivate(e) |
| 20 | { |
| 21 | uiLanguage = QLocale().bcp47Name(); |
| 22 | } |
| 23 | |
| 24 | QQmlApplicationEnginePrivate::~QQmlApplicationEnginePrivate() |
| 25 | { |
| 26 | } |
| 27 | |
| 28 | void QQmlApplicationEnginePrivate::ensureInitialized() |
| 29 | { |
| 30 | if (!isInitialized) { |
| 31 | init(); |
| 32 | isInitialized = true; |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | void QQmlApplicationEnginePrivate::cleanUp() |
| 37 | { |
| 38 | Q_Q(QQmlApplicationEngine); |
| 39 | for (auto obj : std::as_const(t&: objects)) |
| 40 | obj->disconnect(receiver: q); |
| 41 | |
| 42 | qDeleteAll(c: objects); |
| 43 | } |
| 44 | |
| 45 | void QQmlApplicationEnginePrivate::init() |
| 46 | { |
| 47 | Q_Q(QQmlApplicationEngine); |
| 48 | q->connect(sender: q, signal: &QQmlApplicationEngine::quit, context: QCoreApplication::instance(), |
| 49 | slot: &QCoreApplication::quit, type: Qt::QueuedConnection); |
| 50 | q->connect(sender: q, signal: &QQmlApplicationEngine::exit, context: QCoreApplication::instance(), |
| 51 | slot: &QCoreApplication::exit, type: Qt::QueuedConnection); |
| 52 | QObject::connect(sender: q, signal: &QJSEngine::uiLanguageChanged, context: q, slot: [this](){ |
| 53 | _q_loadTranslations(); |
| 54 | }); |
| 55 | #if QT_CONFIG(translation) |
| 56 | QTranslator* qtTranslator = new QTranslator(q); |
| 57 | if (qtTranslator->load(locale: QLocale(), filename: QLatin1String("qt" ), prefix: QLatin1String("_" ), directory: QLibraryInfo::path(p: QLibraryInfo::TranslationsPath), suffix: QLatin1String(".qm" ))) |
| 58 | QCoreApplication::installTranslator(messageFile: qtTranslator); |
| 59 | else |
| 60 | delete qtTranslator; |
| 61 | #endif |
| 62 | auto *selector = new QQmlFileSelector(q,q); |
| 63 | selector->setExtraSelectors(extraFileSelectors); |
| 64 | QCoreApplication::instance()->setProperty(name: "__qml_using_qqmlapplicationengine" , value: QVariant(true)); |
| 65 | } |
| 66 | |
| 67 | void QQmlApplicationEnginePrivate::_q_loadTranslations() |
| 68 | { |
| 69 | #if QT_CONFIG(translation) |
| 70 | Q_Q(QQmlApplicationEngine); |
| 71 | if (translationsDirectory.isEmpty()) |
| 72 | return; |
| 73 | |
| 74 | auto translator = std::make_unique<QTranslator>(); |
| 75 | if (!uiLanguage.value().isEmpty()) { |
| 76 | QLocale locale(uiLanguage); |
| 77 | if (translator->load(locale, filename: QLatin1String("qml" ), prefix: QLatin1String("_" ), directory: translationsDirectory, suffix: QLatin1String(".qm" ))) { |
| 78 | if (activeTranslator) |
| 79 | QCoreApplication::removeTranslator(messageFile: activeTranslator.get()); |
| 80 | QCoreApplication::installTranslator(messageFile: translator.get()); |
| 81 | activeTranslator.swap(u&: translator); |
| 82 | } |
| 83 | } else { |
| 84 | activeTranslator.reset(); |
| 85 | } |
| 86 | q->retranslate(); |
| 87 | #endif |
| 88 | } |
| 89 | |
| 90 | static QString translationsDirectoryFromLocalUrl(const QUrl &url) |
| 91 | { |
| 92 | QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url)); |
| 93 | return fi.path() + "/i18n"_L1 ; |
| 94 | } |
| 95 | |
| 96 | void QQmlApplicationEnginePrivate::startLoad(const QUrl &url, const QByteArray &data, bool dataFlag) |
| 97 | { |
| 98 | Q_Q(QQmlApplicationEngine); |
| 99 | |
| 100 | ensureInitialized(); |
| 101 | |
| 102 | updateTranslationDirectory(url); |
| 103 | |
| 104 | _q_loadTranslations(); //Translations must be loaded before the QML file is |
| 105 | QQmlComponent *c = new QQmlComponent(q, q); |
| 106 | |
| 107 | if (dataFlag) |
| 108 | c->setData(data,baseUrl: url); |
| 109 | else |
| 110 | c->loadUrl(url); |
| 111 | |
| 112 | ensureLoadingFinishes(component: c); |
| 113 | } |
| 114 | |
| 115 | void QQmlApplicationEnginePrivate::startLoad(QAnyStringView uri, QAnyStringView typeName) |
| 116 | { |
| 117 | Q_Q(QQmlApplicationEngine); |
| 118 | |
| 119 | QQmlComponent *c = new QQmlComponent(q, q); |
| 120 | |
| 121 | ensureInitialized(); |
| 122 | |
| 123 | auto *componentPriv = QQmlComponentPrivate::get(c); |
| 124 | componentPriv->prepareLoadFromModule(uri, typeName, mode: QQmlTypeLoader::Synchronous); |
| 125 | |
| 126 | const QQmlType type = componentPriv->loadHelperType(); |
| 127 | |
| 128 | if (type.sourceUrl().isValid()) { |
| 129 | const auto qmlDirData = typeLoader.getQmldir(type.sourceUrl()); |
| 130 | const QUrl url = qmlDirData->finalUrl(); |
| 131 | // A QRC URL coming from a qmldir cannot contain a relative path |
| 132 | Q_ASSERT(url.scheme() != "qrc"_L1 || url.path().startsWith('/'_L1)); |
| 133 | updateTranslationDirectory(url); |
| 134 | } |
| 135 | |
| 136 | /* Translations must be loaded before the QML file. They require translationDirectory to |
| 137 | * already be resolved. But, in order to resolve the translationDirectory, the type of the |
| 138 | * module to load needs to be known. Therefore, loadFromModule is split into resolution and |
| 139 | * loading because the translation directory needs to be set in between. |
| 140 | */ |
| 141 | _q_loadTranslations(); |
| 142 | componentPriv->completeLoadFromModule(uri, typeName); |
| 143 | |
| 144 | ensureLoadingFinishes(component: c); |
| 145 | } |
| 146 | |
| 147 | void QQmlApplicationEnginePrivate::finishLoad(QQmlComponent *c) |
| 148 | { |
| 149 | Q_Q(QQmlApplicationEngine); |
| 150 | switch (c->status()) { |
| 151 | case QQmlComponent::Error: |
| 152 | qWarning() << "QQmlApplicationEngine failed to load component" ; |
| 153 | warning(c->errors()); |
| 154 | q->objectCreated(object: nullptr, url: c->url()); |
| 155 | q->objectCreationFailed(url: c->url()); |
| 156 | break; |
| 157 | case QQmlComponent::Ready: { |
| 158 | auto newObj = initialProperties.empty() ? c->create() : c->createWithInitialProperties(initialProperties); |
| 159 | |
| 160 | if (c->isError()) { |
| 161 | qWarning() << "QQmlApplicationEngine failed to create component" ; |
| 162 | warning(c->errors()); |
| 163 | q->objectCreated(object: nullptr, url: c->url()); |
| 164 | q->objectCreationFailed(url: c->url()); |
| 165 | break; |
| 166 | } |
| 167 | |
| 168 | objects << newObj; |
| 169 | QObject::connect(sender: newObj, signal: &QObject::destroyed, context: q, slot: [&](QObject *obj) { objects.removeAll(t: obj); }); |
| 170 | q->objectCreated(object: objects.constLast(), url: c->url()); |
| 171 | } |
| 172 | break; |
| 173 | case QQmlComponent::Loading: |
| 174 | case QQmlComponent::Null: |
| 175 | return; //These cases just wait for the next status update |
| 176 | } |
| 177 | |
| 178 | c->deleteLater(); |
| 179 | } |
| 180 | |
| 181 | void QQmlApplicationEnginePrivate::ensureLoadingFinishes(QQmlComponent *c) |
| 182 | { |
| 183 | Q_Q(QQmlApplicationEngine); |
| 184 | if (!c->isLoading()) { |
| 185 | finishLoad(c); |
| 186 | return; |
| 187 | } |
| 188 | QObject::connect(sender: c, signal: &QQmlComponent::statusChanged, context: q, slot: [this, c] { this->finishLoad(c); }); |
| 189 | } |
| 190 | |
| 191 | void QQmlApplicationEnginePrivate::updateTranslationDirectory(const QUrl &url) |
| 192 | { |
| 193 | const QString scheme = url.scheme(); |
| 194 | if (scheme == "file"_L1 ) { |
| 195 | translationsDirectory = translationsDirectoryFromLocalUrl(url); |
| 196 | } else if (scheme == "qrc"_L1 ) { |
| 197 | translationsDirectory = translationsDirectoryFromLocalUrl(url); |
| 198 | } else { |
| 199 | translationsDirectory.clear(); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | /*! |
| 204 | \class QQmlApplicationEngine |
| 205 | \since 5.1 |
| 206 | \inmodule QtQml |
| 207 | \brief QQmlApplicationEngine provides a convenient way to load an application from a single QML file. |
| 208 | |
| 209 | This class combines a QQmlEngine and QQmlComponent to provide a convenient way to load a single QML file. It also exposes some central application functionality to QML, which a C++/QML hybrid application would normally control from C++. |
| 210 | |
| 211 | It can be used like so: |
| 212 | |
| 213 | \code |
| 214 | #include <QGuiApplication> |
| 215 | #include <QQmlApplicationEngine> |
| 216 | |
| 217 | int main(int argc, char *argv[]) |
| 218 | { |
| 219 | QGuiApplication app(argc, argv); |
| 220 | QQmlApplicationEngine engine("main.qml"); |
| 221 | return app.exec(); |
| 222 | } |
| 223 | \endcode |
| 224 | |
| 225 | Unlike QQuickView, QQmlApplicationEngine does not automatically create a root |
| 226 | window. If you are using visual items from Qt Quick, you will need to place |
| 227 | them inside of a \l [QML] {Window}. |
| 228 | |
| 229 | You can also use QCoreApplication with QQmlApplicationEngine, if you are not using any QML modules which require a QGuiApplication (such as \c QtQuick). |
| 230 | |
| 231 | List of configuration changes from a default QQmlEngine: |
| 232 | |
| 233 | \list |
| 234 | \li Connecting Qt.quit() to QCoreApplication::quit() |
| 235 | \li Automatically loads translation files from an i18n directory adjacent to the main QML file. |
| 236 | \li Translations are reloaded when the \c QJSEngine::uiLanguage / \c Qt.uiLanguage property is changed. |
| 237 | \li Automatically sets an incubation controller if the scene contains a QQuickWindow. |
| 238 | \li Automatically sets a \c QQmlFileSelector as the url interceptor, applying file selectors to all |
| 239 | QML files and assets. |
| 240 | \endlist |
| 241 | |
| 242 | The engine behavior can be further tweaked by using the inherited methods from QQmlEngine. |
| 243 | |
| 244 | \note Translation files must have a \e qml_ prefix in order to be recognized, |
| 245 | e.g. \e{qml_ja_JP.qm}. |
| 246 | |
| 247 | \note Placing translation files relative to the main QML file involves adding |
| 248 | a \e RESOURCE_PREFIX to the relevant \l qt_add_translations call. This |
| 249 | needs to include the resource prefix of the main file's QML module |
| 250 | (\e{/qt/qml} by default) and the module URI. For example, to provide |
| 251 | translation files for a module called "Translated": |
| 252 | \snippet qml-i18n/CMakeLists.txt 0 |
| 253 | */ |
| 254 | |
| 255 | /*! |
| 256 | \fn QQmlApplicationEngine::objectCreated(QObject *object, const QUrl &url) |
| 257 | |
| 258 | This signal is emitted when an object finishes loading. If loading was |
| 259 | successful, \a object contains a pointer to the loaded object, otherwise |
| 260 | the pointer is NULL. |
| 261 | |
| 262 | The \a url to the component the \a object came from is also provided. |
| 263 | |
| 264 | \note If the path to the component was provided as a QString containing a |
| 265 | relative path, the \a url will contain a fully resolved path to the file. |
| 266 | */ |
| 267 | |
| 268 | /*! |
| 269 | \fn QQmlApplicationEngine::objectCreationFailed(const QUrl &url) |
| 270 | \since 6.4 |
| 271 | |
| 272 | This signal is emitted when loading finishes because an error occurred. |
| 273 | |
| 274 | The \a url to the component that failed to load is provided as an argument. |
| 275 | |
| 276 | \code |
| 277 | QGuiApplication app(argc, argv); |
| 278 | QQmlApplicationEngine engine; |
| 279 | |
| 280 | // exit on error |
| 281 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, |
| 282 | &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); |
| 283 | engine.load(QUrl()); |
| 284 | return app.exec(); |
| 285 | \endcode |
| 286 | |
| 287 | \note If the path to the component was provided as a QString containing a |
| 288 | relative path, the \a url will contain a fully resolved path to the file. |
| 289 | |
| 290 | See also \l {QQmlApplicationEngine::objectCreated}, which will be emitted in |
| 291 | addition to this signal (even though creation failed). |
| 292 | */ |
| 293 | |
| 294 | /*! |
| 295 | Create a new QQmlApplicationEngine with the given \a parent. You will have to call load() later in |
| 296 | order to load a QML file. |
| 297 | */ |
| 298 | QQmlApplicationEngine::QQmlApplicationEngine(QObject *parent) |
| 299 | : QQmlEngine(*(new QQmlApplicationEnginePrivate(this)), parent) |
| 300 | { |
| 301 | QJSEnginePrivate::addToDebugServer(q: this); |
| 302 | } |
| 303 | |
| 304 | /*! |
| 305 | Create a new QQmlApplicationEngine and loads the QML file at the given \a url. |
| 306 | This is provided as a convenience, and is the same as using the empty constructor and calling load afterwards. |
| 307 | */ |
| 308 | QQmlApplicationEngine::QQmlApplicationEngine(const QUrl &url, QObject *parent) |
| 309 | : QQmlApplicationEngine(parent) |
| 310 | { |
| 311 | load(url); |
| 312 | } |
| 313 | |
| 314 | /*! |
| 315 | Create a new QQmlApplicationEngine and loads the QML type specified by |
| 316 | \a uri and \a typeName |
| 317 | This is provided as a convenience, and is the same as using the empty constructor and calling |
| 318 | loadFromModule afterwards. |
| 319 | |
| 320 | \since 6.5 |
| 321 | */ |
| 322 | QQmlApplicationEngine::QQmlApplicationEngine(QAnyStringView uri, QAnyStringView typeName, QObject *parent) |
| 323 | : QQmlApplicationEngine(parent) |
| 324 | { |
| 325 | loadFromModule(uri, typeName); |
| 326 | } |
| 327 | |
| 328 | static QUrl urlFromFilePath(const QString &filePath) |
| 329 | { |
| 330 | return filePath.startsWith(c: QLatin1Char(':')) |
| 331 | ? QUrl(QLatin1String("qrc" ) + filePath) |
| 332 | : QUrl::fromUserInput(userInput: filePath, workingDirectory: QLatin1String("." ), options: QUrl::AssumeLocalFile); |
| 333 | } |
| 334 | |
| 335 | /*! |
| 336 | Create a new QQmlApplicationEngine and loads the QML file at the given |
| 337 | \a filePath, which must be a local file or qrc path. If a relative path is |
| 338 | given then it will be interpreted as relative to the working directory of the |
| 339 | application. |
| 340 | |
| 341 | This is provided as a convenience, and is the same as using the empty constructor and calling load afterwards. |
| 342 | */ |
| 343 | QQmlApplicationEngine::QQmlApplicationEngine(const QString &filePath, QObject *parent) |
| 344 | : QQmlApplicationEngine(urlFromFilePath(filePath), parent) |
| 345 | { |
| 346 | } |
| 347 | |
| 348 | /*! |
| 349 | Destroys the QQmlApplicationEngine and all QML objects it loaded. |
| 350 | */ |
| 351 | QQmlApplicationEngine::~QQmlApplicationEngine() |
| 352 | { |
| 353 | Q_D(QQmlApplicationEngine); |
| 354 | QJSEnginePrivate::removeFromDebugServer(q: this); |
| 355 | d->cleanUp();//Instantiated root objects must be deleted before the engine |
| 356 | } |
| 357 | |
| 358 | /*! |
| 359 | Loads the root QML file located at \a url. The object tree defined by the file |
| 360 | is created immediately for local file urls. Remote urls are loaded asynchronously, |
| 361 | listen to the \l {QQmlApplicationEngine::objectCreated()}{objectCreated} signal to |
| 362 | determine when the object tree is ready. |
| 363 | |
| 364 | If an error occurs, the \l {QQmlApplicationEngine::objectCreated()}{objectCreated} |
| 365 | signal is emitted with a null pointer as parameter and error messages are printed |
| 366 | with qWarning. |
| 367 | */ |
| 368 | void QQmlApplicationEngine::load(const QUrl &url) |
| 369 | { |
| 370 | Q_D(QQmlApplicationEngine); |
| 371 | d->startLoad(url); |
| 372 | } |
| 373 | |
| 374 | /*! |
| 375 | Loads the root QML file located at \a filePath. \a filePath must be a path to |
| 376 | a local file or a path to a file in the resource file system. If \a filePath |
| 377 | is a relative path, it is taken as relative to the application's working |
| 378 | directory. The object tree defined by the file is instantiated immediately. |
| 379 | |
| 380 | If an error occurs, error messages are printed with qWarning. |
| 381 | */ |
| 382 | void QQmlApplicationEngine::load(const QString &filePath) |
| 383 | { |
| 384 | Q_D(QQmlApplicationEngine); |
| 385 | d->startLoad(url: urlFromFilePath(filePath)); |
| 386 | } |
| 387 | |
| 388 | /*! |
| 389 | Loads the QML type \a typeName from the module specified by \a uri. |
| 390 | If the type originates from a QML file located at a remote url, |
| 391 | the type will be loaded asynchronously. |
| 392 | Listen to the \l {QQmlApplicationEngine::objectCreated()}{objectCreated} |
| 393 | signal to determine when the object tree is ready. |
| 394 | |
| 395 | If an error occurs, the \l {QQmlApplicationEngine::objectCreated()}{objectCreated} |
| 396 | signal is emitted with a null pointer as parameter and error messages are printed |
| 397 | with qWarning. |
| 398 | |
| 399 | \code |
| 400 | QQmlApplicationEngine engine; |
| 401 | engine.loadFromModule("QtQuick", "Rectangle"); |
| 402 | \endcode |
| 403 | |
| 404 | \note The module identified by \a uri is searched in the |
| 405 | \l {QML Import Path}{import path}, in the same way as if |
| 406 | you were doing \c{import uri} inside a QML file. If the |
| 407 | module cannot be located there, this function will fail. |
| 408 | |
| 409 | \since 6.5 |
| 410 | \sa QQmlComponent::loadFromModule |
| 411 | */ |
| 412 | void QQmlApplicationEngine::loadFromModule(QAnyStringView uri, QAnyStringView typeName) |
| 413 | { |
| 414 | Q_D(QQmlApplicationEngine); |
| 415 | d->startLoad(uri, typeName); |
| 416 | } |
| 417 | |
| 418 | /*! |
| 419 | Sets the \a initialProperties with which the QML component gets initialized after |
| 420 | it gets loaded. |
| 421 | |
| 422 | \code |
| 423 | QQmlApplicationEngine engine; |
| 424 | |
| 425 | EventDatabase eventDatabase; |
| 426 | EventMonitor eventMonitor; |
| 427 | |
| 428 | engine.setInitialProperties({ |
| 429 | { "eventDatabase", QVariant::fromValue(&eventDatabase) }, |
| 430 | { "eventMonitor", QVariant::fromValue(&eventMonitor) } |
| 431 | }); |
| 432 | \endcode |
| 433 | |
| 434 | \sa QQmlComponent::setInitialProperties |
| 435 | \sa QQmlApplicationEngine::load |
| 436 | \sa QQmlApplicationEngine::loadData |
| 437 | \since 5.14 |
| 438 | */ |
| 439 | void QQmlApplicationEngine::setInitialProperties(const QVariantMap &initialProperties) |
| 440 | { |
| 441 | Q_D(QQmlApplicationEngine); |
| 442 | d->initialProperties = initialProperties; |
| 443 | } |
| 444 | |
| 445 | /*! |
| 446 | Sets the \a extraFileSelectors to be passed to the internal QQmlFileSelector |
| 447 | used for resolving URLs to local files. The \a extraFileSelectors are applied |
| 448 | when the first QML file is loaded. Setting them afterwards has no effect. |
| 449 | |
| 450 | \sa QQmlFileSelector |
| 451 | \sa QFileSelector::setExtraSelectors |
| 452 | \since 6.0 |
| 453 | */ |
| 454 | void QQmlApplicationEngine::(const QStringList &) |
| 455 | { |
| 456 | Q_D(QQmlApplicationEngine); |
| 457 | if (d->isInitialized) { |
| 458 | qWarning() << "QQmlApplicationEngine::setExtraFileSelectors()" |
| 459 | << "called after loading QML files. This has no effect." ; |
| 460 | } else { |
| 461 | d->extraFileSelectors = extraFileSelectors; |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | /*! |
| 466 | Loads the QML given in \a data. The object tree defined by \a data is |
| 467 | instantiated immediately. |
| 468 | |
| 469 | If a \a url is specified it is used as the base url of the component. This affects |
| 470 | relative paths within the data and error messages. |
| 471 | |
| 472 | If an error occurs, error messages are printed with qWarning. |
| 473 | */ |
| 474 | void QQmlApplicationEngine::loadData(const QByteArray &data, const QUrl &url) |
| 475 | { |
| 476 | Q_D(QQmlApplicationEngine); |
| 477 | d->startLoad(url, data, dataFlag: true); |
| 478 | } |
| 479 | |
| 480 | /*! |
| 481 | Returns a list of all the root objects instantiated by the |
| 482 | QQmlApplicationEngine. This will only contain objects loaded via load() or a |
| 483 | convenience constructor. |
| 484 | |
| 485 | \note In Qt versions prior to 5.9, this function is marked as non-\c{const}. |
| 486 | */ |
| 487 | |
| 488 | QList<QObject *> QQmlApplicationEngine::rootObjects() const |
| 489 | { |
| 490 | Q_D(const QQmlApplicationEngine); |
| 491 | return d->objects; |
| 492 | } |
| 493 | |
| 494 | QT_END_NAMESPACE |
| 495 | |
| 496 | #include "moc_qqmlapplicationengine.cpp" |
| 497 | |