Skip to content

Commit 4bd5b31

Browse files
author
Ulf Hermann
committed
Use sticky bindings to write the model through required properties
Since sticky bindings don't break when their target is written, we can use two of them to synchronize the model object and the required property both ways. [ChangeLog][QtQml] DelegateModel now has a new property delegateModelAccess. Setting it to DelegateModel.ReadWrite allows you to write values into the model via required properties the way you can do the same with context properties. Task-number: QTBUG-132420 Change-Id: Iee914ca80e840943c0bbe133ab82ee8e88c45eb3 Reviewed-by: Fabian Kosmale <[email protected]> Reviewed-by: Richard Moe Gustavsen <[email protected]>
1 parent a370d08 commit 4bd5b31

14 files changed

+239
-61
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
This property determines how delegates can access the model.
2+
3+
Set this to \c DelegateModel.ReadOnly to prohibit delegates from
4+
writing the model via either context properties, the \c model object, or
5+
required properties.
6+
7+
Set it to \c DelegateModel.ReadWrite to enable delegates to write the
8+
model via either such means.
9+
10+
Set it to \c DelegateModel.Qt5ReadWrite to enable writing via the \c model
11+
object and context properties but \e not via required properties.
12+
13+
The default is \c DelegateModel.Qt5ReadWrite.

src/qmlmodels/qqmladaptormodel.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ void QQmlAdaptorModel::setModel(const QVariant &variant)
4040
if (qobject_cast<QAbstractItemModel *>(object))
4141
accessors = new VDMAbstractItemModelDataType(this);
4242
else
43-
accessors = new VDMObjectDelegateDataType;
43+
accessors = new VDMObjectDelegateDataType(this);
4444
} else if (list.type() == QQmlListAccessor::ListProperty) {
4545
auto object = static_cast<const QQmlListReference *>(list.list().constData())->object();
4646
if (QQmlData *ddata = QQmlData::get(object))
4747
modelStrongReference = ddata->jsWrapper;
4848
setObject(object);
49-
accessors = new VDMObjectDelegateDataType;
49+
accessors = new VDMObjectDelegateDataType(this);
5050
} else if (list.type() == QQmlListAccessor::ObjectList) {
5151
setObject(nullptr);
52-
accessors = new VDMObjectDelegateDataType;
52+
accessors = new VDMObjectDelegateDataType(this);
5353
} else if (list.type() != QQmlListAccessor::Invalid
5454
&& list.type() != QQmlListAccessor::Instance) { // Null QObject
5555
setObject(nullptr);

src/qmlmodels/qqmladaptormodel_p.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@
1717

1818
#include <QtCore/qabstractitemmodel.h>
1919

20-
#include <private/qtqmlglobal_p.h>
21-
#include <private/qqmllistaccessor_p.h>
22-
#include <private/qtqmlmodelsglobal_p.h>
20+
#include <private/qqmldelegatemodel_p.h>
2321
#include <private/qqmlguard_p.h>
22+
#include <private/qqmllistaccessor_p.h>
2423
#include <private/qqmlnullablevalue_p.h>
2524
#include <private/qqmlpropertycache_p.h>
25+
#include <private/qtqmlglobal_p.h>
26+
#include <private/qtqmlmodelsglobal_p.h>
2627

2728
QT_REQUIRE_CONFIG(qml_delegate_model);
2829

2930
QT_BEGIN_NAMESPACE
3031

3132
class QQmlEngine;
3233

33-
class QQmlDelegateModel;
3434
class QQmlDelegateModelItem;
3535
class QQmlDelegateModelItemMetaType;
3636

@@ -84,6 +84,7 @@ class Q_QMLMODELS_EXPORT QQmlAdaptorModel : public QQmlGuard<QObject>
8484
QV4::PersistentValue modelStrongReference;
8585

8686
QTypeRevision modelItemRevision = QTypeRevision::zero();
87+
QQmlDelegateModel::DelegateModelAccess delegateModelAccess = QQmlDelegateModel::Qt5ReadWrite;
8788

8889
QQmlAdaptorModel();
8990
~QQmlAdaptorModel();

src/qmlmodels/qqmladaptormodelenginedata_p.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ class QQmlAdaptorModelEngineData : public QV4::ExecutionEngine::Deletable
5252
metaType->signalOffset = T::staticMetaObject.methodCount();
5353
}
5454

55-
static void addProperty(QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName, const QByteArray &propertyType)
55+
static void addProperty(
56+
QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName,
57+
const QByteArray &propertyType, bool isWritable)
5658
{
5759
builder->addSignal("__" + QByteArray::number(propertyId) + "()");
5860
QMetaPropertyBuilder property = builder->addProperty(
5961
propertyName, propertyType, propertyId);
60-
property.setWritable(true);
62+
property.setWritable(isWritable);
6163
}
6264

6365
V4_DEFINE_EXTENSION(QQmlAdaptorModelEngineData, get)

src/qmlmodels/qqmldelegatemodel.cpp

+61-7
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,42 @@ void QQmlDelegateModel::setRootIndex(const QVariant &root)
533533
}
534534
}
535535

536+
/*!
537+
\qmlproperty enumeration QtQml.Models::DelegateModel::delegateModelAccess
538+
539+
\include delegatemodelaccess.qdocinc
540+
*/
541+
QQmlDelegateModel::DelegateModelAccess QQmlDelegateModel::delegateModelAccess() const
542+
{
543+
Q_D(const QQmlDelegateModel);
544+
return d->m_adaptorModel.delegateModelAccess;
545+
}
546+
547+
void QQmlDelegateModel::setDelegateModelAccess(
548+
QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
549+
{
550+
Q_D(QQmlDelegateModel);
551+
if (d->m_adaptorModel.delegateModelAccess == delegateModelAccess)
552+
return;
553+
554+
if (d->m_transaction) {
555+
qmlWarning(this) << tr("The delegateModelAccess of a DelegateModel "
556+
"cannot be changed within onUpdated.");
557+
return;
558+
}
559+
560+
if (d->m_complete) {
561+
_q_itemsRemoved(0, d->m_count);
562+
d->m_adaptorModel.delegateModelAccess = delegateModelAccess;
563+
_q_itemsInserted(0, d->adaptorModelCount());
564+
d->requestMoreIfNecessary();
565+
} else {
566+
d->m_adaptorModel.delegateModelAccess = delegateModelAccess;
567+
}
568+
569+
emit delegateModelAccessChanged();
570+
}
571+
536572
/*!
537573
\qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index)
538574
@@ -904,7 +940,9 @@ static bool isDoneIncubating(QQmlIncubator::Status status)
904940
return status == QQmlIncubator::Ready || status == QQmlIncubator::Error;
905941
}
906942

907-
void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object)
943+
void QQDMIncubationTask::initializeRequiredProperties(
944+
QQmlDelegateModelItem *modelItemToIncubate, QObject *object,
945+
QQmlDelegateModel::DelegateModelAccess access)
908946
{
909947
// QQmlObjectCreator produces a private internal context.
910948
// We can always attach the extra object there.
@@ -976,9 +1014,18 @@ void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *mod
9761014
object, propName, requiredProperties,
9771015
engine, &wasInRequired);
9781016
if (wasInRequired) {
979-
QQmlAnyBinding binding = QQmlPropertyToPropertyBinding::create(
980-
engine, QQmlProperty(itemOrProxy, propName), targetProp);
981-
binding.installOn(targetProp);
1017+
QQmlProperty sourceProp(itemOrProxy, propName);
1018+
QQmlAnyBinding forward = QQmlPropertyToPropertyBinding::create(
1019+
engine, sourceProp, targetProp);
1020+
if (access != QQmlDelegateModel::Qt5ReadWrite)
1021+
forward.setSticky();
1022+
forward.installOn(targetProp);
1023+
if (access == QQmlDelegateModel::ReadWrite && sourceProp.isWritable()) {
1024+
QQmlAnyBinding reverse = QQmlPropertyToPropertyBinding::create(
1025+
engine, targetProp, sourceProp);
1026+
reverse.setSticky();
1027+
reverse.installOn(sourceProp);
1028+
}
9821029
}
9831030
}
9841031
}
@@ -1163,7 +1210,8 @@ void QQmlDelegateModelGroupEmitter::destroyingPackage(QQuickPackage *) {}
11631210
void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o)
11641211
{
11651212
QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
1166-
incubationTask->initializeRequiredProperties(incubationTask->incubating, o);
1213+
incubationTask->initializeRequiredProperties(
1214+
incubationTask->incubating, o, m_adaptorModel.delegateModelAccess);
11671215
cacheItem->object = o;
11681216

11691217
if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
@@ -1266,8 +1314,14 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ
12661314
ctxt->setContextObject(cacheItem);
12671315
cacheItem->contextData = ctxt;
12681316

1269-
if (m_adaptorModel.hasProxyObject())
1270-
ctxt = cacheItem->initProxy();
1317+
// If the model is read-only we cannot just expose the object as context
1318+
// We actually need a separate model object to moderate access.
1319+
if (m_adaptorModel.hasProxyObject()) {
1320+
if (m_adaptorModel.delegateModelAccess == QQmlDelegateModel::ReadOnly)
1321+
cacheItem->initProxy();
1322+
else
1323+
ctxt = cacheItem->initProxy();
1324+
}
12711325

12721326
cp->incubateObject(
12731327
cacheItem->incubationTask,

src/qmlmodels/qqmldelegatemodel_p.h

+13
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,22 @@ class Q_QMLMODELS_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQ
4747
Q_PROPERTY(QQmlListProperty<QQmlDelegateModelGroup> groups READ groups CONSTANT)
4848
Q_PROPERTY(QObject *parts READ parts CONSTANT)
4949
Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged)
50+
Q_PROPERTY(DelegateModelAccess delegateModelAccess READ delegateModelAccess
51+
WRITE setDelegateModelAccess NOTIFY delegateModelAccessChanged REVISION(6, 10) FINAL)
5052
Q_CLASSINFO("DefaultProperty", "delegate")
5153
QML_NAMED_ELEMENT(DelegateModel)
5254
QML_ADDED_IN_VERSION(2, 1)
5355
QML_ATTACHED(QQmlDelegateModelAttached)
5456
Q_INTERFACES(QQmlParserStatus)
5557

5658
public:
59+
enum DelegateModelAccess : quint8 {
60+
Qt5ReadWrite,
61+
ReadOnly,
62+
ReadWrite
63+
};
64+
Q_ENUM(DelegateModelAccess)
65+
5766
QQmlDelegateModel();
5867
QQmlDelegateModel(QQmlContext *, QObject *parent=nullptr);
5968
~QQmlDelegateModel();
@@ -70,6 +79,9 @@ class Q_QMLMODELS_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQ
7079
QVariant rootIndex() const;
7180
void setRootIndex(const QVariant &root);
7281

82+
DelegateModelAccess delegateModelAccess() const;
83+
void setDelegateModelAccess(DelegateModelAccess delegateModelAccess);
84+
7385
Q_INVOKABLE QVariant modelIndex(int idx) const;
7486
Q_INVOKABLE QVariant parentModelIndex() const;
7587

@@ -130,6 +142,7 @@ class Q_QMLMODELS_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQ
130142
void defaultGroupsChanged();
131143
void rootIndexChanged();
132144
void delegateChanged();
145+
Q_REVISION(6, 10) void delegateModelAccessChanged();
133146

134147
private Q_SLOTS:
135148
void _q_itemsChanged(int index, int count, const QVector<int> &roles);

src/qmlmodels/qqmldelegatemodel_p_p.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ class QQDMIncubationTask : public QQmlIncubator
214214
, incubating(nullptr)
215215
, vdm(l) {}
216216

217-
void initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject* object);
217+
void initializeRequiredProperties(
218+
QQmlDelegateModelItem *modelItemToIncubate, QObject* object,
219+
QQmlDelegateModel::DelegateModelAccess access);
218220
void statusChanged(Status) override;
219221
void setInitialState(QObject *) override;
220222

src/qmlmodels/qqmldmabstractitemmodeldata_p.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,9 @@ class VDMAbstractItemModelDataType final
323323
const int propertyId = propertyRoles.size();
324324
propertyRoles.append(it.key());
325325
roleNames.insert(it.value(), it.key());
326-
QQmlAdaptorModelEngineData::addProperty(&builder, propertyId, it.value(), propertyType);
326+
QQmlAdaptorModelEngineData::addProperty(
327+
&builder, propertyId, it.value(), propertyType,
328+
model.delegateModelAccess != QQmlDelegateModel::ReadOnly);
327329
}
328330

329331
metaObject.reset(builder.toMetaObject());

src/qmlmodels/qqmldmlistaccessordata.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ int VDMListDelegateDataType::createProperty(const char *name, const char *)
117117

118118
// We use QVariant because the types may be different in the different objects.
119119
QQmlAdaptorModelEngineData::addProperty(
120-
&builder, propertyIndex, name, QByteArrayLiteral("QVariant"));
120+
&builder, propertyIndex, name, QByteArrayLiteral("QVariant"),
121+
model->delegateModelAccess != QQmlDelegateModel::ReadOnly);
121122

122123
metaObject.reset(builder.toMetaObject());
123124
*static_cast<QMetaObject *>(this) = *metaObject;

src/qmlmodels/qqmldmobjectdata_p.h

+13-12
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,26 @@ class VDMObjectDelegateDataType final
5959
public QQmlAdaptorModel::Accessors
6060
{
6161
public:
62-
int propertyOffset;
63-
int signalOffset;
64-
bool shared;
6562
QMetaObjectBuilder builder;
63+
QQmlAdaptorModel *model = nullptr;
64+
int propertyOffset = 0;
65+
int signalOffset = 0;
66+
bool shared = false;
6667

67-
VDMObjectDelegateDataType()
68-
: propertyOffset(0)
69-
, signalOffset(0)
68+
VDMObjectDelegateDataType(QQmlAdaptorModel *model)
69+
: model(model)
7070
, shared(true)
7171
{
7272
}
7373

7474
VDMObjectDelegateDataType(const VDMObjectDelegateDataType &type)
75-
: propertyOffset(type.propertyOffset)
76-
, signalOffset(type.signalOffset)
77-
, shared(false)
78-
, builder(type.metaObject.data(), QMetaObjectBuilder::Properties
75+
: builder(type.metaObject.data(), QMetaObjectBuilder::Properties
7976
| QMetaObjectBuilder::Signals
8077
| QMetaObjectBuilder::SuperClass
8178
| QMetaObjectBuilder::ClassName)
79+
, model(type.model)
80+
, propertyOffset(type.propertyOffset)
81+
, signalOffset(type.signalOffset)
8282
{
8383
builder.setFlags(MetaObjectFlag::DynamicMetaObject);
8484
}
@@ -217,8 +217,9 @@ class QQmlDMObjectDataMetaObject : public QAbstractDynamicMetaObject
217217
} else {
218218
propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName());
219219
}
220-
propertyBuilder.setWritable(property.isWritable());
221-
propertyBuilder.setResettable(property.isResettable());
220+
const bool modelWritable = m_type->model->delegateModelAccess != QQmlDelegateModel::ReadOnly;
221+
propertyBuilder.setWritable(modelWritable && property.isWritable());
222+
propertyBuilder.setResettable(modelWritable && property.isResettable());
222223
propertyBuilder.setConstant(property.isConstant());
223224
}
224225

src/qmlmodels/qqmltableinstancemodel.cpp

+10-3
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,14 @@ void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem,
320320
ctxt->setContextObject(modelItem);
321321
modelItem->contextData = ctxt;
322322

323-
if (m_adaptorModel.hasProxyObject())
324-
ctxt = modelItem->initProxy();
323+
// If the model is read-only we cannot just expose the object as context
324+
// We actually need a separate model object to moderate access.
325+
if (m_adaptorModel.hasProxyObject()) {
326+
if (m_adaptorModel.delegateModelAccess == QQmlDelegateModel::ReadOnly)
327+
modelItem->initProxy();
328+
else
329+
ctxt = modelItem->initProxy();
330+
}
325331

326332
cp->incubateObject(
327333
modelItem->incubationTask,
@@ -523,7 +529,8 @@ const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const
523529

524530
void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object)
525531
{
526-
initializeRequiredProperties(modelItemToIncubate, object);
532+
initializeRequiredProperties(
533+
modelItemToIncubate, object, tableInstanceModel->delegateModelAccess());
527534
modelItemToIncubate->object = object;
528535
emit tableInstanceModel->initItem(modelItemToIncubate->modelIndex(), object);
529536

src/qmlmodels/qqmltableinstancemodel_p.h

+9
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ class Q_QMLMODELS_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel
7272
QQmlComponent *delegate() const;
7373
void setDelegate(QQmlComponent *);
7474

75+
QQmlDelegateModel::DelegateModelAccess delegateModelAccess() const
76+
{
77+
return m_adaptorModel.delegateModelAccess;
78+
}
79+
void setDelegateModelAccess(QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
80+
{
81+
m_adaptorModel.delegateModelAccess = delegateModelAccess;
82+
}
83+
7584
const QAbstractItemModel *abstractItemModel() const override;
7685

7786
QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;

tests/auto/qml/qqmldelegatemodel/data/delegateModelAccess.qml

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import QtQml
2+
import Test
23

34
DelegateModel {
45
id: root
@@ -62,23 +63,23 @@ DelegateModel {
6263
property int y: 12
6364
}
6465

65-
property int n: -1
66-
property int o: -1
66+
property int modelIndex: Model.None
67+
property int delegateIndex: Delegate.None
6768

6869
model: {
69-
switch (n) {
70-
case 0: return singularModel
71-
case 1: return listModel
72-
case 2: return array
73-
case 3: return object
70+
switch (modelIndex) {
71+
case Model.Singular: return singularModel
72+
case Model.List: return listModel
73+
case Model.Array: return array
74+
case Model.Object: return object
7475
}
7576
return undefined;
7677
}
7778

7879
delegate: {
79-
switch (o) {
80-
case 0: return untypedDelegate
81-
case 1: return typedDelegate
80+
switch (delegateIndex) {
81+
case Delegate.Untyped: return untypedDelegate
82+
case Delegate.Typed: return typedDelegate
8283
}
8384
return null
8485
}

0 commit comments

Comments
 (0)