| 1 | // Copyright (C) 2023 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 | #ifndef QQMLDMABSTRACTITEMMODELDATA_P_H |
| 5 | #define QQMLDMABSTRACTITEMMODELDATA_P_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | // |
| 17 | |
| 18 | #include <private/qqmladaptormodelenginedata_p.h> |
| 19 | #include <private/qqmldelegatemodel_p_p.h> |
| 20 | #include <private/qobject_p.h> |
| 21 | |
| 22 | QT_BEGIN_NAMESPACE |
| 23 | |
| 24 | class VDMAbstractItemModelDataType; |
| 25 | class QQmlDMAbstractItemModelData : public QQmlDelegateModelItem |
| 26 | { |
| 27 | Q_OBJECT |
| 28 | Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT) |
| 29 | Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged) |
| 30 | QT_ANONYMOUS_PROPERTY(QVariant READ modelData NOTIFY modelDataChanged FINAL) |
| 31 | |
| 32 | public: |
| 33 | QQmlDMAbstractItemModelData( |
| 34 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
| 35 | VDMAbstractItemModelDataType *dataType, |
| 36 | int index, int row, int column); |
| 37 | |
| 38 | int metaCall(QMetaObject::Call call, int id, void **arguments); |
| 39 | bool hasModelChildren() const; |
| 40 | |
| 41 | QV4::ReturnedValue get() override; |
| 42 | void setValue(const QString &role, const QVariant &value) override; |
| 43 | bool resolveIndex(const QQmlAdaptorModel &model, int idx) override; |
| 44 | |
| 45 | static QV4::ReturnedValue get_property( |
| 46 | const QV4::FunctionObject *b, const QV4::Value *thisObject, |
| 47 | const QV4::Value *argv, int argc); |
| 48 | static QV4::ReturnedValue set_property( |
| 49 | const QV4::FunctionObject *b, const QV4::Value *thisObject, |
| 50 | const QV4::Value *argv, int argc); |
| 51 | |
| 52 | static QV4::ReturnedValue get_modelData( |
| 53 | const QV4::FunctionObject *b, const QV4::Value *thisObject, |
| 54 | const QV4::Value *argv, int argc); |
| 55 | static QV4::ReturnedValue set_modelData( |
| 56 | const QV4::FunctionObject *b, const QV4::Value *thisObject, |
| 57 | const QV4::Value *argv, int argc); |
| 58 | |
| 59 | QVariant modelData() const; |
| 60 | void setModelData(const QVariant &modelData); |
| 61 | |
| 62 | const VDMAbstractItemModelDataType *type() const { return m_type; } |
| 63 | |
| 64 | Q_SIGNALS: |
| 65 | void modelDataChanged(); |
| 66 | |
| 67 | private: |
| 68 | QVariant value(int role) const; |
| 69 | void setValue(int role, const QVariant &value); |
| 70 | |
| 71 | VDMAbstractItemModelDataType *m_type; |
| 72 | QVector<QVariant> m_cachedData; |
| 73 | }; |
| 74 | |
| 75 | class VDMAbstractItemModelDataType final |
| 76 | : public QQmlRefCounted<VDMAbstractItemModelDataType> |
| 77 | , public QQmlAdaptorModel::Accessors |
| 78 | , public QAbstractDynamicMetaObject |
| 79 | { |
| 80 | public: |
| 81 | VDMAbstractItemModelDataType(QQmlAdaptorModel *model) |
| 82 | : model(model) |
| 83 | , propertyOffset(0) |
| 84 | , signalOffset(0) |
| 85 | { |
| 86 | } |
| 87 | |
| 88 | void notifyItem(const QQmlGuard<QQmlDMAbstractItemModelData> &item, const QVector<int> &signalIndexes) const |
| 89 | { |
| 90 | for (const int signalIndex : signalIndexes) { |
| 91 | QMetaObject::activate(sender: item, signal_index: signalIndex, argv: nullptr); |
| 92 | if (item.isNull()) |
| 93 | return; |
| 94 | } |
| 95 | emit item->modelDataChanged(); |
| 96 | } |
| 97 | |
| 98 | bool notify( |
| 99 | const QQmlAdaptorModel &, |
| 100 | const QList<QQmlDelegateModelItem *> &items, |
| 101 | int index, |
| 102 | int count, |
| 103 | const QVector<int> &roles) const override |
| 104 | { |
| 105 | bool changed = roles.isEmpty() && !watchedRoles.isEmpty(); |
| 106 | if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) { |
| 107 | QList<int> roleIds; |
| 108 | for (const QByteArray &r : watchedRoles) { |
| 109 | QHash<QByteArray, int>::const_iterator it = roleNames.find(key: r); |
| 110 | if (it != roleNames.end()) |
| 111 | roleIds << it.value(); |
| 112 | } |
| 113 | const_cast<VDMAbstractItemModelDataType *>(this)->watchedRoleIds = roleIds; |
| 114 | } |
| 115 | |
| 116 | QVector<int> signalIndexes; |
| 117 | for (int i = 0; i < roles.size(); ++i) { |
| 118 | const int role = roles.at(i); |
| 119 | if (!changed && watchedRoleIds.contains(t: role)) |
| 120 | changed = true; |
| 121 | |
| 122 | int propertyId = propertyRoles.indexOf(t: role); |
| 123 | if (propertyId != -1) |
| 124 | signalIndexes.append(t: propertyId + signalOffset); |
| 125 | } |
| 126 | if (roles.isEmpty()) { |
| 127 | const int propertyRolesCount = propertyRoles.size(); |
| 128 | signalIndexes.reserve(asize: propertyRolesCount); |
| 129 | for (int propertyId = 0; propertyId < propertyRolesCount; ++propertyId) |
| 130 | signalIndexes.append(t: propertyId + signalOffset); |
| 131 | } |
| 132 | |
| 133 | QVarLengthArray<QQmlGuard<QQmlDMAbstractItemModelData>> guardedItems; |
| 134 | for (const auto item : items) { |
| 135 | Q_ASSERT(qobject_cast<QQmlDMAbstractItemModelData *>(item) == item); |
| 136 | guardedItems.append(t: static_cast<QQmlDMAbstractItemModelData *>(item)); |
| 137 | } |
| 138 | |
| 139 | for (const auto &item : std::as_const(t&: guardedItems)) { |
| 140 | if (item.isNull()) |
| 141 | continue; |
| 142 | |
| 143 | const int idx = item->modelIndex(); |
| 144 | if (idx >= index && idx < index + count) |
| 145 | notifyItem(item, signalIndexes); |
| 146 | } |
| 147 | return changed; |
| 148 | } |
| 149 | |
| 150 | void replaceWatchedRoles( |
| 151 | QQmlAdaptorModel &, |
| 152 | const QList<QByteArray> &oldRoles, |
| 153 | const QList<QByteArray> &newRoles) const override |
| 154 | { |
| 155 | VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this); |
| 156 | |
| 157 | dataType->watchedRoleIds.clear(); |
| 158 | for (const QByteArray &oldRole : oldRoles) |
| 159 | dataType->watchedRoles.removeOne(t: oldRole); |
| 160 | dataType->watchedRoles += newRoles; |
| 161 | } |
| 162 | |
| 163 | static QV4::ReturnedValue get_hasModelChildren(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
| 164 | { |
| 165 | QV4::Scope scope(b); |
| 166 | QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
| 167 | if (!o) |
| 168 | RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object" ))); |
| 169 | |
| 170 | const QQmlAdaptorModel *const model |
| 171 | = static_cast<QQmlDMAbstractItemModelData *>(o->d()->item)->type()->model; |
| 172 | if (o->d()->item->index >= 0) { |
| 173 | if (const QAbstractItemModel *const aim = model->aim()) |
| 174 | RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); |
| 175 | } |
| 176 | RETURN_RESULT(QV4::Encode(false)); |
| 177 | } |
| 178 | |
| 179 | |
| 180 | void initializeConstructor(QQmlAdaptorModelEngineData *const data) |
| 181 | { |
| 182 | QV4::ExecutionEngine *v4 = data->v4; |
| 183 | QV4::Scope scope(v4); |
| 184 | QV4::ScopedObject proto(scope, v4->newObject()); |
| 185 | proto->defineAccessorProperty(QStringLiteral("index" ), getter: QQmlAdaptorModelEngineData::get_index, setter: nullptr); |
| 186 | proto->defineAccessorProperty(QStringLiteral("hasModelChildren" ), getter: get_hasModelChildren, setter: nullptr); |
| 187 | proto->defineAccessorProperty(QStringLiteral("modelData" ), |
| 188 | getter: QQmlDMAbstractItemModelData::get_modelData, |
| 189 | setter: QQmlDMAbstractItemModelData::set_modelData); |
| 190 | QV4::ScopedProperty p(scope); |
| 191 | |
| 192 | typedef QHash<QByteArray, int>::const_iterator iterator; |
| 193 | for (iterator it = roleNames.constBegin(), end = roleNames.constEnd(); it != end; ++it) { |
| 194 | const qsizetype propertyId = propertyRoles.indexOf(t: it.value()); |
| 195 | const QByteArray &propertyName = it.key(); |
| 196 | |
| 197 | QV4::ScopedString name(scope, v4->newString(s: QString::fromUtf8(ba: propertyName))); |
| 198 | QV4::ScopedFunctionObject g( |
| 199 | scope, |
| 200 | v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>( |
| 201 | args&: v4, args: propertyId, args&: QQmlDMAbstractItemModelData::get_property)); |
| 202 | QV4::ScopedFunctionObject s( |
| 203 | scope, |
| 204 | v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>( |
| 205 | args&: v4, args: propertyId, args&: QQmlDMAbstractItemModelData::set_property)); |
| 206 | p->setGetter(g); |
| 207 | p->setSetter(s); |
| 208 | proto->insertMember(s: name, p, attributes: QV4::Attr_Accessor|QV4::Attr_NotEnumerable|QV4::Attr_NotConfigurable); |
| 209 | } |
| 210 | prototype.set(engine: v4, value: proto); |
| 211 | } |
| 212 | |
| 213 | // QAbstractDynamicMetaObject |
| 214 | |
| 215 | void objectDestroyed(QObject *) override |
| 216 | { |
| 217 | release(); |
| 218 | } |
| 219 | |
| 220 | int metaCall(QObject *object, QMetaObject::Call call, int id, void **arguments) override |
| 221 | { |
| 222 | return static_cast<QQmlDMAbstractItemModelData *>(object)->metaCall(call, id, arguments); |
| 223 | } |
| 224 | |
| 225 | int rowCount(const QQmlAdaptorModel &model) const override |
| 226 | { |
| 227 | if (const QAbstractItemModel *aim = model.aim()) |
| 228 | return aim->rowCount(parent: model.rootIndex); |
| 229 | return 0; |
| 230 | } |
| 231 | |
| 232 | int columnCount(const QQmlAdaptorModel &model) const override |
| 233 | { |
| 234 | if (const QAbstractItemModel *aim = model.aim()) |
| 235 | return aim->columnCount(parent: model.rootIndex); |
| 236 | return 0; |
| 237 | } |
| 238 | |
| 239 | void cleanup(QQmlAdaptorModel &) const override |
| 240 | { |
| 241 | release(); |
| 242 | } |
| 243 | |
| 244 | QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override |
| 245 | { |
| 246 | if (!metaObject) { |
| 247 | VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this); |
| 248 | dataType->initializeMetaType(model); |
| 249 | } |
| 250 | |
| 251 | if (const QAbstractItemModel *aim = model.aim()) { |
| 252 | const QModelIndex modelIndex |
| 253 | = aim->index(row: model.rowAt(index), column: model.columnAt(index), parent: model.rootIndex); |
| 254 | |
| 255 | const auto it = roleNames.find(key: role.toUtf8()), end = roleNames.end(); |
| 256 | if (it != roleNames.end()) |
| 257 | return modelIndex.data(arole: *it); |
| 258 | |
| 259 | if (role.isEmpty() || role == QLatin1String("modelData" )) { |
| 260 | if (roleNames.size() == 1) |
| 261 | return modelIndex.data(arole: roleNames.begin().value()); |
| 262 | |
| 263 | QVariantMap modelData; |
| 264 | for (auto jt = roleNames.begin(); jt != end; ++jt) |
| 265 | modelData.insert(key: QString::fromUtf8(ba: jt.key()), value: modelIndex.data(arole: jt.value())); |
| 266 | return modelData; |
| 267 | } |
| 268 | |
| 269 | if (role == QLatin1String("hasModelChildren" )) |
| 270 | return QVariant(aim->hasChildren(parent: modelIndex)); |
| 271 | } |
| 272 | return QVariant(); |
| 273 | } |
| 274 | |
| 275 | QVariant parentModelIndex(const QQmlAdaptorModel &model) const override |
| 276 | { |
| 277 | if (const QAbstractItemModel *aim = model.aim()) |
| 278 | return QVariant::fromValue(value: aim->parent(child: model.rootIndex)); |
| 279 | return QVariant(); |
| 280 | } |
| 281 | |
| 282 | QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override |
| 283 | { |
| 284 | if (const QAbstractItemModel *aim = model.aim()) |
| 285 | return QVariant::fromValue(value: aim->index(row: model.rowAt(index), column: model.columnAt(index), |
| 286 | parent: model.rootIndex)); |
| 287 | return QVariant(); |
| 288 | } |
| 289 | |
| 290 | bool canFetchMore(const QQmlAdaptorModel &model) const override |
| 291 | { |
| 292 | if (const QAbstractItemModel *aim = model.aim()) |
| 293 | return aim->canFetchMore(parent: model.rootIndex); |
| 294 | return false; |
| 295 | } |
| 296 | |
| 297 | void fetchMore(QQmlAdaptorModel &model) const override |
| 298 | { |
| 299 | if (QAbstractItemModel *aim = model.aim()) |
| 300 | aim->fetchMore(parent: model.rootIndex); |
| 301 | } |
| 302 | |
| 303 | QQmlDelegateModelItem *createItem( |
| 304 | QQmlAdaptorModel &model, |
| 305 | const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
| 306 | int index, int row, int column) override |
| 307 | { |
| 308 | if (!metaObject) |
| 309 | initializeMetaType(model); |
| 310 | return new QQmlDMAbstractItemModelData(metaType, this, index, row, column); |
| 311 | } |
| 312 | |
| 313 | void initializeMetaType(const QQmlAdaptorModel &model) |
| 314 | { |
| 315 | QMetaObjectBuilder builder; |
| 316 | QQmlAdaptorModelEngineData::setModelDataType<QQmlDMAbstractItemModelData>(builder: &builder, metaType: this); |
| 317 | |
| 318 | const QByteArray propertyType = QByteArrayLiteral("QVariant" ); |
| 319 | const QAbstractItemModel *aim = model.aim(); |
| 320 | const QHash<int, QByteArray> names = aim ? aim->roleNames() : QHash<int, QByteArray>(); |
| 321 | for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) { |
| 322 | const int propertyId = propertyRoles.size(); |
| 323 | propertyRoles.append(t: it.key()); |
| 324 | roleNames.insert(key: it.value(), value: it.key()); |
| 325 | QQmlAdaptorModelEngineData::addProperty(builder: &builder, propertyId, propertyName: it.value(), propertyType); |
| 326 | } |
| 327 | |
| 328 | metaObject.reset(other: builder.toMetaObject()); |
| 329 | *static_cast<QMetaObject *>(this) = *metaObject; |
| 330 | propertyCache = QQmlPropertyCache::createStandalone( |
| 331 | metaObject.data(), metaObjectRevision: model.modelItemRevision); |
| 332 | } |
| 333 | |
| 334 | QV4::PersistentValue prototype; |
| 335 | QList<int> propertyRoles; |
| 336 | QList<int> watchedRoleIds; |
| 337 | QList<QByteArray> watchedRoles; |
| 338 | QHash<QByteArray, int> roleNames; |
| 339 | QQmlAdaptorModel *model; |
| 340 | int propertyOffset; |
| 341 | int signalOffset; |
| 342 | }; |
| 343 | |
| 344 | QT_END_NAMESPACE |
| 345 | |
| 346 | #endif // QQMLDMABSTRACTITEMMODELDATA_P_H |
| 347 | |