// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // Qt-Security score:significant #ifndef QQMLDATAMODEL_P_P_H #define QQMLDATAMODEL_P_P_H #include "qqmldelegatemodel_p.h" #include #include #include #include #include #include #include // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // QT_REQUIRE_CONFIG(qml_delegate_model); QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateRecycling) typedef QQmlListCompositor Compositor; class QQmlDelegateModelAttachedMetaObject; class QQmlAbstractDelegateComponent; class QQmlTableInstanceModel; class Q_QMLMODELS_EXPORT QQmlDelegateModelItemMetaType final : public QQmlRefCounted { public: enum class ModelKind : quint8 { InstanceModel, DelegateModel, TableInstanceModel, }; QQmlDelegateModelItemMetaType( QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames); QQmlDelegateModelItemMetaType( QV4::ExecutionEngine *engine, QQmlTableInstanceModel *model); ~QQmlDelegateModelItemMetaType(); void initializeAttachedMetaObject(); void initializePrototype(); int parseGroups(const QStringList &groupNames) const; int parseGroups(const QV4::Value &groupNames) const; QQmlDelegateModel *delegateModel() const { return modelKind == ModelKind::DelegateModel ? static_cast(model.get()) : nullptr; } qsizetype groupCount() const { return groupNames.size(); } void emitModelChanged() const; QPointer model; QV4::ExecutionEngine * const v4Engine; QQmlRefPointer attachedMetaObject; const QStringList groupNames; QV4::PersistentValue modelItemProto; ModelKind modelKind = ModelKind::InstanceModel; }; class QQmlAdaptorModel; class QQDMIncubationTask; class QQmlDelegateModelItem : public QObject { Q_OBJECT Q_PROPERTY(int index READ modelIndex NOTIFY modelIndexChanged) Q_PROPERTY(int row READ modelRow NOTIFY rowChanged REVISION(2, 12)) Q_PROPERTY(int column READ modelColumn NOTIFY columnChanged REVISION(2, 12)) Q_PROPERTY(QObject *model READ modelObject CONSTANT) public: struct ObjectReference { Q_DISABLE_COPY_MOVE(ObjectReference) ObjectReference(QQmlDelegateModelItem *item) : item(item) { ++item->m_objectStrongRef; } ~ObjectReference() { Q_ASSERT(item->m_objectStrongRef > 0); --item->m_objectStrongRef; } private: QQmlDelegateModelItem *item = nullptr; }; struct ObjectSpanReference { Q_DISABLE_COPY_MOVE(ObjectSpanReference) ObjectSpanReference(QSpan span) : items(std::move(span)) { for (QQmlDelegateModelItem *item : items) ++item->m_objectStrongRef; } ~ObjectSpanReference() { for (QQmlDelegateModelItem *item : items) { Q_ASSERT(item->m_objectStrongRef > 0); --item->m_objectStrongRef; } } private: const QSpan items; }; struct ScriptReference { Q_DISABLE_COPY_MOVE(ScriptReference) ScriptReference(QQmlDelegateModelItem *item) : item(item) { item->referenceSript(); } ~ScriptReference() { item->releaseScript(); } QQmlDelegateModelItem *data() const { return item; } private: QQmlDelegateModelItem *item = nullptr; }; QQmlDelegateModelItem(const QQmlRefPointer &metaType, QQmlAdaptorModel::Accessors *accessor, int modelIndex, int row, int column); ~QQmlDelegateModelItem(); [[nodiscard]] QObject *referenceObjectWeak() { Q_ASSERT(m_object); ++m_objectWeakRef; return m_object; } bool releaseObjectWeak() { if (m_objectWeakRef > 0) --m_objectWeakRef; return m_objectWeakRef == 0 && !(m_groups & Compositor::PersistedFlag); } void clearObjectWeakReferences() { m_objectWeakRef = 0; } bool isObjectReferenced() const { return m_objectWeakRef != 0 || m_objectStrongRef != 0 || (m_groups & Compositor::PersistedFlag); } void childContextObjectDestroyed(QObject *childContextObject); bool isScriptReferenced() const { return m_scriptRef || m_incubationTask || ((m_groups & Compositor::UnresolvedFlag) && (m_groups & Compositor::GroupMask)); } void dispose(); QObject *modelObject() { return this; } void destroyObject(); void destroyObjectLater(); static QQmlDelegateModelItem *dataForObject(QObject *object); int groupIndex(Compositor::Group group); int modelRow() const { return m_row; } int modelColumn() const { return m_column; } int modelIndex() const { return m_index; } bool hasValidModelIndex() const { return m_index >= 0; } virtual void setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit = false); bool usesStructuredModelData() const { return m_useStructuredModelData; } virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(m_metaType->v4Engine, this); } virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); } virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; } virtual QQmlRefPointer initProxy() { return m_contextData; } static QV4::ReturnedValue get_model(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); static QV4::ReturnedValue get_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); static QV4::ReturnedValue set_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); static QV4::ReturnedValue get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &); static QV4::ReturnedValue set_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg); static QV4::ReturnedValue get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg); QQmlDelegateModelAttached *attached() const { if (!m_object) return nullptr; QQmlData *ddata = QQmlData::get(m_object); if (!ddata || !ddata->hasExtendedData()) return nullptr; return static_cast( ddata->attachedProperties()->value( QQmlPrivate::attachedPropertiesFunc())); } void disableStructuredModelData() { m_useStructuredModelData = false; } QDynamicMetaObjectData *exchangeMetaObject(QDynamicMetaObjectData *metaObject) { return std::exchange(d_ptr->metaObject, metaObject); } QQDMIncubationTask *incubationTask() const { return m_incubationTask; } void clearIncubationTask() { m_incubationTask = nullptr; } void setIncubationTask(QQDMIncubationTask *incubationTask) { Q_ASSERT(!m_incubationTask); Q_ASSERT(incubationTask); m_incubationTask = incubationTask; } quint16 objectStrongRef() const { return m_objectStrongRef; } quint16 objectWeakRef() const { return m_objectWeakRef; } quint16 scriptRef() const { return m_scriptRef; } QObject *object() const { return m_object; } void setObject(QObject *object) { Q_ASSERT(!m_object); Q_ASSERT(object); m_object = object; } QQmlComponent *delegate() const { return m_delegate; } void setDelegate(QQmlComponent *delegate) { m_delegate = delegate; } const QQmlRefPointer &contextData() const { return m_contextData; } void setContextData(const QQmlRefPointer &contextData) { m_contextData = contextData; } int groups() const { return m_groups; } void setGroups(int groups) { m_groups = groups; } void addGroups(int groups) { m_groups |= groups; } void removeGroups(int groups) { m_groups &= ~groups; } void clearGroups() { m_groups = 0; } const QQmlRefPointer &metaType() const { return m_metaType; } Q_SIGNALS: void modelIndexChanged(); Q_REVISION(2, 12) void rowChanged(); Q_REVISION(2, 12) void columnChanged(); private: void objectDestroyed(QObject *); void referenceSript() { ++m_scriptRef; } bool releaseScript() { Q_ASSERT(m_scriptRef > 0); return --m_scriptRef == 0; } QQmlRefPointer m_metaType; QQmlRefPointer m_contextData; QPointer m_object; QQDMIncubationTask *m_incubationTask = nullptr; QQmlComponent *m_delegate = nullptr; int m_groups = 0; int m_index = -1; int m_row = -1; int m_column = -1; // A strong reference to the object prevents the deletion of the object. We use them as // temporary guards with ObjectReference or ObjectSpanReference to make sure the objects we're // currently manipulating don't disappear. quint16 m_objectStrongRef = 0; // A weak reference to the object is added when handing the object out to the view via object(). // It is dropped when the view calls release(). Weak references are advisory. We try not to // delete an object while weak references are still in place. However, during destruction, the // weak refernces may be ignored. Views are expected to use QPointer or similar guards in // addition. quint16 m_objectWeakRef = 0; // A script reference is a strong reference to the QQmlDelegateModelItem itself. We don't delete // the QQmlDelegateModelItem while script references are still in place. We can use them as // temporary guards to hold on to an item while working on it, or we can attach them to // Heap::QQmlDelegateModelItemObject in order to extend the life time of a QQmlDelegateModelItem // to the life time of its heap object. // The latter case is particular in that it contradicts our usual mechanism of only holding weak // references to QObjects in e.g. QV4::QObjectWrapper. The trick here is that the actual // QQmlDelegateModelItem is never exposed to the user, so that the user has no chance of // manually deleting it. // TODO: This is brittle. quint16 m_scriptRef = 0; bool m_useStructuredModelData = true; }; namespace QV4 { namespace Heap { struct QQmlDelegateModelItemObject : Object { inline void init(QQmlDelegateModelItem *item); void destroy(); alignas(alignof(QQmlDelegateModelItem::ScriptReference)) std::byte ref[sizeof(QQmlDelegateModelItem::ScriptReference)]; QQmlDelegateModelItem *item() const { return reinterpret_cast(&ref)->data(); } }; } } struct QQmlDelegateModelItemObject : QV4::Object { V4_OBJECT2(QQmlDelegateModelItemObject, QV4::Object) V4_NEEDS_DESTROY }; void QV4::Heap::QQmlDelegateModelItemObject::init(QQmlDelegateModelItem *modelItem) { Object::init(); new (ref) QQmlDelegateModelItem::ScriptReference(modelItem); } class QQmlReusableDelegateModelItemsPool { public: bool insertItem(QQmlDelegateModelItem *modelItem); QQmlDelegateModelItem *takeItem(const QQmlComponent *delegate, int newIndexHint); void reuseItem(QQmlDelegateModelItem *item, int newModelIndex); void drain(int maxPoolTime, std::function releaseItem); int size() { Q_ASSERT(m_reusableItemsPool.size() <= MaxSize); return int(m_reusableItemsPool.size()); } private: struct PoolItem { QQmlDelegateModelItem *item = nullptr; int poolTime = 0; }; static constexpr size_t MaxSize = size_t(std::numeric_limits::max()); std::vector m_reusableItemsPool; }; template class QQmlDelegateModelReadOnlyMetaObject : QDynamicMetaObjectData { Q_DISABLE_COPY_MOVE(QQmlDelegateModelReadOnlyMetaObject) public: QQmlDelegateModelReadOnlyMetaObject(const Target &target, int readOnlyProperty) : target(target) , readOnlyProperty(readOnlyProperty) { if (QQmlDelegateModelItem *object = target) original = object->exchangeMetaObject(this); } ~QQmlDelegateModelReadOnlyMetaObject() { if (QQmlDelegateModelItem *object = target) object->exchangeMetaObject(original); } void objectDestroyed(QObject *o) final { if (target) original->objectDestroyed(o); } QMetaObject *toDynamicMetaObject(QObject *o) final { return target ? original->toDynamicMetaObject(o) : nullptr; } int metaCall(QObject *o, QMetaObject::Call call, int id, void **argv) final { if (!target) return 0; if (id == readOnlyProperty && call == QMetaObject::WriteProperty) return 0; return original->metaCall(o, call, id, argv); } private: Target target = {}; QDynamicMetaObjectData *original = nullptr; int readOnlyProperty = -1; }; class QQmlDelegateModelPrivate; class QQDMIncubationTask : public QQmlIncubator { public: QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode) : QQmlIncubator(mode) , incubating(nullptr) , vdm(l) {} void initializeRequiredProperties( QQmlDelegateModelItem *modelItemToIncubate, QObject* object, QQmlDelegateModel::DelegateModelAccess access); void statusChanged(Status) override; void setInitialState(QObject *) override; QQmlDelegateModelItem *incubating = nullptr; QQmlDelegateModelPrivate *vdm = nullptr; QQmlRefPointer proxyContext; QPointer proxiedObject = nullptr; // the proxied object might disapear, so we use a QPointer instead of a raw one int index[QQmlListCompositor::MaximumGroupCount]; }; class QQmlDelegateModelGroupEmitter { public: virtual ~QQmlDelegateModelGroupEmitter(); virtual void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) = 0; virtual void createdPackage(int, QQuickPackage *); virtual void initPackage(int, QQuickPackage *); virtual void destroyingPackage(QQuickPackage *); QIntrusiveListNode emitterNode; }; typedef QIntrusiveList QQmlDelegateModelGroupEmitterList; class QQmlDelegateModelGroupPrivate : public QObjectPrivate { public: Q_DECLARE_PUBLIC(QQmlDelegateModelGroup) QQmlDelegateModelGroupPrivate() : group(Compositor::Cache), defaultInclude(false) {} static QQmlDelegateModelGroupPrivate *get(QQmlDelegateModelGroup *group) { return static_cast(QObjectPrivate::get(group)); } void setModel(QQmlDelegateModel *model, Compositor::Group group); bool isChangedConnected(); void emitChanges(QV4::ExecutionEngine *engine); void emitModelUpdated(bool reset); void createdPackage(int index, QQuickPackage *package); void initPackage(int index, QQuickPackage *package); void destroyingPackage(QQuickPackage *package); bool parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const; bool parseGroupArgs( QQmlV4FunctionPtr args, Compositor::Group *group, int *index, int *count, int *groups) const; Compositor::Group group; QPointer model; QQmlDelegateModelGroupEmitterList emitters; QQmlChangeSet changeSet; QString name; bool defaultInclude; }; class QQmlDelegateModelParts; class QQmlDelegateModelPrivate : public QObjectPrivate, public QQmlDelegateModelGroupEmitter { Q_DECLARE_PUBLIC(QQmlDelegateModel) public: QQmlDelegateModelPrivate(QQmlContext *); ~QQmlDelegateModelPrivate(); static QQmlDelegateModelPrivate *get(QQmlDelegateModel *m) { return static_cast(QObjectPrivate::get(m)); } void init(); void connectModel(QQmlAdaptorModel *model); void connectToAbstractItemModel(); void disconnectFromAbstractItemModel(); void requestMoreIfNecessary(); QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode); QQmlDelegateModel::ReleaseFlags release(QObject *object, QQmlInstanceModel::ReusableFlag reusable = QQmlInstanceModel::NotReusable); QVariant variantValue(Compositor::Group group, int index, const QString &name); void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitCreatedItem(QQDMIncubationTask *incubationTask, QObject *item) { Q_EMIT q_func()->createdItem(incubationTask->index[m_compositorGroup], item); } void emitInitItem(QQDMIncubationTask *incubationTask, QObject *item) { Q_EMIT q_func()->initItem(incubationTask->index[m_compositorGroup], item); } void emitDestroyingPackage(QQuickPackage *package); void emitDestroyingItem(QObject *item) { Q_EMIT q_func()->destroyingItem(item); } void addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it); void removeCacheItem(QQmlDelegateModelItem *cacheItem); void destroyCacheItem(QQmlDelegateModelItem *cacheItem); void updateFilterGroup(); void reuseItem(QQmlDelegateModelItem *item, int newModelIndex, int newGroups); void drainReusableItemsPool(int maxPoolTime); QQmlComponent *resolveDelegate(int index); void addGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); void removeGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); void setGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); void itemsInserted( const QVector &inserts, QVarLengthArray, Compositor::MaximumGroupCount> *translatedInserts, QHash > *movedItems = nullptr); void itemsInserted(const QVector &inserts); void itemsRemoved( const QVector &removes, QVarLengthArray, Compositor::MaximumGroupCount> *translatedRemoves, QHash > *movedItems = nullptr); void itemsRemoved(const QVector &removes); void itemsMoved( const QVector &removes, const QVector &inserts); void itemsChanged(const QVector &changes); void emitChanges(); void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) override; void delegateChanged(bool add = true, bool remove = true); enum class InsertionResult { Success, Error, Retry }; InsertionResult insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups); int adaptorModelCount() const; static void group_append(QQmlListProperty *property, QQmlDelegateModelGroup *group); static qsizetype group_count(QQmlListProperty *property); static QQmlDelegateModelGroup *group_at(QQmlListProperty *property, qsizetype index); void releaseIncubator(QQDMIncubationTask *incubationTask); void incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status); void setInitialState(QQDMIncubationTask *incubationTask, QObject *o); QQmlAdaptorModel m_adaptorModel; QQmlListCompositor m_compositor; QQmlStrongJSQObjectReference m_delegate; QQmlAbstractDelegateComponent *m_delegateChooser; QMetaObject::Connection m_delegateChooserChanged; QQmlRefPointer m_cacheMetaType; QPointer m_context; QQmlDelegateModelParts *m_parts; QQmlDelegateModelGroupEmitterList m_pendingParts; QList m_cache; QQmlReusableDelegateModelItemsPool m_reusableItemsPool; QList m_finishedIncubating; QList m_watchedRoles; QString m_filterGroup; int m_count; int m_groupCount; QQmlListCompositor::Group m_compositorGroup; bool m_complete : 1; bool m_delegateValidated : 1; bool m_reset : 1; bool m_transaction : 1; bool m_incubatorCleanupScheduled : 1; bool m_waitingToFetchMore : 1; union { struct { QQmlDelegateModelGroup *m_cacheItems; QQmlDelegateModelGroup *m_items; QQmlDelegateModelGroup *m_persistedItems; }; QQmlDelegateModelGroup *m_groups[Compositor::MaximumGroupCount]; }; }; class QQmlPartsModel : public QQmlInstanceModel, public QQmlDelegateModelGroupEmitter { Q_OBJECT Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup FINAL) public: QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent = nullptr); ~QQmlPartsModel(); QString filterGroup() const; void setFilterGroup(const QString &group); void resetFilterGroup(); void updateFilterGroup(); void updateFilterGroup(Compositor::Group group, const QQmlChangeSet &changeSet); int count() const override; bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *item, ReusableFlag reusable = NotReusable) override; QVariant variantValue(int index, const QString &role) override; QList watchedRoles() const { return m_watchedRoles; } void setWatchedRoles(const QList &roles) override; QQmlIncubator::Status incubationStatus(int index) override; int indexOf(QObject *item, QObject *objectContext) const override; void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) override; void createdPackage(int index, QQuickPackage *package) override; void initPackage(int index, QQuickPackage *package) override; void destroyingPackage(QQuickPackage *package) override; Q_SIGNALS: void filterGroupChanged(); private: QQmlDelegateModel *m_model; QMultiHash m_packaged; QString m_part; QString m_filterGroup; QList m_watchedRoles; QVector m_pendingPackageInitializations; // vector holds model indices Compositor::Group m_compositorGroup; bool m_inheritGroup; bool m_modelUpdatePending = true; }; class QMetaPropertyBuilder; class QQmlDelegateModelPartsMetaObject : public QQmlOpenMetaObject { public: QQmlDelegateModelPartsMetaObject(QObject *parent) : QQmlOpenMetaObject(parent) {} void propertyCreated(int, QMetaPropertyBuilder &) override; QVariant initialValue(int) override; }; class QQmlDelegateModelParts : public QObject { Q_OBJECT public: QQmlDelegateModelParts(QQmlDelegateModel *parent); QQmlDelegateModel *model; QList models; }; class QQmlDelegateModelAttachedMetaObject final : public QAbstractDynamicMetaObject, public QQmlRefCounted { public: QQmlDelegateModelAttachedMetaObject( QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject); ~QQmlDelegateModelAttachedMetaObject(); void objectDestroyed(QObject *) override; int metaCall(QObject *, QMetaObject::Call, int _id, void **) override; private: QQmlDelegateModelItemMetaType * const metaType; QMetaObject * const metaObject; const int memberPropertyOffset; const int indexPropertyOffset; }; QT_END_NAMESPACE #endif