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 | 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 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 | |
145 | void 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 | |
179 | void 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 | */ |
284 | QQmlApplicationEngine::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 | */ |
294 | QQmlApplicationEngine::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 | */ |
308 | QQmlApplicationEngine::QQmlApplicationEngine(QAnyStringView uri, QAnyStringView typeName, QObject *parent) |
309 | : QQmlApplicationEngine(parent) |
310 | { |
311 | loadFromModule(uri, typeName); |
312 | } |
313 | |
314 | static 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 | */ |
329 | QQmlApplicationEngine::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 | */ |
337 | QQmlApplicationEngine::~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 | */ |
354 | void 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 | */ |
368 | void 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 | */ |
398 | void 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 | */ |
425 | void 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 | */ |
440 | void 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 | */ |
460 | void 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 | |
474 | QList<QObject *> QQmlApplicationEngine::rootObjects() const |
475 | { |
476 | Q_D(const QQmlApplicationEngine); |
477 | return d->objects; |
478 | } |
479 | |
480 | QT_END_NAMESPACE |
481 | |
482 | #include "moc_qqmlapplicationengine.cpp" |
483 |
Definitions
- QQmlApplicationEnginePrivate
- ~QQmlApplicationEnginePrivate
- ensureInitialized
- cleanUp
- init
- _q_loadTranslations
- startLoad
- startLoad
- finishLoad
- ensureLoadingFinishes
- QQmlApplicationEngine
- QQmlApplicationEngine
- QQmlApplicationEngine
- urlFromFilePath
- QQmlApplicationEngine
- ~QQmlApplicationEngine
- load
- load
- loadFromModule
- setInitialProperties
- setExtraFileSelectors
- loadData
Learn Advanced QML with KDAB
Find out more