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 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | |
15 | class QQmlOpenMetaObjectTypePrivate |
16 | { |
17 | public: |
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 | |
37 | QQmlOpenMetaObjectType::QQmlOpenMetaObjectType(const QMetaObject *base) |
38 | : d(new QQmlOpenMetaObjectTypePrivate) |
39 | { |
40 | d->init(metaObj: base); |
41 | } |
42 | |
43 | QQmlOpenMetaObjectType::~QQmlOpenMetaObjectType() |
44 | { |
45 | if (d->mem) |
46 | free(ptr: d->mem); |
47 | delete d; |
48 | } |
49 | |
50 | int QQmlOpenMetaObjectType::propertyOffset() const |
51 | { |
52 | return d->propertyOffset; |
53 | } |
54 | |
55 | int QQmlOpenMetaObjectType::signalOffset() const |
56 | { |
57 | return d->signalOffset; |
58 | } |
59 | |
60 | int QQmlOpenMetaObjectType::propertyCount() const |
61 | { |
62 | return d->names.size(); |
63 | } |
64 | |
65 | QByteArray 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 | |
72 | void 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 | |
94 | int 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 | |
115 | void QQmlOpenMetaObjectType::propertyCreated(int id, QMetaPropertyBuilder &builder) |
116 | { |
117 | if (d->referers.size()) |
118 | (*d->referers.begin())->propertyCreated(id, builder); |
119 | } |
120 | |
121 | void 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 | |
137 | class QQmlOpenMetaObjectPrivate |
138 | { |
139 | public: |
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 | |
211 | QQmlOpenMetaObject::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 | |
223 | QQmlOpenMetaObject::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 | |
236 | QQmlOpenMetaObject::~QQmlOpenMetaObject() |
237 | { |
238 | if (d->parent) |
239 | delete d->parent; |
240 | d->type->d->referers.remove(value: this); |
241 | delete d; |
242 | } |
243 | |
244 | QQmlOpenMetaObjectType *QQmlOpenMetaObject::type() const |
245 | { |
246 | return d->type.data(); |
247 | } |
248 | |
249 | void 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 | |
257 | void QQmlOpenMetaObject::unparent() |
258 | { |
259 | d->parent = nullptr; |
260 | } |
261 | |
262 | int 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 | |
289 | QDynamicMetaObjectData *QQmlOpenMetaObject::parent() const |
290 | { |
291 | return d->parent; |
292 | } |
293 | |
294 | bool 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 | |
304 | QVariant QQmlOpenMetaObject::value(int id) const |
305 | { |
306 | return d->propertyValue(idx: id); |
307 | } |
308 | |
309 | void 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 | |
315 | QVariant 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 | |
324 | QVariant &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 | |
332 | bool 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 | |
349 | void 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() |
383 | bool QQmlOpenMetaObject::hasValue(int id) const |
384 | { |
385 | return d->hasProperty(idx: id); |
386 | } |
387 | |
388 | void 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 | |
409 | bool QQmlOpenMetaObject::autoCreatesProperties() const |
410 | { |
411 | return d->autoCreate; |
412 | } |
413 | |
414 | void QQmlOpenMetaObject::setAutoCreatesProperties(bool autoCreate) |
415 | { |
416 | d->autoCreate = autoCreate; |
417 | } |
418 | |
419 | |
420 | int 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 | |
436 | void QQmlOpenMetaObject::propertyRead(int) |
437 | { |
438 | } |
439 | |
440 | void QQmlOpenMetaObject::propertyWrite(int) |
441 | { |
442 | } |
443 | |
444 | QVariant QQmlOpenMetaObject::propertyWriteValue(int, const QVariant &value) |
445 | { |
446 | return value; |
447 | } |
448 | |
449 | void QQmlOpenMetaObject::propertyWritten(int) |
450 | { |
451 | } |
452 | |
453 | void QQmlOpenMetaObject::propertyCreated(int, QMetaPropertyBuilder &) |
454 | { |
455 | } |
456 | |
457 | QVariant QQmlOpenMetaObject::initialValue(int) |
458 | { |
459 | return QVariant(); |
460 | } |
461 | |
462 | int QQmlOpenMetaObject::count() const |
463 | { |
464 | return d->type->d->names.size(); |
465 | } |
466 | |
467 | QByteArray 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 | |
474 | QObject *QQmlOpenMetaObject::object() const |
475 | { |
476 | return d->object; |
477 | } |
478 | |
479 | QT_END_NAMESPACE |
480 | |