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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | QQmlApplicationEnginePrivate::QQmlApplicationEnginePrivate(QQmlEngine *e) |
17 | : QQmlEnginePrivate(e) |
18 | { |
19 | uiLanguage = QLocale().bcp47Name(); |
20 | } |
21 | |
22 | QQmlApplicationEnginePrivate::~QQmlApplicationEnginePrivate() |
23 | { |
24 | } |
25 | |
26 | void QQmlApplicationEnginePrivate::ensureInitialized() |
27 | { |
28 | if (!isInitialized) { |
29 | init(); |
30 | isInitialized = true; |
31 | } |
32 | } |
33 | |
34 | void 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 | |
43 | void 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 | |
65 | void 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 | |
88 | void 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 | |
112 | void 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 | |
124 | void 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 | |
158 | void 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 | */ |
258 | QQmlApplicationEngine::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 | */ |
268 | QQmlApplicationEngine::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 | */ |
282 | QQmlApplicationEngine::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 | */ |
296 | QQmlApplicationEngine::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 | */ |
304 | QQmlApplicationEngine::~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 | */ |
321 | void 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 | */ |
335 | void 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 | */ |
360 | void 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 | */ |
387 | void 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 | */ |
402 | void QQmlApplicationEngine::(const QStringList &) |
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 | */ |
422 | void 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 | |
436 | QList<QObject *> QQmlApplicationEngine::rootObjects() const |
437 | { |
438 | Q_D(const QQmlApplicationEngine); |
439 | return d->objects; |
440 | } |
441 | |
442 | QT_END_NAMESPACE |
443 | |
444 | #include "moc_qqmlapplicationengine.cpp" |
445 | |