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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qml/qml/qqmlapplicationengine.cpp