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