Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dloader.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
9
10#include <QtQml/qqmlinfo.h>
11
12#include <private/qqmlengine_p.h>
13#include <private/qqmlglobal_p.h>
14
15#include <private/qqmlcomponent_p.h>
16#include <private/qqmlincubator_p.h>
17
19
20void QQuick3DLoaderIncubator::statusChanged(QQmlIncubator::Status status)
21{
22 m_loader->incubatorStateChanged(status);
23}
24
26{
27 m_loader->setInitialState(o);
28}
29
30/*!
31 \qmltype Loader3D
32 \inqmlmodule QtQuick3D
33 \inherits Node
34
35 \brief Allows dynamic loading of a 3D subtree from a URL or Component.
36
37 Loader3D is used to dynamically load QML components for Qt Quick 3D.
38
39 Loader3D can load a
40 QML file (using the \l source property) or a \l Component object (using
41 the \l sourceComponent property). It is useful for delaying the creation
42 of a component until it is required: for example, when a component should
43 be created on demand, or when a component should not be created
44 unnecessarily for performance reasons.
45
46 \note Loader3D works the same way as \l Loader. The difference between the
47 two is that \l Loader provides a way to dynamically load objects that inherit
48 \l Item, whereas Loader3D provides a way to load objects that inherit \l Object3D
49 and is part of a 3D scene.
50*/
51
52QQuick3DLoader::QQuick3DLoader(QQuick3DNode *parent)
53 : QQuick3DNode(parent)
54 , m_item(nullptr)
55 , m_object(nullptr)
56 , m_itemContext(nullptr)
57 , m_incubator(nullptr)
58 , m_active(true)
59 , m_loadingFromSource(false)
60 , m_asynchronous(false)
61{
62}
63
64QQuick3DLoader::~QQuick3DLoader()
65{
66 clear();
67 delete m_incubator;
68 m_incubator = nullptr;
69}
70
71/*!
72 \qmlproperty bool QtQuick3D::Loader3D::active
73 This property is \c true if the Loader3D is currently active.
74 The default value for this property is \c true.
75
76 If the Loader3D is inactive, changing the \l source or \l sourceComponent
77 will not cause the item to be instantiated until the Loader3D is made active.
78
79 Setting the value to inactive will cause any \l item loaded by the loader
80 to be released, but will not affect the \l source or \l sourceComponent.
81
82 The \l status of an inactive loader is always \c Null.
83
84 \sa source, sourceComponent
85 */
86
87bool QQuick3DLoader::active() const
88{
89 return m_active;
90}
91
92void QQuick3DLoader::setActive(bool newVal)
93{
94 if (m_active == newVal)
95 return;
96
97 m_active = newVal;
98 if (newVal) {
99 if (m_loadingFromSource) {
100 loadFromSource();
101 } else {
102 loadFromSourceComponent();
103 }
104 } else {
105 // cancel any current incubation
106 if (m_incubator) {
107 m_incubator->clear();
108 delete m_itemContext;
109 m_itemContext = nullptr;
110 }
111
112 // Prevent any bindings from running while waiting for deletion. Without
113 // this we may get transient errors from use of 'parent', for example.
114 QQmlContext *context = qmlContext(m_object);
115 if (context)
116 QQmlContextData::get(context)->clearContextRecursively();
117
118 if (m_item) {
119 // We can't delete immediately because our item may have triggered
120 // the Loader to load a different item.
121 m_item->setParentItem(nullptr);
122 m_item->setVisible(false);
123 m_item = nullptr;
124 }
125 if (m_object) {
126 m_object->deleteLater();
127 m_object = nullptr;
128 emit itemChanged();
129 }
130 emit statusChanged();
131 }
132 emit activeChanged();
133}
134
135void QQuick3DLoader::setSource(QQmlV4FunctionPtr args)
136{
137 bool ipvError = false;
138 args->setReturnValue(QV4::Encode::undefined());
139 QV4::Scope scope(args->v4engine());
140 QV4::ScopedValue ipv(scope, extractInitialPropertyValues(args, &ipvError));
141 if (ipvError)
142 return;
143
144 clear();
145 QUrl sourceUrl = resolveSourceUrl(args);
146 if (!ipv->isUndefined()) {
147 disposeInitialPropertyValues();
148 m_initialPropertyValues.set(args->v4engine(), ipv);
149 }
150 m_qmlCallingContext.set(scope.engine, scope.engine->qmlContext());
151
152 setSource(sourceUrl, false); // already cleared and set ipv above.
153}
154
155/*!
156 \qmlproperty url QtQuick3D::Loader3D::source
157 This property holds the URL of the QML component to instantiate.
158
159 To unload the currently loaded object, set this property to an empty string,
160 or set \l sourceComponent to \c undefined. Setting \c source to a
161 new URL will also cause the item created by the previous URL to be unloaded.
162
163 \sa sourceComponent, status, progress
164*/
165
166QUrl QQuick3DLoader::source() const
167{
168 return m_source;
169}
170
171void QQuick3DLoader::setSource(const QUrl &url)
172{
173 setSource(url, true);
174}
175
176/*!
177 \qmlproperty Component QtQuick3D::Loader3D::sourceComponent
178 This property holds the \l{Component} to instantiate.
179
180 \qml
181 Item {
182 Component {
183 id: redCube
184 Model {
185 source: "#Cube"
186 materials: DefaultMaterial {
187 diffuseColor: "red"
188 }
189 }
190 }
191
192 Loader3D { sourceComponent: redCube }
193 Loader3D { sourceComponent: redCube; x: 10 }
194 }
195 \endqml
196
197 To unload the currently loaded object, set this property to \c undefined.
198
199 \sa source, progress
200*/
201
202/*!
203 \qmlmethod object QtQuick3D::Loader3D::setSource(url source, object properties)
204
205 Creates an object instance of the given \a source component that will have
206 the given \a properties. The \a properties argument is optional. The instance
207 will be accessible via the \l item property once loading and instantiation
208 is complete.
209
210 If the \l active property is \c false at the time when this function is called,
211 the given \a source component will not be loaded but the \a source and initial
212 \a properties will be cached. When the loader is made \l active, an instance of
213 the \a source component will be created with the initial \a properties set.
214
215 Setting the initial property values of an instance of a component in this manner
216 will \b{not} trigger any associated \l{Behavior}s.
217
218 Note that the cached \a properties will be cleared if the \l source or \l sourceComponent
219 is changed after calling this function but prior to setting the loader \l active.
220
221 \sa source, active
222*/
223
224QQmlComponent *QQuick3DLoader::sourceComponent() const
225{
226 return m_component;
227}
228
229void QQuick3DLoader::setSourceComponent(QQmlComponent *comp)
230{
231 if (comp == m_component)
232 return;
233
234 clear();
235
236 m_component.setObject(comp, this);
237 m_loadingFromSource = false;
238
239 if (m_active)
240 loadFromSourceComponent();
241 else
242 emit sourceComponentChanged();
243}
244
245void QQuick3DLoader::resetSourceComponent()
246{
247 setSourceComponent(nullptr);
248}
249
250/*!
251 \qmlproperty enumeration QtQuick3D::Loader3D::status
252 \readonly
253
254 This property holds the status of QML loading. It can be one of:
255
256 \value Loader3D.Null The loader is inactive or no QML source has been set.
257 \value Loader3D.Ready The QML source has been loaded.
258 \value Loader3D.Loading The QML source is currently being loaded.
259 \value Loader3D.Error An error occurred while loading the QML source.
260
261 Use this status to provide an update or respond to the status change in some way.
262 For example, you could:
263
264 \list
265 \li Trigger a state change:
266 \qml
267 State { name: 'loaded'; when: loader.status == Loader3D.Ready }
268 \endqml
269
270 \li Implement an \c onStatusChanged signal handler:
271 \qml
272 Loader3D {
273 id: loader
274 onStatusChanged: if (loader.status == Loader3D.Ready) console.log('Loaded')
275 }
276 \endqml
277
278 \li Bind to the status value:
279 \qml
280 Text { text: loader.status == Loader3D.Ready ? 'Loaded' : 'Not loaded' }
281 \endqml
282 \endlist
283
284 Note that if the source is a local file, the status will initially be Ready (or Error). While
285 there will be no onStatusChanged signal in that case, the onLoaded will still be invoked.
286
287 \sa progress
288*/
289
290QQuick3DLoader::Status QQuick3DLoader::status() const
291{
292 if (!m_active)
293 return Null;
294
295 if (m_component) {
296 switch (m_component->status()) {
297 case QQmlComponent::Loading:
298 return Loading;
299 case QQmlComponent::Error:
300 return Error;
301 case QQmlComponent::Null:
302 return Null;
303 default:
304 break;
305 }
306 }
307
308 if (m_incubator) {
309 switch (m_incubator->status()) {
310 case QQmlIncubator::Loading:
311 return Loading;
312 case QQmlIncubator::Error:
313 return Error;
314 default:
315 break;
316 }
317 }
318
319 if (m_object)
320 return Ready;
321
322 return m_source.isEmpty() ? Null : Error;
323}
324
325/*!
326 \qmlsignal QtQuick3D::Loader3D::loaded()
327
328 This signal is emitted when the \l status becomes \c Loader3D.Ready, or on successful
329 initial load.
330
331 The corresponding handler is \c onLoaded.
332*/
333
334
335/*!
336 \qmlproperty real QtQuick3D::Loader3D::progress
337 \readonly
338
339 This property holds the progress of loading QML data from the network, from
340 0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so
341 this value will rapidly change from 0 to 1.
342
343 \sa status
344*/
345
346qreal QQuick3DLoader::progress() const
347{
348
349 if (m_object)
350 return 1.0;
351
352 if (m_component)
353 return m_component->progress();
354
355 return 0.0;
356}
357
358/*!
359\qmlproperty bool QtQuick3D::Loader3D::asynchronous
360
361This property holds whether the component will be instantiated asynchronously.
362By default it is \c false.
363
364When used in conjunction with the \l source property, loading and compilation
365will also be performed in a background thread.
366
367Loading asynchronously creates the objects declared by the component
368across multiple frames, and reduces the
369likelihood of glitches in animation. When loading asynchronously the status
370will change to Loader3D.Loading. Once the entire component has been created, the
371\l item will be available and the status will change to Loader.Ready.
372
373Changing the value of this property to \c false while an asynchronous load is in
374progress will force immediate, synchronous completion. This allows beginning an
375asynchronous load and then forcing completion if the Loader3D content must be
376accessed before the asynchronous load has completed.
377
378To avoid seeing the items loading progressively set \c visible appropriately, e.g.
379
380\code
381Loader3D {
382 source: "mycomponent.qml"
383 asynchronous: true
384 visible: status == Loader3D.Ready
385}
386\endcode
387
388Note that this property affects object instantiation only; it is unrelated to
389loading a component asynchronously via a network.
390*/
391
392bool QQuick3DLoader::asynchronous() const
393{
394 return m_asynchronous;
395}
396
397void QQuick3DLoader::setAsynchronous(bool a)
398{
399 if (m_asynchronous == a)
400 return;
401
402 m_asynchronous = a;
403
404 if (!m_asynchronous && isComponentComplete() && m_active) {
405 if (m_loadingFromSource && m_component && m_component->isLoading()) {
406 // Force a synchronous component load
407 QUrl currentSource = m_source;
408 clear();
409 m_source = currentSource;
410 loadFromSource();
411 } else if (m_incubator && m_incubator->isLoading()) {
412 m_incubator->forceCompletion();
413 }
414 }
415
416 emit asynchronousChanged();
417}
418
419/*!
420 \qmlproperty object QtQuick3D::Loader3D::item
421 \readonly
422 This property holds the top-level object that is currently loaded.
423*/
424QObject *QQuick3DLoader::item() const
425{
426 return m_object;
427}
428
429void QQuick3DLoader::componentComplete()
430{
431 QQuick3DNode::componentComplete();
432 if (active()) {
433 if (m_loadingFromSource)
434 createComponent();
435 load();
436 }
437}
438
439void QQuick3DLoader::sourceLoaded()
440{
441 if (!m_component || !m_component->errors().isEmpty()) {
442 if (m_component)
443 QQmlEnginePrivate::warning(qmlEngine(this), m_component->errors());
444 if (m_loadingFromSource)
445 emit sourceChanged();
446 else
447 emit sourceComponentChanged();
448 emit statusChanged();
449 emit progressChanged();
450 emit itemChanged(); //Like clearing source, emit itemChanged even if previous item was also null
451 disposeInitialPropertyValues(); // cleanup
452 return;
453 }
454
455 QQmlContext *creationContext = m_component->creationContext();
456 if (!creationContext)
457 creationContext = qmlContext(this);
458
459 QQmlComponentPrivate *cp = QQmlComponentPrivate::get(m_component);
460 QQmlContext *context = [&](){
461 if (cp->isBound())
462 return creationContext;
463 m_itemContext = new QQmlContext(creationContext);
464 m_itemContext->setContextObject(this);
465 return m_itemContext;
466 }();
467
468 delete m_incubator;
469 m_incubator = new QQuick3DLoaderIncubator(this, m_asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
470
471 m_component->create(*m_incubator, context);
472
473 if (m_incubator && m_incubator->status() == QQmlIncubator::Loading)
474 emit statusChanged();
475}
476
477void QQuick3DLoader::setSource(const QUrl &sourceUrl, bool needsClear)
478{
479 if (m_source == sourceUrl)
480 return;
481
482 if (needsClear)
483 clear();
484
485 m_source = sourceUrl;
486 m_loadingFromSource = true;
487
488 if (m_active)
489 loadFromSource();
490 else
491 emit sourceChanged();
492}
493
494void QQuick3DLoader::loadFromSource()
495{
496 if (m_source.isEmpty()) {
497 emit sourceChanged();
498 emit statusChanged();
499 emit progressChanged();
500 emit itemChanged();
501 return;
502 }
503
504 if (isComponentComplete()) {
505 if (!m_component)
506 createComponent();
507 load();
508 }
509}
510
511void QQuick3DLoader::loadFromSourceComponent()
512{
513 if (!m_component) {
514 emit sourceComponentChanged();
515 emit statusChanged();
516 emit progressChanged();
517 emit itemChanged();
518 return;
519 }
520
521 if (isComponentComplete())
522 load();
523}
524
525void QQuick3DLoader::clear()
526{
527 disposeInitialPropertyValues();
528
529 if (m_incubator)
530 m_incubator->clear();
531
532 delete m_itemContext;
533 m_itemContext = nullptr;
534
535 // Prevent any bindings from running while waiting for deletion. Without
536 // this we may get transient errors from use of 'parent', for example.
537 QQmlContext *context = qmlContext(m_object);
538 if (context)
539 QQmlContextData::get(context)->clearContextRecursively();
540
541 if (m_loadingFromSource && m_component) {
542 // disconnect since we deleteLater
543 QObject::disconnect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
544 this, SLOT(sourceLoaded()));
545 QObject::disconnect(m_component, SIGNAL(progressChanged(qreal)),
546 this, SIGNAL(progressChanged()));
547 m_component->deleteLater();
548 m_component.setObject(nullptr, this);
549 } else if (m_component) {
550 m_component.setObject(nullptr, this);
551 }
552 m_source = QUrl();
553
554 if (m_item) {
555 // We can't delete immediately because our item may have triggered
556 // the Loader to load a different item.
557 m_item->setParentItem(nullptr);
558 m_item->setVisible(false);
559 m_item = nullptr;
560 }
561 if (m_object) {
562 m_object->deleteLater();
563 m_object = nullptr;
564 }
565}
566
567void QQuick3DLoader::load()
568{
569
570 if (!isComponentComplete() || !m_component)
571 return;
572
573 if (!m_component->isLoading()) {
574 sourceLoaded();
575 } else {
576 QObject::connect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
577 this, SLOT(sourceLoaded()));
578 QObject::connect(m_component, SIGNAL(progressChanged(qreal)),
579 this, SIGNAL(progressChanged()));
580 emit statusChanged();
581 emit progressChanged();
582 if (m_loadingFromSource)
583 emit sourceChanged();
584 else
585 emit sourceComponentChanged();
586 emit itemChanged();
587 }
588}
589
590void QQuick3DLoader::incubatorStateChanged(QQmlIncubator::Status status)
591{
592 if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null)
593 return;
594
595 if (status == QQmlIncubator::Ready) {
596 m_object = m_incubator->object();
597 m_item = qmlobject_cast<QQuick3DNode*>(m_object);
598 emit itemChanged();
599 m_incubator->clear();
600 } else if (status == QQmlIncubator::Error) {
601 if (!m_incubator->errors().isEmpty())
602 QQmlEnginePrivate::warning(qmlEngine(this), m_incubator->errors());
603 delete m_itemContext;
604 m_itemContext = nullptr;
605 delete m_incubator->object();
606 m_source = QUrl();
607 emit itemChanged();
608 }
609 if (m_loadingFromSource)
610 emit sourceChanged();
611 else
612 emit sourceComponentChanged();
613 emit statusChanged();
614 emit progressChanged();
615 if (status == QQmlIncubator::Ready)
616 emit loaded();
617 disposeInitialPropertyValues(); // cleanup
618}
619
620void QQuick3DLoader::setInitialState(QObject *obj)
621{
622 QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(obj);
623 if (item) {
624 item->setParentItem(this);
625 }
626 if (obj) {
627 if (m_itemContext)
628 QQml_setParent_noEvent(m_itemContext, obj);
629 QQml_setParent_noEvent(obj, this);
630 m_itemContext = nullptr;
631 }
632
633 if (m_initialPropertyValues.isUndefined())
634 return;
635
636 QQmlComponentPrivate *d = QQmlComponentPrivate::get(m_component);
637 Q_ASSERT(d && d->engine());
638 QV4::ExecutionEngine *v4 = d->engine()->handle();
639 Q_ASSERT(v4);
640 QV4::Scope scope(v4);
641 QV4::ScopedValue ipv(scope, m_initialPropertyValues.value());
642 QV4::Scoped<QV4::QmlContext> qmlContext(scope, m_qmlCallingContext.value());
643 d->initializeObjectWithInitialProperties(qmlContext, ipv, obj, QQmlIncubatorPrivate::get(m_incubator)->requiredProperties());
644}
645
646void QQuick3DLoader::disposeInitialPropertyValues()
647{
648
649}
650
651QUrl QQuick3DLoader::resolveSourceUrl(QQmlV4FunctionPtr args)
652{
653 QV4::Scope scope(args->v4engine());
654 QV4::ScopedValue v(scope, (*args)[0]);
655 QString arg = v->toQString();
656 if (arg.isEmpty())
657 return QUrl();
658
659 auto context = scope.engine->callingQmlContext();
660 Q_ASSERT(!context.isNull());
661 return context->resolvedUrl(QUrl(arg));
662}
663
664QV4::ReturnedValue QQuick3DLoader::extractInitialPropertyValues(QQmlV4FunctionPtr args, bool *error)
665{
666 QV4::Scope scope(args->v4engine());
667 QV4::ScopedValue valuemap(scope, QV4::Encode::undefined());
668 if (args->length() >= 2) {
669 QV4::ScopedValue v(scope, (*args)[1]);
670 if (!v->isObject() || v->as<QV4::ArrayObject>()) {
671 *error = true;
672 qmlWarning(this) << QQuick3DLoader::tr("setSource: value is not an object");
673 } else {
674 *error = false;
675 valuemap = v;
676 }
677 }
678
679 return valuemap->asReturnedValue();
680}
681
682void QQuick3DLoader::createComponent()
683{
684 const QQmlComponent::CompilationMode mode = m_asynchronous
685 ? QQmlComponent::Asynchronous
686 : QQmlComponent::PreferSynchronous;
687 QQmlContext *context = qmlContext(this);
688 m_component.setObject(new QQmlComponent(context->engine(),
689 context->resolvedUrl(m_source),
690 mode,
691 this),
692 this);
693}
694
695QT_END_NAMESPACE
696
697#include "moc_qquick3dloader_p.cpp"
void statusChanged(Status) override
Called when the status of the incubator changes.
void setInitialState(QObject *) override
Called after the object is first created, but before complex property bindings are evaluated and,...
Combined button and popup list for selecting options.