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 "qqmlopenmetaobject_p.h"
5#include <private/qqmlpropertycache_p.h>
6#include <private/qqmldata_p.h>
7#include <private/qqmlmetatype_p.h>
8#include <private/qmetaobjectbuilder_p.h>
9#include <qdebug.h>
10#include <QtCore/qset.h>
11
12QT_BEGIN_NAMESPACE
13
14
15class QQmlOpenMetaObjectTypePrivate
16{
17public:
18 QQmlOpenMetaObjectTypePrivate() : mem(nullptr) {}
19
20 void init(const QMetaObject *metaObj);
21
22 int propertyOffset;
23 int signalOffset;
24 QHash<QByteArray, int> names;
25 QMetaObjectBuilder mob;
26 QMetaObject *mem;
27
28 // TODO: We need to make sure that this does not escape into other threads.
29 // In particular, all its non-const uses are probably wrong. You should
30 // only set the open metaobject to "cached" once it's not going to be
31 // modified anymore.
32 QQmlPropertyCache::Ptr cache;
33
34 QSet<QQmlOpenMetaObject*> referers;
35};
36
37QQmlOpenMetaObjectType::QQmlOpenMetaObjectType(const QMetaObject *base)
38 : d(new QQmlOpenMetaObjectTypePrivate)
39{
40 d->init(metaObj: base);
41}
42
43QQmlOpenMetaObjectType::~QQmlOpenMetaObjectType()
44{
45 if (d->mem)
46 free(ptr: d->mem);
47 delete d;
48}
49
50int QQmlOpenMetaObjectType::propertyOffset() const
51{
52 return d->propertyOffset;
53}
54
55int QQmlOpenMetaObjectType::signalOffset() const
56{
57 return d->signalOffset;
58}
59
60int QQmlOpenMetaObjectType::propertyCount() const
61{
62 return d->names.size();
63}
64
65QByteArray QQmlOpenMetaObjectType::propertyName(int idx) const
66{
67 Q_ASSERT(idx >= 0 && idx < d->names.size());
68
69 return d->mob.property(index: idx).name();
70}
71
72void QQmlOpenMetaObjectType::createProperties(const QVector<QByteArray> &names)
73{
74 for (int i = 0; i < names.size(); ++i) {
75 const QByteArray &name = names.at(i);
76 const int id = d->mob.propertyCount();
77 d->mob.addSignal(signature: "__" + QByteArray::number(id) + "()");
78 QMetaPropertyBuilder build = d->mob.addProperty(name, type: "QVariant", notifierId: id);
79 propertyCreated(id, build);
80 d->names.insert(key: name, value: id);
81 }
82 free(ptr: d->mem);
83 d->mem = d->mob.toMetaObject();
84 QSet<QQmlOpenMetaObject*>::iterator it = d->referers.begin();
85 while (it != d->referers.end()) {
86 QQmlOpenMetaObject *omo = *it;
87 *static_cast<QMetaObject *>(omo) = *d->mem;
88 if (d->cache)
89 d->cache->update(omo);
90 ++it;
91 }
92}
93
94int QQmlOpenMetaObjectType::createProperty(const QByteArray &name)
95{
96 const int signalIdx = d->mob.addSignal(
97 signature: "__" + QByteArray::number(d->mob.propertyCount()) + "()").index();
98 QMetaPropertyBuilder build = d->mob.addProperty(name, type: "QVariant", notifierId: signalIdx);
99 propertyCreated(build.index(), build);
100 free(ptr: d->mem);
101 d->mem = d->mob.toMetaObject();
102 d->names.insert(key: name, value: build.index());
103 QSet<QQmlOpenMetaObject*>::iterator it = d->referers.begin();
104 while (it != d->referers.end()) {
105 QQmlOpenMetaObject *omo = *it;
106 *static_cast<QMetaObject *>(omo) = *d->mem;
107 if (d->cache)
108 d->cache->update(omo);
109 ++it;
110 }
111
112 return d->propertyOffset + build.index();
113}
114
115void QQmlOpenMetaObjectType::propertyCreated(int id, QMetaPropertyBuilder &builder)
116{
117 if (d->referers.size())
118 (*d->referers.begin())->propertyCreated(id, builder);
119}
120
121void QQmlOpenMetaObjectTypePrivate::init(const QMetaObject *metaObj)
122{
123 if (!mem) {
124 mob.setSuperClass(metaObj);
125 mob.setClassName(metaObj->className());
126 mob.setFlags(MetaObjectFlag::DynamicMetaObject);
127
128 mem = mob.toMetaObject();
129
130 propertyOffset = mem->propertyOffset();
131 signalOffset = mem->methodOffset();
132 }
133}
134
135//----------------------------------------------------------------------------
136
137class QQmlOpenMetaObjectPrivate
138{
139public:
140 QQmlOpenMetaObjectPrivate(QQmlOpenMetaObject *_q, QObject *obj)
141 : q(_q), object(obj) {}
142
143 struct Property {
144 private:
145 QVariant m_value;
146 QPointer<QObject> qobjectTracker;
147 public:
148 bool valueSet = false;
149
150 QVariant value() const {
151 if (m_value.metaType().flags() & QMetaType::PointerToQObject
152 && qobjectTracker.isNull())
153 return QVariant::fromValue<QObject*>(value: nullptr);
154 return m_value;
155 }
156 QVariant &valueRef() { return m_value; }
157 void setValue(const QVariant &v) {
158 m_value = v;
159 valueSet = true;
160 if (v.metaType().flags() & QMetaType::PointerToQObject)
161 qobjectTracker = m_value.value<QObject*>();
162 }
163 };
164
165 inline void setPropertyValue(int idx, const QVariant &value) {
166 if (data.size() <= idx)
167 data.resize(size: idx + 1);
168 data[idx].setValue(value);
169 }
170
171 inline Property &propertyRef(int idx) {
172 if (data.size() <= idx)
173 data.resize(size: idx + 1);
174 Property &prop = data[idx];
175 if (!prop.valueSet)
176 prop.setValue(q->initialValue(idx));
177 return prop;
178 }
179
180 inline QVariant propertyValue(int idx) {
181 auto &prop = propertyRef(idx);
182 return prop.value();
183 }
184
185 inline QVariant &propertyValueRef(int idx) {
186 auto &prop = propertyRef(idx);
187 return prop.valueRef();
188 }
189
190 inline bool hasProperty(int idx) const {
191 if (idx >= data.size())
192 return false;
193 return data[idx].valueSet;
194 }
195
196 void dropPropertyCache() {
197 if (QQmlData *ddata = QQmlData::get(object, /*create*/false))
198 ddata->propertyCache.reset();
199 }
200
201 QQmlOpenMetaObject *q;
202 QDynamicMetaObjectData *parent = nullptr;
203 QVector<Property> data;
204 QObject *object;
205 QQmlRefPointer<QQmlOpenMetaObjectType> type;
206 QVector<QByteArray> *deferredPropertyNames = nullptr;
207 bool autoCreate = true;
208 bool cacheProperties = false;
209};
210
211QQmlOpenMetaObject::QQmlOpenMetaObject(QObject *obj, const QMetaObject *base)
212: d(new QQmlOpenMetaObjectPrivate(this, obj))
213{
214 d->type.adopt(other: new QQmlOpenMetaObjectType(base ? base : obj->metaObject()));
215 d->type->d->referers.insert(value: this);
216
217 QObjectPrivate *op = QObjectPrivate::get(o: obj);
218 d->parent = op->metaObject;
219 *static_cast<QMetaObject *>(this) = *d->type->d->mem;
220 op->metaObject = this;
221}
222
223QQmlOpenMetaObject::QQmlOpenMetaObject(
224 QObject *obj, const QQmlRefPointer<QQmlOpenMetaObjectType> &type)
225: d(new QQmlOpenMetaObjectPrivate(this, obj))
226{
227 d->type = type;
228 d->type->d->referers.insert(value: this);
229
230 QObjectPrivate *op = QObjectPrivate::get(o: obj);
231 d->parent = op->metaObject;
232 *static_cast<QMetaObject *>(this) = *d->type->d->mem;
233 op->metaObject = this;
234}
235
236QQmlOpenMetaObject::~QQmlOpenMetaObject()
237{
238 if (d->parent)
239 delete d->parent;
240 d->type->d->referers.remove(value: this);
241 delete d;
242}
243
244QQmlOpenMetaObjectType *QQmlOpenMetaObject::type() const
245{
246 return d->type.data();
247}
248
249void QQmlOpenMetaObject::emitPropertyNotification(const QByteArray &propertyName)
250{
251 QHash<QByteArray, int>::ConstIterator iter = d->type->d->names.constFind(key: propertyName);
252 if (iter == d->type->d->names.constEnd())
253 return;
254 activate(sender: d->object, signal_index: *iter + d->type->d->signalOffset, argv: nullptr);
255}
256
257void QQmlOpenMetaObject::unparent()
258{
259 d->parent = nullptr;
260}
261
262int QQmlOpenMetaObject::metaCall(QObject *o, QMetaObject::Call c, int id, void **a)
263{
264 Q_ASSERT(d->object == o);
265
266 if (( c == QMetaObject::ReadProperty || c == QMetaObject::WriteProperty)
267 && id >= d->type->d->propertyOffset) {
268 int propId = id - d->type->d->propertyOffset;
269 if (c == QMetaObject::ReadProperty) {
270 propertyRead(propId);
271 *reinterpret_cast<QVariant *>(a[0]) = d->propertyValue(idx: propId);
272 } else if (c == QMetaObject::WriteProperty) {
273 if (propId >= d->data.size() || d->data.at(i: propId).value() != *reinterpret_cast<QVariant *>(a[0])) {
274 propertyWrite(propId);
275 d->setPropertyValue(idx: propId, value: propertyWriteValue(propId, *reinterpret_cast<QVariant *>(a[0])));
276 propertyWritten(propId);
277 activate(sender: o, signal_index: d->type->d->signalOffset + propId, argv: nullptr);
278 }
279 }
280 return -1;
281 } else {
282 if (d->parent)
283 return d->parent->metaCall(o, c, id: id, a);
284 else
285 return o->qt_metacall(c, id, a);
286 }
287}
288
289QDynamicMetaObjectData *QQmlOpenMetaObject::parent() const
290{
291 return d->parent;
292}
293
294bool QQmlOpenMetaObject::checkedSetValue(int index, const QVariant &value, bool force)
295{
296 if (!force && d->propertyValue(idx: index) == value)
297 return false;
298
299 d->setPropertyValue(idx: index, value);
300 activate(sender: d->object, signal_index: index + d->type->d->signalOffset, argv: nullptr);
301 return true;
302}
303
304QVariant QQmlOpenMetaObject::value(int id) const
305{
306 return d->propertyValue(idx: id);
307}
308
309void QQmlOpenMetaObject::setValue(int id, const QVariant &value)
310{
311 d->setPropertyValue(idx: id, value: propertyWriteValue(id, value));
312 activate(sender: d->object, signal_index: id + d->type->d->signalOffset, argv: nullptr);
313}
314
315QVariant QQmlOpenMetaObject::value(const QByteArray &name) const
316{
317 QHash<QByteArray, int>::ConstIterator iter = d->type->d->names.constFind(key: name);
318 if (iter == d->type->d->names.cend())
319 return QVariant();
320
321 return d->propertyValue(idx: *iter);
322}
323
324QVariant &QQmlOpenMetaObject::valueRef(const QByteArray &name)
325{
326 QHash<QByteArray, int>::ConstIterator iter = d->type->d->names.constFind(key: name);
327 Q_ASSERT(iter != d->type->d->names.cend());
328
329 return d->propertyValueRef(idx: *iter);
330}
331
332bool QQmlOpenMetaObject::setValue(const QByteArray &name, const QVariant &val, bool force)
333{
334 QHash<QByteArray, int>::ConstIterator iter = d->type->d->names.constFind(key: name);
335
336 int id = -1;
337 if (iter == d->type->d->names.cend()) {
338 id = createProperty(name.constData(), "") - d->type->d->propertyOffset;
339 } else {
340 id = *iter;
341 }
342
343 if (id >= 0)
344 return checkedSetValue(index: id, value: val, force);
345
346 return false;
347}
348
349void QQmlOpenMetaObject::setValues(const QHash<QByteArray, QVariant> &values, bool force)
350{
351 QVector<QByteArray> missingProperties;
352 d->deferredPropertyNames = &missingProperties;
353 const auto &names = d->type->d->names;
354
355 for (auto valueIt = values.begin(), end = values.end(); valueIt != end; ++valueIt) {
356 const auto nameIt = names.constFind(key: valueIt.key());
357 if (nameIt == names.constEnd()) {
358 const int id = createProperty(valueIt.key(), "") - d->type->d->propertyOffset;
359
360 // If id >= 0 some override of createProperty() created it. Then set it.
361 // Else it either ends up in missingProperties and we create it later
362 // or it cannot be created.
363
364 if (id >= 0)
365 checkedSetValue(index: id, value: valueIt.value(), force);
366 } else {
367 checkedSetValue(index: *nameIt, value: valueIt.value(), force);
368 }
369 }
370
371 d->deferredPropertyNames = nullptr;
372 if (missingProperties.isEmpty())
373 return;
374
375 d->type->createProperties(names: missingProperties);
376 d->dropPropertyCache();
377
378 for (const QByteArray &name : std::as_const(t&: missingProperties))
379 checkedSetValue(index: names[name], value: values[name], force);
380}
381
382// returns true if this value has been initialized by a call to either value() or setValue()
383bool QQmlOpenMetaObject::hasValue(int id) const
384{
385 return d->hasProperty(idx: id);
386}
387
388void QQmlOpenMetaObject::setCached(bool c)
389{
390 if (c == d->cacheProperties)
391 return;
392
393 d->cacheProperties = c;
394
395 QQmlData *qmldata = QQmlData::get(object: d->object, create: true);
396 if (d->cacheProperties) {
397 // As the propertyCache is not saved in QQmlMetaType (due to it being dynamic)
398 // we cannot leak it to other places before we're done with it. Yes, it's still
399 // terrible.
400 if (!d->type->d->cache)
401 d->type->d->cache = QQmlPropertyCache::createStandalone(this);
402 qmldata->propertyCache = d->type->d->cache;
403 } else {
404 d->type->d->cache.reset();
405 qmldata->propertyCache.reset();
406 }
407}
408
409bool QQmlOpenMetaObject::autoCreatesProperties() const
410{
411 return d->autoCreate;
412}
413
414void QQmlOpenMetaObject::setAutoCreatesProperties(bool autoCreate)
415{
416 d->autoCreate = autoCreate;
417}
418
419
420int QQmlOpenMetaObject::createProperty(const char *name, const char *)
421{
422 if (d->autoCreate) {
423 if (d->deferredPropertyNames) {
424 // Defer the creation of new properties. See setValues(QHash<QByteArray, QVariant>)
425 d->deferredPropertyNames->append(t: name);
426 return -1;
427 }
428
429 const int result = d->type->createProperty(name);
430 d->dropPropertyCache();
431 return result;
432 } else
433 return -1;
434}
435
436void QQmlOpenMetaObject::propertyRead(int)
437{
438}
439
440void QQmlOpenMetaObject::propertyWrite(int)
441{
442}
443
444QVariant QQmlOpenMetaObject::propertyWriteValue(int, const QVariant &value)
445{
446 return value;
447}
448
449void QQmlOpenMetaObject::propertyWritten(int)
450{
451}
452
453void QQmlOpenMetaObject::propertyCreated(int, QMetaPropertyBuilder &)
454{
455}
456
457QVariant QQmlOpenMetaObject::initialValue(int)
458{
459 return QVariant();
460}
461
462int QQmlOpenMetaObject::count() const
463{
464 return d->type->d->names.size();
465}
466
467QByteArray QQmlOpenMetaObject::name(int idx) const
468{
469 Q_ASSERT(idx >= 0 && idx < d->type->d->names.size());
470
471 return d->type->d->mob.property(index: idx).name();
472}
473
474QObject *QQmlOpenMetaObject::object() const
475{
476 return d->object;
477}
478
479QT_END_NAMESPACE
480

source code of qtdeclarative/src/qml/qml/qqmlopenmetaobject.cpp