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/qqmlfileselector_p.h>
11
12#include <memory>
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 type)
113{
114 Q_Q(QQmlApplicationEngine);
115
116 _q_loadTranslations(); //Translations must be loaded before the QML file is
117 QQmlComponent *c = new QQmlComponent(q, q);
118
119 ensureInitialized();
120 c->loadFromModule(uri, typeName: type);
121 ensureLoadingFinishes(component: c);
122}
123
124void QQmlApplicationEnginePrivate::finishLoad(QQmlComponent *c)
125{
126 Q_Q(QQmlApplicationEngine);
127 switch (c->status()) {
128 case QQmlComponent::Error:
129 qWarning() << "QQmlApplicationEngine failed to load component";
130 warning(c->errors());
131 q->objectCreated(object: nullptr, url: c->url());
132 q->objectCreationFailed(url: c->url());
133 break;
134 case QQmlComponent::Ready: {
135 auto newObj = initialProperties.empty() ? c->create() : c->createWithInitialProperties(initialProperties);
136
137 if (c->isError()) {
138 qWarning() << "QQmlApplicationEngine failed to create component";
139 warning(c->errors());
140 q->objectCreated(object: nullptr, url: c->url());
141 q->objectCreationFailed(url: c->url());
142 break;
143 }
144
145 objects << newObj;
146 QObject::connect(sender: newObj, signal: &QObject::destroyed, context: q, slot: [&](QObject *obj) { objects.removeAll(t: obj); });
147 q->objectCreated(object: objects.constLast(), url: c->url());
148 }
149 break;
150 case QQmlComponent::Loading:
151 case QQmlComponent::Null:
152 return; //These cases just wait for the next status update
153 }
154
155 c->deleteLater();
156}
157
158void QQmlApplicationEnginePrivate::ensureLoadingFinishes(QQmlComponent *c)
159{
160 Q_Q(QQmlApplicationEngine);
161 if (!c->isLoading()) {
162 finishLoad(c);
163 return;
164 }
165 QObject::connect(sender: c, signal: &QQmlComponent::statusChanged, context: q, slot: [this, c] { this->finishLoad(c); });
166}
167
168/*!
169 \class QQmlApplicationEngine
170 \since 5.1
171 \inmodule QtQml
172 \brief QQmlApplicationEngine provides a convenient way to load an application from a single QML file.
173
174 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++.
175
176 It can be used like so:
177
178 \code
179 #include <QGuiApplication>
180 #include <QQmlApplicationEngine>
181
182 int main(int argc, char *argv[])
183 {
184 QGuiApplication app(argc, argv);
185 QQmlApplicationEngine engine("main.qml");
186 return app.exec();
187 }
188 \endcode
189
190 Unlike QQuickView, QQmlApplicationEngine does not automatically create a root
191 window. If you are using visual items from Qt Quick, you will need to place
192 them inside of a \l [QML] {Window}.
193
194 You can also use QCoreApplication with QQmlApplicationEngine, if you are not using any QML modules which require a QGuiApplication (such as \c QtQuick).
195
196 List of configuration changes from a default QQmlEngine:
197
198 \list
199 \li Connecting Qt.quit() to QCoreApplication::quit()
200 \li Automatically loads translation files from an i18n directory adjacent to the main QML file.
201 \list
202 \li Translation files must have "qml_" prefix e.g. qml_ja_JP.qm.
203 \endlist
204 \li Translations are reloaded when the \c QJSEngine::uiLanguage / \c Qt.uiLanguage property is changed.
205 \li Automatically sets an incubation controller if the scene contains a QQuickWindow.
206 \li Automatically sets a \c QQmlFileSelector as the url interceptor, applying file selectors to all
207 QML files and assets.
208 \endlist
209
210 The engine behavior can be further tweaked by using the inherited methods from QQmlEngine.
211
212*/
213
214/*!
215 \fn QQmlApplicationEngine::objectCreated(QObject *object, const QUrl &url)
216
217 This signal is emitted when an object finishes loading. If loading was
218 successful, \a object contains a pointer to the loaded object, otherwise
219 the pointer is NULL.
220
221 The \a url to the component the \a object came from is also provided.
222
223 \note If the path to the component was provided as a QString containing a
224 relative path, the \a url will contain a fully resolved path to the file.
225*/
226
227/*!
228 \fn QQmlApplicationEngine::objectCreationFailed(const QUrl &url)
229 \since 6.4
230
231 This signal is emitted when loading finishes because an error occurred.
232
233 The \a url to the component that failed to load is provided as an argument.
234
235 \code
236 QGuiApplication app(argc, argv);
237 QQmlApplicationEngine engine;
238
239 // quit on error
240 QObject::connect(&engine, QQmlApplicationEngine::objectCreationFailed,
241 QCoreApplication::instance(), QCoreApplication::quit,
242 Qt::QueuedConnection);
243 engine.load(QUrl());
244 return app.exec();
245 \endcode
246
247 \note If the path to the component was provided as a QString containing a
248 relative path, the \a url will contain a fully resolved path to the file.
249
250 See also \l {QQmlApplicationEngine::objectCreated}, which will be emitted in
251 addition to this signal (even though creation failed).
252*/
253
254/*!
255 Create a new QQmlApplicationEngine with the given \a parent. You will have to call load() later in
256 order to load a QML file.
257*/
258QQmlApplicationEngine::QQmlApplicationEngine(QObject *parent)
259: QQmlEngine(*(new QQmlApplicationEnginePrivate(this)), parent)
260{
261 QJSEnginePrivate::addToDebugServer(q: this);
262}
263
264/*!
265 Create a new QQmlApplicationEngine and loads the QML file at the given \a url.
266 This is provided as a convenience, and is the same as using the empty constructor and calling load afterwards.
267*/
268QQmlApplicationEngine::QQmlApplicationEngine(const QUrl &url, QObject *parent)
269 : QQmlApplicationEngine(parent)
270{
271 load(url);
272}
273
274/*!
275 Create a new QQmlApplicationEngine and loads the QML type specified by
276 \a uri and \a typeName
277 This is provided as a convenience, and is the same as using the empty constructor and calling
278 loadFromModule afterwards.
279
280 \since 6.5
281*/
282QQmlApplicationEngine::QQmlApplicationEngine(QAnyStringView uri, QAnyStringView typeName, QObject *parent)
283 : QQmlApplicationEngine(parent)
284{
285 loadFromModule(uri, typeName);
286}
287
288/*!
289 Create a new QQmlApplicationEngine and loads the QML file at the given
290 \a filePath, which must be a local file path. If a relative path is
291 given then it will be interpreted as relative to the working directory of the
292 application.
293
294 This is provided as a convenience, and is the same as using the empty constructor and calling load afterwards.
295*/
296QQmlApplicationEngine::QQmlApplicationEngine(const QString &filePath, QObject *parent)
297 : QQmlApplicationEngine(QUrl::fromUserInput(userInput: filePath, workingDirectory: QLatin1String("."), options: QUrl::AssumeLocalFile), parent)
298{
299}
300
301/*!
302 Destroys the QQmlApplicationEngine and all QML objects it loaded.
303*/
304QQmlApplicationEngine::~QQmlApplicationEngine()
305{
306 Q_D(QQmlApplicationEngine);
307 QJSEnginePrivate::removeFromDebugServer(q: this);
308 d->cleanUp();//Instantiated root objects must be deleted before the engine
309}
310
311/*!
312 Loads the root QML file located at \a url. The object tree defined by the file
313 is created immediately for local file urls. Remote urls are loaded asynchronously,
314 listen to the \l {QQmlApplicationEngine::objectCreated()}{objectCreated} signal to
315 determine when the object tree is ready.
316
317 If an error occurs, the \l {QQmlApplicationEngine::objectCreated()}{objectCreated}
318 signal is emitted with a null pointer as parameter and error messages are printed
319 with qWarning.
320*/
321void QQmlApplicationEngine::load(const QUrl &url)
322{
323 Q_D(QQmlApplicationEngine);
324 d->startLoad(url);
325}
326
327/*!
328 Loads the root QML file located at \a filePath. \a filePath must be a path to
329 a local file. If \a filePath is a relative path, it is taken as relative to
330 the application's working directory. The object tree defined by the file is
331 instantiated immediately.
332
333 If an error occurs, error messages are printed with qWarning.
334*/
335void QQmlApplicationEngine::load(const QString &filePath)
336{
337 Q_D(QQmlApplicationEngine);
338 d->startLoad(url: QUrl::fromUserInput(userInput: filePath, workingDirectory: QLatin1String("."), options: QUrl::AssumeLocalFile));
339}
340
341/*!
342 Loads the QML type \a typeName from the module specified by \a uri.
343 If the type originates from a QML file located at a remote url,
344 the type will be loaded asynchronously.
345 Listen to the \l {QQmlApplicationEngine::objectCreated()}{objectCreated}
346 signal to determine when the object tree is ready.
347
348 If an error occurs, the \l {QQmlApplicationEngine::objectCreated()}{objectCreated}
349 signal is emitted with a null pointer as parameter and error messages are printed
350 with qWarning.
351
352 \code
353 QQmlApplicationEngine engine;
354 engine.loadFromModule("QtQuick", "Rectangle");
355 \endcode
356
357 \since 6.5
358 \sa QQmlComponent::loadFromModule
359 */
360void QQmlApplicationEngine::loadFromModule(QAnyStringView uri, QAnyStringView typeName)
361{
362 Q_D(QQmlApplicationEngine);
363 d->startLoad(uri, type: typeName);
364}
365
366/*!
367 Sets the \a initialProperties with which the QML component gets initialized after
368 it gets loaded.
369
370 \code
371 QQmlApplicationEngine engine;
372
373 EventDatabase eventDatabase;
374 EventMonitor eventMonitor;
375
376 engine.setInitialProperties({
377 { "eventDatabase", QVariant::fromValue(&eventDatabase) },
378 { "eventMonitor", QVariant::fromValue(&eventMonitor) }
379 });
380 \endcode
381
382 \sa QQmlComponent::setInitialProperties
383 \sa QQmlApplicationEngine::load
384 \sa QQmlApplicationEngine::loadData
385 \since 5.14
386*/
387void QQmlApplicationEngine::setInitialProperties(const QVariantMap &initialProperties)
388{
389 Q_D(QQmlApplicationEngine);
390 d->initialProperties = initialProperties;
391}
392
393/*!
394 Sets the \a extraFileSelectors to be passed to the internal QQmlFileSelector
395 used for resolving URLs to local files. The \a extraFileSelectors are applied
396 when the first QML file is loaded. Setting them afterwards has no effect.
397
398 \sa QQmlFileSelector
399 \sa QFileSelector::setExtraSelectors
400 \since 6.0
401*/
402void QQmlApplicationEngine::setExtraFileSelectors(const QStringList &extraFileSelectors)
403{
404 Q_D(QQmlApplicationEngine);
405 if (d->isInitialized) {
406 qWarning() << "QQmlApplicationEngine::setExtraFileSelectors()"
407 << "called after loading QML files. This has no effect.";
408 } else {
409 d->extraFileSelectors = extraFileSelectors;
410 }
411}
412
413/*!
414 Loads the QML given in \a data. The object tree defined by \a data is
415 instantiated immediately.
416
417 If a \a url is specified it is used as the base url of the component. This affects
418 relative paths within the data and error messages.
419
420 If an error occurs, error messages are printed with qWarning.
421*/
422void QQmlApplicationEngine::loadData(const QByteArray &data, const QUrl &url)
423{
424 Q_D(QQmlApplicationEngine);
425 d->startLoad(url, data, dataFlag: true);
426}
427
428/*!
429 Returns a list of all the root objects instantiated by the
430 QQmlApplicationEngine. This will only contain objects loaded via load() or a
431 convenience constructor.
432
433 \note In Qt versions prior to 5.9, this function is marked as non-\c{const}.
434*/
435
436QList<QObject *> QQmlApplicationEngine::rootObjects() const
437{
438 Q_D(const QQmlApplicationEngine);
439 return d->objects;
440}
441
442QT_END_NAMESPACE
443
444#include "moc_qqmlapplicationengine.cpp"
445

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