blob: 2126ad3dc57071a1f00e35ad14bcbaf40c979cbb [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qqmladaptormodel_p.h"
#include <private/qqmldelegatemodel_p_p.h>
#include <private/qmetaobjectbuilder_p.h>
#include <private/qqmlproperty_p.h>
#include <private/qv4value_p.h>
#include <private/qv4functionobject_p.h>
QT_BEGIN_NAMESPACE
class QQmlAdaptorModelEngineData : public QV4::ExecutionEngine::Deletable
{
public:
QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4);
~QQmlAdaptorModelEngineData();
QV4::ExecutionEngine *v4;
QV4::PersistentValue listItemProto;
};
V4_DEFINE_EXTENSION(QQmlAdaptorModelEngineData, engineData)
static QV4::ReturnedValue get_index(const QV4::FunctionObject *f, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(f);
QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
if (!o)
RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")));
RETURN_RESULT(QV4::Encode(o->d()->item->index));
}
template <typename T, typename M> static void setModelDataType(QMetaObjectBuilder *builder, M *metaType)
{
builder->setFlags(QMetaObjectBuilder::DynamicMetaObject);
builder->setClassName(T::staticMetaObject.className());
builder->setSuperClass(&T::staticMetaObject);
metaType->propertyOffset = T::staticMetaObject.propertyCount();
metaType->signalOffset = T::staticMetaObject.methodCount();
}
static void addProperty(QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName, const QByteArray &propertyType)
{
builder->addSignal("__" + QByteArray::number(propertyId) + "()");
QMetaPropertyBuilder property = builder->addProperty(
propertyName, propertyType, propertyId);
property.setWritable(true);
}
class VDMModelDelegateDataType;
class QQmlDMCachedModelData : public QQmlDelegateModelItem
{
public:
QQmlDMCachedModelData(
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
VDMModelDelegateDataType *dataType,
int index, int row, int column);
int metaCall(QMetaObject::Call call, int id, void **arguments);
virtual QVariant value(int role) const = 0;
virtual void setValue(int role, const QVariant &value) = 0;
void setValue(const QString &role, const QVariant &value) override;
bool resolveIndex(const QQmlAdaptorModel &model, int idx) override;
static QV4::ReturnedValue get_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue set_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
VDMModelDelegateDataType *type;
QVector<QVariant> cachedData;
};
class VDMModelDelegateDataType
: public QQmlRefCount
, public QQmlAdaptorModel::Accessors
, public QAbstractDynamicMetaObject
{
public:
VDMModelDelegateDataType(QQmlAdaptorModel *model)
: model(model)
, propertyOffset(0)
, signalOffset(0)
, hasModelData(false)
{
}
bool notify(
const QQmlAdaptorModel &,
const QList<QQmlDelegateModelItem *> &items,
int index,
int count,
const QVector<int> &roles) const override
{
bool changed = roles.isEmpty() && !watchedRoles.isEmpty();
if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) {
QList<int> roleIds;
for (const QByteArray &r : watchedRoles) {
QHash<QByteArray, int>::const_iterator it = roleNames.find(r);
if (it != roleNames.end())
roleIds << it.value();
}
const_cast<VDMModelDelegateDataType *>(this)->watchedRoleIds = roleIds;
}
QVector<int> signalIndexes;
for (int i = 0; i < roles.count(); ++i) {
const int role = roles.at(i);
if (!changed && watchedRoleIds.contains(role))
changed = true;
int propertyId = propertyRoles.indexOf(role);
if (propertyId != -1)
signalIndexes.append(propertyId + signalOffset);
}
if (roles.isEmpty()) {
const int propertyRolesCount = propertyRoles.count();
signalIndexes.reserve(propertyRolesCount);
for (int propertyId = 0; propertyId < propertyRolesCount; ++propertyId)
signalIndexes.append(propertyId + signalOffset);
}
QVarLengthArray<QQmlGuard<QQmlDelegateModelItem>> guardedItems;
for (const auto item : items)
guardedItems.append(item);
for (const auto &item : qAsConst(guardedItems)) {
if (item.isNull())
continue;
const int idx = item->modelIndex();
if (idx >= index && idx < index + count) {
for (int i = 0; i < signalIndexes.count(); ++i)
QMetaObject::activate(item, signalIndexes.at(i), nullptr);
}
}
return changed;
}
void replaceWatchedRoles(
QQmlAdaptorModel &,
const QList<QByteArray> &oldRoles,
const QList<QByteArray> &newRoles) const override
{
VDMModelDelegateDataType *dataType = const_cast<VDMModelDelegateDataType *>(this);
dataType->watchedRoleIds.clear();
for (const QByteArray &oldRole : oldRoles)
dataType->watchedRoles.removeOne(oldRole);
dataType->watchedRoles += newRoles;
}
static QV4::ReturnedValue get_hasModelChildren(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
if (!o)
RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")));
const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(o->d()->item)->type->model;
if (o->d()->item->index >= 0) {
if (const QAbstractItemModel *const aim = model->aim())
RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex))));
}
RETURN_RESULT(QV4::Encode(false));
}
void initializeConstructor(QQmlAdaptorModelEngineData *const data)
{
QV4::ExecutionEngine *v4 = data->v4;
QV4::Scope scope(v4);
QV4::ScopedObject proto(scope, v4->newObject());
proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
proto->defineAccessorProperty(QStringLiteral("hasModelChildren"), get_hasModelChildren, nullptr);
QV4::ScopedProperty p(scope);
typedef QHash<QByteArray, int>::const_iterator iterator;
for (iterator it = roleNames.constBegin(), end = roleNames.constEnd(); it != end; ++it) {
const int propertyId = propertyRoles.indexOf(it.value());
const QByteArray &propertyName = it.key();
QV4::ScopedString name(scope, v4->newString(QString::fromUtf8(propertyName)));
QV4::ExecutionContext *global = v4->rootContext();
QV4::ScopedFunctionObject g(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(global, propertyId, QQmlDMCachedModelData::get_property));
QV4::ScopedFunctionObject s(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(global, propertyId, QQmlDMCachedModelData::set_property));
p->setGetter(g);
p->setSetter(s);
proto->insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable|QV4::Attr_NotConfigurable);
}
prototype.set(v4, proto);
}
// QAbstractDynamicMetaObject
void objectDestroyed(QObject *) override
{
release();
}
int metaCall(QObject *object, QMetaObject::Call call, int id, void **arguments) override
{
return static_cast<QQmlDMCachedModelData *>(object)->metaCall(call, id, arguments);
}
QV4::PersistentValue prototype;
QList<int> propertyRoles;
QList<int> watchedRoleIds;
QList<QByteArray> watchedRoles;
QHash<QByteArray, int> roleNames;
QQmlAdaptorModel *model;
int propertyOffset;
int signalOffset;
bool hasModelData;
};
QQmlDMCachedModelData::QQmlDMCachedModelData(
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
VDMModelDelegateDataType *dataType, int index, int row, int column)
: QQmlDelegateModelItem(metaType, dataType, index, row, column)
, type(dataType)
{
if (index == -1)
cachedData.resize(type->hasModelData ? 1 : type->propertyRoles.count());
QObjectPrivate::get(this)->metaObject = type;
type->addref();
}
int QQmlDMCachedModelData::metaCall(QMetaObject::Call call, int id, void **arguments)
{
if (call == QMetaObject::ReadProperty && id >= type->propertyOffset) {
const int propertyIndex = id - type->propertyOffset;
if (index == -1) {
if (!cachedData.isEmpty()) {
*static_cast<QVariant *>(arguments[0]) = cachedData.at(
type->hasModelData ? 0 : propertyIndex);
}
} else if (*type->model) {
*static_cast<QVariant *>(arguments[0]) = value(type->propertyRoles.at(propertyIndex));
}
return -1;
} else if (call == QMetaObject::WriteProperty && id >= type->propertyOffset) {
const int propertyIndex = id - type->propertyOffset;
if (index == -1) {
const QMetaObject *meta = metaObject();
if (cachedData.count() > 1) {
cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]);
QMetaObject::activate(this, meta, propertyIndex, nullptr);
} else if (cachedData.count() == 1) {
cachedData[0] = *static_cast<QVariant *>(arguments[0]);
QMetaObject::activate(this, meta, 0, nullptr);
QMetaObject::activate(this, meta, 1, nullptr);
}
} else if (*type->model) {
setValue(type->propertyRoles.at(propertyIndex), *static_cast<QVariant *>(arguments[0]));
}
return -1;
} else {
return qt_metacall(call, id, arguments);
}
}
void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value)
{
QHash<QByteArray, int>::iterator it = type->roleNames.find(role.toUtf8());
if (it != type->roleNames.end()) {
for (int i = 0; i < type->propertyRoles.count(); ++i) {
if (type->propertyRoles.at(i) == *it) {
cachedData[i] = value;
return;
}
}
}
}
bool QQmlDMCachedModelData::resolveIndex(const QQmlAdaptorModel &adaptorModel, int idx)
{
if (index == -1) {
Q_ASSERT(idx >= 0);
cachedData.clear();
setModelIndex(idx, adaptorModel.rowAt(idx), adaptorModel.columnAt(idx));
const QMetaObject *meta = metaObject();
const int propertyCount = type->propertyRoles.count();
for (int i = 0; i < propertyCount; ++i)
QMetaObject::activate(this, meta, i, nullptr);
return true;
} else {
return false;
}
}
QV4::ReturnedValue QQmlDMCachedModelData::get_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
if (!o)
return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index;
QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item);
if (o->d()->item->index == -1) {
if (!modelData->cachedData.isEmpty()) {
return scope.engine->fromVariant(
modelData->cachedData.at(modelData->type->hasModelData ? 0 : propertyId));
}
} else if (*modelData->type->model) {
return scope.engine->fromVariant(
modelData->value(modelData->type->propertyRoles.at(propertyId)));
}
return QV4::Encode::undefined();
}
QV4::ReturnedValue QQmlDMCachedModelData::set_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
if (!o)
return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
if (!argc)
return scope.engine->throwTypeError();
uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index;
if (o->d()->item->index == -1) {
QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item);
if (!modelData->cachedData.isEmpty()) {
if (modelData->cachedData.count() > 1) {
modelData->cachedData[propertyId] = scope.engine->toVariant(argv[0], QMetaType::UnknownType);
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), propertyId, nullptr);
} else if (modelData->cachedData.count() == 1) {
modelData->cachedData[0] = scope.engine->toVariant(argv[0], QMetaType::UnknownType);
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 0, nullptr);
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 1, nullptr);
}
}
}
return QV4::Encode::undefined();
}
//-----------------------------------------------------------------
// QAbstractItemModel
//-----------------------------------------------------------------
class QQmlDMAbstractItemModelData : public QQmlDMCachedModelData
{
Q_OBJECT
Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT)
public:
QQmlDMAbstractItemModelData(
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
VDMModelDelegateDataType *dataType,
int index, int row, int column)
: QQmlDMCachedModelData(metaType, dataType, index, row, column)
{
}
bool hasModelChildren() const
{
if (index >= 0) {
if (const QAbstractItemModel *const model = type->model->aim())
return model->hasChildren(model->index(row, column, type->model->rootIndex));
}
return false;
}
QVariant value(int role) const override
{
if (const QAbstractItemModel *aim = type->model->aim())
return aim->index(row, column, type->model->rootIndex).data(role);
return QVariant();
}
void setValue(int role, const QVariant &value) override
{
if (QAbstractItemModel *aim = type->model->aim())
aim->setData(aim->index(row, column, type->model->rootIndex), value, role);
}
QV4::ReturnedValue get() override
{
if (type->prototype.isUndefined()) {
QQmlAdaptorModelEngineData * const data = engineData(v4);
type->initializeConstructor(data);
}
QV4::Scope scope(v4);
QV4::ScopedObject proto(scope, type->prototype.value());
QV4::ScopedObject o(scope, proto->engine()->memoryManager->allocate<QQmlDelegateModelItemObject>(this));
o->setPrototypeOf(proto);
++scriptRef;
return o.asReturnedValue();
}
};
class VDMAbstractItemModelDataType : public VDMModelDelegateDataType
{
public:
VDMAbstractItemModelDataType(QQmlAdaptorModel *model)
: VDMModelDelegateDataType(model)
{
}
int rowCount(const QQmlAdaptorModel &model) const override
{
if (const QAbstractItemModel *aim = model.aim())
return aim->rowCount(model.rootIndex);
return 0;
}
int columnCount(const QQmlAdaptorModel &model) const override
{
if (const QAbstractItemModel *aim = model.aim())
return aim->columnCount(model.rootIndex);
return 0;
}
void cleanup(QQmlAdaptorModel &) const override
{
release();
}
QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
{
if (!metaObject) {
VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this);
dataType->initializeMetaType(model);
}
if (const QAbstractItemModel *aim = model.aim()) {
QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8());
if (it != roleNames.end()) {
return aim->index(model.rowAt(index), model.columnAt(index),
model.rootIndex).data(*it);
} else if (role == QLatin1String("hasModelChildren")) {
return QVariant(aim->hasChildren(aim->index(model.rowAt(index),
model.columnAt(index),
model.rootIndex)));
}
}
return QVariant();
}
QVariant parentModelIndex(const QQmlAdaptorModel &model) const override
{
if (const QAbstractItemModel *aim = model.aim())
return QVariant::fromValue(aim->parent(model.rootIndex));
return QVariant();
}
QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override
{
if (const QAbstractItemModel *aim = model.aim())
return QVariant::fromValue(aim->index(model.rowAt(index), model.columnAt(index),
model.rootIndex));
return QVariant();
}
bool canFetchMore(const QQmlAdaptorModel &model) const override
{
if (const QAbstractItemModel *aim = model.aim())
return aim->canFetchMore(model.rootIndex);
return false;
}
void fetchMore(QQmlAdaptorModel &model) const override
{
if (QAbstractItemModel *aim = model.aim())
aim->fetchMore(model.rootIndex);
}
QQmlDelegateModelItem *createItem(
QQmlAdaptorModel &model,
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
int index, int row, int column) const override
{
VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this);
if (!metaObject)
dataType->initializeMetaType(model);
return new QQmlDMAbstractItemModelData(metaType, dataType, index, row, column);
}
void initializeMetaType(const QQmlAdaptorModel &model)
{
QMetaObjectBuilder builder;
setModelDataType<QQmlDMAbstractItemModelData>(&builder, this);
const QByteArray propertyType = QByteArrayLiteral("QVariant");
const QAbstractItemModel *aim = model.aim();
const QHash<int, QByteArray> names = aim ? aim->roleNames() : QHash<int, QByteArray>();
for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) {
const int propertyId = propertyRoles.count();
propertyRoles.append(it.key());
roleNames.insert(it.value(), it.key());
addProperty(&builder, propertyId, it.value(), propertyType);
}
if (propertyRoles.count() == 1) {
hasModelData = true;
const int role = names.begin().key();
const QByteArray propertyName = QByteArrayLiteral("modelData");
propertyRoles.append(role);
roleNames.insert(propertyName, role);
addProperty(&builder, 1, propertyName, propertyType);
}
metaObject.reset(builder.toMetaObject());
*static_cast<QMetaObject *>(this) = *metaObject;
propertyCache.adopt(new QQmlPropertyCache(metaObject.data(), model.modelItemRevision));
}
};
//-----------------------------------------------------------------
// QQmlListAccessor
//-----------------------------------------------------------------
class QQmlDMListAccessorData : public QQmlDelegateModelItem
{
Q_OBJECT
Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
public:
QQmlDMListAccessorData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
QQmlAdaptorModel::Accessors *accessor,
int index, int row, int column, const QVariant &value)
: QQmlDelegateModelItem(metaType, accessor, index, row, column)
, cachedData(value)
{
}
QVariant modelData() const
{
return cachedData;
}
void setModelData(const QVariant &data)
{
if (data == cachedData)
return;
cachedData = data;
emit modelDataChanged();
}
static QV4::ReturnedValue get_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::ExecutionEngine *v4 = b->engine();
const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>();
if (!o)
return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
return v4->fromVariant(static_cast<QQmlDMListAccessorData *>(o->d()->item)->cachedData);
}
static QV4::ReturnedValue set_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::ExecutionEngine *v4 = b->engine();
const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>();
if (!o)
return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
if (!argc)
return v4->throwTypeError();
static_cast<QQmlDMListAccessorData *>(o->d()->item)->setModelData(v4->toVariant(argv[0], QMetaType::UnknownType));
return QV4::Encode::undefined();
}
QV4::ReturnedValue get() override
{
QQmlAdaptorModelEngineData *data = engineData(v4);
QV4::Scope scope(v4);
QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(this));
QV4::ScopedObject p(scope, data->listItemProto.value());
o->setPrototypeOf(p);
++scriptRef;
return o.asReturnedValue();
}
void setValue(const QString &role, const QVariant &value) override
{
if (role == QLatin1String("modelData"))
cachedData = value;
}
bool resolveIndex(const QQmlAdaptorModel &model, int idx) override
{
if (index == -1) {
index = idx;
cachedData = model.list.at(idx);
emit modelIndexChanged();
emit modelDataChanged();
return true;
} else {
return false;
}
}
Q_SIGNALS:
void modelDataChanged();
private:
QVariant cachedData;
};
class VDMListDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors
{
public:
VDMListDelegateDataType()
: QQmlRefCount()
, QQmlAdaptorModel::Accessors()
{}
void cleanup(QQmlAdaptorModel &) const override
{
const_cast<VDMListDelegateDataType *>(this)->release();
}
int rowCount(const QQmlAdaptorModel &model) const override
{
return model.list.count();
}
int columnCount(const QQmlAdaptorModel &) const override
{
return 1;
}
QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
{
return role == QLatin1String("modelData")
? model.list.at(index)
: QVariant();
}
QQmlDelegateModelItem *createItem(
QQmlAdaptorModel &model,
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
int index, int row, int column) const override
{
VDMListDelegateDataType *dataType = const_cast<VDMListDelegateDataType *>(this);
if (!propertyCache) {
dataType->propertyCache.adopt(new QQmlPropertyCache(
&QQmlDMListAccessorData::staticMetaObject, model.modelItemRevision));
}
return new QQmlDMListAccessorData(
metaType,
dataType,
index, row, column,
index >= 0 && index < model.list.count() ? model.list.at(index) : QVariant());
}
bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override
{
for (auto modelItem : items) {
const int modelItemIndex = modelItem->index;
if (modelItemIndex < index || modelItemIndex >= index + count)
continue;
auto listModelItem = static_cast<QQmlDMListAccessorData *>(modelItem);
QVariant updatedModelData = model.list.at(listModelItem->index);
listModelItem->setModelData(updatedModelData);
}
return true;
}
};
//-----------------------------------------------------------------
// QObject
//-----------------------------------------------------------------
class VDMObjectDelegateDataType;
class QQmlDMObjectData : public QQmlDelegateModelItem, public QQmlAdaptorModelProxyInterface
{
Q_OBJECT
Q_PROPERTY(QObject *modelData READ modelData NOTIFY modelDataChanged)
Q_INTERFACES(QQmlAdaptorModelProxyInterface)
public:
QQmlDMObjectData(
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
VDMObjectDelegateDataType *dataType,
int index, int row, int column,
QObject *object);
void setModelData(QObject *modelData)
{
if (modelData == object)
return;
object = modelData;
emit modelDataChanged();
}
QObject *modelData() const { return object; }
QObject *proxiedObject() override { return object; }
QPointer<QObject> object;
Q_SIGNALS:
void modelDataChanged();
};
class VDMObjectDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors
{
public:
int propertyOffset;
int signalOffset;
bool shared;
QMetaObjectBuilder builder;
VDMObjectDelegateDataType()
: propertyOffset(0)
, signalOffset(0)
, shared(true)
{
}
VDMObjectDelegateDataType(const VDMObjectDelegateDataType &type)
: QQmlRefCount()
, QQmlAdaptorModel::Accessors()
, propertyOffset(type.propertyOffset)
, signalOffset(type.signalOffset)
, shared(false)
, builder(type.metaObject.data(), QMetaObjectBuilder::Properties
| QMetaObjectBuilder::Signals
| QMetaObjectBuilder::SuperClass
| QMetaObjectBuilder::ClassName)
{
builder.setFlags(QMetaObjectBuilder::DynamicMetaObject);
}
int rowCount(const QQmlAdaptorModel &model) const override
{
return model.list.count();
}
int columnCount(const QQmlAdaptorModel &) const override
{
return 1;
}
QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
{
if (QObject *object = model.list.at(index).value<QObject *>())
return object->property(role.toUtf8());
return QVariant();
}
QQmlDelegateModelItem *createItem(
QQmlAdaptorModel &model,
const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
int index, int row, int column) const override
{
VDMObjectDelegateDataType *dataType = const_cast<VDMObjectDelegateDataType *>(this);
if (!metaObject)
dataType->initializeMetaType(model);
return index >= 0 && index < model.list.count()
? new QQmlDMObjectData(metaType, dataType, index, row, column, qvariant_cast<QObject *>(model.list.at(index)))
: nullptr;
}
void initializeMetaType(QQmlAdaptorModel &model)
{
Q_UNUSED(model);
setModelDataType<QQmlDMObjectData>(&builder, this);
metaObject.reset(builder.toMetaObject());
// Note: ATM we cannot create a shared property cache for this class, since each model
// object can have different properties. And to make those properties available to the
// delegate, QQmlDMObjectData makes use of a QAbstractDynamicMetaObject subclass
// (QQmlDMObjectDataMetaObject), which we cannot represent in a QQmlPropertyCache.
// By not having a shared property cache, revisioned properties in QQmlDelegateModelItem
// will always be available to the delegate, regardless of the import version.
}
void cleanup(QQmlAdaptorModel &) const override
{
const_cast<VDMObjectDelegateDataType *>(this)->release();
}
bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override
{
for (auto modelItem : items) {
const int modelItemIndex = modelItem->index;
if (modelItemIndex < index || modelItemIndex >= index + count)
continue;
auto objectModelItem = static_cast<QQmlDMObjectData *>(modelItem);
QObject *updatedModelData = qvariant_cast<QObject *>(model.list.at(objectModelItem->index));
objectModelItem->setModelData(updatedModelData);
}
return true;
}
};
class QQmlDMObjectDataMetaObject : public QAbstractDynamicMetaObject
{
public:
QQmlDMObjectDataMetaObject(QQmlDMObjectData *data, VDMObjectDelegateDataType *type)
: m_data(data)
, m_type(type)
{
QObjectPrivate *op = QObjectPrivate::get(m_data);
*static_cast<QMetaObject *>(this) = *type->metaObject;
op->metaObject = this;
m_type->addref();
}
~QQmlDMObjectDataMetaObject()
{
m_type->release();
}
int metaCall(QObject *o, QMetaObject::Call call, int id, void **arguments) override
{
Q_ASSERT(o == m_data);
Q_UNUSED(o);
static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount();
if (id >= m_type->propertyOffset
&& (call == QMetaObject::ReadProperty
|| call == QMetaObject::WriteProperty
|| call == QMetaObject::ResetProperty)) {
if (m_data->object)
QMetaObject::metacall(m_data->object, call, id - m_type->propertyOffset + objectPropertyOffset, arguments);
return -1;
} else if (id >= m_type->signalOffset && call == QMetaObject::InvokeMetaMethod) {
QMetaObject::activate(m_data, this, id - m_type->signalOffset, nullptr);
return -1;
} else {
return m_data->qt_metacall(call, id, arguments);
}
}
int createProperty(const char *name, const char *) override
{
if (!m_data->object)
return -1;
const QMetaObject *metaObject = m_data->object->metaObject();
static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount();
const int previousPropertyCount = propertyCount() - propertyOffset();
int propertyIndex = metaObject->indexOfProperty(name);
if (propertyIndex == -1)
return -1;
if (previousPropertyCount + objectPropertyOffset == metaObject->propertyCount())
return propertyIndex + m_type->propertyOffset - objectPropertyOffset;
if (m_type->shared) {
VDMObjectDelegateDataType *type = m_type;
m_type = new VDMObjectDelegateDataType(*m_type);
type->release();
}
const int previousMethodCount = methodCount();
int notifierId = previousMethodCount - methodOffset();
for (int propertyId = previousPropertyCount; propertyId < metaObject->propertyCount() - objectPropertyOffset; ++propertyId) {
QMetaProperty property = metaObject->property(propertyId + objectPropertyOffset);
QMetaPropertyBuilder propertyBuilder;
if (property.hasNotifySignal()) {
m_type->builder.addSignal("__" + QByteArray::number(propertyId) + "()");
propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName(), notifierId);
++notifierId;
} else {
propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName());
}
propertyBuilder.setWritable(property.isWritable());
propertyBuilder.setResettable(property.isResettable());
propertyBuilder.setConstant(property.isConstant());
}
m_type->metaObject.reset(m_type->builder.toMetaObject());
*static_cast<QMetaObject *>(this) = *m_type->metaObject;
notifierId = previousMethodCount;
for (int i = previousPropertyCount; i < metaObject->propertyCount() - objectPropertyOffset; ++i) {
QMetaProperty property = metaObject->property(i + objectPropertyOffset);
if (property.hasNotifySignal()) {
QQmlPropertyPrivate::connect(
m_data->object, property.notifySignalIndex(), m_data, notifierId);
++notifierId;
}
}
return propertyIndex + m_type->propertyOffset - objectPropertyOffset;
}
QQmlDMObjectData *m_data;
VDMObjectDelegateDataType *m_type;
};
QQmlDMObjectData::QQmlDMObjectData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
VDMObjectDelegateDataType *dataType,
int index, int row, int column,
QObject *object)
: QQmlDelegateModelItem(metaType, dataType, index, row, column)
, object(object)
{
new QQmlDMObjectDataMetaObject(this, dataType);
}
//-----------------------------------------------------------------
// QQmlAdaptorModel
//-----------------------------------------------------------------
static const QQmlAdaptorModel::Accessors qt_vdm_null_accessors;
QQmlAdaptorModel::Accessors::~Accessors()
{
}
QQmlAdaptorModel::QQmlAdaptorModel()
: accessors(&qt_vdm_null_accessors)
{
}
QQmlAdaptorModel::~QQmlAdaptorModel()
{
accessors->cleanup(*this);
}
void QQmlAdaptorModel::setModel(const QVariant &variant, QObject *parent, QQmlEngine *engine)
{
accessors->cleanup(*this);
list.setList(variant, engine);
if (QObject *object = qvariant_cast<QObject *>(list.list())) {
setObject(object, parent);
if (qobject_cast<QAbstractItemModel *>(object))
accessors = new VDMAbstractItemModelDataType(this);
else
accessors = new VDMObjectDelegateDataType;
} else if (list.type() == QQmlListAccessor::ListProperty) {
setObject(static_cast<const QQmlListReference *>(variant.constData())->object(), parent);
accessors = new VDMObjectDelegateDataType;
} else if (list.type() == QQmlListAccessor::ObjectList) {
setObject(nullptr, parent);
accessors = new VDMObjectDelegateDataType;
} else if (list.type() != QQmlListAccessor::Invalid
&& list.type() != QQmlListAccessor::Instance) { // Null QObject
setObject(nullptr, parent);
accessors = new VDMListDelegateDataType;
} else {
setObject(nullptr, parent);
accessors = &qt_vdm_null_accessors;
}
}
void QQmlAdaptorModel::invalidateModel()
{
accessors->cleanup(*this);
accessors = &qt_vdm_null_accessors;
// Don't clear the model object as we still need the guard to clear the list variant if the
// object is destroyed.
}
bool QQmlAdaptorModel::isValid() const
{
return accessors != &qt_vdm_null_accessors;
}
int QQmlAdaptorModel::count() const
{
return rowCount() * columnCount();
}
int QQmlAdaptorModel::rowCount() const
{
return qMax(0, accessors->rowCount(*this));
}
int QQmlAdaptorModel::columnCount() const
{
return qMax(0, accessors->columnCount(*this));
}
int QQmlAdaptorModel::rowAt(int index) const
{
int count = rowCount();
return count <= 0 ? -1 : index % count;
}
int QQmlAdaptorModel::columnAt(int index) const
{
int count = rowCount();
return count <= 0 ? -1 : index / count;
}
int QQmlAdaptorModel::indexAt(int row, int column) const
{
return column * rowCount() + row;
}
void QQmlAdaptorModel::useImportVersion(int minorVersion)
{
modelItemRevision = minorVersion;
}
void QQmlAdaptorModel::objectDestroyed(QObject *)
{
setModel(QVariant(), nullptr, nullptr);
}
QQmlAdaptorModelEngineData::QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4)
: v4(v4)
{
QV4::Scope scope(v4);
QV4::ScopedObject proto(scope, v4->newObject());
proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
proto->defineAccessorProperty(QStringLiteral("modelData"),
QQmlDMListAccessorData::get_modelData, QQmlDMListAccessorData::set_modelData);
listItemProto.set(v4, proto);
}
QQmlAdaptorModelEngineData::~QQmlAdaptorModelEngineData()
{
}
QT_END_NAMESPACE
#include <qqmladaptormodel.moc>