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 "qqmlcomponent.h" |
5 | #include "qqmlcomponent_p.h" |
6 | #include "qqmlcomponentattached_p.h" |
7 | |
8 | #include "qqmlengine_p.h" |
9 | #include "qqmlvme_p.h" |
10 | #include "qqml.h" |
11 | #include "qqmlengine.h" |
12 | #include "qqmlincubator.h" |
13 | #include "qqmlincubator_p.h" |
14 | #include <private/qqmljavascriptexpression_p.h> |
15 | #include <private/qqmlsourcecoordinate_p.h> |
16 | |
17 | #include <private/qv4functionobject_p.h> |
18 | #include <private/qv4script_p.h> |
19 | #include <private/qv4scopedvalue_p.h> |
20 | #include <private/qv4objectiterator_p.h> |
21 | #include <private/qv4qobjectwrapper_p.h> |
22 | #include <private/qv4jscall_p.h> |
23 | |
24 | #include <QDir> |
25 | #include <QStack> |
26 | #include <QStringList> |
27 | #include <QThreadStorage> |
28 | #include <QtCore/qdebug.h> |
29 | #include <QtCore/qloggingcategory.h> |
30 | #include <qqmlinfo.h> |
31 | |
32 | namespace { |
33 | Q_CONSTINIT thread_local int creationDepth = 0; |
34 | } |
35 | |
36 | Q_LOGGING_CATEGORY(lcQmlComponentGeneral, "qt.qml.qmlcomponent" ) |
37 | |
38 | QT_BEGIN_NAMESPACE |
39 | |
40 | class QQmlComponentExtension : public QV4::ExecutionEngine::Deletable |
41 | { |
42 | public: |
43 | QQmlComponentExtension(QV4::ExecutionEngine *v4); |
44 | virtual ~QQmlComponentExtension(); |
45 | |
46 | QV4::PersistentValue incubationProto; |
47 | }; |
48 | V4_DEFINE_EXTENSION(QQmlComponentExtension, componentExtension); |
49 | |
50 | /*! |
51 | \class QQmlComponent |
52 | \since 5.0 |
53 | \inmodule QtQml |
54 | |
55 | \brief The QQmlComponent class encapsulates a QML component definition. |
56 | |
57 | Components are reusable, encapsulated QML types with well-defined interfaces. |
58 | |
59 | A QQmlComponent instance can be created from a QML file. |
60 | For example, if there is a \c main.qml file like this: |
61 | |
62 | \qml |
63 | import QtQuick 2.0 |
64 | |
65 | Item { |
66 | width: 200 |
67 | height: 200 |
68 | } |
69 | \endqml |
70 | |
71 | The following code loads this QML file as a component, creates an instance of |
72 | this component using create(), and then queries the \l Item's \l {Item::}{width} |
73 | value: |
74 | |
75 | \code |
76 | QQmlEngine *engine = new QQmlEngine; |
77 | QQmlComponent component(engine, QUrl::fromLocalFile("main.qml")); |
78 | |
79 | QObject *myObject = component.create(); |
80 | QQuickItem *item = qobject_cast<QQuickItem*>(myObject); |
81 | int width = item->width(); // width = 200 |
82 | \endcode |
83 | |
84 | To create instances of a component in code where a QQmlEngine instance is |
85 | not available, you can use \l qmlContext() or \l qmlEngine(). For example, |
86 | in the scenario below, child items are being created within a QQuickItem |
87 | subclass: |
88 | |
89 | \code |
90 | void MyCppItem::init() |
91 | { |
92 | QQmlEngine *engine = qmlEngine(this); |
93 | // Or: |
94 | // QQmlEngine *engine = qmlContext(this)->engine(); |
95 | QQmlComponent component(engine, QUrl::fromLocalFile("MyItem.qml")); |
96 | QQuickItem *childItem = qobject_cast<QQuickItem*>(component.create()); |
97 | childItem->setParentItem(this); |
98 | } |
99 | \endcode |
100 | |
101 | Note that these functions will return \c null when called inside the |
102 | constructor of a QObject subclass, as the instance will not yet have |
103 | a context nor engine. |
104 | |
105 | \section2 Network Components |
106 | |
107 | If the URL passed to QQmlComponent is a network resource, or if the QML document references a |
108 | network resource, the QQmlComponent has to fetch the network data before it is able to create |
109 | objects. In this case, the QQmlComponent will have a \l {QQmlComponent::Loading}{Loading} |
110 | \l {QQmlComponent::status()}{status}. An application will have to wait until the component |
111 | is \l {QQmlComponent::Ready}{Ready} before calling \l {QQmlComponent::create()}. |
112 | |
113 | The following example shows how to load a QML file from a network resource. After creating |
114 | the QQmlComponent, it tests whether the component is loading. If it is, it connects to the |
115 | QQmlComponent::statusChanged() signal and otherwise calls the \c {continueLoading()} method |
116 | directly. Note that QQmlComponent::isLoading() may be false for a network component if the |
117 | component has been cached and is ready immediately. |
118 | |
119 | \code |
120 | MyApplication::MyApplication() |
121 | { |
122 | // ... |
123 | component = new QQmlComponent(engine, QUrl("http://www.example.com/main.qml")); |
124 | if (component->isLoading()) { |
125 | QObject::connect(component, &QQmlComponent::statusChanged, |
126 | this, &MyApplication::continueLoading); |
127 | } else { |
128 | continueLoading(); |
129 | } |
130 | } |
131 | |
132 | void MyApplication::continueLoading() |
133 | { |
134 | if (component->isError()) { |
135 | qWarning() << component->errors(); |
136 | } else { |
137 | QObject *myObject = component->create(); |
138 | } |
139 | } |
140 | \endcode |
141 | */ |
142 | |
143 | /*! |
144 | \qmltype Component |
145 | \instantiates QQmlComponent |
146 | \ingroup qml-utility-elements |
147 | \inqmlmodule QtQml |
148 | \brief Encapsulates a QML component definition. |
149 | |
150 | Components are reusable, encapsulated QML types with well-defined interfaces. |
151 | |
152 | Components are often defined by \l {{QML Documents}}{component files} - |
153 | that is, \c .qml files. The \e Component type essentially allows QML components |
154 | to be defined inline, within a \l {QML Documents}{QML document}, rather than as a separate QML file. |
155 | This may be useful for reusing a small component within a QML file, or for defining |
156 | a component that logically belongs with other QML components within a file. |
157 | |
158 | For example, here is a component that is used by multiple \l Loader objects. |
159 | It contains a single item, a \l Rectangle: |
160 | |
161 | \snippet qml/component.qml 0 |
162 | |
163 | Notice that while a \l Rectangle by itself would be automatically |
164 | rendered and displayed, this is not the case for the above rectangle |
165 | because it is defined inside a \c Component. The component encapsulates the |
166 | QML types within, as if they were defined in a separate QML |
167 | file, and is not loaded until requested (in this case, by the |
168 | two \l Loader objects). Because Component is not derived from Item, you cannot |
169 | anchor anything to it. |
170 | |
171 | Defining a \c Component is similar to defining a \l {QML Documents}{QML document}. |
172 | A QML document has a single top-level item that defines the behavior and |
173 | properties of that component, and cannot define properties or behavior outside |
174 | of that top-level item. In the same way, a \c Component definition contains a single |
175 | top level item (which in the above example is a \l Rectangle) and cannot define any |
176 | data outside of this item, with the exception of an \e id (which in the above example |
177 | is \e redSquare). |
178 | |
179 | The \c Component type is commonly used to provide graphical components |
180 | for views. For example, the ListView::delegate property requires a \c Component |
181 | to specify how each list item is to be displayed. |
182 | |
183 | \c Component objects can also be created dynamically using |
184 | \l{QtQml::Qt::createComponent()}{Qt.createComponent()}. |
185 | |
186 | \section2 Creation Context |
187 | |
188 | The creation context of a Component corresponds to the context where the Component was declared. |
189 | This context is used as the parent context (creating a \l{qtqml-documents-scope.html#component-instance-hierarchy}{context hierarchy}) |
190 | when the component is instantiated by an object such as a ListView or a Loader. |
191 | |
192 | In the following example, \c comp1 is created within the root context of MyItem.qml, and any objects |
193 | instantiated from this component will have access to the ids and properties within that context, |
194 | such as \c internalSettings.color. When \c comp1 is used as a ListView delegate in another context |
195 | (as in main.qml below), it will continue to have access to the properties of its creation context |
196 | (which would otherwise be private to external users). |
197 | |
198 | \table |
199 | \row |
200 | \li MyItem.qml |
201 | \li \snippet qml/component/MyItem.qml 0 |
202 | \row |
203 | \li main.qml |
204 | \li \snippet qml/component/main.qml 0 |
205 | \endtable |
206 | |
207 | It is important that the lifetime of the creation context outlive any created objects. See |
208 | \l{Maintaining Dynamically Created Objects} for more details. |
209 | */ |
210 | |
211 | /*! |
212 | \qmlattachedsignal Component::completed() |
213 | |
214 | Emitted after the object has been instantiated. This can be used to |
215 | execute script code at startup, once the full QML environment has been |
216 | established. |
217 | |
218 | The \c onCompleted signal handler can be declared on any object. The order |
219 | of running the handlers is undefined. |
220 | |
221 | \qml |
222 | Rectangle { |
223 | Component.onCompleted: console.log("Completed Running!") |
224 | Rectangle { |
225 | Component.onCompleted: console.log("Nested Completed Running!") |
226 | } |
227 | } |
228 | \endqml |
229 | */ |
230 | |
231 | /*! |
232 | \qmlattachedsignal Component::destruction() |
233 | |
234 | Emitted as the object begins destruction. This can be used to undo |
235 | work done in response to the \l {completed}{completed()} signal, or other |
236 | imperative code in your application. |
237 | |
238 | The \c onDestruction signal handler can be declared on any object. The |
239 | order of running the handlers is undefined. |
240 | |
241 | \qml |
242 | Rectangle { |
243 | Component.onDestruction: console.log("Destruction Beginning!") |
244 | Rectangle { |
245 | Component.onDestruction: console.log("Nested Destruction Beginning!") |
246 | } |
247 | } |
248 | \endqml |
249 | |
250 | \sa {Qt QML} |
251 | */ |
252 | |
253 | /*! |
254 | \enum QQmlComponent::Status |
255 | |
256 | Specifies the loading status of the QQmlComponent. |
257 | |
258 | \value Null This QQmlComponent has no data. Call loadUrl() or setData() to add QML content. |
259 | \value Ready This QQmlComponent is ready and create() may be called. |
260 | \value Loading This QQmlComponent is loading network data. |
261 | \value Error An error has occurred. Call errors() to retrieve a list of \l {QQmlError}{errors}. |
262 | */ |
263 | |
264 | /*! |
265 | \enum QQmlComponent::CompilationMode |
266 | |
267 | Specifies whether the QQmlComponent should load the component immediately, or asynchonously. |
268 | |
269 | \value PreferSynchronous Prefer loading/compiling the component immediately, blocking the thread. |
270 | This is not always possible; for example, remote URLs will always load asynchronously. |
271 | \value Asynchronous Load/compile the component in a background thread. |
272 | */ |
273 | |
274 | void QQmlComponentPrivate::typeDataReady(QQmlTypeData *) |
275 | { |
276 | Q_Q(QQmlComponent); |
277 | |
278 | Q_ASSERT(typeData); |
279 | |
280 | fromTypeData(data: typeData); |
281 | typeData.reset(); |
282 | progress = 1.0; |
283 | |
284 | emit q->statusChanged(q->status()); |
285 | emit q->progressChanged(progress); |
286 | } |
287 | |
288 | void QQmlComponentPrivate::typeDataProgress(QQmlTypeData *, qreal p) |
289 | { |
290 | Q_Q(QQmlComponent); |
291 | |
292 | progress = p; |
293 | |
294 | emit q->progressChanged(p); |
295 | } |
296 | |
297 | void QQmlComponentPrivate::fromTypeData(const QQmlRefPointer<QQmlTypeData> &data) |
298 | { |
299 | url = data->finalUrl(); |
300 | compilationUnit.reset(t: data->compilationUnit()); |
301 | |
302 | if (!compilationUnit) { |
303 | Q_ASSERT(data->isError()); |
304 | state.errors.clear(); |
305 | state.appendErrors(qmlErrors: data->errors()); |
306 | } |
307 | } |
308 | |
309 | bool QQmlComponentPrivate::hadTopLevelRequiredProperties() const |
310 | { |
311 | return state.creator()->componentHadTopLevelRequiredProperties(); |
312 | } |
313 | |
314 | void QQmlComponentPrivate::clear() |
315 | { |
316 | if (typeData) { |
317 | typeData->unregisterCallback(this); |
318 | typeData.reset(); |
319 | } |
320 | |
321 | compilationUnit.reset(); |
322 | loadedType = {}; |
323 | inlineComponentName.reset(); |
324 | } |
325 | |
326 | QObject *QQmlComponentPrivate::doBeginCreate(QQmlComponent *q, QQmlContext *context) |
327 | { |
328 | if (!engine) { |
329 | // ###Qt6: In Qt 6, it should be impossible for users to create a QQmlComponent without an engine, and we can remove this check |
330 | qWarning(msg: "QQmlComponent: Must provide an engine before calling create" ); |
331 | return nullptr; |
332 | } |
333 | if (!context) |
334 | context = engine->rootContext(); |
335 | return q->beginCreate(context); |
336 | } |
337 | |
338 | static void removePendingQPropertyBinding( |
339 | QV4::Value *object, const QString &propertyName, QQmlObjectCreator *creator) |
340 | { |
341 | if (!creator) |
342 | return; |
343 | |
344 | QV4::QObjectWrapper *wrapper = object->as<QV4::QObjectWrapper>(); |
345 | if (!wrapper) |
346 | return; |
347 | |
348 | QObject *o = wrapper->object(); |
349 | if (!o) |
350 | return; |
351 | |
352 | if (QQmlData *ddata = QQmlData::get(object: o)) { |
353 | const QQmlPropertyData *propData = ddata->propertyCache->property( |
354 | key: propertyName, object: o, context: ddata->outerContext); |
355 | if (propData && propData->isBindable()) |
356 | creator->removePendingBinding(target: o, propertyIndex: propData->coreIndex()); |
357 | return; |
358 | } |
359 | |
360 | const QMetaObject *meta = o->metaObject(); |
361 | Q_ASSERT(meta); |
362 | const int index = meta->indexOfProperty(name: propertyName.toUtf8()); |
363 | if (index != -1 && meta->property(index).isBindable()) |
364 | creator->removePendingBinding(target: o, propertyIndex: index); |
365 | } |
366 | |
367 | bool QQmlComponentPrivate::setInitialProperty( |
368 | QObject *base, const QString &name, const QVariant &value) |
369 | { |
370 | const QStringList properties = name.split(sep: u'.'); |
371 | |
372 | if (properties.size() > 1) { |
373 | QV4::Scope scope(engine->handle()); |
374 | QV4::ScopedObject object(scope, QV4::QObjectWrapper::wrap(engine: scope.engine, object: base)); |
375 | QV4::ScopedString segment(scope); |
376 | |
377 | for (int i = 0; i < properties.size() - 1; ++i) { |
378 | segment = scope.engine->newString(s: properties.at(i)); |
379 | object = object->get(name: segment); |
380 | if (scope.engine->hasException) |
381 | break; |
382 | } |
383 | const QString lastProperty = properties.last(); |
384 | segment = scope.engine->newString(s: lastProperty); |
385 | object->put(name: segment, v: scope.engine->metaTypeToJS(type: value.metaType(), data: value.constData())); |
386 | if (scope.engine->hasException) { |
387 | qmlWarning(me: base, error: scope.engine->catchExceptionAsQmlError()); |
388 | scope.engine->hasException = false; |
389 | return false; |
390 | } |
391 | |
392 | removePendingQPropertyBinding(object, propertyName: lastProperty, creator: state.creator()); |
393 | return true; |
394 | } |
395 | |
396 | QQmlProperty prop; |
397 | if (state.hasUnsetRequiredProperties()) |
398 | prop = QQmlComponentPrivate::removePropertyFromRequired( |
399 | createdComponent: base, name, requiredProperties: state.requiredProperties(), engine); |
400 | else |
401 | prop = QQmlProperty(base, name, engine); |
402 | QQmlPropertyPrivate *privProp = QQmlPropertyPrivate::get(p: prop); |
403 | const bool isValid = prop.isValid(); |
404 | if (isValid && privProp->writeValueProperty(value, {})) { |
405 | if (prop.isBindable()) { |
406 | if (QQmlObjectCreator *creator = state.creator()) |
407 | creator->removePendingBinding(target: prop.object(), propertyIndex: prop.index()); |
408 | } |
409 | } else { |
410 | QQmlError error{}; |
411 | error.setUrl(url); |
412 | if (isValid) { |
413 | error.setDescription(QStringLiteral("Could not set initial property %1" ).arg(a: name)); |
414 | } else { |
415 | error.setDescription(QStringLiteral("Setting initial properties failed: " |
416 | "%2 does not have a property called %1" ) |
417 | .arg(args: name, args: QQmlMetaType::prettyTypeName(object: base))); |
418 | } |
419 | qmlWarning(me: base, error); |
420 | return false; |
421 | } |
422 | |
423 | return true; |
424 | |
425 | } |
426 | |
427 | /*! |
428 | \internal |
429 | */ |
430 | QQmlComponent::QQmlComponent(QObject *parent) |
431 | : QObject(*(new QQmlComponentPrivate), parent) |
432 | { |
433 | } |
434 | |
435 | /*! |
436 | Destruct the QQmlComponent. |
437 | */ |
438 | QQmlComponent::~QQmlComponent() |
439 | { |
440 | Q_D(QQmlComponent); |
441 | |
442 | if (d->state.isCompletePending()) { |
443 | qWarning(msg: "QQmlComponent: Component destroyed while completion pending" ); |
444 | |
445 | if (isError()) { |
446 | qWarning() << "This may have been caused by one of the following errors:" ; |
447 | for (const QQmlComponentPrivate::AnnotatedQmlError &e : std::as_const(t&: d->state.errors)) |
448 | qWarning().nospace().noquote() << QLatin1String(" " ) << e.error; |
449 | } |
450 | |
451 | // we might not have the creator anymore if the engine is gone |
452 | if (d->state.hasCreator()) |
453 | d->completeCreate(); |
454 | } |
455 | |
456 | if (d->typeData) { |
457 | d->typeData->unregisterCallback(d); |
458 | d->typeData.reset(); |
459 | } |
460 | } |
461 | |
462 | /*! |
463 | \qmlproperty enumeration Component::status |
464 | |
465 | This property holds the status of component loading. The status can be one of the |
466 | following: |
467 | |
468 | \value Component.Null no data is available for the component |
469 | \value Component.Ready the component has been loaded, and can be used to create instances. |
470 | \value Component.Loading the component is currently being loaded |
471 | \value Component.Error an error occurred while loading the component. |
472 | Calling \l errorString() will provide a human-readable description of any errors. |
473 | */ |
474 | |
475 | /*! |
476 | \property QQmlComponent::status |
477 | The component's current \l{QQmlComponent::Status} {status}. |
478 | */ |
479 | QQmlComponent::Status QQmlComponent::status() const |
480 | { |
481 | Q_D(const QQmlComponent); |
482 | |
483 | if (d->typeData) |
484 | return Loading; |
485 | else if (!d->state.errors.isEmpty()) |
486 | return Error; |
487 | else if (d->engine && (d->compilationUnit || d->loadedType.isValid())) |
488 | return Ready; |
489 | else |
490 | return Null; |
491 | } |
492 | |
493 | /*! |
494 | Returns true if status() == QQmlComponent::Null. |
495 | */ |
496 | bool QQmlComponent::isNull() const |
497 | { |
498 | return status() == Null; |
499 | } |
500 | |
501 | /*! |
502 | Returns true if status() == QQmlComponent::Ready. |
503 | */ |
504 | bool QQmlComponent::isReady() const |
505 | { |
506 | return status() == Ready; |
507 | } |
508 | |
509 | /*! |
510 | Returns true if status() == QQmlComponent::Error. |
511 | */ |
512 | bool QQmlComponent::isError() const |
513 | { |
514 | return status() == Error; |
515 | } |
516 | |
517 | /*! |
518 | Returns true if status() == QQmlComponent::Loading. |
519 | */ |
520 | bool QQmlComponent::isLoading() const |
521 | { |
522 | return status() == Loading; |
523 | } |
524 | |
525 | /*! |
526 | Returns true if the component was created in a QML files that specifies |
527 | \c{pragma ComponentBehavior: Bound}, otherwise returns false. |
528 | |
529 | \since 6.5 |
530 | */ |
531 | bool QQmlComponent::isBound() const |
532 | { |
533 | Q_D(const QQmlComponent); |
534 | return d->isBound(); |
535 | } |
536 | |
537 | /*! |
538 | \qmlproperty real Component::progress |
539 | The progress of loading the component, from 0.0 (nothing loaded) |
540 | to 1.0 (finished). |
541 | */ |
542 | |
543 | /*! |
544 | \property QQmlComponent::progress |
545 | The progress of loading the component, from 0.0 (nothing loaded) |
546 | to 1.0 (finished). |
547 | */ |
548 | qreal QQmlComponent::progress() const |
549 | { |
550 | Q_D(const QQmlComponent); |
551 | return d->progress; |
552 | } |
553 | |
554 | /*! |
555 | \fn void QQmlComponent::progressChanged(qreal progress) |
556 | |
557 | Emitted whenever the component's loading progress changes. \a progress will be the |
558 | current progress between 0.0 (nothing loaded) and 1.0 (finished). |
559 | */ |
560 | |
561 | /*! |
562 | \fn void QQmlComponent::statusChanged(QQmlComponent::Status status) |
563 | |
564 | Emitted whenever the component's status changes. \a status will be the |
565 | new status. |
566 | */ |
567 | |
568 | /*! |
569 | Create a QQmlComponent with no data and give it the specified |
570 | \a engine and \a parent. Set the data with setData(). |
571 | */ |
572 | QQmlComponent::QQmlComponent(QQmlEngine *engine, QObject *parent) |
573 | : QObject(*(new QQmlComponentPrivate), parent) |
574 | { |
575 | Q_D(QQmlComponent); |
576 | d->engine = engine; |
577 | QObject::connect(sender: engine, signal: &QObject::destroyed, context: this, slot: [d]() { |
578 | d->state.clear(); |
579 | d->engine = nullptr; |
580 | }); |
581 | } |
582 | |
583 | /*! |
584 | Create a QQmlComponent from the given \a url and give it the |
585 | specified \a parent and \a engine. |
586 | |
587 | \include qqmlcomponent.qdoc url-note |
588 | |
589 | \sa loadUrl() |
590 | */ |
591 | QQmlComponent::QQmlComponent(QQmlEngine *engine, const QUrl &url, QObject *parent) |
592 | : QQmlComponent(engine, url, QQmlComponent::PreferSynchronous, parent) |
593 | { |
594 | } |
595 | |
596 | /*! |
597 | Create a QQmlComponent from the given \a url and give it the |
598 | specified \a parent and \a engine. If \a mode is \l Asynchronous, |
599 | the component will be loaded and compiled asynchronously. |
600 | |
601 | \include qqmlcomponent.qdoc url-note |
602 | |
603 | \sa loadUrl() |
604 | */ |
605 | QQmlComponent::QQmlComponent(QQmlEngine *engine, const QUrl &url, CompilationMode mode, |
606 | QObject *parent) |
607 | : QQmlComponent(engine, parent) |
608 | { |
609 | Q_D(QQmlComponent); |
610 | d->loadUrl(newUrl: url, mode); |
611 | } |
612 | |
613 | /*! |
614 | Create a QQmlComponent from the given \a uri and \a typeName and give it |
615 | the specified \a parent and \a engine. If possible, the component will |
616 | be loaded synchronously. |
617 | |
618 | \sa loadFromModule() |
619 | \since 6.5 |
620 | \overload |
621 | */ |
622 | QQmlComponent::QQmlComponent(QQmlEngine *engine, QAnyStringView uri, QAnyStringView typeName, QObject *parent) |
623 | : QQmlComponent(engine, uri, typeName, QQmlComponent::PreferSynchronous, parent) |
624 | { |
625 | |
626 | } |
627 | |
628 | /*! |
629 | Create a QQmlComponent from the given \a uri and \a typeName and give it |
630 | the specified \a parent and \a engine. If \a mode is \l Asynchronous, |
631 | the component will be loaded and compiled asynchronously. |
632 | |
633 | \sa loadFromModule() |
634 | \since 6.5 |
635 | \overload |
636 | */ |
637 | QQmlComponent::QQmlComponent(QQmlEngine *engine, QAnyStringView uri, QAnyStringView typeName, CompilationMode mode, QObject *parent) |
638 | : QQmlComponent(engine, parent) |
639 | { |
640 | loadFromModule(uri, typeName, mode); |
641 | } |
642 | |
643 | /*! |
644 | Create a QQmlComponent from the given \a fileName and give it the specified |
645 | \a parent and \a engine. |
646 | |
647 | \sa loadUrl() |
648 | */ |
649 | QQmlComponent::QQmlComponent(QQmlEngine *engine, const QString &fileName, |
650 | QObject *parent) |
651 | : QQmlComponent(engine, fileName, QQmlComponent::PreferSynchronous, parent) |
652 | { |
653 | } |
654 | |
655 | /*! |
656 | Create a QQmlComponent from the given \a fileName and give it the specified |
657 | \a parent and \a engine. If \a mode is \l Asynchronous, |
658 | the component will be loaded and compiled asynchronously. |
659 | |
660 | \sa loadUrl() |
661 | */ |
662 | QQmlComponent::QQmlComponent(QQmlEngine *engine, const QString &fileName, |
663 | CompilationMode mode, QObject *parent) |
664 | : QQmlComponent(engine, parent) |
665 | { |
666 | Q_D(QQmlComponent); |
667 | if (fileName.startsWith(c: u':')) |
668 | d->loadUrl(newUrl: QUrl(QLatin1String("qrc" ) + fileName), mode); |
669 | else if (QDir::isAbsolutePath(path: fileName)) |
670 | d->loadUrl(newUrl: QUrl::fromLocalFile(localfile: fileName), mode); |
671 | else |
672 | d->loadUrl(newUrl: QUrl(fileName), mode); |
673 | } |
674 | |
675 | /*! |
676 | \internal |
677 | */ |
678 | QQmlComponent::QQmlComponent(QQmlEngine *engine, QV4::ExecutableCompilationUnit *compilationUnit, |
679 | int start, QObject *parent) |
680 | : QQmlComponent(engine, parent) |
681 | { |
682 | Q_D(QQmlComponent); |
683 | d->compilationUnit.reset(t: compilationUnit); |
684 | d->start = start; |
685 | d->url = compilationUnit->finalUrl(); |
686 | d->progress = 1.0; |
687 | } |
688 | |
689 | /*! |
690 | Sets the QQmlComponent to use the given QML \a data. If \a url |
691 | is provided, it is used to set the component name and to provide |
692 | a base path for items resolved by this component. |
693 | */ |
694 | void QQmlComponent::setData(const QByteArray &data, const QUrl &url) |
695 | { |
696 | Q_D(QQmlComponent); |
697 | |
698 | if (!d->engine) { |
699 | // ###Qt6: In Qt 6, it should be impossible for users to create a QQmlComponent without an engine, and we can remove this check |
700 | qWarning(msg: "QQmlComponent: Must provide an engine before calling setData" ); |
701 | return; |
702 | } |
703 | |
704 | d->clear(); |
705 | |
706 | d->url = url; |
707 | |
708 | QQmlRefPointer<QQmlTypeData> typeData = QQmlEnginePrivate::get(e: d->engine)->typeLoader.getType(data, url); |
709 | |
710 | if (typeData->isCompleteOrError()) { |
711 | d->fromTypeData(data: typeData); |
712 | } else { |
713 | d->typeData = typeData; |
714 | d->typeData->registerCallback(d); |
715 | } |
716 | |
717 | d->progress = 1.0; |
718 | emit statusChanged(status()); |
719 | emit progressChanged(d->progress); |
720 | } |
721 | |
722 | /*! |
723 | Returns the QQmlContext the component was created in. This is only |
724 | valid for components created directly from QML. |
725 | */ |
726 | QQmlContext *QQmlComponent::creationContext() const |
727 | { |
728 | Q_D(const QQmlComponent); |
729 | if (!d->creationContext.isNull()) |
730 | return d->creationContext->asQQmlContext(); |
731 | |
732 | return qmlContext(this); |
733 | } |
734 | |
735 | /*! |
736 | Returns the QQmlEngine of this component. |
737 | |
738 | \since 5.12 |
739 | */ |
740 | QQmlEngine *QQmlComponent::engine() const |
741 | { |
742 | Q_D(const QQmlComponent); |
743 | return d->engine; |
744 | } |
745 | |
746 | /*! |
747 | Load the QQmlComponent from the provided \a url. |
748 | |
749 | \include qqmlcomponent.qdoc url-note |
750 | */ |
751 | void QQmlComponent::loadUrl(const QUrl &url) |
752 | { |
753 | Q_D(QQmlComponent); |
754 | d->loadUrl(newUrl: url); |
755 | } |
756 | |
757 | /*! |
758 | Load the QQmlComponent from the provided \a url. |
759 | If \a mode is \l Asynchronous, the component will be loaded and compiled asynchronously. |
760 | |
761 | \include qqmlcomponent.qdoc url-note |
762 | */ |
763 | void QQmlComponent::loadUrl(const QUrl &url, QQmlComponent::CompilationMode mode) |
764 | { |
765 | Q_D(QQmlComponent); |
766 | d->loadUrl(newUrl: url, mode); |
767 | } |
768 | |
769 | void QQmlComponentPrivate::loadUrl(const QUrl &newUrl, QQmlComponent::CompilationMode mode) |
770 | { |
771 | Q_Q(QQmlComponent); |
772 | clear(); |
773 | |
774 | if (newUrl.isRelative()) { |
775 | // The new URL is a relative URL like QUrl("main.qml"). |
776 | url = engine->baseUrl().resolved(relative: QUrl(newUrl.toString())); |
777 | } else if (engine->baseUrl().isLocalFile() && newUrl.isLocalFile() && !QDir::isAbsolutePath(path: newUrl.toLocalFile())) { |
778 | // The new URL is a file on disk but it's a relative path; e.g.: |
779 | // QUrl::fromLocalFile("main.qml") or QUrl("file:main.qml") |
780 | // We need to remove the scheme so that it becomes a relative URL with a relative path: |
781 | QUrl fixedUrl(newUrl); |
782 | fixedUrl.setScheme(QString()); |
783 | // Then, turn it into an absolute URL with an absolute path by resolving it against the engine's baseUrl(). |
784 | // This is a compatibility hack for QTBUG-58837. |
785 | url = engine->baseUrl().resolved(relative: fixedUrl); |
786 | } else { |
787 | url = newUrl; |
788 | } |
789 | |
790 | if (newUrl.isEmpty()) { |
791 | QQmlError error; |
792 | error.setDescription(QQmlComponent::tr(s: "Invalid empty URL" )); |
793 | state.errors.emplaceBack(args&: error); |
794 | return; |
795 | } |
796 | |
797 | if (progress != 0.0) { |
798 | progress = 0.0; |
799 | emit q->progressChanged(progress); |
800 | } |
801 | |
802 | QQmlTypeLoader::Mode loaderMode = (mode == QQmlComponent::Asynchronous) |
803 | ? QQmlTypeLoader::Asynchronous |
804 | : QQmlTypeLoader::PreferSynchronous; |
805 | QQmlRefPointer<QQmlTypeData> data = QQmlEnginePrivate::get(e: engine)->typeLoader.getType(unNormalizedUrl: url, mode: loaderMode); |
806 | |
807 | if (data->isCompleteOrError()) { |
808 | fromTypeData(data); |
809 | progress = 1.0; |
810 | } else { |
811 | typeData = data; |
812 | typeData->registerCallback(this); |
813 | progress = data->progress(); |
814 | } |
815 | |
816 | emit q->statusChanged(q->status()); |
817 | if (progress != 0.0) |
818 | emit q->progressChanged(progress); |
819 | } |
820 | |
821 | /*! |
822 | Returns the list of errors that occurred during the last compile or create |
823 | operation. An empty list is returned if isError() is not set. |
824 | */ |
825 | QList<QQmlError> QQmlComponent::errors() const |
826 | { |
827 | Q_D(const QQmlComponent); |
828 | QList<QQmlError> errors; |
829 | errors.reserve(asize: d->state.errors.size()); |
830 | for (const QQmlComponentPrivate::AnnotatedQmlError &annotated : d->state.errors) |
831 | errors.emplaceBack(args: annotated.error); |
832 | return errors; |
833 | } |
834 | |
835 | /*! |
836 | \qmlmethod string Component::errorString() |
837 | |
838 | Returns a human-readable description of any error. |
839 | |
840 | The string includes the file, location, and description of each error. |
841 | If multiple errors are present, they are separated by a newline character. |
842 | |
843 | If no errors are present, an empty string is returned. |
844 | */ |
845 | |
846 | /*! |
847 | \internal |
848 | errorString() is only meant as a way to get the errors from QML side. |
849 | */ |
850 | QString QQmlComponent::errorString() const |
851 | { |
852 | Q_D(const QQmlComponent); |
853 | QString ret; |
854 | if(!isError()) |
855 | return ret; |
856 | for (const QQmlComponentPrivate::AnnotatedQmlError &annotated : d->state.errors) { |
857 | const QQmlError &e = annotated.error; |
858 | ret += e.url().toString() + QLatin1Char(':') + |
859 | QString::number(e.line()) + QLatin1Char(' ') + |
860 | e.description() + QLatin1Char('\n'); |
861 | } |
862 | return ret; |
863 | } |
864 | |
865 | /*! |
866 | \qmlproperty url Component::url |
867 | The component URL. This is the URL that was used to construct the component. |
868 | */ |
869 | |
870 | /*! |
871 | \property QQmlComponent::url |
872 | The component URL. This is the URL passed to either the constructor, |
873 | or the loadUrl(), or setData() methods. |
874 | */ |
875 | QUrl QQmlComponent::url() const |
876 | { |
877 | Q_D(const QQmlComponent); |
878 | return d->url; |
879 | } |
880 | |
881 | /*! |
882 | \internal |
883 | */ |
884 | QQmlComponent::QQmlComponent(QQmlComponentPrivate &dd, QObject *parent) |
885 | : QObject(dd, parent) |
886 | { |
887 | } |
888 | |
889 | /*! |
890 | Create an object instance from this component, within the specified \a context. |
891 | Returns \nullptr if creation failed. |
892 | |
893 | If \a context is \nullptr (the default), it will create the instance in the |
894 | \l {QQmlEngine::rootContext()}{root context} of the engine. |
895 | |
896 | The ownership of the returned object instance is transferred to the caller. |
897 | |
898 | If the object being created from this component is a visual item, it must |
899 | have a visual parent, which can be set by calling |
900 | QQuickItem::setParentItem(). See \l {Concepts - Visual Parent in Qt Quick} |
901 | for more details. |
902 | |
903 | \sa QQmlEngine::ObjectOwnership |
904 | */ |
905 | QObject *QQmlComponent::create(QQmlContext *context) |
906 | { |
907 | Q_D(QQmlComponent); |
908 | return d->createWithProperties(parent: nullptr, properties: QVariantMap {}, context); |
909 | } |
910 | |
911 | /*! |
912 | Create an object instance of this component, within the specified \a context, |
913 | and initialize its top-level properties with \a initialProperties. |
914 | |
915 | \omit |
916 | TODO: also mention errorString() when QTBUG-93239 is fixed |
917 | \endomit |
918 | |
919 | If any of the \a initialProperties cannot be set, a warning is issued. If |
920 | there are unset required properties, the object creation fails and returns |
921 | \c nullptr, in which case \l isError() will return \c true. |
922 | |
923 | \sa QQmlComponent::create |
924 | \since 5.14 |
925 | */ |
926 | QObject *QQmlComponent::createWithInitialProperties(const QVariantMap& initialProperties, QQmlContext *context) |
927 | { |
928 | Q_D(QQmlComponent); |
929 | return d->createWithProperties(parent: nullptr, properties: initialProperties, context); |
930 | } |
931 | |
932 | static void QQmlComponent_setQmlParent(QObject *me, QObject *parent); // forward declaration |
933 | |
934 | /*! \internal |
935 | */ |
936 | QObject *QQmlComponentPrivate::createWithProperties(QObject *parent, const QVariantMap &properties, |
937 | QQmlContext *context, CreateBehavior behavior) |
938 | { |
939 | Q_Q(QQmlComponent); |
940 | |
941 | QObject *rv = doBeginCreate(q, context); |
942 | if (!rv) { |
943 | if (state.isCompletePending()) { |
944 | // overridden completCreate might assume that |
945 | // the object has actually been created |
946 | ++creationDepth; |
947 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine); |
948 | complete(enginePriv: ep, state: &state); |
949 | --creationDepth; |
950 | } |
951 | return nullptr; |
952 | } |
953 | |
954 | QQmlComponent_setQmlParent(me: rv, parent); // internally checks if parent is nullptr |
955 | |
956 | q->setInitialProperties(component: rv, properties); |
957 | q->completeCreate(); |
958 | |
959 | if (state.hasUnsetRequiredProperties()) { |
960 | if (behavior == CreateWarnAboutRequiredProperties) { |
961 | for (const auto &unsetRequiredProperty : std::as_const(t&: *state.requiredProperties())) { |
962 | const QQmlError error = unsetRequiredPropertyToQQmlError(unsetRequiredProperty); |
963 | qmlWarning(me: rv, error); |
964 | } |
965 | } |
966 | delete rv; |
967 | rv = nullptr; |
968 | } |
969 | return rv; |
970 | } |
971 | |
972 | /*! |
973 | Create an object instance from this component, within the specified \a context. |
974 | Returns \nullptr if creation failed. |
975 | |
976 | \note This method provides advanced control over component instance creation. |
977 | In general, programmers should use QQmlComponent::create() to create object |
978 | instances. |
979 | |
980 | When QQmlComponent constructs an instance, it occurs in three steps: |
981 | |
982 | \list 1 |
983 | \li The object hierarchy is created, and constant values are assigned. |
984 | \li Property bindings are evaluated for the first time. |
985 | \li If applicable, QQmlParserStatus::componentComplete() is called on objects. |
986 | \endlist |
987 | |
988 | QQmlComponent::beginCreate() differs from QQmlComponent::create() in that it |
989 | only performs step 1. QQmlComponent::completeCreate() must be called to |
990 | complete steps 2 and 3. |
991 | |
992 | This breaking point is sometimes useful when using attached properties to |
993 | communicate information to an instantiated component, as it allows their |
994 | initial values to be configured before property bindings take effect. |
995 | |
996 | The ownership of the returned object instance is transferred to the caller. |
997 | |
998 | \sa completeCreate(), QQmlEngine::ObjectOwnership |
999 | */ |
1000 | QObject *QQmlComponent::beginCreate(QQmlContext *context) |
1001 | { |
1002 | Q_D(QQmlComponent); |
1003 | Q_ASSERT(context); |
1004 | return d->beginCreate(QQmlContextData::get(context)); |
1005 | } |
1006 | |
1007 | QObject *QQmlComponentPrivate::beginCreate(QQmlRefPointer<QQmlContextData> context) |
1008 | { |
1009 | Q_Q(QQmlComponent); |
1010 | auto cleanup = qScopeGuard(f: [this] { |
1011 | if (!state.errors.isEmpty() && lcQmlComponentGeneral().isDebugEnabled()) { |
1012 | for (const auto &e : std::as_const(t&: state.errors)) { |
1013 | qCDebug(lcQmlComponentGeneral) << "QQmlComponent: " << e.error.toString(); |
1014 | } |
1015 | } |
1016 | }); |
1017 | if (!context) { |
1018 | qWarning(msg: "QQmlComponent: Cannot create a component in a null context" ); |
1019 | return nullptr; |
1020 | } |
1021 | |
1022 | if (!context->isValid()) { |
1023 | qWarning(msg: "QQmlComponent: Cannot create a component in an invalid context" ); |
1024 | return nullptr; |
1025 | } |
1026 | |
1027 | if (context->engine() != engine) { |
1028 | qWarning(msg: "QQmlComponent: Must create component in context from the same QQmlEngine" ); |
1029 | return nullptr; |
1030 | } |
1031 | |
1032 | if (state.isCompletePending()) { |
1033 | qWarning(msg: "QQmlComponent: Cannot create new component instance before completing the previous" ); |
1034 | return nullptr; |
1035 | } |
1036 | |
1037 | // filter out temporary errors as they do not really affect component's |
1038 | // state (they are not part of the document compilation) |
1039 | state.errors.erase(abegin: std::remove_if(first: state.errors.begin(), last: state.errors.end(), |
1040 | pred: [](const QQmlComponentPrivate::AnnotatedQmlError &e) { |
1041 | return e.isTransient; |
1042 | }), |
1043 | aend: state.errors.end()); |
1044 | state.clearRequiredProperties(); |
1045 | |
1046 | if (!q->isReady()) { |
1047 | qWarning(msg: "QQmlComponent: Component is not ready" ); |
1048 | return nullptr; |
1049 | } |
1050 | |
1051 | // Do not create infinite recursion in object creation |
1052 | static const int maxCreationDepth = 10; |
1053 | if (creationDepth >= maxCreationDepth) { |
1054 | qWarning(msg: "QQmlComponent: Component creation is recursing - aborting" ); |
1055 | return nullptr; |
1056 | } |
1057 | |
1058 | QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(e: engine); |
1059 | |
1060 | enginePriv->inProgressCreations++; |
1061 | state.errors.clear(); |
1062 | state.setCompletePending(true); |
1063 | |
1064 | QObject *rv = nullptr; |
1065 | |
1066 | if (!loadedType.isValid()) { |
1067 | enginePriv->referenceScarceResources(); |
1068 | state.initCreator(parentContext: std::move(context), compilationUnit, creationContext); |
1069 | |
1070 | QQmlObjectCreator::CreationFlags flags; |
1071 | if (const QString *icName = inlineComponentName.get()) { |
1072 | flags = QQmlObjectCreator::InlineComponent; |
1073 | if (start == -1) |
1074 | start = compilationUnit->inlineComponentId(inlineComponentName: *icName); |
1075 | Q_ASSERT(start > 0); |
1076 | } else { |
1077 | flags = QQmlObjectCreator::NormalObject; |
1078 | } |
1079 | |
1080 | rv = state.creator()->create(subComponentIndex: start, parent: nullptr, interrupt: nullptr, flags); |
1081 | if (!rv) |
1082 | state.appendCreatorErrors(); |
1083 | enginePriv->dereferenceScarceResources(); |
1084 | } else { |
1085 | rv = loadedType.createWithQQmlData(); |
1086 | QQmlPropertyCache::ConstPtr propertyCache = QQmlData::ensurePropertyCache(object: rv); |
1087 | for (int i = 0, propertyCount = propertyCache->propertyCount(); i < propertyCount; ++i) { |
1088 | if (const QQmlPropertyData *propertyData = propertyCache->property(index: i); propertyData->isRequired()) { |
1089 | state.ensureRequiredPropertyStorage(); |
1090 | RequiredPropertyInfo info; |
1091 | info.propertyName = propertyData->name(rv); |
1092 | state.addPendingRequiredProperty(object: rv, propData: propertyData, info); |
1093 | } |
1094 | } |
1095 | } |
1096 | |
1097 | if (rv) { |
1098 | QQmlData *ddata = QQmlData::get(object: rv); |
1099 | Q_ASSERT(ddata); |
1100 | // top-level objects should never get JS ownership. |
1101 | // if JS ownership is needed this needs to be explicitly undone (like in createObject()) |
1102 | ddata->indestructible = true; |
1103 | ddata->explicitIndestructibleSet = true; |
1104 | ddata->rootObjectInCreation = false; |
1105 | } |
1106 | |
1107 | return rv; |
1108 | } |
1109 | |
1110 | void QQmlComponentPrivate::beginDeferred(QQmlEnginePrivate *enginePriv, |
1111 | QObject *object, DeferredState *deferredState) |
1112 | { |
1113 | QQmlData *ddata = QQmlData::get(object); |
1114 | Q_ASSERT(!ddata->deferredData.isEmpty()); |
1115 | |
1116 | deferredState->reserve(n: ddata->deferredData.size()); |
1117 | |
1118 | for (QQmlData::DeferredData *deferredData : std::as_const(t&: ddata->deferredData)) { |
1119 | enginePriv->inProgressCreations++; |
1120 | |
1121 | ConstructionState state; |
1122 | state.setCompletePending(true); |
1123 | |
1124 | auto creator = state.initCreator( |
1125 | parentContext: deferredData->context->parent(), |
1126 | compilationUnit: deferredData->compilationUnit, |
1127 | creationContext: QQmlRefPointer<QQmlContextData>()); |
1128 | |
1129 | if (!creator->populateDeferredProperties(instance: object, deferredData)) |
1130 | state.appendCreatorErrors(); |
1131 | deferredData->bindings.clear(); |
1132 | |
1133 | deferredState->push_back(x: std::move(state)); |
1134 | } |
1135 | } |
1136 | |
1137 | void QQmlComponentPrivate::completeDeferred(QQmlEnginePrivate *enginePriv, QQmlComponentPrivate::DeferredState *deferredState) |
1138 | { |
1139 | for (ConstructionState &state : *deferredState) |
1140 | complete(enginePriv, state: &state); |
1141 | } |
1142 | |
1143 | void QQmlComponentPrivate::complete(QQmlEnginePrivate *enginePriv, ConstructionState *state) |
1144 | { |
1145 | if (state->isCompletePending()) { |
1146 | QQmlInstantiationInterrupt interrupt; |
1147 | state->creator()->finalize(interrupt); |
1148 | |
1149 | state->setCompletePending(false); |
1150 | |
1151 | enginePriv->inProgressCreations--; |
1152 | |
1153 | if (0 == enginePriv->inProgressCreations) { |
1154 | while (enginePriv->erroredBindings) { |
1155 | enginePriv->warning(enginePriv->erroredBindings->removeError()); |
1156 | } |
1157 | } |
1158 | } |
1159 | } |
1160 | |
1161 | /*! |
1162 | \internal |
1163 | Finds the matching top-level property with name \a name of the component \a createdComponent. |
1164 | If it was a required property or an alias to a required property contained in \a |
1165 | requiredProperties, it is removed from it. |
1166 | \a requiredProperties must be non-null. |
1167 | |
1168 | If wasInRequiredProperties is non-null, the referenced boolean is set to true iff the property |
1169 | was found in requiredProperties. |
1170 | |
1171 | Returns the QQmlProperty with name \a name (which might be invalid if there is no such property), |
1172 | for further processing (for instance, actually setting the property value). |
1173 | |
1174 | Note: This method is used in QQmlComponent and QQmlIncubator to manage required properties. Most |
1175 | classes which create components should not need it and should only need to call |
1176 | setInitialProperties. |
1177 | */ |
1178 | QQmlProperty QQmlComponentPrivate::removePropertyFromRequired( |
1179 | QObject *createdComponent, const QString &name, RequiredProperties *requiredProperties, |
1180 | QQmlEngine *engine, bool *wasInRequiredProperties) |
1181 | { |
1182 | Q_ASSERT(requiredProperties); |
1183 | QQmlProperty prop(createdComponent, name, engine); |
1184 | auto privProp = QQmlPropertyPrivate::get(p: prop); |
1185 | if (prop.isValid()) { |
1186 | // resolve outstanding required properties |
1187 | const QQmlPropertyData *targetProp = &privProp->core; |
1188 | if (targetProp->isAlias()) { |
1189 | auto target = createdComponent; |
1190 | QQmlPropertyIndex originalIndex(targetProp->coreIndex()); |
1191 | QQmlPropertyIndex propIndex; |
1192 | QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); |
1193 | QQmlData *data = QQmlData::get(object: target); |
1194 | Q_ASSERT(data && data->propertyCache); |
1195 | targetProp = data->propertyCache->property(index: propIndex.coreIndex()); |
1196 | } else { |
1197 | // we need to get the pointer from the property cache instead of directly using |
1198 | // targetProp else the lookup will fail |
1199 | QQmlData *data = QQmlData::get(object: createdComponent); |
1200 | Q_ASSERT(data && data->propertyCache); |
1201 | targetProp = data->propertyCache->property(index: targetProp->coreIndex()); |
1202 | } |
1203 | auto it = requiredProperties->find(key: {createdComponent, targetProp}); |
1204 | if (it != requiredProperties->end()) { |
1205 | if (wasInRequiredProperties) |
1206 | *wasInRequiredProperties = true; |
1207 | requiredProperties->erase(it); |
1208 | } else { |
1209 | if (wasInRequiredProperties) |
1210 | *wasInRequiredProperties = false; |
1211 | } |
1212 | } |
1213 | return prop; |
1214 | } |
1215 | |
1216 | /*! |
1217 | This method provides advanced control over component instance creation. |
1218 | In general, programmers should use QQmlComponent::create() to create a |
1219 | component. |
1220 | |
1221 | This function completes the component creation begun with QQmlComponent::beginCreate() |
1222 | and must be called afterwards. |
1223 | |
1224 | \sa beginCreate() |
1225 | */ |
1226 | void QQmlComponent::completeCreate() |
1227 | { |
1228 | Q_D(QQmlComponent); |
1229 | |
1230 | d->completeCreate(); |
1231 | } |
1232 | |
1233 | void QQmlComponentPrivate::completeCreate() |
1234 | { |
1235 | if (state.hasUnsetRequiredProperties()) { |
1236 | for (const auto& unsetRequiredProperty: std::as_const(t&: *state.requiredProperties())) { |
1237 | QQmlError error = unsetRequiredPropertyToQQmlError(unsetRequiredProperty); |
1238 | state.errors.push_back(t: QQmlComponentPrivate::AnnotatedQmlError { error, true }); |
1239 | } |
1240 | } |
1241 | if (loadedType.isValid()) { |
1242 | /* |
1243 | We can directly set completePending to false, as finalize is only concerned |
1244 | with setting up pending bindings, but that cannot happen here, as we're |
1245 | dealing with a pure C++ type, which cannot have pending bindings |
1246 | */ |
1247 | state.setCompletePending(false); |
1248 | QQmlEnginePrivate::get(e: engine)->inProgressCreations--; |
1249 | } else if (state.isCompletePending()) { |
1250 | ++creationDepth; |
1251 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine); |
1252 | complete(enginePriv: ep, state: &state); |
1253 | --creationDepth; |
1254 | } |
1255 | } |
1256 | |
1257 | QQmlComponentAttached::QQmlComponentAttached(QObject *parent) |
1258 | : QObject(parent), m_prev(nullptr), m_next(nullptr) |
1259 | { |
1260 | } |
1261 | |
1262 | QQmlComponentAttached::~QQmlComponentAttached() |
1263 | { |
1264 | if (m_prev) *m_prev = m_next; |
1265 | if (m_next) m_next->m_prev = m_prev; |
1266 | m_prev = nullptr; |
1267 | m_next = nullptr; |
1268 | } |
1269 | |
1270 | /*! |
1271 | \internal |
1272 | */ |
1273 | QQmlComponentAttached *QQmlComponent::qmlAttachedProperties(QObject *obj) |
1274 | { |
1275 | QQmlComponentAttached *a = new QQmlComponentAttached(obj); |
1276 | |
1277 | QQmlEngine *engine = qmlEngine(obj); |
1278 | if (!engine) |
1279 | return a; |
1280 | |
1281 | QQmlEnginePrivate *p = QQmlEnginePrivate::get(e: engine); |
1282 | if (p->activeObjectCreator) { // XXX should only be allowed during begin |
1283 | a->insertIntoList(listHead: p->activeObjectCreator->componentAttachment()); |
1284 | } else { |
1285 | QQmlData *d = QQmlData::get(object: obj); |
1286 | Q_ASSERT(d); |
1287 | Q_ASSERT(d->context); |
1288 | d->context->addComponentAttached(attached: a); |
1289 | } |
1290 | |
1291 | return a; |
1292 | } |
1293 | |
1294 | /*! |
1295 | Load the QQmlComponent for \a typeName in the module \a uri. |
1296 | If the type is implemented via a QML file, \a mode is used to |
1297 | load it. Types backed by C++ are always loaded synchronously. |
1298 | |
1299 | \code |
1300 | QQmlEngine engine; |
1301 | QQmlComponent component(&engine); |
1302 | component.loadFromModule("QtQuick", "Item"); |
1303 | // once the component is ready |
1304 | std::unique_ptr<QObject> item(component.create()); |
1305 | Q_ASSERT(item->metaObject() == &QQuickItem::staticMetaObject); |
1306 | \endcode |
1307 | |
1308 | \since 6.5 |
1309 | \sa loadUrl() |
1310 | */ |
1311 | void QQmlComponent::loadFromModule(QAnyStringView uri, QAnyStringView typeName, |
1312 | QQmlComponent::CompilationMode mode) |
1313 | { |
1314 | Q_D(QQmlComponent); |
1315 | |
1316 | auto enginePriv = QQmlEnginePrivate::get(e: d->engine); |
1317 | // LoadHelper must be on the Heap as it derives from QQmlRefCount |
1318 | auto loadHelper = QQml::makeRefPointer<LoadHelper>(args: &enginePriv->typeLoader, args&: uri); |
1319 | |
1320 | auto [moduleStatus, type] = loadHelper->resolveType(typeName); |
1321 | auto reportError = [&](QString msg) { |
1322 | QQmlError error; |
1323 | error.setDescription(msg); |
1324 | d->state.errors.push_back(t: std::move(error)); |
1325 | emit statusChanged(Error); |
1326 | }; |
1327 | if (moduleStatus == LoadHelper::ResolveTypeResult::NoSuchModule) { |
1328 | reportError(QLatin1String(R"(No module named "%1" found)" ) |
1329 | .arg(args: uri.toString())); |
1330 | } else if (!type.isValid()) { |
1331 | reportError(QLatin1String(R"(Module "%1" contains no type named "%2")" ) |
1332 | .arg(args: uri.toString(), args: typeName.toString())); |
1333 | } else if (type.isCreatable()) { |
1334 | d->clear(); |
1335 | // mimic the progressChanged behavior from loadUrl |
1336 | if (d->progress != 0) { |
1337 | d->progress = 0; |
1338 | emit progressChanged(0); |
1339 | } |
1340 | d->loadedType = type; |
1341 | d->progress = 1; |
1342 | emit progressChanged(1); |
1343 | emit statusChanged(status()); |
1344 | |
1345 | } else if (type.isComposite()) { |
1346 | loadUrl(url: type.sourceUrl(), mode); |
1347 | } else if (type.isInlineComponentType()) { |
1348 | auto baseUrl = type.sourceUrl(); |
1349 | baseUrl.setFragment(fragment: QString()); |
1350 | loadUrl(url: baseUrl, mode); |
1351 | if (!isError()) { |
1352 | d->inlineComponentName = std::make_unique<QString>(args: type.elementName()); |
1353 | Q_ASSERT(!d->inlineComponentName->isEmpty()); |
1354 | } |
1355 | } else if (type.isSingleton() || type.isCompositeSingleton()) { |
1356 | reportError(QLatin1String(R"(%1 is a singleton, and cannot be loaded)" ) |
1357 | .arg(args: typeName.toString())); |
1358 | } else { |
1359 | reportError(QLatin1String("Could not load %1, as the type is uncreatable" ) |
1360 | .arg(args: typeName.toString())); |
1361 | } |
1362 | } |
1363 | |
1364 | /*! |
1365 | Create an object instance from this component using the provided |
1366 | \a incubator. \a context specifies the context within which to create the object |
1367 | instance. |
1368 | |
1369 | If \a context is \nullptr (by default), it will create the instance in the |
1370 | engine's \l {QQmlEngine::rootContext()}{root context}. |
1371 | |
1372 | \a forContext specifies a context that this object creation depends upon. |
1373 | If the \a forContext is being created asynchronously, and the |
1374 | \l QQmlIncubator::IncubationMode is \l QQmlIncubator::AsynchronousIfNested, |
1375 | this object will also be created asynchronously. |
1376 | If \a forContext is \nullptr (by default), the \a context will be used for this decision. |
1377 | |
1378 | The created object and its creation status are available via the |
1379 | \a incubator. |
1380 | |
1381 | \sa QQmlIncubator |
1382 | */ |
1383 | |
1384 | void QQmlComponent::create(QQmlIncubator &incubator, QQmlContext *context, QQmlContext *forContext) |
1385 | { |
1386 | Q_D(QQmlComponent); |
1387 | |
1388 | if (!context) |
1389 | context = d->engine->rootContext(); |
1390 | |
1391 | QQmlRefPointer<QQmlContextData> contextData = QQmlContextData::get(context); |
1392 | QQmlRefPointer<QQmlContextData> forContextData = |
1393 | forContext ? QQmlContextData::get(context: forContext) : contextData; |
1394 | |
1395 | if (!contextData->isValid()) { |
1396 | qWarning(msg: "QQmlComponent: Cannot create a component in an invalid context" ); |
1397 | return; |
1398 | } |
1399 | |
1400 | if (contextData->engine() != d->engine) { |
1401 | qWarning(msg: "QQmlComponent: Must create component in context from the same QQmlEngine" ); |
1402 | return; |
1403 | } |
1404 | |
1405 | if (!isReady()) { |
1406 | qWarning(msg: "QQmlComponent: Component is not ready" ); |
1407 | return; |
1408 | } |
1409 | |
1410 | incubator.clear(); |
1411 | QExplicitlySharedDataPointer<QQmlIncubatorPrivate> p(incubator.d); |
1412 | |
1413 | if (d->loadedType.isValid()) { |
1414 | // there isn't really an incubation process for C++ backed types |
1415 | // so just create the object and signal that we are ready |
1416 | |
1417 | p->incubateCppBasedComponent(component: this, context); |
1418 | return; |
1419 | } |
1420 | |
1421 | QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(e: d->engine); |
1422 | |
1423 | p->compilationUnit = d->compilationUnit; |
1424 | p->enginePriv = enginePriv; |
1425 | p->creator.reset(other: new QQmlObjectCreator(contextData, d->compilationUnit, d->creationContext, p.data())); |
1426 | p->subComponentToCreate = d->start; |
1427 | |
1428 | enginePriv->incubate(incubator, forContextData); |
1429 | } |
1430 | |
1431 | /*! |
1432 | Set top-level \a properties of the \a component. |
1433 | |
1434 | This method provides advanced control over component instance creation. |
1435 | In general, programmers should use |
1436 | \l QQmlComponent::createWithInitialProperties to create a component. |
1437 | |
1438 | Use this method after beginCreate and before completeCreate has been called. |
1439 | If a provided property does not exist, a warning is issued. |
1440 | |
1441 | \since 5.14 |
1442 | */ |
1443 | void QQmlComponent::setInitialProperties(QObject *component, const QVariantMap &properties) |
1444 | { |
1445 | Q_D(QQmlComponent); |
1446 | for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) |
1447 | d->setInitialProperty(base: component, name: it.key(), value: it.value()); |
1448 | } |
1449 | |
1450 | /* |
1451 | This is essentially a copy of QQmlComponent::create(); except it takes the QQmlContextData |
1452 | arguments instead of QQmlContext which means we don't have to construct the rather weighty |
1453 | wrapper class for every delegate item. |
1454 | |
1455 | This is used by QQmlDelegateModel. |
1456 | */ |
1457 | void QQmlComponentPrivate::incubateObject( |
1458 | QQmlIncubator *incubationTask, |
1459 | QQmlComponent *component, |
1460 | QQmlEngine *engine, |
1461 | const QQmlRefPointer<QQmlContextData> &context, |
1462 | const QQmlRefPointer<QQmlContextData> &forContext) |
1463 | { |
1464 | QQmlIncubatorPrivate *incubatorPriv = QQmlIncubatorPrivate::get(incubator: incubationTask); |
1465 | QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(e: engine); |
1466 | QQmlComponentPrivate *componentPriv = QQmlComponentPrivate::get(c: component); |
1467 | |
1468 | incubatorPriv->compilationUnit = componentPriv->compilationUnit; |
1469 | incubatorPriv->enginePriv = enginePriv; |
1470 | incubatorPriv->creator.reset(other: new QQmlObjectCreator(context, componentPriv->compilationUnit, componentPriv->creationContext)); |
1471 | |
1472 | if (start == -1) { |
1473 | if (const QString *icName = componentPriv->inlineComponentName.get()) { |
1474 | start = compilationUnit->inlineComponentId(inlineComponentName: *icName); |
1475 | Q_ASSERT(start > 0); |
1476 | } |
1477 | } |
1478 | incubatorPriv->subComponentToCreate = componentPriv->start; |
1479 | |
1480 | enginePriv->incubate(*incubationTask, forContext); |
1481 | } |
1482 | |
1483 | |
1484 | |
1485 | class QQmlComponentIncubator; |
1486 | |
1487 | namespace QV4 { |
1488 | |
1489 | namespace Heap { |
1490 | |
1491 | #define QmlIncubatorObjectMembers(class, Member) \ |
1492 | Member(class, HeapValue, HeapValue, valuemap) \ |
1493 | Member(class, HeapValue, HeapValue, statusChanged) \ |
1494 | Member(class, Pointer, QmlContext *, qmlContext) \ |
1495 | Member(class, NoMark, QQmlComponentIncubator *, incubator) \ |
1496 | Member(class, NoMark, QV4QPointer<QObject>, parent) |
1497 | |
1498 | DECLARE_HEAP_OBJECT(QmlIncubatorObject, Object) { |
1499 | DECLARE_MARKOBJECTS(QmlIncubatorObject) |
1500 | |
1501 | void init(QQmlIncubator::IncubationMode = QQmlIncubator::Asynchronous); |
1502 | inline void destroy(); |
1503 | }; |
1504 | |
1505 | } |
1506 | |
1507 | struct QmlIncubatorObject : public QV4::Object |
1508 | { |
1509 | V4_OBJECT2(QmlIncubatorObject, Object) |
1510 | V4_NEEDS_DESTROY |
1511 | |
1512 | static ReturnedValue method_get_statusChanged(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); |
1513 | static ReturnedValue method_set_statusChanged(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); |
1514 | static ReturnedValue method_get_status(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); |
1515 | static ReturnedValue method_get_object(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); |
1516 | static ReturnedValue method_forceCompletion(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); |
1517 | |
1518 | void statusChanged(QQmlIncubator::Status); |
1519 | void setInitialState(QObject *, RequiredProperties *requiredProperties); |
1520 | }; |
1521 | |
1522 | } |
1523 | |
1524 | DEFINE_OBJECT_VTABLE(QV4::QmlIncubatorObject); |
1525 | |
1526 | class QQmlComponentIncubator : public QQmlIncubator |
1527 | { |
1528 | public: |
1529 | QQmlComponentIncubator(QV4::Heap::QmlIncubatorObject *inc, IncubationMode mode) |
1530 | : QQmlIncubator(mode) |
1531 | { |
1532 | incubatorObject.set(engine: inc->internalClass->engine, obj: inc); |
1533 | } |
1534 | |
1535 | void statusChanged(Status s) override { |
1536 | QV4::Scope scope(incubatorObject.engine()); |
1537 | QV4::Scoped<QV4::QmlIncubatorObject> i(scope, incubatorObject.as<QV4::QmlIncubatorObject>()); |
1538 | i->statusChanged(s); |
1539 | } |
1540 | |
1541 | void setInitialState(QObject *o) override { |
1542 | QV4::Scope scope(incubatorObject.engine()); |
1543 | QV4::Scoped<QV4::QmlIncubatorObject> i(scope, incubatorObject.as<QV4::QmlIncubatorObject>()); |
1544 | auto d = QQmlIncubatorPrivate::get(incubator: this); |
1545 | i->setInitialState(o, requiredProperties: d->requiredProperties()); |
1546 | } |
1547 | |
1548 | QV4::PersistentValue incubatorObject; // keep a strong internal reference while incubating |
1549 | }; |
1550 | |
1551 | |
1552 | static void QQmlComponent_setQmlParent(QObject *me, QObject *parent) |
1553 | { |
1554 | if (parent) { |
1555 | me->setParent(parent); |
1556 | typedef QQmlPrivate::AutoParentFunction APF; |
1557 | QList<APF> functions = QQmlMetaType::parentFunctions(); |
1558 | |
1559 | bool needParent = false; |
1560 | for (int ii = 0; ii < functions.size(); ++ii) { |
1561 | QQmlPrivate::AutoParentResult res = functions.at(i: ii)(me, parent); |
1562 | if (res == QQmlPrivate::Parented) { |
1563 | needParent = false; |
1564 | break; |
1565 | } else if (res == QQmlPrivate::IncompatibleParent) { |
1566 | needParent = true; |
1567 | } |
1568 | } |
1569 | if (needParent) |
1570 | qmlWarning(me) << "Created graphical object was not placed in the graphics scene." ; |
1571 | } |
1572 | } |
1573 | |
1574 | /*! |
1575 | \qmlmethod QtObject Component::createObject(QtObject parent, object properties) |
1576 | |
1577 | Creates and returns an object instance of this component that will have |
1578 | the given \a parent and \a properties. The \a properties argument is optional. |
1579 | Returns null if object creation fails. |
1580 | |
1581 | The object will be created in the same context as the one in which the component |
1582 | was created. This function will always return null when called on components |
1583 | which were not created in QML. |
1584 | |
1585 | If you wish to create an object without setting a parent, specify \c null for |
1586 | the \a parent value. Note that if the returned object is to be displayed, you |
1587 | must provide a valid \a parent value or set the returned object's \l{Item::parent}{parent} |
1588 | property, otherwise the object will not be visible. |
1589 | |
1590 | If a \a parent is not provided to createObject(), a reference to the returned object must be held so that |
1591 | it is not destroyed by the garbage collector. This is true regardless of whether \l{Item::parent} is set afterwards, |
1592 | because setting the Item parent does not change object ownership. Only the graphical parent is changed. |
1593 | |
1594 | As of \c {QtQuick 1.1}, this method accepts an optional \a properties argument that specifies a |
1595 | map of initial property values for the created object. These values are applied before the object |
1596 | creation is finalized. This is more efficient than setting property values after object creation, |
1597 | particularly where large sets of property values are defined, and also allows property bindings |
1598 | to be set up (using \l{Qt::binding}{Qt.binding}) before the object is created. |
1599 | |
1600 | The \a properties argument is specified as a map of property-value items. For example, the code |
1601 | below creates an object with initial \c x and \c y values of 100 and 100, respectively: |
1602 | |
1603 | \qml |
1604 | const component = Qt.createComponent("Button.qml"); |
1605 | if (component.status === Component.Ready) { |
1606 | component.createObject(parent, { x: 100, y: 100 }); |
1607 | } |
1608 | \endqml |
1609 | |
1610 | Dynamically created instances can be deleted with the \c destroy() method. |
1611 | See \l {Dynamic QML Object Creation from JavaScript} for more information. |
1612 | |
1613 | \sa incubateObject() |
1614 | */ |
1615 | |
1616 | |
1617 | void QQmlComponentPrivate::setInitialProperties( |
1618 | QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, |
1619 | const QV4::Value &v, RequiredProperties *requiredProperties, QObject *createdComponent, |
1620 | QQmlObjectCreator *creator) |
1621 | { |
1622 | QV4::Scope scope(engine); |
1623 | QV4::ScopedObject object(scope); |
1624 | QV4::ScopedObject valueMap(scope, v); |
1625 | QV4::ObjectIterator it(scope, valueMap, QV4::ObjectIterator::EnumerableOnly); |
1626 | QV4::ScopedString name(scope); |
1627 | QV4::ScopedValue val(scope); |
1628 | if (engine->hasException) |
1629 | return; |
1630 | |
1631 | // js modules (mjs) have no qmlContext |
1632 | QV4::ScopedStackFrame frame(scope, qmlContext ? qmlContext : engine->scriptContext()); |
1633 | |
1634 | while (1) { |
1635 | name = it.nextPropertyNameAsString(value: val); |
1636 | if (!name) |
1637 | break; |
1638 | object = o; |
1639 | const QStringList properties = name->toQString().split(sep: QLatin1Char('.')); |
1640 | bool isTopLevelProperty = properties.size() == 1; |
1641 | for (int i = 0; i < properties.size() - 1; ++i) { |
1642 | name = engine->newString(s: properties.at(i)); |
1643 | object = object->get(name); |
1644 | if (engine->hasException || !object) { |
1645 | break; |
1646 | } |
1647 | } |
1648 | if (engine->hasException) { |
1649 | qmlWarning(me: createdComponent, error: engine->catchExceptionAsQmlError()); |
1650 | continue; |
1651 | } |
1652 | if (!object) { |
1653 | QQmlError error; |
1654 | error.setUrl(qmlContext ? qmlContext->qmlContext()->url() : QUrl()); |
1655 | error.setDescription(QLatin1String("Cannot resolve property \"%1\"." ) |
1656 | .arg(args: properties.join(sep: u'.'))); |
1657 | qmlWarning(me: createdComponent, error); |
1658 | continue; |
1659 | } |
1660 | const QString lastProperty = properties.last(); |
1661 | name = engine->newString(s: lastProperty); |
1662 | object->put(name, v: val); |
1663 | if (engine->hasException) { |
1664 | qmlWarning(me: createdComponent, error: engine->catchExceptionAsQmlError()); |
1665 | continue; |
1666 | } else if (isTopLevelProperty && requiredProperties) { |
1667 | auto prop = removePropertyFromRequired(createdComponent, name: name->toQString(), |
1668 | requiredProperties, engine: engine->qmlEngine()); |
1669 | } |
1670 | |
1671 | removePendingQPropertyBinding(object, propertyName: lastProperty, creator); |
1672 | } |
1673 | |
1674 | engine->hasException = false; |
1675 | } |
1676 | |
1677 | QQmlError QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(const RequiredPropertyInfo &unsetRequiredProperty) |
1678 | { |
1679 | QQmlError error; |
1680 | QString description = QLatin1String("Required property %1 was not initialized" ).arg(args: unsetRequiredProperty.propertyName); |
1681 | switch (unsetRequiredProperty.aliasesToRequired.size()) { |
1682 | case 0: |
1683 | break; |
1684 | case 1: { |
1685 | const auto info = unsetRequiredProperty.aliasesToRequired.first(); |
1686 | description += QLatin1String("\nIt can be set via the alias property %1 from %2\n" ).arg(args: info.propertyName, args: info.fileUrl.toString()); |
1687 | break; |
1688 | } |
1689 | default: |
1690 | description += QLatin1String("\nIt can be set via one of the following alias properties:" ); |
1691 | for (auto aliasInfo: unsetRequiredProperty.aliasesToRequired) { |
1692 | description += QLatin1String("\n- %1 (%2)" ).arg(args&: aliasInfo.propertyName, args: aliasInfo.fileUrl.toString()); |
1693 | } |
1694 | description += QLatin1Char('\n'); |
1695 | } |
1696 | error.setDescription(description); |
1697 | error.setUrl(unsetRequiredProperty.fileUrl); |
1698 | error.setLine(qmlConvertSourceCoordinate<quint32, int>( |
1699 | n: unsetRequiredProperty.location.line())); |
1700 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>( |
1701 | n: unsetRequiredProperty.location.column())); |
1702 | return error; |
1703 | } |
1704 | |
1705 | #if QT_DEPRECATED_SINCE(6, 3) |
1706 | /*! |
1707 | \internal |
1708 | */ |
1709 | void QQmlComponent::createObject(QQmlV4Function *args) |
1710 | { |
1711 | Q_D(QQmlComponent); |
1712 | Q_ASSERT(d->engine); |
1713 | Q_ASSERT(args); |
1714 | |
1715 | qmlWarning(me: this) << "Unsuitable arguments passed to createObject(). The first argument should " |
1716 | "be a QObject* or null, and the second argument should be a JavaScript " |
1717 | "object or a QVariantMap" ; |
1718 | |
1719 | QObject *parent = nullptr; |
1720 | QV4::ExecutionEngine *v4 = args->v4engine(); |
1721 | QV4::Scope scope(v4); |
1722 | QV4::ScopedValue valuemap(scope, QV4::Value::undefinedValue()); |
1723 | |
1724 | if (args->length() >= 1) { |
1725 | QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, (*args)[0]); |
1726 | if (qobjectWrapper) |
1727 | parent = qobjectWrapper->object(); |
1728 | } |
1729 | |
1730 | if (args->length() >= 2) { |
1731 | QV4::ScopedValue v(scope, (*args)[1]); |
1732 | if (!v->as<QV4::Object>() || v->as<QV4::ArrayObject>()) { |
1733 | qmlWarning(me: this) << tr(s: "createObject: value is not an object" ); |
1734 | args->setReturnValue(QV4::Encode::null()); |
1735 | return; |
1736 | } |
1737 | valuemap = v; |
1738 | } |
1739 | |
1740 | QQmlContext *ctxt = creationContext(); |
1741 | if (!ctxt) ctxt = d->engine->rootContext(); |
1742 | |
1743 | QObject *rv = beginCreate(context: ctxt); |
1744 | |
1745 | if (!rv) { |
1746 | args->setReturnValue(QV4::Encode::null()); |
1747 | return; |
1748 | } |
1749 | |
1750 | QQmlComponent_setQmlParent(me: rv, parent); |
1751 | |
1752 | QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine: v4, object: rv)); |
1753 | Q_ASSERT(object->isObject()); |
1754 | |
1755 | if (!valuemap->isUndefined()) { |
1756 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, v4->qmlContext()); |
1757 | QQmlComponentPrivate::setInitialProperties( |
1758 | engine: v4, qmlContext, o: object, v: valuemap, requiredProperties: d->state.requiredProperties(), createdComponent: rv, |
1759 | creator: d->state.creator()); |
1760 | } |
1761 | if (d->state.hasUnsetRequiredProperties()) { |
1762 | QList<QQmlError> errors; |
1763 | for (const auto &requiredProperty: std::as_const(t&: *d->state.requiredProperties())) { |
1764 | errors.push_back(t: QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty: requiredProperty)); |
1765 | } |
1766 | qmlWarning(me: rv, errors); |
1767 | args->setReturnValue(QV4::Encode::null()); |
1768 | delete rv; |
1769 | return; |
1770 | } |
1771 | |
1772 | d->completeCreate(); |
1773 | |
1774 | Q_ASSERT(QQmlData::get(rv)); |
1775 | QQmlData::get(object: rv)->explicitIndestructibleSet = false; |
1776 | QQmlData::get(object: rv)->indestructible = false; |
1777 | |
1778 | args->setReturnValue(object->asReturnedValue()); |
1779 | } |
1780 | #endif |
1781 | |
1782 | /*! |
1783 | \internal |
1784 | */ |
1785 | QObject *QQmlComponent::createObject(QObject *parent, const QVariantMap &properties) |
1786 | { |
1787 | Q_D(QQmlComponent); |
1788 | Q_ASSERT(d->engine); |
1789 | QObject *rv = d->createWithProperties(parent, properties, context: creationContext(), |
1790 | behavior: QQmlComponentPrivate::CreateWarnAboutRequiredProperties); |
1791 | if (rv) { |
1792 | QQmlData *qmlData = QQmlData::get(object: rv); |
1793 | Q_ASSERT(qmlData); |
1794 | qmlData->explicitIndestructibleSet = false; |
1795 | qmlData->indestructible = false; |
1796 | } |
1797 | return rv; |
1798 | } |
1799 | |
1800 | /*! |
1801 | \qmlmethod object Component::incubateObject(QtObject parent, object properties, enumeration mode) |
1802 | |
1803 | Creates an incubator for an instance of this component. Incubators allow new component |
1804 | instances to be instantiated asynchronously and do not cause freezes in the UI. |
1805 | |
1806 | The \a parent argument specifies the parent the created instance will have. Omitting the |
1807 | parameter or passing null will create an object with no parent. In this case, a reference |
1808 | to the created object must be held so that it is not destroyed by the garbage collector. |
1809 | |
1810 | The \a properties argument is specified as a map of property-value items which will be |
1811 | set on the created object during its construction. \a mode may be Qt.Synchronous or |
1812 | Qt.Asynchronous, and controls whether the instance is created synchronously or asynchronously. |
1813 | The default is asynchronous. In some circumstances, even if Qt.Synchronous is specified, |
1814 | the incubator may create the object asynchronously. This happens if the component calling |
1815 | incubateObject() is itself being created asynchronously. |
1816 | |
1817 | All three arguments are optional. |
1818 | |
1819 | If successful, the method returns an incubator, otherwise null. The incubator has the following |
1820 | properties: |
1821 | |
1822 | \list |
1823 | \li \c status - The status of the incubator. Valid values are Component.Ready, Component.Loading and |
1824 | Component.Error. |
1825 | \li \c object - The created object instance. Will only be available once the incubator is in the |
1826 | Ready status. |
1827 | \li \c onStatusChanged - Specifies a callback function to be invoked when the status changes. The |
1828 | status is passed as a parameter to the callback. |
1829 | \li \c{forceCompletion()} - Call to complete incubation synchronously. |
1830 | \endlist |
1831 | |
1832 | The following example demonstrates how to use an incubator: |
1833 | |
1834 | \qml |
1835 | const component = Qt.createComponent("Button.qml"); |
1836 | |
1837 | const incubator = component.incubateObject(parent, { x: 10, y: 10 }); |
1838 | if (incubator.status !== Component.Ready) { |
1839 | incubator.onStatusChanged = function(status) { |
1840 | if (status === Component.Ready) { |
1841 | print("Object", incubator.object, "is now ready!"); |
1842 | } |
1843 | }; |
1844 | } else { |
1845 | print("Object", incubator.object, "is ready immediately!"); |
1846 | } |
1847 | \endqml |
1848 | |
1849 | Dynamically created instances can be deleted with the \c destroy() method. |
1850 | See \l {Dynamic QML Object Creation from JavaScript} for more information. |
1851 | |
1852 | \sa createObject() |
1853 | */ |
1854 | |
1855 | /*! |
1856 | \internal |
1857 | */ |
1858 | void QQmlComponent::incubateObject(QQmlV4Function *args) |
1859 | { |
1860 | Q_D(QQmlComponent); |
1861 | Q_ASSERT(d->engine); |
1862 | Q_UNUSED(d); |
1863 | Q_ASSERT(args); |
1864 | QV4::ExecutionEngine *v4 = args->v4engine(); |
1865 | QV4::Scope scope(v4); |
1866 | |
1867 | QObject *parent = nullptr; |
1868 | QV4::ScopedValue valuemap(scope, QV4::Value::undefinedValue()); |
1869 | QQmlIncubator::IncubationMode mode = QQmlIncubator::Asynchronous; |
1870 | |
1871 | if (args->length() >= 1) { |
1872 | QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, (*args)[0]); |
1873 | if (qobjectWrapper) |
1874 | parent = qobjectWrapper->object(); |
1875 | } |
1876 | |
1877 | if (args->length() >= 2) { |
1878 | QV4::ScopedValue v(scope, (*args)[1]); |
1879 | if (v->isNull()) { |
1880 | } else if (!v->as<QV4::Object>() || v->as<QV4::ArrayObject>()) { |
1881 | qmlWarning(me: this) << tr(s: "createObject: value is not an object" ); |
1882 | args->setReturnValue(QV4::Encode::null()); |
1883 | return; |
1884 | } else { |
1885 | valuemap = v; |
1886 | } |
1887 | } |
1888 | |
1889 | if (args->length() >= 3) { |
1890 | QV4::ScopedValue val(scope, (*args)[2]); |
1891 | quint32 v = val->toUInt32(); |
1892 | if (v == 0) |
1893 | mode = QQmlIncubator::Asynchronous; |
1894 | else if (v == 1) |
1895 | mode = QQmlIncubator::AsynchronousIfNested; |
1896 | } |
1897 | |
1898 | QQmlComponentExtension *e = componentExtension(engine: args->v4engine()); |
1899 | |
1900 | QV4::Scoped<QV4::QmlIncubatorObject> r(scope, v4->memoryManager->allocate<QV4::QmlIncubatorObject>(args&: mode)); |
1901 | QV4::ScopedObject p(scope, e->incubationProto.value()); |
1902 | r->setPrototypeOf(p); |
1903 | |
1904 | if (!valuemap->isUndefined()) |
1905 | r->d()->valuemap.set(e: scope.engine, newVal: valuemap); |
1906 | r->d()->qmlContext.set(e: scope.engine, newVal: v4->qmlContext()); |
1907 | r->d()->parent = parent; |
1908 | |
1909 | QQmlIncubator *incubator = r->d()->incubator; |
1910 | create(incubator&: *incubator, context: creationContext()); |
1911 | |
1912 | if (incubator->status() == QQmlIncubator::Null) { |
1913 | args->setReturnValue(QV4::Encode::null()); |
1914 | } else { |
1915 | args->setReturnValue(r.asReturnedValue()); |
1916 | } |
1917 | } |
1918 | |
1919 | // XXX used by QSGLoader |
1920 | void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate, RequiredProperties *requiredProperties) |
1921 | { |
1922 | QV4::ExecutionEngine *v4engine = engine->handle(); |
1923 | QV4::Scope scope(v4engine); |
1924 | |
1925 | QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine: v4engine, object: toCreate)); |
1926 | Q_ASSERT(object->as<QV4::Object>()); |
1927 | |
1928 | if (!valuemap.isUndefined()) { |
1929 | setInitialProperties( |
1930 | engine: v4engine, qmlContext, o: object, v: valuemap, requiredProperties, createdComponent: toCreate, creator: state.creator()); |
1931 | } |
1932 | } |
1933 | |
1934 | QQmlComponentExtension::QQmlComponentExtension(QV4::ExecutionEngine *v4) |
1935 | { |
1936 | QV4::Scope scope(v4); |
1937 | QV4::ScopedObject proto(scope, v4->newObject()); |
1938 | proto->defineAccessorProperty(QStringLiteral("onStatusChanged" ), |
1939 | getter: QV4::QmlIncubatorObject::method_get_statusChanged, setter: QV4::QmlIncubatorObject::method_set_statusChanged); |
1940 | proto->defineAccessorProperty(QStringLiteral("status" ), getter: QV4::QmlIncubatorObject::method_get_status, setter: nullptr); |
1941 | proto->defineAccessorProperty(QStringLiteral("object" ), getter: QV4::QmlIncubatorObject::method_get_object, setter: nullptr); |
1942 | proto->defineDefaultProperty(QStringLiteral("forceCompletion" ), code: QV4::QmlIncubatorObject::method_forceCompletion); |
1943 | |
1944 | incubationProto.set(engine: v4, value: proto); |
1945 | } |
1946 | |
1947 | QV4::ReturnedValue QV4::QmlIncubatorObject::method_get_object(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1948 | { |
1949 | QV4::Scope scope(b); |
1950 | QV4::Scoped<QmlIncubatorObject> o(scope, thisObject->as<QmlIncubatorObject>()); |
1951 | if (!o) |
1952 | THROW_TYPE_ERROR(); |
1953 | |
1954 | return QV4::QObjectWrapper::wrap(engine: scope.engine, object: o->d()->incubator->object()); |
1955 | } |
1956 | |
1957 | QV4::ReturnedValue QV4::QmlIncubatorObject::method_forceCompletion(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1958 | { |
1959 | QV4::Scope scope(b); |
1960 | QV4::Scoped<QmlIncubatorObject> o(scope, thisObject->as<QmlIncubatorObject>()); |
1961 | if (!o) |
1962 | THROW_TYPE_ERROR(); |
1963 | |
1964 | o->d()->incubator->forceCompletion(); |
1965 | |
1966 | RETURN_UNDEFINED(); |
1967 | } |
1968 | |
1969 | QV4::ReturnedValue QV4::QmlIncubatorObject::method_get_status(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1970 | { |
1971 | QV4::Scope scope(b); |
1972 | QV4::Scoped<QmlIncubatorObject> o(scope, thisObject->as<QmlIncubatorObject>()); |
1973 | if (!o) |
1974 | THROW_TYPE_ERROR(); |
1975 | |
1976 | return QV4::Encode(o->d()->incubator->status()); |
1977 | } |
1978 | |
1979 | QV4::ReturnedValue QV4::QmlIncubatorObject::method_get_statusChanged(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1980 | { |
1981 | QV4::Scope scope(b); |
1982 | QV4::Scoped<QmlIncubatorObject> o(scope, thisObject->as<QmlIncubatorObject>()); |
1983 | if (!o) |
1984 | THROW_TYPE_ERROR(); |
1985 | |
1986 | return QV4::Encode(o->d()->statusChanged); |
1987 | } |
1988 | |
1989 | QV4::ReturnedValue QV4::QmlIncubatorObject::method_set_statusChanged(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1990 | { |
1991 | QV4::Scope scope(b); |
1992 | QV4::Scoped<QmlIncubatorObject> o(scope, thisObject->as<QmlIncubatorObject>()); |
1993 | if (!o || argc < 1) |
1994 | THROW_TYPE_ERROR(); |
1995 | |
1996 | o->d()->statusChanged.set(e: scope.engine, newVal: argv[0]); |
1997 | |
1998 | RETURN_UNDEFINED(); |
1999 | } |
2000 | |
2001 | QQmlComponentExtension::~QQmlComponentExtension() |
2002 | { |
2003 | } |
2004 | |
2005 | void QV4::Heap::QmlIncubatorObject::init(QQmlIncubator::IncubationMode m) |
2006 | { |
2007 | Object::init(); |
2008 | valuemap.set(e: internalClass->engine, newVal: QV4::Value::undefinedValue()); |
2009 | statusChanged.set(e: internalClass->engine, newVal: QV4::Value::undefinedValue()); |
2010 | parent.init(); |
2011 | qmlContext.set(e: internalClass->engine, newVal: nullptr); |
2012 | incubator = new QQmlComponentIncubator(this, m); |
2013 | } |
2014 | |
2015 | void QV4::Heap::QmlIncubatorObject::destroy() { |
2016 | delete incubator; |
2017 | parent.destroy(); |
2018 | Object::destroy(); |
2019 | } |
2020 | |
2021 | void QV4::QmlIncubatorObject::setInitialState(QObject *o, RequiredProperties *requiredProperties) |
2022 | { |
2023 | QQmlComponent_setQmlParent(me: o, parent: d()->parent); |
2024 | |
2025 | if (!d()->valuemap.isUndefined()) { |
2026 | QV4::ExecutionEngine *v4 = engine(); |
2027 | QV4::Scope scope(v4); |
2028 | QV4::ScopedObject obj(scope, QV4::QObjectWrapper::wrap(engine: v4, object: o)); |
2029 | QV4::Scoped<QV4::QmlContext> qmlCtxt(scope, d()->qmlContext); |
2030 | QQmlComponentPrivate::setInitialProperties( |
2031 | engine: v4, qmlContext: qmlCtxt, o: obj, v: d()->valuemap, requiredProperties, createdComponent: o, |
2032 | creator: QQmlIncubatorPrivate::get(incubator: d()->incubator)->creator.data()); |
2033 | } |
2034 | } |
2035 | |
2036 | void QV4::QmlIncubatorObject::statusChanged(QQmlIncubator::Status s) |
2037 | { |
2038 | QV4::Scope scope(engine()); |
2039 | // hold the incubated object in a scoped value to prevent it's destruction before this method returns |
2040 | QV4::ScopedObject incubatedObject(scope, QV4::QObjectWrapper::wrap(engine: scope.engine, object: d()->incubator->object())); |
2041 | |
2042 | if (s == QQmlIncubator::Ready) { |
2043 | Q_ASSERT(QQmlData::get(d()->incubator->object())); |
2044 | QQmlData::get(object: d()->incubator->object())->explicitIndestructibleSet = false; |
2045 | QQmlData::get(object: d()->incubator->object())->indestructible = false; |
2046 | } |
2047 | |
2048 | QV4::ScopedFunctionObject f(scope, d()->statusChanged); |
2049 | if (f) { |
2050 | QV4::JSCallArguments jsCallData(scope, 1); |
2051 | *jsCallData.thisObject = this; |
2052 | jsCallData.args[0] = QV4::Value::fromUInt32(i: s); |
2053 | f->call(data: jsCallData); |
2054 | if (scope.hasException()) { |
2055 | QQmlError error = scope.engine->catchExceptionAsQmlError(); |
2056 | QQmlEnginePrivate::warning(QQmlEnginePrivate::get(e: scope.engine->qmlEngine()), error); |
2057 | } |
2058 | } |
2059 | |
2060 | if (s != QQmlIncubator::Loading) |
2061 | d()->incubator->incubatorObject.clear(); |
2062 | } |
2063 | |
2064 | #undef INITIALPROPERTIES_SOURCE |
2065 | |
2066 | QT_END_NAMESPACE |
2067 | |
2068 | #include "moc_qqmlcomponent.cpp" |
2069 | #include "moc_qqmlcomponentattached_p.cpp" |
2070 | |