Models: Straighten out model and modelData properties
If there is more than one role, just return the whole model item as modelData. This makes sure we can always require modelData. That's a basic precondition for writing delegates that work with any model. The test shows that model and modelData behave quite erratically in the different cases, but much of this cannot be changed anymore. At least they are now both available in all cases. Furthermore, provide modelData as anonymous property of model. This way, if you have a model that can be singular, and a role that will be an empty string if the model is singular, you can just write: SomeDelegate { required property var model someData: model[role] } Task-number: QTBUG-111176 Task-number: QTBUG-110980 Task-number: QTBUG-104752 Change-Id: Ie200be467df2098d817b85b03d2409267722b596 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
parent
afa15792eb
commit
e9f650cad8
|
@ -177,7 +177,7 @@ void Object::defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, V
|
||||||
QV4::Scope scope(v4);
|
QV4::Scope scope(v4);
|
||||||
ScopedProperty p(scope);
|
ScopedProperty p(scope);
|
||||||
QString n = name->toQString();
|
QString n = name->toQString();
|
||||||
if (n.at(0) == QLatin1Char('@'))
|
if (!n.isEmpty() && n.at(0) == QLatin1Char('@'))
|
||||||
n = QChar::fromLatin1('[') + QStringView{n}.mid(1) + QChar::fromLatin1(']');
|
n = QChar::fromLatin1('[') + QStringView{n}.mid(1) + QChar::fromLatin1(']');
|
||||||
if (getter) {
|
if (getter) {
|
||||||
ScopedString getName(scope, v4->newString(QString::fromLatin1("get ") + n));
|
ScopedString getName(scope, v4->newString(QString::fromLatin1("get ") + n));
|
||||||
|
|
|
@ -12,7 +12,11 @@ QQmlAdaptorModelEngineData::QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4)
|
||||||
QV4::Scope scope(v4);
|
QV4::Scope scope(v4);
|
||||||
QV4::ScopedObject proto(scope, v4->newObject());
|
QV4::ScopedObject proto(scope, v4->newObject());
|
||||||
proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
|
proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
|
||||||
proto->defineAccessorProperty(QStringLiteral("modelData"),
|
proto->defineAccessorProperty(
|
||||||
|
QStringLiteral("modelData"),
|
||||||
|
QQmlDMListAccessorData::get_modelData, QQmlDMListAccessorData::set_modelData);
|
||||||
|
proto->defineAccessorProperty(
|
||||||
|
QString(),
|
||||||
QQmlDMListAccessorData::get_modelData, QQmlDMListAccessorData::set_modelData);
|
QQmlDMListAccessorData::get_modelData, QQmlDMListAccessorData::set_modelData);
|
||||||
listItemProto.set(v4, proto);
|
listItemProto.set(v4, proto);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ QQmlDMAbstractItemModelData::QQmlDMAbstractItemModelData(
|
||||||
, m_type(dataType)
|
, m_type(dataType)
|
||||||
{
|
{
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
m_cachedData.resize(m_type->hasModelData ? 1 : m_type->propertyRoles.size());
|
m_cachedData.resize(m_type->propertyRoles.size());
|
||||||
|
|
||||||
QObjectPrivate::get(this)->metaObject = m_type;
|
QObjectPrivate::get(this)->metaObject = m_type;
|
||||||
|
|
||||||
|
@ -24,29 +24,28 @@ int QQmlDMAbstractItemModelData::metaCall(QMetaObject::Call call, int id, void *
|
||||||
if (call == QMetaObject::ReadProperty && id >= m_type->propertyOffset) {
|
if (call == QMetaObject::ReadProperty && id >= m_type->propertyOffset) {
|
||||||
const int propertyIndex = id - m_type->propertyOffset;
|
const int propertyIndex = id - m_type->propertyOffset;
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
if (!m_cachedData.isEmpty()) {
|
if (!m_cachedData.isEmpty())
|
||||||
*static_cast<QVariant *>(arguments[0]) = m_cachedData.at(
|
*static_cast<QVariant *>(arguments[0]) = m_cachedData.at(propertyIndex);
|
||||||
m_type->hasModelData ? 0 : propertyIndex);
|
|
||||||
}
|
|
||||||
} else if (*m_type->model) {
|
} else if (*m_type->model) {
|
||||||
*static_cast<QVariant *>(arguments[0]) = value(m_type->propertyRoles.at(propertyIndex));
|
*static_cast<QVariant *>(arguments[0]) = value(m_type->propertyRoles.at(propertyIndex));
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
} else if (call == QMetaObject::WriteProperty && id >= m_type->propertyOffset) {
|
} else if (call == QMetaObject::WriteProperty && id >= m_type->propertyOffset) {
|
||||||
const int propertyIndex = id - m_type->propertyOffset;
|
const int propertyIndex = id - m_type->propertyOffset;
|
||||||
if (index == -1) {
|
|
||||||
const QMetaObject *meta = metaObject();
|
const QMetaObject *meta = metaObject();
|
||||||
|
if (index == -1) {
|
||||||
if (m_cachedData.size() > 1) {
|
if (m_cachedData.size() > 1) {
|
||||||
m_cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]);
|
m_cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]);
|
||||||
QMetaObject::activate(this, meta, propertyIndex, nullptr);
|
QMetaObject::activate(this, meta, propertyIndex, nullptr);
|
||||||
} else if (m_cachedData.size() == 1) {
|
} else if (m_cachedData.size() == 1) {
|
||||||
m_cachedData[0] = *static_cast<QVariant *>(arguments[0]);
|
m_cachedData[0] = *static_cast<QVariant *>(arguments[0]);
|
||||||
QMetaObject::activate(this, meta, 0, nullptr);
|
QMetaObject::activate(this, meta, 0, nullptr);
|
||||||
QMetaObject::activate(this, meta, 1, nullptr);
|
|
||||||
}
|
}
|
||||||
} else if (*m_type->model) {
|
} else if (*m_type->model) {
|
||||||
setValue(m_type->propertyRoles.at(propertyIndex), *static_cast<QVariant *>(arguments[0]));
|
setValue(m_type->propertyRoles.at(propertyIndex), *static_cast<QVariant *>(arguments[0]));
|
||||||
|
QMetaObject::activate(this, meta, propertyIndex, nullptr);
|
||||||
}
|
}
|
||||||
|
emit modelDataChanged();
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return qt_metacall(call, id, arguments);
|
return qt_metacall(call, id, arguments);
|
||||||
|
@ -55,6 +54,15 @@ int QQmlDMAbstractItemModelData::metaCall(QMetaObject::Call call, int id, void *
|
||||||
|
|
||||||
void QQmlDMAbstractItemModelData::setValue(const QString &role, const QVariant &value)
|
void QQmlDMAbstractItemModelData::setValue(const QString &role, const QVariant &value)
|
||||||
{
|
{
|
||||||
|
// Used only for initialization of the cached data. Does not have to emit change signals.
|
||||||
|
|
||||||
|
if (m_type->propertyRoles.size() == 1
|
||||||
|
&& (role.isEmpty() || role == QLatin1String("modelData"))) {
|
||||||
|
// If the model has only a single role, the modelData is that role.
|
||||||
|
m_cachedData[0] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QHash<QByteArray, int>::iterator it = m_type->roleNames.find(role.toUtf8());
|
QHash<QByteArray, int>::iterator it = m_type->roleNames.find(role.toUtf8());
|
||||||
if (it != m_type->roleNames.end()) {
|
if (it != m_type->roleNames.end()) {
|
||||||
for (int i = 0; i < m_type->propertyRoles.size(); ++i) {
|
for (int i = 0; i < m_type->propertyRoles.size(); ++i) {
|
||||||
|
@ -76,6 +84,7 @@ bool QQmlDMAbstractItemModelData::resolveIndex(const QQmlAdaptorModel &adaptorMo
|
||||||
const int propertyCount = m_type->propertyRoles.size();
|
const int propertyCount = m_type->propertyRoles.size();
|
||||||
for (int i = 0; i < propertyCount; ++i)
|
for (int i = 0; i < propertyCount; ++i)
|
||||||
QMetaObject::activate(this, meta, i, nullptr);
|
QMetaObject::activate(this, meta, i, nullptr);
|
||||||
|
emit modelDataChanged();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -93,10 +102,8 @@ QV4::ReturnedValue QQmlDMAbstractItemModelData::get_property(const QV4::Function
|
||||||
|
|
||||||
QQmlDMAbstractItemModelData *modelData = static_cast<QQmlDMAbstractItemModelData *>(o->d()->item);
|
QQmlDMAbstractItemModelData *modelData = static_cast<QQmlDMAbstractItemModelData *>(o->d()->item);
|
||||||
if (o->d()->item->index == -1) {
|
if (o->d()->item->index == -1) {
|
||||||
if (!modelData->m_cachedData.isEmpty()) {
|
if (!modelData->m_cachedData.isEmpty())
|
||||||
return scope.engine->fromVariant(
|
return scope.engine->fromVariant(modelData->m_cachedData.at(propertyId));
|
||||||
modelData->m_cachedData.at(modelData->m_type->hasModelData ? 0 : propertyId));
|
|
||||||
}
|
|
||||||
} else if (*modelData->m_type->model) {
|
} else if (*modelData->m_type->model) {
|
||||||
return scope.engine->fromVariant(
|
return scope.engine->fromVariant(
|
||||||
modelData->value(modelData->m_type->propertyRoles.at(propertyId)));
|
modelData->value(modelData->m_type->propertyRoles.at(propertyId)));
|
||||||
|
@ -125,13 +132,82 @@ QV4::ReturnedValue QQmlDMAbstractItemModelData::set_property(const QV4::Function
|
||||||
} else if (modelData->m_cachedData.size() == 1) {
|
} else if (modelData->m_cachedData.size() == 1) {
|
||||||
modelData->m_cachedData[0] = QV4::ExecutionEngine::toVariant(argv[0], QMetaType {});
|
modelData->m_cachedData[0] = QV4::ExecutionEngine::toVariant(argv[0], QMetaType {});
|
||||||
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 0, nullptr);
|
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 0, nullptr);
|
||||||
QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 1, nullptr);
|
|
||||||
}
|
}
|
||||||
|
emit modelData->modelDataChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QV4::Encode::undefined();
|
return QV4::Encode::undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QV4::ReturnedValue QQmlDMAbstractItemModelData::get_modelData(
|
||||||
|
const QV4::FunctionObject *b, const QV4::Value *thisObject,
|
||||||
|
const QV4::Value *argv, int argc)
|
||||||
|
{
|
||||||
|
Q_UNUSED(argv)
|
||||||
|
Q_UNUSED(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"));
|
||||||
|
|
||||||
|
return scope.engine->fromVariant(
|
||||||
|
static_cast<QQmlDMAbstractItemModelData *>(o->d()->item)->modelData());
|
||||||
|
}
|
||||||
|
|
||||||
|
QV4::ReturnedValue QQmlDMAbstractItemModelData::set_modelData(
|
||||||
|
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();
|
||||||
|
|
||||||
|
static_cast<QQmlDMAbstractItemModelData *>(o->d()->item)->setModelData(
|
||||||
|
QV4::ExecutionEngine::toVariant(argv[0], QMetaType()));
|
||||||
|
|
||||||
|
return QV4::Encode::undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant QQmlDMAbstractItemModelData::modelData() const
|
||||||
|
{
|
||||||
|
if (m_type->propertyRoles.size() == 1) {
|
||||||
|
// If the model has only a single role, the modelData is that role.
|
||||||
|
return index == -1
|
||||||
|
? m_cachedData.isEmpty() ? QVariant() : m_cachedData[0]
|
||||||
|
: value(m_type->propertyRoles[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no context object, we are using required properties.
|
||||||
|
// In that case, return the object itself as modelData.
|
||||||
|
return contextData->contextObject() ? QVariant() : QVariant::fromValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QQmlDMAbstractItemModelData::setModelData(const QVariant &modelData)
|
||||||
|
{
|
||||||
|
if (m_type->propertyRoles.size() != 1) {
|
||||||
|
qWarning() << "Cannot overwrite model object";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the model has only a single role, the modelData is that role.
|
||||||
|
if (index == -1) {
|
||||||
|
if (m_cachedData.isEmpty())
|
||||||
|
m_cachedData.append(modelData);
|
||||||
|
else
|
||||||
|
m_cachedData[0] = modelData;
|
||||||
|
} else {
|
||||||
|
setValue(m_type->propertyRoles[0], modelData);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::activate(this, metaObject(), 0, nullptr);
|
||||||
|
emit modelDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
bool QQmlDMAbstractItemModelData::hasModelChildren() const
|
bool QQmlDMAbstractItemModelData::hasModelChildren() const
|
||||||
{
|
{
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <private/qqmladaptormodelenginedata_p.h>
|
#include <private/qqmladaptormodelenginedata_p.h>
|
||||||
#include <private/qqmldelegatemodel_p_p.h>
|
#include <private/qqmldelegatemodel_p_p.h>
|
||||||
|
#include <private/qobject_p.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
@ -25,6 +26,8 @@ class QQmlDMAbstractItemModelData : public QQmlDelegateModelItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT)
|
Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT)
|
||||||
|
Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
|
||||||
|
QT_ANONYMOUS_PROPERTY(QVariant READ modelData NOTIFY modelDataChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QQmlDMAbstractItemModelData(
|
QQmlDMAbstractItemModelData(
|
||||||
|
@ -39,11 +42,28 @@ public:
|
||||||
void setValue(const QString &role, const QVariant &value) override;
|
void setValue(const QString &role, const QVariant &value) override;
|
||||||
bool resolveIndex(const QQmlAdaptorModel &model, int idx) 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 get_property(
|
||||||
static QV4::ReturnedValue set_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
|
const QV4::FunctionObject *b, const QV4::Value *thisObject,
|
||||||
|
const QV4::Value *argv, int argc);
|
||||||
|
static QV4::ReturnedValue set_property(
|
||||||
|
const QV4::FunctionObject *b, const QV4::Value *thisObject,
|
||||||
|
const QV4::Value *argv, int argc);
|
||||||
|
|
||||||
|
static QV4::ReturnedValue get_modelData(
|
||||||
|
const QV4::FunctionObject *b, const QV4::Value *thisObject,
|
||||||
|
const QV4::Value *argv, int argc);
|
||||||
|
static QV4::ReturnedValue set_modelData(
|
||||||
|
const QV4::FunctionObject *b, const QV4::Value *thisObject,
|
||||||
|
const QV4::Value *argv, int argc);
|
||||||
|
|
||||||
|
QVariant modelData() const;
|
||||||
|
void setModelData(const QVariant &modelData);
|
||||||
|
|
||||||
const VDMAbstractItemModelDataType *type() const { return m_type; }
|
const VDMAbstractItemModelDataType *type() const { return m_type; }
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void modelDataChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVariant value(int role) const;
|
QVariant value(int role) const;
|
||||||
void setValue(int role, const QVariant &value);
|
void setValue(int role, const QVariant &value);
|
||||||
|
@ -62,7 +82,6 @@ public:
|
||||||
: model(model)
|
: model(model)
|
||||||
, propertyOffset(0)
|
, propertyOffset(0)
|
||||||
, signalOffset(0)
|
, signalOffset(0)
|
||||||
, hasModelData(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +120,11 @@ public:
|
||||||
signalIndexes.append(propertyId + signalOffset);
|
signalIndexes.append(propertyId + signalOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVarLengthArray<QQmlGuard<QQmlDelegateModelItem>> guardedItems;
|
QVarLengthArray<QQmlGuard<QQmlDMAbstractItemModelData>> guardedItems;
|
||||||
for (const auto item : items)
|
for (const auto item : items) {
|
||||||
guardedItems.append(item);
|
Q_ASSERT(qobject_cast<QQmlDMAbstractItemModelData *>(item) == item);
|
||||||
|
guardedItems.append(static_cast<QQmlDMAbstractItemModelData *>(item));
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &item : std::as_const(guardedItems)) {
|
for (const auto &item : std::as_const(guardedItems)) {
|
||||||
if (item.isNull())
|
if (item.isNull())
|
||||||
|
@ -113,6 +134,7 @@ public:
|
||||||
if (idx >= index && idx < index + count) {
|
if (idx >= index && idx < index + count) {
|
||||||
for (int i = 0; i < signalIndexes.size(); ++i)
|
for (int i = 0; i < signalIndexes.size(); ++i)
|
||||||
QMetaObject::activate(item, signalIndexes.at(i), nullptr);
|
QMetaObject::activate(item, signalIndexes.at(i), nullptr);
|
||||||
|
emit item->modelDataChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
|
@ -155,6 +177,9 @@ public:
|
||||||
QV4::ScopedObject proto(scope, v4->newObject());
|
QV4::ScopedObject proto(scope, v4->newObject());
|
||||||
proto->defineAccessorProperty(QStringLiteral("index"), QQmlAdaptorModelEngineData::get_index, nullptr);
|
proto->defineAccessorProperty(QStringLiteral("index"), QQmlAdaptorModelEngineData::get_index, nullptr);
|
||||||
proto->defineAccessorProperty(QStringLiteral("hasModelChildren"), get_hasModelChildren, nullptr);
|
proto->defineAccessorProperty(QStringLiteral("hasModelChildren"), get_hasModelChildren, nullptr);
|
||||||
|
proto->defineAccessorProperty(QStringLiteral("modelData"),
|
||||||
|
QQmlDMAbstractItemModelData::get_modelData,
|
||||||
|
QQmlDMAbstractItemModelData::set_modelData);
|
||||||
QV4::ScopedProperty p(scope);
|
QV4::ScopedProperty p(scope);
|
||||||
|
|
||||||
typedef QHash<QByteArray, int>::const_iterator iterator;
|
typedef QHash<QByteArray, int>::const_iterator iterator;
|
||||||
|
@ -212,15 +237,25 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const QAbstractItemModel *aim = model.aim()) {
|
if (const QAbstractItemModel *aim = model.aim()) {
|
||||||
QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8());
|
const QModelIndex modelIndex
|
||||||
if (it != roleNames.end()) {
|
= aim->index(model.rowAt(index), model.columnAt(index), model.rootIndex);
|
||||||
return aim->index(model.rowAt(index), model.columnAt(index),
|
|
||||||
model.rootIndex).data(*it);
|
const auto it = roleNames.find(role.toUtf8()), end = roleNames.end();
|
||||||
} else if (role == QLatin1String("hasModelChildren")) {
|
if (it != roleNames.end())
|
||||||
return QVariant(aim->hasChildren(aim->index(model.rowAt(index),
|
return modelIndex.data(*it);
|
||||||
model.columnAt(index),
|
|
||||||
model.rootIndex)));
|
if (role.isEmpty() || role == QLatin1String("modelData")) {
|
||||||
|
if (roleNames.size() == 1)
|
||||||
|
return modelIndex.data(roleNames.begin().value());
|
||||||
|
|
||||||
|
QVariantMap modelData;
|
||||||
|
for (auto jt = roleNames.begin(); jt != end; ++jt)
|
||||||
|
modelData.insert(QString::fromUtf8(jt.key()), modelIndex.data(jt.value()));
|
||||||
|
return modelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == QLatin1String("hasModelChildren"))
|
||||||
|
return QVariant(aim->hasChildren(modelIndex));
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -278,16 +313,6 @@ public:
|
||||||
QQmlAdaptorModelEngineData::addProperty(&builder, propertyId, it.value(), propertyType);
|
QQmlAdaptorModelEngineData::addProperty(&builder, propertyId, it.value(), propertyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyRoles.size() == 1) {
|
|
||||||
hasModelData = true;
|
|
||||||
const int role = names.begin().key();
|
|
||||||
const QByteArray propertyName = QByteArrayLiteral("modelData");
|
|
||||||
|
|
||||||
propertyRoles.append(role);
|
|
||||||
roleNames.insert(propertyName, role);
|
|
||||||
QQmlAdaptorModelEngineData::addProperty(&builder, 1, propertyName, propertyType);
|
|
||||||
}
|
|
||||||
|
|
||||||
metaObject.reset(builder.toMetaObject());
|
metaObject.reset(builder.toMetaObject());
|
||||||
*static_cast<QMetaObject *>(this) = *metaObject;
|
*static_cast<QMetaObject *>(this) = *metaObject;
|
||||||
propertyCache = QQmlPropertyCache::createStandalone(
|
propertyCache = QQmlPropertyCache::createStandalone(
|
||||||
|
@ -302,7 +327,6 @@ public:
|
||||||
QQmlAdaptorModel *model;
|
QQmlAdaptorModel *model;
|
||||||
int propertyOffset;
|
int propertyOffset;
|
||||||
int signalOffset;
|
int signalOffset;
|
||||||
bool hasModelData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <private/qqmladaptormodelenginedata_p.h>
|
#include <private/qqmladaptormodelenginedata_p.h>
|
||||||
#include <private/qqmldelegatemodel_p_p.h>
|
#include <private/qqmldelegatemodel_p_p.h>
|
||||||
|
#include <private/qobject_p.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ class QQmlDMListAccessorData : public QQmlDelegateModelItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
|
Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
|
||||||
|
QT_ANONYMOUS_PROPERTY(QVariant READ modelData WRITE setModelData NOTIFY modelDataChanged)
|
||||||
public:
|
public:
|
||||||
QQmlDMListAccessorData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
|
QQmlDMListAccessorData(const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
|
||||||
QQmlAdaptorModel::Accessors *accessor,
|
QQmlAdaptorModel::Accessors *accessor,
|
||||||
|
@ -84,7 +86,7 @@ public:
|
||||||
|
|
||||||
void setValue(const QString &role, const QVariant &value) override
|
void setValue(const QString &role, const QVariant &value) override
|
||||||
{
|
{
|
||||||
if (role == QLatin1String("modelData"))
|
if (role == QLatin1String("modelData") || role.isEmpty())
|
||||||
cachedData = value;
|
cachedData = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +140,7 @@ public:
|
||||||
QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
|
QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
|
||||||
{
|
{
|
||||||
const QVariant entry = model.list.at(index);
|
const QVariant entry = model.list.at(index);
|
||||||
if (role == QLatin1String("modelData"))
|
if (role == QLatin1String("modelData") || role.isEmpty())
|
||||||
return entry;
|
return entry;
|
||||||
|
|
||||||
const QMetaType type = entry.metaType();
|
const QMetaType type = entry.metaType();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <private/qqmladaptormodelenginedata_p.h>
|
#include <private/qqmladaptormodelenginedata_p.h>
|
||||||
#include <private/qqmldelegatemodel_p_p.h>
|
#include <private/qqmldelegatemodel_p_p.h>
|
||||||
|
#include <private/qobject_p.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ class QQmlDMObjectData : public QQmlDelegateModelItem, public QQmlAdaptorModelPr
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QObject *modelData READ modelData NOTIFY modelDataChanged)
|
Q_PROPERTY(QObject *modelData READ modelData NOTIFY modelDataChanged)
|
||||||
|
QT_ANONYMOUS_PROPERTY(QObject * READ modelData NOTIFY modelDataChanged)
|
||||||
Q_INTERFACES(QQmlAdaptorModelProxyInterface)
|
Q_INTERFACES(QQmlAdaptorModelProxyInterface)
|
||||||
public:
|
public:
|
||||||
QQmlDMObjectData(
|
QQmlDMObjectData(
|
||||||
|
|
|
@ -188,12 +188,71 @@ To visualize data, bind the view's \c model property to a model and the
|
||||||
possible to delay delegate destruction in some views via a \c delayRemove
|
possible to delay delegate destruction in some views via a \c delayRemove
|
||||||
attached property.)
|
attached property.)
|
||||||
|
|
||||||
Models that do not have named roles (such as the ListModel shown
|
Remember that you can use integers or arrays as model:
|
||||||
below) will have the data provided via the \e modelData role. The \e
|
|
||||||
modelData role is also provided for models that have only one role. In this
|
|
||||||
case the \e modelData role contains the same data as the named role.
|
|
||||||
|
|
||||||
\note \e model, \e index, and \e modelData roles are not accessible
|
\qml
|
||||||
|
Repeater {
|
||||||
|
model: 5
|
||||||
|
Text {
|
||||||
|
required property int modelData
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\endqml
|
||||||
|
|
||||||
|
\qml
|
||||||
|
Repeater {
|
||||||
|
model: ["one", "two", "three"]
|
||||||
|
Text {
|
||||||
|
required property string modelData
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\endqml
|
||||||
|
|
||||||
|
Such models provide a singular, anonymous piece of data to each instance
|
||||||
|
of the delegate. Accessing this piece of data is the primary reason to
|
||||||
|
use \e modelData, but other models also provide \e modelData.
|
||||||
|
|
||||||
|
The object provided via the \e model role has a property with an empty name.
|
||||||
|
This anonymous property holds the \e modelData. Furthermore, the object
|
||||||
|
provided via the \e model role has another property called \e modelData.
|
||||||
|
This property is deprecated and also holds the \e modelData.
|
||||||
|
|
||||||
|
In addition to the \e model role, a \e modelData role is provided. The
|
||||||
|
\e modelData role holds the same data as the \e modelData property and the
|
||||||
|
anonymous property of the object provided via the \e model role.
|
||||||
|
|
||||||
|
The differences between the \e model role and the various means to access
|
||||||
|
\e modelData are as follows:
|
||||||
|
|
||||||
|
\list
|
||||||
|
\li Models that do not have named roles (such as integers or an array of
|
||||||
|
strings) have their data provided via the \e modelData role. The
|
||||||
|
\e modelData role does not necessarily contain an object in this case.
|
||||||
|
In the case of an integer model it would contain an integer (the index
|
||||||
|
of the current model item). In the case of an array of strings it would
|
||||||
|
contain a string. The \e model role still contains an object, but
|
||||||
|
without any properties for named roles. \e model still contains its
|
||||||
|
usual \e modelData and anonymous properties, though.
|
||||||
|
\li If the model has only one named role, the \e modelData role contains
|
||||||
|
the same data as the named role. It is not necessarily an object and it
|
||||||
|
does not contain the named role as a named property the way it usually
|
||||||
|
would. The \e model role still contains an object with the named role as
|
||||||
|
property, and the \e modelData and anonymous properties in this case.
|
||||||
|
\li For models with multiple roles, the \e modelData role is only provided as
|
||||||
|
a required property, not as a context property. This is due to backwards
|
||||||
|
compatibility with older versions of Qt.
|
||||||
|
\endlist
|
||||||
|
|
||||||
|
The anonymous property on \e model allows you to cleanly write delegates
|
||||||
|
that receive both their model data and the role name they should react
|
||||||
|
to as properties from the outside. You can provide a model without or
|
||||||
|
with only one named role, and an empty string as role. Then, a binding that
|
||||||
|
simply accesses \c{model[role]} will do what you expect. You don't have to
|
||||||
|
add special code for this case.
|
||||||
|
|
||||||
|
\note The \e model, \e index, and \e modelData roles are not accessible
|
||||||
if the delegate contains required properties, unless it has also required
|
if the delegate contains required properties, unless it has also required
|
||||||
properties with matching names.
|
properties with matching names.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
DelegateModel {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// useful object as model, string as modelData
|
||||||
|
property ListModel singularModel: ListModel {
|
||||||
|
ListElement {
|
||||||
|
a: "a"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
a: "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same, useful, object as model and modelData
|
||||||
|
property ListModel listModel: ListModel {
|
||||||
|
ListElement {
|
||||||
|
a: "a"
|
||||||
|
b: "a"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
a: "b"
|
||||||
|
b: "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// useful object as modelData
|
||||||
|
// useless object as model because the list accessor cannot deal with it yet.
|
||||||
|
property var array: [
|
||||||
|
{a: "a", b: "a"}, {a: "b", b: "a"}
|
||||||
|
]
|
||||||
|
|
||||||
|
// string as modelData
|
||||||
|
// object with anonymous string property as model.
|
||||||
|
property var stringList: ["a", "b"]
|
||||||
|
|
||||||
|
// useful but different objects as modelData and model
|
||||||
|
// This is how the object accessor works. We can live with it.
|
||||||
|
property QtObject object: QtObject {
|
||||||
|
property string a: "a"
|
||||||
|
property string b: "a"
|
||||||
|
}
|
||||||
|
|
||||||
|
// number as modelData
|
||||||
|
// object with anonymous number property as model
|
||||||
|
property int n: -1
|
||||||
|
|
||||||
|
model: {
|
||||||
|
switch (n) {
|
||||||
|
case 0: return singularModel
|
||||||
|
case 1: return listModel
|
||||||
|
case 2: return array
|
||||||
|
case 3: return stringList
|
||||||
|
case 4: return object
|
||||||
|
case 5: return n
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: QtObject {
|
||||||
|
required property var modelData
|
||||||
|
required property var model
|
||||||
|
|
||||||
|
property var modelA: model.a
|
||||||
|
property var modelDataA: modelData.a
|
||||||
|
property var modelSelf: model
|
||||||
|
property var modelDataSelf: modelData
|
||||||
|
property var modelModelData: model.modelData
|
||||||
|
property var modelAnonymous: model[""]
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ private slots:
|
||||||
void contextAccessedByHandler();
|
void contextAccessedByHandler();
|
||||||
void redrawUponColumnChange();
|
void redrawUponColumnChange();
|
||||||
void nestedDelegates();
|
void nestedDelegates();
|
||||||
|
void universalModelData();
|
||||||
};
|
};
|
||||||
|
|
||||||
class AbstractItemModel : public QAbstractItemModel
|
class AbstractItemModel : public QAbstractItemModel
|
||||||
|
@ -264,6 +265,100 @@ void tst_QQmlDelegateModel::nestedDelegates()
|
||||||
QFAIL("Loader not found");
|
QFAIL("Loader not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QQmlDelegateModel::universalModelData()
|
||||||
|
{
|
||||||
|
QQmlEngine engine;
|
||||||
|
QQmlComponent c(&engine, testFileUrl("universalModelData.qml"));
|
||||||
|
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
|
||||||
|
QScopedPointer<QObject> o(c.create());
|
||||||
|
|
||||||
|
QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data());
|
||||||
|
QVERIFY(delegateModel);
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
delegateModel->setProperty("n", i);
|
||||||
|
QObject *delegate = delegateModel->object(0);
|
||||||
|
QObject *modelItem = delegate->property("modelSelf").value<QObject *>();
|
||||||
|
QVERIFY(modelItem != nullptr);
|
||||||
|
switch (i) {
|
||||||
|
case 0: {
|
||||||
|
// list model with 1 role
|
||||||
|
QCOMPARE(delegate->property("modelA"), QStringLiteral("a"));
|
||||||
|
QVERIFY(!delegate->property("modelDataA").isValid());
|
||||||
|
QCOMPARE(delegate->property("modelDataSelf"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelModelData"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous"), QStringLiteral("a"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// list model with 2 roles
|
||||||
|
QCOMPARE(delegate->property("modelA"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelDataSelf"), QVariant::fromValue(modelItem));
|
||||||
|
QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelItem));
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelItem));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
// JS array of objects
|
||||||
|
QEXPECT_FAIL("", "Model does not properly expose JS objects", Continue);
|
||||||
|
QCOMPARE(delegate->property("modelA"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a"));
|
||||||
|
|
||||||
|
// Do the comparison in QVariantMap. The values get converted back and forth a
|
||||||
|
// few times, making any JavaScript equality comparison impossible.
|
||||||
|
// This is only due to test setup, though.
|
||||||
|
const QVariantMap modelData = delegate->property("modelDataSelf").value<QVariantMap>();
|
||||||
|
QVERIFY(!modelData.isEmpty());
|
||||||
|
QCOMPARE(delegate->property("modelModelData").value<QVariantMap>(), modelData);
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous").value<QVariantMap>(), modelData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
// string list
|
||||||
|
QVERIFY(!delegate->property("modelA").isValid());
|
||||||
|
QVERIFY(!delegate->property("modelDataA").isValid());
|
||||||
|
QCOMPARE(delegate->property("modelDataSelf"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelModelData"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous"), QStringLiteral("a"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
// single object
|
||||||
|
QCOMPARE(delegate->property("modelA"), QStringLiteral("a"));
|
||||||
|
QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a"));
|
||||||
|
QObject *modelData = delegate->property("modelDataSelf").value<QObject *>();
|
||||||
|
QVERIFY(modelData != nullptr);
|
||||||
|
QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelData));
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelData));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
// a number
|
||||||
|
QVERIFY(!delegate->property("modelA").isValid());
|
||||||
|
QVERIFY(!delegate->property("modelDataA").isValid());
|
||||||
|
const QVariant modelData = delegate->property("modelDataSelf");
|
||||||
|
|
||||||
|
// This is int on 32bit systems because qsizetype fits into int there.
|
||||||
|
// On 64bit systems it's double because qsizetype doesn't fit into int.
|
||||||
|
if (sizeof(qsizetype) > sizeof(int))
|
||||||
|
QCOMPARE(modelData.metaType(), QMetaType::fromType<double>());
|
||||||
|
else
|
||||||
|
QCOMPARE(modelData.metaType(), QMetaType::fromType<int>());
|
||||||
|
|
||||||
|
QCOMPARE(modelData.value<int>(), 0);
|
||||||
|
QCOMPARE(delegate->property("modelModelData"), modelData);
|
||||||
|
QCOMPARE(delegate->property("modelAnonymous"), modelData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
QFAIL("wrong model number");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QQmlDelegateModel)
|
QTEST_MAIN(tst_QQmlDelegateModel)
|
||||||
|
|
||||||
#include "tst_qqmldelegatemodel.moc"
|
#include "tst_qqmldelegatemodel.moc"
|
||||||
|
|
|
@ -847,12 +847,9 @@ void tst_qquickvisualdatamodel::modelProperties()
|
||||||
QUrl source(testFileUrl("modelproperties2.qml"));
|
QUrl source(testFileUrl("modelproperties2.qml"));
|
||||||
|
|
||||||
//3 items, 3 i each
|
//3 items, 3 i each
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: ReferenceError: modelData is not defined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: TypeError: Cannot read property 'display' of undefined");
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: ReferenceError: modelData is not defined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: TypeError: Cannot read property 'display' of undefined");
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: ReferenceError: modelData is not defined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":13: TypeError: Cannot read property 'display' of undefined");
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":11: ReferenceError: modelData is not defined");
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":11: ReferenceError: modelData is not defined");
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":11: ReferenceError: modelData is not defined");
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
||||||
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
QTest::ignoreMessage(QtWarningMsg, source.toString().toLatin1() + ":17: TypeError: Cannot read property 'display' of undefined");
|
||||||
|
|
Loading…
Reference in New Issue