1 | // Copyright (C) 2016 The Qt Company Ltd. |
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 "qquickloader_p_p.h" |
5 | |
6 | #include <QtQml/qqmlinfo.h> |
7 | |
8 | #include <private/qqmlengine_p.h> |
9 | #include <private/qqmlglobal_p.h> |
10 | |
11 | #include <private/qqmlcomponent_p.h> |
12 | #include <private/qqmlincubator_p.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | Q_DECLARE_LOGGING_CATEGORY(lcTransient) |
17 | |
18 | static const QQuickItemPrivate::ChangeTypes watchedChanges |
19 | = QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; |
20 | |
21 | QQuickLoaderPrivate::QQuickLoaderPrivate() |
22 | : item(nullptr), object(nullptr), itemContext(nullptr), incubator(nullptr), updatingSize(false), |
23 | active(true), loadingFromSource(false), asynchronous(false), status(computeStatus()) |
24 | { |
25 | } |
26 | |
27 | QQuickLoaderPrivate::~QQuickLoaderPrivate() |
28 | { |
29 | delete itemContext; |
30 | itemContext = nullptr; |
31 | delete incubator; |
32 | disposeInitialPropertyValues(); |
33 | } |
34 | |
35 | void QQuickLoaderPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change, |
36 | const QRectF &oldGeometry) |
37 | { |
38 | if (resizeItem == item) |
39 | _q_updateSize(loaderGeometryChanged: false); |
40 | QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, oldGeometry); |
41 | } |
42 | |
43 | void QQuickLoaderPrivate::itemImplicitWidthChanged(QQuickItem *) |
44 | { |
45 | Q_Q(QQuickLoader); |
46 | q->setImplicitWidth(getImplicitWidth()); |
47 | } |
48 | |
49 | void QQuickLoaderPrivate::itemImplicitHeightChanged(QQuickItem *) |
50 | { |
51 | Q_Q(QQuickLoader); |
52 | q->setImplicitHeight(getImplicitHeight()); |
53 | } |
54 | |
55 | void QQuickLoaderPrivate::clear() |
56 | { |
57 | Q_Q(QQuickLoader); |
58 | disposeInitialPropertyValues(); |
59 | |
60 | if (incubator) |
61 | incubator->clear(); |
62 | |
63 | delete itemContext; |
64 | itemContext = nullptr; |
65 | |
66 | // Prevent any bindings from running while waiting for deletion. Without |
67 | // this we may get transient errors from use of 'parent', for example. |
68 | QQmlContext *context = qmlContext(object); |
69 | if (context) |
70 | QQmlContextData::get(context)->clearContextRecursively(); |
71 | |
72 | if (loadingFromSource && component) { |
73 | // disconnect since we deleteLater |
74 | QObject::disconnect(sender: component, SIGNAL(statusChanged(QQmlComponent::Status)), |
75 | receiver: q, SLOT(_q_sourceLoaded())); |
76 | QObject::disconnect(sender: component, SIGNAL(progressChanged(qreal)), |
77 | receiver: q, SIGNAL(progressChanged())); |
78 | component->deleteLater(); |
79 | component.setObject(obj: nullptr, parent: q); |
80 | } else if (component) { |
81 | component.setObject(obj: nullptr, parent: q); |
82 | } |
83 | source = QUrl(); |
84 | |
85 | if (item) { |
86 | QQuickItemPrivate *p = QQuickItemPrivate::get(item); |
87 | p->removeItemChangeListener(this, types: watchedChanges); |
88 | |
89 | // We can't delete immediately because our item may have triggered |
90 | // the Loader to load a different item. |
91 | item->setParentItem(nullptr); |
92 | item->setVisible(false); |
93 | item = nullptr; |
94 | } |
95 | if (object) { |
96 | object->deleteLater(); |
97 | object = nullptr; |
98 | } |
99 | } |
100 | |
101 | void QQuickLoaderPrivate::initResize() |
102 | { |
103 | if (!item) |
104 | return; |
105 | QQuickItemPrivate *p = QQuickItemPrivate::get(item); |
106 | p->addItemChangeListener(listener: this, types: watchedChanges); |
107 | _q_updateSize(); |
108 | } |
109 | |
110 | qreal QQuickLoaderPrivate::getImplicitWidth() const |
111 | { |
112 | Q_Q(const QQuickLoader); |
113 | // If the Loader has a valid width then Loader has set an explicit width on the |
114 | // item, and we want the item's implicitWidth. If the Loader's width has |
115 | // not been set then its implicitWidth is the width of the item. |
116 | if (item) |
117 | return q->widthValid() ? item->implicitWidth() : item->width(); |
118 | return QQuickImplicitSizeItemPrivate::getImplicitWidth(); |
119 | } |
120 | |
121 | qreal QQuickLoaderPrivate::getImplicitHeight() const |
122 | { |
123 | Q_Q(const QQuickLoader); |
124 | // If the Loader has a valid height then Loader has set an explicit height on the |
125 | // item, and we want the item's implicitHeight. If the Loader's height has |
126 | // not been set then its implicitHeight is the height of the item. |
127 | if (item) |
128 | return q->heightValid() ? item->implicitHeight() : item->height(); |
129 | return QQuickImplicitSizeItemPrivate::getImplicitHeight(); |
130 | } |
131 | |
132 | /*! |
133 | \qmltype Loader |
134 | \instantiates QQuickLoader |
135 | \inqmlmodule QtQuick |
136 | \ingroup qtquick-dynamic |
137 | \inherits Item |
138 | |
139 | \brief Allows dynamic loading of a subtree from a URL or Component. |
140 | |
141 | Loader is used to dynamically load QML components. |
142 | |
143 | Loader can load a |
144 | QML file (using the \l source property) or a \l Component object (using |
145 | the \l sourceComponent property). It is useful for delaying the creation |
146 | of a component until it is required: for example, when a component should |
147 | be created on demand, or when a component should not be created |
148 | unnecessarily for performance reasons. |
149 | |
150 | Here is a Loader that loads "Page1.qml" as a component when the |
151 | \l MouseArea is clicked: |
152 | |
153 | \snippet qml/loader/simple.qml 0 |
154 | |
155 | The loaded object can be accessed using the \l item property. |
156 | |
157 | If the \l source or \l sourceComponent changes, any previously instantiated |
158 | items are destroyed. Setting \l source to an empty string or setting |
159 | \l sourceComponent to \c undefined destroys the currently loaded object, |
160 | freeing resources and leaving the Loader empty. |
161 | |
162 | \section2 Loader Sizing Behavior |
163 | |
164 | When used to load visual types, Loader applies the following sizing rules: |
165 | |
166 | \list |
167 | \li If an explicit size is not specified for the Loader, the Loader |
168 | is automatically resized to the size of the loaded item once the |
169 | component is loaded. |
170 | \li If the size of the Loader is specified explicitly by setting |
171 | the width, height or by anchoring, the loaded item will be resized |
172 | to the size of the Loader. |
173 | \endlist |
174 | |
175 | In both scenarios the size of the item and the Loader are identical. |
176 | This ensures that anchoring to the Loader is equivalent to anchoring |
177 | to the loaded item. |
178 | |
179 | \table |
180 | \row |
181 | \li sizeloader.qml |
182 | \li sizeitem.qml |
183 | \row |
184 | \li \snippet qml/loader/sizeloader.qml 0 |
185 | \li \snippet qml/loader/sizeitem.qml 0 |
186 | \row |
187 | \li The red rectangle will be sized to the size of the root item. |
188 | \li The red rectangle will be 50x50, centered in the root item. |
189 | \endtable |
190 | |
191 | If the source component is not an Item type, Loader does not apply any |
192 | special sizing rules. |
193 | |
194 | \section2 Receiving Signals from Loaded Objects |
195 | |
196 | Any signals emitted from the loaded object can be received using the |
197 | \l Connections type. For example, the following \c application.qml |
198 | loads \c MyItem.qml, and is able to receive the \c message signal from |
199 | the loaded item through a \l Connections object: |
200 | |
201 | \table 70% |
202 | \row |
203 | \li application.qml |
204 | \li MyItem.qml |
205 | \row |
206 | \li \snippet qml/loader/connections.qml 0 |
207 | \li \snippet qml/loader/MyItem.qml 0 |
208 | \endtable |
209 | |
210 | Alternatively, since \c MyItem.qml is loaded within the scope of the |
211 | Loader, it could also directly call any function defined in the Loader or |
212 | its parent \l Item. |
213 | |
214 | |
215 | \section2 Focus and Key Events |
216 | |
217 | Loader is a focus scope. Its \l {Item::}{focus} property must be set to |
218 | \c true for any of its children to get the \e {active focus}. (See |
219 | \l{Keyboard Focus in Qt Quick} |
220 | for more details.) Any key events received in the loaded item should likely |
221 | also be \l {KeyEvent::}{accepted} so they are not propagated to the Loader. |
222 | |
223 | For example, the following \c application.qml loads \c KeyReader.qml when |
224 | the \l MouseArea is clicked. Notice the \l {Item::}{focus} property is |
225 | set to \c true for the Loader as well as the \l Item in the dynamically |
226 | loaded object: |
227 | |
228 | \table |
229 | \row |
230 | \li application.qml |
231 | \li KeyReader.qml |
232 | \row |
233 | \li \snippet qml/loader/focus.qml 0 |
234 | \li \snippet qml/loader/KeyReader.qml 0 |
235 | \endtable |
236 | |
237 | Once \c KeyReader.qml is loaded, it accepts key events and sets |
238 | \c event.accepted to \c true so that the event is not propagated to the |
239 | parent \l Rectangle. |
240 | |
241 | Since \c {QtQuick 2.0}, Loader can also load non-visual components. |
242 | |
243 | \section2 Using a Loader within a View Delegate |
244 | |
245 | In some cases you may wish to use a Loader within a view delegate to improve delegate |
246 | loading performance. This works well in most cases, but there is one important issue to |
247 | be aware of related to the \l{QtQml::Component#Creation Context}{creation context} of a Component. |
248 | |
249 | In the following example, the \c index context property inserted by the ListView into \c delegateComponent's |
250 | context will be inaccessible to Text, as the Loader will use the creation context of \c myComponent as the parent |
251 | context when instantiating it, and \c index does not refer to anything within that context chain. |
252 | |
253 | \snippet qml/loader/creationContext1.qml 0 |
254 | |
255 | In this situation we can either move the component inline, |
256 | |
257 | \snippet qml/loader/creationContext2.qml 0 |
258 | |
259 | into a separate file, |
260 | |
261 | \snippet qml/loader/creationContext3.qml 0 |
262 | |
263 | or explicitly set the required information as a property of the Loader (this works because the |
264 | Loader sets itself as the context object for the component it is loading). |
265 | |
266 | \snippet qml/loader/creationContext4.qml 0 |
267 | |
268 | \sa {dynamic-object-creation}{Dynamic Object Creation} |
269 | */ |
270 | |
271 | QQuickLoader::QQuickLoader(QQuickItem *parent) |
272 | : QQuickImplicitSizeItem(*(new QQuickLoaderPrivate), parent) |
273 | { |
274 | setFlag(flag: ItemIsFocusScope); |
275 | } |
276 | |
277 | QQuickLoader::~QQuickLoader() |
278 | { |
279 | Q_D(QQuickLoader); |
280 | d->clear(); |
281 | } |
282 | |
283 | /*! |
284 | \qmlproperty bool QtQuick::Loader::active |
285 | This property is \c true if the Loader is currently active. |
286 | The default value for this property is \c true. |
287 | |
288 | If the Loader is inactive, changing the \l source or \l sourceComponent |
289 | will not cause the item to be instantiated until the Loader is made active. |
290 | |
291 | Setting the value to inactive will cause any \l item loaded by the loader |
292 | to be released, but will not affect the \l source or \l sourceComponent. |
293 | |
294 | The \l status of an inactive loader is always \c Null. |
295 | |
296 | \sa source, sourceComponent |
297 | */ |
298 | bool QQuickLoader::active() const |
299 | { |
300 | Q_D(const QQuickLoader); |
301 | return d->active; |
302 | } |
303 | |
304 | void QQuickLoader::setActive(bool newVal) |
305 | { |
306 | Q_D(QQuickLoader); |
307 | if (d->active == newVal) |
308 | return; |
309 | |
310 | d->active = newVal; |
311 | if (newVal == true) { |
312 | if (d->loadingFromSource) { |
313 | loadFromSource(); |
314 | } else { |
315 | loadFromSourceComponent(); |
316 | } |
317 | } else { |
318 | // cancel any current incubation |
319 | if (d->incubator) { |
320 | d->incubator->clear(); |
321 | delete d->itemContext; |
322 | d->itemContext = nullptr; |
323 | } |
324 | |
325 | // Prevent any bindings from running while waiting for deletion. Without |
326 | // this we may get transient errors from use of 'parent', for example. |
327 | QQmlContext *context = qmlContext(d->object); |
328 | if (context) |
329 | QQmlContextData::get(context)->clearContextRecursively(); |
330 | |
331 | if (d->item) { |
332 | QQuickItemPrivate *p = QQuickItemPrivate::get(item: d->item); |
333 | p->removeItemChangeListener(d, types: watchedChanges); |
334 | |
335 | // We can't delete immediately because our item may have triggered |
336 | // the Loader to load a different item. |
337 | d->item->setParentItem(nullptr); |
338 | d->item->setVisible(false); |
339 | d->item = nullptr; |
340 | } |
341 | if (d->object) { |
342 | d->object->deleteLater(); |
343 | d->object = nullptr; |
344 | emit itemChanged(); |
345 | } |
346 | d->updateStatus(); |
347 | } |
348 | emit activeChanged(); |
349 | } |
350 | |
351 | |
352 | /*! |
353 | \qmlproperty url QtQuick::Loader::source |
354 | This property holds the URL of the QML component to instantiate. |
355 | |
356 | Since \c {QtQuick 2.0}, Loader is able to load any type of object; it |
357 | is not restricted to Item types. |
358 | |
359 | To unload the currently loaded object, set this property to an empty string, |
360 | or set \l sourceComponent to \c undefined. Setting \c source to a |
361 | new URL will also cause the item created by the previous URL to be unloaded. |
362 | |
363 | \sa sourceComponent, status, progress |
364 | */ |
365 | QUrl QQuickLoader::source() const |
366 | { |
367 | Q_D(const QQuickLoader); |
368 | return d->source; |
369 | } |
370 | |
371 | void QQuickLoader::setSourceWithoutResolve(const QUrl &url) |
372 | { |
373 | setSource(sourceUrl: url, needsClear: true); // clear previous values |
374 | } |
375 | |
376 | void QQuickLoader::setSource(const QUrl &url, bool needsClear) |
377 | { |
378 | Q_D(QQuickLoader); |
379 | if (d->source == url) |
380 | return; |
381 | |
382 | if (needsClear) |
383 | d->clear(); |
384 | |
385 | d->source = url; |
386 | d->loadingFromSource = true; |
387 | |
388 | if (d->active) |
389 | loadFromSource(); |
390 | else |
391 | emit sourceChanged(); |
392 | } |
393 | |
394 | void QQuickLoader::loadFromSource() |
395 | { |
396 | Q_D(QQuickLoader); |
397 | if (d->source.isEmpty()) { |
398 | emit sourceChanged(); |
399 | d->updateStatus(); |
400 | emit progressChanged(); |
401 | emit itemChanged(); |
402 | return; |
403 | } |
404 | |
405 | if (isComponentComplete()) { |
406 | if (!d->component) |
407 | d->createComponent(); |
408 | d->load(); |
409 | } |
410 | } |
411 | |
412 | /*! |
413 | \qmlproperty Component QtQuick::Loader::sourceComponent |
414 | This property holds the \l{Component} to instantiate. |
415 | |
416 | \qml |
417 | Item { |
418 | Component { |
419 | id: redSquare |
420 | Rectangle { color: "red"; width: 10; height: 10 } |
421 | } |
422 | |
423 | Loader { sourceComponent: redSquare } |
424 | Loader { sourceComponent: redSquare; x: 10 } |
425 | } |
426 | \endqml |
427 | |
428 | To unload the currently loaded object, set this property to \c undefined. |
429 | |
430 | Since \c {QtQuick 2.0}, Loader is able to load any type of object; it |
431 | is not restricted to Item types. |
432 | |
433 | \sa source, progress |
434 | */ |
435 | |
436 | QQmlComponent *QQuickLoader::sourceComponent() const |
437 | { |
438 | Q_D(const QQuickLoader); |
439 | return d->component; |
440 | } |
441 | |
442 | void QQuickLoader::setSourceComponent(QQmlComponent *comp) |
443 | { |
444 | Q_D(QQuickLoader); |
445 | if (comp == d->component) |
446 | return; |
447 | |
448 | d->clear(); |
449 | |
450 | d->component.setObject(obj: comp, parent: this); |
451 | d->loadingFromSource = false; |
452 | |
453 | if (d->active) |
454 | loadFromSourceComponent(); |
455 | else |
456 | emit sourceComponentChanged(); |
457 | } |
458 | |
459 | void QQuickLoader::resetSourceComponent() |
460 | { |
461 | setSourceComponent(nullptr); |
462 | } |
463 | |
464 | void QQuickLoader::loadFromSourceComponent() |
465 | { |
466 | Q_D(QQuickLoader); |
467 | if (!d->component) { |
468 | emit sourceComponentChanged(); |
469 | d->updateStatus(); |
470 | emit progressChanged(); |
471 | emit itemChanged(); |
472 | return; |
473 | } |
474 | |
475 | if (isComponentComplete()) |
476 | d->load(); |
477 | } |
478 | |
479 | |
480 | QUrl QQuickLoader::setSourceUrlHelper(const QUrl &unresolvedUrl) |
481 | { |
482 | Q_D(QQuickLoader); |
483 | |
484 | // 1. If setSource is called with a valid url, clear the old component and its corresponding url |
485 | // 2. If setSource is called with an invalid url(e.g. empty url), clear the old component but |
486 | // hold the url for old one.(we will compare it with new url later and may update status of loader to Loader.Null) |
487 | QUrl oldUrl = d->source; |
488 | d->clear(); |
489 | QUrl sourceUrl = qmlEngine(this)->handle()->callingQmlContext()->resolvedUrl(unresolvedUrl); |
490 | if (!sourceUrl.isValid()) |
491 | d->source = oldUrl; |
492 | return sourceUrl; |
493 | } |
494 | |
495 | /*! |
496 | \qmlmethod object QtQuick::Loader::setSource(url source, object properties) |
497 | |
498 | Creates an object instance of the given \a source component that will have |
499 | the given \a properties. The \a properties argument is optional. The instance |
500 | will be accessible via the \l item property once loading and instantiation |
501 | is complete. |
502 | |
503 | If the \l active property is \c false at the time when this function is called, |
504 | the given \a source component will not be loaded but the \a source and initial |
505 | \a properties will be cached. When the loader is made \l active, an instance of |
506 | the \a source component will be created with the initial \a properties set. |
507 | |
508 | Setting the initial property values of an instance of a component in this manner |
509 | will \b{not} trigger any associated \l{Behavior}s. |
510 | |
511 | Note that the cached \a properties will be cleared if the \l source or \l sourceComponent |
512 | is changed after calling this function but prior to setting the loader \l active. |
513 | |
514 | Example: |
515 | \table 70% |
516 | \row |
517 | \li |
518 | \qml |
519 | // ExampleComponent.qml |
520 | import QtQuick 2.0 |
521 | Rectangle { |
522 | id: rect |
523 | color: "red" |
524 | width: 10 |
525 | height: 10 |
526 | |
527 | Behavior on color { |
528 | NumberAnimation { |
529 | target: rect |
530 | property: "width" |
531 | to: (rect.width + 20) |
532 | duration: 0 |
533 | } |
534 | } |
535 | } |
536 | \endqml |
537 | \li |
538 | \qml |
539 | // example.qml |
540 | import QtQuick 2.0 |
541 | Item { |
542 | Loader { |
543 | id: squareLoader |
544 | onLoaded: console.log(squareLoader.item.width); |
545 | // prints [10], not [30] |
546 | } |
547 | |
548 | Component.onCompleted: { |
549 | squareLoader.setSource("ExampleComponent.qml", |
550 | { "color": "blue" }); |
551 | // will trigger the onLoaded code when complete. |
552 | } |
553 | } |
554 | \endqml |
555 | \endtable |
556 | |
557 | \sa source, active |
558 | */ |
559 | void QQuickLoader::setSource(const QUrl &source, QJSValue properties) |
560 | { |
561 | Q_D(QQuickLoader); |
562 | |
563 | if (!(properties.isArray() || properties.isObject())) { |
564 | qmlWarning(me: this) << QQuickLoader::tr(s: "setSource: value is not an object" ); |
565 | return; |
566 | } |
567 | |
568 | QUrl sourceUrl = setSourceUrlHelper(source); |
569 | |
570 | d->disposeInitialPropertyValues(); |
571 | auto engine = qmlEngine(this)->handle(); |
572 | d->initialPropertyValues.set(engine, value: QJSValuePrivate::takeManagedValue(jsval: &properties)->asReturnedValue()); |
573 | d->qmlCallingContext.set(engine, obj: engine->qmlContext()); |
574 | |
575 | setSource(url: sourceUrl, needsClear: false); // already cleared and set ipv above. |
576 | } |
577 | |
578 | void QQuickLoader::setSource(const QUrl &source) |
579 | { |
580 | Q_D(QQuickLoader); |
581 | |
582 | QUrl sourceUrl = setSourceUrlHelper(source); |
583 | |
584 | d->disposeInitialPropertyValues(); |
585 | auto engine = qmlEngine(this)->handle(); |
586 | d->qmlCallingContext.set(engine, obj: engine->qmlContext()); |
587 | |
588 | setSource(url: sourceUrl, needsClear: false); // already cleared and set ipv above. |
589 | } |
590 | |
591 | void QQuickLoaderPrivate::disposeInitialPropertyValues() |
592 | { |
593 | initialPropertyValues.clear(); |
594 | } |
595 | |
596 | void QQuickLoaderPrivate::load() |
597 | { |
598 | Q_Q(QQuickLoader); |
599 | |
600 | if (!q->isComponentComplete() || !component) |
601 | return; |
602 | |
603 | if (!component->isLoading()) { |
604 | _q_sourceLoaded(); |
605 | } else { |
606 | QObject::connect(sender: component, SIGNAL(statusChanged(QQmlComponent::Status)), |
607 | receiver: q, SLOT(_q_sourceLoaded())); |
608 | QObject::connect(sender: component, SIGNAL(progressChanged(qreal)), |
609 | receiver: q, SIGNAL(progressChanged())); |
610 | updateStatus(); |
611 | emit q->progressChanged(); |
612 | if (loadingFromSource) |
613 | emit q->sourceChanged(); |
614 | else |
615 | emit q->sourceComponentChanged(); |
616 | emit q->itemChanged(); |
617 | } |
618 | } |
619 | |
620 | void QQuickLoaderIncubator::setInitialState(QObject *o) |
621 | { |
622 | loader->setInitialState(o); |
623 | } |
624 | |
625 | void QQuickLoaderPrivate::setInitialState(QObject *obj) |
626 | { |
627 | Q_Q(QQuickLoader); |
628 | |
629 | QQuickItem *item = qmlobject_cast<QQuickItem*>(object: obj); |
630 | if (item) { |
631 | // If the item doesn't have an explicit size, but the Loader |
632 | // does, then set the item's size now before bindings are |
633 | // evaluated, otherwise we will end up resizing the item |
634 | // later and triggering any affected bindings/anchors. |
635 | if (widthValid() && !QQuickItemPrivate::get(item)->widthValid()) |
636 | item->setWidth(q->width()); |
637 | if (heightValid() && !QQuickItemPrivate::get(item)->heightValid()) |
638 | item->setHeight(q->height()); |
639 | item->setParentItem(q); |
640 | } |
641 | if (obj) { |
642 | if (itemContext) |
643 | QQml_setParent_noEvent(object: itemContext, parent: obj); |
644 | QQml_setParent_noEvent(object: obj, parent: q); |
645 | itemContext = nullptr; |
646 | } |
647 | |
648 | if (initialPropertyValues.isUndefined()) |
649 | return; |
650 | |
651 | QQmlComponentPrivate *d = QQmlComponentPrivate::get(c: component); |
652 | Q_ASSERT(d && d->engine); |
653 | QV4::ExecutionEngine *v4 = d->engine->handle(); |
654 | Q_ASSERT(v4); |
655 | QV4::Scope scope(v4); |
656 | QV4::ScopedValue ipv(scope, initialPropertyValues.value()); |
657 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, qmlCallingContext.value()); |
658 | auto incubatorPriv = QQmlIncubatorPrivate::get(incubator); |
659 | d->initializeObjectWithInitialProperties(qmlContext, valuemap: ipv, toCreate: obj, requiredProperties: incubatorPriv->requiredProperties()); |
660 | } |
661 | |
662 | void QQuickLoaderIncubator::statusChanged(Status status) |
663 | { |
664 | loader->incubatorStateChanged(status); |
665 | } |
666 | |
667 | void QQuickLoaderPrivate::incubatorStateChanged(QQmlIncubator::Status status) |
668 | { |
669 | Q_Q(QQuickLoader); |
670 | if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null) |
671 | return; |
672 | |
673 | if (status == QQmlIncubator::Ready) { |
674 | object = incubator->object(); |
675 | item = qmlobject_cast<QQuickItem*>(object); |
676 | if (!item) { |
677 | QQuickWindow *window = qmlobject_cast<QQuickWindow*>(object); |
678 | if (window) { |
679 | qCDebug(lcTransient) << window << "is transient for" << q->window(); |
680 | window->setTransientParent(q->window()); |
681 | } |
682 | } |
683 | emit q->itemChanged(); |
684 | initResize(); |
685 | incubator->clear(); |
686 | } else if (status == QQmlIncubator::Error) { |
687 | if (!incubator->errors().isEmpty()) |
688 | QQmlEnginePrivate::warning(qmlEngine(q), incubator->errors()); |
689 | delete itemContext; |
690 | itemContext = nullptr; |
691 | delete incubator->object(); |
692 | source = QUrl(); |
693 | emit q->itemChanged(); |
694 | } |
695 | if (loadingFromSource) |
696 | emit q->sourceChanged(); |
697 | else |
698 | emit q->sourceComponentChanged(); |
699 | updateStatus(); |
700 | emit q->progressChanged(); |
701 | if (status == QQmlIncubator::Ready) |
702 | emit q->loaded(); |
703 | } |
704 | |
705 | void QQuickLoaderPrivate::_q_sourceLoaded() |
706 | { |
707 | Q_Q(QQuickLoader); |
708 | if (!component || !component->errors().isEmpty()) { |
709 | if (component) |
710 | QQmlEnginePrivate::warning(qmlEngine(q), component->errors()); |
711 | if (loadingFromSource) |
712 | emit q->sourceChanged(); |
713 | else |
714 | emit q->sourceComponentChanged(); |
715 | updateStatus(); |
716 | emit q->progressChanged(); |
717 | emit q->itemChanged(); //Like clearing source, emit itemChanged even if previous item was also null |
718 | disposeInitialPropertyValues(); // cleanup |
719 | return; |
720 | } |
721 | |
722 | if (!active) |
723 | return; |
724 | |
725 | QQmlContext *creationContext = component->creationContext(); |
726 | if (!creationContext) |
727 | creationContext = qmlContext(q); |
728 | |
729 | QQmlComponentPrivate *cp = QQmlComponentPrivate::get(c: component); |
730 | QQmlContext *context = [&](){ |
731 | if (cp->isBound()) |
732 | return creationContext; |
733 | itemContext = new QQmlContext(creationContext); |
734 | itemContext->setContextObject(q); |
735 | return itemContext; |
736 | }(); |
737 | |
738 | delete incubator; |
739 | incubator = new QQuickLoaderIncubator(this, asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); |
740 | |
741 | component->create(*incubator, context); |
742 | |
743 | if (incubator && incubator->status() == QQmlIncubator::Loading) |
744 | updateStatus(); |
745 | } |
746 | |
747 | /*! |
748 | \qmlproperty enumeration QtQuick::Loader::status |
749 | |
750 | This property holds the status of QML loading. It can be one of: |
751 | \list |
752 | \li Loader.Null - the loader is inactive or no QML source has been set |
753 | \li Loader.Ready - the QML source has been loaded |
754 | \li Loader.Loading - the QML source is currently being loaded |
755 | \li Loader.Error - an error occurred while loading the QML source |
756 | \endlist |
757 | |
758 | Use this status to provide an update or respond to the status change in some way. |
759 | For example, you could: |
760 | |
761 | \list |
762 | \li Trigger a state change: |
763 | \qml |
764 | State { name: 'loaded'; when: loader.status == Loader.Ready } |
765 | \endqml |
766 | |
767 | \li Implement an \c onStatusChanged signal handler: |
768 | \qml |
769 | Loader { |
770 | id: loader |
771 | onStatusChanged: if (loader.status == Loader.Ready) console.log('Loaded') |
772 | } |
773 | \endqml |
774 | |
775 | \li Bind to the status value: |
776 | \qml |
777 | Text { text: loader.status == Loader.Ready ? 'Loaded' : 'Not loaded' } |
778 | \endqml |
779 | \endlist |
780 | |
781 | Note that if the source is a local file, the status will initially be Ready (or Error). While |
782 | there will be no onStatusChanged signal in that case, the onLoaded will still be invoked. |
783 | |
784 | \sa progress |
785 | */ |
786 | |
787 | QQuickLoader::Status QQuickLoader::status() const |
788 | { |
789 | Q_D(const QQuickLoader); |
790 | |
791 | return static_cast<Status>(d->status); |
792 | } |
793 | |
794 | void QQuickLoader::componentComplete() |
795 | { |
796 | Q_D(QQuickLoader); |
797 | QQuickItem::componentComplete(); |
798 | if (active() && (status() != Ready)) { |
799 | if (d->loadingFromSource) |
800 | d->createComponent(); |
801 | d->load(); |
802 | } |
803 | } |
804 | |
805 | void QQuickLoader::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
806 | { |
807 | switch (change) { |
808 | case ItemSceneChange: { |
809 | QQuickWindow *loadedWindow = qmlobject_cast<QQuickWindow *>(object: item()); |
810 | if (loadedWindow) { |
811 | qCDebug(lcTransient) << loadedWindow << "is transient for" << value.window; |
812 | loadedWindow->setTransientParent(value.window); |
813 | } |
814 | break; |
815 | } |
816 | case ItemChildAddedChange: |
817 | Q_ASSERT(value.item); |
818 | if (value.item->flags().testFlag(flag: QQuickItem::ItemObservesViewport)) |
819 | // Re-trigger the parent traversal to get subtreeTransformChangedEnabled turned on |
820 | value.item->setFlag(flag: QQuickItem::ItemObservesViewport); |
821 | break; |
822 | default: |
823 | break; |
824 | } |
825 | QQuickItem::itemChange(change, value); |
826 | } |
827 | |
828 | /*! |
829 | \qmlsignal QtQuick::Loader::loaded() |
830 | |
831 | This signal is emitted when the \l status becomes \c Loader.Ready, or on successful |
832 | initial load. |
833 | */ |
834 | |
835 | |
836 | /*! |
837 | \qmlproperty real QtQuick::Loader::progress |
838 | |
839 | This property holds the progress of loading QML data from the network, from |
840 | 0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so |
841 | this value will rapidly change from 0 to 1. |
842 | |
843 | \sa status |
844 | */ |
845 | qreal QQuickLoader::progress() const |
846 | { |
847 | Q_D(const QQuickLoader); |
848 | |
849 | if (d->object) |
850 | return 1.0; |
851 | |
852 | if (d->component) |
853 | return d->component->progress(); |
854 | |
855 | return 0.0; |
856 | } |
857 | |
858 | /*! |
859 | \qmlproperty bool QtQuick::Loader::asynchronous |
860 | |
861 | This property holds whether the component will be instantiated asynchronously. |
862 | By default it is \c false. |
863 | |
864 | When used in conjunction with the \l source property, loading and compilation |
865 | will also be performed in a background thread. |
866 | |
867 | Loading asynchronously creates the objects declared by the component |
868 | across multiple frames, and reduces the |
869 | likelihood of glitches in animation. When loading asynchronously the status |
870 | will change to Loader.Loading. Once the entire component has been created, the |
871 | \l item will be available and the status will change to Loader.Ready. |
872 | |
873 | Changing the value of this property to \c false while an asynchronous load is in |
874 | progress will force immediate, synchronous completion. This allows beginning an |
875 | asynchronous load and then forcing completion if the Loader content must be |
876 | accessed before the asynchronous load has completed. |
877 | |
878 | To avoid seeing the items loading progressively set \c visible appropriately, e.g. |
879 | |
880 | \code |
881 | Loader { |
882 | source: "mycomponent.qml" |
883 | asynchronous: true |
884 | visible: status == Loader.Ready |
885 | } |
886 | \endcode |
887 | |
888 | Note that this property affects object instantiation only; it is unrelated to |
889 | loading a component asynchronously via a network. |
890 | */ |
891 | bool QQuickLoader::asynchronous() const |
892 | { |
893 | Q_D(const QQuickLoader); |
894 | return d->asynchronous; |
895 | } |
896 | |
897 | void QQuickLoader::setAsynchronous(bool a) |
898 | { |
899 | Q_D(QQuickLoader); |
900 | if (d->asynchronous == a) |
901 | return; |
902 | |
903 | d->asynchronous = a; |
904 | |
905 | if (!d->asynchronous && isComponentComplete() && d->active) { |
906 | if (d->loadingFromSource && d->component && d->component->isLoading()) { |
907 | // Force a synchronous component load |
908 | QUrl currentSource = d->source; |
909 | d->clear(); |
910 | d->source = currentSource; |
911 | loadFromSource(); |
912 | } else if (d->incubator && d->incubator->isLoading()) { |
913 | d->incubator->forceCompletion(); |
914 | } |
915 | } |
916 | |
917 | emit asynchronousChanged(); |
918 | } |
919 | |
920 | void QQuickLoaderPrivate::_q_updateSize(bool loaderGeometryChanged) |
921 | { |
922 | Q_Q(QQuickLoader); |
923 | if (!item) |
924 | return; |
925 | |
926 | const bool needToUpdateWidth = loaderGeometryChanged && q->widthValid(); |
927 | const bool needToUpdateHeight = loaderGeometryChanged && q->heightValid(); |
928 | |
929 | if (needToUpdateWidth && needToUpdateHeight) |
930 | item->setSize(QSizeF(q->width(), q->height())); |
931 | else if (needToUpdateWidth) |
932 | item->setWidth(q->width()); |
933 | else if (needToUpdateHeight) |
934 | item->setHeight(q->height()); |
935 | |
936 | if (updatingSize) |
937 | return; |
938 | |
939 | updatingSize = true; |
940 | |
941 | q->setImplicitSize(getImplicitWidth(), getImplicitHeight()); |
942 | |
943 | updatingSize = false; |
944 | } |
945 | |
946 | /*! |
947 | \qmlproperty QtObject QtQuick::Loader::item |
948 | This property holds the top-level object that is currently loaded. |
949 | |
950 | Since \c {QtQuick 2.0}, Loader can load any object type. |
951 | */ |
952 | QObject *QQuickLoader::item() const |
953 | { |
954 | Q_D(const QQuickLoader); |
955 | return d->object; |
956 | } |
957 | |
958 | void QQuickLoader::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
959 | { |
960 | Q_D(QQuickLoader); |
961 | if (newGeometry != oldGeometry) { |
962 | d->_q_updateSize(); |
963 | } |
964 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
965 | } |
966 | |
967 | QQuickLoader::Status QQuickLoaderPrivate::computeStatus() const |
968 | { |
969 | if (!active) |
970 | return QQuickLoader::Status::Null; |
971 | |
972 | if (component) { |
973 | switch (component->status()) { |
974 | case QQmlComponent::Loading: |
975 | return QQuickLoader::Status::Loading; |
976 | case QQmlComponent::Error: |
977 | return QQuickLoader::Status::Error; |
978 | case QQmlComponent::Null: |
979 | return QQuickLoader::Status::Null; |
980 | default: |
981 | break; |
982 | } |
983 | } |
984 | |
985 | if (incubator) { |
986 | switch (incubator->status()) { |
987 | case QQmlIncubator::Loading: |
988 | return QQuickLoader::Status::Loading; |
989 | case QQmlIncubator::Error: |
990 | return QQuickLoader::Status::Error; |
991 | default: |
992 | break; |
993 | } |
994 | } |
995 | |
996 | if (object) |
997 | return QQuickLoader::Status::Ready; |
998 | |
999 | return source.isEmpty() ? QQuickLoader::Status::Null : QQuickLoader::Status::Error; |
1000 | } |
1001 | |
1002 | void QQuickLoaderPrivate::updateStatus() |
1003 | { |
1004 | Q_Q(QQuickLoader); |
1005 | auto newStatus = computeStatus(); |
1006 | if (status != newStatus) { |
1007 | status = newStatus; |
1008 | emit q->statusChanged(); |
1009 | } |
1010 | } |
1011 | |
1012 | void QQuickLoaderPrivate::createComponent() |
1013 | { |
1014 | Q_Q(QQuickLoader); |
1015 | const QQmlComponent::CompilationMode mode = asynchronous |
1016 | ? QQmlComponent::Asynchronous |
1017 | : QQmlComponent::PreferSynchronous; |
1018 | if (QQmlContext *context = qmlContext(q)) { |
1019 | if (QQmlEngine *engine = context->engine()) { |
1020 | component.setObject(obj: new QQmlComponent( |
1021 | engine, context->resolvedUrl(source), mode, q), parent: q); |
1022 | return; |
1023 | } |
1024 | } |
1025 | |
1026 | qmlWarning(me: q) << "createComponent: Cannot find a QML engine." ; |
1027 | } |
1028 | |
1029 | QT_END_NAMESPACE |
1030 | |
1031 | #include <moc_qquickloader_p.cpp> |
1032 | |