1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qqmladaptormodel_p.h" |
41 | |
42 | #include <private/qqmldelegatemodel_p_p.h> |
43 | #include <private/qmetaobjectbuilder_p.h> |
44 | #include <private/qqmlproperty_p.h> |
45 | |
46 | #include <private/qv4value_p.h> |
47 | #include <private/qv4functionobject_p.h> |
48 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | class QQmlAdaptorModelEngineData : public QV4::ExecutionEngine::Deletable |
52 | { |
53 | public: |
54 | QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4); |
55 | ~QQmlAdaptorModelEngineData(); |
56 | |
57 | QV4::ExecutionEngine *v4; |
58 | QV4::PersistentValue listItemProto; |
59 | }; |
60 | |
61 | V4_DEFINE_EXTENSION(QQmlAdaptorModelEngineData, engineData) |
62 | |
63 | static QV4::ReturnedValue get_index(const QV4::FunctionObject *f, const QV4::Value *thisObject, const QV4::Value *, int) |
64 | { |
65 | QV4::Scope scope(f); |
66 | QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
67 | if (!o) |
68 | RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object" ))); |
69 | |
70 | RETURN_RESULT(QV4::Encode(o->d()->item->index)); |
71 | } |
72 | |
73 | template <typename T, typename M> static void setModelDataType(QMetaObjectBuilder *builder, M *metaType) |
74 | { |
75 | builder->setFlags(QMetaObjectBuilder::DynamicMetaObject); |
76 | builder->setClassName(T::staticMetaObject.className()); |
77 | builder->setSuperClass(&T::staticMetaObject); |
78 | metaType->propertyOffset = T::staticMetaObject.propertyCount(); |
79 | metaType->signalOffset = T::staticMetaObject.methodCount(); |
80 | } |
81 | |
82 | static void addProperty(QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName, const QByteArray &propertyType) |
83 | { |
84 | builder->addSignal(signature: "__" + QByteArray::number(propertyId) + "()" ); |
85 | QMetaPropertyBuilder property = builder->addProperty( |
86 | name: propertyName, type: propertyType, notifierId: propertyId); |
87 | property.setWritable(true); |
88 | } |
89 | |
90 | class VDMModelDelegateDataType; |
91 | |
92 | class QQmlDMCachedModelData : public QQmlDelegateModelItem |
93 | { |
94 | public: |
95 | QQmlDMCachedModelData( |
96 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
97 | VDMModelDelegateDataType *dataType, |
98 | int index, int row, int column); |
99 | |
100 | int metaCall(QMetaObject::Call call, int id, void **arguments); |
101 | |
102 | virtual QVariant value(int role) const = 0; |
103 | virtual void setValue(int role, const QVariant &value) = 0; |
104 | |
105 | void setValue(const QString &role, const QVariant &value) override; |
106 | bool resolveIndex(const QQmlAdaptorModel &model, int idx) override; |
107 | |
108 | static QV4::ReturnedValue get_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); |
109 | static QV4::ReturnedValue set_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); |
110 | |
111 | VDMModelDelegateDataType *type; |
112 | QVector<QVariant> cachedData; |
113 | }; |
114 | |
115 | class VDMModelDelegateDataType |
116 | : public QQmlRefCount |
117 | , public QQmlAdaptorModel::Accessors |
118 | , public QAbstractDynamicMetaObject |
119 | { |
120 | public: |
121 | VDMModelDelegateDataType(QQmlAdaptorModel *model) |
122 | : model(model) |
123 | , propertyOffset(0) |
124 | , signalOffset(0) |
125 | , hasModelData(false) |
126 | { |
127 | } |
128 | |
129 | bool notify( |
130 | const QQmlAdaptorModel &, |
131 | const QList<QQmlDelegateModelItem *> &items, |
132 | int index, |
133 | int count, |
134 | const QVector<int> &roles) const override |
135 | { |
136 | bool changed = roles.isEmpty() && !watchedRoles.isEmpty(); |
137 | if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) { |
138 | QList<int> roleIds; |
139 | for (const QByteArray &r : watchedRoles) { |
140 | QHash<QByteArray, int>::const_iterator it = roleNames.find(key: r); |
141 | if (it != roleNames.end()) |
142 | roleIds << it.value(); |
143 | } |
144 | const_cast<VDMModelDelegateDataType *>(this)->watchedRoleIds = roleIds; |
145 | } |
146 | |
147 | QVector<int> signalIndexes; |
148 | for (int i = 0; i < roles.count(); ++i) { |
149 | const int role = roles.at(i); |
150 | if (!changed && watchedRoleIds.contains(t: role)) |
151 | changed = true; |
152 | |
153 | int propertyId = propertyRoles.indexOf(t: role); |
154 | if (propertyId != -1) |
155 | signalIndexes.append(t: propertyId + signalOffset); |
156 | } |
157 | if (roles.isEmpty()) { |
158 | const int propertyRolesCount = propertyRoles.count(); |
159 | signalIndexes.reserve(size: propertyRolesCount); |
160 | for (int propertyId = 0; propertyId < propertyRolesCount; ++propertyId) |
161 | signalIndexes.append(t: propertyId + signalOffset); |
162 | } |
163 | |
164 | QVarLengthArray<QQmlGuard<QQmlDelegateModelItem>> guardedItems; |
165 | for (const auto item : items) |
166 | guardedItems.append(t: item); |
167 | |
168 | for (const auto &item : qAsConst(t&: guardedItems)) { |
169 | if (item.isNull()) |
170 | continue; |
171 | |
172 | const int idx = item->modelIndex(); |
173 | if (idx >= index && idx < index + count) { |
174 | for (int i = 0; i < signalIndexes.count(); ++i) |
175 | QMetaObject::activate(sender: item, signal_index: signalIndexes.at(i), argv: nullptr); |
176 | } |
177 | } |
178 | return changed; |
179 | } |
180 | |
181 | void replaceWatchedRoles( |
182 | QQmlAdaptorModel &, |
183 | const QList<QByteArray> &oldRoles, |
184 | const QList<QByteArray> &newRoles) const override |
185 | { |
186 | VDMModelDelegateDataType *dataType = const_cast<VDMModelDelegateDataType *>(this); |
187 | |
188 | dataType->watchedRoleIds.clear(); |
189 | for (const QByteArray &oldRole : oldRoles) |
190 | dataType->watchedRoles.removeOne(t: oldRole); |
191 | dataType->watchedRoles += newRoles; |
192 | } |
193 | |
194 | static QV4::ReturnedValue get_hasModelChildren(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
195 | { |
196 | QV4::Scope scope(b); |
197 | QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
198 | if (!o) |
199 | RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object" ))); |
200 | |
201 | const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(o->d()->item)->type->model; |
202 | if (o->d()->item->index >= 0) { |
203 | if (const QAbstractItemModel *const aim = model->aim()) |
204 | RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); |
205 | } |
206 | RETURN_RESULT(QV4::Encode(false)); |
207 | } |
208 | |
209 | |
210 | void initializeConstructor(QQmlAdaptorModelEngineData *const data) |
211 | { |
212 | QV4::ExecutionEngine *v4 = data->v4; |
213 | QV4::Scope scope(v4); |
214 | QV4::ScopedObject proto(scope, v4->newObject()); |
215 | proto->defineAccessorProperty(QStringLiteral("index" ), getter: get_index, setter: nullptr); |
216 | proto->defineAccessorProperty(QStringLiteral("hasModelChildren" ), getter: get_hasModelChildren, setter: nullptr); |
217 | QV4::ScopedProperty p(scope); |
218 | |
219 | typedef QHash<QByteArray, int>::const_iterator iterator; |
220 | for (iterator it = roleNames.constBegin(), end = roleNames.constEnd(); it != end; ++it) { |
221 | const int propertyId = propertyRoles.indexOf(t: it.value()); |
222 | const QByteArray &propertyName = it.key(); |
223 | |
224 | QV4::ScopedString name(scope, v4->newString(s: QString::fromUtf8(str: propertyName))); |
225 | QV4::ExecutionContext *global = v4->rootContext(); |
226 | QV4::ScopedFunctionObject g(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(args: global, args: propertyId, args: QQmlDMCachedModelData::get_property)); |
227 | QV4::ScopedFunctionObject s(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(args: global, args: propertyId, args: QQmlDMCachedModelData::set_property)); |
228 | p->setGetter(g); |
229 | p->setSetter(s); |
230 | proto->insertMember(s: name, p, attributes: QV4::Attr_Accessor|QV4::Attr_NotEnumerable|QV4::Attr_NotConfigurable); |
231 | } |
232 | prototype.set(engine: v4, value: proto); |
233 | } |
234 | |
235 | // QAbstractDynamicMetaObject |
236 | |
237 | void objectDestroyed(QObject *) override |
238 | { |
239 | release(); |
240 | } |
241 | |
242 | int metaCall(QObject *object, QMetaObject::Call call, int id, void **arguments) override |
243 | { |
244 | return static_cast<QQmlDMCachedModelData *>(object)->metaCall(call, id, arguments); |
245 | } |
246 | |
247 | QV4::PersistentValue prototype; |
248 | QList<int> propertyRoles; |
249 | QList<int> watchedRoleIds; |
250 | QList<QByteArray> watchedRoles; |
251 | QHash<QByteArray, int> roleNames; |
252 | QQmlAdaptorModel *model; |
253 | int propertyOffset; |
254 | int signalOffset; |
255 | bool hasModelData; |
256 | }; |
257 | |
258 | QQmlDMCachedModelData::QQmlDMCachedModelData( |
259 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
260 | VDMModelDelegateDataType *dataType, int index, int row, int column) |
261 | : QQmlDelegateModelItem(metaType, dataType, index, row, column) |
262 | , type(dataType) |
263 | { |
264 | if (index == -1) |
265 | cachedData.resize(size: type->hasModelData ? 1 : type->propertyRoles.count()); |
266 | |
267 | QObjectPrivate::get(o: this)->metaObject = type; |
268 | |
269 | type->addref(); |
270 | } |
271 | |
272 | int QQmlDMCachedModelData::metaCall(QMetaObject::Call call, int id, void **arguments) |
273 | { |
274 | if (call == QMetaObject::ReadProperty && id >= type->propertyOffset) { |
275 | const int propertyIndex = id - type->propertyOffset; |
276 | if (index == -1) { |
277 | if (!cachedData.isEmpty()) { |
278 | *static_cast<QVariant *>(arguments[0]) = cachedData.at( |
279 | i: type->hasModelData ? 0 : propertyIndex); |
280 | } |
281 | } else if (*type->model) { |
282 | *static_cast<QVariant *>(arguments[0]) = value(role: type->propertyRoles.at(i: propertyIndex)); |
283 | } |
284 | return -1; |
285 | } else if (call == QMetaObject::WriteProperty && id >= type->propertyOffset) { |
286 | const int propertyIndex = id - type->propertyOffset; |
287 | if (index == -1) { |
288 | const QMetaObject *meta = metaObject(); |
289 | if (cachedData.count() > 1) { |
290 | cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]); |
291 | QMetaObject::activate(sender: this, meta, local_signal_index: propertyIndex, argv: nullptr); |
292 | } else if (cachedData.count() == 1) { |
293 | cachedData[0] = *static_cast<QVariant *>(arguments[0]); |
294 | QMetaObject::activate(sender: this, meta, local_signal_index: 0, argv: nullptr); |
295 | QMetaObject::activate(sender: this, meta, local_signal_index: 1, argv: nullptr); |
296 | } |
297 | } else if (*type->model) { |
298 | setValue(role: type->propertyRoles.at(i: propertyIndex), value: *static_cast<QVariant *>(arguments[0])); |
299 | } |
300 | return -1; |
301 | } else { |
302 | return qt_metacall(call, id, arguments); |
303 | } |
304 | } |
305 | |
306 | void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value) |
307 | { |
308 | QHash<QByteArray, int>::iterator it = type->roleNames.find(key: role.toUtf8()); |
309 | if (it != type->roleNames.end()) { |
310 | for (int i = 0; i < type->propertyRoles.count(); ++i) { |
311 | if (type->propertyRoles.at(i) == *it) { |
312 | cachedData[i] = value; |
313 | return; |
314 | } |
315 | } |
316 | } |
317 | } |
318 | |
319 | bool QQmlDMCachedModelData::resolveIndex(const QQmlAdaptorModel &adaptorModel, int idx) |
320 | { |
321 | if (index == -1) { |
322 | Q_ASSERT(idx >= 0); |
323 | cachedData.clear(); |
324 | setModelIndex(idx, newRow: adaptorModel.rowAt(index: idx), newColumn: adaptorModel.columnAt(index: idx)); |
325 | const QMetaObject *meta = metaObject(); |
326 | const int propertyCount = type->propertyRoles.count(); |
327 | for (int i = 0; i < propertyCount; ++i) |
328 | QMetaObject::activate(sender: this, meta, local_signal_index: i, argv: nullptr); |
329 | return true; |
330 | } else { |
331 | return false; |
332 | } |
333 | } |
334 | |
335 | QV4::ReturnedValue QQmlDMCachedModelData::get_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
336 | { |
337 | QV4::Scope scope(b); |
338 | QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
339 | if (!o) |
340 | return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object" )); |
341 | |
342 | uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index; |
343 | |
344 | QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item); |
345 | if (o->d()->item->index == -1) { |
346 | if (!modelData->cachedData.isEmpty()) { |
347 | return scope.engine->fromVariant( |
348 | modelData->cachedData.at(i: modelData->type->hasModelData ? 0 : propertyId)); |
349 | } |
350 | } else if (*modelData->type->model) { |
351 | return scope.engine->fromVariant( |
352 | modelData->value(role: modelData->type->propertyRoles.at(i: propertyId))); |
353 | } |
354 | return QV4::Encode::undefined(); |
355 | } |
356 | |
357 | QV4::ReturnedValue QQmlDMCachedModelData::set_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) |
358 | { |
359 | QV4::Scope scope(b); |
360 | QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
361 | if (!o) |
362 | return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object" )); |
363 | if (!argc) |
364 | return scope.engine->throwTypeError(); |
365 | |
366 | uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index; |
367 | |
368 | if (o->d()->item->index == -1) { |
369 | QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item); |
370 | if (!modelData->cachedData.isEmpty()) { |
371 | if (modelData->cachedData.count() > 1) { |
372 | modelData->cachedData[propertyId] = scope.engine->toVariant(value: argv[0], typeHint: QMetaType::UnknownType); |
373 | QMetaObject::activate(sender: o->d()->item, o->d()->item->metaObject(), local_signal_index: propertyId, argv: nullptr); |
374 | } else if (modelData->cachedData.count() == 1) { |
375 | modelData->cachedData[0] = scope.engine->toVariant(value: argv[0], typeHint: QMetaType::UnknownType); |
376 | QMetaObject::activate(sender: o->d()->item, o->d()->item->metaObject(), local_signal_index: 0, argv: nullptr); |
377 | QMetaObject::activate(sender: o->d()->item, o->d()->item->metaObject(), local_signal_index: 1, argv: nullptr); |
378 | } |
379 | } |
380 | } |
381 | return QV4::Encode::undefined(); |
382 | } |
383 | |
384 | //----------------------------------------------------------------- |
385 | // QAbstractItemModel |
386 | //----------------------------------------------------------------- |
387 | |
388 | class QQmlDMAbstractItemModelData : public QQmlDMCachedModelData |
389 | { |
390 | Q_OBJECT |
391 | Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT) |
392 | |
393 | public: |
394 | QQmlDMAbstractItemModelData( |
395 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
396 | VDMModelDelegateDataType *dataType, |
397 | int index, int row, int column) |
398 | : QQmlDMCachedModelData(metaType, dataType, index, row, column) |
399 | { |
400 | } |
401 | |
402 | bool hasModelChildren() const |
403 | { |
404 | if (index >= 0) { |
405 | if (const QAbstractItemModel *const model = type->model->aim()) |
406 | return model->hasChildren(parent: model->index(row, column, parent: type->model->rootIndex)); |
407 | } |
408 | return false; |
409 | } |
410 | |
411 | QVariant value(int role) const override |
412 | { |
413 | if (const QAbstractItemModel *aim = type->model->aim()) |
414 | return aim->index(row, column, parent: type->model->rootIndex).data(arole: role); |
415 | return QVariant(); |
416 | } |
417 | |
418 | void setValue(int role, const QVariant &value) override |
419 | { |
420 | if (QAbstractItemModel *aim = type->model->aim()) |
421 | aim->setData(index: aim->index(row, column, parent: type->model->rootIndex), value, role); |
422 | } |
423 | |
424 | QV4::ReturnedValue get() override |
425 | { |
426 | if (type->prototype.isUndefined()) { |
427 | QQmlAdaptorModelEngineData * const data = engineData(engine: v4); |
428 | type->initializeConstructor(data); |
429 | } |
430 | QV4::Scope scope(v4); |
431 | QV4::ScopedObject proto(scope, type->prototype.value()); |
432 | QV4::ScopedObject o(scope, proto->engine()->memoryManager->allocate<QQmlDelegateModelItemObject>(args: this)); |
433 | o->setPrototypeOf(proto); |
434 | ++scriptRef; |
435 | return o.asReturnedValue(); |
436 | } |
437 | }; |
438 | |
439 | class VDMAbstractItemModelDataType : public VDMModelDelegateDataType |
440 | { |
441 | public: |
442 | VDMAbstractItemModelDataType(QQmlAdaptorModel *model) |
443 | : VDMModelDelegateDataType(model) |
444 | { |
445 | } |
446 | |
447 | int rowCount(const QQmlAdaptorModel &model) const override |
448 | { |
449 | if (const QAbstractItemModel *aim = model.aim()) |
450 | return aim->rowCount(parent: model.rootIndex); |
451 | return 0; |
452 | } |
453 | |
454 | int columnCount(const QQmlAdaptorModel &model) const override |
455 | { |
456 | if (const QAbstractItemModel *aim = model.aim()) |
457 | return aim->columnCount(parent: model.rootIndex); |
458 | return 0; |
459 | } |
460 | |
461 | void cleanup(QQmlAdaptorModel &) const override |
462 | { |
463 | release(); |
464 | } |
465 | |
466 | QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override |
467 | { |
468 | if (!metaObject) { |
469 | VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this); |
470 | dataType->initializeMetaType(model); |
471 | } |
472 | |
473 | if (const QAbstractItemModel *aim = model.aim()) { |
474 | QHash<QByteArray, int>::const_iterator it = roleNames.find(key: role.toUtf8()); |
475 | if (it != roleNames.end()) { |
476 | return aim->index(row: model.rowAt(index), column: model.columnAt(index), |
477 | parent: model.rootIndex).data(arole: *it); |
478 | } else if (role == QLatin1String("hasModelChildren" )) { |
479 | return QVariant(aim->hasChildren(parent: aim->index(row: model.rowAt(index), |
480 | column: model.columnAt(index), |
481 | parent: model.rootIndex))); |
482 | } |
483 | } |
484 | return QVariant(); |
485 | } |
486 | |
487 | QVariant parentModelIndex(const QQmlAdaptorModel &model) const override |
488 | { |
489 | if (const QAbstractItemModel *aim = model.aim()) |
490 | return QVariant::fromValue(value: aim->parent(child: model.rootIndex)); |
491 | return QVariant(); |
492 | } |
493 | |
494 | QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override |
495 | { |
496 | if (const QAbstractItemModel *aim = model.aim()) |
497 | return QVariant::fromValue(value: aim->index(row: model.rowAt(index), column: model.columnAt(index), |
498 | parent: model.rootIndex)); |
499 | return QVariant(); |
500 | } |
501 | |
502 | bool canFetchMore(const QQmlAdaptorModel &model) const override |
503 | { |
504 | if (const QAbstractItemModel *aim = model.aim()) |
505 | return aim->canFetchMore(parent: model.rootIndex); |
506 | return false; |
507 | } |
508 | |
509 | void fetchMore(QQmlAdaptorModel &model) const override |
510 | { |
511 | if (QAbstractItemModel *aim = model.aim()) |
512 | aim->fetchMore(parent: model.rootIndex); |
513 | } |
514 | |
515 | QQmlDelegateModelItem *createItem( |
516 | QQmlAdaptorModel &model, |
517 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
518 | int index, int row, int column) const override |
519 | { |
520 | VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this); |
521 | if (!metaObject) |
522 | dataType->initializeMetaType(model); |
523 | return new QQmlDMAbstractItemModelData(metaType, dataType, index, row, column); |
524 | } |
525 | |
526 | void initializeMetaType(const QQmlAdaptorModel &model) |
527 | { |
528 | QMetaObjectBuilder builder; |
529 | setModelDataType<QQmlDMAbstractItemModelData>(builder: &builder, metaType: this); |
530 | |
531 | const QByteArray propertyType = QByteArrayLiteral("QVariant" ); |
532 | const QAbstractItemModel *aim = model.aim(); |
533 | const QHash<int, QByteArray> names = aim ? aim->roleNames() : QHash<int, QByteArray>(); |
534 | for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) { |
535 | const int propertyId = propertyRoles.count(); |
536 | propertyRoles.append(t: it.key()); |
537 | roleNames.insert(key: it.value(), value: it.key()); |
538 | addProperty(builder: &builder, propertyId, propertyName: it.value(), propertyType); |
539 | } |
540 | if (propertyRoles.count() == 1) { |
541 | hasModelData = true; |
542 | const int role = names.begin().key(); |
543 | const QByteArray propertyName = QByteArrayLiteral("modelData" ); |
544 | |
545 | propertyRoles.append(t: role); |
546 | roleNames.insert(key: propertyName, value: role); |
547 | addProperty(builder: &builder, propertyId: 1, propertyName, propertyType); |
548 | } |
549 | |
550 | metaObject.reset(other: builder.toMetaObject()); |
551 | *static_cast<QMetaObject *>(this) = *metaObject; |
552 | propertyCache.adopt(new QQmlPropertyCache(metaObject.data(), model.modelItemRevision)); |
553 | } |
554 | }; |
555 | |
556 | //----------------------------------------------------------------- |
557 | // QQmlListAccessor |
558 | //----------------------------------------------------------------- |
559 | |
560 | class QQmlDMListAccessorData : public QQmlDelegateModelItem |
561 | { |
562 | Q_OBJECT |
563 | Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged) |
564 | public: |
565 | QQmlDMListAccessorData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
566 | QQmlAdaptorModel::Accessors *accessor, |
567 | int index, int row, int column, const QVariant &value) |
568 | : QQmlDelegateModelItem(metaType, accessor, index, row, column) |
569 | , cachedData(value) |
570 | { |
571 | } |
572 | |
573 | QVariant modelData() const |
574 | { |
575 | return cachedData; |
576 | } |
577 | |
578 | void setModelData(const QVariant &data) |
579 | { |
580 | if (data == cachedData) |
581 | return; |
582 | |
583 | cachedData = data; |
584 | emit modelDataChanged(); |
585 | } |
586 | |
587 | static QV4::ReturnedValue get_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
588 | { |
589 | QV4::ExecutionEngine *v4 = b->engine(); |
590 | const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>(); |
591 | if (!o) |
592 | return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object" )); |
593 | |
594 | return v4->fromVariant(static_cast<QQmlDMListAccessorData *>(o->d()->item)->cachedData); |
595 | } |
596 | |
597 | static QV4::ReturnedValue set_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) |
598 | { |
599 | QV4::ExecutionEngine *v4 = b->engine(); |
600 | const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>(); |
601 | if (!o) |
602 | return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object" )); |
603 | if (!argc) |
604 | return v4->throwTypeError(); |
605 | |
606 | static_cast<QQmlDMListAccessorData *>(o->d()->item)->setModelData(v4->toVariant(value: argv[0], typeHint: QMetaType::UnknownType)); |
607 | return QV4::Encode::undefined(); |
608 | } |
609 | |
610 | QV4::ReturnedValue get() override |
611 | { |
612 | QQmlAdaptorModelEngineData *data = engineData(engine: v4); |
613 | QV4::Scope scope(v4); |
614 | QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(args: this)); |
615 | QV4::ScopedObject p(scope, data->listItemProto.value()); |
616 | o->setPrototypeOf(p); |
617 | ++scriptRef; |
618 | return o.asReturnedValue(); |
619 | } |
620 | |
621 | void setValue(const QString &role, const QVariant &value) override |
622 | { |
623 | if (role == QLatin1String("modelData" )) |
624 | cachedData = value; |
625 | } |
626 | |
627 | bool resolveIndex(const QQmlAdaptorModel &model, int idx) override |
628 | { |
629 | if (index == -1) { |
630 | index = idx; |
631 | cachedData = model.list.at(idx); |
632 | emit modelIndexChanged(); |
633 | emit modelDataChanged(); |
634 | return true; |
635 | } else { |
636 | return false; |
637 | } |
638 | } |
639 | |
640 | |
641 | Q_SIGNALS: |
642 | void modelDataChanged(); |
643 | |
644 | private: |
645 | QVariant cachedData; |
646 | }; |
647 | |
648 | |
649 | class VDMListDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors |
650 | { |
651 | public: |
652 | VDMListDelegateDataType() |
653 | : QQmlRefCount() |
654 | , QQmlAdaptorModel::Accessors() |
655 | {} |
656 | |
657 | void cleanup(QQmlAdaptorModel &) const override |
658 | { |
659 | const_cast<VDMListDelegateDataType *>(this)->release(); |
660 | } |
661 | |
662 | int rowCount(const QQmlAdaptorModel &model) const override |
663 | { |
664 | return model.list.count(); |
665 | } |
666 | |
667 | int columnCount(const QQmlAdaptorModel &) const override |
668 | { |
669 | return 1; |
670 | } |
671 | |
672 | QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override |
673 | { |
674 | return role == QLatin1String("modelData" ) |
675 | ? model.list.at(index) |
676 | : QVariant(); |
677 | } |
678 | |
679 | QQmlDelegateModelItem *createItem( |
680 | QQmlAdaptorModel &model, |
681 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
682 | int index, int row, int column) const override |
683 | { |
684 | VDMListDelegateDataType *dataType = const_cast<VDMListDelegateDataType *>(this); |
685 | if (!propertyCache) { |
686 | dataType->propertyCache.adopt(new QQmlPropertyCache( |
687 | &QQmlDMListAccessorData::staticMetaObject, model.modelItemRevision)); |
688 | } |
689 | |
690 | return new QQmlDMListAccessorData( |
691 | metaType, |
692 | dataType, |
693 | index, row, column, |
694 | index >= 0 && index < model.list.count() ? model.list.at(index) : QVariant()); |
695 | } |
696 | |
697 | bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override |
698 | { |
699 | for (auto modelItem : items) { |
700 | const int modelItemIndex = modelItem->index; |
701 | if (modelItemIndex < index || modelItemIndex >= index + count) |
702 | continue; |
703 | |
704 | auto listModelItem = static_cast<QQmlDMListAccessorData *>(modelItem); |
705 | QVariant updatedModelData = model.list.at(listModelItem->index); |
706 | listModelItem->setModelData(updatedModelData); |
707 | } |
708 | return true; |
709 | } |
710 | }; |
711 | |
712 | //----------------------------------------------------------------- |
713 | // QObject |
714 | //----------------------------------------------------------------- |
715 | |
716 | class VDMObjectDelegateDataType; |
717 | class QQmlDMObjectData : public QQmlDelegateModelItem, public QQmlAdaptorModelProxyInterface |
718 | { |
719 | Q_OBJECT |
720 | Q_PROPERTY(QObject *modelData READ modelData NOTIFY modelDataChanged) |
721 | Q_INTERFACES(QQmlAdaptorModelProxyInterface) |
722 | public: |
723 | QQmlDMObjectData( |
724 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
725 | VDMObjectDelegateDataType *dataType, |
726 | int index, int row, int column, |
727 | QObject *object); |
728 | |
729 | void setModelData(QObject *modelData) |
730 | { |
731 | if (modelData == object) |
732 | return; |
733 | |
734 | object = modelData; |
735 | emit modelDataChanged(); |
736 | } |
737 | |
738 | QObject *modelData() const { return object; } |
739 | QObject *proxiedObject() override { return object; } |
740 | |
741 | QPointer<QObject> object; |
742 | |
743 | Q_SIGNALS: |
744 | void modelDataChanged(); |
745 | }; |
746 | |
747 | class VDMObjectDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors |
748 | { |
749 | public: |
750 | int propertyOffset; |
751 | int signalOffset; |
752 | bool shared; |
753 | QMetaObjectBuilder builder; |
754 | |
755 | VDMObjectDelegateDataType() |
756 | : propertyOffset(0) |
757 | , signalOffset(0) |
758 | , shared(true) |
759 | { |
760 | } |
761 | |
762 | VDMObjectDelegateDataType(const VDMObjectDelegateDataType &type) |
763 | : QQmlRefCount() |
764 | , QQmlAdaptorModel::Accessors() |
765 | , propertyOffset(type.propertyOffset) |
766 | , signalOffset(type.signalOffset) |
767 | , shared(false) |
768 | , builder(type.metaObject.data(), QMetaObjectBuilder::Properties |
769 | | QMetaObjectBuilder::Signals |
770 | | QMetaObjectBuilder::SuperClass |
771 | | QMetaObjectBuilder::ClassName) |
772 | { |
773 | builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); |
774 | } |
775 | |
776 | int rowCount(const QQmlAdaptorModel &model) const override |
777 | { |
778 | return model.list.count(); |
779 | } |
780 | |
781 | int columnCount(const QQmlAdaptorModel &) const override |
782 | { |
783 | return 1; |
784 | } |
785 | |
786 | QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override |
787 | { |
788 | if (QObject *object = model.list.at(index).value<QObject *>()) |
789 | return object->property(name: role.toUtf8()); |
790 | return QVariant(); |
791 | } |
792 | |
793 | QQmlDelegateModelItem *createItem( |
794 | QQmlAdaptorModel &model, |
795 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
796 | int index, int row, int column) const override |
797 | { |
798 | VDMObjectDelegateDataType *dataType = const_cast<VDMObjectDelegateDataType *>(this); |
799 | if (!metaObject) |
800 | dataType->initializeMetaType(model); |
801 | return index >= 0 && index < model.list.count() |
802 | ? new QQmlDMObjectData(metaType, dataType, index, row, column, qvariant_cast<QObject *>(v: model.list.at(index))) |
803 | : nullptr; |
804 | } |
805 | |
806 | void initializeMetaType(QQmlAdaptorModel &model) |
807 | { |
808 | Q_UNUSED(model); |
809 | setModelDataType<QQmlDMObjectData>(builder: &builder, metaType: this); |
810 | |
811 | metaObject.reset(other: builder.toMetaObject()); |
812 | // Note: ATM we cannot create a shared property cache for this class, since each model |
813 | // object can have different properties. And to make those properties available to the |
814 | // delegate, QQmlDMObjectData makes use of a QAbstractDynamicMetaObject subclass |
815 | // (QQmlDMObjectDataMetaObject), which we cannot represent in a QQmlPropertyCache. |
816 | // By not having a shared property cache, revisioned properties in QQmlDelegateModelItem |
817 | // will always be available to the delegate, regardless of the import version. |
818 | } |
819 | |
820 | void cleanup(QQmlAdaptorModel &) const override |
821 | { |
822 | const_cast<VDMObjectDelegateDataType *>(this)->release(); |
823 | } |
824 | |
825 | bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override |
826 | { |
827 | for (auto modelItem : items) { |
828 | const int modelItemIndex = modelItem->index; |
829 | if (modelItemIndex < index || modelItemIndex >= index + count) |
830 | continue; |
831 | |
832 | auto objectModelItem = static_cast<QQmlDMObjectData *>(modelItem); |
833 | QObject *updatedModelData = qvariant_cast<QObject *>(v: model.list.at(objectModelItem->index)); |
834 | objectModelItem->setModelData(updatedModelData); |
835 | } |
836 | return true; |
837 | } |
838 | }; |
839 | |
840 | class QQmlDMObjectDataMetaObject : public QAbstractDynamicMetaObject |
841 | { |
842 | public: |
843 | QQmlDMObjectDataMetaObject(QQmlDMObjectData *data, VDMObjectDelegateDataType *type) |
844 | : m_data(data) |
845 | , m_type(type) |
846 | { |
847 | QObjectPrivate *op = QObjectPrivate::get(o: m_data); |
848 | *static_cast<QMetaObject *>(this) = *type->metaObject; |
849 | op->metaObject = this; |
850 | m_type->addref(); |
851 | } |
852 | |
853 | ~QQmlDMObjectDataMetaObject() |
854 | { |
855 | m_type->release(); |
856 | } |
857 | |
858 | int metaCall(QObject *o, QMetaObject::Call call, int id, void **arguments) override |
859 | { |
860 | Q_ASSERT(o == m_data); |
861 | Q_UNUSED(o); |
862 | |
863 | static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount(); |
864 | if (id >= m_type->propertyOffset |
865 | && (call == QMetaObject::ReadProperty |
866 | || call == QMetaObject::WriteProperty |
867 | || call == QMetaObject::ResetProperty)) { |
868 | if (m_data->object) |
869 | QMetaObject::metacall(m_data->object, call, id - m_type->propertyOffset + objectPropertyOffset, arguments); |
870 | return -1; |
871 | } else if (id >= m_type->signalOffset && call == QMetaObject::InvokeMetaMethod) { |
872 | QMetaObject::activate(sender: m_data, this, local_signal_index: id - m_type->signalOffset, argv: nullptr); |
873 | return -1; |
874 | } else { |
875 | return m_data->qt_metacall(call, id, arguments); |
876 | } |
877 | } |
878 | |
879 | int createProperty(const char *name, const char *) override |
880 | { |
881 | if (!m_data->object) |
882 | return -1; |
883 | const QMetaObject *metaObject = m_data->object->metaObject(); |
884 | static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount(); |
885 | |
886 | const int previousPropertyCount = propertyCount() - propertyOffset(); |
887 | int propertyIndex = metaObject->indexOfProperty(name); |
888 | if (propertyIndex == -1) |
889 | return -1; |
890 | if (previousPropertyCount + objectPropertyOffset == metaObject->propertyCount()) |
891 | return propertyIndex + m_type->propertyOffset - objectPropertyOffset; |
892 | |
893 | if (m_type->shared) { |
894 | VDMObjectDelegateDataType *type = m_type; |
895 | m_type = new VDMObjectDelegateDataType(*m_type); |
896 | type->release(); |
897 | } |
898 | |
899 | const int previousMethodCount = methodCount(); |
900 | int notifierId = previousMethodCount - methodOffset(); |
901 | for (int propertyId = previousPropertyCount; propertyId < metaObject->propertyCount() - objectPropertyOffset; ++propertyId) { |
902 | QMetaProperty property = metaObject->property(index: propertyId + objectPropertyOffset); |
903 | QMetaPropertyBuilder propertyBuilder; |
904 | if (property.hasNotifySignal()) { |
905 | m_type->builder.addSignal(signature: "__" + QByteArray::number(propertyId) + "()" ); |
906 | propertyBuilder = m_type->builder.addProperty(name: property.name(), type: property.typeName(), notifierId); |
907 | ++notifierId; |
908 | } else { |
909 | propertyBuilder = m_type->builder.addProperty(name: property.name(), type: property.typeName()); |
910 | } |
911 | propertyBuilder.setWritable(property.isWritable()); |
912 | propertyBuilder.setResettable(property.isResettable()); |
913 | propertyBuilder.setConstant(property.isConstant()); |
914 | } |
915 | |
916 | m_type->metaObject.reset(other: m_type->builder.toMetaObject()); |
917 | *static_cast<QMetaObject *>(this) = *m_type->metaObject; |
918 | |
919 | notifierId = previousMethodCount; |
920 | for (int i = previousPropertyCount; i < metaObject->propertyCount() - objectPropertyOffset; ++i) { |
921 | QMetaProperty property = metaObject->property(index: i + objectPropertyOffset); |
922 | if (property.hasNotifySignal()) { |
923 | QQmlPropertyPrivate::connect( |
924 | sender: m_data->object, signal_index: property.notifySignalIndex(), receiver: m_data, method_index: notifierId); |
925 | ++notifierId; |
926 | } |
927 | } |
928 | return propertyIndex + m_type->propertyOffset - objectPropertyOffset; |
929 | } |
930 | |
931 | QQmlDMObjectData *m_data; |
932 | VDMObjectDelegateDataType *m_type; |
933 | }; |
934 | |
935 | QQmlDMObjectData::QQmlDMObjectData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
936 | VDMObjectDelegateDataType *dataType, |
937 | int index, int row, int column, |
938 | QObject *object) |
939 | : QQmlDelegateModelItem(metaType, dataType, index, row, column) |
940 | , object(object) |
941 | { |
942 | new QQmlDMObjectDataMetaObject(this, dataType); |
943 | } |
944 | |
945 | //----------------------------------------------------------------- |
946 | // QQmlAdaptorModel |
947 | //----------------------------------------------------------------- |
948 | |
949 | static const QQmlAdaptorModel::Accessors qt_vdm_null_accessors; |
950 | |
951 | QQmlAdaptorModel::Accessors::~Accessors() |
952 | { |
953 | } |
954 | |
955 | QQmlAdaptorModel::QQmlAdaptorModel() |
956 | : accessors(&qt_vdm_null_accessors) |
957 | { |
958 | } |
959 | |
960 | QQmlAdaptorModel::~QQmlAdaptorModel() |
961 | { |
962 | accessors->cleanup(*this); |
963 | } |
964 | |
965 | void QQmlAdaptorModel::setModel(const QVariant &variant, QObject *, QQmlEngine *engine) |
966 | { |
967 | accessors->cleanup(*this); |
968 | |
969 | list.setList(variant, engine); |
970 | modelStrongReference.clear(); |
971 | |
972 | if (QObject *object = qvariant_cast<QObject *>(v: list.list())) { |
973 | if (QQmlData *ddata = QQmlData::get(object)) |
974 | modelStrongReference = ddata->jsWrapper; |
975 | setObject(object); |
976 | if (qobject_cast<QAbstractItemModel *>(object)) |
977 | accessors = new VDMAbstractItemModelDataType(this); |
978 | else |
979 | accessors = new VDMObjectDelegateDataType; |
980 | } else if (list.type() == QQmlListAccessor::ListProperty) { |
981 | auto object = static_cast<const QQmlListReference *>(variant.constData())->object(); |
982 | if (QQmlData *ddata = QQmlData::get(object)) |
983 | modelStrongReference = ddata->jsWrapper; |
984 | setObject(object); |
985 | accessors = new VDMObjectDelegateDataType; |
986 | } else if (list.type() == QQmlListAccessor::ObjectList) { |
987 | setObject(nullptr); |
988 | accessors = new VDMObjectDelegateDataType; |
989 | } else if (list.type() != QQmlListAccessor::Invalid |
990 | && list.type() != QQmlListAccessor::Instance) { // Null QObject |
991 | setObject(nullptr); |
992 | accessors = new VDMListDelegateDataType; |
993 | } else { |
994 | setObject(nullptr); |
995 | accessors = &qt_vdm_null_accessors; |
996 | } |
997 | } |
998 | |
999 | void QQmlAdaptorModel::invalidateModel() |
1000 | { |
1001 | accessors->cleanup(*this); |
1002 | accessors = &qt_vdm_null_accessors; |
1003 | // Don't clear the model object as we still need the guard to clear the list variant if the |
1004 | // object is destroyed. |
1005 | } |
1006 | |
1007 | bool QQmlAdaptorModel::isValid() const |
1008 | { |
1009 | return accessors != &qt_vdm_null_accessors; |
1010 | } |
1011 | |
1012 | int QQmlAdaptorModel::count() const |
1013 | { |
1014 | return rowCount() * columnCount(); |
1015 | } |
1016 | |
1017 | int QQmlAdaptorModel::rowCount() const |
1018 | { |
1019 | return qMax(a: 0, b: accessors->rowCount(*this)); |
1020 | } |
1021 | |
1022 | int QQmlAdaptorModel::columnCount() const |
1023 | { |
1024 | return qMax(a: 0, b: accessors->columnCount(*this)); |
1025 | } |
1026 | |
1027 | int QQmlAdaptorModel::rowAt(int index) const |
1028 | { |
1029 | int count = rowCount(); |
1030 | return count <= 0 ? -1 : index % count; |
1031 | } |
1032 | |
1033 | int QQmlAdaptorModel::columnAt(int index) const |
1034 | { |
1035 | int count = rowCount(); |
1036 | return count <= 0 ? -1 : index / count; |
1037 | } |
1038 | |
1039 | int QQmlAdaptorModel::indexAt(int row, int column) const |
1040 | { |
1041 | return column * rowCount() + row; |
1042 | } |
1043 | |
1044 | void QQmlAdaptorModel::useImportVersion(int minorVersion) |
1045 | { |
1046 | modelItemRevision = minorVersion; |
1047 | } |
1048 | |
1049 | void QQmlAdaptorModel::objectDestroyed(QObject *) |
1050 | { |
1051 | setModel(variant: QVariant(), nullptr, engine: nullptr); |
1052 | } |
1053 | |
1054 | QQmlAdaptorModelEngineData::QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4) |
1055 | : v4(v4) |
1056 | { |
1057 | QV4::Scope scope(v4); |
1058 | QV4::ScopedObject proto(scope, v4->newObject()); |
1059 | proto->defineAccessorProperty(QStringLiteral("index" ), getter: get_index, setter: nullptr); |
1060 | proto->defineAccessorProperty(QStringLiteral("modelData" ), |
1061 | getter: QQmlDMListAccessorData::get_modelData, setter: QQmlDMListAccessorData::set_modelData); |
1062 | listItemProto.set(engine: v4, value: proto); |
1063 | } |
1064 | |
1065 | QQmlAdaptorModelEngineData::~QQmlAdaptorModelEngineData() |
1066 | { |
1067 | } |
1068 | |
1069 | QT_END_NAMESPACE |
1070 | |
1071 | #include <qqmladaptormodel.moc> |
1072 | |