From 8ec25f301d08e981ca066755ffea582c5cbdf1b0 Mon Sep 17 00:00:00 2001 From: Paul Wicking Date: Wed, 27 Mar 2019 10:35:33 +0100 Subject: Doc: Fix typo in code snippet Fixes: QTBUG-74444 Change-Id: If504fe2a6b4a0d88d69e777d433a6773db5f4df3 Reviewed-by: Michael Brasser --- src/quick/util/qquickanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index 02be9daac0..2043b50545 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -812,7 +812,7 @@ QQuickColorAnimation::~QQuickColorAnimation() // States are defined here... ] - transition: Transition { + transitions: Transition { ColorAnimation { from: "#c0c0c0"; duration: 2000 } } } -- cgit v1.2.3 From 9c4174f9f3fa4a0c46b11b3160d1b45d19bf66af Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 16 Apr 2019 16:45:55 +0200 Subject: Debug missing synth-mouse ID in hex, not decimal It's hard to correlate with the other debug messages otherwise. Task-number: QTBUG-74008 Change-Id: I611201cc8ca86739251b72ccc3e1c5860cfdad8a Reviewed-by: Shawn Rutledge --- src/quick/items/qquickwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index bd01e9b3ce..f705f132fc 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -2543,7 +2543,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event, if (point->grabberPointerHandler()) cancelTouchMouseSynthesis(); } else { - qCWarning(DBG_TOUCH_TARGET) << "during delivery of touch press, synth-mouse ID" << touchMouseId << "is missing from" << event; + qCWarning(DBG_TOUCH_TARGET) << "during delivery of touch press, synth-mouse ID" << hex << touchMouseId << "is missing from" << event; } } for (int i = 0; i < pointCount; ++i) { -- cgit v1.2.3 From ea74f0c68cddf706c950d3910cf7b363fe24885b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 17 Apr 2019 12:35:42 +0200 Subject: Don't crash when accessing invalid properties through QObjectWrapper Change-Id: I613bf5dc685bb4235262b429d8f7318ea144fb9d Fixes: QTBUG-75203 Reviewed-by: Erik Verbruggen --- src/qml/jsruntime/qv4qobjectwrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 377c30617a..5467e730e3 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -856,7 +856,7 @@ ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, E if (!ddata || !ddata->propertyCache) { QQmlPropertyData local; QQmlPropertyData *property = QQmlPropertyCache::property(engine->jsEngine(), qobj, name, qmlContext, local); - return getProperty(engine, qobj, property); + return property ? getProperty(engine, qobj, property) : QV4::Encode::undefined(); } QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobj, qmlContext); -- cgit v1.2.3 From c018df5b4075ae962966d4df7653d476dab02840 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 17 Apr 2019 08:38:20 +0200 Subject: QML: Remove static attchedPropertyIds map If the same object is available under two different names it should still have the same attached properties no matter which name you use. This was achieved by having a static map of metaobjects to attached property IDs that would always hold the first attached property ID registered for a given metaobject. This attached property ID was then used as key in the map of attached properties for the actual objects. The obvious downside to that is that we need a global static which gives us thread safety and static initialization (and destruction) problems. It turns out, all the attached properties are created by attached properties functions, registered by the user. Those functions only get the object to be amended as parameter. Therefore, no attached properties function can be registered for multiple attached properties on the same object as it wouldn't know which one to create for a given call. Thus, the whole ID dance is unnecessary as we can as well index the attached property objects by the function that created them. This nicely avoids creating two attached property objects for the same object and function and still makes the global static unnecessary. Fixes: QTBUG-75176 Change-Id: Ie8d53ef0a6f41c9b3d6b9d611cde1603a557901c Reviewed-by: Erik Verbruggen --- src/qml/qml/qqml.h | 9 ++++-- src/qml/qml/qqmldata_p.h | 3 +- src/qml/qml/qqmlengine.cpp | 60 ++++++++++++++++++++++++++++++--------- src/qml/qml/qqmlmetatype.cpp | 35 ++++++++--------------- src/qml/qml/qqmlmetatype_p.h | 2 ++ src/qml/qml/qqmlobjectcreator.cpp | 4 +-- src/qml/qml/qqmlprivate.h | 5 ++++ src/qml/qml/qqmlproperty.cpp | 4 +-- src/qml/qml/qqmltypewrapper.cpp | 7 +++-- 9 files changed, 83 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 05a9f70247..9eacc5bc22 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -582,6 +582,10 @@ namespace QtQml { Q_QML_EXPORT QObject *qmlAttachedPropertiesObjectById(int, const QObject *, bool create = true); Q_QML_EXPORT QObject *qmlAttachedPropertiesObject(int *, const QObject *, const QMetaObject *, bool create); + Q_QML_EXPORT QQmlAttachedPropertiesFunc qmlAttachedPropertiesFunction(QObject *, + const QMetaObject *); + Q_QML_EXPORT QObject *qmlAttachedPropertiesObject(QObject *, QQmlAttachedPropertiesFunc func, + bool create = true); #ifndef Q_QDOC } #endif @@ -601,8 +605,9 @@ Q_QML_EXPORT void qmlRegisterModule(const char *uri, int versionMajor, int versi template QObject *qmlAttachedPropertiesObject(const QObject *obj, bool create = true) { - static int idx = -1; - return qmlAttachedPropertiesObject(&idx, obj, &T::staticMetaObject, create); + QObject *mutableObj = const_cast(obj); + return qmlAttachedPropertiesObject( + mutableObj, qmlAttachedPropertiesFunction(mutableObj, &T::staticMetaObject), create); } Q_QML_EXPORT void qmlRegisterBaseTypes(const char *uri, int versionMajor, int versionMinor); diff --git a/src/qml/qml/qqmldata_p.h b/src/qml/qml/qqmldata_p.h index 2468de6857..f4c03fc17c 100644 --- a/src/qml/qml/qqmldata_p.h +++ b/src/qml/qml/qqmldata_p.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -265,7 +266,7 @@ public: } bool hasExtendedData() const { return extendedData != nullptr; } - QHash *attachedProperties() const; + QHash *attachedProperties() const; static inline bool wasDeleted(const QObject *); diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 5841a480fc..7cd4bf8579 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1647,29 +1647,38 @@ QQmlEngine *qmlEngine(const QObject *obj) return data->context->engine; } -QObject *qmlAttachedPropertiesObjectById(int id, const QObject *object, bool create) +static QObject *resolveAttachedProperties(QQmlAttachedPropertiesFunc pf, QQmlData *data, + QObject *object, bool create) { - QQmlData *data = QQmlData::get(object, create); - if (!data) - return nullptr; // Attached properties are only on objects created by QML, unless explicitly requested (create==true) + if (!pf) + return nullptr; - QObject *rv = data->hasExtendedData()?data->attachedProperties()->value(id):0; + QObject *rv = data->hasExtendedData() ? data->attachedProperties()->value(pf) : 0; if (rv || !create) return rv; - QQmlEnginePrivate *engine = QQmlEnginePrivate::get(data->context); - QQmlAttachedPropertiesFunc pf = QQmlMetaType::attachedPropertiesFuncById(engine, id); - if (!pf) - return nullptr; - - rv = pf(const_cast(object)); + rv = pf(object); if (rv) - data->attachedProperties()->insert(id, rv); + data->attachedProperties()->insert(pf, rv); return rv; } +QObject *qmlAttachedPropertiesObjectById(int id, const QObject *object, bool create) +{ + QQmlData *data = QQmlData::get(object, create); + + // Attached properties are only on objects created by QML, + // unless explicitly requested (create==true) + if (!data) + return nullptr; + + QQmlEnginePrivate *engine = QQmlEnginePrivate::get(data->context); + return resolveAttachedProperties(QQmlMetaType::attachedPropertiesFuncById(engine, id), data, + const_cast(object), create); +} + QObject *qmlAttachedPropertiesObject(int *idCache, const QObject *object, const QMetaObject *attachedMetaObject, bool create) { @@ -1684,6 +1693,29 @@ QObject *qmlAttachedPropertiesObject(int *idCache, const QObject *object, return qmlAttachedPropertiesObjectById(*idCache, object, create); } +QQmlAttachedPropertiesFunc qmlAttachedPropertiesFunction(QObject *object, + const QMetaObject *attachedMetaObject) +{ + QQmlEngine *engine = object ? qmlEngine(object) : nullptr; + return QQmlMetaType::attachedPropertiesFunc(engine ? QQmlEnginePrivate::get(engine) : nullptr, + attachedMetaObject); +} + +QObject *qmlAttachedPropertiesObject(QObject *object, QQmlAttachedPropertiesFunc func, bool create) +{ + if (!object) + return nullptr; + + QQmlData *data = QQmlData::get(object, create); + + // Attached properties are only on objects created by QML, + // unless explicitly requested (create==true) + if (!data) + return nullptr; + + return resolveAttachedProperties(func, data, object, create); +} + } // namespace QtQml #if QT_DEPRECATED_SINCE(5, 1) @@ -1724,7 +1756,7 @@ public: QQmlDataExtended(); ~QQmlDataExtended(); - QHash attachedProperties; + QHash attachedProperties; }; QQmlDataExtended::QQmlDataExtended() @@ -1870,7 +1902,7 @@ void QQmlData::disconnectNotifiers() } } -QHash *QQmlData::attachedProperties() const +QHash *QQmlData::attachedProperties() const { if (!extendedData) extendedData = new QQmlDataExtended; return &extendedData->attachedProperties; diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index f801e9aeba..1873a99a71 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -224,7 +224,6 @@ public: QQmlCustomParser *customParser; QQmlAttachedPropertiesFunc attachedPropertiesFunc; const QMetaObject *attachedPropertiesType; - int attachedPropertiesId; int propertyValueSourceCast; int propertyValueInterceptorCast; bool registerEnumClassesUnscoped; @@ -269,8 +268,6 @@ public: mutable QStringHash scopedEnumIndex; // maps from enum name to index in scopedEnums mutable QList*> scopedEnums; - static QHash attachedPropertyIds; - struct PropertyCacheByMinorVersion { PropertyCacheByMinorVersion() : cache(nullptr), minorVersion(-1) {} @@ -363,8 +360,6 @@ QJSValue QQmlType::SingletonInstanceInfo::scriptApi(QQmlEngine *e) const return scriptApis.value(e); } -QHash QQmlTypePrivate::attachedPropertyIds; - QQmlTypePrivate::QQmlTypePrivate(QQmlType::RegistrationType type) : refCount(1), regType(type), iid(nullptr), typeId(0), listId(0), revision(0), containsRevisionedAttributes(false), baseMetaObject(nullptr), @@ -498,14 +493,6 @@ QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQm d->baseMetaObject = type.metaObject; d->extraData.cd->attachedPropertiesFunc = type.attachedPropertiesFunction; d->extraData.cd->attachedPropertiesType = type.attachedPropertiesMetaObject; - if (d->extraData.cd->attachedPropertiesType) { - auto iter = QQmlTypePrivate::attachedPropertyIds.find(d->baseMetaObject); - if (iter == QQmlTypePrivate::attachedPropertyIds.end()) - iter = QQmlTypePrivate::attachedPropertyIds.insert(d->baseMetaObject, d->index); - d->extraData.cd->attachedPropertiesId = *iter; - } else { - d->extraData.cd->attachedPropertiesId = -1; - } d->extraData.cd->parserStatusCast = type.parserStatusCast; d->extraData.cd->propertyValueSourceCast = type.valueSourceCast; d->extraData.cd->propertyValueInterceptorCast = type.valueInterceptorCast; @@ -571,16 +558,8 @@ QQmlType::QQmlType(QQmlTypePrivate *priv) QQmlType::~QQmlType() { - if (d && !d->refCount.deref()) { - // If attached properties were successfully registered, deregister them. - // (They may not have been registered if some other type used the same baseMetaObject) - if (d->regType == CppType && d->extraData.cd->attachedPropertiesType) { - auto it = QQmlTypePrivate::attachedPropertyIds.find(d->baseMetaObject); - if (it != QQmlTypePrivate::attachedPropertyIds.end() && *it == d->index) - QQmlTypePrivate::attachedPropertyIds.erase(it); - } + if (d && !d->refCount.deref()) delete d; - } } QHashedString QQmlType::module() const @@ -1228,7 +1207,7 @@ int QQmlType::attachedPropertiesId(QQmlEnginePrivate *engine) const if (!d) return -1; if (d->regType == CppType) - return d->extraData.cd->attachedPropertiesId; + return d->extraData.cd->attachedPropertiesType ? d->index : -1; QQmlType base; if (d->regType == CompositeType) @@ -2188,6 +2167,16 @@ QQmlAttachedPropertiesFunc QQmlMetaType::attachedPropertiesFuncById(QQmlEnginePr return data->types.at(id).attachedPropertiesFunction(engine); } +QQmlAttachedPropertiesFunc QQmlMetaType::attachedPropertiesFunc(QQmlEnginePrivate *engine, + const QMetaObject *mo) +{ + QMutexLocker lock(metaTypeDataLock()); + QQmlMetaTypeData *data = metaTypeData(); + + QQmlType type(data->metaObjectToType.value(mo)); + return type.attachedPropertiesFunction(engine); +} + QMetaProperty QQmlMetaType::defaultProperty(const QMetaObject *metaObject) { int idx = metaObject->indexOfClassInfo("DefaultProperty"); diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 8256212207..3c1589d19b 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -119,6 +119,8 @@ public: static int listType(int); static int attachedPropertiesFuncId(QQmlEnginePrivate *engine, const QMetaObject *); static QQmlAttachedPropertiesFunc attachedPropertiesFuncById(QQmlEnginePrivate *, int); + static QQmlAttachedPropertiesFunc attachedPropertiesFunc(QQmlEnginePrivate *, + const QMetaObject *); enum TypeCategory { Unknown, Object, List }; static TypeCategory typeCategory(int); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 5af658194f..b62b07e39c 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -793,8 +793,8 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper else return false; } - const int id = attachedType.attachedPropertiesId(QQmlEnginePrivate::get(engine)); - QObject *qmlObject = qmlAttachedPropertiesObjectById(id, _qobject); + QObject *qmlObject = qmlAttachedPropertiesObject( + _qobject, attachedType.attachedPropertiesFunction(QQmlEnginePrivate::get(engine))); if (!populateInstance(binding->value.objectIndex, qmlObject, qmlObject, /*value type property*/nullptr)) return false; return true; diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index c0232a7691..fa05b3fe19 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -77,6 +77,11 @@ typedef void (*IRLoaderFunction)(Document *, const QQmlPrivate::CachedQmlUnit *) typedef QObject *(*QQmlAttachedPropertiesFunc)(QObject *); +inline uint qHash(QQmlAttachedPropertiesFunc func, uint seed = 0) +{ + return qHash(quintptr(func), seed); +} + template class QQmlTypeInfo { diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 000b88ebaa..c8166695ba 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -277,7 +277,7 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) QQmlAttachedPropertiesFunc func = r.type.attachedPropertiesFunction(enginePrivate); if (!func) return; // Not an attachable type - currentObject = qmlAttachedPropertiesObjectById(r.type.attachedPropertiesId(enginePrivate), currentObject); + currentObject = qmlAttachedPropertiesObject(currentObject, func); if (!currentObject) return; // Something is broken with the attachable type } else if (r.importNamespace) { if ((ii + 1) == path.count()) return; // No type following the namespace @@ -289,7 +289,7 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) QQmlAttachedPropertiesFunc func = r.type.attachedPropertiesFunction(enginePrivate); if (!func) return; // Not an attachable type - currentObject = qmlAttachedPropertiesObjectById(r.type.attachedPropertiesId(enginePrivate), currentObject); + currentObject = qmlAttachedPropertiesObject(currentObject, func); if (!currentObject) return; // Something is broken with the attachable type } else if (r.scriptIndex != -1) { diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index d30c225741..246de04316 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -263,7 +263,9 @@ ReturnedValue QQmlTypeWrapper::virtualGet(const Managed *m, PropertyKey id, cons // Fall through to base implementation } else if (w->d()->object) { - QObject *ao = qmlAttachedPropertiesObjectById(type.attachedPropertiesId(QQmlEnginePrivate::get(v4->qmlEngine())), object); + QObject *ao = qmlAttachedPropertiesObject( + object, + type.attachedPropertiesFunction(QQmlEnginePrivate::get(v4->qmlEngine()))); if (ao) return QV4::QObjectWrapper::getQmlProperty(v4, context, ao, name, QV4::QObjectWrapper::IgnoreRevision, hasProperty); @@ -335,7 +337,8 @@ bool QQmlTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, if (type.isValid() && !type.isSingleton() && w->d()->object) { QObject *object = w->d()->object; QQmlEngine *e = scope.engine->qmlEngine(); - QObject *ao = qmlAttachedPropertiesObjectById(type.attachedPropertiesId(QQmlEnginePrivate::get(e)), object); + QObject *ao = qmlAttachedPropertiesObject( + object, type.attachedPropertiesFunction(QQmlEnginePrivate::get(e))); if (ao) return QV4::QObjectWrapper::setQmlProperty(scope.engine, context, ao, name, QV4::QObjectWrapper::IgnoreRevision, value); return false; -- cgit v1.2.3 From 93818a359973003531e2a5ef2d55cf58b3d440d9 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 22 Mar 2019 14:43:23 +0100 Subject: QQuickTableView: add new 'syncView' property This property can be set to point to another TableView. If set, this TableView will be synchronized to the other table with respect to flicking, column width, row heights, spacing, etc. This logic is needed as a foundation for the upcoming HeaderView. Upcoming patches will implement this logic (together with autotests) gradually. Change-Id: Ic7dea8e1d1aa46bbb3ea6e795953a65c96c25cc6 Reviewed-by: Mitch Curtis --- src/quick/items/qquickitemsmodule.cpp | 3 ++ src/quick/items/qquicktableview.cpp | 75 +++++++++++++++++++++++++++++++++++ src/quick/items/qquicktableview_p.h | 10 +++++ src/quick/items/qquicktableview_p_p.h | 8 ++++ 4 files changed, 96 insertions(+) (limited to 'src') diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index cd5cca5ac9..a6dce33f45 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -475,6 +475,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterUncreatableType(uri, 2, 13, itemViewName, itemViewMessage); qmlRegisterType(uri, 2, 13, "PathView"); qmlRegisterType(uri, 2, 13, "GridView"); +#if QT_CONFIG(quick_tableview) + qmlRegisterType(uri, 2, 14, "TableView"); +#endif } static void initResources() diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 1dca5bea4a..fc7516ec66 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1855,9 +1855,14 @@ void QQuickTableViewPrivate::syncWithPendingChanges() // such assignments into effect until we're in a state that allows it. Q_Q(QQuickTableView); viewportRect = QRectF(q->contentX(), q->contentY(), q->width(), q->height()); + + // Sync rebuild options first, in case we schedule a rebuild from one of the + // other sync calls above. If so, we need to start a new rebuild from the top. syncRebuildOptions(); + syncModel(); syncDelegate(); + syncSyncView(); } void QQuickTableViewPrivate::syncRebuildOptions() @@ -1922,6 +1927,41 @@ void QQuickTableViewPrivate::syncModel() connectToModel(); } +void QQuickTableViewPrivate::syncSyncView() +{ + Q_Q(QQuickTableView); + + if (assignedSyncView != syncView) { + if (syncView) + syncView->d_func()->syncChildren.removeOne(q); + + if (assignedSyncView) { + QQuickTableView *view = assignedSyncView; + + while (view) { + if (view == q) { + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q) << "TableView: recursive syncView connection detected!"; + } + syncView = nullptr; + return; + } + view = view->d_func()->syncView; + } + + assignedSyncView->d_func()->syncChildren.append(q); + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + q->polish(); + } + + syncView = assignedSyncView; + } + + syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal; + syncVertically = syncView && assignedSyncDirection & Qt::Vertical; +} + void QQuickTableViewPrivate::connectToModel() { Q_TABLEVIEW_ASSERT(model, ""); @@ -2214,6 +2254,41 @@ void QQuickTableView::setContentHeight(qreal height) QQuickFlickable::setContentHeight(height); } +QQuickTableView *QQuickTableView::syncView() const +{ + return d_func()->assignedSyncView; +} + +void QQuickTableView::setSyncView(QQuickTableView *view) +{ + Q_D(QQuickTableView); + if (d->assignedSyncView == view) + return; + + d->assignedSyncView = view; + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly); + + emit syncViewChanged(); +} + +Qt::Orientations QQuickTableView::syncDirection() const +{ + return d_func()->assignedSyncDirection; +} + +void QQuickTableView::setSyncDirection(Qt::Orientations direction) +{ + Q_D(QQuickTableView); + if (d->assignedSyncDirection == direction) + return; + + d->assignedSyncDirection = direction; + if (d->assignedSyncView) + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly); + + emit syncDirectionChanged(); +} + void QQuickTableView::forceLayout() { d_func()->forceLayout(); diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h index f32c71b983..3d46221574 100644 --- a/src/quick/items/qquicktableview_p.h +++ b/src/quick/items/qquicktableview_p.h @@ -79,6 +79,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTableView : public QQuickFlickable Q_PROPERTY(bool reuseItems READ reuseItems WRITE setReuseItems NOTIFY reuseItemsChanged) Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged) Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged) + Q_PROPERTY(QQuickTableView *syncView READ syncView WRITE setSyncView NOTIFY syncViewChanged REVISION 14) + Q_PROPERTY(Qt::Orientations syncDirection READ syncDirection WRITE setSyncDirection NOTIFY syncDirectionChanged REVISION 14) public: QQuickTableView(QQuickItem *parent = nullptr); @@ -110,6 +112,12 @@ public: void setContentWidth(qreal width); void setContentHeight(qreal height); + QQuickTableView *syncView() const; + void setSyncView(QQuickTableView *view); + + Qt::Orientations syncDirection() const; + void setSyncDirection(Qt::Orientations direction); + Q_INVOKABLE void forceLayout(); static QQuickTableViewAttached *qmlAttachedProperties(QObject *); @@ -124,6 +132,8 @@ Q_SIGNALS: void modelChanged(); void delegateChanged(); void reuseItemsChanged(); + Q_REVISION(14) void syncViewChanged(); + Q_REVISION(14) void syncDirectionChanged(); protected: void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 6bbfd4e2d6..c018356efa 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -249,6 +249,8 @@ public: bool blockItemCreatedCallback = false; bool layoutWarningIssued = false; bool polishing = false; + bool syncVertically = false; + bool syncHorizontally = false; QJSValue rowHeightProvider; QJSValue columnWidthProvider; @@ -271,6 +273,11 @@ public: QSizeF averageEdgeSize; + QPointer assignedSyncView; + QPointer syncView; + QList > syncChildren; + Qt::Orientations assignedSyncDirection = Qt::Horizontal | Qt::Vertical; + const static QPoint kLeft; const static QPoint kRight; const static QPoint kUp; @@ -369,6 +376,7 @@ public: inline void syncDelegate(); inline void syncModel(); inline void syncRebuildOptions(); + inline void syncSyncView(); void connectToModel(); void disconnectFromModel(); -- cgit v1.2.3 From c642f44448752972f89a121b125873f187e088b3 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 25 Jan 2019 17:10:14 +0100 Subject: Tooling: Use fprintf rather than qDebug() for debug warning This warning is generated from a statically called ctor. At that point the system facilities to run QMessageLogger may not be in place, yet. In addition, we actually don't want the message to go through the regular QMessageLogger redirection and possibly filtering. The message should always be shown. Fixes: QTBUG-73217 Change-Id: Ief192dae8c38d5d94996ee9285e54b5cbd714f4b Reviewed-by: Andy Shaw Reviewed-by: Simon Hausmann (cherry-picked from commit 15525d2a309a6028d548797bc777e38905f36c46) --- src/qml/debugger/qqmldebug.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/qml/debugger/qqmldebug.cpp b/src/qml/debugger/qqmldebug.cpp index 6246fb4207..6532576e03 100644 --- a/src/qml/debugger/qqmldebug.cpp +++ b/src/qml/debugger/qqmldebug.cpp @@ -44,16 +44,16 @@ #include #include +#include + QT_REQUIRE_CONFIG(qml_debug); QT_BEGIN_NAMESPACE QQmlDebuggingEnabler::QQmlDebuggingEnabler(bool printWarning) { - if (!QQmlEnginePrivate::qml_debugging_enabled - && printWarning) { - qDebug("QML debugging is enabled. Only use this in a safe environment."); - } + if (!QQmlEnginePrivate::qml_debugging_enabled && printWarning) + fprintf(stderr, "QML debugging is enabled. Only use this in a safe environment.\n"); QQmlEnginePrivate::qml_debugging_enabled = true; } -- cgit v1.2.3 From 4d098132421fe75492d61d270569c2d8a7acbdde Mon Sep 17 00:00:00 2001 From: Richard Weickelt Date: Sun, 21 Apr 2019 13:27:05 +0200 Subject: Move creation and management of singletons to QQmlEnginePrivate Singleton object instances were previously managed by QQmlType::SingletonInstanceInfo and kept in a shared storage. This caused concurrency problems when instantiating singleton instances from different QML engines in different threads. This patch moves the singleton house-keeping infrastructure to QQmlEnginePrivate and makes SingletonInstanceInfo immutable. Singleton objects are stored in a QHash with QQmlType as the key because the qml type id might be 0 for composite singletons. The public API of QQmlType is extended to provide more information about singleton types so that access to SingletonInstanceInfo is not needed. All internal accesses of singleton objects must now take the same code path via QQmlEnginePrivate::singletonInstance() which simplifies overall usage of singletons and scatters less implementation details throughout the code base. Task-number: QTBUG-75007 Change-Id: I13c5fd21cac2eb7291f2cbcf2c2b504f0f51a07c Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4qmlcontext.cpp | 10 +-- src/qml/qml/qqmlengine.cpp | 77 +++++++++++++--- src/qml/qml/qqmlengine.h | 7 +- src/qml/qml/qqmlengine_p.h | 14 +++ src/qml/qml/qqmltype.cpp | 74 +++------------- src/qml/qml/qqmltype_p.h | 25 ++---- src/qml/qml/qqmltypewrapper.cpp | 171 ++++++++++++++++++------------------ 7 files changed, 184 insertions(+), 194 deletions(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index d2aa334805..4e917feb2d 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -231,17 +231,17 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r } else if (r.type.isValid()) { if (lookup) { if (r.type.isSingleton()) { - QQmlEngine *e = v4->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = r.type.singletonInstanceInfo(); - siinfo->init(e); - if (siinfo->qobjectApi(e)) { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(v4->qmlEngine()); + if (r.type.isQObjectSingleton() || r.type.isCompositeSingleton()) { + e->singletonInstance(r.type); lookup->qmlContextSingletonLookup.singleton = static_cast( Value::fromReturnedValue( QQmlTypeWrapper::create(v4, nullptr, r.type) ).heapObject()); } else { - QV4::ScopedObject o(scope, QJSValuePrivate::convertedToValue(v4, siinfo->scriptApi(e))); + QJSValue singleton = e->singletonInstance(r.type); + QV4::ScopedObject o(scope, QJSValuePrivate::convertedToValue(v4, singleton)); lookup->qmlContextSingletonLookup.singleton = o->d(); } lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupSingleton; diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 840aeb534d..19c3469682 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1057,7 +1057,7 @@ QQmlEngine::~QQmlEngine() // XXX TODO: performance -- store list of singleton types separately? QList singletonTypes = QQmlMetaType::qmlSingletonTypes(); for (const QQmlType &currType : singletonTypes) - currType.singletonInstanceInfo()->destroy(this); + d->destroySingletonInstance(currType); delete d->rootContext; d->rootContext = nullptr; @@ -1402,23 +1402,13 @@ void QQmlEngine::setOutputWarningsToStandardError(bool enabled) template<> QJSValue QQmlEngine::singletonInstance(int qmlTypeId) { + Q_D(QQmlEngine); QQmlType type = QQmlMetaType::qmlType(qmlTypeId, QQmlMetaType::TypeIdCategory::QmlType); if (!type.isValid() || !type.isSingleton()) return QJSValue(); - QQmlType::SingletonInstanceInfo* info = type.singletonInstanceInfo(); - info->init(this); - - if (QObject* o = info->qobjectApi(this)) - return this->newQObject(o); - else { - QJSValue value = info->scriptApi(this); - if (!value.isUndefined()) - return value; - } - - return QJSValue(); + return d->singletonInstance(type); } /*! @@ -2445,6 +2435,67 @@ void QQmlEnginePrivate::unregisterInternalCompositeType(QV4::CompiledData::Compi m_compositeTypes.remove(compilationUnit->metaTypeId); } +template<> +QJSValue QQmlEnginePrivate::singletonInstance(const QQmlType &type) +{ + Q_Q(QQmlEngine); + + QJSValue value = singletonInstances.value(type); + if (!value.isUndefined()) { + return value; + } + + QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); + Q_ASSERT(siinfo != nullptr); + + if (siinfo->scriptCallback) { + value = siinfo->scriptCallback(q, q); + if (value.isQObject()) { + QObject *o = value.toQObject(); + // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) + // should behave identically to QML singleton types. + q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); + } + singletonInstances.insert(type, value); + + } else if (siinfo->qobjectCallback) { + QObject *o = siinfo->qobjectCallback(q, q); + if (!o) { + qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", + qPrintable(QString::fromUtf8(type.typeName()))); + } + // if this object can use a property cache, create it now + QQmlData::ensurePropertyCache(q, o); + // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) + // should behave identically to QML singleton types. + q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); + value = q->newQObject(o); + singletonInstances.insert(type, value); + + } else if (!siinfo->url.isEmpty()) { + QQmlComponent component(q, siinfo->url, QQmlComponent::PreferSynchronous); + QObject *o = component.beginCreate(q->rootContext()); + value = q->newQObject(o); + singletonInstances.insert(type, value); + component.completeCreate(); + } + + return value; +} + +void QQmlEnginePrivate::destroySingletonInstance(const QQmlType &type) +{ + Q_ASSERT(type.isSingleton() || type.isCompositeSingleton()); + + QObject* o = singletonInstances.take(type).toQObject(); + if (o) { + QQmlData *ddata = QQmlData::get(o, false); + if (type.singletonInstanceInfo()->url.isEmpty() && ddata && ddata->indestructible && ddata->explicitIndestructibleSet) + return; + delete o; + } +} + bool QQmlEnginePrivate::isTypeLoaded(const QUrl &url) const { return typeLoader.isTypeLoaded(url); diff --git a/src/qml/qml/qqmlengine.h b/src/qml/qml/qqmlengine.h index 871e6bd9b4..da91c8fa15 100644 --- a/src/qml/qml/qqmlengine.h +++ b/src/qml/qml/qqmlengine.h @@ -175,12 +175,7 @@ Q_QML_EXPORT QJSValue QQmlEngine::singletonInstance(int qmlTypeId); template T QQmlEngine::singletonInstance(int qmlTypeId) { - QJSValue instance = singletonInstance(qmlTypeId); - if (!instance.isQObject()) - return nullptr; - - QObject *object = instance.toQObject(); - return qobject_cast(object); + return qobject_cast(singletonInstance(qmlTypeId).toQObject()); } QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index dab4e54cd6..4f7fb79593 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -229,6 +229,10 @@ public: bool isTypeLoaded(const QUrl &url) const; bool isScriptLoaded(const QUrl &url) const; + template + T singletonInstance(const QQmlType &type); + void destroySingletonInstance(const QQmlType &type); + void sendQuit(); void sendExit(int retCode = 0); void warning(const QQmlError &); @@ -262,6 +266,8 @@ public: mutable QMutex networkAccessManagerMutex; private: + QHash singletonInstances; + // These members must be protected by a QQmlEnginePrivate::Locker as they are required by // the threaded loader. Only access them through their respective accessor methods. QHash m_compositeTypes; @@ -437,6 +443,14 @@ QQmlEnginePrivate *QQmlEnginePrivate::get(QV4::ExecutionEngine *e) return get(qmlEngine); } +template<> +Q_QML_PRIVATE_EXPORT QJSValue QQmlEnginePrivate::singletonInstance(const QQmlType &type); + +template +T QQmlEnginePrivate::singletonInstance(const QQmlType &type) { + return qobject_cast(singletonInstance(type).toQObject()); +} + QT_END_NAMESPACE #endif // QQMLENGINE_P_H diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index f5eef8d93b..26ca995756 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -51,70 +51,6 @@ QT_BEGIN_NAMESPACE -void QQmlType::SingletonInstanceInfo::init(QQmlEngine *e) -{ - if (scriptCallback && scriptApi(e).isUndefined()) { - QJSValue value = scriptCallback(e, e); - if (value.isQObject()) { - QObject *o = value.toQObject(); - // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) - // should behave identically to QML singleton types. - e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); - } - setScriptApi(e, value); - } else if (qobjectCallback && !qobjectApi(e)) { - QObject *o = qobjectCallback(e, e); - setQObjectApi(e, o); - if (!o) { - qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", qPrintable(typeName)); - } - // if this object can use a property cache, create it now - QQmlData::ensurePropertyCache(e, o); - // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) - // should behave identically to QML singleton types. - e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); - } else if (!url.isEmpty() && !qobjectApi(e)) { - QQmlComponent component(e, url, QQmlComponent::PreferSynchronous); - QObject *o = component.beginCreate(e->rootContext()); - setQObjectApi(e, o); - if (o) - component.completeCreate(); - } -} - -void QQmlType::SingletonInstanceInfo::destroy(QQmlEngine *e) -{ - // cleans up the engine-specific singleton instances if they exist. - scriptApis.remove(e); - QObject *o = qobjectApis.take(e); - if (o) { - QQmlData *ddata = QQmlData::get(o, false); - if (url.isEmpty() && ddata && ddata->indestructible && ddata->explicitIndestructibleSet) - return; - delete o; - } -} - -void QQmlType::SingletonInstanceInfo::setQObjectApi(QQmlEngine *e, QObject *o) -{ - qobjectApis.insert(e, o); -} - -QObject *QQmlType::SingletonInstanceInfo::qobjectApi(QQmlEngine *e) const -{ - return qobjectApis.value(e); -} - -void QQmlType::SingletonInstanceInfo::setScriptApi(QQmlEngine *e, const QJSValue &v) -{ - scriptApis.insert(e, v); -} - -QJSValue QQmlType::SingletonInstanceInfo::scriptApi(QQmlEngine *e) const -{ - return scriptApis.value(e); -} - QQmlTypePrivate::QQmlTypePrivate(QQmlType::RegistrationType type) : regType(type), iid(nullptr), typeId(0), listId(0), revision(0), containsRevisionedAttributes(false), baseMetaObject(nullptr), @@ -636,6 +572,16 @@ bool QQmlType::isCompositeSingleton() const return d && d->regType == CompositeSingletonType; } +bool QQmlType::isQObjectSingleton() const +{ + return d && d->regType == SingletonType && d->extraData.sd->singletonInstanceInfo->qobjectCallback; +} + +bool QQmlType::isQJSValueSingleton() const +{ + return d && d->regType == SingletonType && d->extraData.sd->singletonInstanceInfo->scriptCallback; +} + int QQmlType::typeId() const { return d ? d->typeId : -1; diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index 0e59b1be06..7b326ce1c7 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -118,6 +118,8 @@ public: bool isInterface() const; bool isComposite() const; bool isCompositeSingleton() const; + bool isQObjectSingleton() const; + bool isQJSValueSingleton() const; int typeId() const; int qListTypeId() const; @@ -138,28 +140,13 @@ public: int index() const; - class Q_QML_PRIVATE_EXPORT SingletonInstanceInfo + struct Q_QML_PRIVATE_EXPORT SingletonInstanceInfo { - public: - SingletonInstanceInfo() - : scriptCallback(nullptr), qobjectCallback(nullptr), instanceMetaObject(nullptr) {} - - QJSValue (*scriptCallback)(QQmlEngine *, QJSEngine *); - QObject *(*qobjectCallback)(QQmlEngine *, QJSEngine *); - const QMetaObject *instanceMetaObject; + QJSValue (*scriptCallback)(QQmlEngine *, QJSEngine *) = nullptr; + QObject *(*qobjectCallback)(QQmlEngine *, QJSEngine *) = nullptr; + const QMetaObject *instanceMetaObject = nullptr; QString typeName; QUrl url; // used by composite singletons - - void setQObjectApi(QQmlEngine *, QObject *); - QObject *qobjectApi(QQmlEngine *) const; - void setScriptApi(QQmlEngine *, const QJSValue &); - QJSValue scriptApi(QQmlEngine *) const; - - void init(QQmlEngine *); - void destroy(QQmlEngine *); - - QHash scriptApis; - QHash qobjectApis; }; SingletonInstanceInfo *singletonInstanceInfo() const; diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index b72d82ffbc..3e72f5b324 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -89,10 +89,8 @@ QObject* QQmlTypeWrapper::singletonObject() const if (!isSingleton()) return nullptr; - QQmlEngine *e = engine()->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = d()->type().singletonInstanceInfo(); - siinfo->init(e); - return siinfo->qobjectApi(e); + QQmlEnginePrivate *e = QQmlEnginePrivate::get(engine()->qmlEngine()); + return e->singletonInstance(d()->type()); } QVariant QQmlTypeWrapper::toVariant() const @@ -101,13 +99,12 @@ QVariant QQmlTypeWrapper::toVariant() const if (!isSingleton()) return QVariant(); - QQmlEngine *e = engine()->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = d()->type().singletonInstanceInfo(); - siinfo->init(e); - if (QObject *qobjectSingleton = siinfo->qobjectApi(e)) - return QVariant::fromValue(qobjectSingleton); + QQmlEnginePrivate *e = QQmlEnginePrivate::get(engine()->qmlEngine()); + const QQmlType type = d()->type(); + if (type.isQJSValueSingleton()) + return QVariant::fromValue(e->singletonInstance(type)); - return QVariant::fromValue(siinfo->scriptApi(e)); + return QVariant::fromValue(e->singletonInstance(type)); } @@ -195,50 +192,51 @@ ReturnedValue QQmlTypeWrapper::virtualGet(const Managed *m, PropertyKey id, cons // singleton types are handled differently to other types. if (type.isSingleton()) { - QQmlEngine *e = v4->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); - siinfo->init(e); - - QObject *qobjectSingleton = siinfo->qobjectApi(e); - if (qobjectSingleton) { - - // check for enum value - const bool includeEnums = w->d()->mode == Heap::QQmlTypeWrapper::IncludeEnums; - if (includeEnums && name->startsWithUpper()) { - bool ok = false; - int value = enumForSingleton(v4, name, qobjectSingleton, type, &ok); - if (ok) - return QV4::Value::fromInt32(value).asReturnedValue(); - - value = type.scopedEnumIndex(QQmlEnginePrivate::get(v4->qmlEngine()), name, &ok); - if (ok) { - Scoped enumWrapper(scope, v4->memoryManager->allocate()); - enumWrapper->d()->typePrivate = type.priv(); - QQmlType::refHandle(enumWrapper->d()->typePrivate); - enumWrapper->d()->scopeEnumIndex = value; - return enumWrapper.asReturnedValue(); + QQmlEnginePrivate *e = QQmlEnginePrivate::get(v4->qmlEngine()); + QJSValue scriptSingleton; + if (type.isQObjectSingleton() || type.isCompositeSingleton()) { + if (QObject *qobjectSingleton = e->singletonInstance(type)) { + // check for enum value + const bool includeEnums = w->d()->mode == Heap::QQmlTypeWrapper::IncludeEnums; + if (includeEnums && name->startsWithUpper()) { + bool ok = false; + int value = enumForSingleton(v4, name, qobjectSingleton, type, &ok); + if (ok) + return QV4::Value::fromInt32(value).asReturnedValue(); + + value = type.scopedEnumIndex(QQmlEnginePrivate::get(v4->qmlEngine()), name, &ok); + if (ok) { + Scoped enumWrapper(scope, v4->memoryManager->allocate()); + enumWrapper->d()->typePrivate = type.priv(); + QQmlType::refHandle(enumWrapper->d()->typePrivate); + enumWrapper->d()->scopeEnumIndex = value; + return enumWrapper.asReturnedValue(); + } } - } - // check for property. - bool ok; - const ReturnedValue result = QV4::QObjectWrapper::getQmlProperty(v4, context, qobjectSingleton, name, QV4::QObjectWrapper::IgnoreRevision, &ok); - if (hasProperty) - *hasProperty = ok; - - // Warn when attempting to access a lowercased enum value, singleton case - if (!ok && includeEnums && !name->startsWithUpper()) { - enumForSingleton(v4, name, qobjectSingleton, type, &ok); - if (ok) - return throwLowercaseEnumError(v4, name, type); - } + // check for property. + bool ok; + const ReturnedValue result = QV4::QObjectWrapper::getQmlProperty(v4, context, qobjectSingleton, name, QV4::QObjectWrapper::IgnoreRevision, &ok); + if (hasProperty) + *hasProperty = ok; + + // Warn when attempting to access a lowercased enum value, singleton case + if (!ok && includeEnums && !name->startsWithUpper()) { + enumForSingleton(v4, name, qobjectSingleton, type, &ok); + if (ok) + return throwLowercaseEnumError(v4, name, type); + } - return result; - } else if (!siinfo->scriptApi(e).isUndefined()) { - // NOTE: if used in a binding, changes will not trigger re-evaluation since non-NOTIFYable. - QV4::ScopedObject o(scope, QJSValuePrivate::convertedToValue(v4, siinfo->scriptApi(e))); - if (!!o) - return o->get(name); + return result; + } + } else if (type.isQJSValueSingleton()) { + QJSValue scriptSingleton = e->singletonInstance(type); + if (!scriptSingleton.isUndefined()) { + // NOTE: if used in a binding, changes will not trigger re-evaluation since non-NOTIFYable. + QV4::ScopedObject o(scope, QJSValuePrivate::convertedToValue(v4, scriptSingleton)); + if (!!o) + return o->get(name); + } } // Fall through to base implementation @@ -343,21 +341,22 @@ bool QQmlTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, return QV4::QObjectWrapper::setQmlProperty(scope.engine, context, ao, name, QV4::QObjectWrapper::IgnoreRevision, value); return false; } else if (type.isSingleton()) { - QQmlEngine *e = scope.engine->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); - siinfo->init(e); - - QObject *qobjectSingleton = siinfo->qobjectApi(e); - if (qobjectSingleton) { - return QV4::QObjectWrapper::setQmlProperty(scope.engine, context, qobjectSingleton, name, QV4::QObjectWrapper::IgnoreRevision, value); - } else if (!siinfo->scriptApi(e).isUndefined()) { - QV4::ScopedObject apiprivate(scope, QJSValuePrivate::convertedToValue(scope.engine, siinfo->scriptApi(e))); - if (!apiprivate) { - QString error = QLatin1String("Cannot assign to read-only property \"") + name->toQString() + QLatin1Char('\"'); - scope.engine->throwError(error); - return false; - } else { - return apiprivate->put(name, value); + QQmlEnginePrivate *e = QQmlEnginePrivate::get(scope.engine->qmlEngine()); + if (type.isQObjectSingleton() || type.isCompositeSingleton()) { + if (QObject *qobjectSingleton = e->singletonInstance(type)) + return QV4::QObjectWrapper::setQmlProperty(scope.engine, context, qobjectSingleton, name, QV4::QObjectWrapper::IgnoreRevision, value); + + } else { + QJSValue scriptSingleton = e->singletonInstance(type); + if (!scriptSingleton.isUndefined()) { + QV4::ScopedObject apiprivate(scope, QJSValuePrivate::convertedToValue(scope.engine, scriptSingleton)); + if (!apiprivate) { + QString error = QLatin1String("Cannot assign to read-only property \"") + name->toQString() + QLatin1Char('\"'); + scope.engine->throwError(error); + return false; + } else { + return apiprivate->put(name, value); + } } } } @@ -449,27 +448,25 @@ ReturnedValue QQmlTypeWrapper::virtualResolveLookupGetter(const Object *object, if (type.isValid()) { if (type.isSingleton()) { - QQmlEngine *e = engine->qmlEngine(); - QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); - siinfo->init(e); - - QObject *qobjectSingleton = siinfo->qobjectApi(e); - if (qobjectSingleton) { - - const bool includeEnums = w->d()->mode == Heap::QQmlTypeWrapper::IncludeEnums; - if (!includeEnums || !name->startsWithUpper()) { - QQmlData *ddata = QQmlData::get(qobjectSingleton, false); - if (ddata && ddata->propertyCache) { - ScopedValue val(scope, Value::fromReturnedValue(QV4::QObjectWrapper::wrap(engine, qobjectSingleton))); - QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobjectSingleton, qmlContext); - if (property) { - lookup->qobjectLookup.ic = This->internalClass(); - lookup->qobjectLookup.staticQObject = static_cast(val->heapObject()); - lookup->qobjectLookup.propertyCache = ddata->propertyCache; - lookup->qobjectLookup.propertyCache->addref(); - lookup->qobjectLookup.propertyData = property; - lookup->getter = QV4::QObjectWrapper::lookupGetter; - return lookup->getter(lookup, engine, *This); + QQmlEnginePrivate *e = QQmlEnginePrivate::get(engine->qmlEngine()); + if (type.isQObjectSingleton() || type.isCompositeSingleton()) { + if (QObject *qobjectSingleton = e->singletonInstance(type)) { + const bool includeEnums = w->d()->mode == Heap::QQmlTypeWrapper::IncludeEnums; + if (!includeEnums || !name->startsWithUpper()) { + QQmlData *ddata = QQmlData::get(qobjectSingleton, false); + if (ddata && ddata->propertyCache) { + ScopedValue val(scope, Value::fromReturnedValue(QV4::QObjectWrapper::wrap(engine, qobjectSingleton))); + QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobjectSingleton, qmlContext); + if (property) { + lookup->qobjectLookup.ic = This->internalClass(); + lookup->qobjectLookup.staticQObject = static_cast(val->heapObject()); + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = property; + lookup->getter = QV4::QObjectWrapper::lookupGetter; + return lookup->getter(lookup, engine, *This); + } + // Fall through to base implementation } // Fall through to base implementation } -- cgit v1.2.3 From 2cd99e2b69779c12309dd454584aa9546029323e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 18 Apr 2019 15:59:50 +1200 Subject: Fix memory leak with QQuickEventPoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store PointVelocityData by value in the QMap to avoid leaking memory. This also appears to be slightly faster than heap allocation, with the struct being relatively small (24 or 32 bytes depending on qreal). Fixes: QTBUG-73182 Change-Id: Ibd9374746b76fd5b78c23283d278b6af42907c96 Reviewed-by: Shawn Rutledge Reviewed-by: Jan Arve Sæther --- src/quick/items/qquickevents.cpp | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index c43eab6b8a..28b217b7b3 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -1135,10 +1135,10 @@ void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong times struct PointVelocityData { QVector2D velocity; QPointF pos; - ulong timestamp; + ulong timestamp = 0; }; -typedef QMap PointDataForPointIdMap; +typedef QMap PointDataForPointIdMap; Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData) static const int PointVelocityAgeLimit = 500; // milliseconds @@ -1149,42 +1149,36 @@ static const int PointVelocityAgeLimit = 500; // milliseconds */ QVector2D QQuickEventPoint::estimatedVelocity() const { - PointVelocityData *prevPoint = g_previousPointData->value(m_pointId); - if (!prevPoint) { + auto prevPointIt = g_previousPointData->find(m_pointId); + auto end = g_previousPointData->end(); + if (prevPointIt == end) { // cleanup events older than PointVelocityAgeLimit - auto end = g_previousPointData->end(); for (auto it = g_previousPointData->begin(); it != end; ) { - PointVelocityData *data = it.value(); - if (m_timestamp - data->timestamp > PointVelocityAgeLimit) { + if (m_timestamp - it->timestamp > PointVelocityAgeLimit) it = g_previousPointData->erase(it); - delete data; - } else { + else ++it; - } } - // TODO optimize: stop this dynamic memory thrashing - prevPoint = new PointVelocityData; - prevPoint->velocity = QVector2D(); - prevPoint->timestamp = 0; - prevPoint->pos = QPointF(); - g_previousPointData->insert(m_pointId, prevPoint); + prevPointIt = g_previousPointData->insert(m_pointId, PointVelocityData()); } - const ulong timeElapsed = m_timestamp - prevPoint->timestamp; + + auto &prevPoint = prevPointIt.value(); + const ulong timeElapsed = m_timestamp - prevPoint.timestamp; if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint return m_velocity; QVector2D newVelocity; - if (prevPoint->timestamp != 0) - newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed; + if (prevPoint.timestamp != 0) + newVelocity = QVector2D(m_scenePos - prevPoint.pos) / timeElapsed; // VERY simple kalman filter: does a weighted average // where the older velocities get less and less significant static const float KalmanGain = 0.7f; QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain); - prevPoint->velocity = filteredVelocity; - prevPoint->pos = m_scenePos; - prevPoint->timestamp = m_timestamp; + prevPoint.velocity = filteredVelocity; + prevPoint.pos = m_scenePos; + prevPoint.timestamp = m_timestamp; return filteredVelocity; } -- cgit v1.2.3 From 2821172d514469f99301c3592eec941db9ebc438 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Tue, 9 Apr 2019 11:40:10 +0200 Subject: Doc: improve Example Usage section of TableView - Make two sub-sections: C++ and QML - Add a TableModel example to the QML section Change-Id: Ib391b4c0a78e11f5130944b6ac99e20a5982a453 Reviewed-by: Richard Moe Gustavsen Reviewed-by: Venugopal Shivashankar --- .../doc/snippets/qml/tableview/cpp-tablemodel.cpp | 103 +++++++++++++++++++++ .../doc/snippets/qml/tableview/cpp-tablemodel.qml | 71 ++++++++++++++ .../doc/snippets/qml/tableview/qml-tablemodel.qml | 92 ++++++++++++++++++ .../doc/snippets/qml/tableview/tablemodel.cpp | 103 --------------------- .../doc/snippets/qml/tableview/tablemodel.qml | 71 -------------- src/quick/items/qquicktableview.cpp | 13 ++- 6 files changed, 277 insertions(+), 176 deletions(-) create mode 100644 src/quick/doc/snippets/qml/tableview/cpp-tablemodel.cpp create mode 100644 src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml create mode 100644 src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml delete mode 100644 src/quick/doc/snippets/qml/tableview/tablemodel.cpp delete mode 100644 src/quick/doc/snippets/qml/tableview/tablemodel.qml (limited to 'src') diff --git a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.cpp b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.cpp new file mode 100644 index 0000000000..ea9f76f131 --- /dev/null +++ b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + ** + ** Copyright (C) 2018 The Qt Company Ltd. + ** Contact: https://www.qt.io/licensing/ + ** + ** This file is part of the documentation of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:BSD$ + ** 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. + ** + ** BSD License Usage + ** Alternatively, you may use this file under the terms of the BSD license + ** as follows: + ** + ** "Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are + ** met: + ** * Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** * Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in + ** the documentation and/or other materials provided with the + ** distribution. + ** * Neither the name of The Qt Company Ltd nor the names of its + ** contributors may be used to endorse or promote products derived + ** from this software without specific prior written permission. + ** + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +//![0] +#include +#include +#include + +class TableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return 200; + } + + int columnCount(const QModelIndex & = QModelIndex()) const override + { + return 200; + } + + QVariant data(const QModelIndex &index, int role) const override + { + switch (role) { + case Qt::DisplayRole: + return QString("%1, %2").arg(index.column()).arg(index.row()); + default: + break; + } + + return QVariant(); + } + + QHash roleNames() const override + { + return { {Qt::DisplayRole, "display"} }; + } +}; + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType("TableModel", 0, 1, "TableModel"); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} + +#include "main.moc" +//![0] diff --git a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml new file mode 100644 index 0000000000..8a8ec94958 --- /dev/null +++ b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![0] +import QtQuick 2.12 +import TableModel 0.1 + +TableView { + anchors.fill: parent + columnSpacing: 1 + rowSpacing: 1 + clip: true + + model: TableModel {} + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + Text { + text: display + } + } +} +//![0] diff --git a/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml b/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml new file mode 100644 index 0000000000..a01a2a628a --- /dev/null +++ b/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![0] +import QtQuick 2.14 +import Qt.labs.qmlmodels 1.0 + +TableView { + anchors.fill: parent + columnSpacing: 1 + rowSpacing: 1 + clip: true + + model: TableModel { + TableModelColumn { display: "name" } + TableModelColumn { display: "color" } + + rows: [ + { + "name": "cat", + "color": "black" + }, + { + "name": "dog", + "color": "brown" + }, + { + "name": "bird", + "color": "white" + } + ] + } + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + border.width: 1 + + Text { + text: display + anchors.centerIn: parent + } + } +} +//![0] diff --git a/src/quick/doc/snippets/qml/tableview/tablemodel.cpp b/src/quick/doc/snippets/qml/tableview/tablemodel.cpp deleted file mode 100644 index ea9f76f131..0000000000 --- a/src/quick/doc/snippets/qml/tableview/tablemodel.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** - ** - ** Copyright (C) 2018 The Qt Company Ltd. - ** Contact: https://www.qt.io/licensing/ - ** - ** This file is part of the documentation of the Qt Toolkit. - ** - ** $QT_BEGIN_LICENSE:BSD$ - ** 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. - ** - ** BSD License Usage - ** Alternatively, you may use this file under the terms of the BSD license - ** as follows: - ** - ** "Redistribution and use in source and binary forms, with or without - ** modification, are permitted provided that the following conditions are - ** met: - ** * Redistributions of source code must retain the above copyright - ** notice, this list of conditions and the following disclaimer. - ** * Redistributions in binary form must reproduce the above copyright - ** notice, this list of conditions and the following disclaimer in - ** the documentation and/or other materials provided with the - ** distribution. - ** * Neither the name of The Qt Company Ltd nor the names of its - ** contributors may be used to endorse or promote products derived - ** from this software without specific prior written permission. - ** - ** - ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - ** - ** $QT_END_LICENSE$ - ** - ****************************************************************************/ - -//![0] -#include -#include -#include - -class TableModel : public QAbstractTableModel -{ - Q_OBJECT - -public: - - int rowCount(const QModelIndex & = QModelIndex()) const override - { - return 200; - } - - int columnCount(const QModelIndex & = QModelIndex()) const override - { - return 200; - } - - QVariant data(const QModelIndex &index, int role) const override - { - switch (role) { - case Qt::DisplayRole: - return QString("%1, %2").arg(index.column()).arg(index.row()); - default: - break; - } - - return QVariant(); - } - - QHash roleNames() const override - { - return { {Qt::DisplayRole, "display"} }; - } -}; - -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - - qmlRegisterType("TableModel", 0, 1, "TableModel"); - - QQmlApplicationEngine engine; - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - - return app.exec(); -} - -#include "main.moc" -//![0] diff --git a/src/quick/doc/snippets/qml/tableview/tablemodel.qml b/src/quick/doc/snippets/qml/tableview/tablemodel.qml deleted file mode 100644 index 8a8ec94958..0000000000 --- a/src/quick/doc/snippets/qml/tableview/tablemodel.qml +++ /dev/null @@ -1,71 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -//![0] -import QtQuick 2.12 -import TableModel 0.1 - -TableView { - anchors.fill: parent - columnSpacing: 1 - rowSpacing: 1 - clip: true - - model: TableModel {} - - delegate: Rectangle { - implicitWidth: 100 - implicitHeight: 50 - Text { - text: display - } - } -} -//![0] diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index fc7516ec66..5437b54b78 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -76,14 +76,23 @@ \section1 Example Usage + \section2 C++ Models + The following example shows how to create a model from C++ with multiple columns: - \snippet qml/tableview/tablemodel.cpp 0 + \snippet qml/tableview/cpp-tablemodel.cpp 0 And then how to use it from QML: - \snippet qml/tableview/tablemodel.qml 0 + \snippet qml/tableview/cpp-tablemodel.qml 0 + + \section2 QML Models + + For prototyping and displaying very simple data (from a web API, for + example), \l TableModel can be used: + + \snippet qml/tableview/qml-tablemodel.qml 0 \section1 Reusing items -- cgit v1.2.3 From b68554d4d453eab01203384f14dd2158e520ecd3 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Mon, 25 Mar 2019 12:47:15 +0100 Subject: QQuickTableView: sync geometry properties with syncView Ensure that properties that has to do with the layout stays in sync with the syncView. This is currently rowSpacing, columnSpacing, rowHeight, columnWidth, contentWidth and contentHeight. Change-Id: I5af29d7be6c30cefbfa7d2353f53359907c9405b Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 5437b54b78..82702b1a8f 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -615,6 +615,11 @@ void QQuickTableViewPrivate::updateContentWidth() { Q_Q(QQuickTableView); + if (syncHorizontally) { + q->QQuickFlickable::setContentWidth(syncView->contentWidth()); + return; + } + if (explicitContentWidth.isValid()) { // Don't calculate contentWidth when it // was set explicitly by the application. @@ -634,6 +639,11 @@ void QQuickTableViewPrivate::updateContentHeight() { Q_Q(QQuickTableView); + if (syncVertically) { + q->QQuickFlickable::setContentHeight(syncView->contentHeight()); + return; + } + if (explicitContentHeight.isValid()) { // Don't calculate contentHeight when it // was set explicitly by the application. @@ -1057,6 +1067,11 @@ qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column) if (explicitColumnWidth >= 0) return explicitColumnWidth; + if (syncHorizontally) { + if (syncView->d_func()->loadedColumns.contains(column)) + return syncView->d_func()->getColumnLayoutWidth(column); + } + // Iterate over the currently visible items in the column. The downside // of doing that, is that the column width will then only be based on the implicit // width of the currently loaded items (which can be different depending on which @@ -1086,6 +1101,11 @@ qreal QQuickTableViewPrivate::getRowLayoutHeight(int row) if (explicitRowHeight >= 0) return explicitRowHeight; + if (syncVertically) { + if (syncView->d_func()->loadedRows.contains(row)) + return syncView->d_func()->getRowLayoutHeight(row); + } + // Iterate over the currently visible items in the row. The downside // of doing that, is that the row height will then only be based on the implicit // height of the currently loaded items (which can be different depending on which @@ -1115,6 +1135,9 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) if (cachedColumnWidth.startIndex == column) return cachedColumnWidth.size; + if (syncHorizontally) + return syncView->d_func()->getColumnWidth(column); + if (columnWidthProvider.isUndefined()) return noExplicitColumnWidth; @@ -1149,6 +1172,9 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) if (cachedRowHeight.startIndex == row) return cachedRowHeight.size; + if (syncVertically) + return syncView->d_func()->getRowHeight(row); + if (rowHeightProvider.isUndefined()) return noExplicitRowHeight; @@ -1969,6 +1995,11 @@ void QQuickTableViewPrivate::syncSyncView() syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal; syncVertically = syncView && assignedSyncDirection & Qt::Vertical; + + if (syncHorizontally) + q->setColumnSpacing(syncView->columnSpacing()); + if (syncVertically) + q->setRowSpacing(syncView->rowSpacing()); } void QQuickTableViewPrivate::connectToModel() -- cgit v1.2.3 From 679642fcfaac25bf56613df0bf25afa11ed0d01b Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 29 Mar 2019 15:33:58 +0100 Subject: QQuickTableView: implement recursive updateTable() Now that a TableView can be inside a syncView hierarchy, we cannot update a table in isolation, but need to coordinate this with the other views. It's especially important that we update a parent syncView before a child syncView, to ensure that the parent has calculated all the necessary columns width and row heights. For that reason, we always update the table views starting from the top. Change-Id: Iba8ae7d28fa0bb2fbbad9f8fc7aa198e15b91872 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 44 +++++++++++++++++++++++++++++++++++ src/quick/items/qquicktableview_p_p.h | 4 ++++ 2 files changed, 48 insertions(+) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 82702b1a8f..8c64c11bb1 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1768,7 +1768,51 @@ void QQuickTableViewPrivate::scheduleRebuildTable(RebuildOptions options) { q_func()->polish(); } +QQuickTableView *QQuickTableViewPrivate::rootSyncView() const +{ + QQuickTableView *root = const_cast(q_func()); + while (QQuickTableView *view = root->d_func()->syncView) + root = view; + return root; +} + void QQuickTableViewPrivate::updatePolish() +{ + // We always start updating from the top of the syncView tree, since + // the layout of a syncView child will depend on the layout of the syncView. + // E.g when a new column is flicked in, the syncView should load and layout + // the column first, before any syncChildren gets a chance to do the same. + Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!"); + rootSyncView()->d_func()->updateTableRecursive(); +} + +bool QQuickTableViewPrivate::updateTableRecursive() +{ + if (polishing) { + // We're already updating the Table in this view, so + // we cannot continue. Signal this back by returning false. + // The caller can then choose to call "polish()" instead, to + // do the update later. + return false; + } + + updateTable(); + + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + syncChild_d->scheduledRebuildOptions |= rebuildOptions; + + const bool updated = syncChild_d->updateTableRecursive(); + if (!updated) + return false; + } + + rebuildOptions = RebuildOption::None; + + return true; +} + +void QQuickTableViewPrivate::updateTable() { // Whenever something changes, e.g viewport moves, spacing is set to a // new value, model changes etc, this function will end up being called. Here diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index c018356efa..13c69d0b43 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -310,6 +310,10 @@ public: inline int leftColumn() const { return loadedColumns.firstKey(); } inline int rightColumn() const { return loadedColumns.lastKey(); } + QQuickTableView *rootSyncView() const; + + bool updateTableRecursive(); + void updateTable(); void relayoutTable(); void relayoutTableItems(); -- cgit v1.2.3 From 138d5c82f11bdcd9c6594d1e539631b9560353ae Mon Sep 17 00:00:00 2001 From: Michal Klocek Date: Tue, 23 Apr 2019 13:13:14 +0200 Subject: Revert "Optimize some Lookups" This reverts commit 2beb77c81a1f3585c15099a09ba8b2192c6da824. To optimize lookup in case of heapObject we cast its value to Object. This unfortunately does not work well when the value type of heap object is actually a string. Task-number: QTBUG-75335 Change-Id: I55d7c9e0d41f3be617ca7141a4121de3a56f8eef Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4lookup.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index 54bce7d7b3..f8999342d1 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -242,9 +242,6 @@ ReturnedValue Lookup::getter0Inlinegetter0Inline(Lookup *l, ExecutionEngine *eng return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset)->asReturnedValue(); if (l->objectLookupTwoClasses.ic2 == o->internalClass) return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset2)->asReturnedValue(); - Value obj = Value::fromHeapObject(o); - Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); - return static_cast(obj).get(&static_cast(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); @@ -260,9 +257,6 @@ ReturnedValue Lookup::getter0Inlinegetter0MemberData(Lookup *l, ExecutionEngine return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset)->asReturnedValue(); if (l->objectLookupTwoClasses.ic2 == o->internalClass) return o->memberData->values.data()[l->objectLookupTwoClasses.offset2].asReturnedValue(); - Value obj = Value::fromHeapObject(o); - Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); - return static_cast(obj).get(&static_cast(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); @@ -278,9 +272,6 @@ ReturnedValue Lookup::getter0MemberDatagetter0MemberData(Lookup *l, ExecutionEng return o->memberData->values.data()[l->objectLookupTwoClasses.offset].asReturnedValue(); if (l->objectLookupTwoClasses.ic2 == o->internalClass) return o->memberData->values.data()[l->objectLookupTwoClasses.offset2].asReturnedValue(); - Value obj = Value::fromHeapObject(o); - Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); - return static_cast(obj).get(&static_cast(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); @@ -296,9 +287,7 @@ ReturnedValue Lookup::getterProtoTwoClasses(Lookup *l, ExecutionEngine *engine, return l->protoLookupTwoClasses.data->asReturnedValue(); if (l->protoLookupTwoClasses.protoId2 == o->internalClass->protoId) return l->protoLookupTwoClasses.data2->asReturnedValue(); - Value obj = Value::fromHeapObject(o); - Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); - return static_cast(obj).get(&static_cast(str)); + return getterFallback(l, engine, object); } l->getter = getterFallback; return getterFallback(l, engine, object); -- cgit v1.2.3 From 6b6b379135e5022a3425c3135cebb58fc0f3100e Mon Sep 17 00:00:00 2001 From: Kevin Funk Date: Thu, 25 Apr 2019 08:45:24 +0200 Subject: Fix crash in QQmlMetaType::freeUnusedTypesAndCaches() Fix crash when QQmlMetaType::freeUnusedTypesAndCaches() is being called during program exit, i.e. when the parent QJSEngine instance is being destructed during exit(). Sample backtrace: #0 QQmlMetaType::freeUnusedTypesAndCaches () at /home/kfunk/devel/src/qt5.12/qtdeclarative/src/qml/qml/qqmlmetatype.cpp:2600 #1 0x00007fffe12fce83 in QJSEnginePrivate::~QJSEnginePrivate (this=0x60c001c0b040, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtdeclarative/src/qml/jsapi/qjsengine.cpp:982 #2 0x00007fffe12fce9f in QJSEnginePrivate::~QJSEnginePrivate (this=0x60c001c0b040, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtdeclarative/src/qml/jsapi/qjsengine.cpp:980 #3 0x00007ffff53650c3 in QScopedPointerDeleter::cleanup (pointer=) at ../../include/QtCore/../../../../../src/qt5.12/qtbase/src/corelib/tools/qscopedpointer.h:52 #4 QScopedPointer >::~QScopedPointer (this=0x60300178b3a8, __in_chrg=) at ../../include/QtCore/../../../../../src/qt5.12/qtbase/src/corelib/tools/qscopedpointer.h:107 #5 QObject::~QObject (this=, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtbase/src/corelib/kernel/qobject.cpp:891 #6 0x00007fffe12ff572 in QJSEngine::~QJSEngine (this=0x60300178b3a0, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtdeclarative/src/qml/jsapi/qjsengine.cpp:379 #7 0x00007fffe12ff583 in QJSEngine::~QJSEngine (this=0x60300178b3a0, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtdeclarative/src/qml/jsapi/qjsengine.cpp:375 #8 0x00007ffff5363cc4 in QObjectPrivate::deleteChildren (this=this@entry=0x60b00016c380) at /home/kfunk/devel/src/qt5.12/qtbase/src/corelib/kernel/qobject.cpp:2010 #9 0x00007ffff53650f5 in QObject::~QObject (this=, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtbase/src/corelib/kernel/qobject.cpp:1032 #10 0x00007fffe103b43b in Grantlee::ScriptableTagLibrary::~ScriptableTagLibrary (this=0x607000ba4c00) at templates/lib/Grantlee_Templates_autogen/MTDBPGIEEV/../../../../../../../src/kf5/grantlee/templates/scriptabletags/scriptabletags.h:58 #11 0x00007fffe103b469 in Grantlee::ScriptableTagLibrary::~ScriptableTagLibrary (this=0x607000ba4c00) at templates/lib/Grantlee_Templates_autogen/MTDBPGIEEV/../../../../../../../src/kf5/grantlee/templates/scriptabletags/scriptabletags.h:58 #12 0x00007ffff5363cc4 in QObjectPrivate::deleteChildren (this=this@entry=0x60b00016c0c0) at /home/kfunk/devel/src/qt5.12/qtbase/src/corelib/kernel/qobject.cpp:2010 #13 0x00007ffff53650f5 in QObject::~QObject (this=, __in_chrg=) at /home/kfunk/devel/src/qt5.12/qtbase/src/corelib/kernel/qobject.cpp:1032 #14 0x00007fffe0fef704 in Grantlee::Engine::~Engine (this=0x60300178a2c0) at /home/kfunk/devel/src/kf5/grantlee/templates/lib/engine.cpp:60 #15 0x00007fffdf2e2482 in GrantleeTheme::Engine::~Engine (this=0x60300178a2c0) at /home/kfunk/devel/src/kf5/grantleetheme/src/grantleethemeengine.cpp:54 #16 0x00007fffdf2e24a9 in GrantleeTheme::Engine::~Engine (this=0x60300178a2c0) at /home/kfunk/devel/src/kf5/grantleetheme/src/grantleethemeengine.cpp:52 #17 0x00007ffff3f4f8d1 in MessageViewer::MessagePartRendererManager::~MessagePartRendererManager (this=0x7ffff40c8ab0 ) at /home/kfunk/devel/src/kf5/messagelib/messageviewer/src/messagepartthemes/default /messagepartrenderermanager.cpp:118 #18 0x00007ffff4b442ac in ?? () from /lib/x86_64-linux-gnu/libc.so.6 #19 0x00007ffff4b443da in exit () from /lib/x86_64-linux-gnu/libc.so.6 Also see: https://bugs.kde.org/show_bug.cgi?id=406871 Change-Id: If5676880c87f1fa2405701a439e1a0037dce045c Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlmetatype.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 1873a99a71..c0628c3523 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -2593,6 +2593,10 @@ void QQmlMetaType::freeUnusedTypesAndCaches() QMutexLocker lock(metaTypeDataLock()); QQmlMetaTypeData *data = metaTypeData(); + // in case this is being called during program exit, `data` might be destructed already + if (!data) + return; + { bool deletedAtLeastOneType; do { -- cgit v1.2.3 From 561a2cec9b95b22783a00b48078b532010357066 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 17 Apr 2019 15:15:52 +0200 Subject: Transform V4_ENABLE_JIT into a feature This way you can enable or disable the JIT when configuring Qt. The conditions for the availability of the JIT have also been cleaned up. There is no reason anymore to artificially restrict availability on x86 and x86_64. The reason for the existence of those clauses are old problems on windows that have been fixed by now. However, on arm and arm64, we need a specialization of the cacheFlush() function for each OS to be supported. Therefore, restrict to the systems for which such a specialization exists. iOS and tvOS are technically supported and you can enable the JIT via the feature flag now. Due to Apple's policy we disable it by default, though. Change-Id: I5fe2a2bf6799b2d11b7ae7c7a85962bcbf44f919 Reviewed-by: Simon Hausmann --- src/3rdparty/masm/masm-defs.pri | 4 --- src/qml/configure.json | 69 ++++++++++++++++++++++++++++++++++++ src/qml/jit/qv4assemblercommon.cpp | 4 --- src/qml/jit/qv4assemblercommon_p.h | 4 +-- src/qml/jit/qv4baselineassembler.cpp | 4 --- src/qml/jit/qv4baselineassembler_p.h | 2 ++ src/qml/jit/qv4baselinejit.cpp | 4 --- src/qml/jit/qv4baselinejit_p.h | 4 +-- src/qml/jsruntime/qv4engine.cpp | 2 +- src/qml/jsruntime/qv4engine_p.h | 4 +-- src/qml/jsruntime/qv4global_p.h | 46 +----------------------- src/qml/jsruntime/qv4vme_moth.cpp | 6 ++-- src/qml/qml.pro | 4 ++- src/qml/qtqmlglobal.h | 1 + 14 files changed, 85 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/src/3rdparty/masm/masm-defs.pri b/src/3rdparty/masm/masm-defs.pri index 08c46a7ac2..90a795c6ce 100644 --- a/src/3rdparty/masm/masm-defs.pri +++ b/src/3rdparty/masm/masm-defs.pri @@ -33,10 +33,6 @@ disassembler { DEFINES += WTF_USE_UDIS86=0 } -force-compile-jit { - DEFINES += V4_FORCE_COMPILE_JIT -} - INCLUDEPATH += $$PWD/disassembler INCLUDEPATH += $$PWD/disassembler/udis86 INCLUDEPATH += $$_OUT_PWD diff --git a/src/qml/configure.json b/src/qml/configure.json index c35f5be06b..ef6a9f5e7e 100644 --- a/src/qml/configure.json +++ b/src/qml/configure.json @@ -24,6 +24,52 @@ ], "qmake": "CONFIG += c++11" } + }, + "pointer_32bit": { + "label": "32bit pointers", + "type": "compile", + "test": { + "main": "static_assert(sizeof(void *) == 4, \"fail\");" + } + }, + "pointer_64bit": { + "label": "64bit pointers", + "type": "compile", + "test": { + "main": "static_assert(sizeof(void *) == 8, \"fail\");" + } + }, + "arm_thumb": { + "label": "THUMB mode on ARM", + "type": "compile", + "test": { + "main": [ + "#if defined(thumb2) || defined(__thumb2__)", + "# define THUMB_OK", + "#elif (defined(__thumb) || defined(__thumb__)) && __TARGET_ARCH_THUMB-0 == 4", + "# define THUMB_OK", + "#elif defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2", + "// clang 3.5 and later will set this if the core supports the Thumb-2 ISA.", + "# define THUMB_OK", + "#else", + "# error \"fail\"", + "#endif" + ] + } + }, + "arm_fp": { + "label": "Sufficiently recent FPU on ARM", + "type": "compile", + "test": { + "main": [ + "// if !defined(__ARM_FP) we might be on MSVC or we might have a device", + "// without an FPU.", + "// TODO: The latter case is not supported, but the test still succeeds.", + "#if defined(__ARM_FP) && (__ARM_FP <= 0x04)", + "# error \"fail\"", + "#endif" + ] + } } }, @@ -40,10 +86,32 @@ "condition": "features.network", "output": [ "publicFeature" ] }, + "qml-jit": { + "label": "QML just-in-time compiler", + "purpose": "Provides a JIT for QML and JavaScript", + "section": "QML", + "condition": [ + " (arch.i386 && tests.pointer_32bit) + || (arch.x86_64 && tests.pointer_64bit) + || (arch.arm && tests.pointer_32bit && tests.arm_fp && tests.arm_thumb + && (config.linux || config.ios || config.tvos || config.qnx)) + || (arch.arm64 && tests.pointer_64bit && tests.arm_fp + && (config.linux || config.ios || config.tvos || config.qnx || config.integrity))" + ], + "output": [ "privateFeature" ], + "autoDetect": "!config.ios && !config.tvos", + "comment": "On arm and arm64 we need a specialization of cacheFlush() for each OS to be + enabeled. Therefore the config white list. + Also Mind that e.g. x86_32 has arch.x86_64 but 32bit pointers. Therefore + the checks for architecture and pointer size. + Finally, ios and tvos can technically use the JIT but Apple does not allow + it. Therefore, it's disabled by default." + }, "qml-tracing": { "label": "QML tracing JIT support", "purpose": "Provides a JIT that uses trace information generated by the interpreter.", "section": "QML", + "condition": "features.qml-jit", "output": [ "privateFeature" ], "autoDetect": false }, @@ -140,6 +208,7 @@ "entries": [ "qml-network", "qml-debug", + "qml-jit", "qml-tracing", "qml-sequence-object", "qml-list-model", diff --git a/src/qml/jit/qv4assemblercommon.cpp b/src/qml/jit/qv4assemblercommon.cpp index dd810d9d70..800ee22cd7 100644 --- a/src/qml/jit/qv4assemblercommon.cpp +++ b/src/qml/jit/qv4assemblercommon.cpp @@ -53,8 +53,6 @@ #undef ENABLE_ALL_ASSEMBLERS_FOR_REFACTORING_PURPOSES -#ifdef V4_ENABLE_JIT - QT_BEGIN_NAMESPACE namespace QV4 { namespace JIT { @@ -366,5 +364,3 @@ void PlatformAssemblerCommon::storeInt32AsValue(int srcInt, Address destAddr) } // QV4 namepsace QT_END_NAMESPACE - -#endif // V4_ENABLE_JIT diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index d3d7eedae2..e5c2aff1a7 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -58,7 +58,7 @@ #include #include -#ifdef V4_ENABLE_JIT +QT_REQUIRE_CONFIG(qml_jit); QT_BEGIN_NAMESPACE @@ -735,6 +735,4 @@ private: QT_END_NAMESPACE -#endif // V4_ENABLE_JIT - #endif // QV4PLATFORMASSEMBLER_P_H diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index 238c11f478..5e34087ff5 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -55,8 +55,6 @@ #undef ENABLE_ALL_ASSEMBLERS_FOR_REFACTORING_PURPOSES -#ifdef V4_ENABLE_JIT - QT_BEGIN_NAMESPACE namespace QV4 { namespace JIT { @@ -1620,5 +1618,3 @@ void BaselineAssembler::ret() } // QV4 namepsace QT_END_NAMESPACE - -#endif // V4_ENABLE_JIT diff --git a/src/qml/jit/qv4baselineassembler_p.h b/src/qml/jit/qv4baselineassembler_p.h index 3bbaefd000..5e5d9d0672 100644 --- a/src/qml/jit/qv4baselineassembler_p.h +++ b/src/qml/jit/qv4baselineassembler_p.h @@ -55,6 +55,8 @@ #include #include +QT_REQUIRE_CONFIG(qml_jit); + QT_BEGIN_NAMESPACE namespace QV4 { diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 80155d7b20..60880419a6 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -42,8 +42,6 @@ #include #include -#ifdef V4_ENABLE_JIT - QT_USE_NAMESPACE using namespace QV4; using namespace QV4::JIT; @@ -913,5 +911,3 @@ void BaselineJIT::endInstruction(Instr::Type instr) { Q_UNUSED(instr); } - -#endif // V4_ENABLE_JIT diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 37ab37eac2..941e0cd408 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -56,7 +56,7 @@ #include #include -//QT_REQUIRE_CONFIG(qml_jit); +QT_REQUIRE_CONFIG(qml_jit); QT_BEGIN_NAMESPACE @@ -65,7 +65,6 @@ namespace JIT { class BaselineAssembler; -#ifdef V4_ENABLE_JIT class BaselineJIT final: public Moth::ByteCodeHandler { public: @@ -214,7 +213,6 @@ private: QScopedPointer as; QSet labels; }; -#endif // V4_ENABLE_JIT } // namespace JIT } // namespace QV4 diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 18927c637c..b9130aa7f8 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -165,7 +165,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) , m_engineId(engineSerial.fetchAndAddOrdered(1)) , regExpCache(nullptr) , m_multiplyWrappedQObjects(nullptr) -#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) +#if QT_CONFIG(qml_jit) , m_canAllocateExecutableMemory(OSAllocator::canAllocateExecutableMemory()) #endif { diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 6df4545014..d0c58eee8f 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -464,7 +464,7 @@ public: // but any time a QObject is wrapped a second time in another engine, we have to do // bookkeeping. MultiplyWrappedQObjectMap *m_multiplyWrappedQObjects; -#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) +#if QT_CONFIG(qml_jit) const bool m_canAllocateExecutableMemory; #endif @@ -595,7 +595,7 @@ public: bool canJIT(Function *f = nullptr) { -#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) +#if QT_CONFIG(qml_jit) if (!m_canAllocateExecutableMemory) return false; if (f) diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h index d47393b3bb..8a0ef5aa30 100644 --- a/src/qml/jsruntime/qv4global_p.h +++ b/src/qml/jsruntime/qv4global_p.h @@ -82,53 +82,9 @@ inline bool isfinite(double d) { return _finite(d); } inline double trunc(double d) { return d > 0 ? floor(d) : ceil(d); } #endif -// Decide whether to enable or disable the JIT - -// White list architectures -// -// NOTE: This should match the logic in qv4targetplatform_p.h! - -#if defined(Q_PROCESSOR_X86_32) && (QT_POINTER_SIZE == 4) \ - && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_FREEBSD)) -# define V4_ENABLE_JIT -#elif defined(Q_PROCESSOR_X86_64) && (QT_POINTER_SIZE == 8) \ - && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)) -# define V4_ENABLE_JIT -#elif defined(Q_PROCESSOR_ARM_32) && (QT_POINTER_SIZE == 4) \ - && (defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_FREEBSD) || defined(Q_OS_INTEGRITY)) -# if defined(thumb2) || defined(__thumb2__) || ((defined(__thumb) || defined(__thumb__)) && __TARGET_ARCH_THUMB-0 == 4) -# define V4_ENABLE_JIT -# elif defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 // clang 3.5 and later will set this if the core supports the Thumb-2 ISA. -# define V4_ENABLE_JIT -# endif -#elif defined(Q_PROCESSOR_ARM_64) && (QT_POINTER_SIZE == 8) -# if defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_INTEGRITY) -# define V4_ENABLE_JIT -# endif -//#elif defined(Q_PROCESSOR_MIPS_32) && defined(Q_OS_LINUX) -//# define V4_ENABLE_JIT -#endif - -// check FPU with double precision on ARM platform -#if (defined(Q_PROCESSOR_ARM_64) || defined(Q_PROCESSOR_ARM_32)) && defined(V4_ENABLE_JIT) && defined(__ARM_FP) && (__ARM_FP <= 0x04) -# undef V4_ENABLE_JIT -#endif - -// Black list some platforms -#if defined(V4_ENABLE_JIT) -#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) -# undef V4_ENABLE_JIT -#endif -#endif - -// For debug purposes: add CONFIG+=force-compile-jit to qmake's command-line to always compile the JIT. -#if defined(V4_FORCE_COMPILE_JIT) && !defined(V4_ENABLE_JIT) -# define V4_ENABLE_JIT -#endif - // Do certain things depending on whether the JIT is enabled or disabled -#ifdef V4_ENABLE_JIT +#if QT_CONFIG(qml_jit) #define ENABLE_YARR_JIT 1 #define ENABLE_JIT 1 #define ENABLE_ASSEMBLER 1 diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 98e4f4f7b9..71e35bdc07 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -61,7 +61,9 @@ #include "qv4alloca_p.h" +#if QT_CONFIG(qml_jit) #include +#endif #undef COUNT_INSTRUCTIONS @@ -499,7 +501,7 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) Profiling::FunctionCallProfiler profiler(engine, function); // start execution profiling QV4::Debugging::Debugger *debugger = engine->debugger(); -#ifdef V4_ENABLE_JIT +#if QT_CONFIG(qml_jit) if (debugger == nullptr) { if (function->jittedCode == nullptr) { if (engine->canJIT(function)) { @@ -514,7 +516,7 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) } } } -#endif // V4_ENABLE_JIT +#endif // QT_CONFIG(qml_jit) // interpreter if (debugger) diff --git a/src/qml/qml.pro b/src/qml/qml.pro index d96a1c285a..ca3282556e 100644 --- a/src/qml/qml.pro +++ b/src/qml/qml.pro @@ -70,7 +70,9 @@ include(parser/parser.pri) include(compiler/compiler.pri) include(jsapi/jsapi.pri) include(jsruntime/jsruntime.pri) -include(jit/jit.pri) +qtConfig(qml-jit) { + include(jit/jit.pri) +} include(qml/qml.pri) include(debugger/debugger.pri) include(qmldirparser/qmldirparser.pri) diff --git a/src/qml/qtqmlglobal.h b/src/qml/qtqmlglobal.h index 090b830b3c..348cfd86b8 100644 --- a/src/qml/qtqmlglobal.h +++ b/src/qml/qtqmlglobal.h @@ -53,6 +53,7 @@ #else # define QT_FEATURE_qml_debug -1 # define QT_FEATURE_qml_sequence_object 1 +# define QT_FEATURE_qml_jit -1 # define QT_FEATURE_qml_tracing -1 #endif -- cgit v1.2.3 From ef6c58722e6a639a6b48efba007b2f59822b826e Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Tue, 9 Apr 2019 14:55:27 +0200 Subject: QQuickTableView: update viewportMoved() to take syncView into account Now that several table views can stay in sync through the syncView parent-child chain, we also need to ensure that the position of the content views stays in sync. This patch will recursively go through all connected views when one of the views are moved and set the same position on them all according to the syncDirection flag. Change-Id: I5a5b8e795426484eeab3771f6c8d4c9b7da046eb Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 150 ++++++++++++++++++++++++++-------- src/quick/items/qquicktableview_p_p.h | 7 ++ 2 files changed, 122 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 8c64c11bb1..da097f8888 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1583,6 +1583,11 @@ void QQuickTableViewPrivate::beginRebuildTable() loadedTableInnerRect = QRect(); clearEdgeSizeCache(); + if (syncHorizontally) + setLocalViewportX(syncView->contentX()); + if (syncVertically) + setLocalViewportY(syncView->contentY()); + if (!model) { qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty"; return; @@ -2175,6 +2180,84 @@ void QQuickTableViewPrivate::modelResetCallback() scheduleRebuildTable(RebuildOption::All); } +void QQuickTableViewPrivate::scheduleRebuildIfFastFlick() +{ + Q_Q(QQuickTableView); + + // If the viewport has moved more than one page vertically or horizontally, we switch + // strategy from refilling edges around the current table to instead rebuild the table + // from scratch inside the new viewport. This will greatly improve performance when flicking + // a long distance in one go, which can easily happen when dragging on scrollbars. + + // Check the viewport moved more than one page vertically + if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) { + scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow; + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + } + + // Check the viewport moved more than one page horizontally + if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) { + scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn; + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + } +} + +void QQuickTableViewPrivate::setLocalViewportX(qreal contentX) +{ + // Set the new viewport position if changed, but don't trigger any + // rebuilds or updates. We use this function internally to distinguish + // external flicking from internal sync-ing of the content view. + Q_Q(QQuickTableView); + QBoolBlocker blocker(inSetLocalViewportPos, true); + + if (qFuzzyCompare(contentX, q->contentX())) + return; + + q->setContentX(contentX); +} + +void QQuickTableViewPrivate::setLocalViewportY(qreal contentY) +{ + // Set the new viewport position if changed, but don't trigger any + // rebuilds or updates. We use this function internally to distinguish + // external flicking from internal sync-ing of the content view. + Q_Q(QQuickTableView); + QBoolBlocker blocker(inSetLocalViewportPos, true); + + if (qFuzzyCompare(contentY, q->contentY())) + return; + + q->setContentY(contentY); +} + +void QQuickTableViewPrivate::syncViewportPosRecursive() +{ + Q_Q(QQuickTableView); + QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true); + + if (syncView) { + auto syncView_d = syncView->d_func(); + if (!syncView_d->inSyncViewportPosRecursive) { + if (syncHorizontally) + syncView_d->setLocalViewportX(q->contentX()); + if (syncVertically) + syncView_d->setLocalViewportY(q->contentY()); + syncView_d->syncViewportPosRecursive(); + } + } + + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + if (!syncChild_d->inSyncViewportPosRecursive) { + if (syncChild_d->syncHorizontally) + syncChild_d->setLocalViewportX(q->contentX()); + if (syncChild_d->syncVertically) + syncChild_d->setLocalViewportY(q->contentY()); + syncChild_d->syncViewportPosRecursive(); + } + } +} + QQuickTableView::QQuickTableView(QQuickItem *parent) : QQuickFlickable(*(new QQuickTableViewPrivate), parent) { @@ -2400,45 +2483,42 @@ void QQuickTableView::geometryChanged(const QRectF &newGeometry, const QRectF &o void QQuickTableView::viewportMoved(Qt::Orientations orientation) { Q_D(QQuickTableView); + + // If the new viewport position was set from the setLocalViewportXY() + // functions, we just update the position silently and return. Otherwise, if + // the viewport was flicked by the user, or some other control, we + // recursively sync all the views in the hierarchy to the same position. QQuickFlickable::viewportMoved(orientation); + if (d->inSetLocalViewportPos) + return; - QQuickTableViewPrivate::RebuildOptions options = QQuickTableViewPrivate::RebuildOption::None; + // Move all views in the syncView hierarchy to the same contentX/Y. + // We need to start from this view (and not the root syncView) to + // ensure that we respect all the individual syncDirection flags + // between the individual views in the hierarchy. + d->syncViewportPosRecursive(); - // Check the viewport moved more than one page vertically - if (!d->viewportRect.intersects(QRectF(d->viewportRect.x(), contentY(), 1, height()))) - options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow; - // Check the viewport moved more than one page horizontally - if (!d->viewportRect.intersects(QRectF(contentX(), d->viewportRect.y(), width(), 1))) - options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn; - - if (options) { - // When the viewport has moved more than one page vertically or horizontally, we switch - // strategy from refilling edges around the current table to instead rebuild the table - // from scratch inside the new viewport. This will greatly improve performance when flicking - // a long distance in one go, which can easily happen when dragging on scrollbars. - options |= QQuickTableViewPrivate::RebuildOption::ViewportOnly; - d->scheduleRebuildTable(options); - } - - if (d->scheduledRebuildOptions) { - // No reason to do anything, since we're about to rebuild the whole table anyway. - // Besides, calling updatePolish, which will start the rebuild, can easily cause - // binding loops to happen since we usually end up modifying the geometry of the - // viewport (contentItem) as well. - return; - } + auto rootView = d->rootSyncView(); + auto rootView_d = rootView->d_func(); - // Calling polish() will schedule a polish event. But while the user is flicking, several - // mouse events will be handled before we get an updatePolish() call. And the updatePolish() - // call will only see the last mouse position. This results in a stuttering flick experience - // (especially on windows). We improve on this by calling updatePolish() directly. But this - // has the pitfall that we open up for recursive callbacks. E.g while inside updatePolish(), we - // load/unload items, and emit signals. The application can listen to those signals and set a - // new contentX/Y on the flickable. So we need to guard for this, to avoid unexpected behavior. - if (d->polishing) - polish(); - else - d->updatePolish(); + rootView_d->scheduleRebuildIfFastFlick(); + + if (!rootView_d->polishScheduled) { + if (rootView_d->scheduledRebuildOptions) { + // When we need to rebuild, collecting several viewport + // moves and do a single polish gives a quicker UI. + rootView->polish(); + } else { + // Updating the table right away when flicking + // slowly gives a smoother experience. + const bool updated = rootView->d_func()->updateTableRecursive(); + if (!updated) { + // One, or more, of the views are already in an + // update, so we need to wait a cycle. + rootView->polish(); + } + } + } } void QQuickTableViewPrivate::_q_componentFinalized() diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 13c69d0b43..0b15e63342 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -251,6 +251,8 @@ public: bool polishing = false; bool syncVertically = false; bool syncHorizontally = false; + bool inSetLocalViewportPos = false; + bool inSyncViewportPosRecursive = false; QJSValue rowHeightProvider; QJSValue columnWidthProvider; @@ -394,6 +396,11 @@ public: void layoutChangedCallback(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); void modelResetCallback(); + void scheduleRebuildIfFastFlick(); + void setLocalViewportX(qreal contentX); + void setLocalViewportY(qreal contentY); + void syncViewportPosRecursive(); + void _q_componentFinalized(); void registerCallbackWhenBindingsAreEvaluated(); -- cgit v1.2.3 From 0bb72db9aa381cb53e419cda9d51cd3fc6f0cb5f Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 16 Apr 2019 15:40:26 +0200 Subject: Fix line number precision for a binding's expressionIdentifier Location::line is 20 bits, so assigning it to a 16 unsigned integer truncates the number unnecessarily. Change-Id: I50a7ec73d6a88505c7d41b5a2b7ceb726b9dd437 Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlbinding.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index b164517011..656c7dd515 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -517,9 +517,9 @@ QString QQmlBinding::expressionIdentifier() const { if (auto f = function()) { QString url = f->sourceFile(); - quint16 lineNumber = f->compiledFunction->location.line; - quint16 columnNumber = f->compiledFunction->location.column; - return url + QString::asprintf(":%u:%u", uint(lineNumber), uint(columnNumber)); + uint lineNumber = f->compiledFunction->location.line; + uint columnNumber = f->compiledFunction->location.column; + return url + QString::asprintf(":%u:%u", lineNumber, columnNumber); } return QStringLiteral("[native code]"); -- cgit v1.2.3 From 64a6677ae612f0af01cbec0b8b2a2dd625bcf85b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 26 Apr 2019 14:43:48 +0200 Subject: Fix compile error with qml_tracing enabled "hasTry" doesn't exist. Change-Id: Ia6df66406e296c7623fa872ef32acc46d93b3319 Reviewed-by: Erik Verbruggen --- src/qml/compiler/qv4compilercontext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index b9ab4e5173..ca2d5128f4 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -428,7 +428,7 @@ bool Context::canUseTracingJit() const //### the next condition should be refined and have the IR distinguish between escaping and // non-escaping locals - return !hasTry && !requiresExecutionContext && !hasNestedFunctions; + return !requiresExecutionContext && !hasNestedFunctions; #else return false; #endif -- cgit v1.2.3 From eb363c3a0b7f96015d7b8f2551dbeaa86f5acf16 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 26 Apr 2019 09:54:28 +0200 Subject: Fix maximum call stack depth limits for ASAN builds ASAN enabled builds require more stack space and therefore our call depth limits should be lower. In my measurements with a recursion through arrow functions with the interpreter, as per the test case in the bug report, different types of builds require different amounts of stack space. On x86-64 Linux, I measured, by printing $rsp and subtracting: Debug: ~6k Debug with -Og: ~590 bytes Release with -O2: ~570 bytes Release (-O2) with ASAN: ~40k Fixes: QTBUG-75410 Change-Id: I403b261c677b1adb9f349958339b5a1294ae4d5d Reviewed-by: Erik Verbruggen Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index bd1124beb6..966ff12506 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -169,7 +169,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) bool ok = false; maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok); if (!ok || maxCallDepth <= 0) { -#ifdef QT_NO_DEBUG +#if defined(QT_NO_DEBUG) && !defined(__SANITIZE_ADDRESS__) maxCallDepth = 1234; #else // no (tail call) optimization is done, so there'll be a lot mare stack frames active -- cgit v1.2.3 From 3a349ea9229eae7e3e189e92e3124b169fe4f447 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 25 Apr 2019 15:07:33 +0200 Subject: Don't call unknown types "null" That is rather confusing. Task-number: QTBUG-74815 Change-Id: Id683a7f9efd63c8859c5740ceab9f161cea46ee3 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4qobjectwrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 5467e730e3..6fed538fa8 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -565,9 +565,9 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP QQmlContextData *callingQmlContext = scope.engine->callingQmlContext(); if (!QQmlPropertyPrivate::write(object, *property, v, callingQmlContext)) { - const char *valueType = nullptr; - if (v.userType() == QVariant::Invalid) valueType = "null"; - else valueType = QMetaType::typeName(v.userType()); + const char *valueType = (v.userType() == QMetaType::UnknownType) + ? "an unknown type" + : QMetaType::typeName(v.userType()); const char *targetTypeName = QMetaType::typeName(property->propType()); if (!targetTypeName) -- cgit v1.2.3 From dbbbbe569da0aa3900a17ca22bd2be2708fb76f7 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 25 Apr 2019 15:21:19 +0200 Subject: Allow creation of variants from non-singleton QQmlTypeWrappers I don't see any reason why this should be prohibited. Change-Id: I4a54c55eff4b9151691d0587627efad4a06485f1 Fixes: QTBUG-74815 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmltypewrapper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index 246de04316..24c5aecc00 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -97,9 +97,8 @@ QObject* QQmlTypeWrapper::singletonObject() const QVariant QQmlTypeWrapper::toVariant() const { - // Only Singleton type wrappers can be converted to a variant. if (!isSingleton()) - return QVariant(); + return QVariant::fromValue(d()->object); QQmlEngine *e = engine()->qmlEngine(); QQmlType::SingletonInstanceInfo *siinfo = d()->type().singletonInstanceInfo(); -- cgit v1.2.3 From 8a5001247b250a7f7cb938743481b1405dcdc3a0 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 1 Apr 2019 18:01:41 +0200 Subject: MultiPointHandler: eliminate "no points" warning with native gestures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In handlePointerEventImpl, there is the call d->centroid.reset(d->currentPoints); with the expectation that currentPoints is not empty. But we weren't populating it in case of a native gesture. It still ends up being empty at the end of the gesture, but it's normal to return false from wantsPointerEvent() when there are no eligible points. Fixes: QTBUG-70083 Change-Id: I12ca6460a24d2eb6c44a639f83ce3ff17eb37613 Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickmultipointhandler.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index baa68e5e53..6fbab29077 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -70,14 +70,15 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) return false; -#if QT_CONFIG(gestures) - if (event->asPointerNativeGestureEvent()) - return true; -#endif - if (event->asPointerScrollEvent()) return false; + bool ret = false; +#if QT_CONFIG(gestures) + if (event->asPointerNativeGestureEvent() && event->point(0)->state() != QQuickEventPoint::Released) + ret = true; +#endif + // If points were pressed or released within parentItem, reset stored state // and check eligible points again. This class of handlers is intended to // handle a specific number of points, so a differing number of points will @@ -97,7 +98,7 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) return true; } - const bool ret = (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); + ret = ret || (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); if (ret) { const int c = candidatePoints.count(); m_currentPoints.resize(c); -- cgit v1.2.3 From 0bcaed279fc303ffd0fd6e77b0ebc83a4519ac74 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 27 Mar 2019 13:23:31 +0100 Subject: QQuickTableView: update calculateTopLeft() to take syncView into account calculateTopLeft() takes care of finding which cell should be the 'corner stone' that needs to be loaded first when doing a rebuild. When we have a syncView, the top left cell should match the top left cell of the syncView, so the logic needs to change quite a bit to take this into account. Change-Id: Ia0b621a3155bbd113fa37c2ed585f16627d46443 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 129 +++++++++++++++++++++++++--------- src/quick/items/qquicktableview_p_p.h | 1 - 2 files changed, 97 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index da097f8888..c30d40138e 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1524,48 +1524,103 @@ bool QQuickTableViewPrivate::moveToNextRebuildState() return true; } -QPoint QQuickTableViewPrivate::calculateNewTopLeft() -{ - const int firstVisibleLeft = nextVisibleEdgeIndex(Qt::RightEdge, 0); - const int firstVisibleTop = nextVisibleEdgeIndex(Qt::BottomEdge, 0); - - return QPoint(firstVisibleLeft, firstVisibleTop); -} - -void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos) +void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topLeftPos) { if (tableSize.isEmpty()) { - releaseLoadedItems(QQmlTableInstanceModel::NotReusable); - topLeft = QPoint(kEdgeIndexAtEnd, kEdgeIndexAtEnd); + // There is no cell that can be top left + topLeftCell.rx() = kEdgeIndexAtEnd; + topLeftCell.ry() = kEdgeIndexAtEnd; return; } - if (rebuildOptions & RebuildOption::All) { - qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All"; - releaseLoadedItems(QQmlTableInstanceModel::NotReusable); - topLeft = calculateNewTopLeft(); - } else if (rebuildOptions & RebuildOption::ViewportOnly) { - qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly"; - releaseLoadedItems(reusableFlag); + if (syncHorizontally || syncVertically) { + const auto syncView_d = syncView->d_func(); - if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) { - const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height())); - topLeft.ry() = qBound(0, newRow, tableSize.height() - 1); - topLeftPos.ry() = topLeft.y() * (averageEdgeSize.height() + cellSpacing.height()); - } else { - topLeft.ry() = qBound(0, topRow(), tableSize.height() - 1); - topLeftPos.ry() = loadedTableOuterRect.topLeft().y(); + if (syncView_d->loadedItems.isEmpty()) { + // The sync view contains no loaded items. This probably means + // that it has not been rebuilt yet. Which also means that + // we cannot rebuild anything before this happens. + topLeftCell.rx() = kEdgeIndexNotSet; + topLeftCell.ry() = kEdgeIndexNotSet; + return; } - if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) { + + // Get sync view top left, and use that as our own top left (if possible) + const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow()); + const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell); + const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft(); + + if (syncHorizontally) { + topLeftCell.rx() = syncViewTopLeftCell.x(); + topLeftPos.rx() = syncViewTopLeftPos.x(); + + if (topLeftCell.x() >= tableSize.width()) { + // Top left is outside our own model. + topLeftCell.rx() = kEdgeIndexAtEnd; + topLeftPos.rx() = kEdgeIndexAtEnd; + } + } + + if (syncVertically) { + topLeftCell.ry() = syncViewTopLeftCell.y(); + topLeftPos.ry() = syncViewTopLeftPos.y(); + + if (topLeftCell.y() >= tableSize.height()) { + // Top left is outside our own model. + topLeftCell.ry() = kEdgeIndexAtEnd; + topLeftPos.ry() = kEdgeIndexAtEnd; + } + } + + if (syncHorizontally && syncVertically) { + // We have a valid top left, so we're done + return; + } + } + + // Since we're not sync-ing both horizontal and vertical, calculate the missing + // dimention(s) ourself. If we rebuild all, we find the first visible top-left + // item starting from cell(0, 0). Otherwise, guesstimate which row or column that + // should be the new top-left given the geometry of the viewport. + + if (!syncHorizontally) { + if (rebuildOptions & RebuildOption::All) { + // Find the first visible column from the beginning + topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0); + if (topLeftCell.x() == kEdgeIndexAtEnd) { + // No visible column found + return; + } + } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) { + // Guesstimate new top left const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width())); - topLeft.rx() = qBound(0, newColumn, tableSize.width() - 1); - topLeftPos.rx() = topLeft.x() * (averageEdgeSize.width() + cellSpacing.width()); + topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1); + topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width()); } else { - topLeft.rx() = qBound(0, leftColumn(), tableSize.width() - 1); + // Keep the current top left, unless it's outside model + topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1); topLeftPos.rx() = loadedTableOuterRect.topLeft().x(); } - } else { - Q_TABLEVIEW_UNREACHABLE(rebuildOptions); + } + + if (!syncVertically) { + if (rebuildOptions & RebuildOption::All) { + // Find the first visible row from the beginning + topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0); + if (topLeftCell.y() == kEdgeIndexAtEnd) { + // No visible row found + return; + } + } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) { + // Guesstimate new top left + const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height())); + topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1); + topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height()); + } else { + // Keep the current top left, unless it's outside model + topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1); + topLeftPos.ry() = loadedTableOuterRect.topLeft().y(); + } } } @@ -1577,6 +1632,11 @@ void QQuickTableViewPrivate::beginRebuildTable() QPointF topLeftPos; calculateTopLeft(topLeft, topLeftPos); + if (rebuildOptions & RebuildOption::All) + releaseLoadedItems(QQmlTableInstanceModel::NotReusable); + else if (rebuildOptions & RebuildOption::ViewportOnly) + releaseLoadedItems(reusableFlag); + loadedColumns.clear(); loadedRows.clear(); loadedTableOuterRect = QRect(); @@ -1604,7 +1664,12 @@ void QQuickTableViewPrivate::beginRebuildTable() } if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) { - qCDebug(lcTableViewDelegateLifecycle()) << "no visible rows or columns, leaving table empty"; + qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty"; + return; + } + + if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) { + qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty"; return; } diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 0b15e63342..ab7c0c3275 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -362,7 +362,6 @@ public: void processRebuildTable(); bool moveToNextRebuildState(); - QPoint calculateNewTopLeft(); void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos); void beginRebuildTable(); void layoutAfterLoadingInitialTable(); -- cgit v1.2.3 From 3d55d182704e2c2b01aa3e5fc159507e322dd281 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 13 Mar 2019 09:50:14 +0100 Subject: Add BoundaryRule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ChangeLog][Qt Labs Animation] Added the BoundaryRule QML type, a PropertyValueInterceptor that restricts the range of values a numeric property can have, applies "resistance" when the value is overshooting, and provides the ability to animate it back within range. Change-Id: I677b407a351c12b0c5b23c34a45933154310c2cd Reviewed-by: Jan Arve Sæther --- src/imports/imports.pro | 1 + src/imports/labsanimation/dependencies.json | 2 + src/imports/labsanimation/labsanimation.pro | 11 + src/imports/labsanimation/plugin.cpp | 81 ++++ src/imports/labsanimation/plugins.qmltypes | 36 ++ src/imports/labsanimation/qmldir | 3 + src/quick/doc/snippets/qml/boundaryRule.qml | 74 ++++ src/quick/util/qquickboundaryrule.cpp | 574 ++++++++++++++++++++++++++++ src/quick/util/qquickboundaryrule_p.h | 145 +++++++ src/quick/util/util.pri | 2 + 10 files changed, 929 insertions(+) create mode 100644 src/imports/labsanimation/dependencies.json create mode 100644 src/imports/labsanimation/labsanimation.pro create mode 100644 src/imports/labsanimation/plugin.cpp create mode 100644 src/imports/labsanimation/plugins.qmltypes create mode 100644 src/imports/labsanimation/qmldir create mode 100644 src/quick/doc/snippets/qml/boundaryRule.qml create mode 100644 src/quick/util/qquickboundaryrule.cpp create mode 100644 src/quick/util/qquickboundaryrule_p.h (limited to 'src') diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 24e93fec1c..9b1cfa6aa8 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -4,6 +4,7 @@ SUBDIRS += \ builtins \ qtqml \ models \ + labsanimation \ labsmodels qtConfig(thread): SUBDIRS += folderlistmodel diff --git a/src/imports/labsanimation/dependencies.json b/src/imports/labsanimation/dependencies.json new file mode 100644 index 0000000000..0d4f101c7a --- /dev/null +++ b/src/imports/labsanimation/dependencies.json @@ -0,0 +1,2 @@ +[ +] diff --git a/src/imports/labsanimation/labsanimation.pro b/src/imports/labsanimation/labsanimation.pro new file mode 100644 index 0000000000..64e076401f --- /dev/null +++ b/src/imports/labsanimation/labsanimation.pro @@ -0,0 +1,11 @@ +CXX_MODULE = qml +TARGET = labsanimationplugin +TARGETPATH = Qt/labs/animation +IMPORT_VERSION = 1.0 + +SOURCES += \ + plugin.cpp + +QT = qml-private quick-private + +load(qml_plugin) diff --git a/src/imports/labsanimation/plugin.cpp b/src/imports/labsanimation/plugin.cpp new file mode 100644 index 0000000000..d8c0c071ca --- /dev/null +++ b/src/imports/labsanimation/plugin.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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 +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule Qt.labs.animation 1.0 + \title Qt Quick experimental animation types + \ingroup qmlmodules + \brief Provides QML experimental types for animation + \since 5.14 + + This QML module contains experimental QML types related to animation. + + To use the types in this module, import the module with the following line: + + \code + import Qt.labs.animation 1.0 + \endcode +*/ + +//![class decl] +class QtLabsAnimationPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QtLabsAnimationPlugin(QObject *parent = nullptr) : QQmlExtensionPlugin(parent) { } + void registerTypes(const char *uri) override + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.animation")); + qmlRegisterType(uri, 1, 0, "BoundaryRule"); + qmlRegisterModule(uri, 1, 0); + } +}; +//![class decl] + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/labsanimation/plugins.qmltypes b/src/imports/labsanimation/plugins.qmltypes new file mode 100644 index 0000000000..065e65ad7a --- /dev/null +++ b/src/imports/labsanimation/plugins.qmltypes @@ -0,0 +1,36 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable Qt.labs.animation 1.0' + +Module { + dependencies: ["QtQuick 2.0"] + Component { + name: "QQuickBoundaryRule" + prototype: "QObject" + exports: ["Qt.labs.animation/BoundaryRule 1.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "OvershootFilter" + values: { + "None": 0, + "Peak": 1 + } + } + Property { name: "enabled"; type: "bool" } + Property { name: "minimum"; type: "double" } + Property { name: "minimumOvershoot"; type: "double" } + Property { name: "maximum"; type: "double" } + Property { name: "maximumOvershoot"; type: "double" } + Property { name: "overshootScale"; type: "double" } + Property { name: "currentOvershoot"; type: "double"; isReadonly: true } + Property { name: "peakOvershoot"; type: "double"; isReadonly: true } + Property { name: "overshootFilter"; type: "OvershootFilter" } + Property { name: "easing"; type: "QEasingCurve" } + Property { name: "returnDuration"; type: "int" } + Method { name: "returnToBounds"; type: "bool" } + } +} diff --git a/src/imports/labsanimation/qmldir b/src/imports/labsanimation/qmldir new file mode 100644 index 0000000000..b24fc98bfa --- /dev/null +++ b/src/imports/labsanimation/qmldir @@ -0,0 +1,3 @@ +module Qt.labs.animation +plugin labsanimationplugin +classname QtLabsAnimationPlugin diff --git a/src/quick/doc/snippets/qml/boundaryRule.qml b/src/quick/doc/snippets/qml/boundaryRule.qml new file mode 100644 index 0000000000..c010f5de5e --- /dev/null +++ b/src/quick/doc/snippets/qml/boundaryRule.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//![0] +import QtQuick 2.14 +import Qt.labs.animation 1.0 + +Rectangle { + id: root + width: 170; height: 120 + color: "green" + + DragHandler { + id: dragHandler + yAxis.minimum: -1000 + xAxis.minimum: -1000 + onActiveChanged: if (!active) xbr.returnToBounds(); + } + + BoundaryRule on x { + id: xbr + minimum: -50 + maximum: 100 + minimumOvershoot: 40 + maximumOvershoot: 40 + } +} +//![0] diff --git a/src/quick/util/qquickboundaryrule.cpp b/src/quick/util/qquickboundaryrule.cpp new file mode 100644 index 0000000000..3558c8bfa0 --- /dev/null +++ b/src/quick/util/qquickboundaryrule.cpp @@ -0,0 +1,574 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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 "qquickboundaryrule_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcBR, "qt.quick.boundaryrule") + +class QQuickBoundaryReturnJob; +class QQuickBoundaryRulePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickBoundaryRule) +public: + QQuickBoundaryRulePrivate() {} + + QQmlProperty property; + QEasingCurve easing = QEasingCurve(QEasingCurve::OutQuad); + QQuickBoundaryReturnJob *returnAnimationJob = nullptr; + // read-only properties, updated on each write() + qreal targetValue = 0; // after easing was applied + qreal peakOvershoot = 0; + qreal currentOvershoot = 0; + // settable properties + qreal minimum = 0; + qreal maximum = 0; + qreal minimumOvershoot = 0; + qreal maximumOvershoot = 0; + qreal overshootScale = 0.5; + int returnDuration = 100; + QQuickBoundaryRule::OvershootFilter overshootFilter = QQuickBoundaryRule::OvershootFilter::None; + bool enabled = true; + bool finalized = false; + + qreal easedOvershoot(qreal overshootingValue); + void resetOvershoot(); +}; + +class QQuickBoundaryReturnJob : public QAbstractAnimationJob +{ +public: + QQuickBoundaryReturnJob(QQuickBoundaryRulePrivate *br, qreal to) + : QAbstractAnimationJob() + , boundaryRule(br) + , fromValue(br->targetValue) + , toValue(to) {} + + int duration() const override { return boundaryRule->returnDuration; } + + void updateCurrentTime(int) override; + + void updateState(QAbstractAnimationJob::State newState, + QAbstractAnimationJob::State oldState) override; + + QQuickBoundaryRulePrivate *boundaryRule; + qreal fromValue; // snapshot of initial value from which we're returning + qreal toValue; // target property value to which we're returning +}; + +void QQuickBoundaryReturnJob::updateCurrentTime(int t) +{ + // The easing property tells how to behave when the property is being + // externally manipulated beyond the bounds. During returnToBounds() + // we run it in reverse, by reversing time. + qreal progress = (duration() - t) / qreal(duration()); + qreal easingValue = boundaryRule->easing.valueForProgress(progress); + qreal delta = qAbs(fromValue - toValue) * easingValue; + qreal value = (fromValue > toValue ? toValue + delta : toValue - delta); + qCDebug(lcBR) << t << "ms" << qRound(progress * 100) << "% easing" << easingValue << "->" << value; + QQmlPropertyPrivate::write(boundaryRule->property, value, + QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); +} + +void QQuickBoundaryReturnJob::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) +{ + Q_UNUSED(oldState) + if (newState == QAbstractAnimationJob::Stopped) { + qCDebug(lcBR) << "return animation done"; + boundaryRule->resetOvershoot(); + boundaryRule->returnAnimationJob = nullptr; + delete this; + } +} + +/*! + \qmltype BoundaryRule + \instantiates QQuickBoundaryRule + \inqmlmodule Qt.labs.animation + \ingroup qtquick-transitions-animations + \ingroup qtquick-interceptors + \brief Defines a restriction on the range of values that can be set on a numeric property. + \since 5.14 + + A BoundaryRule defines the range of values that a particular property is + allowed to have. When an out-of-range value would otherwise be set, + it applies "resistance" via an easing curve. + + For example, the following BoundaryRule prevents DragHandler from dragging + the Rectangle too far: + + \snippet qml/boundaryRule.qml 0 + + Note that a property cannot have more than one assigned BoundaryRule. + + \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt QML} +*/ + +QQuickBoundaryRule::QQuickBoundaryRule(QObject *parent) + : QObject(*(new QQuickBoundaryRulePrivate), parent) + , QQmlPropertyValueInterceptor() +{ +} + +QQuickBoundaryRule::~QQuickBoundaryRule() +{ + Q_D(QQuickBoundaryRule); + // stop any running animation and + // prevent QQuickBoundaryReturnJob::updateState() from accessing QQuickBoundaryRulePrivate + delete d->returnAnimationJob; +} + +/*! + \qmlproperty bool QtQuick::BoundaryRule::enabled + + This property holds whether the rule will be enforced when the tracked + property changes value. + + By default a BoundaryRule is enabled. +*/ +bool QQuickBoundaryRule::enabled() const +{ + Q_D(const QQuickBoundaryRule); + return d->enabled; +} + +void QQuickBoundaryRule::setEnabled(bool enabled) +{ + Q_D(QQuickBoundaryRule); + if (d->enabled == enabled) + return; + d->enabled = enabled; + emit enabledChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::minimum + + This property holds the smallest unconstrained value that the property is + allowed to have. If the property is set to a smaller value, it will be + constrained by \l easing and \l minimumOvershoot. + + The default is \c 0. +*/ +qreal QQuickBoundaryRule::minimum() const +{ + Q_D(const QQuickBoundaryRule); + return d->minimum; +} + +void QQuickBoundaryRule::setMinimum(qreal minimum) +{ + Q_D(QQuickBoundaryRule); + if (qFuzzyCompare(d->minimum, minimum)) + return; + d->minimum = minimum; + emit minimumChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::minimumOvershoot + + This property holds the amount that the property is allowed to be + less than \l minimum. Whenever the value is less than \l minimum + and greater than \c {minimum - minimumOvershoot}, it is constrained + by the \l easing curve. When the value attempts to go under + \c {minimum - minimumOvershoots} there is a hard stop. + + The default is \c 0. +*/ +qreal QQuickBoundaryRule::minimumOvershoot() const +{ + Q_D(const QQuickBoundaryRule); + return d->minimumOvershoot; +} + +void QQuickBoundaryRule::setMinimumOvershoot(qreal minimumOvershoot) +{ + Q_D(QQuickBoundaryRule); + if (qFuzzyCompare(d->minimumOvershoot, minimumOvershoot)) + return; + d->minimumOvershoot = minimumOvershoot; + emit minimumOvershootChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::maximum + + This property holds the largest unconstrained value that the property is + allowed to have. If the property is set to a larger value, it will be + constrained by \l easing and \l maximumOvershoot. + + The default is \c 1. +*/ +qreal QQuickBoundaryRule::maximum() const +{ + Q_D(const QQuickBoundaryRule); + return d->maximum; +} + +void QQuickBoundaryRule::setMaximum(qreal maximum) +{ + Q_D(QQuickBoundaryRule); + if (qFuzzyCompare(d->maximum, maximum)) + return; + d->maximum = maximum; + emit maximumChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::maximumOvershoot + + This property holds the amount that the property is allowed to be + more than \l maximum. Whenever the value is greater than \l maximum + and less than \c {maximum + maximumOvershoot}, it is constrained + by the \l easing curve. When the value attempts to exceed + \c {maximum + maximumOvershoot} there is a hard stop. + + The default is 0. +*/ +qreal QQuickBoundaryRule::maximumOvershoot() const +{ + Q_D(const QQuickBoundaryRule); + return d->maximumOvershoot; +} + +void QQuickBoundaryRule::setMaximumOvershoot(qreal maximumOvershoot) +{ + Q_D(QQuickBoundaryRule); + if (qFuzzyCompare(d->maximumOvershoot, maximumOvershoot)) + return; + d->maximumOvershoot = maximumOvershoot; + emit maximumOvershootChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::overshootScale + + This property holds the amount by which the \l easing is scaled during the + overshoot condition. For example if an Item is restricted from moving more + than 100 pixels beyond some limit, and the user (by means of some Input + Handler) is trying to drag it 100 pixels past the limit, if overshootScale + is set to 1, the user will succeed: the only effect of the easing curve is + to change the rate at which the item moves from overshoot 0 to overshoot + 100. But if it is set to 0.5, the BoundaryRule provides resistance such + that when the user tries to move 100 pixels, the Item will only move 50 + pixels; and the easing curve modulates the rate of movement such that it + may move in sync with the user's attempted movement at the beginning, and + then slows down, depending on the shape of the easing curve. + + The default is 0.5. +*/ +qreal QQuickBoundaryRule::overshootScale() const +{ + Q_D(const QQuickBoundaryRule); + return d->overshootScale; +} + +void QQuickBoundaryRule::setOvershootScale(qreal overshootScale) +{ + Q_D(QQuickBoundaryRule); + if (qFuzzyCompare(d->overshootScale, overshootScale)) + return; + d->overshootScale = overshootScale; + emit overshootScaleChanged(); +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::currentOvershoot + + This property holds the amount by which the most recently set value of the + intercepted property exceeds \l maximum or is less than \l minimum. + + It is positive if the property value exceeds \l maximum, negative if the + property value is less than \l minimum, or 0 if the property value is + within both boundaries. +*/ +qreal QQuickBoundaryRule::currentOvershoot() const +{ + Q_D(const QQuickBoundaryRule); + return d->currentOvershoot; +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::peakOvershoot + + This property holds the most-positive or most-negative value of + \l currentOvershoot that has been seen, until \l returnToBounds() is called. + + This can be useful when the intercepted property value is known to + fluctuate, and you want to find and react to the maximum amount of + overshoot rather than to the fluctuations. + + \sa overshootFilter +*/ +qreal QQuickBoundaryRule::peakOvershoot() const +{ + Q_D(const QQuickBoundaryRule); + return d->peakOvershoot; +} + +/*! + \qmlproperty enum QtQuick::BoundaryRule::overshootFilter + + This property specifies the aggregation function that will be applied to + the intercepted property value. + + If this is set to \c BoundaryRule.None (the default), the intercepted + property will hold a value whose overshoot is limited to \l currentOvershoot. + If this is set to \c BoundaryRule.Peak, the intercepted property will hold + a value whose overshoot is limited to \l peakOvershoot. +*/ +QQuickBoundaryRule::OvershootFilter QQuickBoundaryRule::overshootFilter() const +{ + Q_D(const QQuickBoundaryRule); + return d->overshootFilter; +} + +void QQuickBoundaryRule::setOvershootFilter(OvershootFilter overshootFilter) +{ + Q_D(QQuickBoundaryRule); + if (d->overshootFilter == overshootFilter) + return; + d->overshootFilter = overshootFilter; + emit overshootFilterChanged(); +} + +/*! + \qmlmethod bool QtQuick::BoundaryRule::returnToBounds + + Returns the intercepted property to a value between \l minimum and + \l maximum, such that \l currentOvershoot and \l peakOvershoot are both + zero. This will be animated if \l returnDuration is greater than zero. + + Returns true if the value needed to be adjusted, or false if it was already + within bounds. +*/ +bool QQuickBoundaryRule::returnToBounds() +{ + Q_D(QQuickBoundaryRule); + if (d->returnAnimationJob) { + qCDebug(lcBR) << "animation already in progress"; + return true; + } + if (currentOvershoot() > 0) { + if (d->returnDuration > 0) + d->returnAnimationJob = new QQuickBoundaryReturnJob(d, maximum()); + else + write(maximum()); + } else if (currentOvershoot() < 0) { + if (d->returnDuration > 0) + d->returnAnimationJob = new QQuickBoundaryReturnJob(d, minimum()); + else + write(minimum()); + } else { + return false; + } + if (d->returnAnimationJob) { + qCDebug(lcBR) << "animating from" << d->returnAnimationJob->fromValue << "to" << d->returnAnimationJob->toValue; + d->returnAnimationJob->start(); + } else { + d->resetOvershoot(); + qCDebug(lcBR) << "returned to" << d->property.read(); + } + return true; +} + +/*! + \qmlproperty qreal QtQuick::BoundaryRule::easing + + This property holds the easing curve to be applied in overshoot mode + (whenever the \l minimum or \l maximum constraint is violated, while + the value is still within the respective overshoot range). + + The default easing curve is \l QEasingCurve::OutQuad. +*/ +QEasingCurve QQuickBoundaryRule::easing() const +{ + Q_D(const QQuickBoundaryRule); + return d->easing; +} + +void QQuickBoundaryRule::setEasing(const QEasingCurve &easing) +{ + Q_D(QQuickBoundaryRule); + if (d->easing == easing) + return; + d->easing = easing; + emit easingChanged(); +} + +/*! + \qmlproperty int QtQuick::BoundaryRule::returnDuration + + This property holds the amount of time in milliseconds that + \l returnToBounds() will take to return the target property to the nearest bound. + If it is set to 0, returnToBounds() will set the property immediately + rather than creating an animation job. + + The default is 100 ms. +*/ +int QQuickBoundaryRule::returnDuration() const +{ + Q_D(const QQuickBoundaryRule); + return d->returnDuration; +} + +void QQuickBoundaryRule::setReturnDuration(int duration) +{ + Q_D(QQuickBoundaryRule); + if (d->returnDuration == duration) + return; + d->returnDuration = duration; + emit returnDurationChanged(); +} + +void QQuickBoundaryRule::write(const QVariant &value) +{ + bool conversionOk = false; + qreal rValue = value.toReal(&conversionOk); + if (!conversionOk) { + qWarning() << "BoundaryRule doesn't work with non-numeric values:" << value; + return; + } + Q_D(QQuickBoundaryRule); + bool bypass = !d->enabled || !d->finalized || QQmlEnginePrivate::designerMode(); + if (bypass) { + QQmlPropertyPrivate::write(d->property, value, + QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); + return; + } + + qmlExecuteDeferred(this); + d->targetValue = d->easedOvershoot(rValue); + QQmlPropertyPrivate::write(d->property, d->targetValue, + QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); +} + +void QQuickBoundaryRule::setTarget(const QQmlProperty &property) +{ + Q_D(QQuickBoundaryRule); + d->property = property; + + QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(qmlEngine(this)); + static int finalizedIdx = -1; + if (finalizedIdx < 0) + finalizedIdx = metaObject()->indexOfSlot("componentFinalized()"); + engPriv->registerFinalizeCallback(this, finalizedIdx); +} + +void QQuickBoundaryRule::componentFinalized() +{ + Q_D(QQuickBoundaryRule); + d->finalized = true; +} + +/*! + \internal + Given that something is trying to set the target property to \a value, + this function applies the easing curve and returns the value that the + property should actually get instead. +*/ +qreal QQuickBoundaryRulePrivate::easedOvershoot(qreal value) +{ + qreal ret = value; + Q_Q(QQuickBoundaryRule); + if (value > maximum) { + qreal overshootWas = currentOvershoot; + currentOvershoot = value - maximum; + if (!qFuzzyCompare(overshootWas, currentOvershoot)) + emit q->currentOvershootChanged(); + overshootWas = peakOvershoot; + peakOvershoot = qMax(currentOvershoot, peakOvershoot); + if (!qFuzzyCompare(overshootWas, peakOvershoot)) + emit q->peakOvershootChanged(); + ret = maximum + maximumOvershoot * easing.valueForProgress( + (overshootFilter == QQuickBoundaryRule::OvershootFilter::Peak ? peakOvershoot : currentOvershoot) + * overshootScale / maximumOvershoot); + qCDebug(lcBR).nospace() << value << " overshoots maximum " << maximum << " by " + << currentOvershoot << " (peak " << peakOvershoot << "): eased to " << ret; + } else if (value < minimum) { + qreal overshootWas = currentOvershoot; + currentOvershoot = value - minimum; + if (!qFuzzyCompare(overshootWas, currentOvershoot)) + emit q->currentOvershootChanged(); + overshootWas = peakOvershoot; + peakOvershoot = qMin(currentOvershoot, peakOvershoot); + if (!qFuzzyCompare(overshootWas, peakOvershoot)) + emit q->peakOvershootChanged(); + ret = minimum - minimumOvershoot * easing.valueForProgress( + -(overshootFilter == QQuickBoundaryRule::OvershootFilter::Peak ? peakOvershoot : currentOvershoot) + * overshootScale / minimumOvershoot); + qCDebug(lcBR).nospace() << value << " overshoots minimum " << minimum << " by " + << currentOvershoot << " (peak " << peakOvershoot << "): eased to " << ret; + } else { + resetOvershoot(); + } + return ret; +} + +/*! + \internal + Resets the currentOvershoot and peakOvershoot + properties to zero. +*/ +void QQuickBoundaryRulePrivate::resetOvershoot() +{ + Q_Q(QQuickBoundaryRule); + if (!qFuzzyCompare(peakOvershoot, 0)) { + peakOvershoot = 0; + emit q->peakOvershootChanged(); + } + if (!qFuzzyCompare(currentOvershoot, 0)) { + currentOvershoot = 0; + emit q->currentOvershootChanged(); + } +} + +QT_END_NAMESPACE + +#include "moc_qquickboundaryrule_p.cpp" diff --git a/src/quick/util/qquickboundaryrule_p.h b/src/quick/util/qquickboundaryrule_p.h new file mode 100644 index 0000000000..3325b675c5 --- /dev/null +++ b/src/quick/util/qquickboundaryrule_p.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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$ +** +****************************************************************************/ + +#ifndef QQUICKBOUNDARYRULE_H +#define QQUICKBOUNDARYRULE_H + +// +// 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. +// + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQuickAbstractAnimation; +class QQuickBoundaryRulePrivate; +class Q_QUICK_PRIVATE_EXPORT QQuickBoundaryRule : public QObject, public QQmlPropertyValueInterceptor +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickBoundaryRule) + + Q_INTERFACES(QQmlPropertyValueInterceptor) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY minimumChanged) + Q_PROPERTY(qreal minimumOvershoot READ minimumOvershoot WRITE setMinimumOvershoot NOTIFY minimumOvershootChanged) + Q_PROPERTY(qreal maximum READ maximum WRITE setMaximum NOTIFY maximumChanged) + Q_PROPERTY(qreal maximumOvershoot READ maximumOvershoot WRITE setMaximumOvershoot NOTIFY maximumOvershootChanged) + Q_PROPERTY(qreal overshootScale READ overshootScale WRITE setOvershootScale NOTIFY overshootScaleChanged) + Q_PROPERTY(qreal currentOvershoot READ currentOvershoot NOTIFY currentOvershootChanged) + Q_PROPERTY(qreal peakOvershoot READ peakOvershoot NOTIFY peakOvershootChanged) + Q_PROPERTY(OvershootFilter overshootFilter READ overshootFilter WRITE setOvershootFilter NOTIFY overshootFilterChanged) + Q_PROPERTY(QEasingCurve easing READ easing WRITE setEasing NOTIFY easingChanged) + Q_PROPERTY(int returnDuration READ returnDuration WRITE setReturnDuration NOTIFY returnDurationChanged) + +public: + enum OvershootFilter { + None, + Peak + }; + Q_ENUM(OvershootFilter) + + QQuickBoundaryRule(QObject *parent=nullptr); + ~QQuickBoundaryRule(); + + void setTarget(const QQmlProperty &) override; + void write(const QVariant &value) override; + + bool enabled() const; + void setEnabled(bool enabled); + + qreal minimum() const; + void setMinimum(qreal minimum); + qreal minimumOvershoot() const; + void setMinimumOvershoot(qreal minimum); + + qreal maximum() const; + void setMaximum(qreal maximum); + qreal maximumOvershoot() const; + void setMaximumOvershoot(qreal maximum); + + qreal overshootScale() const; + void setOvershootScale(qreal scale); + + qreal currentOvershoot() const; + qreal peakOvershoot() const; + + OvershootFilter overshootFilter() const; + void setOvershootFilter(OvershootFilter overshootFilter); + + Q_INVOKABLE bool returnToBounds(); + + QEasingCurve easing() const; + void setEasing(const QEasingCurve &easing); + + int returnDuration() const; + void setReturnDuration(int duration); + +Q_SIGNALS: + void enabledChanged(); + void minimumChanged(); + void minimumOvershootChanged(); + void maximumChanged(); + void maximumOvershootChanged(); + void overshootScaleChanged(); + void currentOvershootChanged(); + void peakOvershootChanged(); + void overshootFilterChanged(); + void easingChanged(); + void returnDurationChanged(); + +private Q_SLOTS: + void componentFinalized(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickBoundaryRule) + +#endif // QQUICKBOUNDARYRULE_H diff --git a/src/quick/util/util.pri b/src/quick/util/util.pri index c51f082d03..63d995e34c 100644 --- a/src/quick/util/util.pri +++ b/src/quick/util/util.pri @@ -15,6 +15,7 @@ SOURCES += \ $$PWD/qquicktimeline.cpp \ $$PWD/qquickpixmapcache.cpp \ $$PWD/qquickbehavior.cpp \ + $$PWD/qquickboundaryrule.cpp \ $$PWD/qquickfontloader.cpp \ $$PWD/qquickstyledtext.cpp \ $$PWD/qquickimageprovider.cpp \ @@ -50,6 +51,7 @@ HEADERS += \ $$PWD/qquicktimeline_p_p.h \ $$PWD/qquickpixmapcache_p.h \ $$PWD/qquickbehavior_p.h \ + $$PWD/qquickboundaryrule_p.h \ $$PWD/qquickfontloader_p.h \ $$PWD/qquickstyledtext_p.h \ $$PWD/qquickimageprovider.h \ -- cgit v1.2.3 From 5d45aa1a6400b9fdb9bafa0559675996aff5e58a Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 27 Dec 2018 15:08:04 +0100 Subject: Add WheelHandler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It can be used to change any qreal property of its target Item in response to wheel rotation, or it can be used in other ways that involve bindings but without a target item. [ChangeLog][QtQuick][Event Handlers] Added WheelHandler, which handles mouse wheel rotation by modifying arbitrary Item properties. Fixes: QTBUG-68119 Change-Id: I247e2325ee993cc1b91a47fbd6c4ba0ffde7ad49 Reviewed-by: Jan Arve Sæther --- .../doc/snippets/pointerHandlers/handlerFlick.qml | 88 ++++ .../doc/snippets/pointerHandlers/wheelHandler.qml | 63 +++ src/quick/handlers/handlers.pri | 6 + src/quick/handlers/qquickwheelhandler.cpp | 520 +++++++++++++++++++++ src/quick/handlers/qquickwheelhandler_p.h | 128 +++++ src/quick/handlers/qquickwheelhandler_p_p.h | 87 ++++ src/quick/items/qquickevents_p_p.h | 1 + src/quick/items/qquickitemsmodule.cpp | 6 +- 8 files changed, 898 insertions(+), 1 deletion(-) create mode 100644 src/quick/doc/snippets/pointerHandlers/handlerFlick.qml create mode 100644 src/quick/doc/snippets/pointerHandlers/wheelHandler.qml create mode 100644 src/quick/handlers/qquickwheelhandler.cpp create mode 100644 src/quick/handlers/qquickwheelhandler_p.h create mode 100644 src/quick/handlers/qquickwheelhandler_p_p.h (limited to 'src') diff --git a/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml new file mode 100644 index 0000000000..f74b075357 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//![0] +import QtQuick 2.14 +import Qt.labs.animation 1.0 + +Item { + width: 320; height: 480 + Flow { + id: content + width: parent.width + spacing: 2; padding: 2 + + WheelHandler { + orientation: Qt.Vertical + property: "y" + rotationScale: 15 + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onActiveChanged: if (!active) ybr.returnToBounds() + } + + DragHandler { + xAxis.enabled: false + onActiveChanged: if (!active) ybr.returnToBounds() + } + + BoundaryRule on y { + id: ybr + minimum: content.parent.height - content.height + maximum: 0 + minimumOvershoot: 400; maximumOvershoot: 400 + overshootFilter: BoundaryRule.Peak + } + + Repeater { + model: 1000 + Rectangle { color: "gray"; width: 10 + Math.random() * 100; height: 15 } + } + } +} +//![0] diff --git a/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml new file mode 100644 index 0000000000..2c9913ac97 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//![0] +import QtQuick 2.14 + +Rectangle { + width: 170; height: 120 + color: "green"; antialiasing: true + + WheelHandler { + property: "rotation" + onWheel: console.log("rotation", event.angleDelta.y, + "scaled", rotation, "@", point.position, "=>", parent.rotation) + } +} +//![0] diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri index fa2f25c793..49975bd1ca 100644 --- a/src/quick/handlers/handlers.pri +++ b/src/quick/handlers/handlers.pri @@ -27,3 +27,9 @@ SOURCES += \ $$PWD/qquicksinglepointhandler.cpp \ $$PWD/qquicktaphandler.cpp \ $$PWD/qquickdragaxis.cpp + +qtConfig(wheelevent) { + HEADERS += $$PWD/qquickwheelhandler_p.h $$PWD/qquickwheelhandler_p_p.h + SOURCES += $$PWD/qquickwheelhandler.cpp +} + diff --git a/src/quick/handlers/qquickwheelhandler.cpp b/src/quick/handlers/qquickwheelhandler.cpp new file mode 100644 index 0000000000..90e4fef97e --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler.cpp @@ -0,0 +1,520 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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 "qquickwheelhandler_p.h" +#include "qquickwheelhandler_p_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel") + +/*! + \qmltype WheelHandler + \instantiates QQuickWheelHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-handlers + \brief Handler for the mouse wheel. + + WheelHandler is a handler that is used to interactively manipulate some + numeric property of an Item as the user rotates the mouse wheel. Like other + Input Handlers, by default it manipulates its \l {PointerHandler::target} + {target}. Declare \l property to control which target property will be + manipulated: + + \snippet pointerHandlers/wheelHandler.qml 0 + + \l BoundaryRule is quite useful in combination with WheelHandler (as well + as with other Input Handlers) to declare the allowed range of values that + the target property can have. For example it is possible to implement + scrolling using a combination of WheelHandler and \l DragHandler to + manipulate the scrollable Item's \l{QQuickItem::y}{y} property when the + user rotates the wheel or drags the item on a touchscreen, and + \l BoundaryRule to limit the range of motion from the top to the bottom: + + \snippet pointerHandlers/handlerFlick.qml 0 + + Alternatively if \l targetProperty is not set or \l target is null, + WheelHandler will not automatically manipulate anything; but the + \l rotation property can be used in a binding to manipulate another + property, or you can implement \c onWheel and handle the wheel event + directly. + + WheelHandler handles only a rotating mouse wheel by default. + Optionally it can handle smooth-scrolling events from touchpad gestures, + by setting \l acceptedDevices to \c{PointerDevice.Mouse | PointerDevice.TouchPad}. + + \note Some non-mouse hardware (such as a touch-sensitive Wacom tablet, or + a Linux laptop touchpad) generates real wheel events from gestures. + WheelHandler will respond to those events as wheel events regardless of the + setting of the \l acceptedDevices property. + + \sa MouseArea + \sa Flickable +*/ + +QQuickWheelHandler::QQuickWheelHandler(QQuickItem *parent) + : QQuickSinglePointHandler(*(new QQuickWheelHandlerPrivate), parent) +{ + setAcceptedDevices(QQuickPointerDevice::Mouse); +} + +/*! + \qmlproperty enum QtQuick::WheelHandler::orientation + + Which wheel to react to. The default is \c Qt.Vertical. + + Not every mouse has a \c Horizontal wheel; sometimes it is emulated by + tilting the wheel sideways. A touchpad can usually generate both vertical + and horizontal wheel events. +*/ +Qt::Orientation QQuickWheelHandler::orientation() const +{ + Q_D(const QQuickWheelHandler); + return d->orientation; +} + +void QQuickWheelHandler::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickWheelHandler); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(); +} + +/*! + \qmlproperty bool QtQuick::WheelHandler::invertible + + Whether or not to reverse the direction of property change if + \l QQuickPointerScrollEvent::inverted is true. The default is \c true. + + If the operating system has a "natural scrolling" setting that causes + scrolling to be in the same direction as the finger movement, then if this + property is set to \c true, and WheelHandler is directly setting a property + on \l target, the direction of movement will correspond to the system setting. + If this property is set to \l false, it will invert the \l rotation so that + the direction of motion is always the same as the direction of finger movement. +*/ +bool QQuickWheelHandler::isInvertible() const +{ + Q_D(const QQuickWheelHandler); + return d->invertible; +} + +void QQuickWheelHandler::setInvertible(bool invertible) +{ + Q_D(QQuickWheelHandler); + if (d->invertible == invertible) + return; + + d->invertible = invertible; + emit invertibleChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::activeTimeout + + The amount of time in seconds after which the \l active property will + revert to \c false if no more wheel events are received. The default is + \c 0.1 (100 ms). + + When WheelHandler handles events that contain + \l {Qt::ScrollPhase}{scroll phase} information, such as events from some + touchpads, the \l active property will become \c false as soon as an event + with phase \l Qt::ScrollEnd is received; in that case the timeout is not + necessary. But a conventional mouse with a wheel does not provide the + \l {QQuickPointerScrollEvent::phase}{scroll phase}: the mouse cannot detect + when the user has decided to stop scrolling, so the \l active property + transitions to \c false after this much time has elapsed. +*/ +qreal QQuickWheelHandler::activeTimeout() const +{ + Q_D(const QQuickWheelHandler); + return d->activeTimeout; +} + +void QQuickWheelHandler::setActiveTimeout(qreal timeout) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->activeTimeout, timeout)) + return; + + if (timeout < 0) { + qWarning("activeTimeout must be positive"); + return; + } + + d->activeTimeout = timeout; + emit activeTimeoutChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::rotation + + The angle through which the mouse wheel has been rotated since the last + time this property was set, in wheel degrees. + + A positive value indicates that the wheel was rotated up/right; + a negative value indicates that the wheel was rotated down/left. + + A basic mouse click-wheel works in steps of 15 degrees. + + The default is \c 0 at startup. It can be programmatically set to any value + at any time. The value will be adjusted from there as the user rotates the + mouse wheel. + + \sa orientation +*/ +qreal QQuickWheelHandler::rotation() const +{ + Q_D(const QQuickWheelHandler); + return d->rotation * d->rotationScale; +} + +void QQuickWheelHandler::setRotation(qreal rotation) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->rotation, rotation / d->rotationScale)) + return; + + d->rotation = rotation / d->rotationScale; + emit rotationChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::rotationScale + + The scaling to be applied to the \l rotation property, and to the + \l property on the \l target item, if any. The default is 1, such that + \l rotation will be in units of degrees of rotation. It can be set to a + negative number to invert the effect of the direction of mouse wheel + rotation. +*/ +qreal QQuickWheelHandler::rotationScale() const +{ + Q_D(const QQuickWheelHandler); + return d->rotationScale; +} + +void QQuickWheelHandler::setRotationScale(qreal rotationScale) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->rotationScale, rotationScale)) + return; + if (qFuzzyIsNull(rotationScale)) { + qWarning("rotationScale cannot be set to zero"); + return; + } + + d->rotationScale = rotationScale; + emit rotationScaleChanged(); +} + +/*! + \qmlproperty string QtQuick::WheelHandler::property + + The property to be modified on the \l target when the mouse wheel is rotated. + + The default is no property (empty string). When no target property is being + automatically modified, you can use bindings to react to mouse wheel + rotation in arbitrary ways. + + You can use the mouse wheel to adjust any numeric property. For example if + \c property is set to \c x, the \l target will move horizontally as the + wheel is rotated. The following properties have special behavior: + + \value scale + \l{QQuickItem::scale}{scale} will be modified in a non-linear fashion + as described under \l targetScaleMultiplier. If + \l targetTransformAroundCursor is \c true, the \l{QQuickItem::x}{x} and + \l{QQuickItem::y}{y} properties will be simultaneously adjusted so that + the user will effectively zoom into or out of the point under the mouse + cursor. + \value rotation + \l{QQuickItem::rotation}{rotation} will be set to \l rotation. If + \l targetTransformAroundCursor is \c true, the l{QQuickItem::x}{x} and + \l{QQuickItem::y}{y} properties will be simultaneously adjusted so + that the user will effectively rotate the item around the point under + the mouse cursor. + + The adjustment of the given target property is always scaled by \l rotationScale. +*/ +QString QQuickWheelHandler::property() const +{ + Q_D(const QQuickWheelHandler); + return d->propertyName; +} + +void QQuickWheelHandler::setProperty(const QString &propertyName) +{ + Q_D(QQuickWheelHandler); + if (d->propertyName == propertyName) + return; + + d->propertyName = propertyName; + d->metaPropertyDirty = true; + emit propertyChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::targetScaleMultiplier + + The amount by which the \l target \l{QQuickItem::scale}{scale} is to be + multiplied whenever the \l rotation changes by 15 degrees. This + is relevant only when \l property is \c "scale". + + The \c scale will be multiplied by + \c targetScaleMultiplier \sup {angleDelta * rotationScale / 15}. + The default is \c 2 \sup {1/3}, which means that if \l rotationScale is left + at its default value, and the mouse wheel is rotated by one "click" + (15 degrees), the \l target will be scaled by approximately 1.25; after + three "clicks" its size will be doubled or halved, depending on the + direction that the wheel is rotated. If you want to make it double or halve + with every 2 clicks of the wheel, set this to \c 2 \sup {1/2} (1.4142). + If you want to make it scale the opposite way as the wheel is rotated, + set \c rotationScale to a negative value. +*/ +qreal QQuickWheelHandler::targetScaleMultiplier() const +{ + Q_D(const QQuickWheelHandler); + return d->targetScaleMultiplier; +} + +void QQuickWheelHandler::setTargetScaleMultiplier(qreal targetScaleMultiplier) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->targetScaleMultiplier, targetScaleMultiplier)) + return; + + d->targetScaleMultiplier = targetScaleMultiplier; + emit targetScaleMultiplierChanged(); +} + +/*! + \qmlproperty bool QtQuick::WheelHandler::targetTransformAroundCursor + + Whether the \l target should automatically be repositioned in such a way + that it is transformed around the mouse cursor position while the + \l property is adjusted. The default is \c true. + + If \l property is set to \c "rotation" and \l targetTransformAroundCursor + is \c true, then as the wheel is rotated, the \l target item will rotate in + place around the mouse cursor position. If \c targetTransformAroundCursor + is \c false, it will rotate around its + \l{QQuickItem::transformOrigin}{transformOrigin} instead. +*/ +bool QQuickWheelHandler::isTargetTransformAroundCursor() const +{ + Q_D(const QQuickWheelHandler); + return d->targetTransformAroundCursor; +} + +void QQuickWheelHandler::setTargetTransformAroundCursor(bool ttac) +{ + Q_D(QQuickWheelHandler); + if (d->targetTransformAroundCursor == ttac) + return; + + d->targetTransformAroundCursor = ttac; + emit targetTransformAroundCursorChanged(); +} + +bool QQuickWheelHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!event) + return false; + QQuickPointerScrollEvent *scroll = event->asPointerScrollEvent(); + if (!scroll) + return false; + if (!acceptedDevices().testFlag(QQuickPointerDevice::DeviceType::TouchPad) + && scroll->synthSource() != Qt::MouseEventNotSynthesized) + return false; + if (!active()) { + switch (orientation()) { + case Qt::Horizontal: + if (qFuzzyIsNull(scroll->angleDelta().x()) && qFuzzyIsNull(scroll->pixelDelta().x())) + return false; + break; + case Qt::Vertical: + if (qFuzzyIsNull(scroll->angleDelta().y()) && qFuzzyIsNull(scroll->pixelDelta().y())) + return false; + break; + } + } + QQuickEventPoint *point = event->point(0); + if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) { + setPointId(point->pointId()); + return true; + } + return false; +} + +void QQuickWheelHandler::handleEventPoint(QQuickEventPoint *point) +{ + Q_D(QQuickWheelHandler); + QQuickPointerScrollEvent *event = point->pointerEvent()->asPointerScrollEvent(); + setActive(true); // ScrollEnd will not happen unless it was already active (see setActive(false) below) + point->setAccepted(); + qreal inversion = !d->invertible && event->isInverted() ? -1 : 1; + qreal angleDelta = inversion * qreal(orientation() == Qt::Horizontal ? event->angleDelta().x() : + event->angleDelta().y()) / 8; + d->rotation += angleDelta; + emit rotationChanged(); + emit wheel(event); + if (!d->propertyName.isEmpty() && target()) { + QQuickItem *t = target(); + // writing target()'s property is done via QMetaProperty::write() so that any registered interceptors can react. + if (d->propertyName == QLatin1String("scale")) { + qreal multiplier = qPow(d->targetScaleMultiplier, angleDelta * d->rotationScale / 15); // wheel "clicks" + const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition()); + const QPointF positionWas = t->position(); + const qreal scaleWas = t->scale(); + const qreal activePropertyValue = scaleWas * multiplier; + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in parent" << centroidParentPos + << "in scene" << point->scenePosition() + << "multiplier" << multiplier << "scale" << scaleWas + << "->" << activePropertyValue; + d->targetMetaProperty().write(t, activePropertyValue); + if (d->targetTransformAroundCursor) { + // If an interceptor intervened, scale may now be different than we asked for. Adjust accordingly. + multiplier = t->scale() / scaleWas; + const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform( + centroidParentPos, positionWas, QVector2D(), scaleWas, multiplier, t->rotation(), 0); + qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos); + t->setPosition(adjPos); + } + } else if (d->propertyName == QLatin1String("rotation")) { + const QPointF positionWas = t->position(); + const qreal rotationWas = t->rotation(); + const qreal activePropertyValue = rotationWas + angleDelta * d->rotationScale; + const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition()); + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in parent" << centroidParentPos + << "in scene" << point->scenePosition() << "rotation" << t->rotation() + << "->" << activePropertyValue; + d->targetMetaProperty().write(t, activePropertyValue); + if (d->targetTransformAroundCursor) { + // If an interceptor intervened, rotation may now be different than we asked for. Adjust accordingly. + const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform( + centroidParentPos, positionWas, QVector2D(), + t->scale(), 1, rotationWas, t->rotation() - rotationWas); + qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos); + t->setPosition(adjPos); + } + } else { + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "scaled" << angleDelta << "total" << d->rotation << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in scene" << point->scenePosition() << "rotation" << t->rotation(); + qreal delta = 0; + if (event->hasPixelDelta()) { + delta = inversion * d->rotationScale * qreal(orientation() == Qt::Horizontal ? event->pixelDelta().x() : event->pixelDelta().y()); + qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by pixel delta" << delta << "from" << event; + } else { + delta = angleDelta * d->rotationScale; + qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by scaled angle delta" << delta << "from" << event; + } + bool ok = false; + qreal value = d->targetMetaProperty().read(t).toReal(&ok); + if (ok) + d->targetMetaProperty().write(t, value + qreal(delta)); + else + qWarning() << "failed to read property" << d->propertyName << "of" << t; + } + } + switch (event->phase()) { + case Qt::ScrollEnd: + qCDebug(lcWheelHandler) << objectName() << "deactivating due to ScrollEnd phase"; + setActive(false); + break; + case Qt::NoScrollPhase: + d->deactivationTimer.start(qRound(d->activeTimeout * 1000), this); + break; + case Qt::ScrollBegin: + case Qt::ScrollUpdate: + case Qt::ScrollMomentum: + break; + } +} + +void QQuickWheelHandler::onTargetChanged(QQuickItem *oldTarget) +{ + Q_UNUSED(oldTarget) + Q_D(QQuickWheelHandler); + d->metaPropertyDirty = true; +} + +void QQuickWheelHandler::onActiveChanged() +{ + Q_D(QQuickWheelHandler); + if (!active()) + d->deactivationTimer.stop(); +} + +void QQuickWheelHandler::timerEvent(QTimerEvent *event) +{ + Q_D(const QQuickWheelHandler); + if (event->timerId() == d->deactivationTimer.timerId()) { + qCDebug(lcWheelHandler) << objectName() << "deactivating due to timeout"; + setActive(false); + } +} + +QQuickWheelHandlerPrivate::QQuickWheelHandlerPrivate() + : QQuickSinglePointHandlerPrivate() +{ +} + +QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const +{ + Q_Q(const QQuickWheelHandler); + if (metaPropertyDirty && q->target()) { + if (!propertyName.isEmpty()) { + const QMetaObject *targetMeta = q->target()->metaObject(); + metaProperty = targetMeta->property( + targetMeta->indexOfProperty(propertyName.toLocal8Bit().constData())); + } + metaPropertyDirty = false; + } + return metaProperty; +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickwheelhandler_p.h b/src/quick/handlers/qquickwheelhandler_p.h new file mode 100644 index 0000000000..f8d1c00726 --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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$ +** +****************************************************************************/ + +#ifndef QQUICKWHEELHANDLER_H +#define QQUICKWHEELHANDLER_H + +// +// 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. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickWheelEvent; +class QQuickWheelHandlerPrivate; + +class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(bool invertible READ isInvertible WRITE setInvertible NOTIFY invertibleChanged) + Q_PROPERTY(qreal activeTimeout READ activeTimeout WRITE setActiveTimeout NOTIFY activeTimeoutChanged) + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(qreal rotationScale READ rotationScale WRITE setRotationScale NOTIFY rotationScaleChanged) + Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged) + Q_PROPERTY(qreal targetScaleMultiplier READ targetScaleMultiplier WRITE setTargetScaleMultiplier NOTIFY targetScaleMultiplierChanged) + Q_PROPERTY(bool targetTransformAroundCursor READ isTargetTransformAroundCursor WRITE setTargetTransformAroundCursor NOTIFY targetTransformAroundCursorChanged) + +public: + explicit QQuickWheelHandler(QQuickItem *parent = nullptr); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + bool isInvertible() const; + void setInvertible(bool invertible); + + qreal activeTimeout() const; + void setActiveTimeout(qreal timeout); + + qreal rotation() const; + void setRotation(qreal rotation); + + qreal rotationScale() const; + void setRotationScale(qreal rotationScale); + + QString property() const; + void setProperty(const QString &name); + + qreal targetScaleMultiplier() const; + void setTargetScaleMultiplier(qreal targetScaleMultiplier); + + bool isTargetTransformAroundCursor() const; + void setTargetTransformAroundCursor(bool ttac); + +Q_SIGNALS: + void wheel(QQuickPointerScrollEvent *event); + + void orientationChanged(); + void invertibleChanged(); + void activeTimeoutChanged(); + void rotationChanged(); + void rotationScaleChanged(); + void propertyChanged(); + void targetScaleMultiplierChanged(); + void targetTransformAroundCursorChanged(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void handleEventPoint(QQuickEventPoint *point) override; + void onTargetChanged(QQuickItem *oldTarget) override; + void onActiveChanged() override; + void timerEvent(QTimerEvent *event) override; + + Q_DECLARE_PRIVATE(QQuickWheelHandler) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickWheelHandler) + +#endif // QQUICKWHEELHANDLER_H diff --git a/src/quick/handlers/qquickwheelhandler_p_p.h b/src/quick/handlers/qquickwheelhandler_p_p.h new file mode 100644 index 0000000000..d35e04c51b --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler_p_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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$ +** +****************************************************************************/ + +#ifndef QQUICKWHEELHANDLER_P_P_H +#define QQUICKWHEELHANDLER_P_P_H + +// +// 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. +// + +#include "qquicksinglepointhandler_p_p.h" +#include "qquickwheelhandler_p.h" +#include + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandlerPrivate : public QQuickSinglePointHandlerPrivate +{ + Q_DECLARE_PUBLIC(QQuickWheelHandler) + +public: + static QQuickWheelHandlerPrivate* get(QQuickWheelHandler *q) { return q->d_func(); } + static const QQuickWheelHandlerPrivate* get(const QQuickWheelHandler *q) { return q->d_func(); } + + QQuickWheelHandlerPrivate(); + + QMetaProperty &targetMetaProperty() const; + + QBasicTimer deactivationTimer; + qreal activeTimeout = 0.1; + qreal rotationScale = 1; + qreal rotation = 0; // in units of degrees + qreal targetScaleMultiplier = 1.25992104989487; // qPow(2, 1/3) + QString propertyName; + mutable QMetaProperty metaProperty; + Qt::Orientation orientation = Qt::Vertical; + mutable bool metaPropertyDirty = true; + bool invertible = true; + bool targetTransformAroundCursor = true; +}; + +QT_END_NAMESPACE + +#endif // QQUICKWHEELHANDLER_P_P_H diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index e614b1bd6d..1a3737091f 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -605,6 +605,7 @@ private: bool m_inverted = false; friend class QQuickWindowPrivate; + friend class QQuickWheelHandler; Q_DISABLE_COPY(QQuickPointerScrollEvent) }; diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index a6dce33f45..2a1c442653 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -120,6 +120,7 @@ #include "handlers/qquickpinchhandler_p.h" #include "handlers/qquickpointhandler_p.h" #include "handlers/qquicktaphandler_p.h" +#include "handlers/qquickwheelhandler_p.h" QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcTransient) @@ -478,6 +479,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) #if QT_CONFIG(quick_tableview) qmlRegisterType(uri, 2, 14, "TableView"); #endif +#if QT_CONFIG(wheelevent) + qmlRegisterType(uri, 2, 14, "WheelHandler"); +#endif } static void initResources() -- cgit v1.2.3 From 03b19f4fecee22f90fc55cc547e2227e69baea13 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 26 Apr 2019 16:38:38 +0200 Subject: PinchHandler: change qCInfo to qCDebug qCInfo is enabled by default, so this turns into noise for users. Task-number: QTBUG-70083 Change-Id: Ie7f50d393055846bd2f9935c2bbe72830b1b24a3 Reviewed-by: Mitch Curtis --- src/quick/handlers/qquickpinchhandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index 9ae2116d39..dc1a9a92f9 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -285,9 +285,9 @@ void QQuickPinchHandler::onActiveChanged() m_startScale = m_accumulatedScale; m_startRotation = 0; } - qCInfo(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation; + qCDebug(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation; } else { - qCInfo(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation; + qCDebug(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation; } } -- cgit v1.2.3 From 9e5ca92712da3392d1f2957dc1e546cdddd1ce0a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 18 Apr 2019 14:22:10 +0200 Subject: Remove tracing JIT infrastructure The tracing JIT won't be finished. Therefore, remove the parts that have already been integrated. Change-Id: If72036be904bd7fc17ba9bcba0a317f8ed6cb30d Reviewed-by: Erik Verbruggen --- src/qml/compiler/qv4bytecodegenerator_p.h | 53 +- src/qml/compiler/qv4codegen.cpp | 76 +- src/qml/compiler/qv4compileddata_p.h | 6 +- src/qml/compiler/qv4compiler.cpp | 1 - src/qml/compiler/qv4compilercontext.cpp | 22 - src/qml/compiler/qv4compilercontext_p.h | 3 - src/qml/compiler/qv4instr_moth.cpp | 62 +- src/qml/compiler/qv4instr_moth_p.h | 58 +- src/qml/configure.json | 10 - src/qml/jit/jit.pri | 31 - src/qml/jit/qv4baselinejit.cpp | 59 +- src/qml/jit/qv4baselinejit_p.h | 58 +- src/qml/jit/qv4blockscheduler.cpp | 208 ---- src/qml/jit/qv4blockscheduler_p.h | 155 --- src/qml/jit/qv4domtree.cpp | 436 -------- src/qml/jit/qv4domtree_p.h | 136 --- src/qml/jit/qv4graph.cpp | 103 -- src/qml/jit/qv4graph_p.h | 146 --- src/qml/jit/qv4graphbuilder.cpp | 1683 ----------------------------- src/qml/jit/qv4graphbuilder_p.h | 298 ----- src/qml/jit/qv4ir.cpp | 382 ------- src/qml/jit/qv4ir_p.h | 228 ---- src/qml/jit/qv4loopinfo.cpp | 199 ---- src/qml/jit/qv4loopinfo_p.h | 159 --- src/qml/jit/qv4lowering.cpp | 200 ---- src/qml/jit/qv4lowering_p.h | 107 -- src/qml/jit/qv4mi.cpp | 251 ----- src/qml/jit/qv4mi_p.h | 627 ----------- src/qml/jit/qv4miblockset_p.h | 291 ----- src/qml/jit/qv4node.cpp | 215 ---- src/qml/jit/qv4node_p.h | 626 ----------- src/qml/jit/qv4operation.cpp | 770 ------------- src/qml/jit/qv4operation_p.h | 567 ---------- src/qml/jit/qv4runtimesupport_p.h | 255 ----- src/qml/jit/qv4schedulers.cpp | 912 ---------------- src/qml/jit/qv4schedulers_p.h | 162 --- src/qml/jit/qv4tracingjit.cpp | 91 -- src/qml/jsruntime/qv4engine.cpp | 9 - src/qml/jsruntime/qv4function.cpp | 36 +- src/qml/jsruntime/qv4function_p.h | 25 - src/qml/jsruntime/qv4global_p.h | 14 - src/qml/jsruntime/qv4math_p.h | 27 +- src/qml/jsruntime/qv4runtime.cpp | 48 - src/qml/jsruntime/qv4runtimeapi_p.h | 8 - src/qml/jsruntime/qv4vme_moth.cpp | 172 +-- src/qml/jsruntime/qv4vme_moth_p.h | 2 - src/qml/qtqmlglobal.h | 1 - 47 files changed, 176 insertions(+), 9812 deletions(-) delete mode 100644 src/qml/jit/qv4blockscheduler.cpp delete mode 100644 src/qml/jit/qv4blockscheduler_p.h delete mode 100644 src/qml/jit/qv4domtree.cpp delete mode 100644 src/qml/jit/qv4domtree_p.h delete mode 100644 src/qml/jit/qv4graph.cpp delete mode 100644 src/qml/jit/qv4graph_p.h delete mode 100644 src/qml/jit/qv4graphbuilder.cpp delete mode 100644 src/qml/jit/qv4graphbuilder_p.h delete mode 100644 src/qml/jit/qv4ir.cpp delete mode 100644 src/qml/jit/qv4ir_p.h delete mode 100644 src/qml/jit/qv4loopinfo.cpp delete mode 100644 src/qml/jit/qv4loopinfo_p.h delete mode 100644 src/qml/jit/qv4lowering.cpp delete mode 100644 src/qml/jit/qv4lowering_p.h delete mode 100644 src/qml/jit/qv4mi.cpp delete mode 100644 src/qml/jit/qv4mi_p.h delete mode 100644 src/qml/jit/qv4miblockset_p.h delete mode 100644 src/qml/jit/qv4node.cpp delete mode 100644 src/qml/jit/qv4node_p.h delete mode 100644 src/qml/jit/qv4operation.cpp delete mode 100644 src/qml/jit/qv4operation_p.h delete mode 100644 src/qml/jit/qv4runtimesupport_p.h delete mode 100644 src/qml/jit/qv4schedulers.cpp delete mode 100644 src/qml/jit/qv4schedulers_p.h delete mode 100644 src/qml/jit/qv4tracingjit.cpp (limited to 'src') diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h index 1d0a57c536..ab8661dbe3 100644 --- a/src/qml/compiler/qv4bytecodegenerator_p.h +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -66,8 +66,6 @@ namespace Moth { class BytecodeGenerator { public: - typedef CompiledData::Function::TraceInfoCount TraceInfoCount; - BytecodeGenerator(int line, bool debug) : startLine(line), debugMode(debug) {} @@ -164,15 +162,6 @@ public: addInstructionHelper(Moth::Instr::Type(InstrT), genericInstr); } - // Same as addInstruction, but also add a trace slot. Move only, because the instruction cannot - // be reused afterwards. - template - void addTracingInstruction(InstrData data) - { - data.traceSlot = nextTraceInfo(); - addInstruction(data); - } - Q_REQUIRED_RESULT Jump jump() { QT_WARNING_PUSH @@ -184,12 +173,12 @@ QT_WARNING_POP Q_REQUIRED_RESULT Jump jumpTrue() { - return addTracingJumpInstruction(Instruction::JumpTrue()); + return addJumpInstruction(Instruction::JumpTrue()); } Q_REQUIRED_RESULT Jump jumpFalse() { - return addTracingJumpInstruction(Instruction::JumpFalse()); + return addJumpInstruction(Instruction::JumpFalse()); } Q_REQUIRED_RESULT Jump jumpNotUndefined() @@ -209,7 +198,7 @@ QT_WARNING_POP Instruction::CmpStrictEqual cmp; cmp.lhs = lhs; addInstruction(std::move(cmp)); - addTracingJumpInstruction(Instruction::JumpTrue()).link(target); + addJumpInstruction(Instruction::JumpTrue()).link(target); } void jumpStrictNotEqual(const StackSlot &lhs, const Label &target) @@ -217,7 +206,7 @@ QT_WARNING_POP Instruction::CmpStrictNotEqual cmp; cmp.lhs = lhs; addInstruction(std::move(cmp)); - addTracingJumpInstruction(Instruction::JumpTrue()).link(target); + addJumpInstruction(Instruction::JumpTrue()).link(target); } void setUnwindHandler(ExceptionHandler *handler) @@ -257,13 +246,6 @@ QT_WARNING_POP void finalize(Compiler::Context *context); - template - Jump addTracingJumpInstruction(InstrData &&data) - { - data.traceSlot = nextTraceInfo(); - return addJumpInstruction(data); - } - template Jump addJumpInstruction(const InstrData &data) { @@ -275,9 +257,9 @@ QT_WARNING_POP void addCJumpInstruction(bool jumpOnFalse, const Label *trueLabel, const Label *falseLabel) { if (jumpOnFalse) - addTracingJumpInstruction(Instruction::JumpFalse()).link(*falseLabel); + addJumpInstruction(Instruction::JumpFalse()).link(*falseLabel); else - addTracingJumpInstruction(Instruction::JumpTrue()).link(*trueLabel); + addJumpInstruction(Instruction::JumpTrue()).link(*trueLabel); } void clearLastInstruction() @@ -285,27 +267,6 @@ QT_WARNING_POP lastInstrType = -1; } - TraceInfoCount nextTraceInfo() - { - // If tracing is disabled, use slot 0 to unconditionally store all trace info - if (nTraceInfos == CompiledData::Function::NoTracing()) - return TraceInfoCount(0); - return nTraceInfos++; - } - - void setTracing(bool onoff, int argumentCount) - { - if (onoff) - nTraceInfos = argumentCount; - else - nTraceInfos = CompiledData::Function::NoTracing(); - } - - TraceInfoCount traceInfoCount() const - { - return nTraceInfos; - } - void addLoopStart(const Label &start) { _labelInfos.push_back({ start.index }); @@ -346,8 +307,6 @@ private: int lastInstrType = -1; Moth::Instr lastInstr; - TraceInfoCount nTraceInfos = TraceInfoCount(0); - struct LabelInfo { int labelIndex; }; diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 88d3dbe9c5..1537ce408d 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -289,13 +289,13 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) case UMinus: { expr.loadInAccumulator(); Instruction::UMinus uminus = {}; - bytecodeGenerator->addTracingInstruction(uminus); + bytecodeGenerator->addInstruction(uminus); return Reference::fromAccumulator(this); } case UPlus: { expr.loadInAccumulator(); Instruction::UPlus uplus = {}; - bytecodeGenerator->addTracingInstruction(uplus); + bytecodeGenerator->addInstruction(uplus); return Reference::fromAccumulator(this); } case Not: { @@ -315,10 +315,10 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::UPlus uplus = {}; - bytecodeGenerator->addTracingInstruction(uplus); + bytecodeGenerator->addInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); Instruction::Increment inc = {}; - bytecodeGenerator->addTracingInstruction(inc); + bytecodeGenerator->addInstruction(inc); e.storeConsumeAccumulator(); return originalValue; } else { @@ -330,7 +330,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::Increment inc = {}; - bytecodeGenerator->addTracingInstruction(inc); + bytecodeGenerator->addInstruction(inc); if (exprAccept(nx)) return e.storeConsumeAccumulator(); else @@ -341,10 +341,10 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::UPlus uplus = {}; - bytecodeGenerator->addTracingInstruction(uplus); + bytecodeGenerator->addInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); Instruction::Decrement dec = {}; - bytecodeGenerator->addTracingInstruction(dec); + bytecodeGenerator->addInstruction(dec); e.storeConsumeAccumulator(); return originalValue; } else { @@ -356,7 +356,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::Decrement dec = {}; - bytecodeGenerator->addTracingInstruction(dec); + bytecodeGenerator->addInstruction(dec); if (exprAccept(nx)) return e.storeConsumeAccumulator(); else @@ -1139,7 +1139,7 @@ bool Codegen::visit(ArrayPattern *ast) index.loadInAccumulator(); Instruction::Increment inc = {}; - bytecodeGenerator->addTracingInstruction(inc); + bytecodeGenerator->addInstruction(inc); index.storeConsumeAccumulator(); }; @@ -1196,7 +1196,7 @@ bool Codegen::visit(ArrayPattern *ast) next.value = lhsValue.stackSlot(); next.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addTracingJumpInstruction(Instruction::JumpTrue()).link(end); + bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); lhsValue.loadInAccumulator(); pushAccumulator(); @@ -1487,20 +1487,20 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re right.loadInAccumulator(); Instruction::Add add; add.lhs = left.stackSlot(); - bytecodeGenerator->addTracingInstruction(add); + bytecodeGenerator->addInstruction(add); break; } case QSOperator::Sub: { if (right.isConstant() && right.constant == Encode(int(1))) { left.loadInAccumulator(); Instruction::Decrement dec = {}; - bytecodeGenerator->addTracingInstruction(dec); + bytecodeGenerator->addInstruction(dec); } else { left = left.storeOnStack(); right.loadInAccumulator(); Instruction::Sub sub; sub.lhs = left.stackSlot(); - bytecodeGenerator->addTracingInstruction(sub); + bytecodeGenerator->addInstruction(sub); } break; } @@ -1517,7 +1517,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re right.loadInAccumulator(); Instruction::Mul mul; mul.lhs = left.stackSlot(); - bytecodeGenerator->addTracingInstruction(mul); + bytecodeGenerator->addInstruction(mul); break; } case QSOperator::Div: { @@ -1533,7 +1533,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re right.loadInAccumulator(); Instruction::Mod mod; mod.lhs = left.stackSlot(); - bytecodeGenerator->addTracingInstruction(mod); + bytecodeGenerator->addInstruction(mod); break; } case QSOperator::BitAnd: @@ -1902,7 +1902,7 @@ bool Codegen::visit(CallExpression *ast) call.thisObject = baseObject.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else { Instruction::TailCall call; call.func = base.stackSlot(); @@ -1931,14 +1931,14 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.lookupIndex = registerGetterLookup(base.propertyNameIndex); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else { Instruction::CallProperty call; call.base = base.propertyBase.stackSlot(); call.name = base.propertyNameIndex; call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } } else if (base.type == Reference::Subscript) { Instruction::CallElement call; @@ -1946,33 +1946,33 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.index = base.elementSubscript.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else if (base.type == Reference::Name) { if (base.name == QStringLiteral("eval")) { Instruction::CallPossiblyDirectEval call; call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else if (!disable_lookups && useFastLookups && base.global) { if (base.qmlGlobal) { Instruction::CallQmlContextPropertyLookup call; call.index = registerQmlContextPropertyGetterLookup(base.nameAsIndex()); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else { Instruction::CallGlobalLookup call; call.index = registerGlobalGetterLookup(base.nameAsIndex()); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } } else { Instruction::CallName call; call.name = base.nameAsIndex(); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } } else if (base.type == Reference::SuperProperty) { Reference receiver = base.baseObject(); @@ -1989,14 +1989,14 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.thisObject = receiver.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } else { Q_ASSERT(base.isStackSlot()); Instruction::CallValue call; call.name = base.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; - bytecodeGenerator->addTracingInstruction(call); + bytecodeGenerator->addInstruction(call); } setExprResult(Reference::fromAccumulator(this)); @@ -2732,14 +2732,14 @@ bool Codegen::visit(TemplateLiteral *ast) Instruction::Add instr; instr.lhs = temp2; - bytecodeGenerator->addTracingInstruction(instr); + bytecodeGenerator->addInstruction(instr); } else { expr.loadInAccumulator(); } Instruction::Add instr; instr.lhs = temp; - bytecodeGenerator->addTracingInstruction(instr); + bytecodeGenerator->addInstruction(instr); } auto r = Reference::fromAccumulator(this); @@ -2997,7 +2997,6 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, bool savedFunctionEndsWithReturn = functionEndsWithReturn; functionEndsWithReturn = endsWithReturn(_module, body); - bytecodeGenerator->setTracing(_functionContext->canUseTracingJit(), _context->arguments.size()); // reserve the js stack frame (Context & js Function & accumulator) bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size()); @@ -3084,7 +3083,6 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, Q_ASSERT(_context == _functionContext); bytecodeGenerator->finalize(_context); _context->registerCountInFunction = bytecodeGenerator->registerCount(); - _context->nTraceInfos = bytecodeGenerator->traceInfoCount(); static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); if (showCode) { qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict @@ -3288,7 +3286,7 @@ bool Codegen::visit(ForEachStatement *ast) next.value = lhsValue.stackSlot(); next.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addTracingJumpInstruction(Instruction::JumpTrue()).link(end); + bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); // each iteration gets it's own context, as per spec { @@ -4225,7 +4223,7 @@ void Codegen::Reference::storeAccumulator() const Instruction::StoreElement store; store.base = elementBase; store.index = elementSubscript.stackSlot(); - codegen->bytecodeGenerator->addTracingInstruction(store); + codegen->bytecodeGenerator->addInstruction(store); } return; case Invalid: case Accumulator: @@ -4315,12 +4313,12 @@ QT_WARNING_POP if (!scope) { Instruction::LoadLocal load; load.index = index; - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } else { Instruction::LoadScopedLocal load; load.index = index; load.scope = scope; - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } tdzCheck(requiresTDZCheck); return; @@ -4344,16 +4342,16 @@ QT_WARNING_POP if (qmlGlobal) { Instruction::LoadQmlContextPropertyLookup load; load.index = codegen->registerQmlContextPropertyGetterLookup(nameAsIndex()); - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } else { Instruction::LoadGlobalLookup load; load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } } else { Instruction::LoadName load; load.name = nameAsIndex(); - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } return; case Member: @@ -4362,11 +4360,11 @@ QT_WARNING_POP if (!disable_lookups && codegen->useFastLookups) { Instruction::GetLookup load; load.index = codegen->registerGetterLookup(propertyNameIndex); - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } else { Instruction::LoadProperty load; load.name = propertyNameIndex; - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } return; case Import: { @@ -4381,7 +4379,7 @@ QT_WARNING_POP tdzCheck(subscriptRequiresTDZCheck); Instruction::LoadElement load; load.base = elementBase; - codegen->bytecodeGenerator->addTracingInstruction(load); + codegen->bytecodeGenerator->addInstruction(load); } return; case Invalid: break; diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 4cfd2d86e8..dd7ba471c3 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -79,7 +79,7 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x22 // Add trace slot to UPlus +#define QV4_DATA_STRUCTURE_VERSION 0x23 // Remove trace slots class QIODevice; class QQmlPropertyData; @@ -296,10 +296,6 @@ struct Function quint32_le nLabelInfos; size_t labelInfosOffset() const { return lineNumberOffset() + nLineNumbers * sizeof(CodeOffsetToLine); } - typedef quint16_le TraceInfoCount; - TraceInfoCount nTraceInfos; - static constexpr TraceInfoCount NoTracing() { return TraceInfoCount::max(); } - // Keep all unaligned data at the end quint8 flags; quint8 padding1; diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 01c033cb2a..123d77f788 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -424,7 +424,6 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte Q_ASSERT(function->lineNumberOffset() == currentOffset); currentOffset += function->nLineNumbers * sizeof(CompiledData::CodeOffsetToLine); - function->nTraceInfos = irFunction->nTraceInfos; function->nRegisters = irFunction->registerCountInFunction; if (!irFunction->labelInfo.empty()) { diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 52215c2ce6..d1a5fee92b 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -410,26 +410,4 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) nRegisters = bytecodeGenerator->currentRegister() - registerOffset; } -bool Context::canUseTracingJit() const -{ -#if QT_CONFIG(qml_tracing) - static bool forceTracing = !qEnvironmentVariableIsEmpty("QV4_FORCE_TRACING"); - if (forceTracing) //### we can probably remove this when tracing is turned on by default - return true; // to be used by unittests - - static bool disableTracing = !qEnvironmentVariableIsEmpty("QV4_DISABLE_TRACING"); - if (disableTracing) - return false; - - static QStringList onlyTrace = - qEnvironmentVariable("QV4_ONLY_TRACE").split(QLatin1Char(','), QString::SkipEmptyParts); - if (!onlyTrace.isEmpty()) - return onlyTrace.contains(name); - - return true; -#else - return false; -#endif -} - QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 57ef4be36e..f56942fffa 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -162,7 +162,6 @@ struct Context { int line = 0; int column = 0; int registerCountInFunction = 0; - uint nTraceInfos = 0; int functionIndex = -1; int blockIndex = -1; @@ -363,8 +362,6 @@ struct Context { return parent->canHaveTailCalls(); return false; } - - bool canUseTracingJit() const; }; diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index b019f191fa..e022d14264 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -171,8 +171,6 @@ QString dumpArguments(int argc, int argv, int nFormals) return QStringLiteral("(") + dumpRegister(argv, nFormals) + QStringLiteral(", ") + QString::number(argc) + QStringLiteral(")"); } -#define TRACE_SLOT QStringLiteral(" {%1}").arg(traceSlot) - void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*startLine*/, const QVector &lineNumberMapping) { MOTH_JUMP_TABLE; @@ -241,9 +239,9 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_BEGIN_INSTR(LoadLocal) if (index < nLocals) - d << "l" << index << TRACE_SLOT; + d << "l" << index; else - d << "a" << (index - nLocals) << TRACE_SLOT; + d << "a" << (index - nLocals); MOTH_END_INSTR(LoadLocal) MOTH_BEGIN_INSTR(StoreLocal) @@ -255,9 +253,9 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_BEGIN_INSTR(LoadScopedLocal) if (index < nLocals) - d << "l" << index << "@" << scope << TRACE_SLOT; + d << "l" << index << "@" << scope; else - d << "a" << (index - nLocals) << "@" << scope << TRACE_SLOT; + d << "a" << (index - nLocals) << "@" << scope; MOTH_END_INSTR(LoadScopedLocal) MOTH_BEGIN_INSTR(StoreScopedLocal) @@ -280,15 +278,15 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(LoadClosure) MOTH_BEGIN_INSTR(LoadName) - d << name << TRACE_SLOT; + d << name; MOTH_END_INSTR(LoadName) MOTH_BEGIN_INSTR(LoadGlobalLookup) - d << index << TRACE_SLOT; + d << index; MOTH_END_INSTR(LoadGlobalLookup) MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup) - d << index << TRACE_SLOT; + d << index; MOTH_END_INSTR(LoadQmlContextPropertyLookup) MOTH_BEGIN_INSTR(StoreNameSloppy) @@ -300,20 +298,19 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(StoreNameStrict) MOTH_BEGIN_INSTR(LoadElement) - d << dumpRegister(base, nFormals) << "[acc]" << TRACE_SLOT; + d << dumpRegister(base, nFormals) << "[acc]"; MOTH_END_INSTR(LoadElement) MOTH_BEGIN_INSTR(StoreElement) - d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]" - << TRACE_SLOT; + d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]"; MOTH_END_INSTR(StoreElement) MOTH_BEGIN_INSTR(LoadProperty) - d << "acc[" << name << "]" << TRACE_SLOT; + d << "acc[" << name << "]"; MOTH_END_INSTR(LoadProperty) MOTH_BEGIN_INSTR(GetLookup) - d << "acc(" << index << ")" << TRACE_SLOT; + d << "acc(" << index << ")"; MOTH_END_INSTR(GetLookup) MOTH_BEGIN_INSTR(StoreProperty) @@ -343,49 +340,48 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(Resume) MOTH_BEGIN_INSTR(CallValue) - d << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + d << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallValue) MOTH_BEGIN_INSTR(CallWithReceiver) d << dumpRegister(name, nFormals) << dumpRegister(thisObject, nFormals) - << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallWithReceiver) MOTH_BEGIN_INSTR(CallProperty) d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals) - << TRACE_SLOT; + ; MOTH_END_INSTR(CallProperty) MOTH_BEGIN_INSTR(CallPropertyLookup) d << dumpRegister(base, nFormals) << "." << lookupIndex - << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallPropertyLookup) MOTH_BEGIN_INSTR(CallElement) d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]" - << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallElement) MOTH_BEGIN_INSTR(CallName) - d << name << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + d << name << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallName) MOTH_BEGIN_INSTR(CallPossiblyDirectEval) - d << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + d << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallPossiblyDirectEval) MOTH_BEGIN_INSTR(CallGlobalLookup) - d << index << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + d << index << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallGlobalLookup) MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup) - d << index << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + d << index << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallQmlContextPropertyLookup) MOTH_BEGIN_INSTR(CallWithSpread) d << "new " << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) - << dumpArguments(argc, argv, nFormals) - << TRACE_SLOT; + << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallWithSpread) MOTH_BEGIN_INSTR(Construct) @@ -528,11 +524,11 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(Jump) MOTH_BEGIN_INSTR(JumpTrue) - d << ABSOLUTE_OFFSET() << TRACE_SLOT; + d << ABSOLUTE_OFFSET(); MOTH_END_INSTR(JumpTrue) MOTH_BEGIN_INSTR(JumpFalse) - d << ABSOLUTE_OFFSET() << TRACE_SLOT; + d << ABSOLUTE_OFFSET(); MOTH_END_INSTR(JumpFalse) MOTH_BEGIN_INSTR(JumpNotUndefined) @@ -593,26 +589,22 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(UNot) MOTH_BEGIN_INSTR(UPlus) - d << TRACE_SLOT; MOTH_END_INSTR(UPlus) MOTH_BEGIN_INSTR(UMinus) - d << TRACE_SLOT; MOTH_END_INSTR(UMinus) MOTH_BEGIN_INSTR(UCompl) MOTH_END_INSTR(UCompl) MOTH_BEGIN_INSTR(Increment) - d << TRACE_SLOT; MOTH_END_INSTR(Increment) MOTH_BEGIN_INSTR(Decrement) - d << TRACE_SLOT; MOTH_END_INSTR(Decrement) MOTH_BEGIN_INSTR(Add) - d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + d << dumpRegister(lhs, nFormals) << ", acc"; MOTH_END_INSTR(Add) MOTH_BEGIN_INSTR(BitAnd) @@ -668,7 +660,7 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(Exp) MOTH_BEGIN_INSTR(Mul) - d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + d << dumpRegister(lhs, nFormals) << ", acc"; MOTH_END_INSTR(Mul) MOTH_BEGIN_INSTR(Div) @@ -676,11 +668,11 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(Div) MOTH_BEGIN_INSTR(Mod) - d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + d << dumpRegister(lhs, nFormals) << ", acc"; MOTH_END_INSTR(Mod) MOTH_BEGIN_INSTR(Sub) - d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + d << dumpRegister(lhs, nFormals) << ", acc"; MOTH_END_INSTR(Sub) MOTH_BEGIN_INSTR(CmpIn) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 6a8c9a9549..6421fc9d67 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -77,20 +77,20 @@ QT_BEGIN_NAMESPACE #define INSTR_StoreReg(op) INSTRUCTION(op, StoreReg, 1, reg) #define INSTR_MoveReg(op) INSTRUCTION(op, MoveReg, 2, srcReg, destReg) #define INSTR_LoadImport(op) INSTRUCTION(op, LoadImport, 1, index) -#define INSTR_LoadLocal(op) INSTRUCTION(op, LoadLocal, 2, index, traceSlot) +#define INSTR_LoadLocal(op) INSTRUCTION(op, LoadLocal, 1, index) #define INSTR_StoreLocal(op) INSTRUCTION(op, StoreLocal, 1, index) -#define INSTR_LoadScopedLocal(op) INSTRUCTION(op, LoadScopedLocal, 3, scope, index, traceSlot) +#define INSTR_LoadScopedLocal(op) INSTRUCTION(op, LoadScopedLocal, 2, scope, index) #define INSTR_StoreScopedLocal(op) INSTRUCTION(op, StoreScopedLocal, 2, scope, index) #define INSTR_LoadRuntimeString(op) INSTRUCTION(op, LoadRuntimeString, 1, stringId) #define INSTR_MoveRegExp(op) INSTRUCTION(op, MoveRegExp, 2, regExpId, destReg) #define INSTR_LoadClosure(op) INSTRUCTION(op, LoadClosure, 1, value) -#define INSTR_LoadName(op) INSTRUCTION(op, LoadName, 2, name, traceSlot) -#define INSTR_LoadGlobalLookup(op) INSTRUCTION(op, LoadGlobalLookup, 2, index, traceSlot) -#define INSTR_LoadQmlContextPropertyLookup(op) INSTRUCTION(op, LoadQmlContextPropertyLookup, 2, index, traceSlot) +#define INSTR_LoadName(op) INSTRUCTION(op, LoadName, 1, name) +#define INSTR_LoadGlobalLookup(op) INSTRUCTION(op, LoadGlobalLookup, 1, index) +#define INSTR_LoadQmlContextPropertyLookup(op) INSTRUCTION(op, LoadQmlContextPropertyLookup, 1, index) #define INSTR_StoreNameSloppy(op) INSTRUCTION(op, StoreNameSloppy, 1, name) #define INSTR_StoreNameStrict(op) INSTRUCTION(op, StoreNameStrict, 1, name) -#define INSTR_LoadProperty(op) INSTRUCTION(op, LoadProperty, 2, name, traceSlot) -#define INSTR_GetLookup(op) INSTRUCTION(op, GetLookup, 2, index, traceSlot) +#define INSTR_LoadProperty(op) INSTRUCTION(op, LoadProperty, 1, name) +#define INSTR_GetLookup(op) INSTRUCTION(op, GetLookup, 1, index) #define INSTR_LoadIdObject(op) INSTRUCTION(op, LoadIdObject, 2, index, base) #define INSTR_Yield(op) INSTRUCTION(op, Yield, 0) #define INSTR_YieldStar(op) INSTRUCTION(op, YieldStar, 0) @@ -100,18 +100,18 @@ QT_BEGIN_NAMESPACE #define INSTR_SetLookup(op) INSTRUCTION(op, SetLookup, 2, index, base) #define INSTR_LoadSuperProperty(op) INSTRUCTION(op, LoadSuperProperty, 1, property) #define INSTR_StoreSuperProperty(op) INSTRUCTION(op, StoreSuperProperty, 1, property) -#define INSTR_LoadElement(op) INSTRUCTION(op, LoadElement, 2, base, traceSlot) -#define INSTR_StoreElement(op) INSTRUCTION(op, StoreElement, 3, base, index, traceSlot) -#define INSTR_CallValue(op) INSTRUCTION(op, CallValue, 4, name, argc, argv, traceSlot) -#define INSTR_CallWithReceiver(op) INSTRUCTION(op, CallWithReceiver, 5, name, thisObject, argc, argv, traceSlot) -#define INSTR_CallProperty(op) INSTRUCTION(op, CallProperty, 5, name, base, argc, argv, traceSlot) -#define INSTR_CallPropertyLookup(op) INSTRUCTION(op, CallPropertyLookup, 5, lookupIndex, base, argc, argv, traceSlot) -#define INSTR_CallElement(op) INSTRUCTION(op, CallElement, 5, base, index, argc, argv, traceSlot) -#define INSTR_CallName(op) INSTRUCTION(op, CallName, 4, name, argc, argv, traceSlot) -#define INSTR_CallPossiblyDirectEval(op) INSTRUCTION(op, CallPossiblyDirectEval, 3, argc, argv, traceSlot) -#define INSTR_CallGlobalLookup(op) INSTRUCTION(op, CallGlobalLookup, 4, index, argc, argv, traceSlot) -#define INSTR_CallQmlContextPropertyLookup(op) INSTRUCTION(op, CallQmlContextPropertyLookup, 4, index, argc, argv, traceSlot) -#define INSTR_CallWithSpread(op) INSTRUCTION(op, CallWithSpread, 5, func, thisObject, argc, argv, traceSlot) +#define INSTR_LoadElement(op) INSTRUCTION(op, LoadElement, 1, base) +#define INSTR_StoreElement(op) INSTRUCTION(op, StoreElement, 2, base, index) +#define INSTR_CallValue(op) INSTRUCTION(op, CallValue, 3, name, argc, argv) +#define INSTR_CallWithReceiver(op) INSTRUCTION(op, CallWithReceiver, 4, name, thisObject, argc, argv) +#define INSTR_CallProperty(op) INSTRUCTION(op, CallProperty, 4, name, base, argc, argv) +#define INSTR_CallPropertyLookup(op) INSTRUCTION(op, CallPropertyLookup, 4, lookupIndex, base, argc, argv) +#define INSTR_CallElement(op) INSTRUCTION(op, CallElement, 4, base, index, argc, argv) +#define INSTR_CallName(op) INSTRUCTION(op, CallName, 3, name, argc, argv) +#define INSTR_CallPossiblyDirectEval(op) INSTRUCTION(op, CallPossiblyDirectEval, 2, argc, argv) +#define INSTR_CallGlobalLookup(op) INSTRUCTION(op, CallGlobalLookup, 3, index, argc, argv) +#define INSTR_CallQmlContextPropertyLookup(op) INSTRUCTION(op, CallQmlContextPropertyLookup, 3, index, argc, argv) +#define INSTR_CallWithSpread(op) INSTRUCTION(op, CallWithSpread, 4, func, thisObject, argc, argv) #define INSTR_Construct(op) INSTRUCTION(op, Construct, 3, func, argc, argv) #define INSTR_ConstructWithSpread(op) INSTRUCTION(op, ConstructWithSpread, 3, func, argc, argv) #define INSTR_SetUnwindHandler(op) INSTRUCTION(op, SetUnwindHandler, 1, offset) @@ -148,8 +148,8 @@ QT_BEGIN_NAMESPACE #define INSTR_LoadSuperConstructor(op) INSTRUCTION(op, LoadSuperConstructor, 0) #define INSTR_ToObject(op) INSTRUCTION(op, ToObject, 0) #define INSTR_Jump(op) INSTRUCTION(op, Jump, 1, offset) -#define INSTR_JumpTrue(op) INSTRUCTION(op, JumpTrue, 2, traceSlot, offset) -#define INSTR_JumpFalse(op) INSTRUCTION(op, JumpFalse, 2, traceSlot, offset) +#define INSTR_JumpTrue(op) INSTRUCTION(op, JumpTrue, 1, offset) +#define INSTR_JumpFalse(op) INSTRUCTION(op, JumpFalse, 1, offset) #define INSTR_JumpNotUndefined(op) INSTRUCTION(op, JumpNotUndefined, 1, offset) #define INSTR_JumpNoException(op) INSTRUCTION(op, JumpNoException, 1, offset) #define INSTR_CmpEqNull(op) INSTRUCTION(op, CmpEqNull, 0) @@ -167,12 +167,12 @@ QT_BEGIN_NAMESPACE #define INSTR_CmpIn(op) INSTRUCTION(op, CmpIn, 1, lhs) #define INSTR_CmpInstanceOf(op) INSTRUCTION(op, CmpInstanceOf, 1, lhs) #define INSTR_UNot(op) INSTRUCTION(op, UNot, 0) -#define INSTR_UPlus(op) INSTRUCTION(op, UPlus, 1, traceSlot) -#define INSTR_UMinus(op) INSTRUCTION(op, UMinus, 1, traceSlot) +#define INSTR_UPlus(op) INSTRUCTION(op, UPlus, 0) +#define INSTR_UMinus(op) INSTRUCTION(op, UMinus, 0) #define INSTR_UCompl(op) INSTRUCTION(op, UCompl, 0) -#define INSTR_Increment(op) INSTRUCTION(op, Increment, 1, traceSlot) -#define INSTR_Decrement(op) INSTRUCTION(op, Decrement, 1, traceSlot) -#define INSTR_Add(op) INSTRUCTION(op, Add, 2, lhs, traceSlot) +#define INSTR_Increment(op) INSTRUCTION(op, Increment, 0) +#define INSTR_Decrement(op) INSTRUCTION(op, Decrement, 0) +#define INSTR_Add(op) INSTRUCTION(op, Add, 1, lhs) #define INSTR_BitAnd(op) INSTRUCTION(op, BitAnd, 1, lhs) #define INSTR_BitOr(op) INSTRUCTION(op, BitOr, 1, lhs) #define INSTR_BitXor(op) INSTRUCTION(op, BitXor, 1, lhs) @@ -186,10 +186,10 @@ QT_BEGIN_NAMESPACE #define INSTR_ShrConst(op) INSTRUCTION(op, ShrConst, 1, rhs) #define INSTR_ShlConst(op) INSTRUCTION(op, ShlConst, 1, rhs) #define INSTR_Exp(op) INSTRUCTION(op, Exp, 1, lhs) -#define INSTR_Mul(op) INSTRUCTION(op, Mul, 2, lhs, traceSlot) +#define INSTR_Mul(op) INSTRUCTION(op, Mul, 1, lhs) #define INSTR_Div(op) INSTRUCTION(op, Div, 1, lhs) -#define INSTR_Mod(op) INSTRUCTION(op, Mod, 2, lhs, traceSlot) -#define INSTR_Sub(op) INSTRUCTION(op, Sub, 2, lhs, traceSlot) +#define INSTR_Mod(op) INSTRUCTION(op, Mod, 1, lhs) +#define INSTR_Sub(op) INSTRUCTION(op, Sub, 1, lhs) #define INSTR_LoadQmlImportedScripts(op) INSTRUCTION(op, LoadQmlImportedScripts, 1, result) #define INSTR_InitializeBlockDeadTemporalZone(op) INSTRUCTION(op, InitializeBlockDeadTemporalZone, 2, firstReg, count) #define INSTR_ThrowOnNullOrUndefined(op) INSTRUCTION(op, ThrowOnNullOrUndefined, 0) diff --git a/src/qml/configure.json b/src/qml/configure.json index ef6a9f5e7e..0f7de29594 100644 --- a/src/qml/configure.json +++ b/src/qml/configure.json @@ -8,7 +8,6 @@ "commandline": { "options": { "qml-network": "boolean", - "qml-tracing": "boolean", "qml-debug": "boolean" } }, @@ -107,14 +106,6 @@ Finally, ios and tvos can technically use the JIT but Apple does not allow it. Therefore, it's disabled by default." }, - "qml-tracing": { - "label": "QML tracing JIT support", - "purpose": "Provides a JIT that uses trace information generated by the interpreter.", - "section": "QML", - "condition": "features.qml-jit", - "output": [ "privateFeature" ], - "autoDetect": false - }, "qml-debug": { "label": "QML debugging and profiling support", "purpose": "Provides infrastructure and plugins for debugging and profiling.", @@ -209,7 +200,6 @@ "qml-network", "qml-debug", "qml-jit", - "qml-tracing", "qml-sequence-object", "qml-list-model", "qml-xml-http-request", diff --git a/src/qml/jit/jit.pri b/src/qml/jit/jit.pri index bc0d20dba7..503ce0ebcd 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -10,34 +10,3 @@ HEADERS += \ $$PWD/qv4baselinejit_p.h \ $$PWD/qv4baselineassembler_p.h \ $$PWD/qv4assemblercommon_p.h - -qtConfig(qml-tracing) { -SOURCES += \ - $$PWD/qv4ir.cpp \ - $$PWD/qv4operation.cpp \ - $$PWD/qv4node.cpp \ - $$PWD/qv4graph.cpp \ - $$PWD/qv4graphbuilder.cpp \ - $$PWD/qv4lowering.cpp \ - $$PWD/qv4tracingjit.cpp \ - $$PWD/qv4mi.cpp \ - $$PWD/qv4domtree.cpp \ - $$PWD/qv4schedulers.cpp \ - $$PWD/qv4blockscheduler.cpp \ - $$PWD/qv4loopinfo.cpp - -HEADERS += \ - $$PWD/qv4ir_p.h \ - $$PWD/qv4operation_p.h \ - $$PWD/qv4runtimesupport_p.h \ - $$PWD/qv4node_p.h \ - $$PWD/qv4graph_p.h \ - $$PWD/qv4graphbuilder_p.h \ - $$PWD/qv4lowering_p.h \ - $$PWD/qv4mi_p.h \ - $$PWD/qv4miblockset_p.h \ - $$PWD/qv4domtree_p.h \ - $$PWD/qv4schedulers_p.h \ - $$PWD/qv4blockscheduler_p.h \ - $$PWD/qv4loopinfo_p.h -} diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 60880419a6..517f0940e5 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -149,7 +149,7 @@ void BaselineJIT::generate_LoadImport(int index) as->loadImport(index); } -void BaselineJIT::generate_LoadLocal(int index, int /*traceSlot*/) +void BaselineJIT::generate_LoadLocal(int index) { as->loadLocal(index); } @@ -160,7 +160,7 @@ void BaselineJIT::generate_StoreLocal(int index) as->storeLocal(index); } -void BaselineJIT::generate_LoadScopedLocal(int scope, int index, int /*traceSlot*/) +void BaselineJIT::generate_LoadScopedLocal(int scope, int index) { as->loadLocal(index, scope); } @@ -193,7 +193,7 @@ void BaselineJIT::generate_LoadClosure(int value) BASELINEJIT_GENERATE_RUNTIME_CALL(Closure, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) +void BaselineJIT::generate_LoadName(int name) { STORE_IP(); as->prepareCallWithArgCount(2); @@ -202,7 +202,7 @@ void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) BASELINEJIT_GENERATE_RUNTIME_CALL(LoadName, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_LoadGlobalLookup(int index, int /*traceSlot*/) +void BaselineJIT::generate_LoadGlobalLookup(int index) { as->prepareCallWithArgCount(3); as->passInt32AsArg(index, 2); @@ -211,7 +211,7 @@ void BaselineJIT::generate_LoadGlobalLookup(int index, int /*traceSlot*/) BASELINEJIT_GENERATE_RUNTIME_CALL(LoadGlobalLookup, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_LoadQmlContextPropertyLookup(int index, int /*traceSlot*/) +void BaselineJIT::generate_LoadQmlContextPropertyLookup(int index) { as->prepareCallWithArgCount(2); as->passInt32AsArg(index, 1); @@ -241,7 +241,7 @@ void BaselineJIT::generate_StoreNameStrict(int name) BASELINEJIT_GENERATE_RUNTIME_CALL(StoreNameStrict, CallResultDestination::Ignore); } -void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) +void BaselineJIT::generate_LoadElement(int base) { STORE_IP(); STORE_ACC(); @@ -252,7 +252,7 @@ void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) BASELINEJIT_GENERATE_RUNTIME_CALL(LoadElement, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) +void BaselineJIT::generate_StoreElement(int base, int index) { STORE_IP(); STORE_ACC(); @@ -264,7 +264,7 @@ void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) BASELINEJIT_GENERATE_RUNTIME_CALL(StoreElement, CallResultDestination::Ignore); } -void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) +void BaselineJIT::generate_LoadProperty(int name) { STORE_IP(); STORE_ACC(); @@ -275,7 +275,7 @@ void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) BASELINEJIT_GENERATE_RUNTIME_CALL(LoadProperty, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_GetLookup(int index, int /*traceSlot*/) +void BaselineJIT::generate_GetLookup(int index) { STORE_IP(); STORE_ACC(); @@ -353,7 +353,7 @@ void BaselineJIT::generate_Resume(int) Q_UNREACHABLE(); } -void BaselineJIT::generate_CallValue(int name, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallValue(int name, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(4); @@ -364,7 +364,7 @@ void BaselineJIT::generate_CallValue(int name, int argc, int argv, int /*traceSl BASELINEJIT_GENERATE_RUNTIME_CALL(CallValue, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(5); @@ -376,7 +376,7 @@ void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, BASELINEJIT_GENERATE_RUNTIME_CALL(CallWithReceiver, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(5); @@ -388,7 +388,7 @@ void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, BASELINEJIT_GENERATE_RUNTIME_CALL(CallProperty, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(5); @@ -400,7 +400,7 @@ void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int arg BASELINEJIT_GENERATE_RUNTIME_CALL(CallPropertyLookup, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(5); @@ -412,7 +412,7 @@ void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, BASELINEJIT_GENERATE_RUNTIME_CALL(CallElement, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallName(int name, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(4); @@ -423,7 +423,7 @@ void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlo BASELINEJIT_GENERATE_RUNTIME_CALL(CallName, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(3); @@ -433,7 +433,7 @@ void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*trac BASELINEJIT_GENERATE_RUNTIME_CALL(CallPossiblyDirectEval, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(4); @@ -444,8 +444,7 @@ void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int / BASELINEJIT_GENERATE_RUNTIME_CALL(CallGlobalLookup, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, - int /*traceSlot*/) +void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(4); @@ -456,7 +455,7 @@ void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int BASELINEJIT_GENERATE_RUNTIME_CALL(CallQmlContextPropertyLookup, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, int argv, int /*traceSlot*/) +void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, int argv) { STORE_IP(); as->prepareCallWithArgCount(5); @@ -775,12 +774,12 @@ void BaselineJIT::generate_Jump(int offset) labels.insert(as->jump(absoluteOffset(offset))); } -void BaselineJIT::generate_JumpTrue(int /*traceSlot*/, int offset) +void BaselineJIT::generate_JumpTrue(int offset) { labels.insert(as->jumpTrue(absoluteOffset(offset))); } -void BaselineJIT::generate_JumpFalse(int /*traceSlot*/, int offset) +void BaselineJIT::generate_JumpFalse(int offset) { labels.insert(as->jumpFalse(absoluteOffset(offset))); } @@ -829,12 +828,12 @@ void BaselineJIT::generate_CmpInstanceOf(int lhs) } void BaselineJIT::generate_UNot() { as->unot(); } -void BaselineJIT::generate_UPlus(int /*traceSlot*/) { as->toNumber(); } -void BaselineJIT::generate_UMinus(int /*traceSlot*/) { as->uminus(); } +void BaselineJIT::generate_UPlus() { as->toNumber(); } +void BaselineJIT::generate_UMinus() { as->uminus(); } void BaselineJIT::generate_UCompl() { as->ucompl(); } -void BaselineJIT::generate_Increment(int /*traceSlot*/) { as->inc(); } -void BaselineJIT::generate_Decrement(int /*traceSlot*/) { as->dec(); } -void BaselineJIT::generate_Add(int lhs, int /*traceSlot*/) { as->add(lhs); } +void BaselineJIT::generate_Increment() { as->inc(); } +void BaselineJIT::generate_Decrement() { as->dec(); } +void BaselineJIT::generate_Add(int lhs) { as->add(lhs); } void BaselineJIT::generate_BitAnd(int lhs) { as->bitAnd(lhs); } void BaselineJIT::generate_BitOr(int lhs) { as->bitOr(lhs); } @@ -858,10 +857,10 @@ void BaselineJIT::generate_Exp(int lhs) { as->passJSSlotAsArg(lhs, 0); BASELINEJIT_GENERATE_RUNTIME_CALL(Exp, CallResultDestination::InAccumulator); } -void BaselineJIT::generate_Mul(int lhs, int /*traceSlot*/) { as->mul(lhs); } +void BaselineJIT::generate_Mul(int lhs) { as->mul(lhs); } void BaselineJIT::generate_Div(int lhs) { as->div(lhs); } -void BaselineJIT::generate_Mod(int lhs, int /*traceSlot*/) { as->mod(lhs); } -void BaselineJIT::generate_Sub(int lhs, int /*traceSlot*/) { as->sub(lhs); } +void BaselineJIT::generate_Mod(int lhs) { as->mod(lhs); } +void BaselineJIT::generate_Sub(int lhs) { as->sub(lhs); } //void BaselineJIT::generate_BinopContext(int alu, int lhs) //{ diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 941e0cd408..46622d29e6 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -87,22 +87,22 @@ public: void generate_StoreReg(int reg) override; void generate_MoveReg(int srcReg, int destReg) override; void generate_LoadImport(int index) override; - void generate_LoadLocal(int index, int traceSlot) override; + void generate_LoadLocal(int index) override; void generate_StoreLocal(int index) override; - void generate_LoadScopedLocal(int scope, int index, int traceSlot) override; + void generate_LoadScopedLocal(int scope, int index) override; void generate_StoreScopedLocal(int scope, int index) override; void generate_LoadRuntimeString(int stringId) override; void generate_MoveRegExp(int regExpId, int destReg) override; void generate_LoadClosure(int value) override; - void generate_LoadName(int name, int traceSlot) override; - void generate_LoadGlobalLookup(int index, int traceSlot) override; - void generate_LoadQmlContextPropertyLookup(int index, int traceSlot) override; + void generate_LoadName(int name) override; + void generate_LoadGlobalLookup(int index) override; + void generate_LoadQmlContextPropertyLookup(int index) override; void generate_StoreNameSloppy(int name) override; void generate_StoreNameStrict(int name) override; - void generate_LoadElement(int base, int traceSlot) override; - void generate_StoreElement(int base, int index, int traceSlot) override; - void generate_LoadProperty(int name, int traceSlot) override; - void generate_GetLookup(int index, int traceSlot) override; + void generate_LoadElement(int base) override; + void generate_StoreElement(int base, int index) override; + void generate_LoadProperty(int name) override; + void generate_GetLookup(int index) override; void generate_StoreProperty(int name, int base) override; void generate_SetLookup(int index, int base) override; void generate_LoadSuperProperty(int property) override; @@ -111,16 +111,16 @@ public: void generate_YieldStar() override; void generate_Resume(int) override; - void generate_CallValue(int name, int argc, int argv, int traceSlot) override; - void generate_CallWithReceiver(int name, int thisObject, int argc, int argv, int traceSlot) override; - void generate_CallProperty(int name, int base, int argc, int argv, int traceSlot) override; - void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, int traceSlot) override; - void generate_CallElement(int base, int index, int argc, int argv, int traceSlot) override; - void generate_CallName(int name, int argc, int argv, int traceSlot) override; - void generate_CallPossiblyDirectEval(int argc, int argv, int traceSlot) override; - void generate_CallGlobalLookup(int index, int argc, int argv, int traceSlot) override; - void generate_CallQmlContextPropertyLookup(int index, int argc, int argv, int traceSlot) override; - void generate_CallWithSpread(int func, int thisObject, int argc, int argv, int traceSlot) override; + void generate_CallValue(int name, int argc, int argv) override; + void generate_CallWithReceiver(int name, int thisObject, int argc, int argv) override; + void generate_CallProperty(int name, int base, int argc, int argv) override; + void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) override; + void generate_CallElement(int base, int index, int argc, int argv) override; + void generate_CallName(int name, int argc, int argv) override; + void generate_CallPossiblyDirectEval(int argc, int argv) override; + void generate_CallGlobalLookup(int index, int argc, int argv) override; + void generate_CallQmlContextPropertyLookup(int index, int argc, int argv) override; + void generate_CallWithSpread(int func, int thisObject, int argc, int argv) override; void generate_TailCall(int func, int thisObject, int argc, int argv) override; void generate_Construct(int func, int argc, int argv) override; void generate_ConstructWithSpread(int func, int argc, int argv) override; @@ -159,8 +159,8 @@ public: void generate_LoadSuperConstructor() override; void generate_ToObject() override; void generate_Jump(int offset) override; - void generate_JumpTrue(int traceSlot, int offset) override; - void generate_JumpFalse(int traceSlot, int offset) override; + void generate_JumpTrue(int offset) override; + void generate_JumpFalse(int offset) override; void generate_JumpNoException(int offset) override; void generate_JumpNotUndefined(int offset) override; void generate_CmpEqNull() override; @@ -178,12 +178,12 @@ public: void generate_CmpIn(int lhs) override; void generate_CmpInstanceOf(int lhs) override; void generate_UNot() override; - void generate_UPlus(int) override; - void generate_UMinus(int traceSlot) override; + void generate_UPlus() override; + void generate_UMinus() override; void generate_UCompl() override; - void generate_Increment(int traceSlot) override; - void generate_Decrement(int traceSlot) override; - void generate_Add(int lhs, int traceSlot) override; + void generate_Increment() override; + void generate_Decrement() override; + void generate_Add(int lhs) override; void generate_BitAnd(int lhs) override; void generate_BitOr(int lhs) override; void generate_BitXor(int lhs) override; @@ -197,10 +197,10 @@ public: void generate_ShrConst(int rhs) override; void generate_ShlConst(int rhs) override; void generate_Exp(int lhs) override; - void generate_Mul(int lhs, int traceSlot) override; + void generate_Mul(int lhs) override; void generate_Div(int lhs) override; - void generate_Mod(int lhs, int traceSlot) override; - void generate_Sub(int lhs, int traceSlot) override; + void generate_Mod(int lhs) override; + void generate_Sub(int lhs) override; void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; void generate_ThrowOnNullOrUndefined() override; void generate_GetTemplateObject(int index) override; diff --git a/src/qml/jit/qv4blockscheduler.cpp b/src/qml/jit/qv4blockscheduler.cpp deleted file mode 100644 index 3e2bfe15c5..0000000000 --- a/src/qml/jit/qv4blockscheduler.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4blockscheduler_p.h" -#include "qv4domtree_p.h" -#include "qv4loopinfo_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcBlockScheduler, "qt.v4.ir.blockscheduler") - -bool BlockScheduler::checkCandidate(MIBlock *candidate) -{ - Q_ASSERT(loopInfo.loopHeaderFor(candidate) == currentGroup.group); - - for (MIBlock *pred : candidate->inEdges()) { - if (pred->isDeoptBlock()) - continue; - - if (emitted.alreadyProcessed(pred)) - continue; - - if (dominatorTree.dominates(candidate->index(), pred->index())) { - // this is a loop, where there in - // -> candidate edge is the jump back to the top of the loop. - continue; - } - - if (pred == candidate) - // this is a very tight loop, e.g.: - // L1: ... - // goto L1 - // This can happen when, for example, the basic-block merging gets rid of the empty - // body block. In this case, we can safely schedule this block (if all other - // incoming edges are either loop-back edges, or have been scheduled already). - continue; - - return false; // an incoming edge that is not yet emitted, and is not a back-edge - } - - if (loopInfo.isLoopHeader(candidate)) { - // postpone everything, and schedule the loop first. - postponedGroups.push(currentGroup); - currentGroup = WorkForGroup(candidate); - } - - return true; -} - -MIBlock *BlockScheduler::pickNext() -{ - while (true) { - while (currentGroup.postponed.isEmpty()) { - if (postponedGroups.isEmpty()) - return nullptr; - if (currentGroup.group) // record the first and the last node of a group - loopsStartEnd[currentGroup.group] = sequence.back(); - currentGroup = postponedGroups.pop(); - } - - MIBlock *next = currentGroup.postponed.pop(); - if (checkCandidate(next)) - return next; - } - - Q_UNREACHABLE(); - return nullptr; -} - -void BlockScheduler::emitBlock(MIBlock *bb) -{ - if (emitted.alreadyProcessed(bb)) - return; - - sequence.push_back(bb); - emitted.markAsProcessed(bb); -} - -void BlockScheduler::schedule(MIBlock *functionEntryPoint) -{ - MIBlock *next = functionEntryPoint; - - while (next) { - emitBlock(next); - // postpone all outgoing edges, if they were not already processed - QVarLengthArray nonExceptionEdges; - // first postpone all exception edges, so they will be processed last - for (int i = next->outEdges().size(); i != 0; ) { - --i; - MIBlock *out = next->outEdges().at(i); - if (emitted.alreadyProcessed(out)) - continue; - if (out == nullptr) - continue; - if (out->instructions().front().opcode() == Meta::OnException) - postpone(out); - else - nonExceptionEdges.append(out); - } - for (MIBlock *edge : nonExceptionEdges) - postpone(edge); - next = pickNext(); - } - - // finally schedule all de-optimization blocks at the end - for (auto bb : dominatorTree.function()->blocks()) { - if (bb->isDeoptBlock()) - emitBlock(bb); - } -} - -void BlockScheduler::postpone(MIBlock *bb) -{ - if (currentGroup.group == loopInfo.loopHeaderFor(bb)) { - currentGroup.postponed.append(bb); - return; - } - - for (int i = postponedGroups.size(); i != 0; ) { - --i; - WorkForGroup &g = postponedGroups[i]; - if (g.group == loopInfo.loopHeaderFor(bb)) { - g.postponed.append(bb); - return; - } - } - - Q_UNREACHABLE(); -} - -void BlockScheduler::dump() const -{ - if (!lcBlockScheduler().isDebugEnabled()) - return; - - QString s = QStringLiteral("Scheduled blocks:\n"); - for (auto *bb : sequence) { - s += QLatin1String(" L") + QString::number(bb->index()); - MIBlock *loopEnd = loopsStartEnd[bb]; - if (loopEnd) - s += QLatin1String(", loop start, ends at L") + QString::number(loopEnd->index()); - s += QLatin1Char('\n'); - } - qCDebug(lcBlockScheduler).noquote().nospace() << s; -} - -BlockScheduler::BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo) - : dominatorTree(dominatorTree) - , loopInfo(loopInfo) - , sequence(0) - , emitted(dominatorTree.function()) -{ - schedule(dominatorTree.function()->blocks().front()); - - dump(); - - if (dominatorTree.function()->blockCount() != sequence.size()) { - qFatal("The block scheduler did not schedule all blocks. This is most likely due to" - "a non-natural loop."); - // Usually caused by having an execution path that manages to skip over unwind handler - // reset: any exception happening after will jump back to the unwind handler, and thereby - // creating a loop that can be entered in 2 different ways. - } -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4blockscheduler_p.h b/src/qml/jit/qv4blockscheduler_p.h deleted file mode 100644 index 1289fda9f0..0000000000 --- a/src/qml/jit/qv4blockscheduler_p.h +++ /dev/null @@ -1,155 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4BLOCKSCHEDULER_P_H -#define QV4BLOCKSCHEDULER_P_H - -// -// 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. -// - -#include - -#include "qv4mi_p.h" -#include "qv4util_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class DominatorTree; -class LoopInfo; - -// High-level algorithm: -// 0. start with the first node (the start node) of a function -// 1. emit the node -// 2. add all outgoing edges that are not yet emitted to the postponed stack -// 3. When the postponed stack is empty, pop a stack from the loop stack. If that is empty too, -// we're done. -// 4. pop a node from the postponed stack, and check if it can be scheduled: -// a. if all incoming edges are scheduled, go to 4. -// b. if an incoming edge is unscheduled, but it's a back-edge (an edge in a loop that jumps -// back to the start of the loop), ignore it -// c. if there is any unscheduled edge that is not a back-edge, ignore this node, and go to 4. -// 5. if this node is the start of a loop, push the postponed stack on the loop stack. -// 6. go back to 1. -// -// The postponing action in step 2 will put the node into its containing group. The case where this -// is important is when a (labeled) continue or a (labeled) break statement occur in a loop: the -// outgoing edge points to a node that is not part of the current loop (and possibly not of the -// parent loop). -// -// Linear scan register allocation benefits greatly from short life-time intervals with few holes -// (see for example section 4 (Lifetime Analysis) of [Wimmer1]). This algorithm makes sure that the -// blocks of a group are scheduled together, with no non-loop blocks in between. This applies -// recursively for nested loops. It also schedules groups of if-then-else-endif blocks together for -// the same reason. -class BlockScheduler -{ - const DominatorTree &dominatorTree; - const LoopInfo &loopInfo; - - struct WorkForGroup - { - MIBlock *group; - QStack postponed; - - WorkForGroup(MIBlock *group = nullptr) : group(group) {} - }; - WorkForGroup currentGroup; - QStack postponedGroups; - std::vector sequence; - - class ProcessedBlocks - { - BitVector processed; - - public: - ProcessedBlocks(MIFunction *function) - : processed(int(function->blockCount()), false) - {} - - bool alreadyProcessed(MIBlock *bb) const - { - Q_ASSERT(bb); - - return processed.at(bb->index()); - } - - void markAsProcessed(MIBlock *bb) - { - processed.setBit(bb->index()); - } - } emitted; - QHash loopsStartEnd; - - bool checkCandidate(MIBlock *candidate); - MIBlock *pickNext(); - void emitBlock(MIBlock *bb); - void schedule(MIBlock *functionEntryPoint); - void postpone(MIBlock *bb); - void dump() const; - -public: - BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo); - - const std::vector &scheduledBlockSequence() const - { return sequence; } - - QHash loopEndsByStartBlock() const - { return loopsStartEnd; } -}; - - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4BLOCKSCHEDULER_P_H diff --git a/src/qml/jit/qv4domtree.cpp b/src/qml/jit/qv4domtree.cpp deleted file mode 100644 index 9484f4e2dc..0000000000 --- a/src/qml/jit/qv4domtree.cpp +++ /dev/null @@ -1,436 +0,0 @@ -/**************************************************************************** -** -** 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 -#include - -#include "qv4domtree_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcDomTree, "qt.v4.ir.domTree") -Q_LOGGING_CATEGORY(lcDomFrontier, "qt.v4.ir.domFrontier") - -DominatorTree::DominatorTree(MIFunction *f) - : m_function(f) - , m_data(new Data) -{ - calculateIDoms(); - m_data.reset(); -} - -void DominatorTree::dumpImmediateDominators() const -{ - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Immediate dominators for " << m_function->irFunction()->name() << ":" << endl; - for (MIBlock *to : m_function->blocks()) { - MIBlock::Index from = m_idom.at(to->index()); - if (from != MIBlock::InvalidIndex) - qout << " " << from; - else - qout << " (none)"; - qout << " dominates " << to->index() << endl; - } - qCDebug(lcDomTree, "%s", buf.data().constData()); -} - -void DominatorTree::setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator) -{ - if (m_idom.size() <= size_t(dominated)) - m_idom.resize(dominated + 1); - m_idom[dominated] = dominator; -} - -bool DominatorTree::dominates(MIBlock::Index dominator, MIBlock::Index dominated) const -{ - // dominator can be Invalid when the dominated block has no dominator (i.e. the start node) - Q_ASSERT(dominated != MIBlock::InvalidIndex); - - if (dominator == dominated) - return false; - - for (MIBlock::Index it = m_idom[dominated]; it != MIBlock::InvalidIndex; it = m_idom[it]) { - if (it == dominator) - return true; - } - - return false; -} - -// Calculate a depth-first iteration order on the nodes of the dominator tree. -// -// The order of the nodes in the vector is not the same as one where a recursive depth-first -// iteration is done on a tree. Rather, the nodes are (reverse) sorted on tree depth. -// So for the: -// 1 dominates 2 -// 2 dominates 3 -// 3 dominates 4 -// 2 dominates 5 -// the order will be: -// 4, 3, 5, 2, 1 -// or: -// 4, 5, 3, 2, 1 -// So the order of nodes on the same depth is undefined, but it will be after the nodes -// they dominate, and before the nodes that dominate them. -// -// The reason for this order is that a proper DFS pre-/post-order would require inverting -// the idom vector by either building a real tree datastructure or by searching the idoms -// for siblings and children. Both have a higher time complexity than sorting by depth. -std::vector DominatorTree::calculateDFNodeIterOrder() const -{ - std::vector depths = calculateNodeDepths(); - std::vector order = m_function->blocks(); - std::sort(order.begin(), order.end(), [&depths](MIBlock *one, MIBlock *two) -> bool { - return depths.at(one->index()) > depths.at(two->index()); - }); - return order; -} - -// Algorithm: -// - for each node: -// - get the depth of a node. If it's unknown (-1): -// - get the depth of the immediate dominator. -// - if that's unknown too, calculate it by calling calculateNodeDepth -// - set the current node's depth to that of immediate dominator + 1 -std::vector DominatorTree::calculateNodeDepths() const -{ - std::vector nodeDepths(size_t(m_function->blockCount()), -1); - for (MIBlock *bb : m_function->blocks()) { - int &bbDepth = nodeDepths[bb->index()]; - if (bbDepth == -1) { - const int immDom = m_idom[bb->index()]; - if (immDom == -1) { - // no immediate dominator, so it's either the start block, or an unreachable block - bbDepth = 0; - } else { - int immDomDepth = nodeDepths[immDom]; - if (immDomDepth == -1) - immDomDepth = calculateNodeDepth(immDom, nodeDepths); - bbDepth = immDomDepth + 1; - } - } - } - return nodeDepths; -} - -// Algorithm: -// - search for the first dominator of a node that has a known depth. As all nodes are -// reachable from the start node, and that node's depth is 0, this is finite. -// - while doing that search, put all unknown nodes in the worklist -// - pop all nodes from the worklist, and set their depth to the previous' (== dominating) -// node's depth + 1 -// This way every node's depth is calculated once, and the complexity is O(n). -int DominatorTree::calculateNodeDepth(MIBlock::Index nodeIdx, std::vector &nodeDepths) const -{ - std::vector worklist; - worklist.reserve(8); - int depth = -1; - - do { - worklist.push_back(nodeIdx); - nodeIdx = m_idom[nodeIdx]; - depth = nodeDepths[nodeIdx]; - } while (depth == -1); - - for (auto it = worklist.rbegin(), eit = worklist.rend(); it != eit; ++it) - nodeDepths[*it] = ++depth; - - return depth; -} - -namespace { -struct DFSTodo { - MIBlock::Index node = MIBlock::InvalidIndex; - MIBlock::Index parent = MIBlock::InvalidIndex; - - DFSTodo() = default; - DFSTodo(MIBlock::Index node, MIBlock::Index parent) - : node(node) - , parent(parent) - {} -}; -} // anonymous namespace - -void DominatorTree::dfs(MIBlock::Index node) -{ - std::vector worklist; - worklist.reserve(m_data->vertex.capacity() / 2); - DFSTodo todo(node, MIBlock::InvalidIndex); - - while (true) { - MIBlock::Index n = todo.node; - - if (m_data->dfnum[n] == 0) { - m_data->dfnum[n] = m_data->size; - m_data->vertex[m_data->size] = n; - m_data->parent[n] = todo.parent; - ++m_data->size; - - MIBlock::OutEdges out = m_function->block(n)->outEdges(); - for (int i = out.size() - 1; i > 0; --i) - worklist.emplace_back(out[i]->index(), n); - - if (!out.isEmpty()) { - todo.node = out.first()->index(); - todo.parent = n; - continue; - } - } - - if (worklist.empty()) - break; - - todo = worklist.back(); - worklist.pop_back(); - } -} - -void DominatorTree::link(MIBlock::Index p, MIBlock::Index n) -{ - m_data->ancestor[n] = p; - m_data->best[n] = n; -} - -void DominatorTree::calculateIDoms() -{ - Q_ASSERT(m_function->block(0)->inEdges().count() == 0); - - const size_t bbCount = m_function->blockCount(); - m_data->vertex = std::vector(bbCount, MIBlock::InvalidIndex); - m_data->parent = std::vector(bbCount, MIBlock::InvalidIndex); - m_data->dfnum = std::vector(bbCount, 0); - m_data->semi = std::vector(bbCount, MIBlock::InvalidIndex); - m_data->ancestor = std::vector(bbCount, MIBlock::InvalidIndex); - m_idom = std::vector(bbCount, MIBlock::InvalidIndex); - m_data->samedom = std::vector(bbCount, MIBlock::InvalidIndex); - m_data->best = std::vector(bbCount, MIBlock::InvalidIndex); - - QHash> bucket; - bucket.reserve(int(bbCount)); - - dfs(m_function->block(0)->index()); - - std::vector worklist; - worklist.reserve(m_data->vertex.capacity() / 2); - - for (int i = m_data->size - 1; i > 0; --i) { - MIBlock::Index n = m_data->vertex[i]; - MIBlock::Index p = m_data->parent[n]; - MIBlock::Index s = p; - - for (auto inEdge : m_function->block(n)->inEdges()) { - if (inEdge->isDeoptBlock()) - continue; - MIBlock::Index v = inEdge->index(); - MIBlock::Index ss = MIBlock::InvalidIndex; - if (m_data->dfnum[v] <= m_data->dfnum[n]) - ss = v; - else - ss = m_data->semi[ancestorWithLowestSemi(v, worklist)]; - if (m_data->dfnum[ss] < m_data->dfnum[s]) - s = ss; - } - m_data->semi[n] = s; - bucket[s].push_back(n); - link(p, n); - if (bucket.contains(p)) { - for (MIBlock::Index v : bucket[p]) { - MIBlock::Index y = ancestorWithLowestSemi(v, worklist); - MIBlock::Index semi_v = m_data->semi[v]; - if (m_data->semi[y] == semi_v) - m_idom[v] = semi_v; - else - m_data->samedom[v] = y; - } - bucket.remove(p); - } - } - - for (unsigned i = 1; i < m_data->size; ++i) { - MIBlock::Index n = m_data->vertex[i]; - Q_ASSERT(n != MIBlock::InvalidIndex); - Q_ASSERT(!bucket.contains(n)); - Q_ASSERT(m_data->ancestor[n] != MIBlock::InvalidIndex); - Q_ASSERT((m_data->semi[n] != MIBlock::InvalidIndex - && m_data->dfnum[m_data->ancestor[n]] <= m_data->dfnum[m_data->semi[n]]) - || m_data->semi[n] == n); - MIBlock::Index sdn = m_data->samedom[n]; - if (sdn != MIBlock::InvalidIndex) - m_idom[n] = m_idom[sdn]; - } - - if (lcDomTree().isDebugEnabled()) - dumpImmediateDominators(); - - m_data.reset(nullptr); -} - -MIBlock::Index DominatorTree::ancestorWithLowestSemi(MIBlock::Index v, - std::vector &worklist) -{ - worklist.clear(); - for (MIBlock::Index it = v; it != MIBlock::InvalidIndex; it = m_data->ancestor[it]) - worklist.push_back(it); - - if (worklist.size() < 2) - return m_data->best[v]; - - MIBlock::Index b = MIBlock::InvalidIndex; - MIBlock::Index last = worklist.back(); - Q_ASSERT(worklist.size() <= INT_MAX); - for (int it = static_cast(worklist.size()) - 2; it >= 0; --it) { - MIBlock::Index bbIt = worklist[it]; - m_data->ancestor[bbIt] = last; - MIBlock::Index &best_it = m_data->best[bbIt]; - if (b != MIBlock::InvalidIndex - && m_data->dfnum[m_data->semi[b]] < m_data->dfnum[m_data->semi[best_it]]) { - best_it = b; - } else { - b = best_it; - } - } - return b; -} - -void DominatorFrontier::compute(const DominatorTree &domTree) -{ - struct NodeProgress { - std::vector children; - std::vector todo; - }; - - MIFunction *function = domTree.function(); - m_df.resize(function->blockCount()); - - // compute children of each node in the dominator tree - std::vector > children; // BasicBlock index -> children - children.resize(function->blockCount()); - for (MIBlock *n : function->blocks()) { - const MIBlock::Index nodeIndex = n->index(); - Q_ASSERT(function->block(nodeIndex) == n); - const MIBlock::Index nodeDominator = domTree.immediateDominator(nodeIndex); - if (nodeDominator == MIBlock::InvalidIndex) - continue; // there is no dominator to add this node to as a child (e.g. the start node) - children[nodeDominator].push_back(nodeIndex); - } - - // Fill the worklist and initialize the node status for each basic-block - std::vector nodeStatus; - nodeStatus.resize(function->blockCount()); - std::vector worklist; - worklist.reserve(function->blockCount()); - for (MIBlock *bb : function->blocks()) { - MIBlock::Index nodeIndex = bb->index(); - worklist.push_back(nodeIndex); - NodeProgress &np = nodeStatus[nodeIndex]; - np.children = children[nodeIndex]; - np.todo = children[nodeIndex]; - } - - BitVector DF_done(int(function->blockCount()), false); - - while (!worklist.empty()) { - MIBlock::Index node = worklist.back(); - - if (DF_done.at(node)) { - worklist.pop_back(); - continue; - } - - NodeProgress &np = nodeStatus[node]; - auto it = np.todo.begin(); - while (it != np.todo.end()) { - if (DF_done.at(*it)) { - it = np.todo.erase(it); - } else { - worklist.push_back(*it); - break; - } - } - - if (np.todo.empty()) { - MIBlockSet &miBlockSet = m_df[node]; - miBlockSet.init(function); - for (MIBlock *y : function->block(node)->outEdges()) { - if (domTree.immediateDominator(y->index()) != node) - miBlockSet.insert(y); - } - for (MIBlock::Index child : np.children) { - const MIBlockSet &ws = m_df[child]; - for (auto w : ws) { - const MIBlock::Index wIndex = w->index(); - if (node == wIndex || !domTree.dominates(node, w->index())) - miBlockSet.insert(w); - } - } - DF_done.setBit(node); - worklist.pop_back(); - } - } - - if (lcDomFrontier().isDebugEnabled()) - dump(domTree.function()); -} - -void DominatorFrontier::dump(MIFunction *function) -{ - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Dominator Frontiers:" << endl; - for (MIBlock *n : function->blocks()) { - qout << "\tDF[" << n->index() << "]: {"; - const MIBlockSet &SList = m_df[n->index()]; - for (MIBlockSet::const_iterator i = SList.begin(), ei = SList.end(); i != ei; ++i) { - if (i != SList.begin()) - qout << ", "; - qout << (*i)->index(); - } - qout << "}" << endl; - } - qCDebug(lcDomFrontier, "%s", buf.data().constData()); -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4domtree_p.h b/src/qml/jit/qv4domtree_p.h deleted file mode 100644 index 703e17ab61..0000000000 --- a/src/qml/jit/qv4domtree_p.h +++ /dev/null @@ -1,136 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4DOMTREE_P_H -#define QV4DOMTREE_P_H - -// -// 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. -// - -#include "qv4mi_p.h" -#include "qv4miblockset_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class DominatorTree -{ - Q_DISABLE_COPY_MOVE(DominatorTree) - -public: - DominatorTree(MIFunction *f); - ~DominatorTree() = default; - - void dumpImmediateDominators() const; - MIFunction *function() const - { return m_function; } - - void setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator); - - MIBlock::Index immediateDominator(MIBlock::Index blockIndex) const - { return m_idom[blockIndex]; } - - bool dominates(MIBlock::Index dominator, MIBlock::Index dominated) const; - - bool insideSameDominatorChain(MIBlock::Index one, MIBlock::Index other) const - { return one == other || dominates(one, other) || dominates(other, one); } - - std::vector calculateDFNodeIterOrder() const; - - std::vector calculateNodeDepths() const; - -private: // functions - int calculateNodeDepth(MIBlock::Index nodeIdx, std::vector &nodeDepths) const; - void link(MIBlock::Index p, MIBlock::Index n); - void calculateIDoms(); - void dfs(MIBlock::Index node); - MIBlock::Index ancestorWithLowestSemi(MIBlock::Index v, std::vector &worklist); - -private: // data - struct Data { - std::vector dfnum; // MIBlock index -> dfnum - std::vector vertex; - std::vector parent; // MIBlock index -> parent MIBlock index - std::vector ancestor; // MIBlock index -> ancestor MIBlock index - std::vector best; // MIBlock index -> best MIBlock index - std::vector semi; // MIBlock index -> semi dominator MIBlock index - std::vector samedom; // MIBlock index -> same dominator MIBlock index - unsigned size = 0; - }; - - MIFunction *m_function; - QScopedPointer m_data; - std::vector m_idom; // MIBlock index -> immediate dominator MIBlock index -}; - -class DominatorFrontier -{ -public: - DominatorFrontier(const DominatorTree &domTree) - { compute(domTree); } - - const MIBlockSet &operator[](MIBlock *n) const - { return m_df[n->index()]; } - -private: // functions - void compute(const DominatorTree &domTree); - void dump(MIFunction *function); - -private: // data - std::vector m_df; // MIBlock index -> dominator frontier -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4DOMTREE_P_H diff --git a/src/qml/jit/qv4graph.cpp b/src/qml/jit/qv4graph.cpp deleted file mode 100644 index 4025ceb993..0000000000 --- a/src/qml/jit/qv4graph.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4graph_p.h" -#include "qv4operation_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Graph *Graph::create(Function *function) -{ - auto storage = function->pool()->allocate(sizeof(Graph)); - auto g = new (storage) Graph(function); - g->m_undefinedNode = g->createNode(g->opBuilder()->get()); - g->m_emptyNode = g->createNode(g->opBuilder()->get()); - g->m_nullNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::nullValue())); - g->m_trueNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(true))); - g->m_falseNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(false))); - return g; -} - -Graph::MemoryPool *Graph::pool() const -{ - return m_function->pool(); -} - -Node *Graph::createNode(const Operation *op, Node *const operands[], size_t opCount, - bool incomplete) -{ - return Node::create(pool(), m_nextNodeId++, op, opCount, operands, incomplete); -} - -Node *Graph::createConstantBoolNode(bool value) -{ - return createNode(opBuilder()->getConstant(Primitive::fromBoolean(value))); -} - -Node *Graph::createConstantIntNode(int value) -{ - return createNode(opBuilder()->getConstant(Primitive::fromInt32(value))); -} - -Graph::Graph(Function *function) - : m_function(function) - , m_opBuilder(OperationBuilder::create(pool())) -{} - -Node *Graph::createConstantHeapNode(Heap::Base *heap) -{ - return createNode(opBuilder()->getConstant(Primitive::fromHeapObject(heap))); -} - -void Graph::addEndInput(Node *n) -{ - if (m_endNode) { - auto newEnd = m_opBuilder->getEnd(m_endNode->operation()->controlInputCount() + 1); - m_endNode->setOperation(newEnd); - m_endNode->addInput(m_function->pool(), n); - } -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graph_p.h b/src/qml/jit/qv4graph_p.h deleted file mode 100644 index 4706399c94..0000000000 --- a/src/qml/jit/qv4graph_p.h +++ /dev/null @@ -1,146 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4GRAPH_P_H -#define QV4GRAPH_P_H - -// -// 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. -// - -#include -#include -#include - -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class Function; -class Operation; -class OperationBuilder; - -class Graph final -{ - Q_DISABLE_COPY_MOVE(Graph) - -public: - using MemoryPool = QQmlJS::MemoryPool; - -public: - static Graph *create(Function *function); - ~Graph() = delete; - - MemoryPool *pool() const; - OperationBuilder *opBuilder() const - { return m_opBuilder; } - - Node *createNode(const Operation *op, Node * const operands[] = nullptr, size_t opCount = 0, - bool incomplete = false); - template - Node *createNode(Operation *op, Nodes*... nodes) { - std::array nodesArray {{ nodes... }}; - return createNode(op, nodesArray.data(), nodesArray.size()); - } - Node *createConstantBoolNode(bool value); - Node *createConstantIntNode(int value); - Node *createConstantHeapNode(Heap::Base *heap); - - Node *undefinedNode() const { return m_undefinedNode; } - Node *emptyNode() const { return m_emptyNode; } - Node *nullNode() const { return m_nullNode; } - Node *trueConstant() const { return m_trueNode; } - Node *falseConstant() const { return m_falseNode; } - - Node *startNode() const { return m_startNode; } - Node *engineNode() const { return m_engineNode; } - Node *functionNode() const { return m_functionNode; } - Node *cppFrameNode() const { return m_cppFrameNode; } - Node *endNode() const { return m_endNode; } - Node *initialFrameState() const { return m_initialFrameState; } - void setStartNode(Node *n) { m_startNode = n; } - void setEngineNode(Node *n) { m_engineNode = n; } - void setFunctionNode(Node *n) { m_functionNode = n; } - void setCppFrameNode(Node *n) { m_cppFrameNode = n; } - void setEndNode(Node *n) { m_endNode = n; } - void setInitialFrameState(Node *n) { m_initialFrameState = n; } - - unsigned nodeCount() const - { return unsigned(m_nextNodeId); } - - void addEndInput(Node *n); - -private: // types and methods - Graph(Function *function); - -private: // fields - Function *m_function; - OperationBuilder *m_opBuilder; - Node::Id m_nextNodeId = 0; - Node *m_undefinedNode = nullptr; - Node *m_emptyNode = nullptr; - Node *m_nullNode = nullptr; - Node *m_trueNode = nullptr; - Node *m_falseNode = nullptr; - Node *m_startNode = nullptr; - Node *m_engineNode = nullptr; - Node *m_functionNode = nullptr; - Node *m_cppFrameNode = nullptr; - Node *m_endNode = nullptr; - Node *m_initialFrameState = nullptr; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4GRAPH_P_H diff --git a/src/qml/jit/qv4graphbuilder.cpp b/src/qml/jit/qv4graphbuilder.cpp deleted file mode 100644 index 2c073701ee..0000000000 --- a/src/qml/jit/qv4graphbuilder.cpp +++ /dev/null @@ -1,1683 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4graphbuilder_p.h" -#include "qv4function_p.h" -#include "qv4lookup_p.h" -#include "qv4stackframe_p.h" -#include "qv4operation_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcIRGraphBuilder, "qt.v4.ir.graphbuilder") - -using MemoryPool = QQmlJS::MemoryPool; - -namespace { -template -char (&ArraySizeHelper(T (&array)[N]))[N]; -template -char (&ArraySizeHelper(const T (&array)[N]))[N]; - -template -inline size_t arraySize(const Array &array) -{ - Q_UNUSED(array); // for MSVC - return sizeof(ArraySizeHelper(array)); -} -} // anonymous namespace - -class GraphBuilder::InterpreterEnvironment -{ -public: - struct FrameState: public QQmlJS::FixedPoolArray - { - FrameState(MemoryPool *pool, int totalSlotCount) - : FixedPoolArray(pool, totalSlotCount) - {} - - Node *&unwindHandlerOffset() - { return at(size() - 1); } - - static FrameState *create(MemoryPool *pool, int jsSlotCount) - { - auto totalSlotCount = jsSlotCount; - auto fs = pool->New(pool, totalSlotCount); - return fs; - } - - static FrameState *clone(MemoryPool *pool, FrameState *other) - { - FrameState *fs = create(pool, other->size()); - - for (int i = 0, ei = other->size(); i != ei; ++i) - fs->at(i) = other->at(i); - - return fs; - } - }; - -public: - InterpreterEnvironment(GraphBuilder *graphBuilder, Node *controlDependency) - : m_graphBuilder(graphBuilder) - , m_effectDependency(controlDependency) - , m_controlDependency(controlDependency) - , m_currentFrame(nullptr) - {} - - void createEnvironment() - { - Function *f = function(); - QV4::Function *v4Function = f->v4Function(); - const size_t nRegisters = v4Function->compiledFunction->nRegisters; - - // 1 extra slot for the unwindHandlerOffset - m_currentFrame = FrameState::create(graph()->pool(), int(nRegisters + 1)); - } - - void setupStartEnvironment() - { - Function *f = function(); - QV4::Function *v4Function = f->v4Function(); - const size_t nFormals = v4Function->compiledFunction->nFormals; - const size_t nRegisters = v4Function->compiledFunction->nRegisters; - - createEnvironment(); - - Node *startNode = graph()->startNode(); - auto opB = opBuilder(); - auto create = [&](int index, const char *name) { - m_currentFrame->at(index) = graph()->createNode( - opB->getParam(index, f->addString(QLatin1String(name))), startNode); - }; - create(0, "%function"); - create(1, "%context"); - create(2, "%acc"); - create(3, "%this"); - create(4, "%newTarget"); - create(5, "%argc"); - const quint32_le *formalNameIdx = v4Function->compiledFunction->formalsTable(); - for (size_t i = 0; i < nFormals; ++i, ++formalNameIdx) { - const int slot = int(CallData::HeaderSize() + i); - Q_ASSERT(*formalNameIdx <= quint32(std::numeric_limits::max())); - auto op = opB->getParam( - slot, - f->addString(v4Function->compilationUnit->stringAt(int(*formalNameIdx)))); - Node *argNode = graph()->createNode(op, startNode); - m_currentFrame->at(slot) = argNode; - } - Node *undefinedNode = graph()->undefinedNode(); - Node *emptyNode = graph()->emptyNode(); - const auto firstDeadZoneRegister - = v4Function->compiledFunction->firstTemporalDeadZoneRegister; - const auto registerDeadZoneSize - = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone; - for (size_t i = CallData::HeaderSize() + nFormals; i < nRegisters; ++i) { - const bool isDead = i >= firstDeadZoneRegister - && i < size_t(firstDeadZoneRegister + registerDeadZoneSize); - m_currentFrame->at(int(i)) = isDead ? emptyNode : undefinedNode; - } - setUnwindHandlerOffset(0); - } - - Function *function() const { return m_graphBuilder->function(); } - Graph *graph() const { return function()->graph(); } - OperationBuilder *opBuilder() const { return graph()->opBuilder(); } - GraphBuilder *graphBuilder() const { return m_graphBuilder; } - - Node *bindAcc(Node *node) - { - bindNodeToSlot(node, CallData::Accumulator); - return node; - } - - Node *accumulator() const - { return slot(CallData::Accumulator); } - - Node *bindNodeToSlot(Node *node, int slot) - { - m_currentFrame->at(size_t(slot)) = node; - return node; - } - - Node *slot(int slot) const - { return m_currentFrame->at(slot); } - - int slotCount() const - { return m_currentFrame->size(); } - - Node *effectDependency() const - { return m_effectDependency; } - - void setEffectDependency(Node *newNode) - { m_effectDependency = newNode; } - - Node *controlDependency() const - { return m_controlDependency; } - - void setControlDependency(Node *newNode) - { m_controlDependency = newNode; } - - Node *createFrameState() - { - return graph()->createNode(graphBuilder()->opBuilder()->getFrameState(slotCount()), - m_currentFrame->begin(), slotCount()); - } - - Node *merge(InterpreterEnvironment *other); - - InterpreterEnvironment *copy() const - { - auto *newEnv = graph()->pool()->New(graphBuilder(), - controlDependency()); - newEnv->setEffectDependency(effectDependency()); - newEnv->m_currentFrame = FrameState::clone(graph()->pool(), m_currentFrame); - return newEnv; - } - - int unwindHandlerOffset() const - { - auto uhOp = m_currentFrame->unwindHandlerOffset()->operation(); - Q_ASSERT(uhOp->kind() == Meta::Constant); - return ConstantPayload::get(*uhOp)->value().int_32(); - } - - void setUnwindHandlerOffset(int newOffset) - { m_currentFrame->unwindHandlerOffset() = graphBuilder()->createConstant(newOffset); } - - FrameState *frameState() const - { return m_currentFrame; } - -private: - GraphBuilder *m_graphBuilder; - Node *m_effectDependency; - Node *m_controlDependency; - FrameState *m_currentFrame; -}; - -namespace { -class InterpreterSubEnvironment final -{ - Q_DISABLE_COPY_MOVE(InterpreterSubEnvironment) - -public: - explicit InterpreterSubEnvironment(GraphBuilder *builder) - : m_builder(builder) - , m_parent(builder->env()->copy()) - {} - - ~InterpreterSubEnvironment() - { m_builder->setEnv(m_parent); } - -private: - GraphBuilder *m_builder; - GraphBuilder::InterpreterEnvironment *m_parent; -}; -} // anonymous namespace - -Node *GraphBuilder::InterpreterEnvironment::merge(InterpreterEnvironment *other) -{ - Q_ASSERT(m_currentFrame->size() == other->m_currentFrame->size()); - - auto gb = graphBuilder(); - Node *mergedControl = gb->mergeControl(controlDependency(), other->controlDependency()); - setControlDependency(mergedControl); - setEffectDependency(gb->mergeEffect(effectDependency(), other->effectDependency(), mergedControl)); - - // insert/update phi nodes, but not for the unwind handler: - for (int i = 0, ei = m_currentFrame->size() - 1; i != ei; ++i) { - //### use lifeness info to trim this! - m_currentFrame->at(i) = gb->mergeValue(m_currentFrame->at(i), - other->m_currentFrame->at(i), - mergedControl); - } - Q_ASSERT(unwindHandlerOffset() >= 0); // specifically: don't crash - return mergedControl; -} - -void GraphBuilder::buildGraph(IR::Function *function) -{ - const char *code = function->v4Function()->codeData; - uint len = function->v4Function()->compiledFunction->codeSize; - - GraphBuilder builder(function); - builder.startGraph(); - - InterpreterEnvironment initial(&builder, function->graph()->startNode()); - initial.setupStartEnvironment(); - builder.setEnv(&initial); - builder.graph()->setInitialFrameState(initial.createFrameState()); - builder.decode(code, len); - builder.endGraph(); -}; - -GraphBuilder::GraphBuilder(IR::Function *function) - : m_func(function) - , m_graph(function->graph()) - , m_currentEnv(nullptr) -{ - for (unsigned i = 0, ei = m_func->v4Function()->compiledFunction->nLabelInfos; i != ei; ++i) { - unsigned label = m_func->v4Function()->compiledFunction->labelInfoTable()[i]; - m_labelInfos.emplace_back(label); - if (lcIRGraphBuilder().isDebugEnabled()) { - const LabelInfo &li = m_labelInfos.back(); - qCDebug(lcIRGraphBuilder) << "Loop start at" << li.labelOffset; - } - } -} - -void GraphBuilder::startGraph() -{ - size_t nValuesOut = 1 + CallData::HeaderSize() - + m_func->v4Function()->compiledFunction->nFormals; - Node *start = m_graph->createNode(opBuilder()->getStart(uint16_t(nValuesOut)), nullptr, 0); - m_func->nodeInfo(start)->setBytecodeOffsets(0, 0); - m_graph->setStartNode(start); - m_graph->setEngineNode(m_graph->createNode(opBuilder()->get(), &start, 1)); - auto frame = m_graph->createNode(opBuilder()->get(), &start, 1); - m_graph->setCppFrameNode(frame); - m_graph->setFunctionNode(m_graph->createNode(opBuilder()->get(), - &frame, 1)); -} - -void GraphBuilder::endGraph() -{ - const auto inputCount = uint16_t(m_exitControls.size()); - Node **inputs = &m_exitControls.front(); - Q_ASSERT(m_graph->endNode() == nullptr); - m_graph->setEndNode(m_graph->createNode(opBuilder()->getEnd(inputCount), inputs, inputCount)); -} - -Node *GraphBuilder::bindAcc(Node *n) -{ - return env()->bindAcc(n); -} - -/* IMPORTANT!!! - * - * This might change the success environment, so don't call: - * env()->bindAcc(createNode(...)) - * because the binding should only happen on success, but the call to env() will get the - * environment from *before* the new success environment was created. Instead, do: - * bindAcc(createNode(....)) - */ -Node *GraphBuilder::createAndLinkNode(Operation *op, Node *operands[], size_t opCount, - bool incomplete) -{ - Q_ASSERT(op->effectInputCount() < 2); - Q_ASSERT(op->controlInputCount() < 2); - - QVarLengthArray inputs(static_cast(opCount)); - std::copy_n(operands, opCount, inputs.data()); - - if (op->effectInputCount() == 1) - inputs.append(env()->effectDependency()); - if (op->controlInputCount() == 1) - inputs.append(env()->controlDependency()); - if (op->hasFrameStateInput()) - inputs.append(env()->createFrameState()); - - Node *node = m_graph->createNode(op, inputs.data(), inputs.size(), incomplete); - - if (op->needsBytecodeOffsets()) { - m_func->nodeInfo(node)->setBytecodeOffsets(currentInstructionOffset(), - nextInstructionOffset()); - } - - if (op->effectOutputCount() > 0) - env()->setEffectDependency(node); - if (op->controlOutputCount() > 0) - env()->setControlDependency(node); - - if (op->canThrow() && env()->unwindHandlerOffset()) { - InterpreterSubEnvironment successEnv(this); - Node *control = env()->controlDependency(); - control = m_graph->createNode(opBuilder()->get(), &control, 1); - env()->setControlDependency(control); - auto unwindHandlerOffset = env()->unwindHandlerOffset(); - mergeIntoSuccessor(unwindHandlerOffset); - } - - return node; -} - -Node *GraphBuilder::createNode(Operation *op, bool incomplete) -{ - return createAndLinkNode(op, nullptr, 0, incomplete); -} - -Node *GraphBuilder::createNode(Operation *op, Node *n1) -{ - Node *buf[] = { n1 }; - return createAndLinkNode(op, buf, arraySize(buf)); -} - -Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2) -{ - Node *buf[] = { n1, n2 }; - return createAndLinkNode(op, buf, arraySize(buf)); -} - -Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3) -{ - Node *buf[] = { n1, n2, n3 }; - return createAndLinkNode(op, buf, arraySize(buf)); -} - -Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4) -{ - Node *buf[] = { n1, n2, n3, n4 }; - return createAndLinkNode(op, buf, arraySize(buf)); -} - -Node *GraphBuilder::createRegion(unsigned nControlInputs) -{ - return createNode(opBuilder()->getRegion(nControlInputs), true); -} - -Node *GraphBuilder::createIfTrue() -{ - return createNode(opBuilder()->get()); -} - -Node *GraphBuilder::createIfFalse() -{ - return createNode(opBuilder()->get()); -} - -Node *GraphBuilder::createConstant(int v) -{ - return m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(v))); -} - -Node *GraphBuilder::createPhi(unsigned nInputs, Node *input, Node *control) -{ - auto phiOp = opBuilder()->getPhi(nInputs); - QVarLengthArray buffer(int(nInputs + 1)); - std::fill_n(buffer.data(), nInputs, input); - buffer[int(nInputs)] = control; - return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); -} - -Node *GraphBuilder::createEffectPhi(unsigned nInputs, Node *input, Node *control) -{ - auto phiOp = opBuilder()->getEffectPhi(nInputs); - QVarLengthArray buffer(int(nInputs + 1)); - std::fill_n(buffer.data(), nInputs, input); - buffer[int(nInputs)] = control; - return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); -} - -Node *GraphBuilder::createHandleUnwind(int offset) -{ - return createNode(opBuilder()->getHandleUnwind(offset)); -} - -Node *GraphBuilder::mergeControl(Node *c1, Node *c2) -{ - if (c1->operation()->kind() == Meta::Region) { - const unsigned nInputs = c1->operation()->controlInputCount() + 1; - c1->addInput(m_graph->pool(), c2); - c1->setOperation(opBuilder()->getRegion(nInputs)); - return c1; - } - auto op = opBuilder()->getRegion(2); - Node *inputs[] = { c1, c2 }; - return m_graph->createNode(op, inputs, 2); -} - -Node *GraphBuilder::mergeEffect(Node *e1, Node *e2, Node *control) -{ - const unsigned nInputs = control->operation()->controlInputCount(); - if (e1->operation()->kind() == Meta::EffectPhi && e1->controlInput() == control) { - e1->insertInput(m_graph->pool(), nInputs - 1, e2); - e1->setOperation(opBuilder()->getEffectPhi(nInputs)); - return e1; - } - - if (e1 != e2) { - Node *phi = createEffectPhi(nInputs, e1, control); - phi->replaceInput(nInputs - 1, e2); - return phi; - } - - return e1; -} - -Node *GraphBuilder::mergeValue(Node *v1, Node *v2, Node *control) -{ - const unsigned nInputs = control->operation()->controlInputCount(); - if (v1->operation()->kind() == Meta::Phi && v1->controlInput() == control) { - v1->insertInput(m_graph->pool(), nInputs - 1, v2); - v1->setOperation(opBuilder()->getPhi(nInputs)); - return v1; - } - - if (v1 != v2) { - Node *phi = createPhi(nInputs, v1, control); - phi->replaceInput(nInputs - 1, v2); - return phi; - } - - return v1; -} - -Node *GraphBuilder::createToBoolean(Node *input) -{ - return createNode(opBuilder()->get(), input); -} - -void GraphBuilder::populate(VarArgNodes &args, int argc, int argv) -{ - for (int i = 0; i < argc; ++i) - args.append(env()->slot(argv + i)); - Q_ASSERT(argc >= 0 && argc <= std::numeric_limits::max()); -} - -void GraphBuilder::queueFunctionExit(Node *exitNode) -{ - m_exitControls.push_back(exitNode); - setEnv(nullptr); -} - -Node *GraphBuilder::mergeIntoSuccessor(int offset) -{ - InterpreterEnvironment *&successorEnvironment = m_envForOffset[offset]; - - Node *region = nullptr; - if (successorEnvironment == nullptr) { - region = createRegion(1); - successorEnvironment = env(); - } else { - // Merge any values which are live coming into the successor. - region = successorEnvironment->merge(env()); - } - setEnv(nullptr); - return region; -} - -const GraphBuilder::LabelInfo *GraphBuilder::labelInfoAt(unsigned offset) const -{ - for (const LabelInfo &li : m_labelInfos) { - if (li.labelOffset == offset) - return &li; - } - return nullptr; -} - -const GraphBuilder::LabelInfo *GraphBuilder::isLoopStart(unsigned offset) const -{ - if (auto li = labelInfoAt(offset)) { - //### in the future, check if this is a loop start, or some other label - return li; - } - - return nullptr; -} - -void GraphBuilder::handleLoopStart(const LabelInfo &labelInfo) -{ - Q_ASSERT(env() != nullptr); - - // We unconditionally insert a region node with phi nodes here. Now there might already be - // such a node, (e.g. the region after an if-then-else), but for simplicity we ignore that. - // A subsequent pass will fold/remove chains of Region nodes. - //### FIXME: add a DCE pass - - const auto offset = int(labelInfo.labelOffset); - Node *control = createRegion(1); - env()->setControlDependency(control); - Node *effect = createEffectPhi(1, env()->effectDependency(), control); - env()->setEffectDependency(effect); - - // insert/update phi nodes, but not for the unwind handler: - for (int i = 0, ei = env()->slotCount() - 1; i != ei; ++i) { - //### use lifeness info to trim this further! - if (i == CallData::Accumulator) - continue; // should never be alive on loop entry - env()->bindNodeToSlot(createPhi(1, env()->slot(i), control), i); - } - - m_envForOffset.insert(offset, env()->copy()); -} - -void GraphBuilder::startUnwinding() -{ - if (int target = env()->unwindHandlerOffset()) { - mergeIntoSuccessor(target); - } else { - bindAcc(graph()->undefinedNode()); - generate_Ret(); - } -} - -void GraphBuilder::generate_Ret() -{ - Node* control = createNode(opBuilder()->get(), env()->accumulator()); - queueFunctionExit(control); -} - -void GraphBuilder::generate_Debug() { Q_UNREACHABLE(); } - -void GraphBuilder::generate_LoadConst(int index) -{ - auto func = function()->v4Function(); - Value v = func->compilationUnit->constants[index]; - bindAcc(createNode(opBuilder()->getConstant(v))); -} - -void GraphBuilder::generate_LoadZero() -{ - bindAcc(createConstant(0)); -} - -void GraphBuilder::generate_LoadTrue() -{ - bindAcc(m_graph->trueConstant()); -} - -void GraphBuilder::generate_LoadFalse() -{ - bindAcc(m_graph->falseConstant()); -} - -void GraphBuilder::generate_LoadNull() -{ - bindAcc(m_graph->nullNode()); -} - -void GraphBuilder::generate_LoadUndefined() -{ - bindAcc(m_graph->undefinedNode()); -} - -void GraphBuilder::generate_LoadInt(int value) -{ - bindAcc(m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(value)))); -} - -void GraphBuilder::generate_MoveConst(int constIndex, int destTemp) -{ - auto func = function()->v4Function(); - Value v = func->compilationUnit->constants[constIndex]; - env()->bindNodeToSlot(createNode(opBuilder()->getConstant(v)), destTemp); -} - -void GraphBuilder::generate_LoadReg(int reg) -{ - bindAcc(env()->slot(reg)); -} - -void GraphBuilder::generate_StoreReg(int reg) -{ - Node *n = env()->accumulator(); - if (reg == CallData::This) - n = createNode(opBuilder()->get(), n); - env()->bindNodeToSlot(n, reg); -} - -void GraphBuilder::generate_MoveReg(int srcReg, int destReg) -{ - env()->bindNodeToSlot(env()->slot(srcReg), destReg); -} - -void GraphBuilder::generate_LoadImport(int index) -{ - auto func = function()->v4Function(); - Value v = *func->compilationUnit->imports[index]; - bindAcc(createNode(opBuilder()->getConstant(v))); -} - -void GraphBuilder::generate_LoadLocal(int index, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(0), - createConstant(index))); -} - -void GraphBuilder::generate_StoreLocal(int index) -{ - createNode(opBuilder()->get(), - createConstant(0), - createConstant(index), - env()->accumulator()); -} - -void GraphBuilder::generate_LoadScopedLocal(int scope, int index, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(scope), - createConstant(index))); -} - -void GraphBuilder::generate_StoreScopedLocal(int scope, int index) -{ - createNode(opBuilder()->get(), - createConstant(scope), - createConstant(index), - env()->accumulator()); -} - -void GraphBuilder::generate_LoadRuntimeString(int stringId) -{ - auto func = function()->v4Function(); - Value v = Value::fromHeapObject(func->compilationUnit->runtimeStrings[stringId]); - bindAcc(createNode(opBuilder()->getConstant(v))); -} - -void GraphBuilder::generate_MoveRegExp(int regExpId, int destReg) -{ - env()->bindNodeToSlot(createNode(opBuilder()->get(), - createConstant(regExpId)), destReg); -} - -void GraphBuilder::generate_LoadClosure(int value) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(value))); -} - -void GraphBuilder::generate_LoadName(int name, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(name))); -} - -void GraphBuilder::generate_LoadGlobalLookup(int index, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), createConstant(index))); -} - -void GraphBuilder::generate_StoreNameSloppy(int name) -{ - createNode(opBuilder()->get(), createConstant(name), env()->accumulator()); -} - -void GraphBuilder::generate_StoreNameStrict(int name) -{ - createNode(opBuilder()->get(), createConstant(name), env()->accumulator()); -} - -void GraphBuilder::generate_LoadElement(int base, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(base), - env()->accumulator())); -} - -void GraphBuilder::generate_StoreElement(int base, int index, int /*traceSlot*/) -{ - createNode(opBuilder()->get(), - env()->slot(base), - env()->slot(index), - env()->accumulator()); -} - -void GraphBuilder::generate_LoadProperty(int name, int /*traceSlot*/) -{ - Node *n = createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(name)); - bindAcc(n); -} - -void GraphBuilder::generate_GetLookup(int index, int /*traceSlot*/) -{ - Node *n = createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(index)); - bindAcc(n); -} - -void GraphBuilder::generate_StoreProperty(int name, int base) -{ - createNode(opBuilder()->get(), - env()->slot(base), - createConstant(name), - env()->accumulator()); -} - -void GraphBuilder::generate_SetLookup(int index, int base) -{ - - function()->v4Function()->isStrict() - ? createNode(opBuilder()->get(), env()->slot(base), - createConstant(index), env()->accumulator()) - : createNode(opBuilder()->get(), env()->slot(base), - createConstant(index), env()->accumulator()); -} - -void GraphBuilder::generate_LoadSuperProperty(int property) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(property))); -} - -void GraphBuilder::generate_StoreSuperProperty(int property) -{ - createNode(opBuilder()->get(), - createConstant(property), - env()->accumulator()); -} - -void GraphBuilder::generate_LoadQmlContextPropertyLookup(int propertyIndex, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(propertyIndex))); -} - -void GraphBuilder::generate_Yield() { Q_UNREACHABLE(); } -void GraphBuilder::generate_YieldStar() { Q_UNREACHABLE(); } -void GraphBuilder::generate_Resume(int /*offset*/) { Q_UNREACHABLE(); } - -void GraphBuilder::finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv) -{ - populate(args, argc, argv); - bindAcc(createAndLinkNode(opBuilder()->getJSVarArgsCall(kind, uint16_t(args.size())), - args.data(), size_t(args.size()))); -} - -void GraphBuilder::generate_CallValue(int name, int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(name)); - finalizeCall(Meta::JSCallValue, args, argc, argv); -} - -void GraphBuilder::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, - int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(name)); - args.append(env()->slot(thisObject)); - finalizeCall(Meta::JSCallWithReceiver, args, argc, argv); -} - -void GraphBuilder::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(base)); - args.append(createConstant(name)); - finalizeCall(Meta::JSCallProperty, args, argc, argv); -} - -void GraphBuilder::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, - int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(base)); - args.append(createConstant(lookupIndex)); - finalizeCall(Meta::JSCallLookup, args, argc, argv); -} - -void GraphBuilder::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(base)); - args.append(env()->slot(index)); - finalizeCall(Meta::JSCallElement, args, argc, argv); -} - -void GraphBuilder::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - args.append(createConstant(name)); - finalizeCall(Meta::JSCallName, args, argc, argv); -} - -void GraphBuilder::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - finalizeCall(Meta::JSCallPossiblyDirectEval, args, argc, argv); -} - -void GraphBuilder::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) -{ - VarArgNodes args; - args.append(createConstant(index)); - finalizeCall(Meta::JSCallGlobalLookup, args, argc, argv); -} - -void GraphBuilder::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, - int /*traceSlot*/) -{ - VarArgNodes args; - args.append(createConstant(index)); - finalizeCall(Meta::QMLCallQmlContextPropertyLookup, args, argc, argv); -} - -void GraphBuilder::generate_SetUnwindHandler(int offset) -{ - m_currentUnwindHandlerOffset = offset ? absoluteOffset(offset) : 0; - env()->setUnwindHandlerOffset(m_currentUnwindHandlerOffset); -} - -void GraphBuilder::generate_UnwindDispatch() -{ - auto e = createNode(opBuilder()->get(), graph()->engineNode()); - createNode(opBuilder()->get(), e); - { - InterpreterSubEnvironment subEnvironment(this); - createIfTrue(); - startUnwinding(); - } - - createIfFalse(); - - const auto unwindHandlerOffset = env()->unwindHandlerOffset(); - const auto fallthroughSuccessor = nextInstructionOffset(); - auto nContinuations = m_func->unwindLabelOffsets().size() + 1; - if (unwindHandlerOffset) - ++nContinuations; - Q_ASSERT(nContinuations <= std::numeric_limits::max()); - createNode(opBuilder()->getUnwindDispatch(unsigned(nContinuations), unwindHandlerOffset, - fallthroughSuccessor)); - - { - InterpreterSubEnvironment fallthroughEnv(this); - mergeIntoSuccessor(fallthroughSuccessor); - } - - if (unwindHandlerOffset) { - InterpreterSubEnvironment unwindHandlerEnv(this); - createHandleUnwind(unwindHandlerOffset); - mergeIntoSuccessor(unwindHandlerOffset); - } - - for (int unwindLabelOffset : m_func->unwindLabelOffsets()) { - if (unwindLabelOffset <= currentInstructionOffset()) - continue; - InterpreterSubEnvironment unwindLabelEnv(this); - createHandleUnwind(unwindLabelOffset); - mergeIntoSuccessor(unwindLabelOffset); - } - - setEnv(nullptr); -} - -void GraphBuilder::generate_UnwindToLabel(int level, int offset) -{ - //### For de-optimization, the relative offset probably also needs to be stored - int unwinder = absoluteOffset(offset); - createNode(opBuilder()->get(), - createConstant(level), - createConstant(unwinder)); - m_func->addUnwindLabelOffset(unwinder); - startUnwinding(); -} - -void GraphBuilder::generate_DeadTemporalZoneCheck(int name) -{ - Node *check = createNode(opBuilder()->get(), env()->accumulator()); - createNode(opBuilder()->get(), check); - - { //### it's probably better to handle this by de-optimizing - InterpreterSubEnvironment subEnvironment(this); - createIfTrue(); - createNode(opBuilder()->get(), - createConstant(name)); - startUnwinding(); - } - - createIfFalse(); -} - -void GraphBuilder::generate_ThrowException() -{ - createNode(opBuilder()->get(), env()->accumulator()); - startUnwinding(); -} - -void GraphBuilder::generate_GetException() -{ - bindAcc(createNode(opBuilder()->get())); -} - -void GraphBuilder::generate_SetException() -{ - createNode(opBuilder()->get(), - env()->accumulator()); -} - -void GraphBuilder::generate_CreateCallContext() -{ - createNode(opBuilder()->get()); -} - -void GraphBuilder::generate_PushCatchContext(int index, int name) -{ - createNode(opBuilder()->get(), - createConstant(index), - createConstant(name)); -} - -void GraphBuilder::generate_PushWithContext() -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_PushBlockContext(int index) -{ - createNode(opBuilder()->get(), - createConstant(index)); -} - -void GraphBuilder::generate_CloneBlockContext() -{ - createNode(opBuilder()->get()); -} - -void GraphBuilder::generate_PushScriptContext(int index) -{ - createNode(opBuilder()->get(), createConstant(index)); -} - -void GraphBuilder::generate_PopScriptContext() -{ - createNode(opBuilder()->get()); -} - -void GraphBuilder::generate_PopContext() -{ - createNode(opBuilder()->get()); -} - -void GraphBuilder::generate_GetIterator(int iterator) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(iterator))); -} - -void GraphBuilder::generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, - int resultSlot) -{ - // See generate_IteratorNext for why this method exists. - - // check that no-one messed around with the operation and made it throwing - Q_ASSERT(iterationNode->operation()->controlOutputCount() == 1); - - // check that it's in the effect chain, because HasException relies on that - Q_ASSERT(iterationNode->operation()->effectOutputCount() == 1); - - env()->bindNodeToSlot(createNode(opBuilder()->get(), - iterationNode, - createConstant(1), - graph()->undefinedNode()), - resultSlot); - // Note: the following will NOT set the accumulator, because it contains the return value of - // the runtime call! - Node *ehCheck = createNode(opBuilder()->get(), graph()->engineNode()); - createNode(opBuilder()->get(), ehCheck); - - { // EH path: - InterpreterSubEnvironment subEnvironment(this); - createIfTrue(); - if (auto ehOffset = env()->unwindHandlerOffset()) { - // Ok, there is an exception handler, so go there: - mergeIntoSuccessor(ehOffset); - } else { - // No Exception Handler, so keep the exception set in the engine, and leave the function - // a.s.a.p.: - bindAcc(graph()->undefinedNode()); - generate_Ret(); - } - } - - // Normal control flow: - createIfFalse(); -} - -void GraphBuilder::generate_IteratorNext(int value, int done) -{ - // The way we model exceptions in the graph is that a runtime function will either succeed and - // return a value, or it fails and throws an exception. If it throws, the return value is not - // used because the method did not complete normally, and therefore it might be tainted. - // - // This is a problem for (and only for) IteratorNext and IteratorNextForYieldStart. - // - // What would happen in the normal case, is that the return value (done) is not used/assigned - // when IteratorNext throws, because the exception handling path is chosen. However, the - // interpreter *does* assign it, and will only check for an exception *after* that assignment. - // - // So, in order to work around this odd-duck behavior, we mark the operation as NoThrow, - // override the runtime method and flag it to not throw, and insert extra exception check nodes - // after the SelectOutput that follows the IteratorNext(ForYieldStar). - // - // Also note that the IteratorNext and IteratorNextForYieldStar are the only operations that - // have an inout parameter, and thus require a SelectOutput node to retrieve this. - - Node *n = createNode(opBuilder()->get(), - env()->accumulator(), - graph()->undefinedNode()); - bindAcc(n); - env()->bindNodeToSlot(n, done); - generate_IteratorNextAndFriends_TrailingStuff(n, value); -} - -void GraphBuilder::generate_IteratorNextForYieldStar(int iterator, int object) -{ - // Please, PLEASE read the comment in generate_IteratorNext. - Node *n = createNode(opBuilder()->get(), - env()->accumulator(), - env()->slot(iterator), - graph()->undefinedNode()); - // Note: the following is a tiny bit different from what generate_IteratorNext does. - bindAcc(n); - generate_IteratorNextAndFriends_TrailingStuff(n, object); -} - -void GraphBuilder::generate_IteratorClose(int done) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - env()->slot(done))); -} - -void GraphBuilder::generate_DestructureRestElement() -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_DeleteProperty(int base, int index) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(base), - env()->slot(index))); -} - -void GraphBuilder::generate_DeleteName(int name) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(name))); -} - -void GraphBuilder::generate_TypeofName(int name) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(name))); -} - -void GraphBuilder::generate_TypeofValue() -{ - bindAcc(createNode(opBuilder()->get(), env()->accumulator())); -} - -void GraphBuilder::generate_DeclareVar(int varName, int isDeletable) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(isDeletable), - createConstant(varName))); -} - -void GraphBuilder::generate_DefineArray(int argc, int argv) -{ - VarArgNodes args; - finalizeCall(Meta::JSDefineArray, args, argc, argv); -} - -void GraphBuilder::generate_DefineObjectLiteral(int internalClassId, int argc, int argv) -{ - VarArgNodes args; - args.append(createConstant(internalClassId)); - finalizeCall(Meta::JSDefineObjectLiteral, args, argc, argv); -} - -void GraphBuilder::generate_CreateClass(int classIndex, int heritage, int computedNames) -{ - int argc = 0; - int argv = computedNames; - - const QV4::CompiledData::Class *cls = function()->v4Function()->compilationUnit->unitData() - ->classAt(classIndex); - const CompiledData::Method *methods = cls->methodTable(); - for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { - if (methods[i].name == std::numeric_limits::max()) - ++argc; - } - - VarArgNodes args; - args.append(createConstant(classIndex)); - args.append(env()->slot(heritage)); - finalizeCall(Meta::JSCreateClass, args, argc, argv); -} - -void GraphBuilder::generate_CreateMappedArgumentsObject() -{ - bindAcc(createNode(opBuilder()->get())); -} - -void GraphBuilder::generate_CreateUnmappedArgumentsObject() -{ - bindAcc(createNode(opBuilder()->get())); -} - -void GraphBuilder::generate_CreateRestParameter(int argIndex) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(argIndex))); -} - -void GraphBuilder::generate_ConvertThisToObject() -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(CallData::This)); - env()->bindNodeToSlot(control, CallData::This); -} - -void GraphBuilder::generate_LoadSuperConstructor() -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(CallData::Function))); -} - -void GraphBuilder::generate_ToObject() -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_CallWithSpread(int func, int thisObject, int argc, int argv, - int /*traceSlot*/) -{ - VarArgNodes args; - args.append(env()->slot(func)); - args.append(env()->slot(thisObject)); - finalizeCall(Meta::JSCallWithSpread, args, argc, argv); -} - -void GraphBuilder::generate_TailCall(int func, int thisObject, int argc, int argv) -{ - VarArgNodes args; - args.append(env()->slot(func)); - args.append(env()->slot(thisObject)); - populate(args, argc, argv); - Node *n = createAndLinkNode(opBuilder()->getJSTailCall(uint16_t(args.size())), args.data(), - size_t(args.size())); - queueFunctionExit(n); -} - -void GraphBuilder::generate_Construct(int func, int argc, int argv) -{ - VarArgNodes args; - args.append(env()->slot(func)); - args.append(env()->accumulator()); - finalizeCall(Meta::JSConstruct, args, argc, argv); -} - -void GraphBuilder::generate_ConstructWithSpread(int func, int argc, int argv) -{ - VarArgNodes args; - args.append(env()->slot(func)); - args.append(env()->accumulator()); - finalizeCall(Meta::JSConstructWithSpread, args, argc, argv); -} - -void GraphBuilder::generate_Jump(int offset) -{ - auto jumpTarget = absoluteOffset(offset); - mergeIntoSuccessor(jumpTarget); -} - -void GraphBuilder::generate_JumpTrue(int /*traceSlot*/, int offset) -{ - createNode(opBuilder()->get(), createToBoolean(env()->accumulator())); - - { - InterpreterSubEnvironment subEnvironment(this); - auto jumpTarget = absoluteOffset(offset); - createIfTrue(); - mergeIntoSuccessor(jumpTarget); - } - - createIfFalse(); -} - -void GraphBuilder::generate_JumpFalse(int traceSlot, int offset) -{ - generate_JumpFalse(env()->accumulator(), traceSlot, offset); -} - -void GraphBuilder::generate_JumpFalse(Node *condition, int /*traceSlot*/, int offset) -{ - createNode(opBuilder()->get(), createToBoolean(condition)); - - { - InterpreterSubEnvironment subEnvironment(this); - auto jumpTarget = absoluteOffset(offset); - createIfFalse(); - mergeIntoSuccessor(jumpTarget); - } - - createIfTrue(); -} - -void GraphBuilder::generate_JumpNoException(int offset) -{ - auto e = createNode(opBuilder()->get(), graph()->engineNode()); - createNode(opBuilder()->get(), e); - - { - InterpreterSubEnvironment subEnvironment(this); - auto jumpTarget = absoluteOffset(offset); - createIfFalse(); - mergeIntoSuccessor(jumpTarget); - } - - createIfTrue(); -} - -void GraphBuilder::generate_JumpNotUndefined(int offset) -{ - Node *condition = createNode(opBuilder()->get(), - env()->accumulator(), - graph()->undefinedNode()); - generate_JumpFalse(condition, NoTraceSlot, offset); -} - -void GraphBuilder::generate_CmpEqNull() -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - graph()->nullNode())); -} - -void GraphBuilder::generate_CmpNeNull() -{ - generate_CmpEqNull(); - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_CmpEqInt(int lhs) -{ - auto left = createConstant(lhs); - Node* control = createNode(opBuilder()->get(), - left, - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpNeInt(int lhs) -{ - generate_CmpEqInt(lhs); - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_CmpEq(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpNe(int lhs) -{ - generate_CmpEq(lhs); - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_CmpGt(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpGe(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpLt(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpLe(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpStrictEqual(int lhs) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_CmpStrictNotEqual(int lhs) -{ - generate_CmpStrictEqual(lhs); - bindAcc(createNode(opBuilder()->get(), - env()->accumulator())); -} - -void GraphBuilder::generate_CmpIn(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_CmpInstanceOf(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_UNot() -{ - bindAcc(createNode(opBuilder()->get(), - createToBoolean(env()->accumulator()))); -} - -void GraphBuilder::generate_UPlus(int /*traceSlot*/) -{ - Node* control = createNode(opBuilder()->get(), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_UMinus(int /*traceSlot*/) -{ - Node* control = createNode(opBuilder()->get(), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_UCompl() -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(-1))); -} - -void GraphBuilder::generate_Increment(int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(1))); -} - - -void GraphBuilder::generate_Decrement(int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(1))); -} - -void GraphBuilder::generate_Add(int lhs, int /*traceSlot*/) -{ - Node* control = createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator()); - bindAcc(control); -} - -void GraphBuilder::generate_BitAnd(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_BitOr(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_BitXor(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_UShr(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Shr(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Shl(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - - -void GraphBuilder::generate_BitAndConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_BitOrConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_BitXorConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_UShrConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_ShrConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_ShlConst(int rhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->accumulator(), - createConstant(rhs))); -} - -void GraphBuilder::generate_Exp(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Mul(int lhs, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Div(int lhs) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Mod(int lhs, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_Sub(int lhs, int /*traceSlot*/) -{ - bindAcc(createNode(opBuilder()->get(), - env()->slot(lhs), - env()->accumulator())); -} - -void GraphBuilder::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) -{ - for (int reg = firstReg; reg < firstReg + count; ++reg) - env()->bindNodeToSlot(graph()->emptyNode(), reg); -} - -void GraphBuilder::generate_ThrowOnNullOrUndefined() -{ - createNode(opBuilder()->get(), - env()->accumulator()); -} - -void GraphBuilder::generate_GetTemplateObject(int index) -{ - bindAcc(createNode(opBuilder()->get(), - createConstant(index))); -} - -GraphBuilder::Verdict GraphBuilder::startInstruction(Moth::Instr::Type /*instr*/) -{ - // This handles a couple of cases on how flow control can end up at this instruction. - - const auto off = currentInstructionOffset(); - if (auto newEnv = m_envForOffset[off]) { - // Ok, there was a jump from before to this point (which registered an environment), so we - // have two options: - if (env() != newEnv && env() != nullptr) { - // There is a current environment different from the environment active when we took the - // jump. This happens with e.g. an if-then-else: - // - // acc = condition - // JumpFalse else-block - // ... then block - // Jump end-if - // else-block: - // ... else block - // end-if: - // .. some instruction <--- we're here - // - // in that case we merge the after-else environment into the after-then environment: - newEnv->merge(env()); - } else { - // There is not a current environment. This can happen with e.g. a loop: - // loop-start: - // acc = condition - // JumpFalse loop-end - // ... loop body - // Jump loop-start - // loop-end: - // .... some instruction <--- we're here - // - // The last jump of the loop will clear the environment, so at this point we only have - // the environment registered by the JumpFalse. This is the asy case: no merges, just - // take the registered environment unchanged. - } - - // Leave the merged environment as-is, and continue with a copy. We cannot change the - // registered environment in case this point also happens to be a loop start. - setEnv(newEnv->copy()); - } - - if (env() == nullptr) { - // Ok, there is no environment, meaning nobody jumped to this instruction, and the previous - // instruction doesn't let control flow end up here. So, this is dead code. - // This can happen for JS like: - // - // if (condition) { - // return something - // } else { - // return somethingElse - // } - // someCode <--- we're here - return SkipInstruction; - } - - const LabelInfo *info = isLoopStart(off); - if (info && env()) { - // Ok, this instruction is the start of a loop, meaning there will be a jump backwards to - // this point. Make sure there is a Region node with Phi nodes here. - handleLoopStart(*info); - } - - return ProcessInstruction; -} - -void GraphBuilder::endInstruction(Moth::Instr::Type /*instr*/) {} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graphbuilder_p.h b/src/qml/jit/qv4graphbuilder_p.h deleted file mode 100644 index 450d8640b7..0000000000 --- a/src/qml/jit/qv4graphbuilder_p.h +++ /dev/null @@ -1,298 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4GRAPHBUILDER_P_H -#define QV4GRAPHBUILDER_P_H - -// -// 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. -// - -#include -#include -#include -#include "qv4graph_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -// The graph builder walks the byte-code, and produces a graph. The graph is a digraph, where the -// nodes have operations, and the edges are dependencies (or inputs). -class GraphBuilder: protected Moth::ByteCodeHandler -{ - Q_DISABLE_COPY_MOVE(GraphBuilder) - - enum { NoTraceSlot = -1 }; - - struct LabelInfo { //### extend this to also capture the amount of slots that are live - LabelInfo() = default; - LabelInfo(unsigned label) : labelOffset(label) {} - unsigned labelOffset = 0; - }; - -public: - static void buildGraph(IR::Function *function); - - class InterpreterEnvironment; - - void setEnv(InterpreterEnvironment *newEnv) - { m_currentEnv = newEnv; } - - InterpreterEnvironment *env() const - { return m_currentEnv; } - -private: - GraphBuilder(IR::Function *function); - ~GraphBuilder() override = default; - - void startGraph(); - void endGraph(); - - Node *bindAcc(Node *n); - Node *createAndLinkNode(Operation *op, Node *operands[], size_t opCount, bool incomplete = false); - Node *createNode(Operation *op, bool incomplete = false); - Node *createNode(Operation *op, Node *n1); - Node *createNode(Operation *op, Node *n1, Node *n2); - Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3); - Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4); - Node *createRegion(unsigned nControlInputs); - Node *createIfTrue(); - Node *createIfFalse(); - Node *createConstant(int v); - Node *createPhi(unsigned nInputs, Node *input, Node *control); - Node *createEffectPhi(unsigned nInputs, Node *input, Node *control); - Node *createHandleUnwind(int offset); - Node *mergeControl(Node *c1, Node *c2); - Node *mergeEffect(Node *e1, Node *e2, Node *control); - Node *mergeValue(Node *v1, Node *v2, Node *control); - - Node *createToBoolean(Node *input); - - using VarArgNodes = QVarLengthArray; - void populate(VarArgNodes &args, int argc, int argv); - - void queueFunctionExit(Node *exitNode); - - Function *function() const - { return m_func; } - - Graph *graph() - { return m_graph; } - - Node *mergeIntoSuccessor(int offset); - - OperationBuilder *opBuilder() const - { return m_graph->opBuilder(); } - - int absoluteOffset(int offset) const - { return offset + nextInstructionOffset(); } - - const LabelInfo *labelInfoAt(unsigned offset) const; - const LabelInfo *isLoopStart(unsigned offset) const; - void handleLoopStart(const LabelInfo &labelInfo); - void startUnwinding(); - -protected: // ByteCodeHandler - void generate_Ret() override; - void generate_Debug() override; - void generate_LoadConst(int index) override; - void generate_LoadZero() override; - void generate_LoadTrue() override; - void generate_LoadFalse() override; - void generate_LoadNull() override; - void generate_LoadUndefined() override; - void generate_LoadInt(int value) override; - void generate_MoveConst(int constIndex, int destTemp) override; - void generate_LoadReg(int reg) override; - void generate_StoreReg(int reg) override; - void generate_MoveReg(int srcReg, int destReg) override; - void generate_LoadImport(int index) override; - void generate_LoadLocal(int index, int traceSlot) override; - void generate_StoreLocal(int index) override; - void generate_LoadScopedLocal(int scope, int index, int traceSlot) override; - void generate_StoreScopedLocal(int scope, int index) override; - void generate_LoadRuntimeString(int stringId) override; - void generate_MoveRegExp(int regExpId, int destReg) override; - void generate_LoadClosure(int value) override; - void generate_LoadName(int name, int traceSlot) override; - void generate_LoadGlobalLookup(int index, int traceSlot) override; - void generate_StoreNameSloppy(int name) override; - void generate_StoreNameStrict(int name) override; - void generate_LoadElement(int base, int traceSlot) override; - void generate_StoreElement(int base, int index, int traceSlot) override; - void generate_LoadProperty(int name, int traceSlot) override; - void generate_GetLookup(int index, int traceSlot) override; - void generate_StoreProperty(int name, int base) override; - void generate_SetLookup(int index, int base) override; - void generate_LoadSuperProperty(int property) override; - void generate_StoreSuperProperty(int property) override; - void generate_LoadQmlContextPropertyLookup(int property, int traceSlot) override; - void generate_Yield() override; - void generate_YieldStar() override; - void generate_Resume(int offset) override; - void finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv); - void generate_CallValue(int name, int argc, int argv, int traceSlot) override; - void generate_CallWithReceiver(int name, int thisObject, int argc, int argv, - int traceSlot) override; - void generate_CallProperty(int name, int base, int argc, int argv, int traceSlot) override; - void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, - int traceSlot) override; - void generate_CallElement(int base, int index, int argc, int argv, int traceSlot) override; - void generate_CallName(int name, int argc, int argv, int traceSlot) override; - void generate_CallPossiblyDirectEval(int argc, int argv, int traceSlot) override; - void generate_CallGlobalLookup(int index, int argc, int argv, int traceSlot) override; - void generate_CallQmlContextPropertyLookup(int index, int argc, int argv, int traceSlot) override; - void generate_SetUnwindHandler(int offset) override; - void generate_UnwindDispatch() override; - void generate_UnwindToLabel(int level, int offset) override; - void generate_DeadTemporalZoneCheck(int name) override; - void generate_ThrowException() override; - void generate_GetException() override; - void generate_SetException() override; - void generate_CreateCallContext() override; - void generate_PushCatchContext(int index, int name) override; - void generate_PushWithContext() override; - void generate_PushBlockContext(int index) override; - void generate_CloneBlockContext() override; - void generate_PushScriptContext(int index) override; - void generate_PopScriptContext() override; - void generate_PopContext() override; - void generate_GetIterator(int iterator) override; - void generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, int resultSlot); - void generate_IteratorNext(int value, int done) override; - void generate_IteratorNextForYieldStar(int iterator, int object) override; - void generate_IteratorClose(int done) override; - void generate_DestructureRestElement() override; - void generate_DeleteProperty(int base, int index) override; - void generate_DeleteName(int name) override; - void generate_TypeofName(int name) override; - void generate_TypeofValue() override; - void generate_DeclareVar(int varName, int isDeletable) override; - void generate_DefineArray(int argc, int argv) override; - void generate_DefineObjectLiteral(int internalClassId, int argc, int argv) override; - void generate_CreateClass(int classIndex, int heritage, int computedNames) override; - void generate_CreateMappedArgumentsObject() override; - void generate_CreateUnmappedArgumentsObject() override; - void generate_CreateRestParameter(int argIndex) override; - void generate_ConvertThisToObject() override; - void generate_LoadSuperConstructor() override; - void generate_ToObject() override; - void generate_CallWithSpread(int func, int thisObject, int argc, int argv, - int traceSlot) override; - void generate_TailCall(int func, int thisObject, int argc, int argv) override; - void generate_Construct(int func, int argc, int argv) override; - void generate_ConstructWithSpread(int func, int argc, int argv) override; - void generate_Jump(int offset) override; - void generate_JumpTrue(int traceSlot, int offset) override; - void generate_JumpFalse(int traceSlot, int offset) override; - void generate_JumpFalse(Node *condition, int traceSlot, int offset); - void generate_JumpNoException(int offset) override; - void generate_JumpNotUndefined(int offset) override; - void generate_CmpEqNull() override; - void generate_CmpNeNull() override; - void generate_CmpEqInt(int lhs) override; - void generate_CmpNeInt(int lhs) override; - void generate_CmpEq(int lhs) override; - void generate_CmpNe(int lhs) override; - void generate_CmpGt(int lhs) override; - void generate_CmpGe(int lhs) override; - void generate_CmpLt(int lhs) override; - void generate_CmpLe(int lhs) override; - void generate_CmpStrictEqual(int lhs) override; - void generate_CmpStrictNotEqual(int lhs) override; - void generate_CmpIn(int lhs) override; - void generate_CmpInstanceOf(int lhs) override; - void generate_UNot() override; - void generate_UPlus(int traceSlot) override; - void generate_UMinus(int traceSlot) override; - void generate_UCompl() override; - void generate_Increment(int traceSlot) override; - void generate_Decrement(int traceSlot) override; - void generate_Add(int lhs, int traceSlot) override; - void generate_BitAnd(int lhs) override; - void generate_BitOr(int lhs) override; - void generate_BitXor(int lhs) override; - void generate_UShr(int lhs) override; - void generate_Shr(int lhs) override; - void generate_Shl(int lhs) override; - void generate_BitAndConst(int rhs) override; - void generate_BitOrConst(int rhs) override; - void generate_BitXorConst(int rhs) override; - void generate_UShrConst(int rhs) override; - void generate_ShrConst(int rhs) override; - void generate_ShlConst(int rhs) override; - void generate_Exp(int lhs) override; - void generate_Mul(int lhs, int traceSlot) override; - void generate_Div(int lhs) override; - void generate_Mod(int lhs, int traceSlot) override; - void generate_Sub(int lhs, int traceSlot) override; - void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; - void generate_ThrowOnNullOrUndefined() override; - void generate_GetTemplateObject(int index) override; - - Verdict startInstruction(Moth::Instr::Type instr) override; - void endInstruction(Moth::Instr::Type instr) override; - -private: - IR::Function *m_func; - Graph *m_graph; - InterpreterEnvironment *m_currentEnv; - std::vector m_exitControls; - QHash m_envForOffset; - std::vector m_labelInfos; - int m_currentUnwindHandlerOffset = 0; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4GRAPHBUILDER_P_H diff --git a/src/qml/jit/qv4ir.cpp b/src/qml/jit/qv4ir.cpp deleted file mode 100644 index cb3eeeec60..0000000000 --- a/src/qml/jit/qv4ir.cpp +++ /dev/null @@ -1,382 +0,0 @@ -/**************************************************************************** -** -** 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 -#include "qv4ir_p.h" -#include "qv4node_p.h" -#include "qv4function_p.h" -#include -#include "qv4stackframe_p.h" -#include "qv4operation_p.h" -#include "qv4util_p.h" - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcJsonIR, "qt.v4.ir.json"); -Q_LOGGING_CATEGORY(lcDotIR, "qt.v4.ir.dot"); -Q_LOGGING_CATEGORY(lcVerify, "qt.v4.ir.verify"); - -Function::Function(QV4::Function *qv4Function) - : qv4Function(qv4Function) - , m_graph(Graph::create(this)) - , m_dumper(nullptr) - , m_nodeInfo(128, nullptr) -{ -} - -Function::~Function() -{ - delete m_dumper; -} - -QString Function::name() const -{ - QString name; - if (auto n = v4Function()->name()) - name = n->toQString(); - if (name.isEmpty()) - name = QString::asprintf("%p", v4Function()); - auto loc = v4Function()->sourceLocation(); - return name + QStringLiteral(" (%1:%2:%3)").arg(loc.sourceFile, QString::number(loc.line), - QString::number(loc.column)); -} - -void Function::dump(const QString &description) const -{ - Dumper::dump(this, description); -} - -void Function::dump() const -{ - dump(QStringLiteral("Debug:")); -} - -Dumper *Function::dumper() const -{ - if (!m_dumper) - m_dumper = new Dumper(this); - return m_dumper; -} - -Function::StringId Function::addString(const QString &s) -{ - m_stringPool.push_back(s); - return m_stringPool.size() - 1; -} - -NodeInfo *Function::nodeInfo(Node *n, bool createIfNecessary) const -{ - if (n->id() >= m_nodeInfo.size()) - m_nodeInfo.resize(n->id() * 2, nullptr); - - NodeInfo *&info = m_nodeInfo[n->id()]; - if (info == nullptr && createIfNecessary) { - info = m_pool.New(); - info->setType(n->operation()->type()); - } - return info; -} - -void Function::copyBytecodeOffsets(Node *from, Node *to) -{ - auto toInfo = nodeInfo(to); - if (auto fromInfo = nodeInfo(from)) { - toInfo->setBytecodeOffsets(fromInfo->currentInstructionOffset(), - fromInfo->nextInstructionOffset()); - } -} - -Dumper::Dumper(const Function *f) -{ - if (!f) - return; -} - -void Dumper::dump(const Function *f, const QString &description) -{ - if (false && lcJsonIR().isDebugEnabled()) { - Dumper *dumper = f->dumper(); - - qCDebug(lcJsonIR).noquote().nospace() << description + QLatin1String(":\n"); - for (const auto &line : dumper->dump(f).split('\n')) - qCDebug(lcJsonIR).noquote().nospace() << line; - } - - if (lcDotIR().isDebugEnabled()) - dot(f, description); -} - -QByteArray Dumper::dump(const Function *f) -{ - QJsonObject fo; - - { - QString name; - if (auto n = f->v4Function()->name()) - name = n->toQString(); - fo[QLatin1String("_searchKey")] = QStringLiteral("function %1").arg(name); - if (name.isEmpty()) - name = QString::asprintf("%p", f->v4Function()); - fo[QLatin1String("name")] = name; - } - - auto loc = f->v4Function()->sourceLocation(); - fo[QLatin1String("source")] = loc.sourceFile; - fo[QLatin1String("line")] = loc.line; - fo[QLatin1String("column")] = loc.column; - - { - QJsonArray gn; - QJsonArray ge; - NodeCollector nodes(f->graph(), /*collectUses =*/ true); - nodes.sortById(); - for (Node *n : nodes.reachable()) { - gn.append(dump(n, f)); - int inputIndex = 0; - for (Node *input : n->inputs()) { - QJsonObject edge; - edge[QLatin1String("from")] = int(input->id()); - edge[QLatin1String("to")] = int(n->id()); - edge[QLatin1String("index")] = inputIndex; - if (inputIndex < n->operation()->valueInputCount()) { - edge[QLatin1String("type")] = QLatin1String("value"); - } else if (inputIndex < n->operation()->valueInputCount() - + n->operation()->effectInputCount()) { - edge[QLatin1String("type")] = QLatin1String("effect"); - } else { - edge[QLatin1String("type")] = QLatin1String("control"); - } - Q_ASSERT(inputIndex < n->operation()->valueInputCount() - + n->operation()->effectInputCount() - + n->operation()->controlInputCount()); - ge.append(edge); - ++inputIndex; - } - } - QJsonObject g; - g[QLatin1String("nodes")] = gn; - g[QLatin1String("edges")] = ge; - fo[QLatin1String("graph")] = g; - } - - m_doc.setObject(fo); - return m_doc.toJson(QJsonDocument::Indented); -} - -QJsonValue toJSonValue(QV4::Value v) -{ - switch (v.type()) { - case QV4::Value::Undefined_Type: return QJsonValue(QJsonValue::Undefined); - case QV4::Value::Null_Type: return QJsonValue(QJsonValue::Null); - case QV4::Value::Boolean_Type: return QJsonValue(v.booleanValue()); - case QV4::Value::Integer_Type: return QJsonValue(v.int_32()); - case QV4::Value::Managed_Type: - if (String *s = v.stringValue()) - return QJsonValue(s->toQString()); - else - return QJsonValue(QLatin1String("")); - default: return QJsonValue(v.doubleValue()); - } -} - -QJsonValue Dumper::dump(const Node * const node, const Function *f) -{ - QJsonObject n; - n[QLatin1String("id")] = int(node->id()); - n[QLatin1String("kind")] = node->operation()->debugString(); - switch (node->operation()->kind()) { - case Meta::Parameter: { - auto info = ParameterPayload::get(*node->operation()); - n[QLatin1String("name")] = f->string(info->stringId()); - n[QLatin1String("index")] = int(info->parameterIndex()); - break; - } - case Meta::Constant: { - auto info = ConstantPayload::get(*node->operation()); - n[QLatin1String("value")] = toJSonValue(info->value()); - break; - } - default: - break; - } - return n; -} - -void Dumper::dot(const Function *f, const QString &description) -{ - static const bool skipFramestate = qEnvironmentVariableIsSet("QV4_JIT_DOT_SKIP_FRAMESTATE"); - - auto node = [](Node *n) { - return QStringLiteral("n%1[label=\"%1: %2%3\"];\n").arg(QString::number(n->id()), - n->operation()->debugString(), - n->isDead() ? QStringLiteral(" (dead)") - : QString()); - }; - - Graph *g = f->graph(); - QString out; - out += QLatin1Char('\n'); - out += QStringLiteral("digraph{root=\"n%1\" label=\"%2\";" - "node[shape=rect];" - "edge[dir=back fontsize=10];\n") - .arg(g->startNode()->id()) - .arg(description); - out += node(g->startNode()); - const bool dumpUses = false; // set to true to see all nodes - NodeCollector nodes(g, dumpUses, skipFramestate); - for (Node *n : nodes.reachable()) { - if (n == g->startNode()) - continue; - - out += node(n); - - unsigned inputIndex = 0; - for (Node *input : n->inputs()) { - if (input == nullptr) - continue; - out += QStringLiteral("n%2->n%1[style=").arg(QString::number(n->id()), - QString::number(input->id())); - if (inputIndex < n->operation()->valueInputCount() || - inputIndex == n->operation()->indexOfFrameStateInput()) { - out += QStringLiteral("solid headlabel=\"%1\"").arg(inputIndex); - } else if (inputIndex < unsigned(n->operation()->valueInputCount() - + n->operation()->effectInputCount())) { - out += QStringLiteral("dotted headlabel=\"%1\"").arg(inputIndex); - } else { - out += QStringLiteral("dashed headlabel=\"%1\"").arg(inputIndex); - } - out += QStringLiteral("];\n"); - ++inputIndex; - } - } - out += QStringLiteral("}\n"); - qCDebug(lcDotIR).nospace().noquote() << out; - - QFile of(description + QStringLiteral(".dot")); - of.open(QIODevice::WriteOnly); - of.write(out.toUtf8()); - of.close(); -} - -void Function::verify() const -{ -#ifndef QT_NO_DEBUG - unsigned problemsFound = 0; - - auto verifyNodeAgainstOperation = [&problemsFound](const Node *n) { - const Operation *op = n->operation(); - if (op->totalInputCount() != n->inputCount()) { - ++problemsFound; - qCDebug(lcVerify()) << "Node" << n->id() << "has" << n->inputCount() - << "inputs, but it's operation" << op->debugString() - << "requires" << op->totalInputCount() << "inputs"; - } - - if (n->opcode() == Meta::Phi || n->opcode() == Meta::EffectPhi) { - if (n->controlInput()->opcode() != Meta::Region) { - ++problemsFound; - qCDebug(lcVerify()) << "Control input of phi node" << n->id() << "is not a region"; - } - if (n->controlInput()->inputCount() + 1 != n->inputCount()) { - ++problemsFound; - qCDebug(lcVerify()) << "Control input of phi node" << n->id() - << "has" << n->controlInput()->inputCount() - << "inputs while phi node has" << n->inputCount() - << "inputs"; - } - } - - //### todo: verify outputs: value outputs are allowed to be unused, but the effect and - // control outputs have to be linked up, except: - //### todo: verify if no use is a nullptr, except for operations that can throw, where the - // last one is allowed to be a nullptr when an unwind handler is missing. - }; - - NodeWorkList todo(graph()); - todo.enqueue(graph()->endNode()); - while (Node *n = todo.dequeueNextNodeForVisiting()) { - todo.enqueueAllInputs(n); - todo.enqueueAllUses(n); - - verifyNodeAgainstOperation(n); - } - //### TODO: - if (problemsFound != 0) { - dump(QStringLiteral("Problematic graph")); - qFatal("Found %u problems during graph verification!", problemsFound); - } -#endif // QT_NO_xDEBUG -} - -QString Type::debugString() const -{ - if (isNone()) - return QStringLiteral("none"); - if (isInvalid()) - return QStringLiteral("invalid"); - - QStringList s; - if (m_t & Bool) - s += QStringLiteral("boolean"); - if (m_t & Int32) - s += QStringLiteral("int32"); - if (m_t & Double) - s += QStringLiteral("double"); - if (m_t & Undefined) - s += QStringLiteral("undefined"); - if (m_t & Null) - s += QStringLiteral("null"); - if (m_t & Empty) - s += QStringLiteral("empty"); - if (m_t & RawPointer) - s += QStringLiteral("raw pointer"); - - return s.join(QLatin1String(" ")); -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4ir_p.h b/src/qml/jit/qv4ir_p.h deleted file mode 100644 index e21a80528d..0000000000 --- a/src/qml/jit/qv4ir_p.h +++ /dev/null @@ -1,228 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4IR_P_H -#define QV4IR_P_H - -// -// 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. -// - -#include -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class Dumper; -class Graph; - -class Node; -class NodeInfo; - -class Function -{ - Q_DISABLE_COPY_MOVE(Function) -public: - Function(QV4::Function *qv4Function); - ~Function(); - - void verify() const; - - QV4::Function *v4Function() const - { return qv4Function; } - - QString name() const; - - QQmlJS::MemoryPool *pool() - { return &m_pool; } - - Graph *graph() const - { return m_graph; } - - void dump(const QString &description) const; - void dump() const; // for calling in the debugger - Dumper *dumper() const; - - using StringId = size_t; - StringId addString(const QString &s); - QString string(StringId id) const - { return m_stringPool[id]; } - - NodeInfo *nodeInfo(Node *n, bool createIfNecessary = true) const; - void copyBytecodeOffsets(Node *from, Node *to); - - void addUnwindLabelOffset(int absoluteOffset) - { m_unwindLabelOffsets.push_back(absoluteOffset); } - - const std::vector &unwindLabelOffsets() const - { return m_unwindLabelOffsets; } - -private: - QV4::Function *qv4Function; - mutable QQmlJS::MemoryPool m_pool; - Graph *m_graph; - mutable Dumper *m_dumper; - std::vector m_stringPool; - mutable std::vector m_nodeInfo; //### move the into the _pool - std::vector m_unwindLabelOffsets; -}; - -class Dumper -{ - Q_DISABLE_COPY_MOVE(Dumper) - -public: - Dumper(const Function *f); - ~Dumper() = default; - - static void dump(const Function *f, const QString &description); - static void dot(const Function *f, const QString &description); - -private: - QByteArray dump(const Function *f); - QJsonValue dump(const Node *node, const Function *f); - -private: - QJsonDocument m_doc; -}; - -class Type -{ - // None is for nodes with no type (e.g. a Return) - // The others form a lattice: - // Any -> Object -> Invalid - // ^^^ -> Number -> Integral -> Int32 -> ^^^^^^^ - // ^^^ -> Number -> Integral -> UInt32 -> ^^^^^^^ - // ^^^ -> Number -> Integral -> Bool -> ^^^^^^^ - // ^^^ -> Number -> Double -> ^^^^^^^ - // ^^^ -> Undefined -> ^^^^^^^ - // ^^^ -> Null -> ^^^^^^^ - // ^^^ -> Empty -> ^^^^^^^ - enum InternalType: int16_t { - None = 0, - - Object = 1 << 0, - Bool = 1 << 1, - Int32 = 1 << 2, - UInt32 = 1 << 3, - Double = 1 << 4, - Undefined = 1 << 5, - Null = 1 << 6, - Empty = 1 << 7, - RawPointer = 1 << 8, - Invalid = -1, - - Integral = Int32 | UInt32 | Bool, - Number = Integral | Double, - Any = Object | Number | Undefined | Empty | Null, - }; - - Type(InternalType t) : m_t(t) {} - -public: - Type() = default; - - bool operator==(const Type &other) const - { return m_t == other.m_t; } - - static Type noneType() { return Type(None); } - static Type anyType() { return Type(Any); } - static Type undefinedType() { return Type(Undefined); } - static Type emptyType() { return Type(Empty); } - static Type booleanType() { return Type(Bool); } - static Type int32Type() { return Type(Int32); } - static Type doubleType() { return Type(Double); } - static Type numberType() { return Type(Number); } - static Type nullType() { return Type(Null); } - static Type objectType() { return Type(Object); } - static Type rawPointerType() { return Type(RawPointer); } - - bool isAny() const { return m_t == Any; } - bool isBoolean() const { return m_t == Bool; } - bool isInt32() const { return m_t == Int32; } - bool isInvalid() const { return m_t == Invalid; } - bool isNone() const { return m_t == None; } - bool isDouble() const { return m_t == Double; } - bool isUndefined() const { return m_t == Undefined; } - bool isNull() const { return m_t == Null; } - bool isEmpty() const { return m_t == Empty; } - bool isObject() const { return m_t == Object; } - bool isRawPointer() const { return m_t == RawPointer; } - bool isIntegral() const { return matches(Integral); } - bool isNumber() const { return matches(Number); } - - Type operator|(Type other) const - { return Type(InternalType(int16_t(m_t) | int16_t(other.m_t))); } - - Type &operator|=(Type other) - { - m_t = (InternalType(int16_t(m_t) | int16_t(other.m_t))); - return *this; - } - - QString debugString() const; - -private: - bool matches(InternalType it) const - { - return (m_t & ~it) == 0 && (m_t & it) != 0; - } - -private: - InternalType m_t = None; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4IR_P_H diff --git a/src/qml/jit/qv4loopinfo.cpp b/src/qml/jit/qv4loopinfo.cpp deleted file mode 100644 index 0366c49e30..0000000000 --- a/src/qml/jit/qv4loopinfo.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4loopinfo_p.h" -#include "qv4domtree_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcLoopinfo, "qt.v4.ir.loopinfo") - -void LoopInfo::detectLoops() -{ - blockInfos.resize(dt.function()->blockCount()); - - std::vector backedges; - backedges.reserve(4); - - const auto order = dt.calculateDFNodeIterOrder(); - for (MIBlock *bb : order) { - if (bb->isDeoptBlock()) - continue; - - backedges.clear(); - - for (MIBlock *pred : bb->inEdges()) { - if (bb == pred || dt.dominates(bb->index(), pred->index())) - backedges.push_back(pred); - } - - if (!backedges.empty()) - subLoop(bb, backedges); - } - - collectLoopExits(); - - dump(); -} - -void LoopInfo::collectLoopExits() -{ - for (MIBlock::Index i = 0, ei = MIBlock::Index(blockInfos.size()); i != ei; ++i) { - BlockInfo &bi = blockInfos[i]; - MIBlock *currentBlock = dt.function()->block(i); - if (bi.isLoopHeader) { - for (MIBlock *outEdge : currentBlock->outEdges()) { - if (outEdge != currentBlock && !inLoopOrSubLoop(outEdge, currentBlock)) - bi.loopExits.push_back(outEdge); - } - } - if (MIBlock *containingLoop = bi.loopHeader) { - BlockInfo &loopInfo = blockInfos[containingLoop->index()]; - for (MIBlock *outEdge : currentBlock->outEdges()) { - if (outEdge != containingLoop && !inLoopOrSubLoop(outEdge, containingLoop)) - loopInfo.loopExits.push_back(outEdge); - } - } - } -} - -bool LoopInfo::inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const -{ - const BlockInfo &bi = blockInfos[block->index()]; - MIBlock *loopHeaderForBlock = bi.loopHeader; - if (loopHeaderForBlock == nullptr) - return false; // block is not in any loop - - while (loopHeader) { - if (loopHeader == loopHeaderForBlock) - return true; - // look into the parent loop of loopHeader to see if block is contained there - loopHeader = blockInfos[loopHeader->index()].loopHeader; - } - - return false; -} - -void LoopInfo::subLoop(MIBlock *loopHead, const std::vector &backedges) -{ - blockInfos[loopHead->index()].isLoopHeader = true; - - std::vector worklist; - worklist.reserve(backedges.size() + 8); - worklist.insert(worklist.end(), backedges.begin(), backedges.end()); - while (!worklist.empty()) { - MIBlock *predIt = worklist.back(); - worklist.pop_back(); - - MIBlock *subloop = blockInfos[predIt->index()].loopHeader; - if (subloop) { - // This is a discovered block. Find its outermost discovered loop. - while (MIBlock *parentLoop = blockInfos[subloop->index()].loopHeader) - subloop = parentLoop; - - // If it is already discovered to be a subloop of this loop, continue. - if (subloop == loopHead) - continue; - - // Yay, it's a subloop of this loop. - blockInfos[subloop->index()].loopHeader = loopHead; - predIt = subloop; - - // Add all predecessors of the subloop header to the worklist, as long as - // those predecessors are not in the current subloop. It might be the case - // that they are in other loops, which we will then add as a subloop to the - // current loop. - for (MIBlock *predIn : predIt->inEdges()) - if (blockInfos[predIn->index()].loopHeader != subloop) - worklist.push_back(predIn); - } else { - if (predIt == loopHead) - continue; - - // This is an undiscovered block. Map it to the current loop. - blockInfos[predIt->index()].loopHeader = loopHead; - - // Add all incoming edges to the worklist. - for (MIBlock *bb : predIt->inEdges()) - worklist.push_back(bb); - } - } -} - -void LoopInfo::dump() const -{ - if (!lcLoopinfo().isDebugEnabled()) - return; - - QString s = QStringLiteral("Loop information:\n"); - for (size_t i = 0, ei = blockInfos.size(); i != ei; ++i) { - const BlockInfo &bi = blockInfos[i]; - s += QStringLiteral(" %1 : is loop header: %2, contained in loop header's loop: ") - .arg(i).arg(bi.isLoopHeader ? QLatin1String("yes") : QLatin1String("no")); - if (bi.loopHeader) - s += QString::number(bi.loopHeader->index()); - else - s += QLatin1String(""); - if (bi.isLoopHeader) { - s += QStringLiteral(", loop exits: "); - if (bi.loopExits.empty()) { - s += QLatin1String(""); - } else { - bool first = true; - for (MIBlock *exit : bi.loopExits) { - if (first) - first = false; - else - s += QStringLiteral(", "); - s += QString::number(exit->index()); - } - } - } - s += QLatin1Char('\n'); - } - qCDebug(lcLoopinfo).noquote().nospace() << s; -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4loopinfo_p.h b/src/qml/jit/qv4loopinfo_p.h deleted file mode 100644 index 6a865e6dc6..0000000000 --- a/src/qml/jit/qv4loopinfo_p.h +++ /dev/null @@ -1,159 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4LOOPINFO_P_H -#define QV4LOOPINFO_P_H - -// -// 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. -// - -#include "qv4mi_p.h" - -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class DominatorTree; - -// Detect all (sub-)loops in a function. -// -// Doing loop detection on the CFG is better than relying on the statement information in -// order to mark loops. Although JavaScript only has natural loops, it can still be the case -// that something is not a loop even though a loop-like-statement is in the source. For -// example: -// while (true) { -// if (i > 0) -// break; -// else -// break; -// } -// -// Algorithm: -// - do a DFS on the dominator tree, where for each node: -// - collect all back-edges -// - if there are back-edges, the node is a loop-header for a new loop, so: -// - walk the CFG is reverse-direction, and for every node: -// - if the node already belongs to a loop, we've found a nested loop: -// - get the loop-header for the (outermost) nested loop -// - add that loop-header to the current loop -// - continue by walking all incoming edges that do not yet belong to the current loop -// - if the node does not belong to a loop yet, add it to the current loop, and -// go on with all incoming edges -// -// Loop-header detection by checking for back-edges is very straight forward: a back-edge is -// an incoming edge where the other node is dominated by the current node. Meaning: all -// execution paths that reach that other node have to go through the current node, that other -// node ends with a (conditional) jump back to the loop header. -// -// The exact order of the DFS on the dominator tree is not important. The only property has to -// be that a node is only visited when all the nodes it dominates have been visited before. -// The reason for the DFS is that for nested loops, the inner loop's loop-header is dominated -// by the outer loop's header. So, by visiting depth-first, sub-loops are identified before -// their containing loops, which makes nested-loop identification free. An added benefit is -// that the nodes for those sub-loops are only processed once. -// -// Note: independent loops that share the same header are merged together. For example, in -// the code snippet below, there are 2 back-edges into the loop-header, but only one single -// loop will be detected. -// while (a) { -// if (b) -// continue; -// else -// continue; -// } -class LoopInfo -{ - Q_DISABLE_COPY_MOVE(LoopInfo) - - struct BlockInfo - { - MIBlock *loopHeader = nullptr; - bool isLoopHeader = false; - std::vector loopExits; - }; - -public: - LoopInfo(const DominatorTree &dt) - : dt(dt) - {} - - ~LoopInfo() = default; - - void detectLoops(); - - MIBlock *loopHeaderFor(MIBlock *bodyBlock) const - { return blockInfos[bodyBlock->index()].loopHeader; } - - bool isLoopHeader(MIBlock *block) const - { return blockInfos[block->index()].isLoopHeader; } - - const std::vector loopExitsForLoop(MIBlock *loopHeader) const - { return blockInfos[loopHeader->index()].loopExits; } - -private: - void subLoop(MIBlock *loopHead, const std::vector &backedges); - void collectLoopExits(); - bool inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const; - - void dump() const; - -private: - const DominatorTree &dt; - std::vector blockInfos; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4LOOPINFO_P_H diff --git a/src/qml/jit/qv4lowering.cpp b/src/qml/jit/qv4lowering.cpp deleted file mode 100644 index 3b3711e7fa..0000000000 --- a/src/qml/jit/qv4lowering.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4lowering_p.h" -#include "qv4graph_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcLower, "qt.v4.ir.lowering") - -GenericLowering::GenericLowering(Function &f) - : m_function(f) -{} - -void GenericLowering::lower() -{ - NodeWorkList worklist(graph()); - // The order doesn't really matter for generic lowering, as long as it's done in 1 pass, and - // have any clean-up done afterwards. - worklist.enqueueAllInputs(graph()->endNode()); - - while (Node *n = worklist.dequeueNextNodeForVisiting()) { - worklist.enqueueAllInputs(n); - - if (!CallPayload::isRuntimeCall(n->opcode())) - continue; - - if (CallPayload::isVarArgsCall(n->opcode())) - replaceWithVarArgsCall(n); - else - replaceWithCall(n); - } -} - -void GenericLowering::replaceWithCall(Node *n) -{ - auto newOp = opBuilder()->getCall(n->opcode()); - - QVarLengthArray args; - if (CallPayload::takesEngineAsArg(n->opcode(), 0)) - args.append(graph()->engineNode()); - if (CallPayload::takesFunctionAsArg(n->opcode(), args.size())) - args.append(graph()->functionNode()); - if (CallPayload::takesFrameAsArg(n->opcode(), args.size())) - args.append(graph()->cppFrameNode()); - const int extraLeadingArguments = args.size(); - - for (unsigned arg = 0, earg = n->inputCount(); arg != earg; ++arg) { - Node *input = n->input(arg); - if (input->opcode() == Meta::FrameState) - continue; - - if (arg >= n->operation()->valueInputCount()) { - // effect or control input - args.append(input); - continue; - } - - if (CallPayload::needsStorageOnJSStack(n->opcode(), args.size(), input->operation(), - function().nodeInfo(input)->type())) - input = graph()->createNode(opBuilder()->get(), input); - - args.append(input); - } - - Node *newCall = graph()->createNode(newOp, args.data(), args.size()); - - qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() - << "with node" << newCall->id() << newOp->debugString(); - qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); - qCDebug(lcLower) << "... old node #uses:" << n->useCount(); - - function().nodeInfo(newCall)->setType(CallPayload::returnType(n->opcode())); - n->replaceAllUsesWith(newCall); - n->kill(); - - qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); - qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); - - for (Node *use : newCall->uses()) { - // fix-up indices for SelectOutput: - if (use->opcode() == Meta::SelectOutput) { - const int oldIndex = ConstantPayload::get(*use->input(1)->operation())->value().int_32(); - const int newIndex = oldIndex + extraLeadingArguments; - use->replaceInput(1, graph()->createConstantIntNode(newIndex)); - use->replaceInput(2, newCall->input(newIndex)); - break; - } - } -} - -void GenericLowering::replaceWithVarArgsCall(Node *n) -{ - const bool isTailCall = n->opcode() == Meta::JSTailCall; - Operation *newOp = isTailCall ? opBuilder()->getTailCall() - : opBuilder()->getCall(n->opcode()); - - //### optimize this for 0 and 1 argument: we don't need to create a VarArgs array for these cases - - const unsigned varArgsStart = CallPayload::varArgsStart(n->opcode()) - 1; // subtract 1 because the runtime calls all take the engine argument as arg0, which isn't in the graph before lowering. - Node *vaAlloc = graph()->createNode( - opBuilder()->get(), - graph()->createConstantIntNode(n->operation()->valueInputCount() - varArgsStart), - n->effectInput()); - QVarLengthArray vaSealIn; - vaSealIn.append(vaAlloc); - for (unsigned i = varArgsStart, ei = n->operation()->valueInputCount(); i != ei; ++i) { - vaSealIn.append(graph()->createNode(opBuilder()->get(), vaAlloc, - graph()->createConstantIntNode(vaSealIn.size() - 1), - n->input(i))); - } - vaSealIn.append(vaAlloc); - Node *vaSeal = graph()->createNode(opBuilder()->getVASeal(vaSealIn.size() - 2), - vaSealIn.data(), - vaSealIn.size()); - QVarLengthArray callArgs; - if (isTailCall) - callArgs.append(graph()->cppFrameNode()); - callArgs.append(graph()->engineNode()); - for (unsigned i = 0; i != varArgsStart; ++i) { - Node *input = n->input(i); - if (CallPayload::needsStorageOnJSStack(n->opcode(), callArgs.size(), input->operation(), - function().nodeInfo(input)->type())) - input = graph()->createNode(opBuilder()->get(), input); - callArgs.append(input); - } - callArgs.append(vaSeal); // args - if (n->opcode() != Meta::JSCreateClass) // JSCreateClass is the odd duck - callArgs.append(graph()->createConstantIntNode(vaSealIn.size() - 2)); // argc - callArgs.append(vaSeal); // effect - callArgs.append(n->controlInput(0)); // control flow - Node *newCall = graph()->createNode(newOp, callArgs.data(), unsigned(callArgs.size())); - - qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() - << "with node" << newCall->id() << newOp->debugString(); - qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); - qCDebug(lcLower) << "... old node #uses:" << n->useCount(); - - n->replaceAllUsesWith(newCall); - n->kill(); - - qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); - qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); -} - -bool GenericLowering::allUsesAsUnboxedBool(Node *n) -{ - for (Node *use : n->uses()) { - if (use->operation()->kind() != Meta::Branch) - return false; - } - - return true; -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4lowering_p.h b/src/qml/jit/qv4lowering_p.h deleted file mode 100644 index 0b482bc9f0..0000000000 --- a/src/qml/jit/qv4lowering_p.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4LOWERING_P_H -#define QV4LOWERING_P_H - -// -// 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. -// - -#include -#include -#include -#include -#include -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -// Lowering replaces JS level operations with lower level ones. E.g. a JSAdd is lowered to an AddI32 -// if both inputs and the output are 32bit integers, or to a runtime call in all other cases. This -// transforms the graph into something that is closer to actual executable code. - - -// Last lowering phase: replace all JSOperations that are left with runtime calls. There is nothing -// smart here, all that should have been done before this phase. -class GenericLowering final -{ - Q_DISABLE_COPY(GenericLowering) - -public: - GenericLowering(Function &f); - - void lower(); - -private: - void replaceWithCall(Node *n); - void replaceWithVarArgsCall(Node *n); - static bool allUsesAsUnboxedBool(Node *n); - - Function &function() - { return m_function; } - - Graph *graph() - { return function().graph(); } - - OperationBuilder *opBuilder() - { return graph()->opBuilder(); } - -private: - Function &m_function; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4LOWERING_P_H diff --git a/src/qml/jit/qv4mi.cpp b/src/qml/jit/qv4mi.cpp deleted file mode 100644 index f0b172243d..0000000000 --- a/src/qml/jit/qv4mi.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/**************************************************************************** -** -** 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 -#include - -#include "qv4mi_p.h" -#include "qv4node_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcMI, "qt.v4.ir.mi") - -QString MIOperand::debugString() const -{ - switch (kind()) { - case Invalid: return QStringLiteral("<>"); - case Constant: return ConstantPayload::debugString(constantValue()); - case VirtualRegister: return QStringLiteral("vreg%1").arg(virtualRegister()); - case EngineRegister: return QStringLiteral("engine"); - case CppFrameRegister: return QStringLiteral("cppFrame"); - case Function: return QStringLiteral("function"); - case JSStackSlot: return QStringLiteral("jsstack[%1]").arg(stackSlot()); - case BoolStackSlot: return QStringLiteral("bstack[%1]").arg(stackSlot()); - case JumpTarget: return targetBlock() ? QStringLiteral("L%1").arg(targetBlock()->index()) - : QStringLiteral("<>"); - default: Q_UNREACHABLE(); - } -} - -MIInstr *MIInstr::create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands) -{ - return pool->New(irNode, pool, nOperands); -} - -static QString commentIndent(const QString &line) -{ - int spacing = std::max(70 - line.length(), 1); - return line + QString(spacing, QLatin1Char(' ')); -} - -static QString indent(int nr) -{ - QString s = nr == -1 ? QString() : QString::number(nr); - int padding = 6 - s.size(); - if (padding > 0) - s = QString(padding, QLatin1Char(' ')) + s; - return s; -} - -MIFunction::MIFunction(Function *irFunction) - : m_irFunction(irFunction) -{} - -void MIFunction::renumberBlocks() -{ - for (size_t i = 0, ei = m_blocks.size(); i != ei; ++i) { - MIBlock *b = m_blocks[i]; - b->setIndex(unsigned(i)); - } -} - -void MIFunction::renumberInstructions() -{ - int pos = 0; - for (MIBlock *b : m_blocks) { - for (MIInstr &instr : b->instructions()) { - pos += 2; - instr.setPosition(pos); - } - } -} - -void MIFunction::dump(const QString &description) const -{ - if (!lcMI().isDebugEnabled()) - return; - - QString s = description + QLatin1String(":\n"); - QString name; - if (auto n = irFunction()->v4Function()->name()) - name = n->toQString(); - if (name.isEmpty()) - QString::asprintf("%p", static_cast(irFunction()->v4Function())); - QString line = QStringLiteral("function %1 {").arg(name); - auto loc = irFunction()->v4Function()->sourceLocation(); - s += commentIndent(line) + QStringLiteral("; %1:%2:%3\n").arg(loc.sourceFile, - QString::number(loc.line), - QString::number(loc.column)); - for (const MIBlock *b : blocks()) { - line = QStringLiteral("L%1").arg(b->index()); - bool first = true; - if (!b->arguments().empty()) { - line += QLatin1Char('('); - for (const MIOperand &arg : b->arguments()) { - if (first) - first = false; - else - line += QStringLiteral(", "); - line += arg.debugString(); - } - line += QLatin1Char(')'); - } - line += QLatin1Char(':'); - line = commentIndent(line) + QStringLiteral("; preds: "); - if (b->inEdges().isEmpty()) { - line += QStringLiteral(""); - } else { - bool first = true; - for (MIBlock *in : b->inEdges()) { - if (first) - first = false; - else - line += QStringLiteral(", "); - line += QStringLiteral("L%1").arg(in->index()); - } - } - s += line + QLatin1Char('\n'); - for (const MIInstr &i : b->instructions()) { - line = indent(i.position()) + QLatin1String(": "); - if (i.hasDestination()) - line += i.destination().debugString() + QStringLiteral(" = "); - line += i.irNode()->operation()->debugString(); - bool first = true; - for (const MIOperand &op : i.operands()) { - if (first) - first = false; - else - line += QLatin1Char(','); - line += QLatin1Char(' ') + op.debugString(); - } - line = commentIndent(line) + QStringLiteral("; node-id: %1").arg(i.irNode()->id()); - if (i.irNode()->operation()->needsBytecodeOffsets()) - line += QStringLiteral(", bytecode-offset: %1").arg(irFunction()->nodeInfo(i.irNode())->currentInstructionOffset()); - s += line + QLatin1Char('\n'); - } - s += commentIndent(QString()) + QStringLiteral("; succs: "); - if (b->outEdges().isEmpty()) { - s += QStringLiteral(""); - } else { - bool first = true; - for (MIBlock *succ : b->outEdges()) { - if (first) - first = false; - else - s += QStringLiteral(", "); - s += QStringLiteral("L%1").arg(succ->index()); - } - } - s += QLatin1Char('\n'); - } - s += QLatin1Char('}'); - - for (const QStringRef &line : s.splitRef('\n')) - qCDebug(lcMI).noquote().nospace() << line; -} - -unsigned MIFunction::extraJSSlots() const -{ - uint interpreterFrameSize = CppStackFrame::requiredJSStackFrameSize(irFunction()->v4Function()); - if (m_jsSlotCount <= interpreterFrameSize) - return 0; - return m_jsSlotCount - interpreterFrameSize; -} - -void MIFunction::setStartBlock(MIBlock *newStartBlock) -{ - auto it = std::find(m_blocks.begin(), m_blocks.end(), newStartBlock); - Q_ASSERT(it != m_blocks.end()); - std::swap(*m_blocks.begin(), *it); -} - -void MIFunction::setStackSlotCounts(unsigned dword, unsigned qword, unsigned js) -{ - m_vregCount = 0; - m_dwordSlotCount = dword; - m_qwordSlotCount = qword; - m_jsSlotCount = js; -} - -void MIFunction::verifyCFG() const -{ - if (block(MIFunction::StartBlockIndex)->instructions().front().opcode() != Meta::Start) - qFatal("MIFunction block 0 is not the start block"); - - for (MIBlock *b : m_blocks) { - for (MIBlock *in : b->inEdges()) { - if (!in->outEdges().contains(b)) - qFatal("block %u has incoming edge from block %u, " - "but does not appear in that block's outgoing edges", - b->index(), in->index()); - } - for (MIBlock *out : b->outEdges()) { - if (!out->inEdges().contains(b)) - qFatal("block %u has outgoing edge from block %u, " - "but does not appear in that block's incoming edges", - b->index(), out->index()); - } - } -} - -MIBlock *MIBlock::findEdgeTo(Operation::Kind target) const -{ - for (MIBlock *outEdge : outEdges()) { - if (outEdge->instructions().front().opcode() == target) - return outEdge; - } - return nullptr; -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4mi_p.h b/src/qml/jit/qv4mi_p.h deleted file mode 100644 index f976d1dc94..0000000000 --- a/src/qml/jit/qv4mi_p.h +++ /dev/null @@ -1,627 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4MI_P_H -#define QV4MI_P_H - -// -// 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. -// - -#include -#include -#include -#include - -#include -#include -#include -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -// This file contains the Machine Interface (MI) data structures, on which ultimately the assembler -// will operate: - -class MIFunction; // containing all basic blocks, and a reference to the IR function - -class MIBlock; // containing an ordered sequence of instructions - -class MIInstr; // containing operands, and a reference to the IR node, that indicates which - // operation is represented by an instruction - -class MIOperand; // contains a description of where to get/put the input/result of an operation - -// A detail about the stack slots: there two stacks, the JS stack and the native stack. A frame on -// the native stack is divided in two parts: the quad-word part and the double-word part. The -// qword part holds 64bit values, like doubles, and pointers on 64bit architectures. The dword part -// holds 32bit values, like int32s, booleans, and pointers on 32bit architectures. We need to know -// the type of value a slot holds, because if we have to move it to the JS stack, we have to box it -// correctly. -class MIOperand final -{ -public: - enum Kind { - Invalid = 0, - Constant, - VirtualRegister, - - EngineRegister, - CppFrameRegister, - Function, - - JSStackSlot, - BoolStackSlot, - - JumpTarget, - }; - - using List = QQmlJS::FixedPoolArray; - -public: - MIOperand() = default; - - static MIOperand createConstant(Node *irNode) - { - MIOperand op; - op.m_kind = Constant; - op.m_irNode = irNode; - return op; - } - - static MIOperand createVirtualRegister(Node *irNode, unsigned vreg) - { - MIOperand op; - op.m_kind = VirtualRegister; - op.m_irNode = irNode; - op.m_vreg = vreg; - return op; - } - - static MIOperand createEngineRegister(Node *irNode) - { - MIOperand op; - op.m_kind = EngineRegister; - op.m_irNode = irNode; - return op; - } - - static MIOperand createCppFrameRegister(Node *irNode) - { - MIOperand op; - op.m_kind = CppFrameRegister; - op.m_irNode = irNode; - return op; - } - - static MIOperand createFunction(Node *irNode) - { - MIOperand op; - op.m_kind = Function; - op.m_irNode = irNode; - return op; - } - - static MIOperand createJSStackSlot(Node *irNode, unsigned slot) - { - MIOperand op; - op.m_kind = JSStackSlot; - op.m_irNode = irNode; - op.m_slot = slot; - return op; - } - - static MIOperand createBoolStackSlot(Node *irNode, unsigned slot) - { - MIOperand op; - op.m_kind = BoolStackSlot; - op.m_irNode = irNode; - op.m_slot = slot; - return op; - } - - //### or name this createDeoptBlock? - static MIOperand createJumpTarget(Node *irNode, MIBlock *targetBlock) - { - MIOperand op; - op.m_kind = JumpTarget; - op.m_irNode = irNode; - op.m_targetBlock = targetBlock; - return op; - } - - Kind kind() const - { return m_kind; } - - bool isValid() const - { return m_kind != Invalid; } - - bool isConstant() const - { return m_kind == Constant; } - - bool isVirtualRegister() const - { return kind() == VirtualRegister; } - - bool isEngineRegister() const - { return kind() == EngineRegister; } - - bool isCppFrameRegister() const - { return kind() == CppFrameRegister; } - - bool isFunction() const - { return kind() == Function; } - - bool isJSStackSlot() const - { return kind() == JSStackSlot; } - - bool isBoolStackSlot() const - { return kind() == BoolStackSlot; } - - bool isStackSlot() const - { return isJSStackSlot() || isDWordSlot() || isQWordSlot(); } - - bool isJumpTarget() const - { return kind() == JumpTarget; } - - Node *irNode() const - { return m_irNode; } - - inline Type nodeType(MIFunction *f) const; - - QString debugString() const; - - QV4::Value constantValue() const - { - Q_ASSERT(isConstant()); - if (irNode()->opcode() == Meta::Undefined) - return QV4::Value::undefinedValue(); - if (irNode()->opcode() == Meta::Empty) - return QV4::Value::emptyValue(); - return ConstantPayload::get(*irNode()->operation())->value(); - } - - unsigned virtualRegister() const - { Q_ASSERT(isVirtualRegister()); return m_vreg; } - - unsigned stackSlot() const - { Q_ASSERT(isStackSlot()); return m_slot; } - - MIBlock *targetBlock() const - { Q_ASSERT(isJumpTarget()); return m_targetBlock; } - - inline bool operator==(const MIOperand &other) const - { - if (kind() != other.kind()) - return false; - - if (isStackSlot()) - return stackSlot() == other.stackSlot(); - - switch (kind()) { - case MIOperand::Invalid: - return !other.isValid(); - case MIOperand::Constant: - return constantValue().asReturnedValue() == other.constantValue().asReturnedValue(); - case MIOperand::VirtualRegister: - return virtualRegister() == other.virtualRegister(); - case MIOperand::JumpTarget: - return targetBlock() == other.targetBlock(); - default: - Q_UNREACHABLE(); - return false; - } - } - - bool isDWordSlot() const - { - switch (kind()) { - case BoolStackSlot: - return true; - default: - return false; - } - } - - bool isQWordSlot() const - { - switch (kind()) { - //### TODO: double slots - default: - return false; - } - } - - bool overlaps(const MIOperand &other) const - { - if ((isDWordSlot() && other.isDWordSlot()) || (isQWordSlot() && other.isQWordSlot())) - ; // fine, these are the same - else if (kind() != other.kind()) - return false; - - if (isStackSlot()) - return stackSlot() == other.stackSlot(); - - return false; - } - -private: - Node *m_irNode = nullptr; - union { - unsigned m_vreg; - unsigned m_slot; - MIBlock *m_targetBlock = nullptr; - }; - Kind m_kind = Invalid; -}; - -template struct MIInstrListParentType {}; -template <> struct MIInstrListParentType { using type = MIBlock; }; - -template class MIInstrList; - -template -class MIInstrListTraits : public llvm::ilist_noalloc_traits -{ -protected: - using ListTy = MIInstrList; - using iterator = typename llvm::simple_ilist::iterator; - using ItemParentClass = typename MIInstrListParentType::type; - -public: - MIInstrListTraits() = default; - -protected: - void setListOwner(ItemParentClass *listOwner) - { m_owner = listOwner; } - -private: - ItemParentClass *m_owner = nullptr; - - /// getListOwner - Return the object that owns this list. If this is a list - /// of instructions, it returns the BasicBlock that owns them. - ItemParentClass *getListOwner() const { - return m_owner; - } - - static ListTy &getList(ItemParentClass *Par) { - return Par->*(Par->getSublistAccess()); - } - - static MIInstrListTraits *getSymTab(ItemParentClass *Par) { - return Par ? toPtr(Par->getValueSymbolTable()) : nullptr; - } - -public: - void addNodeToList(MISubClass *V) { V->setParent(getListOwner()); } - void removeNodeFromList(MISubClass *V) { V->setParent(nullptr); } - void transferNodesFromList(MIInstrListTraits &L2, iterator first, - iterator last); -}; - -template -class MIInstrList: public llvm::iplist_impl, MIInstrListTraits> -{ -public: - MIInstrList(typename MIInstrListTraits::ItemParentClass *owner) - { this->setListOwner(owner); } -}; - -class MIInstr final : public llvm::ilist_node_with_parent -{ - Q_DISABLE_COPY_MOVE(MIInstr) // heap use only! - -protected: - friend class QQmlJS::MemoryPool; - MIInstr() : m_operands(nullptr, 0) {} - - explicit MIInstr(Node *irNode, QQmlJS::MemoryPool *pool, unsigned nOperands) - : m_irNode(irNode) - , m_operands(pool, nOperands) - {} - - ~MIInstr() = default; - -public: - static MIInstr *create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands); - - MIBlock *parent() const - { return m_parent; } - - MIBlock *getParent() const // for ilist_node_with_parent - { return parent(); } - - void setParent(MIBlock *parent) - { m_parent = parent; } - - Node *irNode() const - { return m_irNode; } - - Operation::Kind opcode() const - { return m_irNode->opcode(); } - - int position() const - { return m_position; } - - inline void insertBefore(MIInstr *insertPos); - inline void insertAfter(MIInstr *insertPos); - inline MIInstrList::iterator eraseFromParent(); - - bool hasDestination() const - { return m_destination.isValid(); } - - MIOperand destination() const - { return m_destination; } - - void setDestination(const MIOperand &dest) - { m_destination = dest; } - - const MIOperand &operand(unsigned index) const - { return m_operands.at(index); } - - void setOperand(unsigned index, const MIOperand &op) - { m_operands.at(index) = op; } - - MIOperand &operand(unsigned index) - { return m_operands.at(index); } - - const MIOperand::List &operands() const - { return m_operands; } - - MIOperand::List &operands() - { return m_operands; } - -private: - friend MIFunction; - void setPosition(int newPosition) - { m_position = newPosition; } - -private: - MIBlock *m_parent = nullptr; - Node *m_irNode = nullptr; - int m_position = -1; - MIOperand m_destination; - MIOperand::List m_operands; -}; - -class MIBlock final -{ - Q_DISABLE_COPY_MOVE(MIBlock) - -public: - using Index = unsigned; - enum : Index { InvalidIndex = std::numeric_limits::max() }; - - using MIInstructionList = MIInstrList; - - using InEdges = QVarLengthArray; - using OutEdges = QVarLengthArray; - -protected: - friend MIFunction; - explicit MIBlock(Index index) - : m_instructions(this), - m_index(index) - {} - - void setIndex(Index newIndex) - { m_index = newIndex; } - -public: - ~MIBlock() = default; - - const MIInstructionList &instructions() const - { return m_instructions; } - - MIInstructionList &instructions() - { return m_instructions; } - - static MIInstructionList MIBlock::*getSublistAccess(MIInstr * = nullptr) - { return &MIBlock::m_instructions; } - - void addArgument(MIOperand &&arg) - { m_arguments.push_back(arg); } - - const std::vector &arguments() const - { return m_arguments; } - - std::vector &arguments() - { return m_arguments; } - - void clearArguments() - { m_arguments.resize(0); } - - const InEdges &inEdges() const - { return m_inEdges; } - - void addInEdge(MIBlock *edge) - { m_inEdges.append(edge); } - - const OutEdges &outEdges() const - { return m_outEdges; } - - void addOutEdge(MIBlock *edge) - { m_outEdges.append(edge); } - - Index index() const - { return m_index; } - - MIBlock *findEdgeTo(Operation::Kind target) const; - - bool isDeoptBlock() const - { return m_isDeoptBlock; } - - void markAsDeoptBlock() - { m_isDeoptBlock = true; } - -private: - std::vector m_arguments; - MIInstructionList m_instructions; - InEdges m_inEdges; - OutEdges m_outEdges; - Index m_index; - bool m_isDeoptBlock = false; -}; - -class MIFunction final -{ - Q_DISABLE_COPY_MOVE(MIFunction) - -public: - static constexpr MIBlock::Index StartBlockIndex = 0; - -public: - MIFunction(Function *irFunction); - ~MIFunction() - { qDeleteAll(m_blocks); } - - Function *irFunction() const - { return m_irFunction; } - - void setStartBlock(MIBlock *newStartBlock); - void renumberBlocks(); - void renumberInstructions(); - - void dump(const QString &description) const; - - size_t blockCount() const - { return blocks().size(); } - - MIBlock *block(MIBlock::Index index) const - { return m_blocks[index]; } - - const std::vector &blocks() const - { return m_blocks; } - - MIBlock *addBlock() - { - auto *b = new MIBlock(unsigned(m_blocks.size())); - m_blocks.push_back(b); - return b; - } - - void setBlockOrder(const std::vector &newSequence) - { m_blocks = newSequence; } - - unsigned vregCount() const - { return m_vregCount; } - - void setVregCount(unsigned vregCount) - { m_vregCount = vregCount; } - - unsigned dwordSlotCount() const - { return m_dwordSlotCount; } - - unsigned qwordSlotCount() const - { return m_qwordSlotCount; } - - unsigned jsSlotCount() const - { return m_jsSlotCount; } - - unsigned extraJSSlots() const; - - void setStackSlotCounts(unsigned dword, unsigned qword, unsigned js); - - void verifyCFG() const; - -private: - Function *m_irFunction = nullptr; - std::vector m_blocks; - unsigned m_vregCount = 0; - unsigned m_dwordSlotCount = 0; - unsigned m_qwordSlotCount = 0; - unsigned m_jsSlotCount = 0; -}; - -Type MIOperand::nodeType(MIFunction *f) const -{ - return f->irFunction()->nodeInfo(irNode())->type(); -} - -inline uint qHash(const MIOperand &key, uint seed) -{ - uint h = ::qHash(key.kind(), seed); - switch (key.kind()) { - case MIOperand::VirtualRegister: - h ^= key.virtualRegister(); - break; - case MIOperand::BoolStackSlot: Q_FALLTHROUGH(); - case MIOperand::JSStackSlot: - h ^= key.stackSlot(); - break; - default: - qFatal("%s: cannot hash %s", Q_FUNC_INFO, key.debugString().toUtf8().constData()); - } - return h; -} - -void MIInstr::insertBefore(MIInstr *insertPos) -{ - insertPos->parent()->instructions().insert(insertPos->getIterator(), this); -} - -void MIInstr::insertAfter(MIInstr *insertPos) -{ - insertPos->parent()->instructions().insert(++insertPos->getIterator(), this); -} - -MIInstrList::iterator MIInstr::eraseFromParent() -{ - auto p = parent(); - setParent(nullptr); - return p->instructions().erase(getIterator()); -} - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4MI_P_H diff --git a/src/qml/jit/qv4miblockset_p.h b/src/qml/jit/qv4miblockset_p.h deleted file mode 100644 index 5f814b99e0..0000000000 --- a/src/qml/jit/qv4miblockset_p.h +++ /dev/null @@ -1,291 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4MIBLOCKSET_P_H -#define QV4MIBLOCKSET_P_H - -// -// 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. -// - -#include "qv4mi_p.h" -#include "qv4util_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class MIBlockSet -{ - using Flags = BitVector; - - QVarLengthArray blockNumbers; - Flags *blockFlags = nullptr; - MIFunction *function = nullptr; - enum { MaxVectorCapacity = 8 }; - -public: - class const_iterator; - friend class const_iterator; - -public: - MIBlockSet(MIFunction *f = nullptr) - { - if (f) - init(f); - } - - MIBlockSet(MIBlockSet &&other) noexcept - { - std::swap(blockNumbers, other.blockNumbers); - std::swap(blockFlags, other.blockFlags); - std::swap(function, other.function); - } - - MIBlockSet(const MIBlockSet &other) - : function(other.function) - { - if (other.blockFlags) - blockFlags = new Flags(*other.blockFlags); - blockNumbers = other.blockNumbers; - } - - MIBlockSet &operator=(const MIBlockSet &other) - { - if (blockFlags) { - delete blockFlags; - blockFlags = nullptr; - } - function = other.function; - if (other.blockFlags) - blockFlags = new Flags(*other.blockFlags); - blockNumbers = other.blockNumbers; - return *this; - } - - MIBlockSet &operator=(MIBlockSet &&other) noexcept - { - if (&other != this) { - std::swap(blockNumbers, other.blockNumbers); - - delete blockFlags; - blockFlags = other.blockFlags; - other.blockFlags = nullptr; - - function = other.function; - } - return *this; - } - - ~MIBlockSet() - { - delete blockFlags; - } - - void init(MIFunction *f) - { - Q_ASSERT(!function); - Q_ASSERT(f); - function = f; - } - - bool empty() const; - - void insert(MIBlock *bb) - { - Q_ASSERT(function); - - if (blockFlags) { - blockFlags->setBit(bb->index()); - return; - } - - for (unsigned int blockNumber : qAsConst(blockNumbers)) { - if (blockNumber == bb->index()) - return; - } - - if (blockNumbers.size() == MaxVectorCapacity) { - blockFlags = new Flags(int(function->blockCount()), false); - for (unsigned int blockNumber : qAsConst(blockNumbers)) { - blockFlags->setBit(int(blockNumber)); - } - blockNumbers.clear(); - blockFlags->setBit(int(bb->index())); - } else { - blockNumbers.append(bb->index()); - } - } - - void remove(MIBlock *bb) - { - Q_ASSERT(function); - - if (blockFlags) { - blockFlags->clearBit(bb->index()); - return; - } - - for (int i = 0; i < blockNumbers.size(); ++i) { - if (blockNumbers[i] == bb->index()) { - blockNumbers.remove(i); - return; - } - } - } - - const_iterator begin() const; - const_iterator end() const; - - void collectValues(std::vector &bbs) const; - - bool contains(MIBlock *bb) const - { - Q_ASSERT(function); - - if (blockFlags) - return blockFlags->at(bb->index()); - - for (unsigned int blockNumber : blockNumbers) { - if (blockNumber == bb->index()) - return true; - } - - return false; - } -}; - -class MIBlockSet::const_iterator -{ - const MIBlockSet &set; - // ### These two members could go into a union, but clang won't compile - // (https://codereview.qt-project.org/#change,74259) - QVarLengthArray::const_iterator numberIt; - MIBlock::Index flagIt; - - friend class MIBlockSet; - const_iterator(const MIBlockSet &set, bool end) - : set(set) - { - if (end || !set.function) { - if (!set.blockFlags) - numberIt = set.blockNumbers.end(); - else - flagIt = set.blockFlags->size(); - } else { - if (!set.blockFlags) - numberIt = set.blockNumbers.begin(); - else - findNextWithFlags(0); - } - } - - void findNextWithFlags(int start) - { - flagIt = MIBlock::Index(set.blockFlags->findNext(start, true, /*wrapAround = */false)); - Q_ASSERT(flagIt <= MIBlock::Index(set.blockFlags->size())); - } - -public: - MIBlock *operator*() const - { - if (!set.blockFlags) - return set.function->block(*numberIt); - - Q_ASSERT(flagIt <= set.function->blockCount()); - return set.function->block(flagIt); - - } - - bool operator==(const const_iterator &other) const - { - if (&set != &other.set) - return false; - if (!set.blockFlags) - return numberIt == other.numberIt; - return flagIt == other.flagIt; - } - - bool operator!=(const const_iterator &other) const - { return !(*this == other); } - - const_iterator &operator++() - { - if (!set.blockFlags) - ++numberIt; - else - findNextWithFlags(flagIt + 1); - - return *this; - } -}; - -inline bool MIBlockSet::empty() const -{ return begin() == end(); } - -inline MIBlockSet::const_iterator MIBlockSet::begin() const -{ return const_iterator(*this, false); } - -inline MIBlockSet::const_iterator MIBlockSet::end() const -{ return const_iterator(*this, true); } - -inline void MIBlockSet::collectValues(std::vector &bbs) const -{ - Q_ASSERT(function); - - for (auto it : *this) - bbs.push_back(it); -} - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4MIBLOCKSET_P_H diff --git a/src/qml/jit/qv4node.cpp b/src/qml/jit/qv4node.cpp deleted file mode 100644 index e059e9fef6..0000000000 --- a/src/qml/jit/qv4node.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4node_p.h" -#include "qv4graph_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Node *Node::create(Node::MemoryPool *pool, Node::Id id, const Operation *op, size_t nInputs, - Node *const *inputs, bool inputsAreExtensible) -{ - size_t capacity = nInputs; - if (inputsAreExtensible) - capacity += 3; - - Node *node = new (pool->allocate(sizeof(Node))) Node(pool, id, op, unsigned(nInputs), - int(capacity)); - for (uint i = 0; i < capacity; ++i) - new (&node->m_inputs[int(i)]) Use(node); - for (size_t i = 0; i < nInputs; ++i) { - Q_ASSERT(inputs[i] != nullptr); - node->replaceInput(unsigned(i), inputs[i]); - } - - return node; -} - -void Node::addInput(MemoryPool *pool, Node *in) -{ - Q_ASSERT(in); - ++m_nInputs; - if (m_nInputs >= unsigned(m_inputs.size())) { - QQmlJS::FixedPoolArray oldInputs = m_inputs; - m_inputs = QQmlJS::FixedPoolArray(pool, int(m_nInputs + 3)); - for (Use &input : m_inputs) - new (&input) Use(this); - for (int i = 0, ei = oldInputs.size(); i != ei; ++i) { - Node *in = oldInputs[i].m_input; - oldInputs[i].set(nullptr); - m_inputs[i].set(in); - } - } - m_inputs.at(int(m_nInputs - 1)).set(in); -} - -void Node::removeInput(unsigned index) -{ - Q_ASSERT(index < inputCount()); - for (unsigned i = index, ei = inputCount(); i < ei - 1; ++i) - replaceInput(i, input(i + 1)); - trimInputCount(inputCount() - 1); -} - -void Node::removeInputs(unsigned start, unsigned count) -{ - for (unsigned idx = start; idx < start + count; ++idx) - m_inputs.at(int(idx)).set(nullptr); -} - -void Node::removeAllInputs() -{ - removeInputs(0, inputCount()); -} - -void Node::trimInputCount(unsigned newCount) -{ - unsigned currentCount = inputCount(); - if (newCount == currentCount) - return; - Q_ASSERT(newCount < currentCount); - removeInputs(newCount, currentCount - newCount); - m_nInputs = newCount; -} - -void Node::removeExceptionHandlerUse() -{ - for (Use* use = m_firstUse; use; use = use->m_next) { - if (use->m_input->opcode() == Meta::OnException) { - use->set(nullptr); - break; - } - } -} - -void Node::insertInput(Node::MemoryPool *pool, unsigned index, Node *newInput) -{ - Q_ASSERT(index < inputCount()); - addInput(pool, input(inputCount() - 1)); - for (unsigned i = inputCount() - 1; i > index; --i) - replaceInput(i, input(i - 1)); - replaceInput(index, newInput); -} - -void Node::replaceAllUsesWith(Node *replacement) -{ - for (Use *use = m_firstUse; use; ) { - Use *next = use->m_next; - const unsigned inIdx = use->inputIndex(); - use->user()->replaceInput(inIdx, replacement); - use = next; - } -} - -void Node::replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput) -{ - for (Use *use = m_firstUse; use; ) { - Use *next = use->m_next; - const Operation *inOp = use->user()->operation(); - const unsigned inIdx = use->inputIndex(); - if (inIdx < inOp->valueInputCount()) - use->user()->replaceInput(inIdx, newValueInput); - else if (inIdx < inOp->indexOfFirstControl()) - use->user()->replaceInput(inIdx, newEffectInput); - else - use->user()->replaceInput(inIdx, newControlInput); - use = next; - } -} - -Node *Node::firstValueUse() -{ - for (auto it = uses().begin(), eit = uses().end(); it != eit; ++it) { - if (it.isUsedAsValue()) - return *it; - } - return nullptr; -} - -Node::Node(MemoryPool *pool, Node::Id id, const Operation *op, unsigned nInputs, int capacity) - : m_op(op) - , m_inputs(pool, capacity) - , m_nInputs(nInputs) - , m_id(id) -{ -} - -NodeWorkList::NodeWorkList(const Graph *g) - : m_nodeState(g->nodeCount(), Unvisited) -{ m_worklist.reserve(64); } - -void NodeWorkList::reset() -{ - std::fill(m_nodeState.begin(), m_nodeState.end(), Unvisited); - m_worklist.clear(); - if (m_worklist.capacity() < 64) - m_worklist.reserve(64); -} - -NodeCollector::NodeCollector(const Graph *g, bool collectUses, bool skipFramestate) -{ - markReachable(g->endNode()); - for (size_t i = 0; i < m_reachable.size(); ++i) { // _reachable.size() is on purpose! - Node *n = m_reachable.at(i); - for (auto input : n->inputs()) { - if (input == nullptr) - continue; - if (isReachable(input->id())) - continue; - if (skipFramestate && input->opcode() == Meta::FrameState) - continue; - markReachable(input); - } - - if (collectUses) { - for (Node *use : n->uses()) { - if (use && !isReachable(use->id())) - markReachable(use); - } - } - } -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4node_p.h b/src/qml/jit/qv4node_p.h deleted file mode 100644 index 76065fb1bc..0000000000 --- a/src/qml/jit/qv4node_p.h +++ /dev/null @@ -1,626 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4NODE_P_H -#define QV4NODE_P_H - -// -// 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. -// - -#include -#include -#include -#include "qv4util_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -class Use -{ - Q_DISABLE_COPY_MOVE(Use) - -public: - Use(Node *user) - : m_user(user) - {} - - ~Use() - { - if (m_input) - removeFromList(); - } - - operator Node *() const { return m_input; } - Node *input() const { return m_input; } - Node *user() const { return m_user; } - - inline void set(Node *newInput); - - void validate() const - { - Q_ASSERT(m_user); - if (m_input) { - if (m_prev != nullptr) - Q_ASSERT(*m_prev == this); - if (m_next) { - Q_ASSERT(m_next->m_input == m_input); - Q_ASSERT(m_next->m_prev == &m_next); - m_next->validate(); - } - } - } - - inline int inputIndex() const; - -protected: - friend class Node; - - void addToList(Use **list) { - validate(); - m_next = *list; - if (m_next) - m_next->m_prev = &m_next; - m_prev = list; - *list = this; - validate(); - } - - void removeFromList() { - validate(); - Use **newPrev = m_prev; - *newPrev = m_next; - m_prev = nullptr; - if (m_next) - m_next->m_prev = newPrev; - m_next = nullptr; - m_input = nullptr; - validate(); - } - -private: - Node *m_input = nullptr; - Node *m_user = nullptr; - Use *m_next = nullptr; - Use **m_prev = nullptr; -}; - -// A node represents an calculation, action, or marker in the graph. Each node has an operation, -// input dependencies and uses. The operation indicates what kind of node it is, e.g.: JSAdd, -// Constant, Region, and so on. Two nodes can have the same operation, but different inputs. -// For example, the expressions 1 + 2 and 3 + 4 will each have a node with an JSAdd operation -// (which is exactly the same operation for both nodes), but the nodes have different inputs (1, and -// 2 in the first expression, while the second operation has 3 and 4 as inputs). -class Node final -{ - Q_DISABLE_COPY_MOVE(Node) - -public: - using Id = uint32_t; - using MemoryPool = QQmlJS::MemoryPool; - class Inputs; - -public: - static Node *create(MemoryPool *pool, Id id, const Operation *op, size_t nInputs, - Node * const *inputs, bool inputsAreExtensible = false); - ~Node() = delete; - - inline bool isDead() const; - inline void kill(); - - Id id() const { return m_id; } - - const Operation *operation() const - { return m_op; } - - void setOperation(const Operation *op) - { m_op = op; } - - Operation::Kind opcode() const - { return operation()->kind(); } - - inline Inputs inputs() const; - void addInput(MemoryPool *pool, Node *in); - void removeInput(unsigned index); - void removeInputs(unsigned start, unsigned count); - void removeAllInputs(); - uint32_t inputCount() const - { return m_nInputs; } - void trimInputCount(unsigned newCount); - - void removeExceptionHandlerUse(); - - Node *input(unsigned idx) const - { - Q_ASSERT(idx < inputCount()); - return m_inputs.at(idx); - } - - Node *effectInput(unsigned effectIndex = 0) const - { - if (operation()->effectInputCount() == 0) - return nullptr; - Q_ASSERT(effectIndex < operation()->effectInputCount()); - return input(operation()->indexOfFirstEffect() + effectIndex); - } - - Node *controlInput(unsigned controlIndex = 0) const - { - if (operation()->controlInputCount() == 0) - return nullptr; - Q_ASSERT(controlIndex < operation()->controlInputCount()); - return input(operation()->indexOfFirstControl() + controlIndex); - } - - Node *frameStateInput() const - { - if (operation()->hasFrameStateInput()) - return input(operation()->indexOfFrameStateInput()); - return nullptr; - } - - void setFrameStateInput(Node *newFramestate) - { - if (operation()->hasFrameStateInput()) - replaceInput(operation()->indexOfFrameStateInput(), newFramestate); - } - - void insertInput(MemoryPool *pool, unsigned index, Node *newInput); - - void replaceInput(Node *oldIn, Node *newIn) - { - for (unsigned i = 0, ei = inputCount(); i != ei; ++i) { - if (input(i) == oldIn) - replaceInput(i, newIn); - } - } - - void replaceInput(unsigned idx, Node *newIn) - { - m_inputs[idx].set(newIn); - } - - class Uses - { - public: - explicit Uses(Node *node) - : m_node(node) - {} - - class const_iterator; - inline const_iterator begin() const; - inline const_iterator end() const; - - bool isEmpty() const; - - private: - Node *m_node; - }; - - Uses uses() { return Uses(this); } - bool hasUses() const { return m_firstUse != nullptr; } - unsigned useCount() const - { - unsigned cnt = 0; - for (Use *it = m_firstUse; it; it = it->m_next) - ++cnt; - return cnt; - } - void replaceAllUsesWith(Node *replacement); - void replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput); - - Node *firstValueUse(); - -private: // types and utility methods - friend class Use; - Node(MemoryPool *pool, Id id, const Operation *op, unsigned nInputs, int capacity); - -private: // fields - Use *m_firstUse = nullptr; - const Operation *m_op = nullptr; - QQmlJS::FixedPoolArray m_inputs; - unsigned m_nInputs = 0; - Id m_id = 0; -}; - -void Use::set(Node *newInput) -{ - if (m_input) - removeFromList(); - m_input = newInput; - if (newInput) - addToList(&newInput->m_firstUse); -} - -class Node::Inputs final -{ -public: - using value_type = Node *; - - class const_iterator; - inline const_iterator begin() const; - inline const_iterator end() const; - - bool empty() const - { return m_nInputs == 0; } - - unsigned count() const - { return m_nInputs; } - - explicit Inputs(const Use *inputs, unsigned nInputs) - : m_inputs(inputs), m_nInputs(nInputs) - {} - -private: - const Use *m_inputs = nullptr; - unsigned m_nInputs = 0; -}; - -class Node::Inputs::const_iterator final -{ -public: - using iterator_category = std::forward_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = Node *; - using pointer = const value_type *; - using reference = value_type &; - - Node *operator*() const - { return m_inputs->m_input; } - - bool operator==(const const_iterator &other) const - { return m_inputs == other.m_inputs; } - - bool operator!=(const const_iterator &other) const - { return !(*this == other); } - - const_iterator &operator++() - { ++m_inputs; return *this; } - - const_iterator& operator+=(difference_type offset) - { m_inputs += offset; return *this; } - - const_iterator operator+(difference_type offset) const - { return const_iterator(m_inputs + offset); } - - difference_type operator-(const const_iterator &other) const - { return m_inputs - other.m_inputs; } - -private: - friend class Node::Inputs; - - explicit const_iterator(const Use *inputs) - : m_inputs(inputs) - {} - - const Use *m_inputs; -}; - -Node::Inputs::const_iterator Node::Inputs::begin() const -{ return const_iterator(m_inputs); } - -Node::Inputs::const_iterator Node::Inputs::end() const -{ return const_iterator(m_inputs + m_nInputs); } - -Node::Inputs Node::inputs() const -{ - return Inputs(m_inputs.begin(), m_nInputs); -} - -class Node::Uses::const_iterator final -{ -public: - using iterator_category = std::forward_iterator_tag; - using difference_type = int; - using value_type = Node *; - using pointer = Node **; - using reference = Node *&; - - Node *operator*() const - { return m_current->user(); } - - bool operator==(const const_iterator &other) const - { return other.m_current == m_current; } - - bool operator!=(const const_iterator &other) const - { return other.m_current != m_current; } - - const_iterator &operator++() - { m_current = m_next; setNext(); return *this; } - - unsigned inputIndex() const - { return m_current->inputIndex(); } - - bool isUsedAsValue() const - { return inputIndex() < operator*()->operation()->valueInputCount(); } - - bool isUsedAsControl() const - { return operator*()->operation()->indexOfFirstControl() <= inputIndex(); } - -private: - friend class Node::Uses; - - const_iterator() = default; - - explicit const_iterator(Node* node) - : m_current(node->m_firstUse) - { setNext(); } - - void setNext() - { - if (m_current) - m_next = m_current->m_next; - else - m_next = nullptr; - } - -private: - Use *m_current = nullptr; - Use *m_next = nullptr; -}; - -Node::Uses::const_iterator Node::Uses::begin() const -{ return const_iterator(this->m_node); } - -Node::Uses::const_iterator Node::Uses::end() const -{ return const_iterator(); } - -int Use::inputIndex() const -{ - if (!m_user) - return -1; - return int(this - m_user->m_inputs.begin()); -} - -bool Node::isDead() const -{ - Inputs in = inputs(); - return !in.empty() && *in.begin() == nullptr; -} - -void Node::kill() -{ - removeAllInputs(); -} - -class NodeWorkList final -{ - enum State: uint8_t { - Unvisited = 0, - Queued, - Visited, - }; - -public: - NodeWorkList(const Graph *g); - - void reset(); - - bool enqueue(Node *n) - { - State &s = nodeState(n); - if (s == Queued || s == Visited) - return false; - - m_worklist.push_back(n); - s = Queued; - return true; - } - - void enqueue(const std::vector &nodes) - { - m_worklist.insert(m_worklist.end(), nodes.begin(), nodes.end()); - for (Node *n : nodes) - nodeState(n) = Queued; - } - - void reEnqueue(Node *n) - { - if (!n) - return; - State &s = nodeState(n); - if (s == Queued) - return; - s = Queued; - m_worklist.push_back(n); - } - - void enqueueAllInputs(Node *n) - { - for (Node *input : n->inputs()) - enqueue(input); - } - - void reEnqueueAllInputs(Node *n) - { - for (Node *input : n->inputs()) - reEnqueue(input); - } - - void enqueueValueInputs(Node *n) - { - for (unsigned i = 0, ei = n->operation()->valueInputCount(); i != ei; ++i) - enqueue(n->input(i)); - } - - void enqueueEffectInputs(Node *n) - { - for (unsigned i = n->operation()->indexOfFirstEffect(), ei = n->operation()->effectInputCount(); i != ei; ++i) - enqueue(n->input(i)); - } - - void enqueueAllUses(Node *n) - { - for (Node *use : n->uses()) - enqueue(use); - } - - Node *dequeueNextNodeForVisiting() - { - while (!m_worklist.empty()) { - Node *n = m_worklist.back(); - m_worklist.pop_back(); - State &s = nodeState(n); - Q_ASSERT(s == Queued); - s = Visited; - return n; - } - - return nullptr; - } - - bool isVisited(Node *n) const - { return nodeState(n) == Visited; } - - bool isEmpty() const - { return m_worklist.empty(); } - - QString status(Node *n) const - { - QString s = QStringLiteral("status for node %1: ").arg(n->id()); - switch (nodeState(n)) { - case Queued: s += QLatin1String("queued"); break; - case Visited: s += QLatin1String("visited"); break; - case Unvisited: s += QLatin1String("unvisited"); break; - } - return s; - } - -private: - State &nodeState(Node *n) - { - const unsigned position(n->id()); - if (position >= m_nodeState.size()) - m_nodeState.resize(position + 1, Unvisited); - - return m_nodeState[position]; - } - - State nodeState(Node *n) const - { return m_nodeState[unsigned(n->id())]; } - -private: - std::vector m_worklist; - std::vector m_nodeState; -}; - -class NodeInfo -{ -public: - enum { NoInstructionOffset = -1 }; - -public: - NodeInfo() = default; - - Type type() const { return m_type; } - void setType(Type t) { m_type = t; } - - int currentInstructionOffset() const - { return m_currentInstructionOffset; } - - int nextInstructionOffset() const - { return m_nextInstructionOffset; } - - void setBytecodeOffsets(int current, int next) - { - Q_ASSERT(current != NoInstructionOffset); - Q_ASSERT(next != NoInstructionOffset); - m_currentInstructionOffset = current; - m_nextInstructionOffset = next; - } - -private: - Type m_type; - int m_currentInstructionOffset = NoInstructionOffset; - int m_nextInstructionOffset = NoInstructionOffset; -}; - -class NodeCollector -{ -public: - NodeCollector(const Graph *g, bool collectUses = false, bool skipFramestate = false); - - const std::vector &reachable() const - { return m_reachable; } - - void sortById() - { - std::sort(m_reachable.begin(), m_reachable.end(), [](Node *n1, Node *n2) { - return n1->id() < n2->id(); - }); - } - - bool isReachable(Node::Id nodeId) const - { - if (nodeId >= Node::Id(m_isReachable.size())) - return false; - return m_isReachable.at(int(nodeId)); - } - - void markReachable(Node *node) - { - auto nodeId = node->id(); - m_reachable.push_back(node); - if (nodeId >= Node::Id(m_isReachable.size())) - m_isReachable.resize(int(nodeId + 1), false); - m_isReachable.setBit(int(nodeId)); - } - -private: - std::vector m_reachable; - BitVector m_isReachable; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4NODE_P_H diff --git a/src/qml/jit/qv4operation.cpp b/src/qml/jit/qv4operation.cpp deleted file mode 100644 index acd5328fd0..0000000000 --- a/src/qml/jit/qv4operation.cpp +++ /dev/null @@ -1,770 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4operation_p.h" -#include "qv4runtimesupport_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -OperationBuilder::OperationBuilder(QQmlJS::MemoryPool *graphPool) - : m_graphPool(graphPool) -{} - -OperationBuilder *OperationBuilder::create(QQmlJS::MemoryPool *pool) -{ - return pool->New(pool); -} - -Operation *OperationBuilder::getConstant(Value v) -{ - Type t; - switch (v.type()) { - case Value::Undefined_Type: t = Type::undefinedType(); break; - case Value::Integer_Type: t = Type::int32Type(); break; - case Value::Boolean_Type: t = Type::booleanType(); break; - case Value::Null_Type: t = Type::nullType(); break; - case Value::Double_Type: t = Type::doubleType(); break; - case Value::Managed_Type: t = Type::objectType(); break; - default: - if (v.isEmpty()) - t = Type::emptyType(); - else - Q_UNREACHABLE(); - } - return OperationWithPayload::create( - m_graphPool, Meta::Constant, 0, 0, 0, 1, 0, 0, t, Operation::NoFlags, - ConstantPayload(v)); -} - -Operation *OperationBuilder::getParam(unsigned index, const Function::StringId name) -{ - return OperationWithPayload::create(m_graphPool, Meta::Parameter, - 1, 0, 0, - 1, 0, 0, - Type::anyType(), Operation::NoFlags, - ParameterPayload(index, name)); -} - -Operation *OperationBuilder::getRegion(unsigned nControlInputs) //### cache common operands in the static pool -{ - return Operation::create(m_graphPool, Meta::Region, - 0, 0, uint16_t(nControlInputs), - 0, 0, 1, - Type(), Operation::NoFlags); -} - -Operation *OperationBuilder::getPhi(unsigned nValueInputs) //### cache common operands in the static pool -{ - return Operation::create(m_graphPool, Meta::Phi, - uint16_t(nValueInputs), 0, 1, - 1, 0, 0, - Type::anyType(), Operation::NoFlags); -} - -Operation *OperationBuilder::getEffectPhi(unsigned nEffectInputs) //### cache common operands in the static pool -{ - return Operation::create(m_graphPool, Meta::EffectPhi, - 0, uint16_t(nEffectInputs), 1, - 0, 1, 0, - Type(), Operation::NoFlags); -} - -Operation *OperationBuilder::getUnwindDispatch(unsigned nContinuations, int unwindHandlerOffset, - int fallthroughSuccessor) -{ - return OperationWithPayload::create( - m_graphPool, Meta::UnwindDispatch, - 0, 1, 1, 0, nContinuations, nContinuations, - Type(), Operation::NoFlags, - UnwindDispatchPayload(unwindHandlerOffset, - fallthroughSuccessor)); -} - -Operation *OperationBuilder::getHandleUnwind(int unwindHandlerOffset) -{ - return OperationWithPayload::create(m_graphPool, Meta::HandleUnwind, - 0, 1, 1, 0, 1, 1, - Type(), Operation::NoFlags, - HandleUnwindPayload(unwindHandlerOffset)); -} - -Operation *OperationBuilder::getFrameState(uint16_t frameSize) -{ - if (m_opFrameState == nullptr) - m_opFrameState = Operation::create(m_graphPool, Meta::FrameState, - frameSize, 0, 0, 0, 0, 1, - Type(), Operation::NoFlags); - else - Q_ASSERT(frameSize == m_opFrameState->valueInputCount()); - - return m_opFrameState; -} - -Operation *OperationBuilder::getStart(uint16_t outputCount) -{ - return Operation::create(m_graphPool, Meta::Start, - 0, 0, 0, - outputCount, 1, 1, - Type(), Operation::NoFlags); -} - -Operation *OperationBuilder::getEnd(uint16_t controlInputCount) -{ - return Operation::create(m_graphPool, Meta::End, - 0, 0, controlInputCount, - 0, 0, 0, - Type(), Operation::NoFlags); -} - -inline Operation *createOperation(Operation::Kind kind, QQmlJS::MemoryPool *staticPool) -{ - auto get = [&](uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, - uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, - Type (*typeCreator)(), uint8_t flags) { - return Operation::create(staticPool, kind, inValueCount, inEffectCount, inControlCount, - outValueCount, outEffectCount, outControlCount, typeCreator(), - flags); - }; - - using K = Operation::Kind; - using F = Operation::Flags; - const auto none = &Type::noneType; - const auto any = &Type::anyType; - const auto number = &Type::numberType; - const auto boolean = &Type::booleanType; - - switch (kind) { - case K::Undefined: - return OperationWithPayload::create( - staticPool, K::Undefined, 0, 0, 0, 1, 0, 0, Type::undefinedType(), F::NoFlags, - ConstantPayload(Primitive::undefinedValue())); - case K::Empty: - return OperationWithPayload::create( - staticPool, K::Constant, 0, 0, 0, 1, 0, 0, Type::emptyType(), Operation::NoFlags, - ConstantPayload(Primitive::emptyValue())); - case K::Engine: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); - case K::CppFrame: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); - case K::Function: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); - case K::Jump: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); - case K::Return: return get(1, 1, 1, 0, 0, 1, none, F::NoFlags); - case K::Branch: return get(1, 0, 1, 0, 0, 2, none, F::HasFrameStateInput | F::NeedsBytecodeOffsets); - case K::IfTrue: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); - case K::IfFalse: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); - case K::SelectOutput: return get(3, 1, 1, 1, 1, 1, any, F::NoFlags); - case K::Throw: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); - case K::OnException: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); - case K::ThrowReferenceError: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); - case K::UnwindToLabel: return get(2, 1, 1, 0, 1, 1, none, F::NoFlags); - case K::LoadRegExp: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); - case K::ScopedLoad: return get(2, 1, 0, 1, 1, 0, any, F::NoFlags); - case K::ScopedStore: return get(3, 1, 0, 0, 1, 0, none, F::NoFlags); - case K::JSLoadElement: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSGetLookup: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSLoadProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSStoreElement: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSSetLookupStrict: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSSetLookupSloppy: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSStoreProperty: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSLoadName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSLoadGlobalLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSStoreNameSloppy: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSStoreNameStrict: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSLoadSuperProperty: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSStoreSuperProperty: return get(2, 1, 1, 0, 1, 2, any, F::CanThrow); - case K::JSLoadClosure: return get(1, 1, 0, 1, 1, 0, any, F::Pure); - case K::JSGetIterator: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - - // special case: see GraphBuilder::generate_IteratorNext - case K::JSIteratorNext: return get(2, 1, 1, 2, 1, 1, any, F::NoFlags); - - // special case: see GraphBuilder::generate_IteratorNext - case K::JSIteratorNextForYieldStar: return get(3, 1, 1, 2, 1, 1, any, F::NoFlags); - - case K::JSIteratorClose: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSDeleteProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSDeleteName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSIn: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSInstanceOf: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::QMLLoadQmlContextPropertyLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - - case K::JSEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - case K::JSGreaterThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - case K::JSGreaterEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - case K::JSLessThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - case K::JSLessEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - case K::JSStrictEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); - - case K::JSAdd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSSubtract: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::JSMultiply: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::JSDivide: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::JSModulo: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::JSExponentiate: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); - - case K::JSBitAnd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSBitOr: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSBitXor: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSUnsignedShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSShiftLeft: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - - case K::JSNegate: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::JSToNumber: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); - case K::Alloca: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); - - //### it is questionable if VAAlloc/VASeal need effect edges - case K::VAAlloc: return get(1, 1, 0, 1, 1, 0, none, F::NoFlags); - - case K::VAStore: return get(3, 0, 0, 1, 0, 0, none, F::NoFlags); - - case K::JSTypeofName: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); - case K::JSTypeofValue: return get(1, 0, 0, 1, 0, 0, any, F::Pure); - case K::JSDeclareVar: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::JSDestructureRestElement: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - - case K::JSCreateCallContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); - case K::JSCreateCatchContext: return get(2, 1, 1, 1, 1, 1, none, F::NoFlags); - case K::JSCreateWithContext: return get(1, 1, 1, 1, 1, 1, any, F::NoFlags); - case K::JSCreateBlockContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); - case K::JSCloneBlockContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); - case K::JSCreateScriptContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); - case K::JSPopScriptContext: return get(0, 1, 1, 1, 1, 1, none, F::NoFlags); - case K::PopContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); - - case K::JSThisToObject: return get(1, 1, 1, 0, 1, 2, any, F::NoFlags); - case K::JSCreateMappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); - case K::JSCreateUnmappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); - case K::JSCreateRestParameter: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); - case K::JSLoadSuperConstructor: return get(1, 1, 1, 1, 1, 2, any, F::NoFlags); - case K::JSThrowOnNullOrUndefined: return get(1, 1, 1, 0, 1, 2, none, F::CanThrow); - case K::JSGetTemplateObject: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); - case K::StoreThis: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); - - case K::GetException: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); - case K::SetException: return get(1, 1, 0, 0, 1, 0, any, F::NoFlags); - - case K::ToObject: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); - case K::ToBoolean: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); - - case K::IsEmpty: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); - - case K::BooleanNot: return get(1, 0, 0, 1, 0, 0, boolean, F::NoFlags); - case K::HasException: return get(1, 1, 0, 1, 1, 0, boolean, F::NoFlags); - - case K::Swap: return get(0, 0, 0, 0, 0, 0, none, F::NoFlags); - case K::Move: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); - - default: // Non-static operations: - return nullptr; - } -} - -Operation *OperationBuilder::staticOperation(Operation::Kind kind) -{ - static QAtomicPointer ops; - if (Operation **staticOps = ops.load()) - return staticOps[kind]; - - static QAtomicInt initializing = 0; - if (initializing.testAndSetOrdered(0, 1)) { - // This is safe now, because we can only run this piece of code once during the life time - // of the application as we can only change initializing from 0 to 1 once. - Operation **staticOps = new Operation *[Meta::KindsEnd]; - static QQmlJS::MemoryPool pool; - for (int i = 0; i < Meta::KindsEnd; ++i) - staticOps[i] = createOperation(Operation::Kind(i), &pool); - bool success = ops.testAndSetOrdered(nullptr, staticOps); - Q_ASSERT(success); - } else { - // Unfortunately we need to busy wait now until the other thread finishes the static - // initialization; - while (!ops.load()) {} - } - - return ops.load()[kind]; -} - -Operation *OperationBuilder::getJSVarArgsCall(Operation::Kind kind, uint16_t argc) -{ - return Operation::create(m_graphPool, kind, - argc, 1, 1, 1, 1, 2, - Type::anyType(), Operation::CanThrow); -} - -Operation *OperationBuilder::getJSTailCall(uint16_t argc) -{ - return Operation::create(m_graphPool, Meta::JSTailCall, - argc, 1, 1, 0, 0, 1, - Type(), Operation::NoFlags); -} - -Operation *OperationBuilder::getTailCall() -{ - // special varargs call, takes cppframe, engine, func, thisObject, argv, argc - return Operation::create(m_graphPool, Meta::TailCall, - 6, 1, 1, 0, 0, 1, - Type(), Operation::NoFlags); -} - -Operation *OperationBuilder::getCall(Operation::Kind callee) -{ - const bool canThrow = CallPayload::canThrow(callee); - const Type retTy = CallPayload::returnType(callee); - uint16_t nControlInputs = 0; - uint16_t nControlOutputs = 0; - if (canThrow) { - nControlInputs = 1; - nControlOutputs += 2; - } - if (CallPayload::changesContext(callee)) { - nControlInputs = 1; - nControlOutputs = std::max(nControlInputs, 1); - } - if (callee == Meta::Throw || callee == Meta::ThrowReferenceError || - callee == Meta::JSIteratorNext || callee == Meta::JSIteratorNextForYieldStar) { - nControlInputs = 1; - nControlOutputs = 1; - } - Operation::Flags flags = Operation::NoFlags; - if (canThrow) - flags = Operation::Flags(flags | Operation::CanThrow); - if (CallPayload::isPure(callee)) - flags = Operation::Flags(flags | Operation::Pure); - const uint16_t nEffects = (flags & Operation::Pure) ? 0 : 1; - const uint16_t nValueOutputs = retTy.isNone() ? 0 : 1; - const uint16_t nValueInputs = CallPayload::argc(callee); - - return OperationWithPayload::create( - m_graphPool, Meta::Call, - nValueInputs, nEffects, nControlInputs, - nValueOutputs, nEffects, nControlOutputs, - retTy, flags, - CallPayload(callee)); -} - -Operation *OperationBuilder::getVASeal(uint16_t nElements) -{ - return Operation::create(m_graphPool, Meta::VASeal, - nElements + 1, 1, 0, 1, 1, 0, - Type::anyType(), Operation::NoFlags); -} - -QString Operation::debugString() const -{ - switch (kind()) { - - case Meta::Constant: - return QStringLiteral("Constant[%1]").arg(ConstantPayload::get(*this)->debugString()); - case Meta::Parameter: - return QStringLiteral("Parameter[%1]").arg(ParameterPayload::get(*this)->debugString()); - case Meta::Call: - return QStringLiteral("Call[%1]").arg(CallPayload::get(*this)->debugString()); - case Meta::UnwindDispatch: - return QStringLiteral("UnwindDispatch[%1]").arg(UnwindDispatchPayload::get(*this) - ->debugString()); - case Meta::HandleUnwind: - return QStringLiteral("HandleUnwind[%1]").arg(HandleUnwindPayload::get(*this) - ->debugString()); - - default: - return QString::fromLatin1(QMetaEnum::fromType().valueToKey(kind())); - } -} - -QString ConstantPayload::debugString() const -{ - return debugString(m_value); -} - -QString ConstantPayload::debugString(QV4::Value v) -{ - if (v.isManaged()) - return QString::asprintf("Ptr: %p", v.heapObject()); - if (v.isEmpty()) - return QStringLiteral("empty"); - return v.toQStringNoThrow(); -} - -QString ParameterPayload::debugString() const -{ - return QStringLiteral("%1").arg(m_index); -} - -QString UnwindDispatchPayload::debugString() const -{ - return QStringLiteral("%1, %2").arg(QString::number(m_fallthroughSuccessor), - QString::number(m_unwindHandlerOffset)); -} - -QString HandleUnwindPayload::debugString() const -{ - return QStringLiteral("%1").arg(m_unwindHandlerOffset); -} - -static Type translateType(RuntimeSupport::ArgumentType t) -{ - switch (t) { - case RuntimeSupport::ArgumentType::Int: return Type::int32Type(); - case RuntimeSupport::ArgumentType::Bool: return Type::booleanType(); - case RuntimeSupport::ArgumentType::Void: return Type(); - case RuntimeSupport::ArgumentType::Engine: return Type::rawPointerType(); - case RuntimeSupport::ArgumentType::ValueRef: return Type::anyType(); - case RuntimeSupport::ArgumentType::ValueArray: return Type::anyType(); - case RuntimeSupport::ArgumentType::ReturnedValue: return Type::anyType(); - default: Q_UNREACHABLE(); - } -} - -template class M /* MetaOperation */, typename ReturnValue> -static ReturnValue operateOnRuntimeCall(Operation::Kind kind, bool abortOnMissingCall = true) -{ - using K = Operation::Kind; - using R = Runtime; - - switch (kind) { - case K::Throw: return M::doIt(); - case K::ThrowReferenceError: return M::doIt(); - - case K::JSEqual: return M::doIt(); - case K::JSGreaterThan: return M::doIt(); - case K::JSGreaterEqual: return M::doIt(); - case K::JSLessThan: return M::doIt(); - case K::JSLessEqual: return M::doIt(); - case K::JSStrictEqual: return M::doIt(); - - case K::JSBitAnd: return M::doIt(); - case K::JSBitOr: return M::doIt(); - case K::JSBitXor: return M::doIt(); - case K::JSUnsignedShiftRight: return M::doIt(); - case K::JSShiftRight: return M::doIt(); - case K::JSShiftLeft: return M::doIt(); - - case K::JSAdd: return M::doIt(); - case K::JSSubtract: return M::doIt(); - case K::JSMultiply: return M::doIt(); - case K::JSDivide: return M::doIt(); - case K::JSModulo: return M::doIt(); - case K::JSExponentiate: return M::doIt(); - - case K::ToBoolean: return M::doIt(); - case K::ToObject: return M::doIt(); - - case K::JSNegate: return M::doIt(); - case K::JSToNumber: return M::doIt(); - - case K::JSLoadName: return M::doIt(); - case K::JSLoadElement: return M::doIt(); - case K::JSStoreElement: return M::doIt(); - case K::JSGetLookup: return M::doIt(); - case K::JSSetLookupStrict: return M::doIt(); - case K::JSSetLookupSloppy: return M::doIt(); - case K::JSLoadProperty: return M::doIt(); - case K::JSStoreProperty: return M::doIt(); - case K::JSLoadGlobalLookup: return M::doIt(); - case K::JSStoreNameSloppy: return M::doIt(); - case K::JSStoreNameStrict: return M::doIt(); - case K::JSLoadSuperProperty: return M::doIt(); - case K::JSStoreSuperProperty: return M::doIt(); - case K::JSLoadClosure: return M::doIt(); - case K::JSGetIterator: return M::doIt(); - case K::JSIteratorNext: return M::doIt(); - case K::JSIteratorNextForYieldStar: return M::doIt(); - case K::JSIteratorClose: return M::doIt(); - case K::JSDeleteProperty: return M::doIt(); - case K::JSDeleteName: return M::doIt(); - case K::JSIn: return M::doIt(); - case K::JSInstanceOf: return M::doIt(); - case K::QMLLoadQmlContextPropertyLookup: return M::doIt(); - - case K::JSTypeofName: return M::doIt(); - case K::JSTypeofValue: return M::doIt(); - case K::JSDeclareVar: return M::doIt(); - case K::JSDestructureRestElement: return M::doIt(); - case K::JSThisToObject: return M::doIt(); - case K::JSCreateMappedArgumentsObject: return M::doIt(); - case K::JSCreateUnmappedArgumentsObject: return M::doIt(); - case K::JSCreateRestParameter: return M::doIt(); - case K::JSLoadSuperConstructor: return M::doIt(); - case K::JSThrowOnNullOrUndefined: return M::doIt(); - - case K::JSCreateCallContext: return M::doIt(); - case K::JSCreateCatchContext: return M::doIt(); - case K::JSCreateWithContext: return M::doIt(); - case K::JSCreateBlockContext: return M::doIt(); - case K::JSCloneBlockContext: return M::doIt(); - case K::JSCreateScriptContext: return M::doIt(); - case K::JSPopScriptContext: return M::doIt(); - - case K::LoadRegExp: return M::doIt(); - case K::JSGetTemplateObject: return M::doIt(); - - case K::JSCallName: return M::doIt(); - case K::JSCallValue: return M::doIt(); - case K::JSCallElement: return M::doIt(); - case K::JSCallLookup: return M::doIt(); - case K::JSCallProperty: return M::doIt(); - case K::JSCallGlobalLookup: return M::doIt(); - case K::JSCallPossiblyDirectEval: return M::doIt(); - case K::JSCallWithReceiver: return M::doIt(); - case K::JSDefineObjectLiteral: return M::doIt(); - case K::JSDefineArray: return M::doIt(); - case K::JSCallWithSpread: return M::doIt(); - case K::JSConstruct: return M::doIt(); - case K::JSConstructWithSpread: return M::doIt(); - case K::JSTailCall: return M::doIt(); - case K::JSCreateClass: return M::doIt(); - default: - if (abortOnMissingCall) - Q_UNREACHABLE(); - else - return ReturnValue(); - } -} - -template -struct IsRuntimeMethodOperation -{ - static constexpr bool doIt() { return true; } -}; - -bool CallPayload::isRuntimeCall(Operation::Kind m) -{ - return operateOnRuntimeCall(m, false); -} - -QString CallPayload::debugString() const -{ - return QString::fromLatin1(QMetaEnum::fromType().valueToKey(m_callee)); -} - -template -struct MethodArgcOperation -{ - static constexpr unsigned doIt() { return RuntimeSupport::argumentCount(); } -}; - -unsigned CallPayload::argc(Operation::Kind callee) -{ - return operateOnRuntimeCall(callee); -} - -template struct MethodArg1TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg1Type(); } }; -template struct MethodArg2TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg2Type(); } }; -template struct MethodArg3TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg3Type(); } }; -template struct MethodArg4TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg4Type(); } }; -template struct MethodArg5TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg5Type(); } }; -template struct MethodArg6TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg6Type(); } }; - -static RuntimeSupport::ArgumentType untranslatedArgumentType(Operation::Kind m, unsigned arg) -{ - if (m == Meta::JSTailCall) { - if (arg < 4) - return RuntimeSupport::ArgumentType::ValueRef; - else - return RuntimeSupport::ArgumentType::Invalid; - } - - switch (arg) { - case 0: return operateOnRuntimeCall(m); - case 1: return operateOnRuntimeCall(m); - case 2: return operateOnRuntimeCall(m); - case 3: return operateOnRuntimeCall(m); - case 4: return operateOnRuntimeCall(m); - case 5: return operateOnRuntimeCall(m); - default: return RuntimeSupport::ArgumentType::Invalid; - } -} - -bool CallPayload::needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, - Type nodeType) -{ - auto argTy = untranslatedArgumentType(m, arg); - if (argTy == RuntimeSupport::ArgumentType::ValueArray) - return true; - if (argTy != RuntimeSupport::ArgumentType::ValueRef) - return false; - - if (op->kind() == Meta::Constant) - return true; - - return !nodeType.isObject() && !nodeType.isRawPointer() && !nodeType.isAny(); -} - -template -struct MethodRetTyOperation -{ - static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::retType(); } -}; - -Type CallPayload::returnType(Operation::Kind m) -{ - if (m == Meta::JSTailCall) - return Type(); - - auto t = operateOnRuntimeCall(m); - return translateType(t); -} - -static int firstArgumentPositionForType(Operation::Kind m, RuntimeSupport::ArgumentType type) -{ - if (operateOnRuntimeCall(m) == type) - return 1; - if (operateOnRuntimeCall(m) == type) - return 2; - if (operateOnRuntimeCall(m) == type) - return 3; - if (operateOnRuntimeCall(m) == type) - return 4; - if (operateOnRuntimeCall(m) == type) - return 5; - if (operateOnRuntimeCall(m) == type) - return 6; - return -1; -} - -unsigned CallPayload::varArgsStart(Operation::Kind m) -{ - if (m == Meta::JSTailCall) - return 4 - 1; - - int pos = firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) - 1; - Q_ASSERT(pos >= 0); - return pos; -} - -bool CallPayload::isVarArgsCall(Operation::Kind m) -{ - if (m == Meta::JSTailCall) - return true; - if (lastArgumentIsOutputValue(m)) - return false; - return firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) != -1; -} - -bool CallPayload::isVarArgsCall() const -{ - return isVarArgsCall(m_callee); -} - -template -struct MethodsLastArgumentIsOutputValue -{ - static constexpr bool doIt() { return Method::lastArgumentIsOutputValue; } -}; - -bool CallPayload::lastArgumentIsOutputValue(Operation::Kind m) -{ - return operateOnRuntimeCall(m); -} - -template -struct MethodChangesContext -{ - static constexpr bool doIt() { return Method::changesContext; } -}; - -bool CallPayload::changesContext(Operation::Kind m) -{ - return operateOnRuntimeCall(m); -} - -template -struct MethodIsPure -{ - static constexpr bool doIt() { return Method::pure; } -}; - -bool CallPayload::isPure(Operation::Kind m) -{ - return operateOnRuntimeCall(m); -} - -template -struct MethodCanThrow -{ - static constexpr bool doIt() { return Method::throws; } -}; - -bool CallPayload::canThrow(Operation::Kind m) -{ - switch (m) { - case Meta::Throw: Q_FALLTHROUGH(); - case Meta::ThrowReferenceError: - // the execution path following these instructions is already linked up to the exception handler - return false; - case Meta::JSIteratorNext: Q_FALLTHROUGH(); - case Meta::JSIteratorNextForYieldStar: - // special case: see GraphBuilder::generate_IteratorNext - return false; - default: - return operateOnRuntimeCall(m); - } -} - -bool CallPayload::takesEngineAsArg(Operation::Kind m, int arg) -{ - return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Engine; -} - -bool CallPayload::takesFunctionAsArg(Operation::Kind m, int arg) -{ - return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Function; -} - -bool CallPayload::takesFrameAsArg(Operation::Kind m, int arg) -{ - return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Frame; -} - -template -struct GetMethodPtr -{ - static constexpr void *doIt() { return reinterpret_cast(&Method::call); } -}; - -void *CallPayload::getMethodPtr(Operation::Kind m) -{ - return operateOnRuntimeCall(m); -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4operation_p.h b/src/qml/jit/qv4operation_p.h deleted file mode 100644 index 43214023e8..0000000000 --- a/src/qml/jit/qv4operation_p.h +++ /dev/null @@ -1,567 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4OPERATION_P_H -#define QV4OPERATION_P_H - -// -// 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. -// - -#include -#include - -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -namespace Meta { -enum OpKind: uint16_t { - FrameState, - Start, - End, - - Undefined, - Constant, - Parameter, - Empty, - Engine, - CppFrame, - Function, - - Jump, - Return, - JSTailCall, - TailCall, - Branch, - IfTrue, - IfFalse, - Region, - OnException, - Phi, - EffectPhi, - SelectOutput, - UnwindDispatch, - UnwindToLabel, - HandleUnwind, - Throw, - ThrowReferenceError, - - Call, - - LoadRegExp, - ScopedLoad, - ScopedStore, - - JSLoadElement, - JSStoreElement, - JSGetLookup, - JSSetLookupStrict, - JSSetLookupSloppy, - JSLoadProperty, - JSStoreProperty, - JSLoadName, - JSLoadGlobalLookup, - JSStoreNameSloppy, - JSStoreNameStrict, - JSLoadSuperProperty, - JSStoreSuperProperty, - JSLoadClosure, - JSGetIterator, - JSIteratorNext, - JSIteratorNextForYieldStar, - JSIteratorClose, - JSDeleteProperty, - JSDeleteName, - JSIn, - JSInstanceOf, - - /* ok, these are qml object ops, but we don't care for now and treat them as JS */ - QMLLoadQmlContextPropertyLookup, - QMLCallQmlContextPropertyLookup, - - JSEqual, - JSGreaterThan, - JSGreaterEqual, - JSLessThan, - JSLessEqual, - JSStrictEqual, - - JSAdd, - JSSubtract, - JSMultiply, - JSDivide, - JSModulo, - JSExponentiate, - - JSBitAnd, - JSBitOr, - JSBitXor, - JSUnsignedShiftRight, - JSShiftRight, - JSShiftLeft, - - JSNegate, - JSToNumber, - - JSCallName, - JSCallValue, - JSCallElement, - JSCallProperty, - JSCallLookup, - JSCallGlobalLookup, - JSCallPossiblyDirectEval, - JSCallWithReceiver, - JSCallWithSpread, - JSDefineObjectLiteral, - JSDefineArray, - JSCreateClass, - JSConstruct, - JSConstructWithSpread, - - JSTypeofName, - JSTypeofValue, - JSDeclareVar, - JSDestructureRestElement, - JSThisToObject, - JSCreateMappedArgumentsObject, - JSCreateUnmappedArgumentsObject, - JSCreateRestParameter, - JSLoadSuperConstructor, - JSThrowOnNullOrUndefined, - JSGetTemplateObject, - StoreThis, - - JSCreateCallContext, - JSCreateCatchContext, - JSCreateWithContext, - JSCreateBlockContext, - JSCloneBlockContext, - JSCreateScriptContext, - JSPopScriptContext, - PopContext, - - GetException, - SetException, - - ToObject, - ToBoolean, - - //### do we need this? Or should a later phase generate JumpIsEmpty? - IsEmpty, - - Alloca, - VAAlloc, - VAStore, - VASeal, - - BooleanNot, - HasException, - - // Low level, used by the register allocator and stack allocator: - Swap, - Move, - KindsEnd -}; -Q_NAMESPACE -Q_ENUM_NS(OpKind) -} // namespace Ops - -class Operation -{ - Q_DISABLE_COPY_MOVE(Operation) - -public: - using Kind = Meta::OpKind; - - enum Flags: uint8_t { - NoFlags = 0, - ThrowsFlag = 1 << 0, - Pure = 1 << 1, // no read/write side effect, cannot throw, cannot deopt, and is idempotent - NeedsBytecodeOffsets = 1 << 2, - - CanThrow = ThrowsFlag | NeedsBytecodeOffsets, - - HasFrameStateInput = 1 << 3, - }; - -public: - static Operation *create(QQmlJS::MemoryPool *pool, Kind kind, uint16_t inValueCount, - uint16_t inEffectCount, uint16_t inControlCount, - uint16_t outValueCount, uint16_t outEffectCount, - uint16_t outControlCount, Type type, uint8_t flags) - { - return pool->New(kind, inValueCount, inEffectCount, inControlCount, - outValueCount, outEffectCount, outControlCount, - type, Flags(flags)); - } - - Kind kind() const - { return m_kind; } - - bool isConstant() const - { - switch (kind()) { - case Meta::Undefined: Q_FALLTHROUGH(); - case Meta::Constant: - case Meta::Empty: - return true; - default: - return false; - } - } - - QString debugString() const; - - uint16_t valueInputCount() const { return m_inValueCount; } - uint16_t effectInputCount() const { return m_inEffectCount; } - uint16_t controlInputCount() const { return m_inControlCount; } - uint16_t valueOutputCount() const { return m_outValueCount; } - uint16_t effectOutputCount() const { return m_outEffectCount; } - uint16_t controlOutputCount() const { return m_outControlCount; } - - unsigned indexOfFirstEffect() const { return m_inValueCount; } - unsigned indexOfFirstControl() const { return m_inValueCount + m_inEffectCount; } - unsigned indexOfFrameStateInput() const - { - return hasFrameStateInput() ? indexOfFirstControl() + m_inControlCount - : std::numeric_limits::max(); - } - - Type type() const - { return m_type; } - - bool canThrow() const - { return m_flags & ThrowsFlag; } - - bool isPure() const - { return m_flags & Pure; } - - bool needsBytecodeOffsets() const - { return m_flags & NeedsBytecodeOffsets; } - - bool hasFrameStateInput() const - { return m_flags & HasFrameStateInput; } - - unsigned totalInputCount() const - { - return valueInputCount() + effectInputCount() + controlInputCount() + - (hasFrameStateInput() ? 1 : 0); - } - unsigned totalOutputCount() const { return valueOutputCount() + effectOutputCount() + controlOutputCount(); } - -protected: - friend class QQmlJS::MemoryPool; - Operation(Kind kind, - uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, - uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, - Type type, uint8_t flags) - : m_kind(kind) - , m_inValueCount(inValueCount) - , m_inEffectCount(inEffectCount) - , m_inControlCount(inControlCount) - , m_outValueCount(outValueCount) - , m_outEffectCount(outEffectCount) - , m_outControlCount(outControlCount) - , m_type(type) - , m_flags(Flags(flags)) - { - } - - ~Operation() = default; - -private: - Kind m_kind; - uint16_t m_inValueCount; - uint16_t m_inEffectCount; - uint16_t m_inControlCount; - uint16_t m_outValueCount; - uint16_t m_outEffectCount; - uint16_t m_outControlCount; - Type m_type; - Flags m_flags; -}; - -template -class OperationWithPayload: public Operation -{ -public: - static OperationWithPayload *create(QQmlJS::MemoryPool *pool, Kind kind, - uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, - uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, - Type type, Flags flags, Payload payload) - { - return pool->New(kind, inValueCount, inEffectCount, inControlCount, - outValueCount, outEffectCount, outControlCount, - type, flags, payload); - } - - const Payload &payload() const - { return m_payload; } - -protected: - friend class QQmlJS::MemoryPool; - OperationWithPayload(Kind kind, - uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, - uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, - Type type, Flags flags, Payload payload) - : Operation(kind, - inValueCount, inEffectCount, inControlCount, - outValueCount, outEffectCount, outControlCount, - type, flags) - , m_payload(payload) - {} - - ~OperationWithPayload() = default; - -private: - Payload m_payload; -}; - -class ConstantPayload -{ -public: - explicit ConstantPayload(QV4::Value v) - : m_value(v) - {} - - QV4::Value value() const - { return m_value; } - - static const ConstantPayload *get(const Operation &op) - { - if (op.kind() != Meta::Constant) - return nullptr; - - return &static_cast&>(op).payload(); - } - - QString debugString() const; - static QString debugString(QV4::Value v); - -private: - QV4::Value m_value; -}; - -class ParameterPayload -{ -public: - ParameterPayload(size_t index, Function::StringId stringId) - : m_index(index) - , m_stringId(stringId) - {} - - size_t parameterIndex() const - { return m_index; } - - Function::StringId stringId() const - { return m_stringId; } - - static const ParameterPayload *get(const Operation &op) - { - if (op.kind() != Meta::Parameter) - return nullptr; - - return &static_cast&>(op).payload(); - } - - QString debugString() const; - -private: - size_t m_index; - Function::StringId m_stringId; -}; - -class CallPayload -{ -public: - CallPayload(Operation::Kind callee) - : m_callee(callee) - {} - - static const CallPayload *get(const Operation &op) - { - if (op.kind() != Meta::Call) - return nullptr; - - return &static_cast&>(op).payload(); - } - - static bool isRuntimeCall(Operation::Kind m); - - Operation::Kind callee() const { return m_callee; } - QString debugString() const; - - unsigned argc() const { return argc(m_callee); } - static unsigned argc(Operation::Kind callee); - static bool needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, - Type nodeType); - static Type returnType(Operation::Kind m); - static int engineArgumentPosition(Operation::Kind m); - static int functionArgumentPosition(Operation::Kind m); - - static constexpr unsigned NoVarArgs = std::numeric_limits::max(); - static unsigned varArgsStart(Operation::Kind m); - static bool isVarArgsCall(Operation::Kind m); - bool isVarArgsCall() const; - static bool lastArgumentIsOutputValue(Operation::Kind m); - static bool changesContext(Operation::Kind m); - static bool isPure(Operation::Kind m); - static bool canThrow(Operation::Kind m); - static bool takesEngineAsArg(Operation::Kind m, int arg); - static bool takesFunctionAsArg(Operation::Kind m, int arg); - static bool takesFrameAsArg(Operation::Kind m, int arg); - static void *getMethodPtr(Operation::Kind m); - -private: - Operation::Kind m_callee; -}; - -class UnwindDispatchPayload -{ -public: - UnwindDispatchPayload(int unwindHandlerOffset, int fallthroughSuccessor) - : m_unwindHandlerOffset(unwindHandlerOffset) - , m_fallthroughSuccessor(fallthroughSuccessor) - {} - - int unwindHandlerOffset() const - { return m_unwindHandlerOffset; } - - int fallthroughSuccessor() const //### unused... - { return m_fallthroughSuccessor; } - - static const UnwindDispatchPayload *get(const Operation &op) - { - if (op.kind() != Meta::UnwindDispatch) - return nullptr; - - return &static_cast&>(op).payload(); - } - - QString debugString() const; - -private: - int m_unwindHandlerOffset; - int m_fallthroughSuccessor; -}; - -class HandleUnwindPayload -{ -public: - HandleUnwindPayload(int unwindHandlerOffset) - : m_unwindHandlerOffset(unwindHandlerOffset) - {} - - int unwindHandlerOffset() const - { return m_unwindHandlerOffset; } - - static const HandleUnwindPayload *get(const Operation &op) - { - if (op.kind() != Meta::HandleUnwind) - return nullptr; - - return &static_cast&>(op).payload(); - } - - QString debugString() const; - -private: - int m_unwindHandlerOffset; -}; - -class OperationBuilder -{ - Q_DISABLE_COPY_MOVE(OperationBuilder) - - friend class QQmlJS::MemoryPool; - OperationBuilder(QQmlJS::MemoryPool *graphPool); - -public: - static OperationBuilder *create(QQmlJS::MemoryPool *pool); - ~OperationBuilder() = delete; - - Operation *getConstant(QV4::Value v); - Operation *getFrameState(uint16_t frameSize); - Operation *getStart(uint16_t outputCount); - Operation *getEnd(uint16_t controlInputCount); - Operation *getParam(unsigned index, Function::StringId name); - Operation *getRegion(unsigned nControlInputs); - Operation *getPhi(unsigned nValueInputs); - Operation *getEffectPhi(unsigned nEffectInputs); - Operation *getUnwindDispatch(unsigned nControlOutputs, int unwindHandlerOffset, int fallthroughSuccessor); - Operation *getHandleUnwind(int unwindHandlerOffset); - - template - Operation *get() { - return staticOperation(kind); - } - - Operation *getVASeal(uint16_t nElements); - - Operation *getJSVarArgsCall(Operation::Kind kind, uint16_t argc); - Operation *getJSTailCall(uint16_t argc); - Operation *getTailCall(); - - Operation *getCall(Operation::Kind callee); - -private: - QQmlJS::MemoryPool *m_graphPool; // used to store per-graph nodes - Operation *m_opFrameState = nullptr; - static Operation *staticOperation(Operation::Kind kind); -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4OPERATION_P_H diff --git a/src/qml/jit/qv4runtimesupport_p.h b/src/qml/jit/qv4runtimesupport_p.h deleted file mode 100644 index 0dc6022331..0000000000 --- a/src/qml/jit/qv4runtimesupport_p.h +++ /dev/null @@ -1,255 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4RUNTIMESUPPORT_P_H -#define QV4RUNTIMESUPPORT_P_H - -// -// 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. -// - -#include - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { -namespace RuntimeSupport { - -template -struct CountArguments { - static constexpr unsigned count = 0; -}; -template -struct CountArguments { - static constexpr unsigned count = sizeof...(Args) ; -}; - -template -static constexpr unsigned argumentCount() { - using type = decltype(&M::call); - return CountArguments::count; -} - -enum class ArgumentType { - Invalid, - Engine, - Frame, - Function, - ValueRef, - ValueArray, - ReturnedValue, - Int, - Bool, - Void, -}; - - -template -struct JavaScriptType -{ - // No default type. We want to make sure everything we do is actually recognized. -}; - -template -struct ReturnValue -{ - // No default type. -}; - -template -struct Argument -{ - // For simplicity, we add a default here. Otherwise we would need to spell out more - // combinations of I and number of arguments of T. - static constexpr ArgumentType type = ArgumentType::Invalid; -}; - -template -struct Argument<1, RetTy (*)(T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct Argument<2, RetTy (*)(Arg1, T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct Argument<3, RetTy (*)(Arg1, Arg2, T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct Argument<4, RetTy (*)(Arg1, Arg2, Arg3, T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct Argument<5, RetTy (*)(Arg1, Arg2, Arg3, Arg4, T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct Argument<6, RetTy (*)(Arg1, Arg2, Arg3, Arg4, Arg5, T, Args... args)> { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template -struct ReturnValue { - static constexpr ArgumentType type = JavaScriptType::type; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Engine; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Frame; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Function; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::ValueRef; -}; - -template<> -// We need to pass Value * in order to match a parmeter Value[]. -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::ValueArray; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Int; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Bool; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::ReturnedValue; -}; - -template<> -struct JavaScriptType -{ - static constexpr ArgumentType type = ArgumentType::Void; -}; - -template -static constexpr ArgumentType retType() { - using Type = decltype(&M::call); - return ReturnValue::type; -} - -template -static constexpr ArgumentType arg1Type() { - using Type = decltype(&M::call); - return Argument<1, Type>::type; -} - -template -static constexpr ArgumentType arg2Type() { - using Type = decltype(&M::call); - return Argument<2, Type>::type; -} - -template -static constexpr ArgumentType arg3Type() { - using Type = decltype(&M::call); - return Argument<3, Type>::type; -} - -template -static constexpr ArgumentType arg4Type() { - using Type = decltype(&M::call); - return Argument<4, Type>::type; -} - -template -static constexpr ArgumentType arg5Type() { - using Type = decltype(&M::call); - return Argument<5, Type>::type; -} - -template -static constexpr ArgumentType arg6Type() { - using Type = decltype(&M::call); - return Argument<6, Type>::type; -} - -} // namespace RuntimeSupport -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4RUNTIMESUPPORT_P_H diff --git a/src/qml/jit/qv4schedulers.cpp b/src/qml/jit/qv4schedulers.cpp deleted file mode 100644 index 0dffefa951..0000000000 --- a/src/qml/jit/qv4schedulers.cpp +++ /dev/null @@ -1,912 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4schedulers_p.h" -#include "qv4util_p.h" -#include "qv4graph_p.h" -#include "qv4blockscheduler_p.h" -#include "qv4stackframe_p.h" - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace IR { - -Q_LOGGING_CATEGORY(lcSched, "qt.v4.ir.scheduling") -Q_LOGGING_CATEGORY(lcDotCFG, "qt.v4.ir.scheduling.cfg") - -static bool needsScheduling(Node *n) -{ - if (n->operation()->isConstant()) - return false; - switch (n->opcode()) { - case Meta::Function: Q_FALLTHROUGH(); - case Meta::CppFrame: - case Meta::Phi: - case Meta::EffectPhi: - return false; - default: - return true; - } -} - -bool NodeScheduler::canStartBlock(Node *node) const -{ - switch (node->operation()->kind()) { - case Meta::Start: Q_FALLTHROUGH(); - case Meta::IfTrue: - case Meta::IfFalse: - case Meta::Region: - case Meta::HandleUnwind: - case Meta::OnException: - return true; - - default: - return false; - } -} - -bool NodeScheduler::isControlFlowSplit(Node *node) const -{ - int nOutputs = node->operation()->controlOutputCount(); - if (nOutputs == 2) { - // if there is a "missing" control output, it's for exception flow without unwinder - int controlUses = 0; - auto uses = node->uses(); - for (auto it = uses.begin(), eit = uses.end(); it != eit; ++it) { - if (isLive(*it) && it.isUsedAsControl()) - ++controlUses; - } - return controlUses == 2; - } - return nOutputs > 2; -} - -bool NodeScheduler::isBlockTerminator(Node *node) const -{ - switch (node->operation()->kind()) { - case Meta::Branch: Q_FALLTHROUGH(); - case Meta::Jump: - case Meta::Return: - case Meta::TailCall: - case Meta::UnwindDispatch: - case Meta::End: - return true; - case Meta::Call: - return isControlFlowSplit(node); - default: - return false; - } -} - -MIBlock *NodeScheduler::getCommonDominator(MIBlock *one, MIBlock *other) const -{ - MIBlock::Index a = one->index(); - MIBlock::Index b = other->index(); - - while (a != b) { - if (m_dominatorDepthForBlock[a] < m_dominatorDepthForBlock[b]) - b = m_domTree->immediateDominator(b); - else - a = m_domTree->immediateDominator(a); - } - - return m_miFunction->block(a); -} - -// For Nodes that end up inside loops, it'd be great if we can move (hoist) them out of the loop. -// To do that, we need a block that preceeds the loop. (So the block before the loop header.) -// This function calculates that hoist block if the original block is in a loop. -MIBlock *NodeScheduler::getHoistBlock(MIBlock *block) const -{ - if (m_loopInfo->isLoopHeader(block)) - return m_miFunction->block(m_domTree->immediateDominator(block->index())); - - // make the loop header a candidate: - MIBlock *loopHeader = m_loopInfo->loopHeaderFor(block); - if (loopHeader == nullptr) - return nullptr; // block is not in a loop - - // And now the tricky part: block has to dominate all exits from the loop. If it does not do - // that, it meanse that there is an exit from the loop that can be reached before block. In - // that case, hoisting from "block" to "loopHeader" would mean there now is an extra calculation - // that is not needed for a certain loop exit. - for (MIBlock *outEdge : m_loopInfo->loopExitsForLoop(loopHeader)) { - if (getCommonDominator(block, outEdge) != block) - return nullptr; - } - - return m_miFunction->block(m_domTree->immediateDominator(loopHeader->index())); -} - -NodeScheduler::NodeScheduler(Function *irFunction) - : m_irFunction(irFunction) - , m_vregs(irFunction->graph()->nodeCount(), std::numeric_limits::max()) - , m_live(irFunction->graph(), /*collectUses =*/ false /* do explicitly NOT collect uses! */) -{ -} - -MIFunction *NodeScheduler::buildMIFunction() -{ - m_miFunction = new MIFunction(m_irFunction); - - // step 1: build the CFG - auto roots = buildCFG(); - m_miFunction->renumberBlocks(); - m_miFunction->dump(QStringLiteral("CFG after renumbering")); - - Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->index() - == MIFunction::StartBlockIndex); - Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->instructions().front().opcode() - == Meta::Start); - - // step 2: build the dominator tree - if (lcDotCFG().isDebugEnabled()) - dumpDotCFG(); - m_domTree.reset(new DominatorTree(m_miFunction)); - m_dominatorDepthForBlock = m_domTree->calculateNodeDepths(); - - // step 3: find loops - m_loopInfo.reset(new LoopInfo(*m_domTree.data())); - m_loopInfo->detectLoops(); - - // step 4: schedule early - scheduleEarly(roots); - showNodesByBlock(QStringLiteral("nodes per block after early scheduling")); - - // step 5: schedule late - scheduleLate(roots); - showNodesByBlock(QStringLiteral("nodes per block after late scheduling")); - - // step 6: schedule instructions in each block - scheduleNodesInBlock(); - - m_miFunction->dump(QStringLiteral("MI before block scheduling")); - - // step 7: order the basic blocks in the CFG - BlockScheduler blockScheduler(*m_domTree.data(), *m_loopInfo.data()); - m_miFunction->setBlockOrder(blockScheduler.scheduledBlockSequence()); - - // we're done - m_miFunction->renumberInstructions(); - m_miFunction->setVregCount(m_nextVReg); - m_miFunction->dump(QStringLiteral("MI after scheduling")); - return m_miFunction; -} - -static Node *splitEdge(Function *irFunction, Node *node, unsigned inputIndex) -{ - Graph *g = irFunction->graph(); - Node *in = node->input(inputIndex); - Node *region = g->createNode(g->opBuilder()->getRegion(1), &in, 1); - Node *jump = g->createNode(g->opBuilder()->get(), ®ion, 1); - - qCDebug(lcSched) << "splitting critical edge from node" << node->id() - << "to node" << node->input(inputIndex)->id() - << "by inserting jump node" << jump->id() - << "and region node" << region->id(); - - node->replaceInput(inputIndex, jump); - return jump; -} - -// See Chapter 6.3.1 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for -// a description of the algorithm. -std::vector NodeScheduler::buildCFG() -{ - std::vector roots; - roots.reserve(32); - NodeWorkList todo(m_irFunction->graph()); - - auto enqueueControlInputs = [this, &todo](Node *node) { - for (unsigned i = 0, ei = node->operation()->controlInputCount(); i != ei; ++i) { - const auto inputIndex = node->operation()->indexOfFirstControl() + i; - Node *input = node->input(inputIndex); - Q_ASSERT(input); - if (node->operation()->kind() == Meta::Region - && node->operation()->controlInputCount() > 1 - && isControlFlowSplit(input)) { - // critical edge! - input = splitEdge(m_irFunction, node, inputIndex); - m_live.markReachable(input); - m_live.markReachable(input->controlInput(0)); - } - if (!isBlockTerminator(input)) { - auto g = m_irFunction->graph(); - Node *jump = g->createNode(g->opBuilder()->get(), &input, 1); - node->replaceInput(inputIndex, jump); - m_live.markReachable(jump); - qCDebug(lcSched) << "inserting jump node" << jump->id() - << "between node" << node->id() - << "and node" << input->id(); - input = jump; - } - todo.enqueue(input); - } - }; - - // create the CFG by scheduling control dependencies that start/end blocks: - todo.enqueue(m_irFunction->graph()->endNode()); - while (Node *node = todo.dequeueNextNodeForVisiting()) { - Q_ASSERT(isBlockTerminator(node)); - - if (schedulerData(node)->minimumBlock) - continue; - - MIBlock *b = m_miFunction->addBlock(); - - qCDebug(lcSched) << "scheduling node" << node->id() << "as terminator for new block" - << b->index(); - b->instructions().push_front(createMIInstruction(node)); - placeFixed(node, b, Schedule); - roots.push_back(node); - - if (Node *framestate = node->frameStateInput()) { - placeFixed(framestate, b, DontSchedule); - qCDebug(lcSched) << ".. also scheduling framestate dependency node" << node->id() - << "in block" << b->index(); - } - - if (node->opcode() == Meta::End) { - enqueueControlInputs(node); - continue; - } - - while (true) { - Node *controlDependency = node->controlInput(0); - if (!controlDependency) - break; - if (todo.isVisited(controlDependency)) - break; - if (schedulerData(controlDependency)->isFixed) - break; - - if (controlDependency->opcode() == Meta::Start) { - qCDebug(lcSched) << "placing start node" << controlDependency->id() - << "in block" << b->index(); - handleStartNode(controlDependency, b); - placeFixed(controlDependency, b, Schedule); - roots.push_back(controlDependency); - break; // we're done with this block - } - if (isBlockTerminator(controlDependency)) { - qCDebug(lcSched) << "found terminator node" << controlDependency->id() - << "for another block, so finish block" << b->index(); - Node *merge = m_irFunction->graph()->createNode( - m_irFunction->graph()->opBuilder()->getRegion(1), &controlDependency, 1); - node->replaceInput(node->operation()->indexOfFirstControl(), merge); - addBlockStart(roots, merge, b); - placeFixed(merge, b, Schedule); - m_live.markReachable(merge); - todo.enqueue(controlDependency); - break; // we're done with this block - } - if (canStartBlock(controlDependency) - || schedulerData(controlDependency->controlInput())->isFixed) { - qCDebug(lcSched) << "found block start node" << controlDependency->id() - << "for this block, so finish block" << b->index(); - addBlockStart(roots, controlDependency, b); - placeFixed(controlDependency, b, Schedule); - roots.push_back(controlDependency); - enqueueControlInputs(controlDependency); - break; // we're done with this block - } - qCDebug(lcSched) << "skipping node" << controlDependency->id(); - node = controlDependency; - } - } - - // link the edges of the MIBlocks, and add basic-block arguments: - for (MIBlock *toBlock : m_miFunction->blocks()) { - Q_ASSERT(!toBlock->instructions().empty()); - MIInstr &instr = toBlock->instructions().front(); - Node *toNode = instr.irNode(); - const auto opcode = toNode->operation()->kind(); - if (opcode == Meta::Region) { - unsigned inputNr = 0; - for (Node *input : toNode->inputs()) { - MIBlock *fromBlock = schedulerData(input)->minimumBlock; - fromBlock->addOutEdge(toBlock); - toBlock->addInEdge(fromBlock); - MIInstr &fromTerminator = fromBlock->instructions().back(); - if (fromTerminator.irNode()->opcode() == Meta::Jump || - fromTerminator.irNode()->opcode() == Meta::UnwindDispatch) { - unsigned arg = 0; - for (const MIOperand &bbArg : toBlock->arguments()) { - fromTerminator.setOperand(arg++, - createMIOperand(bbArg.irNode()->input(inputNr))); - } - } - ++inputNr; - } - } else if (opcode == Meta::End) { - for (Node *input : toNode->inputs()) { - MIBlock *fromBlock = schedulerData(input)->minimumBlock; - fromBlock->addOutEdge(toBlock); - toBlock->addInEdge(fromBlock); - } - } else if (Node *fromNode = toNode->controlInput()) { - MIBlock *fromBlock = schedulerData(fromNode)->minimumBlock; - fromBlock->addOutEdge(toBlock); - toBlock->addInEdge(fromBlock); - } - } - - m_irFunction->dump(QStringLiteral("graph after building CFG")); - - auto startBlock = schedulerData(m_irFunction->graph()->startNode())->minimumBlock; - m_miFunction->setStartBlock(startBlock); - - if (lcSched().isDebugEnabled()) - m_miFunction->dump(QStringLiteral("control flow graph before renumbering")); - m_miFunction->verifyCFG(); - - return roots; -} - -// See Chapter 6.3.3 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for -// a description of the algorithm. -void NodeScheduler::scheduleEarly(const std::vector &roots) -{ - // scheduling one node might have the effect of queueing its dependencies - NodeWorkList todo(m_irFunction->graph()); - for (Node *root : roots) { - todo.enqueue(root); - while (Node *node = todo.dequeueNextNodeForVisiting()) - scheduleEarly(node, todo); - } -} - -void NodeScheduler::scheduleEarly(Node *node, NodeWorkList &todo) -{ - qCDebug(lcSched) << "Scheduling node" << node->id() << "early..."; - - SchedulerData *sd = schedulerData(node); - - if (sd->isFixed) { - // Fixed nodes already know their schedule early position. - qCDebug(lcSched) << ".. Fixed node" << node->id() << "is on minimum block" - << sd->minimumBlock->index() - << "which has dominator depth" - << m_dominatorDepthForBlock[sd->minimumBlock->index()]; - } - - for (Node *use : node->uses()) { - if (isLive(use)) - propagateMinimumPosition(sd->minimumBlock, use, todo); - else - qCDebug(lcSched) << ".. Skipping node" << use->id() << "as it's not live"; - } -} - -void NodeScheduler::propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, - NodeWorkList &todo) -{ - Q_ASSERT(newMinimumPosition); - - SchedulerData *sd = schedulerData(toNode); - if (sd->isFixed) // nothing to do - return; - - MIBlock::Index minimumBlockIndex = sd->minimumBlock - ? sd->minimumBlock->index() - : MIFunction::StartBlockIndex; - Q_ASSERT(m_domTree->insideSameDominatorChain(newMinimumPosition->index(), minimumBlockIndex)); - if (sd->minimumBlock == nullptr - || m_dominatorDepthForBlock[newMinimumPosition->index()] - > m_dominatorDepthForBlock[minimumBlockIndex]) { - // ok, some input for toNode is scheduled *after* our current minimum depth, so we need - // to adjust out minimal position. (This might involve rescheduling toNode's uses.) - place(toNode, newMinimumPosition); - todo.reEnqueue(toNode); - qCDebug(lcSched) << ".. Propagating minimum block" << sd->minimumBlock->index() - << "which has dominator depth" - << m_dominatorDepthForBlock[newMinimumPosition->index()] - << "to use node" << toNode->id(); - } else { - qCDebug(lcSched) << ".. Minimum position" << newMinimumPosition->index() - << "is not better than" << minimumBlockIndex - << "for node" << toNode->id(); - } -} - -// See Chapter 6.3.4 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for -// a description of the algorithm. -// -// There is one extra detail not described in the thesis mentioned above: loop hoisting. Before we -// place a node in the latest block that dominates all uses, we check if we accidentally sink it -// *into* a loop (meaning the latest block is inside a loop, where it is not if the earliest -// possible block would be chosen). If we detect that a nodes is going to sink into a loop, we walk -// the dominator path from the latest block up to the earliest block, and pick the first block that -// is in the same loop (if any) as the earlieast block. -// -// As noted in the thesis, this strategy might enlongen life times, which could be harmful for -// values that are simple to re-materialized or re-calculate. -void NodeScheduler::scheduleLate(const std::vector &roots) -{ - NodeWorkList todo(m_irFunction->graph()); - for (Node *root : roots) - todo.enqueue(root); - - while (Node *node = todo.dequeueNextNodeForVisiting()) - scheduleNodeLate(node, todo); -} - -void NodeScheduler::scheduleNodeLate(Node *node, NodeWorkList &todo) -{ - if (!needsScheduling(node)) - return; - qCDebug(lcSched) << "Scheduling node" << node->id() << "late..."; - - auto sd = schedulerData(node); - if (sd->unscheduledUses == SchedulerData::NotYetCalculated) { - sd->unscheduledUses = 0; - for (Node *use : node->uses()) { - if (!isLive(use)) - continue; - if (!needsScheduling(use)) - continue; - if (schedulerData(use)->isFixed) - continue; - todo.enqueue(use); - ++sd->unscheduledUses; - } - } - - if (sd->isFixed) { - qCDebug(lcSched) << ".. it's fixed"; - enqueueInputs(node, todo); - return; - } - - if (sd->unscheduledUses) { - qCDebug(lcSched).noquote() << ".. not all uses are fixed, postpone it."<< todo.status(node); - return; - } - - MIBlock *&minBlock = sd->minimumBlock; - if (minBlock == nullptr) - minBlock = m_miFunction->block(MIFunction::StartBlockIndex); - MIBlock *commonUseDominator = commonDominatorOfUses(node); - qCDebug(lcSched) << ".. common use dominator: block" << commonUseDominator->index(); - - // the minBlock has to dominate the uses, *and* the common dominator of the uses. - Q_ASSERT(minBlock->index() == commonUseDominator->index() || - m_domTree->dominates(minBlock->index(), commonUseDominator->index())); - - // we now found the deepest block, so use it as the target block: - MIBlock *targetBlock = commonUseDominator; - - if (node->opcode() == Meta::FrameState) { - // never hoist framestates: they're used (among other things) to keep their inputs alive, so - // hoisting them out would end the life-time of those inputs prematurely - } else { - // but we want to prevent blocks sinking into loops unnecessary - MIBlock *hoistBlock = getHoistBlock(targetBlock); - while (hoistBlock - && m_dominatorDepthForBlock[hoistBlock->index()] - >= m_dominatorDepthForBlock[minBlock->index()]) { - qCDebug(lcSched) << ".. hoisting node" << node->id() << "from block" - << targetBlock->index() << "to block" << hoistBlock->index(); - // ok, so there a) is a hoist block and b) it's deeper than the minimum block, - // so lift it up one level ... - targetBlock = hoistBlock; - // ... and see if we can lift it one more level - hoistBlock = getHoistBlock(targetBlock); - } - } - - qCDebug(lcSched) << ".. fixating it in block" << targetBlock->index() - << "where the minimum block was" << minBlock->index(); - - placeFixed(node, targetBlock, DontSchedule); - enqueueInputs(node, todo); -} - -void NodeScheduler::enqueueInputs(Node *node, NodeWorkList &todo) -{ - for (Node *input : node->inputs()) { - if (!input) - continue; - if (!needsScheduling(input)) - continue; - if (!isLive(input)) - continue; - auto sd = schedulerData(input); - if (sd->isFixed) - continue; - qCDebug(lcSched).noquote() << "... enqueueing input node" << input->id() - << todo.status(input); - if (sd->unscheduledUses != SchedulerData::NotYetCalculated) { - if (sd->unscheduledUses > 0) - --sd->unscheduledUses; - if (sd->unscheduledUses == 0) - todo.reEnqueue(input); - } else { - todo.reEnqueue(input); - } - } -} - -Node *NodeScheduler::firstNotFixedUse(Node *node) -{ - for (Node *use : node->uses()) { - if (!isLive(use)) - continue; - if (!schedulerData(use)->isFixed) - return use; - } - return nullptr; -} - -MIBlock *NodeScheduler::commonDominatorOfUses(Node *node) -{ - MIBlock *commonDominator = nullptr; - for (auto useIt = node->uses().begin(), useEIt = node->uses().end(); useIt != useEIt; ++useIt) { - Node *use = *useIt; - if (!isLive(use)) - continue; - // region nodes use other nodes through their control dependency. But those nodes should - // already have been placed as block terminators before. - Q_ASSERT(use->opcode() != Meta::Region); - if (use->opcode() == Meta::Phi || use->opcode() == Meta::EffectPhi) { - // find the predecessor block defining this input - Node *region = use->controlInput(0); - Node *input = region->controlInput(useIt.inputIndex()); - use = input; - } - auto minBlock = schedulerData(use)->minimumBlock; - if (commonDominator == nullptr) - commonDominator = minBlock; - else - commonDominator = getCommonDominator(commonDominator, minBlock); - } - return commonDominator; -} - -void NodeScheduler::scheduleNodesInBlock() -{ - auto startBlock = m_miFunction->block(MIFunction::StartBlockIndex); - for (Node *n : m_live.reachable()) { - auto sd = schedulerData(n); - if (!sd->minimumBlock) - sd->minimumBlock = startBlock; - } - - std::vector> nodesForBlock; - nodesForBlock.resize(m_miFunction->blockCount()); - - for (auto sd : m_schedulerData) { - if (sd == nullptr) - continue; - if (!isLive(sd->node)) - continue; - sd->unscheduledUses = 0; - for (Node *use : sd->node->uses()) { - if (!needsScheduling(use)) - continue; - if (schedulerData(use)->isScheduledInBlock) - continue; - if (schedulerData(use)->minimumBlock == sd->minimumBlock) - ++sd->unscheduledUses; - } - if (sd->unscheduledUses == 0) - nodesForBlock[sd->minimumBlock->index()].push_back(sd); - } - - NodeWorkList todo(m_irFunction->graph()); - for (MIBlock *b : m_miFunction->blocks()) { - qCDebug(lcSched) << "Scheduling inside block" << b->index(); - MIInstr *insertionPoint = &b->instructions().back(); - todo.enqueue(insertionPoint->irNode()); - scheduleNodesInBlock(insertionPoint, b, todo); - Q_ASSERT(todo.isEmpty()); - for (auto sd : nodesForBlock[b->index()]) { - if (!sd->isScheduledInBlock) - todo.enqueue(sd->node); - } - scheduleNodesInBlock(insertionPoint, b, todo); - Q_ASSERT(todo.isEmpty()); - todo.reset(); - } -} - -void NodeScheduler::scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo) -{ - while (Node *n = todo.dequeueNextNodeForVisiting()) - scheduleNodeInBlock(n, insertionPoint, b, todo); -} - -void NodeScheduler::scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, - NodeWorkList &todo) -{ - Q_ASSERT(!node->isDead()); - - if (!isLive(node)) - return; - - if (!needsScheduling(node)) - return; - - auto nodeData = schedulerData(node); - if (nodeData->minimumBlock != b) - return; - - const bool wasAlreadyScheduled = nodeData->isScheduledInBlock; - if (!wasAlreadyScheduled) { - if (nodeData->unscheduledUses) - return; - - scheduleNodeNow(node, insertionPoint); - } - - if (Node *framestate = node->frameStateInput()) - scheduleNodeInBlock(framestate, insertionPoint, b, todo); - - for (Node *input : node->inputs()) { - if (!input) - continue; - if (!needsScheduling(input)) - continue; - if (!isLive(input)) - continue; - auto inputInfo = schedulerData(input); - if (inputInfo->isScheduledInBlock) - continue; - Q_ASSERT(inputInfo->minimumBlock != nullptr); - if (inputInfo->minimumBlock != b) - continue; - Q_ASSERT(!input->isDead()); - Q_ASSERT(inputInfo->unscheduledUses != SchedulerData::NotYetCalculated); - if (!wasAlreadyScheduled && inputInfo->unscheduledUses > 0) - --inputInfo->unscheduledUses; - if (inputInfo->unscheduledUses == 0) - todo.enqueue(input); - } -} - -void NodeScheduler::scheduleNodeNow(Node *node, MIInstr *&insertionPoint) -{ - qCDebug(lcSched) << ".. scheduling node" << node->id() - << "in block" << insertionPoint->parent()->index() - << "before node" << insertionPoint->irNode()->id(); - - MIInstr *newInstr = createMIInstruction(node); - newInstr->insertBefore(insertionPoint); - insertionPoint = newInstr; -} - -void NodeScheduler::place(Node *node, MIBlock *b) -{ - Q_ASSERT(!node->isDead()); - - if (b == nullptr) - return; - - schedulerData(node)->minimumBlock = b; -} - -void NodeScheduler::placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled) -{ - place(node, b); - auto sd = schedulerData(node); - Q_ASSERT(!sd->isFixed); - sd->isFixed = true; - sd->isScheduledInBlock = markScheduled == Schedule; -} - -unsigned NodeScheduler::vregForNode(Node *node) -{ - unsigned &vreg = m_vregs[unsigned(node->id())]; - if (vreg == std::numeric_limits::max()) - vreg = m_nextVReg++; - return vreg; -} - -void NodeScheduler::addBlockStart(std::vector &roots, Node *startNode, MIBlock *block) -{ - block->instructions().insert(block->instructions().begin(), createMIInstruction(startNode)); - if (startNode->opcode() == Meta::Region) { - for (Node *use : startNode->uses()) { - if (use->opcode() == Meta::Phi && isLive(use)) { - block->addArgument(MIOperand::createVirtualRegister(use, vregForNode(use))); - placeFixed(use, block, Schedule); - roots.push_back(use); - } else if (use->opcode() == Meta::EffectPhi && isLive(use)) { - placeFixed(use, block, Schedule); - roots.push_back(use); - } - } - } -} - -void NodeScheduler::handleStartNode(Node *startNode, MIBlock *startBlock) -{ - startBlock->instructions().push_front(createMIInstruction(startNode)); - - QVarLengthArray args; - for (Node *use : startNode->uses()) { - switch (use->opcode()) { - case Meta::Engine: Q_FALLTHROUGH(); - case Meta::CppFrame: - case Meta::Function: - placeFixed(use, startBlock, Schedule); - break; - case Meta::Parameter: { - auto param = ParameterPayload::get(*use->operation()); - int idx = int(param->parameterIndex()); - if (args.size() <= idx) - args.resize(idx + 1); - args[int(idx)] = use; - placeFixed(use, startBlock, Schedule); - } - break; - default: - break; - } - } - - for (unsigned i = 0, ei = unsigned(args.size()); i != ei; ++i) { - if (Node *arg = args.at(int(i))) - startBlock->addArgument(MIOperand::createJSStackSlot(arg, i)); - } -} - -static Node *firstControlOutput(Node *n) -{ - for (auto it = n->uses().begin(), eit = n->uses().end(); it != eit; ++it) { - if (it.isUsedAsControl()) - return *it; - } - return nullptr; -} - -MIInstr *NodeScheduler::createMIInstruction(Node *node) -{ - const auto opcode = node->operation()->kind(); - - unsigned nArgs = 0; - switch (opcode) { - case Meta::UnwindDispatch: Q_FALLTHROUGH(); - case Meta::Jump: { - Node *target = firstControlOutput(node); - if (target->opcode() == Meta::Region) { - for (Node *n : target->uses()) { - if (n->opcode() == Meta::Phi && isLive(n)) - ++nArgs; - } - } - } - break; - case Meta::Branch: - nArgs = 1; - break; - case Meta::Return: - nArgs = 1; - break; - default: - nArgs = node->operation()->valueInputCount(); - break; - } - - MIInstr *instr = MIInstr::create(m_irFunction->pool(), node, nArgs); - for (unsigned i = 0, ei = node->operation()->valueInputCount(); i != ei; ++i) - instr->setOperand(i, createMIOperand(node->input(i))); - if (node->opcode() != Meta::Start && node->operation()->valueOutputCount() > 0) - instr->setDestination(createMIOperand(node)); - - schedulerData(node)->isScheduledInBlock = true; - return instr; -} - -MIOperand NodeScheduler::createMIOperand(Node *node) -{ - if (node->operation()->isConstant()) - return MIOperand::createConstant(node); - - auto opcode = node->operation()->kind(); - switch (opcode) { - case Meta::Parameter: - return MIOperand::createJSStackSlot( - node, unsigned(ParameterPayload::get(*node->operation())->parameterIndex())); - case Meta::Engine: - return MIOperand::createEngineRegister(node); - case Meta::CppFrame: - return MIOperand::createCppFrameRegister(node); - case Meta::Function: - return MIOperand::createFunction(node); - default: - if ((node->opcode() == Meta::Call - && CallPayload::get(*node->operation())->callee() == Meta::JSThisToObject) - || node->opcode() == Meta::StoreThis) { - return MIOperand::createJSStackSlot(node, CallData::This); - } else { - return MIOperand::createVirtualRegister(node, vregForNode(node)); - } - } -} - -void NodeScheduler::showNodesByBlock(const QString &description) const -{ - if (!lcSched().isDebugEnabled()) - return; - - qCDebug(lcSched) << description; - - for (MIBlock *b : m_miFunction->blocks()) { - QString s; - for (const SchedulerData *sd : m_schedulerData) { - if (!sd) - continue; - if (!isLive(sd->node)) - continue; - if (sd->minimumBlock == b) { - if (!s.isEmpty()) - s += QStringLiteral(", "); - s += QStringLiteral("%1 (%2)").arg(QString::number(sd->node->id()), - sd->node->operation()->debugString()); - } - } - if (s.isEmpty()) - s = QStringLiteral("<>"); - qCDebug(lcSched, "Nodes in block %u: %s", b->index(), s.toUtf8().constData()); - } -} - -void NodeScheduler::dumpDotCFG() const -{ - QString out; - out += QLatin1Char('\n'); - out += QStringLiteral("digraph{root=\"L%1\" label=\"Control Flow Graph\";" - "node[shape=circle];edge[dir=forward fontsize=10]\n") - .arg(MIFunction::StartBlockIndex); - for (MIBlock *src : m_miFunction->blocks()) { - for (MIBlock *dst : src->outEdges()) { - out += QStringLiteral("L%1->L%2\n").arg(QString::number(src->index()), - QString::number(dst->index())); - } - } - out += QStringLiteral("}\n"); - qCDebug(lcDotCFG).nospace().noquote() << out; -} - -} // IR namespace -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jit/qv4schedulers_p.h b/src/qml/jit/qv4schedulers_p.h deleted file mode 100644 index f9179816df..0000000000 --- a/src/qml/jit/qv4schedulers_p.h +++ /dev/null @@ -1,162 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QV4SCHEDULER_P_H -#define QV4SCHEDULER_P_H - -// -// 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. -// - -#include "qv4global_p.h" -#include "qv4mi_p.h" -#include "qv4node_p.h" -#include "qv4domtree_p.h" -#include "qv4loopinfo_p.h" - -QT_REQUIRE_CONFIG(qml_tracing); - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -// Node scheduling "flattens" the graph into basic blocks with an ordered list of instructions. -// -// The various steps are mentioned in buildMIFunction, but the general idea is described in -// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf in chapter 6. -class NodeScheduler final -{ - Q_DISABLE_COPY_MOVE(NodeScheduler) - - class SchedulerData final { - Q_DISABLE_COPY_MOVE(SchedulerData) - public: - static SchedulerData *create(QQmlJS::MemoryPool *pool) - { return pool->New(); } - - SchedulerData() = default; - ~SchedulerData() = default; - - Node *node = nullptr; - MIBlock *minimumBlock = nullptr; - bool isFixed = false; - bool isScheduledInBlock = false; - static constexpr unsigned NotYetCalculated = std::numeric_limits::max(); - unsigned unscheduledUses = NotYetCalculated; - }; - -public: - NodeScheduler(Function *irFunction); - ~NodeScheduler() = default; - - MIFunction *buildMIFunction(); - -private: - std::vector buildCFG(); - void scheduleEarly(const std::vector &roots); - void scheduleEarly(Node *node, NodeWorkList &todo); - void propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, NodeWorkList &todo); - void scheduleLate(const std::vector &roots); - void scheduleNodeLate(Node *node, NodeWorkList &todo); - void enqueueInputs(Node *node, NodeWorkList &todo); - Node *firstNotFixedUse(Node *node); - MIBlock *commonDominatorOfUses(Node *node); - void scheduleNodesInBlock(); - void scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); - void scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); - void scheduleNodeNow(Node *node, MIInstr *&insertionPoint); - - void place(Node *node, MIBlock *b); - enum ScheduleOrNot { DontSchedule, Schedule }; - void placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled); - unsigned vregForNode(Node *node); - void addBlockStart(std::vector &roots, Node *startNode, MIBlock *block); - void enqueueControlInputs(Node *node); - void handleStartNode(Node *startNode, MIBlock *startBlock); - MIInstr *createMIInstruction(Node *node); - MIOperand createMIOperand(Node *node); - SchedulerData *schedulerData(Node *n) - { - if (Q_UNLIKELY(m_schedulerData.size() <= n->id())) - m_schedulerData.resize(n->id() + 8); - SchedulerData *&sd = m_schedulerData[n->id()]; - if (Q_UNLIKELY(sd == nullptr)) { - sd = SchedulerData::create(m_irFunction->pool()); - sd->node = n; - } - return sd; - } - bool isLive(Node *n) const - { return m_live.isReachable(n->id()); } - bool canStartBlock(Node *node) const; - bool isControlFlowSplit(Node *node) const; - bool isBlockTerminator(Node *node) const; - MIBlock *getCommonDominator(MIBlock *one, MIBlock *other) const; - MIBlock *getHoistBlock(MIBlock *block) const; - - void showNodesByBlock(const QString &description) const; - - void dumpDotCFG() const; - -private: - Function *m_irFunction = nullptr; - MIFunction *m_miFunction = nullptr; - QScopedPointer m_loopInfo; - QScopedPointer m_domTree; - std::vector m_dominatorDepthForBlock; - std::vector m_vregs; - std::vector m_schedulerData; - NodeCollector m_live; - unsigned m_nextVReg = 0; -}; - -} // namespace IR -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4SCHEDULER_P_H diff --git a/src/qml/jit/qv4tracingjit.cpp b/src/qml/jit/qv4tracingjit.cpp deleted file mode 100644 index c8974b3a1b..0000000000 --- a/src/qml/jit/qv4tracingjit.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "qv4vme_moth_p.h" -#include "qv4graphbuilder_p.h" -#include "qv4lowering_p.h" -#include "qv4mi_p.h" -#include "qv4schedulers_p.h" - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTracing, "qt.v4.tracing") - -namespace QV4 { - -// This is the entry point for the "tracing JIT". It uses the sea-of-nodes concept as described in -// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf -// -// The minimal pipeline is as follows: -// - create the graph for the function -// - do generic lowering -// - schedule the nodes -// - run minimal stack slot allocation (no re-use of slots) -// - run the assembler -// -// This pipeline has no optimizations, and generates quite inefficient code. It does have the -// advantage that no trace information is used, so it can be used for testing where it replaces -// the baseline JIT. Any optimizations are additions to this pipeline. -// -// Note: generators (or resuming functions in general) are not supported by this JIT. -void Moth::runTracingJit(QV4::Function *function) -{ - IR::Function irFunction(function); - qCDebug(lcTracing).noquote() << "runTracingJit called for" << irFunction.name() << "..."; - - qCDebug(lcTracing).noquote().nospace() << function->traceInfoToString(); - - IR::GraphBuilder::buildGraph(&irFunction); - irFunction.dump(QStringLiteral("initial IR")); - irFunction.verify(); - - IR::GenericLowering(irFunction).lower(); - irFunction.dump(QStringLiteral("after generic lowering")); - irFunction.verify(); - - IR::NodeScheduler scheduler(&irFunction); - QScopedPointer miFunction(scheduler.buildMIFunction()); - miFunction->dump(QStringLiteral("initial MI")); - irFunction.verify(); -} - -} // QV4 namespace -QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f374a092b7..a0b11c2c51 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -138,8 +138,6 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcTracingAll, "qt.v4.tracing.all") - using namespace QV4; #ifndef V4_BOOTSTRAP @@ -658,13 +656,6 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ExecutionEngine::~ExecutionEngine() { - if (Q_UNLIKELY(lcTracingAll().isDebugEnabled())) { - for (auto cu : compilationUnits) { - for (auto f : qAsConst(cu->runtimeFunctions)) - qCDebug(lcTracingAll).noquote().nospace() << f->traceInfoToString(); - } - } - modules.clear(); delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = nullptr; diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index 1bd4329fe8..debdf23d27 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -75,19 +75,12 @@ ReturnedValue Function::call(const Value *thisObject, const Value *argv, int arg Function *Function::create(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function) { - quint16 traceSlotCount = 0; -#if QT_CONFIG(qml_tracing) - traceSlotCount = function->nTraceInfos == CompiledData::Function::NoTracing() - ? 1 - : function->nTraceInfos; -#endif - quint8 *storage = new quint8[sizeof(Function) + traceSlotCount]; - return new(storage) Function(engine, unit, function); + return new Function(engine, unit, function); } void Function::destroy() { - delete[] reinterpret_cast(this); + delete this; } Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function) @@ -111,13 +104,6 @@ Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, internalClass = ic->d(); nFormals = compiledFunction->nFormals; - -#if QT_CONFIG(qml_tracing) - if (tracingEnabled()) { - for (uint i = 0; i < function->nTraceInfos; ++i) - *traceInfo(i) = 0; - } -#endif } Function::~Function() @@ -188,22 +174,4 @@ QQmlSourceLocation Function::sourceLocation() const return QQmlSourceLocation(sourceFile(), compiledFunction->location.line, compiledFunction->location.column); } -QString Function::traceInfoToString() -{ - QString info = QLatin1String("=== Trace information for ") + name()->toQString() + QLatin1Char(':'); - if (!tracingEnabled()) - return info + QStringLiteral(" disabled. Interpreter call count: %1\n").arg(interpreterCallCount); - if (compiledFunction->nTraceInfos == 0) - return info + QLatin1String(" none.\n"); - - info += QLatin1Char('\n'); - for (uint i = 0, ei = compiledFunction->nTraceInfos; i < ei; ++i) { - auto bits = QString::number(*traceInfo(i), 2); - if (bits.size() < 8) - bits.prepend(QString(8 - bits.size(), '0')); - info += QStringLiteral(" %1: %2\n").arg(QString::number(i), bits); - } - return info; -} - QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index f8125a58f8..01b212370d 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -123,31 +123,6 @@ public: return nullptr; return compilationUnit->runtimeFunctions[compiledFunction->nestedFunctionIndex]; } - - Q_NEVER_INLINE QString traceInfoToString(); - - quint8 *traceInfo(uint i) - { -#if QT_CONFIG(qml_tracing) - Q_ASSERT((tracingEnabled() && i < traceInfoCount()) || (i == 0)); - return reinterpret_cast(this) + sizeof(Function) + i; -#else - Q_UNUSED(i); - return nullptr; -#endif - } - - quint32 traceInfoCount() const - { return compiledFunction->nTraceInfos; } - - bool tracingEnabled() const - { -#if QT_CONFIG(qml_tracing) - return traceInfoCount() != CompiledData::Function::NoTracing(); -#else - return false; -#endif - } }; } diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h index 8a0ef5aa30..42b6edb6e2 100644 --- a/src/qml/jsruntime/qv4global_p.h +++ b/src/qml/jsruntime/qv4global_p.h @@ -236,20 +236,6 @@ struct IdentifierTable; class RegExpCache; class MultiplyWrappedQObjectMap; -enum class ObservedTraceValues : quint8 { - Integer = 1 << 0, - Boolean = 1 << 1, - Double = 1 << 2, - Other = 1 << 3, - TypeMask = Integer | Boolean | Double | Other, - - TruePathTaken = 1 << 0, - FalsePathTaken = 1 << 1, - - ArrayWasAccessed = 1 << 7, - ArrayAccessNeededFallback = 1 << 6, -}; - enum PropertyFlag { Attr_Data = 0, Attr_Accessor = 0x1, diff --git a/src/qml/jsruntime/qv4math_p.h b/src/qml/jsruntime/qv4math_p.h index a60a49a811..90246c4229 100644 --- a/src/qml/jsruntime/qv4math_p.h +++ b/src/qml/jsruntime/qv4math_p.h @@ -66,42 +66,27 @@ QT_BEGIN_NAMESPACE namespace QV4 { -static inline QMLJS_READONLY ReturnedValue add_int32(int a, int b, quint8 *traceInfo = nullptr) +static inline QMLJS_READONLY ReturnedValue add_int32(int a, int b) { int result; - if (Q_UNLIKELY(add_overflow(a, b, &result))) { - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + if (Q_UNLIKELY(add_overflow(a, b, &result))) return Value::fromDouble(static_cast(a) + b).asReturnedValue(); - } - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); return Value::fromInt32(result).asReturnedValue(); } -static inline QMLJS_READONLY ReturnedValue sub_int32(int a, int b, quint8 *traceInfo = nullptr) +static inline QMLJS_READONLY ReturnedValue sub_int32(int a, int b) { int result; - if (Q_UNLIKELY(sub_overflow(a, b, &result))) { - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + if (Q_UNLIKELY(sub_overflow(a, b, &result))) return Value::fromDouble(static_cast(a) - b).asReturnedValue(); - } - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); return Value::fromInt32(result).asReturnedValue(); } -static inline QMLJS_READONLY ReturnedValue mul_int32(int a, int b, quint8 *traceInfo = nullptr) +static inline QMLJS_READONLY ReturnedValue mul_int32(int a, int b) { int result; - if (Q_UNLIKELY(mul_overflow(a, b, &result))) { - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + if (Q_UNLIKELY(mul_overflow(a, b, &result))) return Value::fromDouble(static_cast(a) * b).asReturnedValue(); - } - if (traceInfo) - *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); return Value::fromInt32(result).asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 8f2b162106..e95dfa775f 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -717,30 +717,6 @@ ReturnedValue Runtime::LoadElement::call(ExecutionEngine *engine, const Value &o return getElementFallback(engine, object, index); } -ReturnedValue Runtime::LoadElement_Traced::call(ExecutionEngine *engine, const Value &object, const Value &index, quint8 *traceSlot) -{ - *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); - if (index.isPositiveInt()) { - uint idx = static_cast(index.int_32()); - if (Heap::Base *b = object.heapObject()) { - if (b->internalClass->vtable->isObject) { - Heap::Object *o = static_cast(b); - if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { - Heap::SimpleArrayData *s = o->arrayData.cast(); - if (idx < s->values.size) - if (!s->data(idx).isEmpty()) - return s->data(idx).asReturnedValue(); - } - } - } - *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); - return getElementIntFallback(engine, object, idx); - } - - *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); - return getElementFallback(engine, object, index); -} - static Q_NEVER_INLINE bool setElementFallback(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) { Scope scope(engine); @@ -796,30 +772,6 @@ void Runtime::StoreElement::call(ExecutionEngine *engine, const Value &object, c engine->throwTypeError(); } -void Runtime::StoreElement_traced::call(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value, quint8 *traceSlot) -{ - *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); - if (index.isPositiveInt()) { - uint idx = static_cast(index.int_32()); - if (Heap::Base *b = object.heapObject()) { - if (b->internalClass->vtable->isObject) { - Heap::Object *o = static_cast(b); - if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { - Heap::SimpleArrayData *s = o->arrayData.cast(); - if (idx < s->values.size) { - s->setData(engine, idx, value); - return; - } - } - } - } - } - - *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); - if (!setElementFallback(engine, object, index, value) && engine->currentStackFrame->v4Function->isStrict()) - engine->throwTypeError(); -} - ReturnedValue Runtime::GetIterator::call(ExecutionEngine *engine, const Value &in, int iterator) { Scope scope(engine); diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index 86cbccde23..13a73b7046 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -154,10 +154,6 @@ struct Q_QML_PRIVATE_EXPORT Runtime { { static void call(ExecutionEngine *, const Value &, const Value &, const Value &); }; - struct Q_QML_PRIVATE_EXPORT StoreElement_traced : Method - { - static void call(ExecutionEngine *, const Value &, const Value &, const Value &, quint8 *); - }; struct Q_QML_PRIVATE_EXPORT LoadProperty : Method { static ReturnedValue call(ExecutionEngine *, const Value &, int); @@ -170,10 +166,6 @@ struct Q_QML_PRIVATE_EXPORT Runtime { { static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); }; - struct Q_QML_PRIVATE_EXPORT LoadElement_Traced : Method - { - static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, quint8 *); - }; struct Q_QML_PRIVATE_EXPORT LoadSuperProperty : Method { static ReturnedValue call(ExecutionEngine *, const Value &); diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 71e35bdc07..4c292d429a 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -350,82 +350,6 @@ static struct InstrCount { if (engine->hasException) \ goto handleUnwind -static inline void traceJumpTakesTruePath(bool truePathTaken, Function *f, int slot) -{ -#if QT_CONFIG(qml_tracing) - quint8 *traceInfo = f->traceInfo(slot); - Q_ASSERT(traceInfo); - *traceInfo |= truePathTaken ? quint8(ObservedTraceValues::TruePathTaken) - : quint8(ObservedTraceValues::FalsePathTaken); -#else - Q_UNUSED(truePathTaken); - Q_UNUSED(f); - Q_UNUSED(slot); -#endif -} - -static inline void traceValue(ReturnedValue acc, Function *f, int slot) -{ -#if QT_CONFIG(qml_tracing) - quint8 *traceInfo = f->traceInfo(slot); - Q_ASSERT(traceInfo); - switch (Primitive::fromReturnedValue(acc).type()) { - case QV4::Value::Integer_Type: - *traceInfo |= quint8(ObservedTraceValues::Integer); - break; - case QV4::Value::Boolean_Type: - *traceInfo |= quint8(ObservedTraceValues::Boolean); - break; - case QV4::Value::Double_Type: - *traceInfo |= quint8(ObservedTraceValues::Double); - break; - default: - *traceInfo |= quint8(ObservedTraceValues::Other); - break; - } -#else - Q_UNUSED(acc); - Q_UNUSED(f); - Q_UNUSED(slot); -#endif -} - -static inline void traceIntValue(Function *f, int slot) -{ -#if QT_CONFIG(qml_tracing) - quint8 *traceInfo = f->traceInfo(slot); - Q_ASSERT(traceInfo); - *traceInfo |= quint8(ObservedTraceValues::Integer); -#else - Q_UNUSED(f); - Q_UNUSED(slot); -#endif -} - -static inline void traceDoubleValue(Function *f, int slot) -{ -#if QT_CONFIG(qml_tracing) - quint8 *traceInfo = f->traceInfo(slot); - Q_ASSERT(traceInfo); - *traceInfo |= quint8(ObservedTraceValues::Double); -#else - Q_UNUSED(f); - Q_UNUSED(slot); -#endif -} - -static inline void traceOtherValue(Function *f, int slot) -{ -#if QT_CONFIG(qml_tracing) - quint8 *traceInfo = f->traceInfo(slot); - Q_ASSERT(traceInfo); - *traceInfo |= quint8(ObservedTraceValues::Other); -#else - Q_UNUSED(f); - Q_UNUSED(slot); -#endif -} - static inline Heap::CallContext *getScope(QV4::Value *stack, int level) { Heap::ExecutionContext *scope = static_cast(stack[CallData::Context]).d(); @@ -504,16 +428,10 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) #if QT_CONFIG(qml_jit) if (debugger == nullptr) { if (function->jittedCode == nullptr) { - if (engine->canJIT(function)) { -#if QT_CONFIG(qml_tracing) - if (function->tracingEnabled()) - runTracingJit(function); - else -#endif - QV4::JIT::BaselineJIT(function).generate(); - } else { + if (engine->canJIT(function)) + QV4::JIT::BaselineJIT(function).generate(); + else ++function->interpreterCallCount; - } } } #endif // QT_CONFIG(qml_jit) @@ -525,22 +443,6 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) ReturnedValue result; if (function->jittedCode != nullptr && debugger == nullptr) { result = function->jittedCode(frame, engine); - if (QV4::Value::fromReturnedValue(result).isEmpty()) { // de-optimize! - if (ShowWhenDeoptimiationHappens) { - // This is debug code, which is disabled by default, and completely removed by the - // compiler. - fprintf(stderr, "*********************** DEOPT! %s ***********************\n" - "*** deopt IP: %d, line: %d\n", - function->name()->toQString().toUtf8().constData(), - frame->instructionPointer, - frame->lineNumber()); - } - delete function->codeRef; - function->codeRef = nullptr; - function->jittedCode = nullptr; - function->interpreterCallCount = 0; // reset to restart tracing: apparently we didn't have enough info before - result = interpret(frame, engine, function->codeData + frame->instructionPointer); - } } else { // interpreter result = interpret(frame, engine, function->codeData); @@ -559,11 +461,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, QV4::ReturnedValue acc = accumulator.asReturnedValue(); Value *stack = reinterpret_cast(frame->jsFrame); - if (function->tracingEnabled()) { - for (int i = 0; i < int(function->nFormals); ++i) - traceValue(frame->jsFrame->argument(i), function, i); - } - MOTH_JUMP_TABLE; for (;;) { @@ -622,7 +519,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, auto cc = static_cast(stack[CallData::Context].m()); Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext); acc = cc->locals[index].asReturnedValue(); - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadLocal) MOTH_BEGIN_INSTR(StoreLocal) @@ -635,7 +531,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(LoadScopedLocal) auto cc = getScope(stack, scope); acc = cc->locals[index].asReturnedValue(); - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadScopedLocal) MOTH_BEGIN_INSTR(StoreScopedLocal) @@ -660,7 +555,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STORE_IP(); acc = Runtime::LoadName::call(engine, name); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadName) MOTH_BEGIN_INSTR(LoadGlobalLookup) @@ -668,7 +562,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; acc = l->globalGetter(l, engine); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadGlobalLookup) MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup) @@ -676,7 +569,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; acc = l->qmlContextPropertyGetter(l, engine, nullptr); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadQmlContextPropertyLookup) MOTH_BEGIN_INSTR(StoreNameStrict) @@ -696,25 +588,14 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(LoadElement) STORE_IP(); STORE_ACC(); -#if QT_CONFIG(qml_tracing) - acc = Runtime::LoadElement_Traced::call(engine, STACK_VALUE(base), accumulator, function->traceInfo(traceSlot)); - traceValue(acc, function, traceSlot); -#else - Q_UNUSED(traceSlot); acc = Runtime::LoadElement::call(engine, STACK_VALUE(base), accumulator); -#endif CHECK_EXCEPTION; MOTH_END_INSTR(LoadElement) MOTH_BEGIN_INSTR(StoreElement) STORE_IP(); STORE_ACC(); -#if QT_CONFIG(qml_tracing) - Runtime::StoreElement_traced::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator, function->traceInfo(traceSlot)); -#else - Q_UNUSED(traceSlot); Runtime::StoreElement::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator); -#endif CHECK_EXCEPTION; MOTH_END_INSTR(StoreElement) @@ -723,7 +604,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STORE_ACC(); acc = Runtime::LoadProperty::call(engine, accumulator, name); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadProperty) MOTH_BEGIN_INSTR(GetLookup) @@ -742,7 +622,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = l->getter(l, engine, accumulator); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(GetLookup) MOTH_BEGIN_INSTR(StoreProperty) @@ -816,7 +695,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, Value undef = Value::undefinedValue(); acc = static_cast(func).call(&undef, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallValue) MOTH_BEGIN_INSTR(CallWithReceiver) @@ -828,14 +706,12 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, } acc = static_cast(func).call(stack + thisObject, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallWithReceiver) MOTH_BEGIN_INSTR(CallProperty) STORE_IP(); acc = Runtime::CallProperty::call(engine, stack[base], name, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallProperty) MOTH_BEGIN_INSTR(CallPropertyLookup) @@ -863,49 +739,42 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = static_cast(f).call(stack + base, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallPropertyLookup) MOTH_BEGIN_INSTR(CallElement) STORE_IP(); acc = Runtime::CallElement::call(engine, stack[base], STACK_VALUE(index), stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallElement) MOTH_BEGIN_INSTR(CallName) STORE_IP(); acc = Runtime::CallName::call(engine, name, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallName) MOTH_BEGIN_INSTR(CallPossiblyDirectEval) STORE_IP(); acc = Runtime::CallPossiblyDirectEval::call(engine, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallPossiblyDirectEval) MOTH_BEGIN_INSTR(CallGlobalLookup) STORE_IP(); acc = Runtime::CallGlobalLookup::call(engine, index, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallGlobalLookup) MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup) STORE_IP(); acc = Runtime::CallQmlContextPropertyLookup::call(engine, index, stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallQmlContextPropertyLookup) MOTH_BEGIN_INSTR(CallWithSpread) STORE_IP(); acc = Runtime::CallWithSpread::call(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallWithSpread) MOTH_BEGIN_INSTR(TailCall) @@ -1120,7 +989,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, takeJump = ACC.int_32(); else takeJump = ACC.toBoolean(); - traceJumpTakesTruePath(takeJump, function, traceSlot); if (takeJump) code += offset; MOTH_END_INSTR(JumpTrue) @@ -1131,7 +999,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, takeJump = !ACC.int_32(); else takeJump = !ACC.toBoolean(); - traceJumpTakesTruePath(!takeJump, function, traceSlot); if (takeJump) code += offset; MOTH_END_INSTR(JumpFalse) @@ -1291,12 +1158,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(UNot) MOTH_BEGIN_INSTR(UPlus) - if (Q_LIKELY(ACC.isNumber())) { - if (ACC.isDouble()) - traceDoubleValue(function, traceSlot); - else - traceIntValue(function, traceSlot); - } else { + if (Q_UNLIKELY(!ACC.isNumber())) { acc = Encode(ACC.toNumberImpl()); CHECK_EXCEPTION; } @@ -1307,17 +1169,14 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, int a = ACC.int_32(); if (a == 0 || a == std::numeric_limits::min()) { acc = Encode(-static_cast(a)); - traceDoubleValue(function, traceSlot); } else { - acc = sub_int32(0, ACC.int_32(), function->traceInfo(traceSlot)); + acc = sub_int32(0, ACC.int_32()); } } else if (ACC.isDouble()) { acc ^= (1ull << 63); // simply flip sign bit - traceDoubleValue(function, traceSlot); } else { acc = Encode(-ACC.toNumberImpl()); CHECK_EXCEPTION; - traceOtherValue(function, traceSlot); } MOTH_END_INSTR(UMinus) @@ -1328,57 +1187,49 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(Increment) if (Q_LIKELY(ACC.integerCompatible())) { - acc = add_int32(ACC.int_32(), 1, function->traceInfo(traceSlot)); + acc = add_int32(ACC.int_32(), 1); } else if (ACC.isDouble()) { acc = QV4::Encode(ACC.doubleValue() + 1.); - traceDoubleValue(function, traceSlot); } else { acc = Encode(ACC.toNumberImpl() + 1.); CHECK_EXCEPTION; - traceDoubleValue(function, traceSlot); } MOTH_END_INSTR(Increment) MOTH_BEGIN_INSTR(Decrement) if (Q_LIKELY(ACC.integerCompatible())) { - acc = sub_int32(ACC.int_32(), 1, function->traceInfo(traceSlot)); + acc = sub_int32(ACC.int_32(), 1); } else if (ACC.isDouble()) { acc = QV4::Encode(ACC.doubleValue() - 1.); - traceDoubleValue(function, traceSlot); } else { acc = Encode(ACC.toNumberImpl() - 1.); CHECK_EXCEPTION; - traceDoubleValue(function, traceSlot); } MOTH_END_INSTR(Decrement) MOTH_BEGIN_INSTR(Add) const Value left = STACK_VALUE(lhs); if (Q_LIKELY(Value::integerCompatible(left, ACC))) { - acc = add_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + acc = add_int32(left.int_32(), ACC.int_32()); } else if (left.isNumber() && ACC.isNumber()) { acc = Encode(left.asDouble() + ACC.asDouble()); - traceDoubleValue(function, traceSlot); } else { STORE_ACC(); acc = Runtime::Add::call(engine, left, accumulator); CHECK_EXCEPTION; - traceOtherValue(function, traceSlot); } MOTH_END_INSTR(Add) MOTH_BEGIN_INSTR(Sub) const Value left = STACK_VALUE(lhs); if (Q_LIKELY(Value::integerCompatible(left, ACC))) { - acc = sub_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + acc = sub_int32(left.int_32(), ACC.int_32()); } else if (left.isNumber() && ACC.isNumber()) { acc = Encode(left.asDouble() - ACC.asDouble()); - traceDoubleValue(function, traceSlot); } else { STORE_ACC(); acc = Runtime::Sub::call(left, accumulator); CHECK_EXCEPTION; - traceOtherValue(function, traceSlot); } MOTH_END_INSTR(Sub) @@ -1395,15 +1246,13 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(Mul) const Value left = STACK_VALUE(lhs); if (Q_LIKELY(Value::integerCompatible(left, ACC))) { - acc = mul_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + acc = mul_int32(left.int_32(), ACC.int_32()); } else if (left.isNumber() && ACC.isNumber()) { acc = Encode(left.asDouble() * ACC.asDouble()); - traceDoubleValue(function, traceSlot); } else { STORE_ACC(); acc = Runtime::Mul::call(left, accumulator); CHECK_EXCEPTION; - traceOtherValue(function, traceSlot); } MOTH_END_INSTR(Mul) @@ -1417,7 +1266,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STORE_ACC(); acc = Runtime::Mod::call(STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; - traceValue(acc, function, traceSlot); MOTH_END_INSTR(Mod) MOTH_BEGIN_INSTR(BitAnd) diff --git a/src/qml/jsruntime/qv4vme_moth_p.h b/src/qml/jsruntime/qv4vme_moth_p.h index 4ac7120d36..8a76e60f20 100644 --- a/src/qml/jsruntime/qv4vme_moth_p.h +++ b/src/qml/jsruntime/qv4vme_moth_p.h @@ -58,8 +58,6 @@ QT_BEGIN_NAMESPACE namespace QV4 { namespace Moth { -void runTracingJit(QV4::Function *function); - class VME { public: diff --git a/src/qml/qtqmlglobal.h b/src/qml/qtqmlglobal.h index 348cfd86b8..a111f72e81 100644 --- a/src/qml/qtqmlglobal.h +++ b/src/qml/qtqmlglobal.h @@ -54,7 +54,6 @@ # define QT_FEATURE_qml_debug -1 # define QT_FEATURE_qml_sequence_object 1 # define QT_FEATURE_qml_jit -1 -# define QT_FEATURE_qml_tracing -1 #endif QT_BEGIN_NAMESPACE -- cgit v1.2.3 From d4239d2bf94274d849344a95f94dba65c1c22999 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 29 Apr 2019 09:55:54 +0200 Subject: Fix maximum call stack depth for clang ASAN builds For clang we need to use has_feature to detect the presence of an ASAN build. Amends commit eb363c3a0b7f96015d7b8f2551dbeaa86f5acf16 Task-number: QTBUG-75410 Change-Id: I2adb69deb07f8c6b77be8c2f23751fd0a2bbdb95 Reviewed-by: Erik Verbruggen --- src/qml/jsruntime/qv4engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 966ff12506..b6000dbcca 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -169,7 +169,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) bool ok = false; maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok); if (!ok || maxCallDepth <= 0) { -#if defined(QT_NO_DEBUG) && !defined(__SANITIZE_ADDRESS__) +#if defined(QT_NO_DEBUG) && !defined(__SANITIZE_ADDRESS__) && !QT_HAS_FEATURE(address_sanitizer) maxCallDepth = 1234; #else // no (tail call) optimization is done, so there'll be a lot mare stack frames active -- cgit v1.2.3 From a452f14fd9b20b429048fc28aeac0c0542484a50 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 29 Apr 2019 11:48:10 +0200 Subject: Fix coverity warning Commit bc00353cffbfe0f74b602a16452f2e7bcd588152 accidentally removed the assert that expressed how objectForId will always succeed with the alias target. That caused coverity to complain that objectAt() may be called with a negative (then array) index. Change-Id: I8651e0826c92e41ab00bf8a44f1abfd1cbfb0e06 Reviewed-by: Ulf Hermann --- src/qml/compiler/qqmlpropertycachecreator_p.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h index 074dc98648..901602d17b 100644 --- a/src/qml/compiler/qqmlpropertycachecreator_p.h +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -703,8 +703,9 @@ inline QQmlCompileError QQmlPropertyCacheAliasCreator::property QVarLengthArray seenAliases({lastAlias}); do { - const CompiledObject *targetObject = objectContainer->objectAt( - objectForId(component, lastAlias->targetObjectId)); + const int targetObjectIndex = objectForId(component, lastAlias->targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + const CompiledObject *targetObject = objectContainer->objectAt(targetObjectIndex); Q_ASSERT(targetObject); auto nextAlias = targetObject->aliasesBegin(); -- cgit v1.2.3 From 8fb8cfd040a5fa8c4caa5715efe1328da9c49536 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 24 Apr 2019 11:46:37 +0200 Subject: QQuickTableView: avoid building syncView children before syncView has finished If you put two tables inside an async loader, with one being the syncView for the other, the syncView child will start loading items async simultaneously with the syncView. This is unnecessary, and steals loading resources, since the child will have to rebuild anyway once the syncView has completed loading. So return early from the recursiveUpdateTable call before handling the children if we detect that the parent is not done. Change-Id: I8c0badaf3cfa3a353a650e5f38f381bf9a7b98f9 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 22 ++++++++++++++-------- src/quick/items/qquicktableview_p_p.h | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index c30d40138e..60bf42fb10 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1866,14 +1866,16 @@ bool QQuickTableViewPrivate::updateTableRecursive() return false; } - updateTable(); + const bool updateComplete = updateTable(); + if (!updateComplete) + return false; for (auto syncChild : qAsConst(syncChildren)) { auto syncChild_d = syncChild->d_func(); syncChild_d->scheduledRebuildOptions |= rebuildOptions; - const bool updated = syncChild_d->updateTableRecursive(); - if (!updated) + const bool descendantUpdateComplete = syncChild_d->updateTableRecursive(); + if (!descendantUpdateComplete) return false; } @@ -1882,11 +1884,13 @@ bool QQuickTableViewPrivate::updateTableRecursive() return true; } -void QQuickTableViewPrivate::updateTable() +bool QQuickTableViewPrivate::updateTable() { // Whenever something changes, e.g viewport moves, spacing is set to a // new value, model changes etc, this function will end up being called. Here // we check what needs to be done, and load/unload cells accordingly. + // If we cannot complete the update (because we need to wait for an item + // to load async), we return false. Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!"); QBoolBlocker polishGuard(polishing, true); @@ -1896,25 +1900,27 @@ void QQuickTableViewPrivate::updateTable() // as an atomic operation, which means that we don't continue doing anything else until all // items have been received and laid out. Note that updatePolish is then called once more // after the loadRequest has completed to handle anything that might have occurred in-between. - return; + return false; } if (rebuildState != RebuildState::Done) { processRebuildTable(); - return; + return rebuildState == RebuildState::Done; } syncWithPendingChanges(); if (rebuildState == RebuildState::Begin) { processRebuildTable(); - return; + return rebuildState == RebuildState::Done; } if (loadedItems.isEmpty()) - return; + return !loadRequest.isActive(); loadAndUnloadVisibleEdges(); + + return !loadRequest.isActive(); } void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent) diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index ab7c0c3275..728896d30e 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -315,7 +315,7 @@ public: QQuickTableView *rootSyncView() const; bool updateTableRecursive(); - void updateTable(); + bool updateTable(); void relayoutTable(); void relayoutTableItems(); -- cgit v1.2.3 From 4fea3ec29c9911522a379a01418394b5cad29ecc Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 29 Apr 2019 16:44:48 +0200 Subject: Skip block context within call contexts when searching for parameters Only the call context contains the signal parameters. However, there can be any number of nested block contexts in a function. This manifests itself when the function needs an execution context. The simplest way to trigger this is attaching a debugger. Fixes: QTBUG-75393 Change-Id: Iabdc06a9fe7bf88204525d6940b626575fee1579 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4qmlcontext.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 12ada7ee70..0c5226d46c 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -458,11 +458,17 @@ ReturnedValue QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(Lookup * // into the handler expression through the locals of the call context. So for onClicked: { ... } // the parameters of the clicked signal are injected and we must allow for them to be found here // before any other property from the QML context. - ExecutionContext &ctx = static_cast(engine->currentStackFrame->jsFrame->context); - if (ctx.d()->type == Heap::ExecutionContext::Type_CallContext) { - uint index = ctx.d()->internalClass->indexOfValueOrGetter(name); - if (index < UINT_MAX) - return static_cast(ctx.d())->locals[index].asReturnedValue(); + for (Heap::ExecutionContext *ctx = engine->currentContext()->d(); ctx; ctx = ctx->outer) { + if (ctx->type == Heap::ExecutionContext::Type_CallContext) { + const uint index = ctx->internalClass->indexOfValueOrGetter(name); + if (index < std::numeric_limits::max()) + return static_cast(ctx)->locals[index].asReturnedValue(); + } + + // Skip only block contexts within the current call context. + // Other contexts need a regular QML property lookup. See below. + if (ctx->type != Heap::ExecutionContext::Type_BlockContext) + break; } bool hasProperty = false; -- cgit v1.2.3 From ae31808f2f47cf1b9fe1b995e79b8eaef355441d Mon Sep 17 00:00:00 2001 From: Richard Weickelt Date: Tue, 30 Apr 2019 00:23:14 +0200 Subject: Fix error when building qtdeclarative without Qt Quick Qt Labs Animation requires Qt Quick, but was unconditionally built even when Qt Quick was not present. Change-Id: Idf5093574e409e91b7a662f41f18101907746c6a Reviewed-by: Shawn Rutledge --- src/imports/imports.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 9b1cfa6aa8..901c263be5 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -4,7 +4,6 @@ SUBDIRS += \ builtins \ qtqml \ models \ - labsanimation \ labsmodels qtConfig(thread): SUBDIRS += folderlistmodel @@ -16,6 +15,7 @@ qtHaveModule(quick) { QT_FOR_CONFIG += quick-private SUBDIRS += \ + labsanimation \ layouts \ qtquick2 \ window \ -- cgit v1.2.3 From 11b3bb4cd898c5a837b41258e37a5012ae5ed863 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 17 Apr 2019 10:55:15 +0200 Subject: Mark functions involving attached property IDs as deprecated Task-number: QTBUG-75176 Change-Id: I969e7987ebf8b98aed9ba9e17388a0e168ef5e09 Reviewed-by: Simon Hausmann --- src/qml/qml/qqml.h | 8 +++++--- src/qml/qml/qqmlengine.cpp | 2 ++ src/qml/qml/qqmlmetatype.cpp | 2 ++ src/qml/qml/qqmlmetatype_p.h | 8 ++++++-- src/qml/qml/qqmltype.cpp | 2 ++ src/qml/qml/qqmltype_p.h | 4 +++- 6 files changed, 20 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 82dc8d0c1b..7b3f89e943 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -579,9 +579,11 @@ namespace QtQml { Q_QML_EXPORT void qmlExecuteDeferred(QObject *); Q_QML_EXPORT QQmlContext *qmlContext(const QObject *); Q_QML_EXPORT QQmlEngine *qmlEngine(const QObject *); - Q_QML_EXPORT QObject *qmlAttachedPropertiesObjectById(int, const QObject *, bool create = true); - Q_QML_EXPORT QObject *qmlAttachedPropertiesObject(int *, const QObject *, - const QMetaObject *, bool create); +#if QT_DEPRECATED_SINCE(5, 14) + Q_QML_EXPORT QT_DEPRECATED QObject *qmlAttachedPropertiesObjectById(int, const QObject *, bool create = true); + Q_QML_EXPORT QT_DEPRECATED QObject *qmlAttachedPropertiesObject( + int *, const QObject *, const QMetaObject *, bool create); +#endif Q_QML_EXPORT QQmlAttachedPropertiesFunc qmlAttachedPropertiesFunction(QObject *, const QMetaObject *); Q_QML_EXPORT QObject *qmlAttachedPropertiesObject(QObject *, QQmlAttachedPropertiesFunc func, diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 19c3469682..bb2b3e462c 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1625,6 +1625,7 @@ static QObject *resolveAttachedProperties(QQmlAttachedPropertiesFunc pf, QQmlDat return rv; } +#if QT_DEPRECATED_SINCE(5, 14) QObject *qmlAttachedPropertiesObjectById(int id, const QObject *object, bool create) { QQmlData *data = QQmlData::get(object, create); @@ -1652,6 +1653,7 @@ QObject *qmlAttachedPropertiesObject(int *idCache, const QObject *object, return qmlAttachedPropertiesObjectById(*idCache, object, create); } +#endif QQmlAttachedPropertiesFunc qmlAttachedPropertiesFunction(QObject *object, const QMetaObject *attachedMetaObject) diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 01da5923d9..09df23de51 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -899,6 +899,7 @@ int QQmlMetaType::listType(int id) return 0; } +#if QT_DEPRECATED_SINCE(5, 14) int QQmlMetaType::attachedPropertiesFuncId(QQmlEnginePrivate *engine, const QMetaObject *mo) { QQmlMetaTypeDataPtr data; @@ -920,6 +921,7 @@ QQmlAttachedPropertiesFunc QQmlMetaType::attachedPropertiesFuncById(QQmlEnginePr QQmlMetaTypeDataPtr data; return data->types.at(id).attachedPropertiesFunction(engine); } +#endif QQmlAttachedPropertiesFunc QQmlMetaType::attachedPropertiesFunc(QQmlEnginePrivate *engine, const QMetaObject *mo) diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index b7304cfa63..9af982d1c3 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -119,8 +119,12 @@ public: static QObject *toQObject(const QVariant &, bool *ok = nullptr); static int listType(int); - static int attachedPropertiesFuncId(QQmlEnginePrivate *engine, const QMetaObject *); - static QQmlAttachedPropertiesFunc attachedPropertiesFuncById(QQmlEnginePrivate *, int); +#if QT_DEPRECATED_SINCE(5, 14) + static QT_DEPRECATED int attachedPropertiesFuncId(QQmlEnginePrivate *engine, + const QMetaObject *); + static QT_DEPRECATED QQmlAttachedPropertiesFunc attachedPropertiesFuncById(QQmlEnginePrivate *, + int); +#endif static QQmlAttachedPropertiesFunc attachedPropertiesFunc(QQmlEnginePrivate *, const QMetaObject *); diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index 26ca995756..926e2810d5 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -650,6 +650,7 @@ const QMetaObject *QQmlType::attachedPropertiesType(QQmlEnginePrivate *engine) c return base.attachedPropertiesType(engine); } +#if QT_DEPRECATED_SINCE(5, 14) /* This is the id passed to qmlAttachedPropertiesById(). This is different from the index for the case that a single class is registered under two or more names (eg. Item in @@ -667,6 +668,7 @@ int QQmlType::attachedPropertiesId(QQmlEnginePrivate *engine) const base = resolveCompositeBaseType(engine); return base.attachedPropertiesId(engine); } +#endif int QQmlType::parserStatusCast() const { diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index 7b326ce1c7..1d65a08c8f 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -131,7 +131,9 @@ public: QQmlAttachedPropertiesFunc attachedPropertiesFunction(QQmlEnginePrivate *engine) const; const QMetaObject *attachedPropertiesType(QQmlEnginePrivate *engine) const; - int attachedPropertiesId(QQmlEnginePrivate *engine) const; +#if QT_DEPRECATED_SINCE(5, 14) + QT_DEPRECATED int attachedPropertiesId(QQmlEnginePrivate *engine) const; +#endif int parserStatusCast() const; const char *interfaceIId() const; -- cgit v1.2.3 From a9886b4bf9ee80f9bc29bc5e8fd801705568f4da Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 26 Apr 2019 14:23:21 +0200 Subject: Clean up QStringHash Make it completely inline, move the (4 times duplicated) primeForNumBits function into its own file, address some warnings, move QHashedString::compare into qhashedstring.cpp. Change-Id: I778bb3d3e176cfec45eda9be9d7e5982585e6474 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4identifier.cpp | 16 +-- src/qml/jsruntime/qv4identifiertable.cpp | 16 +-- src/qml/jsruntime/qv4internalclass.cpp | 13 +-- src/qml/qml/ftw/ftw.pri | 4 +- src/qml/qml/ftw/qhashedstring.cpp | 54 ++++++++++ src/qml/qml/ftw/qprimefornumbits_p.h | 80 +++++++++++++++ src/qml/qml/ftw/qstringhash.cpp | 168 ------------------------------- src/qml/qml/ftw/qstringhash_p.h | 69 +++++++++++-- 8 files changed, 203 insertions(+), 217 deletions(-) create mode 100644 src/qml/qml/ftw/qprimefornumbits_p.h delete mode 100644 src/qml/qml/ftw/qstringhash.cpp (limited to 'src') diff --git a/src/qml/jsruntime/qv4identifier.cpp b/src/qml/jsruntime/qv4identifier.cpp index 5db5bd46ec..f9bc7b68c6 100644 --- a/src/qml/jsruntime/qv4identifier.cpp +++ b/src/qml/jsruntime/qv4identifier.cpp @@ -39,29 +39,19 @@ #include "qv4identifier_p.h" #include "qv4identifiertable_p.h" #include "qv4string_p.h" +#include QT_BEGIN_NAMESPACE namespace QV4 { -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 -}; - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - - IdentifierHashData::IdentifierHashData(IdentifierTable *table, int numBits) : size(0) , numBits(numBits) , identifierTable(table) { refCount.store(1); - alloc = primeForNumBits(numBits); + alloc = qPrimeForNumBits(numBits); entries = (IdentifierHashEntry *)malloc(alloc*sizeof(IdentifierHashEntry)); memset(entries, 0, alloc*sizeof(IdentifierHashEntry)); identifierTable->addIdentifierHash(this); @@ -110,7 +100,7 @@ IdentifierHashEntry *IdentifierHash::addEntry(PropertyKey identifier) if (grow) { ++d->numBits; - int newAlloc = primeForNumBits(d->numBits); + int newAlloc = qPrimeForNumBits(d->numBits); IdentifierHashEntry *newEntries = (IdentifierHashEntry *)malloc(newAlloc * sizeof(IdentifierHashEntry)); memset(newEntries, 0, newAlloc*sizeof(IdentifierHashEntry)); for (int i = 0; i < d->alloc; ++i) { diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp index ae937b2889..21b47c3909 100644 --- a/src/qml/jsruntime/qv4identifiertable.cpp +++ b/src/qml/jsruntime/qv4identifiertable.cpp @@ -38,28 +38,18 @@ ****************************************************************************/ #include "qv4identifiertable_p.h" #include "qv4symbol_p.h" +#include QT_BEGIN_NAMESPACE namespace QV4 { -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 -}; - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - - IdentifierTable::IdentifierTable(ExecutionEngine *engine, int numBits) : engine(engine) , size(0) , numBits(numBits) { - alloc = primeForNumBits(numBits); + alloc = qPrimeForNumBits(numBits); entriesByHash = (Heap::StringOrSymbol **)malloc(alloc*sizeof(Heap::StringOrSymbol *)); entriesById = (Heap::StringOrSymbol **)malloc(alloc*sizeof(Heap::StringOrSymbol *)); memset(entriesByHash, 0, alloc*sizeof(Heap::String *)); @@ -87,7 +77,7 @@ void IdentifierTable::addEntry(Heap::StringOrSymbol *str) if (grow) { ++numBits; - int newAlloc = primeForNumBits(numBits); + int newAlloc = qPrimeForNumBits(numBits); Heap::StringOrSymbol **newEntries = (Heap::StringOrSymbol **)malloc(newAlloc*sizeof(Heap::String *)); memset(newEntries, 0, newAlloc*sizeof(Heap::StringOrSymbol *)); for (uint i = 0; i < alloc; ++i) { diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index a10fda79f2..d597335031 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -45,27 +45,18 @@ #include "qv4identifiertable_p.h" #include "qv4value_p.h" #include "qv4mm_p.h" +#include QT_BEGIN_NAMESPACE namespace QV4 { -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 -}; - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - PropertyHashData::PropertyHashData(int numBits) : refCount(1) , size(0) , numBits(numBits) { - alloc = primeForNumBits(numBits); + alloc = qPrimeForNumBits(numBits); entries = (PropertyHash::Entry *)malloc(alloc*sizeof(PropertyHash::Entry)); memset(entries, 0, alloc*sizeof(PropertyHash::Entry)); } diff --git a/src/qml/qml/ftw/ftw.pri b/src/qml/qml/ftw/ftw.pri index 0bb8cb954e..eadba394b4 100644 --- a/src/qml/qml/ftw/ftw.pri +++ b/src/qml/qml/ftw/ftw.pri @@ -3,6 +3,7 @@ HEADERS += \ $$PWD/qintrusivelist_p.h \ $$PWD/qpodvector_p.h \ $$PWD/qhashedstring_p.h \ + $$PWD/qprimefornumbits_p.h \ $$PWD/qqmlrefcount_p.h \ $$PWD/qfieldlist_p.h \ $$PWD/qqmlthread_p.h \ @@ -18,8 +19,7 @@ HEADERS += \ SOURCES += \ $$PWD/qintrusivelist.cpp \ $$PWD/qhashedstring.cpp \ - $$PWD/qqmlthread.cpp \ - $$PWD/qstringhash.cpp + $$PWD/qqmlthread.cpp # mirrors logic in $$QT_SOURCE_TREE/config.tests/unix/clock-gettime/clock-gettime.pri # clock_gettime() is implemented in librt on these systems diff --git a/src/qml/qml/ftw/qhashedstring.cpp b/src/qml/qml/ftw/qhashedstring.cpp index bb6688599d..7a8fdd0a14 100644 --- a/src/qml/qml/ftw/qhashedstring.cpp +++ b/src/qml/qml/ftw/qhashedstring.cpp @@ -41,6 +41,60 @@ QT_BEGIN_NAMESPACE +// Copy of QString's qMemCompare +bool QHashedString::compare(const QChar *lhs, const QChar *rhs, int length) +{ + Q_ASSERT(lhs && rhs); + const quint16 *a = (const quint16 *)lhs; + const quint16 *b = (const quint16 *)rhs; + + if (a == b || !length) + return true; + + union { + const quint16 *w; + const quint32 *d; + quintptr value; + } sa, sb; + sa.w = a; + sb.w = b; + + // check alignment + if ((sa.value & 2) == (sb.value & 2)) { + // both addresses have the same alignment + if (sa.value & 2) { + // both addresses are not aligned to 4-bytes boundaries + // compare the first character + if (*sa.w != *sb.w) + return false; + --length; + ++sa.w; + ++sb.w; + + // now both addresses are 4-bytes aligned + } + + // both addresses are 4-bytes aligned + // do a fast 32-bit comparison + const quint32 *e = sa.d + (length >> 1); + for ( ; sa.d != e; ++sa.d, ++sb.d) { + if (*sa.d != *sb.d) + return false; + } + + // do we have a tail? + return (length & 1) ? *sa.w == *sb.w : true; + } else { + // one of the addresses isn't 4-byte aligned but the other is + const quint16 *e = sa.w + length; + for ( ; sa.w != e; ++sa.w, ++sb.w) { + if (*sa.w != *sb.w) + return false; + } + } + return true; +} + QHashedStringRef QHashedStringRef::mid(int offset, int length) const { Q_ASSERT(offset < m_length); diff --git a/src/qml/qml/ftw/qprimefornumbits_p.h b/src/qml/qml/ftw/qprimefornumbits_p.h new file mode 100644 index 0000000000..6e9acbf7fd --- /dev/null +++ b/src/qml/qml/ftw/qprimefornumbits_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QPRIMEFORNUMBITS_P_H +#define QPRIMEFORNUMBITS_P_H + +// +// 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. +// + +#include + +QT_BEGIN_NAMESPACE + +/* + The prime_deltas array is a table of selected prime values, even + though it doesn't look like one. The primes we are using are 1, + 2, 5, 11, 17, 37, 67, 131, 257, ..., i.e. primes in the immediate + surrounding of a power of two. + + The qPrimeForNumBits() function returns the prime associated to a + power of two. For example, qPrimeForNumBits(8) returns 257. +*/ + +inline int qPrimeForNumBits(int numBits) +{ + static constexpr const uchar prime_deltas[] = { + 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, + 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 + }; + + return (1 << numBits) + prime_deltas[numBits]; +} + +QT_END_NAMESPACE + +#endif // QPRIMEFORNUMBITS_P_H diff --git a/src/qml/qml/ftw/qstringhash.cpp b/src/qml/qml/ftw/qstringhash.cpp deleted file mode 100644 index a483dcb810..0000000000 --- a/src/qml/qml/ftw/qstringhash.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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 "qstringhash_p.h" - -QT_BEGIN_NAMESPACE - -/* - A QHash has initially around pow(2, MinNumBits) buckets. For - example, if MinNumBits is 4, it has 17 buckets. -*/ -static const int MinNumBits = 4; - -/* - The prime_deltas array is a table of selected prime values, even - though it doesn't look like one. The primes we are using are 1, - 2, 5, 11, 17, 37, 67, 131, 257, ..., i.e. primes in the immediate - surrounding of a power of two. - - The primeForNumBits() function returns the prime associated to a - power of two. For example, primeForNumBits(8) returns 257. -*/ - -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 -}; - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - -void QStringHashData::rehashToSize(int size) -{ - short bits = qMax(MinNumBits, (int)numBits); - while (primeForNumBits(bits) < size) bits++; - - if (bits > numBits) - rehashToBits(bits); -} - -void QStringHashData::rehashToBits(short bits) -{ - numBits = qMax(MinNumBits, (int)bits); - - int nb = primeForNumBits(numBits); - if (nb == numBuckets && buckets) - return; - - QStringHashNode **newBuckets = new QStringHashNode *[nb]; - ::memset(newBuckets, 0, sizeof(QStringHashNode *) * nb); - - // Preserve the existing order within buckets so that items with the - // same key will retain the same find/findNext order - for (int i = 0; i < numBuckets; ++i) { - QStringHashNode *bucket = buckets[i]; - if (bucket) - rehashNode(newBuckets, nb, bucket); - } - - delete [] buckets; - buckets = newBuckets; - numBuckets = nb; -} - -void QStringHashData::rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node) -{ - QStringHashNode *next = node->next.data(); - if (next) - rehashNode(newBuckets, nb, next); - - int bucket = node->hash % nb; - node->next = newBuckets[bucket]; - newBuckets[bucket] = node; -} - -// Copy of QString's qMemCompare -bool QHashedString::compare(const QChar *lhs, const QChar *rhs, int length) -{ - Q_ASSERT(lhs && rhs); - const quint16 *a = (const quint16 *)lhs; - const quint16 *b = (const quint16 *)rhs; - - if (a == b || !length) - return true; - - union { - const quint16 *w; - const quint32 *d; - quintptr value; - } sa, sb; - sa.w = a; - sb.w = b; - - // check alignment - if ((sa.value & 2) == (sb.value & 2)) { - // both addresses have the same alignment - if (sa.value & 2) { - // both addresses are not aligned to 4-bytes boundaries - // compare the first character - if (*sa.w != *sb.w) - return false; - --length; - ++sa.w; - ++sb.w; - - // now both addresses are 4-bytes aligned - } - - // both addresses are 4-bytes aligned - // do a fast 32-bit comparison - const quint32 *e = sa.d + (length >> 1); - for ( ; sa.d != e; ++sa.d, ++sb.d) { - if (*sa.d != *sb.d) - return false; - } - - // do we have a tail? - return (length & 1) ? *sa.w == *sb.w : true; - } else { - // one of the addresses isn't 4-byte aligned but the other is - const quint16 *e = sa.w + length; - for ( ; sa.w != e; ++sa.w, ++sb.w) { - if (*sa.w != *sb.w) - return false; - } - } - return true; -} - -QT_END_NAMESPACE diff --git a/src/qml/qml/ftw/qstringhash_p.h b/src/qml/qml/ftw/qstringhash_p.h index c7251e8837..f9435b4919 100644 --- a/src/qml/qml/ftw/qstringhash_p.h +++ b/src/qml/qml/ftw/qstringhash_p.h @@ -52,11 +52,14 @@ // #include +#include + +#include QT_BEGIN_NAMESPACE class QStringHashData; -class Q_AUTOTEST_EXPORT QStringHashNode +class QStringHashNode { public: QStringHashNode() @@ -154,12 +157,20 @@ public: } }; -class Q_AUTOTEST_EXPORT QStringHashData +class QStringHashData { + Q_DISABLE_COPY_MOVE(QStringHashData) public: - QStringHashData() {} + QStringHashData() = default; + ~QStringHashData() = default; + + /* + A QHash has initially around pow(2, MinNumBits) buckets. For + example, if MinNumBits is 4, it has 17 buckets. + */ + enum { MinNumBits = 4 }; - QStringHashNode **buckets = nullptr; + QStringHashNode **buckets = nullptr; // life cycle managed by QStringHash int numBuckets = 0; int size = 0; short numBits = 0; @@ -174,13 +185,51 @@ public: QStringHashNode *n; StringHash *p; }; - void rehashToBits(short); - void rehashToSize(int); - void rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node); -private: - QStringHashData(const QStringHashData &); - QStringHashData &operator=(const QStringHashData &); + void rehashToBits(short bits) + { + numBits = qMax(short(MinNumBits), bits); + + int nb = qPrimeForNumBits(numBits); + if (nb == numBuckets && buckets) + return; + + QStringHashNode **newBuckets = new QStringHashNode *[nb]; + ::memset(newBuckets, 0, sizeof(QStringHashNode *) * nb); + + // Preserve the existing order within buckets so that items with the + // same key will retain the same find/findNext order + for (int i = 0; i < numBuckets; ++i) { + QStringHashNode *bucket = buckets[i]; + if (bucket) + rehashNode(newBuckets, nb, bucket); + } + + delete [] buckets; + buckets = newBuckets; + numBuckets = nb; + } + + void rehashToSize(int size) + { + short bits = qMax(short(MinNumBits), numBits); + while (qPrimeForNumBits(bits) < size) + bits++; + + if (bits > numBits) + rehashToBits(bits); + } + + void rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node) + { + QStringHashNode *next = node->next.data(); + if (next) + rehashNode(newBuckets, nb, next); + + int bucket = node->hash % nb; + node->next = newBuckets[bucket]; + newBuckets[bucket] = node; + } }; // For a supplied key type, in what form do we need to keep a hashed version? -- cgit v1.2.3 From ac0d313ab15aa78c444d00ed6a1a202a1351dfa1 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 30 Apr 2019 13:29:45 +0200 Subject: Yarr: Reject quantifiers larger than 16M Nobody needs those and we run into integer overflows later on if we accept them. Fixes: QTBUG-74048 Change-Id: Ib8ccd05e4bd6f662c38fbe95bf1350f81982e1b8 Reviewed-by: Simon Hausmann --- src/3rdparty/masm/yarr/YarrParser.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/3rdparty/masm/yarr/YarrParser.h b/src/3rdparty/masm/yarr/YarrParser.h index 3e5311f1fb..edc6beb1f0 100644 --- a/src/3rdparty/masm/yarr/YarrParser.h +++ b/src/3rdparty/masm/yarr/YarrParser.h @@ -694,7 +694,8 @@ private: ASSERT(!hasError(m_errorCode)); ASSERT(min <= max); - if (min == UINT_MAX) { + const unsigned quantifyLimit = 1 << 24; + if (min > quantifyLimit || (max != quantifyInfinite && max > quantifyLimit)) { m_errorCode = ErrorCode::QuantifierTooLarge; return; } -- cgit v1.2.3 From d7db2ef07bf6f0f7c39f342aed94d51cca42df11 Mon Sep 17 00:00:00 2001 From: Yulong Bai Date: Wed, 10 Apr 2019 15:00:33 +0200 Subject: Add QQuickTableSectionSizeProvider for QQuickTableView Used to store columnWidths and rowHeights. Change-Id: Id66fba9de05afa2c4df15761fb004b4f046fe103 Reviewed-by: Richard Moe Gustavsen --- src/quick/items/qquicktableview.cpp | 65 +++++++++++++++++++++++++++++++++++ src/quick/items/qquicktableview_p_p.h | 21 +++++++++++ 2 files changed, 86 insertions(+) (limited to 'src') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 60bf42fb10..5e7e0db154 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -2625,6 +2625,71 @@ void QQuickTableView::componentComplete() d_func()->registerCallbackWhenBindingsAreEvaluated(); } +class QObjectPrivate; +class QQuickTableSectionSizeProviderPrivate : public QObjectPrivate { +public: + QQuickTableSectionSizeProviderPrivate(); + ~QQuickTableSectionSizeProviderPrivate(); + QHash hash; +}; + +QQuickTableSectionSizeProvider::QQuickTableSectionSizeProvider(QObject *parent) + : QObject (*(new QQuickTableSectionSizeProviderPrivate), parent) +{ +} + +void QQuickTableSectionSizeProvider::setSize(int section, qreal size) +{ + Q_D(QQuickTableSectionSizeProvider); + if (section < 0 || size < 0) { + qmlWarning(this) << "setSize: section or size less than zero"; + return; + } + if (qFuzzyCompare(QQuickTableSectionSizeProvider::size(section), size)) + return; + d->hash.insert(section, size); + emit sizeChanged(); +} + +// return -1.0 if no valid explicit size retrieved +qreal QQuickTableSectionSizeProvider::size(int section) +{ + Q_D(QQuickTableSectionSizeProvider); + auto it = d->hash.find(section); + if (it != d->hash.end()) + return *it; + return -1.0; +} + +// return true if section is valid +bool QQuickTableSectionSizeProvider::resetSize(int section) +{ + Q_D(QQuickTableSectionSizeProvider); + if (d->hash.empty()) + return false; + + auto ret = d->hash.remove(section); + if (ret) + emit sizeChanged(); + return ret; +} + +void QQuickTableSectionSizeProvider::resetAll() +{ + Q_D(QQuickTableSectionSizeProvider); + d->hash.clear(); + emit sizeChanged(); +} + +QQuickTableSectionSizeProviderPrivate::QQuickTableSectionSizeProviderPrivate() + : QObjectPrivate() +{ +} + +QQuickTableSectionSizeProviderPrivate::~QQuickTableSectionSizeProviderPrivate() +{ + +} #include "moc_qquicktableview_p.cpp" QT_END_NAMESPACE diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 728896d30e..cbf6824278 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -70,6 +70,25 @@ static const qreal kDefaultRowHeight = 50; static const qreal kDefaultColumnWidth = 50; class FxTableItem; +class QQuickTableSectionSizeProviderPrivate; + +class Q_QUICK_PRIVATE_EXPORT QQuickTableSectionSizeProvider : public QObject { + Q_OBJECT + +public: + QQuickTableSectionSizeProvider(QObject *parent=nullptr); + void setSize(int section, qreal size); + qreal size(int section); + bool resetSize(int section); + void resetAll(); + +Q_SIGNALS: + void sizeChanged(); + +private: + Q_DISABLE_COPY(QQuickTableSectionSizeProvider) + Q_DECLARE_PRIVATE(QQuickTableSectionSizeProvider) +}; class Q_QML_AUTOTEST_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate { @@ -256,6 +275,8 @@ public: QJSValue rowHeightProvider; QJSValue columnWidthProvider; + QQuickTableSectionSizeProvider rowHeights; + QQuickTableSectionSizeProvider columnWidths; EdgeRange cachedNextVisibleEdgeIndex[4]; EdgeRange cachedColumnWidth; -- cgit v1.2.3 From 6c26a1a137ff328ea144bccc5cb9ad52d71cf67d Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 30 Apr 2019 14:50:49 +0200 Subject: Fix -Winit-list-lifetime Enable by -Wextra in gcc 9. Change-Id: I6642240f7ff9fe9f5fc6a456b66d86c9591eaf5f Reviewed-by: Ulf Hermann --- src/qmldebug/qqmlprofilerevent_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/qmldebug/qqmlprofilerevent_p.h b/src/qmldebug/qqmlprofilerevent_p.h index 1e205d8dbb..a7e37d1964 100644 --- a/src/qmldebug/qqmlprofilerevent_p.h +++ b/src/qmldebug/qqmlprofilerevent_p.h @@ -291,7 +291,7 @@ private: squeeze(const Container &numbers) { typedef typename QIntegerForSize::Signed Small; - foreach (Number item, numbers) { + for (Number item : numbers) { if (!squeezable(item)) return false; } -- cgit v1.2.3 From 9b6c20cb719542cfcf3e031afd5b2b0b1cd3833e Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 18 Apr 2019 15:37:17 +0200 Subject: Make JavaScript execution interruptible Add an atomic isInterrupted flag to BaseEngine and check that in addition to the hasException flag on checkException(). Add some more exception checks to cover all possible infinite loops. Also, remove the writeBarrierActive member from QV4::EngineBase. It isn't used. Fixes: QTBUG-49080 Change-Id: I86b3114e3e61aff3e5eb9b020749a908ed801c2b Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4bytecodegenerator_p.h | 6 ++++++ src/qml/compiler/qv4codegen.cpp | 7 ++++++ src/qml/compiler/qv4instr_moth.cpp | 3 +++ src/qml/compiler/qv4instr_moth_p.h | 2 ++ src/qml/jit/qv4assemblercommon_p.h | 5 +++++ src/qml/jit/qv4baselinejit.cpp | 5 +++++ src/qml/jit/qv4baselinejit_p.h | 1 + src/qml/jsapi/qjsengine.cpp | 36 ++++++++++++++++++++++++++++++- src/qml/jsapi/qjsengine.h | 3 +++ src/qml/jsapi/qjsvalue.cpp | 6 ++++++ src/qml/jsruntime/qv4enginebase_p.h | 20 ++++++++++++++++- src/qml/jsruntime/qv4vme_moth.cpp | 11 ++++++++-- 12 files changed, 101 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h index ab8661dbe3..acd4aa62ea 100644 --- a/src/qml/compiler/qv4bytecodegenerator_p.h +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -209,6 +209,12 @@ QT_WARNING_POP addJumpInstruction(Instruction::JumpTrue()).link(target); } + void checkException() + { + Instruction::CheckException chk; + addInstruction(chk); + } + void setUnwindHandler(ExceptionHandler *handler) { currentExceptionHandler = handler; diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 1537ce408d..1bf0e7147d 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1201,6 +1201,7 @@ bool Codegen::visit(ArrayPattern *ast) lhsValue.loadInAccumulator(); pushAccumulator(); + bytecodeGenerator->checkException(); bytecodeGenerator->jump().link(in); end.link(); } @@ -3201,11 +3202,13 @@ bool Codegen::visit(DoWhileStatement *ast) cond.link(); if (AST::cast(ast->expression)) { // do {} while (true) -> just jump back to the loop body, no need to generate a condition + bytecodeGenerator->checkException(); bytecodeGenerator->jump().link(body); } else if (AST::cast(ast->expression)) { // do {} while (false) -> fall through, no need to generate a condition } else { TailCallBlocker blockTailCalls(this); + bytecodeGenerator->checkException(); condition(ast->expression, &body, &end, false); } @@ -3322,6 +3325,7 @@ bool Codegen::visit(ForEachStatement *ast) setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken); } + bytecodeGenerator->checkException(); bytecodeGenerator->jump().link(in); error: @@ -3370,6 +3374,7 @@ bool Codegen::visit(ForStatement *ast) bytecodeGenerator->addInstruction(clone); } statement(ast->expression); + bytecodeGenerator->checkException(); bytecodeGenerator->jump().link(cond); end.link(); @@ -3652,6 +3657,8 @@ bool Codegen::visit(WhileStatement *ast) ControlFlowLoop flow(this, &end, &cond); bytecodeGenerator->addLoopStart(cond); + bytecodeGenerator->checkException(); + if (!AST::cast(ast->expression)) { TailCallBlocker blockTailCalls(this); condition(ast->expression, &start, &end, true); diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index e022d14264..5148154a6a 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -539,6 +539,9 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << ABSOLUTE_OFFSET(); MOTH_END_INSTR(JumpNoException) + MOTH_BEGIN_INSTR(CheckException) + MOTH_END_INSTR(CheckException) + MOTH_BEGIN_INSTR(CmpEqNull) MOTH_END_INSTR(CmpEqNull) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 6421fc9d67..35a5fdfba5 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -152,6 +152,7 @@ QT_BEGIN_NAMESPACE #define INSTR_JumpFalse(op) INSTRUCTION(op, JumpFalse, 1, offset) #define INSTR_JumpNotUndefined(op) INSTRUCTION(op, JumpNotUndefined, 1, offset) #define INSTR_JumpNoException(op) INSTRUCTION(op, JumpNoException, 1, offset) +#define INSTR_CheckException(op) INSTRUCTION(op, CheckException, 0) #define INSTR_CmpEqNull(op) INSTRUCTION(op, CmpEqNull, 0) #define INSTR_CmpNeNull(op) INSTRUCTION(op, CmpNeNull, 0) #define INSTR_CmpEqInt(op) INSTRUCTION(op, CmpEqInt, 1, lhs) @@ -241,6 +242,7 @@ QT_BEGIN_NAMESPACE F(JumpFalse) \ F(JumpNoException) \ F(JumpNotUndefined) \ + F(CheckException) \ F(CmpEqNull) \ F(CmpNeNull) \ F(CmpEqInt) \ diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index e5c2aff1a7..f305213ce2 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -619,6 +619,9 @@ public: for (Jump j : catchyJumps) j.link(this); + // We don't need to check for isInterrupted here because if that is set, + // then the first checkException() in any exception handler will find another "exception" + // and jump out of the exception handler. loadPtr(exceptionHandlerAddress(), ScratchRegister); Jump exitFunction = branchPtr(Equal, ScratchRegister, TrustedImmPtr(0)); jump(ScratchRegister); @@ -633,6 +636,8 @@ public: void checkException() { + // This actually reads 4 bytes, starting at hasException. + // Therefore, it also reads the isInterrupted flag, and triggers an exception on that. addCatchyJump( branch32(NotEqual, Address(EngineRegister, offsetof(EngineBase, hasException)), diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 517f0940e5..f4807f1917 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -794,6 +794,11 @@ void BaselineJIT::generate_JumpNotUndefined(int offset) labels.insert(as->jumpNotUndefined(absoluteOffset(offset))); } +void BaselineJIT::generate_CheckException() +{ + as->checkException(); +} + void BaselineJIT::generate_CmpEqNull() { as->cmpeqNull(); } void BaselineJIT::generate_CmpNeNull() { as->cmpneNull(); } void BaselineJIT::generate_CmpEqInt(int lhs) { as->cmpeqInt(lhs); } diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 46622d29e6..284faf0ff0 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -163,6 +163,7 @@ public: void generate_JumpFalse(int offset) override; void generate_JumpNoException(int offset) override; void generate_JumpNotUndefined(int offset) override; + void generate_CheckException() override; void generate_CmpEqNull() override; void generate_CmpNeNull() override; void generate_CmpEqInt(int lhs) override; diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index aab72f8b2d..45ea79d31a 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -470,6 +470,33 @@ void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSVal QV4::GlobalExtensions::init(obj, extensions); } +/*! + \since 5.14 + Interrupts or re-enables JavaScript execution. + + If \a interrupted is \c true, any JavaScript executed by this engine + immediately aborts and returns an error object until this function is + called again with a value of \c false for \a interrupted. + + This function is thread safe. You may call it from a different thread + in order to interrupt, for example, an infinite loop in JavaScript. +*/ +void QJSEngine::setInterrupted(bool interrupted) +{ + m_v4Engine->isInterrupted = interrupted; +} + +/*! + \since 5.14 + Returns whether JavaScript execution is currently interrupted. + + \sa setInterrupted() +*/ +bool QJSEngine::isInterrupted() const +{ + return m_v4Engine->isInterrupted; +} + static QUrl urlForFileName(const QString &fileName) { if (!fileName.startsWith(QLatin1Char(':'))) @@ -527,6 +554,8 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in result = script.run(); if (scope.engine->hasException) result = v4->catchException(); + if (v4->isInterrupted) + result = v4->newErrorObject(QStringLiteral("Interrupted")); QJSValue retval(v4, result->asReturnedValue()); @@ -565,7 +594,12 @@ QJSValue QJSEngine::importModule(const QString &fileName) if (m_v4Engine->hasException) return QJSValue(m_v4Engine, m_v4Engine->catchException()); moduleUnit->evaluate(); - return QJSValue(m_v4Engine, moduleNamespace->asReturnedValue()); + if (!m_v4Engine->isInterrupted) + return QJSValue(m_v4Engine, moduleNamespace->asReturnedValue()); + + return QJSValue( + m_v4Engine, + m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue()); } /*! diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 6300842341..31a4d68baa 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -113,6 +113,9 @@ public: void installExtensions(Extensions extensions, const QJSValue &object = QJSValue()); + void setInterrupted(bool interrupted); + bool isInterrupted() const; + QV4::ExecutionEngine *handle() const { return m_v4Engine; } void throwError(const QString &message); diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index e0bd986920..92eaf1d8ee 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -769,6 +769,8 @@ QJSValue QJSValue::call(const QJSValueList &args) ScopedValue result(scope, f->call(jsCallData)); if (engine->hasException) result = engine->catchException(); + if (engine->isInterrupted) + result = engine->newErrorObject(QStringLiteral("Interrupted")); return QJSValue(engine, result->asReturnedValue()); } @@ -825,6 +827,8 @@ QJSValue QJSValue::callWithInstance(const QJSValue &instance, const QJSValueList ScopedValue result(scope, f->call(jsCallData)); if (engine->hasException) result = engine->catchException(); + if (engine->isInterrupted) + result = engine->newErrorObject(QStringLiteral("Interrupted")); return QJSValue(engine, result->asReturnedValue()); } @@ -873,6 +877,8 @@ QJSValue QJSValue::callAsConstructor(const QJSValueList &args) ScopedValue result(scope, f->callAsConstructor(jsCallData)); if (engine->hasException) result = engine->catchException(); + if (engine->isInterrupted) + result = engine->newErrorObject(QStringLiteral("Interrupted")); return QJSValue(engine, result->asReturnedValue()); } diff --git a/src/qml/jsruntime/qv4enginebase_p.h b/src/qml/jsruntime/qv4enginebase_p.h index b5cfea8863..82eccd9f3c 100644 --- a/src/qml/jsruntime/qv4enginebase_p.h +++ b/src/qml/jsruntime/qv4enginebase_p.h @@ -69,9 +69,23 @@ struct Q_QML_EXPORT EngineBase { CppStackFrame *currentStackFrame = nullptr; Value *jsStackTop = nullptr; + + // The JIT expects hasException and isInterrupted to be in the same 32bit word in memory. quint8 hasException = false; - quint8 writeBarrierActive = false; + // isInterrupted is expected to be set from a different thread +#if defined(Q_ATOMIC_INT8_IS_SUPPORTED) + QAtomicInteger isInterrupted = false; quint16 unused = 0; +#elif defined(Q_ATOMIC_INT16_IS_SUPPORTED) + quint8 unused = 0; + QAtomicInteger isInterrupted = false; +#elif defined(V4_BOOTSTRAP) + // We don't need the isInterrupted flag when bootstrapping. + quint8 unused[3]; +#else +# error V4 needs either 8bit or 16bit atomics. +#endif + quint8 isExecutingInRegExpJIT = false; quint8 padding[3]; MemoryManager *memoryManager = nullptr; @@ -137,6 +151,10 @@ Q_STATIC_ASSERT(offsetof(EngineBase, hasException) == offsetof(EngineBase, jsSta Q_STATIC_ASSERT(offsetof(EngineBase, memoryManager) == offsetof(EngineBase, hasException) + 8); Q_STATIC_ASSERT(offsetof(EngineBase, runtime) == offsetof(EngineBase, memoryManager) + QT_POINTER_SIZE); +#ifndef V4_BOOTSTRAP +Q_STATIC_ASSERT(offsetof(EngineBase, isInterrupted) + sizeof(EngineBase::isInterrupted) <= offsetof(EngineBase, hasException) + 4); +#endif + } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 4c292d429a..ec44f42933 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -347,7 +347,7 @@ static struct InstrCount { #undef CHECK_EXCEPTION #endif #define CHECK_EXCEPTION \ - if (engine->hasException) \ + if (engine->hasException || engine->isInterrupted) \ goto handleUnwind static inline Heap::CallContext *getScope(QV4::Value *stack, int level) @@ -1013,6 +1013,10 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, code += offset; MOTH_END_INSTR(JumpNotUndefined) + MOTH_BEGIN_INSTR(CheckException) + CHECK_EXCEPTION; + MOTH_END_INSTR(CheckException) + MOTH_BEGIN_INSTR(CmpEqNull) acc = Encode(ACC.isNullOrUndefined()); MOTH_END_INSTR(CmpEqNull) @@ -1363,7 +1367,10 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(Debug) handleUnwind: - Q_ASSERT(engine->hasException || frame->unwindLevel); + // We do start the exception handler in case of isInterrupted. The exception handler will + // immediately abort, due to the same isInterrupted. We don't skip the exception handler + // because the current behavior is easier to implement in the JIT. + Q_ASSERT(engine->hasException || engine->isInterrupted || frame->unwindLevel); if (!frame->unwindHandler) { acc = Encode::undefined(); return acc; -- cgit v1.2.3 From 325e6305b418ffe1dfb9a36c2516c6a8a3de5733 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:22:22 +0200 Subject: Move model types into their own library The model types are not part of the core QML runtime and should only be loaded if you explicitly import them. We cannot enforce that in Qt5 as some of them are available from the QtQml import, but we can change it in Qt6. Change-Id: I1e49e84d748e352537ec2d4af901c034c91d038f Reviewed-by: Erik Verbruggen --- src/imports/labsmodels/labsmodels.pro | 2 +- src/imports/models/models.pro | 2 +- src/imports/qtqml/plugin.cpp | 2 +- src/imports/qtqml/qtqml.pro | 3 +- src/imports/qtquick2/plugin.cpp | 5 +- src/imports/qtquick2/qtquick2.pro | 2 +- src/qml/configure.json | 16 +- src/qml/jsruntime/qv4dateobject_p.h | 2 +- src/qml/jsruntime/qv4functionobject_p.h | 12 +- src/qml/jsruntime/qv4lookup_p.h | 2 +- src/qml/jsruntime/qv4object_p.h | 2 +- src/qml/jsruntime/qv4propertykey_p.h | 2 +- src/qml/jsruntime/qv4serialize.cpp | 73 +- src/qml/jsruntime/qv4vtable_p.h | 2 +- src/qml/qml/qqmljavascriptexpression_p.h | 2 +- src/qml/types/qqmldelegatecomponent.cpp | 300 --- src/qml/types/qqmldelegatecomponent_p.h | 155 -- src/qml/types/qqmldelegatemodel.cpp | 3540 ---------------------------- src/qml/types/qqmldelegatemodel_p.h | 248 -- src/qml/types/qqmldelegatemodel_p_p.h | 450 ---- src/qml/types/qqmlinstantiator.cpp | 509 ---- src/qml/types/qqmlinstantiator_p.h | 119 - src/qml/types/qqmlinstantiator_p_p.h | 98 - src/qml/types/qqmlitemmodels.qdoc | 110 - src/qml/types/qqmlitemselectionmodel.qdoc | 239 -- src/qml/types/qqmllistmodel.cpp | 2900 ----------------------- src/qml/types/qqmllistmodel_p.h | 208 -- src/qml/types/qqmllistmodel_p_p.h | 428 ---- src/qml/types/qqmllistmodelworkeragent.cpp | 177 -- src/qml/types/qqmllistmodelworkeragent_p.h | 140 -- src/qml/types/qqmlmodelsmodule.cpp | 123 - src/qml/types/qqmlmodelsmodule_p.h | 72 - src/qml/types/qqmlobjectmodel.cpp | 431 ---- src/qml/types/qqmlobjectmodel_p.h | 194 -- src/qml/types/qqmltableinstancemodel.cpp | 547 ----- src/qml/types/qqmltableinstancemodel_p.h | 162 -- src/qml/types/qqmltablemodel.cpp | 1059 --------- src/qml/types/qqmltablemodel_p.h | 170 -- src/qml/types/qqmltablemodelcolumn.cpp | 200 -- src/qml/types/qqmltablemodelcolumn_p.h | 224 -- src/qml/types/qquickpackage.cpp | 198 -- src/qml/types/qquickpackage_p.h | 101 - src/qml/types/types.pri | 41 +- src/qml/util/qqmladaptormodel.cpp | 1037 -------- src/qml/util/qqmladaptormodel_p.h | 179 -- src/qml/util/qqmlchangeset.cpp | 583 ----- src/qml/util/qqmlchangeset_p.h | 161 -- src/qml/util/qqmllistaccessor.cpp | 160 -- src/qml/util/qqmllistaccessor_p.h | 83 - src/qml/util/qqmllistcompositor.cpp | 1482 ------------ src/qml/util/qqmllistcompositor_p.h | 372 --- src/qml/util/util.pri | 14 - src/qmlmodels/configure.json | 31 + src/qmlmodels/qmlmodels.pro | 57 + src/qmlmodels/qqmladaptormodel.cpp | 1037 ++++++++ src/qmlmodels/qqmladaptormodel_p.h | 181 ++ src/qmlmodels/qqmlchangeset.cpp | 583 +++++ src/qmlmodels/qqmlchangeset_p.h | 161 ++ src/qmlmodels/qqmldelegatecomponent.cpp | 300 +++ src/qmlmodels/qqmldelegatecomponent_p.h | 155 ++ src/qmlmodels/qqmldelegatemodel.cpp | 3540 ++++++++++++++++++++++++++++ src/qmlmodels/qqmldelegatemodel_p.h | 246 ++ src/qmlmodels/qqmldelegatemodel_p_p.h | 450 ++++ src/qmlmodels/qqmlinstantiator.cpp | 509 ++++ src/qmlmodels/qqmlinstantiator_p.h | 119 + src/qmlmodels/qqmlinstantiator_p_p.h | 98 + src/qmlmodels/qqmlitemmodels.qdoc | 110 + src/qmlmodels/qqmlitemselectionmodel.qdoc | 239 ++ src/qmlmodels/qqmllistaccessor.cpp | 160 ++ src/qmlmodels/qqmllistaccessor_p.h | 83 + src/qmlmodels/qqmllistcompositor.cpp | 1482 ++++++++++++ src/qmlmodels/qqmllistcompositor_p.h | 372 +++ src/qmlmodels/qqmllistmodel.cpp | 2900 +++++++++++++++++++++++ src/qmlmodels/qqmllistmodel_p.h | 209 ++ src/qmlmodels/qqmllistmodel_p_p.h | 429 ++++ src/qmlmodels/qqmllistmodelworkeragent.cpp | 185 ++ src/qmlmodels/qqmllistmodelworkeragent_p.h | 128 + src/qmlmodels/qqmlmodelsmodule.cpp | 124 + src/qmlmodels/qqmlmodelsmodule_p.h | 72 + src/qmlmodels/qqmlobjectmodel.cpp | 431 ++++ src/qmlmodels/qqmlobjectmodel_p.h | 194 ++ src/qmlmodels/qqmltableinstancemodel.cpp | 547 +++++ src/qmlmodels/qqmltableinstancemodel_p.h | 162 ++ src/qmlmodels/qqmltablemodel.cpp | 1059 +++++++++ src/qmlmodels/qqmltablemodel_p.h | 170 ++ src/qmlmodels/qqmltablemodelcolumn.cpp | 200 ++ src/qmlmodels/qqmltablemodelcolumn_p.h | 224 ++ src/qmlmodels/qquickpackage.cpp | 198 ++ src/qmlmodels/qquickpackage_p.h | 101 + src/qmlmodels/qtqmlmodelsglobal.h | 59 + src/qmlmodels/qtqmlmodelsglobal_p.h | 61 + src/quick/configure.json | 3 +- src/quick/items/qquickitemview_p_p.h | 6 +- src/quick/items/qquickrepeater.cpp | 1 - src/quick/items/qquicktableview.cpp | 6 +- src/quick/items/qquicktableview_p_p.h | 4 +- src/quick/quick.pro | 2 +- src/src.pro | 4 +- 98 files changed, 17452 insertions(+), 17283 deletions(-) delete mode 100644 src/qml/types/qqmldelegatecomponent.cpp delete mode 100644 src/qml/types/qqmldelegatecomponent_p.h delete mode 100644 src/qml/types/qqmldelegatemodel.cpp delete mode 100644 src/qml/types/qqmldelegatemodel_p.h delete mode 100644 src/qml/types/qqmldelegatemodel_p_p.h delete mode 100644 src/qml/types/qqmlinstantiator.cpp delete mode 100644 src/qml/types/qqmlinstantiator_p.h delete mode 100644 src/qml/types/qqmlinstantiator_p_p.h delete mode 100644 src/qml/types/qqmlitemmodels.qdoc delete mode 100644 src/qml/types/qqmlitemselectionmodel.qdoc delete mode 100644 src/qml/types/qqmllistmodel.cpp delete mode 100644 src/qml/types/qqmllistmodel_p.h delete mode 100644 src/qml/types/qqmllistmodel_p_p.h delete mode 100644 src/qml/types/qqmllistmodelworkeragent.cpp delete mode 100644 src/qml/types/qqmllistmodelworkeragent_p.h delete mode 100644 src/qml/types/qqmlmodelsmodule.cpp delete mode 100644 src/qml/types/qqmlmodelsmodule_p.h delete mode 100644 src/qml/types/qqmlobjectmodel.cpp delete mode 100644 src/qml/types/qqmlobjectmodel_p.h delete mode 100644 src/qml/types/qqmltableinstancemodel.cpp delete mode 100644 src/qml/types/qqmltableinstancemodel_p.h delete mode 100644 src/qml/types/qqmltablemodel.cpp delete mode 100644 src/qml/types/qqmltablemodel_p.h delete mode 100644 src/qml/types/qqmltablemodelcolumn.cpp delete mode 100644 src/qml/types/qqmltablemodelcolumn_p.h delete mode 100644 src/qml/types/qquickpackage.cpp delete mode 100644 src/qml/types/qquickpackage_p.h delete mode 100644 src/qml/util/qqmladaptormodel.cpp delete mode 100644 src/qml/util/qqmladaptormodel_p.h delete mode 100644 src/qml/util/qqmlchangeset.cpp delete mode 100644 src/qml/util/qqmlchangeset_p.h delete mode 100644 src/qml/util/qqmllistaccessor.cpp delete mode 100644 src/qml/util/qqmllistaccessor_p.h delete mode 100644 src/qml/util/qqmllistcompositor.cpp delete mode 100644 src/qml/util/qqmllistcompositor_p.h create mode 100644 src/qmlmodels/configure.json create mode 100644 src/qmlmodels/qmlmodels.pro create mode 100644 src/qmlmodels/qqmladaptormodel.cpp create mode 100644 src/qmlmodels/qqmladaptormodel_p.h create mode 100644 src/qmlmodels/qqmlchangeset.cpp create mode 100644 src/qmlmodels/qqmlchangeset_p.h create mode 100644 src/qmlmodels/qqmldelegatecomponent.cpp create mode 100644 src/qmlmodels/qqmldelegatecomponent_p.h create mode 100644 src/qmlmodels/qqmldelegatemodel.cpp create mode 100644 src/qmlmodels/qqmldelegatemodel_p.h create mode 100644 src/qmlmodels/qqmldelegatemodel_p_p.h create mode 100644 src/qmlmodels/qqmlinstantiator.cpp create mode 100644 src/qmlmodels/qqmlinstantiator_p.h create mode 100644 src/qmlmodels/qqmlinstantiator_p_p.h create mode 100644 src/qmlmodels/qqmlitemmodels.qdoc create mode 100644 src/qmlmodels/qqmlitemselectionmodel.qdoc create mode 100644 src/qmlmodels/qqmllistaccessor.cpp create mode 100644 src/qmlmodels/qqmllistaccessor_p.h create mode 100644 src/qmlmodels/qqmllistcompositor.cpp create mode 100644 src/qmlmodels/qqmllistcompositor_p.h create mode 100644 src/qmlmodels/qqmllistmodel.cpp create mode 100644 src/qmlmodels/qqmllistmodel_p.h create mode 100644 src/qmlmodels/qqmllistmodel_p_p.h create mode 100644 src/qmlmodels/qqmllistmodelworkeragent.cpp create mode 100644 src/qmlmodels/qqmllistmodelworkeragent_p.h create mode 100644 src/qmlmodels/qqmlmodelsmodule.cpp create mode 100644 src/qmlmodels/qqmlmodelsmodule_p.h create mode 100644 src/qmlmodels/qqmlobjectmodel.cpp create mode 100644 src/qmlmodels/qqmlobjectmodel_p.h create mode 100644 src/qmlmodels/qqmltableinstancemodel.cpp create mode 100644 src/qmlmodels/qqmltableinstancemodel_p.h create mode 100644 src/qmlmodels/qqmltablemodel.cpp create mode 100644 src/qmlmodels/qqmltablemodel_p.h create mode 100644 src/qmlmodels/qqmltablemodelcolumn.cpp create mode 100644 src/qmlmodels/qqmltablemodelcolumn_p.h create mode 100644 src/qmlmodels/qquickpackage.cpp create mode 100644 src/qmlmodels/qquickpackage_p.h create mode 100644 src/qmlmodels/qtqmlmodelsglobal.h create mode 100644 src/qmlmodels/qtqmlmodelsglobal_p.h (limited to 'src') diff --git a/src/imports/labsmodels/labsmodels.pro b/src/imports/labsmodels/labsmodels.pro index 1795ae5e43..5ef2ad76f6 100644 --- a/src/imports/labsmodels/labsmodels.pro +++ b/src/imports/labsmodels/labsmodels.pro @@ -6,6 +6,6 @@ IMPORT_VERSION = 1.0 SOURCES += \ plugin.cpp -QT = qml-private +QT = qml-private qmlmodels-private load(qml_plugin) diff --git a/src/imports/models/models.pro b/src/imports/models/models.pro index fc87533cea..fd13b12401 100644 --- a/src/imports/models/models.pro +++ b/src/imports/models/models.pro @@ -6,6 +6,6 @@ IMPORT_VERSION = 2.$$QT_MINOR_VERSION SOURCES += \ plugin.cpp -QT = qml-private +QT = qml-private qmlmodels-private load(qml_plugin) diff --git a/src/imports/qtqml/plugin.cpp b/src/imports/qtqml/plugin.cpp index eb8c0ffc2f..7595d6d65b 100644 --- a/src/imports/qtqml/plugin.cpp +++ b/src/imports/qtqml/plugin.cpp @@ -43,7 +43,7 @@ #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -#include +#include #endif QT_BEGIN_NAMESPACE diff --git a/src/imports/qtqml/qtqml.pro b/src/imports/qtqml/qtqml.pro index d5bb313d0c..7a5169b8fc 100644 --- a/src/imports/qtqml/qtqml.pro +++ b/src/imports/qtqml/qtqml.pro @@ -6,6 +6,7 @@ IMPORT_VERSION = 2.$$QT_MINOR_VERSION SOURCES += \ plugin.cpp -QT = qml-private +# In Qt6 we won't need qmlmodels-private here +QT = qml-private qmlmodels-private load(qml_plugin) diff --git a/src/imports/qtquick2/plugin.cpp b/src/imports/qtquick2/plugin.cpp index 4dc6fee916..a5a2c73ced 100644 --- a/src/imports/qtquick2/plugin.cpp +++ b/src/imports/qtquick2/plugin.cpp @@ -38,8 +38,11 @@ ****************************************************************************/ #include + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include -#include +#include +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include diff --git a/src/imports/qtquick2/qtquick2.pro b/src/imports/qtquick2/qtquick2.pro index 744dce4195..1b45d69eb7 100644 --- a/src/imports/qtquick2/qtquick2.pro +++ b/src/imports/qtquick2/qtquick2.pro @@ -6,6 +6,6 @@ IMPORT_VERSION = 2.$$QT_MINOR_VERSION SOURCES += \ plugin.cpp -QT += quick-private qml-private +QT += quick-private qml-private qmlmodels-private load(qml_plugin) diff --git a/src/qml/configure.json b/src/qml/configure.json index 0f7de29594..2f88aef1fb 100644 --- a/src/qml/configure.json +++ b/src/qml/configure.json @@ -149,12 +149,6 @@ "section": "QML", "output": [ "privateFeature" ] }, - "qml-list-model": { - "label": "QML list model", - "purpose": "Provides the ListModel QML type.", - "section": "QML", - "output": [ "privateFeature" ] - }, "qml-xml-http-request": { "label": "QML XML http request", "purpose": "Provides support for sending XML http requests.", @@ -178,12 +172,6 @@ "condition": "features.animation", "output": [ "privateFeature" ] }, - "qml-delegate-model": { - "label": "QML delegate model", - "purpose": "Provides the DelegateModel QML type.", - "section": "QML", - "output": [ "privateFeature" ] - }, "qml-worker-script": { "label": "QML WorkerScript", "purpose": "Enables the use of threads in QML.", @@ -201,10 +189,8 @@ "qml-debug", "qml-jit", "qml-sequence-object", - "qml-list-model", "qml-xml-http-request", - "qml-locale", - "qml-delegate-model" + "qml-locale" ] } ] diff --git a/src/qml/jsruntime/qv4dateobject_p.h b/src/qml/jsruntime/qv4dateobject_p.h index 5b9934282c..a87eb92caf 100644 --- a/src/qml/jsruntime/qv4dateobject_p.h +++ b/src/qml/jsruntime/qv4dateobject_p.h @@ -96,7 +96,7 @@ struct DateObject: Object { double date() const { return d()->date; } void setDate(double date) { d()->date = date; } - QDateTime toQDateTime() const; + Q_QML_PRIVATE_EXPORT QDateTime toQDateTime() const; }; template<> diff --git a/src/qml/jsruntime/qv4functionobject_p.h b/src/qml/jsruntime/qv4functionobject_p.h index e03d49c74d..4fee26f341 100644 --- a/src/qml/jsruntime/qv4functionobject_p.h +++ b/src/qml/jsruntime/qv4functionobject_p.h @@ -87,11 +87,11 @@ DECLARE_HEAP_OBJECT(FunctionObject, Object) { } Q_QML_PRIVATE_EXPORT void init(QV4::ExecutionContext *scope, QV4::String *name, VTable::Call call); - void init(QV4::ExecutionContext *scope, QV4::String *name = nullptr); - void init(QV4::ExecutionContext *scope, QV4::Function *function, QV4::String *n = nullptr); - void init(QV4::ExecutionContext *scope, const QString &name); - void init(); - void destroy(); + Q_QML_PRIVATE_EXPORT void init(QV4::ExecutionContext *scope, QV4::String *name = nullptr); + Q_QML_PRIVATE_EXPORT void init(QV4::ExecutionContext *scope, QV4::Function *function, QV4::String *n = nullptr); + Q_QML_PRIVATE_EXPORT void init(QV4::ExecutionContext *scope, const QString &name); + Q_QML_PRIVATE_EXPORT void init(); + Q_QML_PRIVATE_EXPORT void destroy(); void setFunction(Function *f); @@ -260,7 +260,7 @@ struct FunctionPrototype: FunctionObject static ReturnedValue method_hasInstance(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; -struct IndexedBuiltinFunction : FunctionObject +struct Q_QML_PRIVATE_EXPORT IndexedBuiltinFunction : FunctionObject { V4_OBJECT2(IndexedBuiltinFunction, FunctionObject) }; diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 7309749a81..f2e0afd797 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -64,7 +64,7 @@ QT_BEGIN_NAMESPACE namespace QV4 { -struct Lookup { +struct Q_QML_PRIVATE_EXPORT Lookup { union { ReturnedValue (*getter)(Lookup *l, ExecutionEngine *engine, const Value &object); ReturnedValue (*globalGetter)(Lookup *l, ExecutionEngine *engine); diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index 567382cbc0..38055ef407 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -410,7 +410,7 @@ private: friend struct ObjectPrototype; }; -struct ObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator +struct Q_QML_PRIVATE_EXPORT ObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator { uint arrayIndex = 0; uint memberIndex = 0; diff --git a/src/qml/jsruntime/qv4propertykey_p.h b/src/qml/jsruntime/qv4propertykey_p.h index 523afd4ccf..b2a2ec3dea 100644 --- a/src/qml/jsruntime/qv4propertykey_p.h +++ b/src/qml/jsruntime/qv4propertykey_p.h @@ -124,7 +124,7 @@ public: return m(); } - bool isString() const; + Q_QML_EXPORT bool isString() const; bool isSymbol() const; bool isCanonicalNumericIndexString() const; diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp index a84521e205..a5e62d3e35 100644 --- a/src/qml/jsruntime/qv4serialize.cpp +++ b/src/qml/jsruntime/qv4serialize.cpp @@ -39,11 +39,6 @@ #include "qv4serialize_p.h" -#if QT_CONFIG(qml_list_model) -#include -#include -#endif - #include #include #include @@ -85,9 +80,7 @@ enum Type { WorkerNumber, WorkerDate, WorkerRegexp, -#if QT_CONFIG(qml_list_model) WorkerListModel, -#endif #if QT_CONFIG(qml_sequence_object) WorkerSequence #endif @@ -235,18 +228,15 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine } else if (const QObjectWrapper *qobjectWrapper = v.as()) { // XXX TODO: Generalize passing objects between the main thread and worker scripts so // that others can trivially plug in their elements. -#if QT_CONFIG(qml_list_model) - QQmlListModel *lm = qobject_cast(qobjectWrapper->object()); - if (lm && lm->agent()) { - QQmlListModelWorkerAgent *agent = lm->agent(); - agent->addref(); - push(data, valueheader(WorkerListModel)); - push(data, (void *)agent); - return; + if (QObject *lm = qobjectWrapper->object()) { + if (QObject *agent = qvariant_cast(lm->property("agent"))) { + if (QMetaObject::invokeMethod(agent, "addref")) { + push(data, valueheader(WorkerListModel)); + push(data, (void *)agent); + return; + } + } } -#else - Q_UNUSED(qobjectWrapper); -#endif // No other QObject's are allowed to be sent push(data, valueheader(WorkerUndefined)); } else if (const Object *o = v.as()) { @@ -298,6 +288,41 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine } } +struct VariantRef +{ + VariantRef() : obj(nullptr) {} + VariantRef(const VariantRef &r) : obj(r.obj) { addref(); } + VariantRef(QObject *a) : obj(a) { addref(); } + ~VariantRef() { release(); } + + VariantRef &operator=(const VariantRef &o) { + o.addref(); + release(); + obj = o.obj; + return *this; + } + + void addref() const + { + if (obj) + QMetaObject::invokeMethod(obj, "addref"); + } + + void release() const + { + if (obj) + QMetaObject::invokeMethod(obj, "release"); + + } + + QObject *obj; +}; + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(VariantRef) +Q_DECLARE_METATYPE(QV4::ExecutionEngine *) +QT_BEGIN_NAMESPACE + ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) { quint32 header = popUint32(data); @@ -366,24 +391,21 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) data += ALIGN(length * sizeof(quint16)); return Encode(engine->newRegExpObject(pattern, flags)); } -#if QT_CONFIG(qml_list_model) case WorkerListModel: { - void *ptr = popPtr(data); - QQmlListModelWorkerAgent *agent = (QQmlListModelWorkerAgent *)ptr; + QObject *agent = reinterpret_cast(popPtr(data)); QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, agent)); // ### Find a better solution then the ugly property - QQmlListModelWorkerAgent::VariantRef ref(agent); + VariantRef ref(agent); QVariant var = QVariant::fromValue(ref); QV4::ScopedValue v(scope, scope.engine->fromVariant(var)); QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref"))); rv->as()->defineReadonlyProperty(s, v); - agent->release(); - agent->setEngine(engine); + QMetaObject::invokeMethod(agent, "release"); + agent->setProperty("engine", QVariant::fromValue(engine)); return rv->asReturnedValue(); } -#endif #if QT_CONFIG(qml_sequence_object) case WorkerSequence: { @@ -423,4 +445,3 @@ ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *en } QT_END_NAMESPACE - diff --git a/src/qml/jsruntime/qv4vtable_p.h b/src/qml/jsruntime/qv4vtable_p.h index a4d91640c5..9dda104cd1 100644 --- a/src/qml/jsruntime/qv4vtable_p.h +++ b/src/qml/jsruntime/qv4vtable_p.h @@ -58,7 +58,7 @@ namespace QV4 { struct Lookup; -struct OwnPropertyKeyIterator { +struct Q_QML_PRIVATE_EXPORT OwnPropertyKeyIterator { virtual ~OwnPropertyKeyIterator() = 0; virtual PropertyKey next(const Object *o, Property *p = nullptr, PropertyAttributes *attrs = nullptr) = 0; }; diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h index 453c8ab8a8..92f2ccbb4a 100644 --- a/src/qml/qml/qqmljavascriptexpression_p.h +++ b/src/qml/qml/qqmljavascriptexpression_p.h @@ -182,7 +182,7 @@ private: QV4::Function *m_v4Function; }; -class QQmlPropertyCapture +class Q_QML_PRIVATE_EXPORT QQmlPropertyCapture { public: QQmlPropertyCapture(QQmlEngine *engine, QQmlJavaScriptExpression *e, QQmlJavaScriptExpression::DeleteWatcher *w) diff --git a/src/qml/types/qqmldelegatecomponent.cpp b/src/qml/types/qqmldelegatecomponent.cpp deleted file mode 100644 index 470f6cab6a..0000000000 --- a/src/qml/types/qqmldelegatecomponent.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/**************************************************************************** -** -** 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 "qqmldelegatecomponent_p.h" -#include - -QT_BEGIN_NAMESPACE - -QQmlAbstractDelegateComponent::QQmlAbstractDelegateComponent(QObject *parent) - : QQmlComponent(parent) -{ -} - -QQmlAbstractDelegateComponent::~QQmlAbstractDelegateComponent() -{ -} - -QVariant QQmlAbstractDelegateComponent::value(QQmlAdaptorModel *adaptorModel, int row, int column, const QString &role) const -{ - if (!adaptorModel) - return QVariant(); - return adaptorModel->value(adaptorModel->indexAt(row, column), role); -} - -/*! - \qmlmodule Qt.labs.qmlmodels 1.0 - \title Qt Labs QML Models - QML Types - \ingroup qmlmodules - \brief The Qt Labs QML Models module provides various model-related types for use with views. - - To use this module, import the module with the following line: - - \qml - import Qt.labs.qmlmodels 1.0 - \endqml -*/ - -/*! - \qmltype DelegateChoice - \instantiates QQmlDelegateChoice - \inqmlmodule Qt.labs.qmlmodels - \brief Encapsulates a delegate and when to use it. - - The DelegateChoice type wraps a delegate and defines the circumstances - in which it should be chosen. - - DelegateChoices can be nested inside a DelegateChooser. - - \sa DelegateChooser -*/ - -/*! - \qmlproperty string QtQml.Models::DelegateChoice::roleValue - This property holds the value used to match the role data for the role provided by \l DelegateChooser::role. -*/ -QVariant QQmlDelegateChoice::roleValue() const -{ - return m_value; -} - -void QQmlDelegateChoice::setRoleValue(const QVariant &value) -{ - if (m_value == value) - return; - m_value = value; - emit roleValueChanged(); - emit changed(); -} - -/*! - \qmlproperty index QtQml.Models::DelegateChoice::row - This property holds the value used to match the row value of model elements. - With models that have only the index property (and thus only one column), this property - should be intended as an index, and set to the desired index value. - - \note Setting both row and index has undefined behavior. The two are equivalent and only - one should be used. - - \sa index -*/ - -/*! - \qmlproperty index QtQml.Models::DelegateChoice::index - This property holds the value used to match the index value of model elements. - This is effectively an alias for \l row. - - \sa row -*/ -int QQmlDelegateChoice::row() const -{ - return m_row; -} - -void QQmlDelegateChoice::setRow(int r) -{ - if (m_row == r) - return; - m_row = r; - emit rowChanged(); - emit indexChanged(); - emit changed(); -} - -/*! - \qmlproperty index QtQml.Models::DelegateChoice::column - This property holds the value used to match the column value of model elements. -*/ -int QQmlDelegateChoice::column() const -{ - return m_column; -} - -void QQmlDelegateChoice::setColumn(int c) -{ - if (m_column == c) - return; - m_column = c; - emit columnChanged(); - emit changed(); -} - -QQmlComponent *QQmlDelegateChoice::delegate() const -{ - return m_delegate; -} - -/*! - \qmlproperty Component QtQml.Models::DelegateChoice::delegate - This property holds the delegate to use if this choice matches the model item. -*/ -void QQmlDelegateChoice::setDelegate(QQmlComponent *delegate) -{ - if (m_delegate == delegate) - return; - QQmlAbstractDelegateComponent *adc = static_cast(m_delegate); - if (adc) - disconnect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged); - m_delegate = delegate; - adc = static_cast(delegate); - if (adc) - connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged); - emit delegateChanged(); - emit changed(); -} - -bool QQmlDelegateChoice::match(int row, int column, const QVariant &value) const -{ - if (!m_value.isValid() && m_row < 0 && m_column < 0) - return true; - - const bool roleMatched = (m_value.isValid()) ? value == m_value : true; - const bool rowMatched = (m_row < 0 ) ? true : m_row == row; - const bool columnMatched = (m_column < 0 ) ? true : m_column == column; - return roleMatched && rowMatched && columnMatched; -} - -/*! - \qmltype DelegateChooser - \instantiates QQmlDelegateChooser - \inqmlmodule Qt.labs.qmlmodels - \brief Allows a view to use different delegates for different types of items in the model. - - The DelegateChooser is a special \l Component type intended for those scenarios where a Component is required - by a view and used as a delegate. - DelegateChooser encapsulates a set of \l {DelegateChoice}s. - These choices are used determine the delegate that will be instantiated for each - item in the model. - The selection of the choice is performed based on the value that a model item has for \l role, - and also based on index. - - \note This type is intended to transparently work only with TableView and any DelegateModel-based view. - Views (including user-defined views) that aren't internally based on a DelegateModel need to explicitly support - this type of component to make it function as described. - - \sa DelegateChoice -*/ - -/*! - \qmlproperty string QtQml.Models::DelegateChooser::role - This property holds the role used to determine the delegate for a given model item. - - \sa DelegateChoice -*/ -void QQmlDelegateChooser::setRole(const QString &role) -{ - if (m_role == role) - return; - m_role = role; - emit roleChanged(); -} - -/*! - \qmlproperty list QtQml.Models::DelegateChooser::choices - \default - - The list of DelegateChoices for the chooser. - - The list is treated as an ordered list, where the first DelegateChoice to match - will be used be a view. - - It should not generally be necessary to refer to the \c choices property, - as it is the default property for DelegateChooser and thus all child items are - automatically assigned to this property. -*/ - -QQmlListProperty QQmlDelegateChooser::choices() -{ - return QQmlListProperty(this, nullptr, - QQmlDelegateChooser::choices_append, - QQmlDelegateChooser::choices_count, - QQmlDelegateChooser::choices_at, - QQmlDelegateChooser::choices_clear); -} - -void QQmlDelegateChooser::choices_append(QQmlListProperty *prop, QQmlDelegateChoice *choice) -{ - QQmlDelegateChooser *q = static_cast(prop->object); - q->m_choices.append(choice); - connect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged); - q->delegateChanged(); -} - -int QQmlDelegateChooser::choices_count(QQmlListProperty *prop) -{ - QQmlDelegateChooser *q = static_cast(prop->object); - return q->m_choices.count(); -} - -QQmlDelegateChoice *QQmlDelegateChooser::choices_at(QQmlListProperty *prop, int index) -{ - QQmlDelegateChooser *q = static_cast(prop->object); - return q->m_choices.at(index); -} - -void QQmlDelegateChooser::choices_clear(QQmlListProperty *prop) -{ - QQmlDelegateChooser *q = static_cast(prop->object); - for (QQmlDelegateChoice *choice : q->m_choices) - disconnect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged); - q->m_choices.clear(); - q->delegateChanged(); -} - -QQmlComponent *QQmlDelegateChooser::delegate(QQmlAdaptorModel *adaptorModel, int row, int column) const -{ - QVariant v; - if (!m_role.isNull()) - v = value(adaptorModel, row, column, m_role); - if (!v.isValid()) { // check if the row only has modelData, for example if the row is a QVariantMap - v = value(adaptorModel, row, column, QStringLiteral("modelData")); - if (v.isValid()) - v = v.toMap().value(m_role); - } - // loop through choices, finding first one that fits - for (int i = 0; i < m_choices.count(); ++i) { - const QQmlDelegateChoice *choice = m_choices.at(i); - if (choice->match(row, column, v)) - return choice->delegate(); - } - - return nullptr; -} - -QT_END_NAMESPACE diff --git a/src/qml/types/qqmldelegatecomponent_p.h b/src/qml/types/qqmldelegatecomponent_p.h deleted file mode 100644 index c925ed9a60..0000000000 --- a/src/qml/types/qqmldelegatecomponent_p.h +++ /dev/null @@ -1,155 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QQMLDELEGATECOMPONENT_P_H -#define QQMLDELEGATECOMPONENT_P_H - -// -// 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. -// - -#include -#include - -QT_REQUIRE_CONFIG(qml_delegate_model); - -QT_BEGIN_NAMESPACE - -// TODO: consider making QQmlAbstractDelegateComponent public API -class QQmlAbstractDelegateComponentPrivate; -class QQmlAdaptorModel; -class Q_QML_PRIVATE_EXPORT QQmlAbstractDelegateComponent : public QQmlComponent -{ - Q_OBJECT -public: - QQmlAbstractDelegateComponent(QObject *parent = nullptr); - ~QQmlAbstractDelegateComponent() override; - - virtual QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = 0) const = 0; - -signals: - void delegateChanged(); - -protected: - QVariant value(QQmlAdaptorModel *adaptorModel,int row, int column, const QString &role) const; - -private: - Q_DECLARE_PRIVATE(QQmlAbstractDelegateComponent) - Q_DISABLE_COPY(QQmlAbstractDelegateComponent) -}; - -class Q_QML_PRIVATE_EXPORT QQmlDelegateChoice : public QObject -{ - Q_OBJECT - Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) - Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged) - Q_PROPERTY(int index READ row WRITE setRow NOTIFY indexChanged) - Q_PROPERTY(int column READ column WRITE setColumn NOTIFY columnChanged) - Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) - Q_CLASSINFO("DefaultProperty", "delegate") -public: - QVariant roleValue() const; - void setRoleValue(const QVariant &roleValue); - - int row() const; - void setRow(int r); - - int column() const; - void setColumn(int c); - - QQmlComponent *delegate() const; - void setDelegate(QQmlComponent *delegate); - - virtual bool match(int row, int column, const QVariant &value) const; - -signals: - void roleValueChanged(); - void rowChanged(); - void indexChanged(); - void columnChanged(); - void delegateChanged(); - void changed(); - -private: - QVariant m_value; - int m_row = -1; - int m_column = -1; - QQmlComponent *m_delegate = nullptr; -}; - -class Q_QML_PRIVATE_EXPORT QQmlDelegateChooser : public QQmlAbstractDelegateComponent -{ - Q_OBJECT - Q_PROPERTY(QString role READ role WRITE setRole NOTIFY roleChanged) - Q_PROPERTY(QQmlListProperty choices READ choices CONSTANT) - Q_CLASSINFO("DefaultProperty", "choices") - -public: - QString role() const { return m_role; } - void setRole(const QString &role); - - virtual QQmlListProperty choices(); - static void choices_append(QQmlListProperty *, QQmlDelegateChoice *); - static int choices_count(QQmlListProperty *); - static QQmlDelegateChoice *choices_at(QQmlListProperty *, int); - static void choices_clear(QQmlListProperty *); - - QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = -1) const override; - -signals: - void roleChanged(); - -private: - QString m_role; - QList m_choices; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlDelegateChoice) -QML_DECLARE_TYPE(QQmlDelegateChooser) - -#endif // QQMLDELEGATECOMPONENT_P_H diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp deleted file mode 100644 index 0e57119368..0000000000 --- a/src/qml/types/qqmldelegatemodel.cpp +++ /dev/null @@ -1,3540 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmldelegatemodel_p_p.h" -#include "qqmldelegatecomponent_p.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QQmlDelegateModelItem; - -namespace QV4 { - -namespace Heap { - -struct DelegateModelGroupFunction : FunctionObject { - void init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)); - - QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg); - uint flag; -}; - -struct QQmlDelegateModelGroupChange : Object { - void init() { Object::init(); } - - QQmlChangeSet::ChangeData change; -}; - -struct QQmlDelegateModelGroupChangeArray : Object { - void init(const QVector &changes); - void destroy() { - delete changes; - Object::destroy(); - } - - QVector *changes; -}; - - -} - -struct DelegateModelGroupFunction : QV4::FunctionObject -{ - V4_OBJECT2(DelegateModelGroupFunction, FunctionObject) - - static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) - { - return scope->engine()->memoryManager->allocate(scope, flag, code); - } - - static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc) - { - QV4::Scope scope(that->engine()); - QV4::Scoped f(scope, static_cast(that)); - QV4::Scoped o(scope, thisObject); - if (!o) - return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - - QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue()); - return f->d()->code(o->d()->item, f->d()->flag, v); - } -}; - -void Heap::DelegateModelGroupFunction::init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) -{ - QV4::Heap::FunctionObject::init(scope, QStringLiteral("DelegateModelGroupFunction")); - this->flag = flag; - this->code = code; -} - -} - -DEFINE_OBJECT_VTABLE(QV4::DelegateModelGroupFunction); - - - -class QQmlDelegateModelEngineData : public QV8Engine::Deletable -{ -public: - QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4); - ~QQmlDelegateModelEngineData(); - - QV4::ReturnedValue array(QV4::ExecutionEngine *engine, - const QVector &changes); - - QV4::PersistentValue changeProto; -}; - -V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData) - - -void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) -{ - prop.setWritable(false); -} - -QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id) -{ - QQmlDelegateModelParts *parts = static_cast(object()); - QQmlPartsModel *m = new QQmlPartsModel( - parts->model, QString::fromUtf8(name(id)), parts); - parts->models.append(m); - return QVariant::fromValue(static_cast(m)); -} - -QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent) -: QObject(parent), model(parent) -{ - new QQmlDelegateModelPartsMetaObject(this); -} - -//--------------------------------------------------------------------------- - -/*! - \qmltype DelegateModel - \instantiates QQmlDelegateModel - \inqmlmodule QtQml.Models - \brief Encapsulates a model and delegate. - - The DelegateModel type encapsulates a model and the delegate that will - be instantiated for items in the model. - - It is usually not necessary to create a DelegateModel. - However, it can be useful for manipulating and accessing the \l modelIndex - when a QAbstractItemModel subclass is used as the - model. Also, DelegateModel is used together with \l Package to - provide delegates to multiple views, and with DelegateModelGroup to sort and filter - delegate items. - - The example below illustrates using a DelegateModel with a ListView. - - \snippet delegatemodel/delegatemodel.qml 0 -*/ - -QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt) - : m_delegateChooser(nullptr) - , m_cacheMetaType(nullptr) - , m_context(ctxt) - , m_parts(nullptr) - , m_filterGroup(QStringLiteral("items")) - , m_count(0) - , m_groupCount(Compositor::MinimumGroupCount) - , m_compositorGroup(Compositor::Cache) - , m_complete(false) - , m_delegateValidated(false) - , m_reset(false) - , m_transaction(false) - , m_incubatorCleanupScheduled(false) - , m_waitingToFetchMore(false) - , m_cacheItems(nullptr) - , m_items(nullptr) - , m_persistedItems(nullptr) -{ -} - -QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() -{ - qDeleteAll(m_finishedIncubating); - - if (m_cacheMetaType) - m_cacheMetaType->release(); -} - -int QQmlDelegateModelPrivate::adaptorModelCount() const -{ - // QQmlDelegateModel currently only support list models. - // So even if a model is a table model, only the first - // column will be used. - return m_adaptorModel.rowCount(); -} - -void QQmlDelegateModelPrivate::requestMoreIfNecessary() -{ - Q_Q(QQmlDelegateModel); - if (!m_waitingToFetchMore && m_adaptorModel.canFetchMore()) { - m_waitingToFetchMore = true; - QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); - } -} - -void QQmlDelegateModelPrivate::init() -{ - Q_Q(QQmlDelegateModel); - m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag); - - m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q); - m_items->setDefaultInclude(true); - m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q); - QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this); -} - -QQmlDelegateModel::QQmlDelegateModel() - : QQmlDelegateModel(nullptr, nullptr) -{ -} - -QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent) -: QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent) -{ - Q_D(QQmlDelegateModel); - d->init(); -} - -QQmlDelegateModel::~QQmlDelegateModel() -{ - Q_D(QQmlDelegateModel); - d->disconnectFromAbstractItemModel(); - d->m_adaptorModel.setObject(nullptr, this); - - for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) { - if (cacheItem->object) { - delete cacheItem->object; - - cacheItem->object = nullptr; - cacheItem->contextData->invalidate(); - Q_ASSERT(cacheItem->contextData->refCount == 1); - cacheItem->contextData = nullptr; - cacheItem->scriptRef -= 1; - } - cacheItem->groups &= ~Compositor::UnresolvedFlag; - cacheItem->objectRef = 0; - if (!cacheItem->isReferenced()) - delete cacheItem; - else if (cacheItem->incubationTask) - cacheItem->incubationTask->vdm = nullptr; - } -} - - -void QQmlDelegateModel::classBegin() -{ - Q_D(QQmlDelegateModel); - if (!d->m_context) - d->m_context = qmlContext(this); -} - -void QQmlDelegateModel::componentComplete() -{ - Q_D(QQmlDelegateModel); - d->m_complete = true; - - int defaultGroups = 0; - QStringList groupNames; - groupNames.append(QStringLiteral("items")); - groupNames.append(QStringLiteral("persistedItems")); - if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude) - defaultGroups |= Compositor::DefaultFlag; - if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude) - defaultGroups |= Compositor::PersistedFlag; - for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) { - QString name = d->m_groups[i]->name(); - if (name.isEmpty()) { - d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; - --d->m_groupCount; - --i; - } else if (name.at(0).isUpper()) { - qmlWarning(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter"); - d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; - --d->m_groupCount; - --i; - } else { - groupNames.append(name); - - QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]); - group->setModel(this, Compositor::Group(i)); - if (group->defaultInclude) - defaultGroups |= (1 << i); - } - } - - d->m_cacheMetaType = new QQmlDelegateModelItemMetaType( - d->m_context->engine()->handle(), this, groupNames); - - d->m_compositor.setGroupCount(d->m_groupCount); - d->m_compositor.setDefaultGroups(defaultGroups); - d->updateFilterGroup(); - - while (!d->m_pendingParts.isEmpty()) - static_cast(d->m_pendingParts.first())->updateFilterGroup(); - - QVector inserts; - d->m_count = d->adaptorModelCount(); - d->m_compositor.append( - &d->m_adaptorModel, - 0, - d->m_count, - defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag, - &inserts); - d->itemsInserted(inserts); - d->emitChanges(); - d->requestMoreIfNecessary(); -} - -/*! - \qmlproperty model QtQml.Models::DelegateModel::model - This property holds the model providing data for the DelegateModel. - - The model provides a set of data that is used to create the items - for a view. For large or dynamic datasets the model is usually - provided by a C++ model object. The C++ model object must be a \l - {QAbstractItemModel} subclass or a simple list. - - Models can also be created directly in QML, using a \l{ListModel} or - \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}. - - \sa {qml-data-models}{Data Models} - \keyword dm-model-property -*/ -QVariant QQmlDelegateModel::model() const -{ - Q_D(const QQmlDelegateModel); - return d->m_adaptorModel.model(); -} - -void QQmlDelegateModelPrivate::connectToAbstractItemModel() -{ - Q_Q(QQmlDelegateModel); - if (!m_adaptorModel.adaptsAim()) - return; - - auto aim = m_adaptorModel.aim(); - - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), - q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int))); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), - q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int))); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), - q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()), - q, QQmlDelegateModel, SLOT(_q_modelReset())); - qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), - q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); -} - -void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() -{ - Q_Q(QQmlDelegateModel); - if (!m_adaptorModel.adaptsAim()) - return; - - auto aim = m_adaptorModel.aim(); - - QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)), - q, SLOT(_q_rowsInserted(QModelIndex,int,int))); - QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); - QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)), - q, SLOT(_q_rowsRemoved(QModelIndex,int,int))); - QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), - q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); - QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); - QObject::disconnect(aim, SIGNAL(modelReset()), - q, SLOT(_q_modelReset())); - QObject::disconnect(aim, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), - q, SLOT(_q_layoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); -} - -void QQmlDelegateModel::setModel(const QVariant &model) -{ - Q_D(QQmlDelegateModel); - - if (d->m_complete) - _q_itemsRemoved(0, d->m_count); - - d->disconnectFromAbstractItemModel(); - d->m_adaptorModel.setModel(model, this, d->m_context->engine()); - d->connectToAbstractItemModel(); - - d->m_adaptorModel.replaceWatchedRoles(QList(), d->m_watchedRoles); - for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) { - d->m_adaptorModel.replaceWatchedRoles( - QList(), d->m_parts->models.at(i)->watchedRoles()); - } - - if (d->m_complete) { - _q_itemsInserted(0, d->adaptorModelCount()); - d->requestMoreIfNecessary(); - } -} - -/*! - \qmlproperty Component QtQml.Models::DelegateModel::delegate - - The delegate provides a template defining each item instantiated by a view. - The index is exposed as an accessible \c index property. Properties of the - model are also available depending upon the type of \l {qml-data-models}{Data Model}. -*/ -QQmlComponent *QQmlDelegateModel::delegate() const -{ - Q_D(const QQmlDelegateModel); - return d->m_delegate; -} - -void QQmlDelegateModel::setDelegate(QQmlComponent *delegate) -{ - Q_D(QQmlDelegateModel); - if (d->m_transaction) { - qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated."); - return; - } - if (d->m_delegate == delegate) - return; - bool wasValid = d->m_delegate != nullptr; - d->m_delegate.setObject(delegate, this); - d->m_delegateValidated = false; - if (d->m_delegateChooser) - QObject::disconnect(d->m_delegateChooserChanged); - - d->m_delegateChooser = nullptr; - if (delegate) { - QQmlAbstractDelegateComponent *adc = - qobject_cast(delegate); - if (adc) { - d->m_delegateChooser = adc; - d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, - [d](){ d->delegateChanged(); }); - } - } - d->delegateChanged(d->m_delegate, wasValid); -} - -/*! - \qmlproperty QModelIndex QtQml.Models::DelegateModel::rootIndex - - QAbstractItemModel provides a hierarchical tree of data, whereas - QML only operates on list data. \c rootIndex allows the children of - any node in a QAbstractItemModel to be provided by this model. - - This property only affects models of type QAbstractItemModel that - are hierarchical (e.g, a tree model). - - For example, here is a simple interactive file system browser. - When a directory name is clicked, the view's \c rootIndex is set to the - QModelIndex node of the clicked directory, thus updating the view to show - the new directory's contents. - - \c main.cpp: - \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0 - - \c view.qml: - \snippet delegatemodel/delegatemodel_rootindex/view.qml 0 - - If the \l {dm-model-property}{model} is a QAbstractItemModel subclass, - the delegate can also reference a \c hasModelChildren property (optionally - qualified by a \e model. prefix) that indicates whether the delegate's - model item has any child nodes. - - \sa modelIndex(), parentModelIndex() -*/ -QVariant QQmlDelegateModel::rootIndex() const -{ - Q_D(const QQmlDelegateModel); - return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex)); -} - -void QQmlDelegateModel::setRootIndex(const QVariant &root) -{ - Q_D(QQmlDelegateModel); - - QModelIndex modelIndex = qvariant_cast(root); - const bool changed = d->m_adaptorModel.rootIndex != modelIndex; - if (changed || !d->m_adaptorModel.isValid()) { - const int oldCount = d->m_count; - d->m_adaptorModel.rootIndex = modelIndex; - if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) { - // The previous root index was invalidated, so we need to reconnect the model. - d->disconnectFromAbstractItemModel(); - d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine()); - d->connectToAbstractItemModel(); - } - if (d->m_adaptorModel.canFetchMore()) - d->m_adaptorModel.fetchMore(); - if (d->m_complete) { - const int newCount = d->adaptorModelCount(); - if (oldCount) - _q_itemsRemoved(0, oldCount); - if (newCount) - _q_itemsInserted(0, newCount); - } - if (changed) - emit rootIndexChanged(); - } -} - -/*! - \qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index) - - QAbstractItemModel provides a hierarchical tree of data, whereas - QML only operates on list data. This function assists in using - tree models in QML. - - Returns a QModelIndex for the specified index. - This value can be assigned to rootIndex. - - \sa rootIndex -*/ -QVariant QQmlDelegateModel::modelIndex(int idx) const -{ - Q_D(const QQmlDelegateModel); - return d->m_adaptorModel.modelIndex(idx); -} - -/*! - \qmlmethod QModelIndex QtQml.Models::DelegateModel::parentModelIndex() - - QAbstractItemModel provides a hierarchical tree of data, whereas - QML only operates on list data. This function assists in using - tree models in QML. - - Returns a QModelIndex for the parent of the current rootIndex. - This value can be assigned to rootIndex. - - \sa rootIndex -*/ -QVariant QQmlDelegateModel::parentModelIndex() const -{ - Q_D(const QQmlDelegateModel); - return d->m_adaptorModel.parentModelIndex(); -} - -/*! - \qmlproperty int QtQml.Models::DelegateModel::count -*/ - -int QQmlDelegateModel::count() const -{ - Q_D(const QQmlDelegateModel); - if (!d->m_delegate) - return 0; - return d->m_compositor.count(d->m_compositorGroup); -} - -QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object) -{ - if (!object) - return QQmlDelegateModel::ReleaseFlags(0); - - QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object); - if (!cacheItem) - return QQmlDelegateModel::ReleaseFlags(0); - - if (!cacheItem->releaseObject()) - return QQmlDelegateModel::Referenced; - - cacheItem->destroyObject(); - emitDestroyingItem(object); - if (cacheItem->incubationTask) { - releaseIncubator(cacheItem->incubationTask); - cacheItem->incubationTask = nullptr; - } - cacheItem->Dispose(); - return QQmlInstanceModel::Destroyed; -} - -/* - Returns ReleaseStatus flags. -*/ - -QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) -{ - Q_D(QQmlDelegateModel); - QQmlInstanceModel::ReleaseFlags stat = d->release(item); - return stat; -} - -// Cancel a requested async item -void QQmlDelegateModel::cancel(int index) -{ - Q_D(QQmlDelegateModel); - if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { - qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup); - return; - } - - Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); - QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0; - if (cacheItem) { - if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) { - d->releaseIncubator(cacheItem->incubationTask); - cacheItem->incubationTask = nullptr; - - if (cacheItem->object) { - QObject *object = cacheItem->object; - cacheItem->destroyObject(); - if (QQuickPackage *package = qmlobject_cast(object)) - d->emitDestroyingPackage(package); - else - d->emitDestroyingItem(object); - } - - cacheItem->scriptRef -= 1; - } - if (!cacheItem->isReferenced()) { - d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag); - d->m_cache.removeAt(it.cacheIndex); - delete cacheItem; - Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache)); - } - } -} - -void QQmlDelegateModelPrivate::group_append( - QQmlListProperty *property, QQmlDelegateModelGroup *group) -{ - QQmlDelegateModelPrivate *d = static_cast(property->data); - if (d->m_complete) - return; - if (d->m_groupCount == Compositor::MaximumGroupCount) { - qmlWarning(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8"); - return; - } - d->m_groups[d->m_groupCount] = group; - d->m_groupCount += 1; -} - -int QQmlDelegateModelPrivate::group_count( - QQmlListProperty *property) -{ - QQmlDelegateModelPrivate *d = static_cast(property->data); - return d->m_groupCount - 1; -} - -QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at( - QQmlListProperty *property, int index) -{ - QQmlDelegateModelPrivate *d = static_cast(property->data); - return index >= 0 && index < d->m_groupCount - 1 - ? d->m_groups[index + 1] - : nullptr; -} - -/*! - \qmlproperty list QtQml.Models::DelegateModel::groups - - This property holds a delegate model's group definitions. - - Groups define a sub-set of the items in a delegate model and can be used to filter - a model. - - For every group defined in a DelegateModel two attached properties are added to each - delegate item. The first of the form DelegateModel.in\e{GroupName} holds whether the - item belongs to the group and the second DelegateModel.\e{groupName}Index holds the - index of the item in that group. - - The following example illustrates using groups to select items in a model. - - \snippet delegatemodel/delegatemodelgroup.qml 0 - \keyword dm-groups-property -*/ - -QQmlListProperty QQmlDelegateModel::groups() -{ - Q_D(QQmlDelegateModel); - return QQmlListProperty( - this, - d, - QQmlDelegateModelPrivate::group_append, - QQmlDelegateModelPrivate::group_count, - QQmlDelegateModelPrivate::group_at, - nullptr); -} - -/*! - \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items - - This property holds default group to which all new items are added. -*/ - -QQmlDelegateModelGroup *QQmlDelegateModel::items() -{ - Q_D(QQmlDelegateModel); - return d->m_items; -} - -/*! - \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems - - This property holds delegate model's persisted items group. - - Items in this group are not destroyed when released by a view, instead they are persisted - until removed from the group. - - An item can be removed from the persistedItems group by setting the - DelegateModel.inPersistedItems property to false. If the item is not referenced by a view - at that time it will be destroyed. Adding an item to this group will not create a new - instance. - - Items returned by the \l QtQml.Models::DelegateModelGroup::create() function are automatically added - to this group. -*/ - -QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems() -{ - Q_D(QQmlDelegateModel); - return d->m_persistedItems; -} - -/*! - \qmlproperty string QtQml.Models::DelegateModel::filterOnGroup - - This property holds name of the group that is used to filter the delegate model. - - Only items that belong to this group are visible to a view. - - By default this is the \l items group. -*/ - -QString QQmlDelegateModel::filterGroup() const -{ - Q_D(const QQmlDelegateModel); - return d->m_filterGroup; -} - -void QQmlDelegateModel::setFilterGroup(const QString &group) -{ - Q_D(QQmlDelegateModel); - - if (d->m_transaction) { - qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); - return; - } - - if (d->m_filterGroup != group) { - d->m_filterGroup = group; - d->updateFilterGroup(); - emit filterGroupChanged(); - } -} - -void QQmlDelegateModel::resetFilterGroup() -{ - setFilterGroup(QStringLiteral("items")); -} - -void QQmlDelegateModelPrivate::updateFilterGroup() -{ - Q_Q(QQmlDelegateModel); - if (!m_cacheMetaType) - return; - - QQmlListCompositor::Group previousGroup = m_compositorGroup; - m_compositorGroup = Compositor::Default; - for (int i = 1; i < m_groupCount; ++i) { - if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) { - m_compositorGroup = Compositor::Group(i); - break; - } - } - - QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this); - if (m_compositorGroup != previousGroup) { - QVector removes; - QVector inserts; - m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); - - QQmlChangeSet changeSet; - changeSet.move(removes, inserts); - emit q->modelUpdated(changeSet, false); - - if (changeSet.difference() != 0) - emit q->countChanged(); - - if (m_parts) { - auto partsCopy = m_parts->models; // deliberate; this may alter m_parts - for (QQmlPartsModel *model : qAsConst(partsCopy)) - model->updateFilterGroup(m_compositorGroup, changeSet); - } - } -} - -/*! - \qmlproperty object QtQml.Models::DelegateModel::parts - - The \a parts property selects a DelegateModel which creates - delegates from the part named. This is used in conjunction with - the \l Package type. - - For example, the code below selects a model which creates - delegates named \e list from a \l Package: - - \code - DelegateModel { - id: visualModel - delegate: Package { - Item { Package.name: "list" } - } - model: myModel - } - - ListView { - width: 200; height:200 - model: visualModel.parts.list - } - \endcode - - \sa Package -*/ - -QObject *QQmlDelegateModel::parts() -{ - Q_D(QQmlDelegateModel); - if (!d->m_parts) - d->m_parts = new QQmlDelegateModelParts(this); - return d->m_parts; -} - -const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const -{ - Q_D(const QQmlDelegateModel); - return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr; -} - -void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) -{ - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package); -} - -void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) -{ - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package); -} - -void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package) -{ - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package); -} - -static bool isDoneIncubating(QQmlIncubator::Status status) -{ - return status == QQmlIncubator::Ready || status == QQmlIncubator::Error; -} - -void QQDMIncubationTask::statusChanged(Status status) -{ - if (vdm) { - vdm->incubatorStatusChanged(this, status); - } else if (isDoneIncubating(status)) { - Q_ASSERT(incubating); - // The model was deleted from under our feet, cleanup ourselves - delete incubating->object; - incubating->object = nullptr; - if (incubating->contextData) { - incubating->contextData->invalidate(); - Q_ASSERT(incubating->contextData->refCount == 1); - incubating->contextData = nullptr; - } - incubating->scriptRef = 0; - incubating->deleteLater(); - } -} - -void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask) -{ - Q_Q(QQmlDelegateModel); - if (!incubationTask->isError()) - incubationTask->clear(); - m_finishedIncubating.append(incubationTask); - if (!m_incubatorCleanupScheduled) { - m_incubatorCleanupScheduled = true; - QCoreApplication::postEvent(q, new QEvent(QEvent::User)); - } -} - -void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it) -{ - m_cache.insert(it.cacheIndex, item); - m_compositor.setFlags(it, 1, Compositor::CacheFlag); - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); -} - -void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem) -{ - int cidx = m_cache.lastIndexOf(cacheItem); - if (cidx >= 0) { - m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag); - m_cache.removeAt(cidx); - } - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); -} - -void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status) -{ - if (!isDoneIncubating(status)) - return; - - const QList incubationTaskErrors = incubationTask->errors(); - - QQmlDelegateModelItem *cacheItem = incubationTask->incubating; - cacheItem->incubationTask = nullptr; - incubationTask->incubating = nullptr; - releaseIncubator(incubationTask); - - if (status == QQmlIncubator::Ready) { - cacheItem->referenceObject(); - if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) - emitCreatedPackage(incubationTask, package); - else - emitCreatedItem(incubationTask, cacheItem->object); - cacheItem->releaseObject(); - } else if (status == QQmlIncubator::Error) { - qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate"; - } - - if (!cacheItem->isObjectReferenced()) { - if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) - emitDestroyingPackage(package); - else - emitDestroyingItem(cacheItem->object); - delete cacheItem->object; - cacheItem->object = nullptr; - cacheItem->scriptRef -= 1; - if (cacheItem->contextData) { - cacheItem->contextData->invalidate(); - Q_ASSERT(cacheItem->contextData->refCount == 1); - } - cacheItem->contextData = nullptr; - - if (!cacheItem->isReferenced()) { - removeCacheItem(cacheItem); - delete cacheItem; - } - } -} - -void QQDMIncubationTask::setInitialState(QObject *o) -{ - vdm->setInitialState(this, o); -} - -void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) -{ - QQmlDelegateModelItem *cacheItem = incubationTask->incubating; - cacheItem->object = o; - - if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) - emitInitPackage(incubationTask, package); - else - emitInitItem(incubationTask, cacheItem->object); -} - -QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode) -{ - if (!m_delegate || index < 0 || index >= m_compositor.count(group)) { - qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group); - return nullptr; - } else if (!m_context || !m_context->isValid()) { - return nullptr; - } - - Compositor::iterator it = m_compositor.find(group, index); - - QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0; - - if (!cacheItem) { - cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex()); - if (!cacheItem) - return nullptr; - - cacheItem->groups = it->flags; - addCacheItem(cacheItem, it); - } - - // Bump the reference counts temporarily so neither the content data or the delegate object - // are deleted if incubatorStatusChanged() is called synchronously. - cacheItem->scriptRef += 1; - cacheItem->referenceObject(); - - if (cacheItem->incubationTask) { - bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); - if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) { - // previously requested async - now needed immediately - cacheItem->incubationTask->forceCompletion(); - } - } else if (!cacheItem->object) { - QQmlComponent *delegate = m_delegate; - if (m_delegateChooser) { - QQmlAbstractDelegateComponent *chooser = m_delegateChooser; - do { - delegate = chooser->delegate(&m_adaptorModel, index); - chooser = qobject_cast(delegate); - } while (chooser); - if (!delegate) - return nullptr; - } - - QQmlContext *creationContext = delegate->creationContext(); - - cacheItem->scriptRef += 1; - - cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode); - cacheItem->incubationTask->incubating = cacheItem; - cacheItem->incubationTask->clear(); - - for (int i = 1; i < m_groupCount; ++i) - cacheItem->incubationTask->index[i] = it.index[i]; - - QQmlContextData *ctxt = new QQmlContextData; - ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); - ctxt->contextObject = cacheItem; - cacheItem->contextData = ctxt; - - if (m_adaptorModel.hasProxyObject()) { - if (QQmlAdaptorModelProxyInterface *proxy - = qobject_cast(cacheItem)) { - ctxt = new QQmlContextData; - ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); - QObject *proxied = proxy->proxiedObject(); - ctxt->contextObject = proxied; - // We don't own the proxied object. We need to clear it if it goes away. - QObject::connect(proxied, &QObject::destroyed, - cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); - } - } - - QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate); - cp->incubateObject( - cacheItem->incubationTask, - delegate, - m_context->engine(), - ctxt, - QQmlContextData::get(m_context)); - } - - if (index == m_compositor.count(group) - 1) - requestMoreIfNecessary(); - - // Remove the temporary reference count. - cacheItem->scriptRef -= 1; - if (cacheItem->object && (!cacheItem->incubationTask || isDoneIncubating(cacheItem->incubationTask->status()))) - return cacheItem->object; - - cacheItem->releaseObject(); - if (!cacheItem->isReferenced()) { - removeCacheItem(cacheItem); - delete cacheItem; - } - - return nullptr; -} - -/* - If asynchronous is true or the component is being loaded asynchronously due - to an ancestor being loaded asynchronously, object() may return 0. In this - case createdItem() will be emitted when the object is available. The object - at this stage does not have any references, so object() must be called again - to ensure a reference is held. Any call to object() which returns a valid object - must be matched by a call to release() in order to destroy the object. -*/ -QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode) -{ - Q_D(QQmlDelegateModel); - if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { - qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup); - return nullptr; - } - - return d->object(d->m_compositorGroup, index, incubationMode); -} - -QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index) -{ - Q_D(QQmlDelegateModel); - Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); - if (!it->inCache()) - return QQmlIncubator::Null; - - if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask) - return incubationTask->status(); - - return QQmlIncubator::Ready; -} - -QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name) -{ - Compositor::iterator it = m_compositor.find(group, index); - if (QQmlAdaptorModel *model = it.list()) { - QString role = name; - int dot = name.indexOf(QLatin1Char('.')); - if (dot > 0) - role = name.left(dot); - QVariant value = model->value(it.modelIndex(), role); - while (dot > 0) { - QObject *obj = qvariant_cast(value); - if (!obj) - return QVariant(); - const int from = dot + 1; - dot = name.indexOf(QLatin1Char('.'), from); - value = obj->property(name.midRef(from, dot - from).toUtf8()); - } - return value; - } - return QVariant(); -} - -QVariant QQmlDelegateModel::variantValue(int index, const QString &role) -{ - Q_D(QQmlDelegateModel); - return d->variantValue(d->m_compositorGroup, index, role); -} - -int QQmlDelegateModel::indexOf(QObject *item, QObject *) const -{ - Q_D(const QQmlDelegateModel); - if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item)) - return cacheItem->groupIndex(d->m_compositorGroup); - return -1; -} - -void QQmlDelegateModel::setWatchedRoles(const QList &roles) -{ - Q_D(QQmlDelegateModel); - d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles); - d->m_watchedRoles = roles; -} - -void QQmlDelegateModelPrivate::addGroups( - Compositor::iterator from, int count, Compositor::Group group, int groupFlags) -{ - QVector inserts; - m_compositor.setFlags(from, count, group, groupFlags, &inserts); - itemsInserted(inserts); - emitChanges(); -} - -void QQmlDelegateModelPrivate::removeGroups( - Compositor::iterator from, int count, Compositor::Group group, int groupFlags) -{ - QVector removes; - m_compositor.clearFlags(from, count, group, groupFlags, &removes); - itemsRemoved(removes); - emitChanges(); -} - -void QQmlDelegateModelPrivate::setGroups( - Compositor::iterator from, int count, Compositor::Group group, int groupFlags) -{ - QVector removes; - QVector inserts; - - m_compositor.setFlags(from, count, group, groupFlags, &inserts); - itemsInserted(inserts); - const int removeFlags = ~groupFlags & Compositor::GroupMask; - - from = m_compositor.find(from.group, from.index[from.group]); - m_compositor.clearFlags(from, count, group, removeFlags, &removes); - itemsRemoved(removes); - emitChanges(); -} - -bool QQmlDelegateModel::event(QEvent *e) -{ - Q_D(QQmlDelegateModel); - if (e->type() == QEvent::UpdateRequest) { - d->m_waitingToFetchMore = false; - d->m_adaptorModel.fetchMore(); - } else if (e->type() == QEvent::User) { - d->m_incubatorCleanupScheduled = false; - qDeleteAll(d->m_finishedIncubating); - d->m_finishedIncubating.clear(); - } - return QQmlInstanceModel::event(e); -} - -void QQmlDelegateModelPrivate::itemsChanged(const QVector &changes) -{ - if (!m_delegate) - return; - - QVarLengthArray, Compositor::MaximumGroupCount> translatedChanges(m_groupCount); - - for (const Compositor::Change &change : changes) { - for (int i = 1; i < m_groupCount; ++i) { - if (change.inGroup(i)) { - translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count)); - } - } - } - - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i)); -} - -void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector &roles) -{ - Q_D(QQmlDelegateModel); - if (count <= 0 || !d->m_complete) - return; - - if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) { - QVector changes; - d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes); - d->itemsChanged(changes); - d->emitChanges(); - } -} - -static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas) -{ - if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { - for (int i = 1; i < count; ++i) - incubationTask->index[i] += deltas[i]; - } - if (QQmlDelegateModelAttached *attached = cacheItem->attached) { - for (int i = 1; i < qMin(count, Compositor::MaximumGroupCount); ++i) - attached->m_currentIndex[i] += deltas[i]; - } -} - -void QQmlDelegateModelPrivate::itemsInserted( - const QVector &inserts, - QVarLengthArray, Compositor::MaximumGroupCount> *translatedInserts, - QHash > *movedItems) -{ - int cacheIndex = 0; - - int inserted[Compositor::MaximumGroupCount]; - for (int i = 1; i < m_groupCount; ++i) - inserted[i] = 0; - - for (const Compositor::Insert &insert : inserts) { - for (; cacheIndex < insert.cacheIndex; ++cacheIndex) - incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted); - - for (int i = 1; i < m_groupCount; ++i) { - if (insert.inGroup(i)) { - (*translatedInserts)[i].append( - QQmlChangeSet::Change(insert.index[i], insert.count, insert.moveId)); - inserted[i] += insert.count; - } - } - - if (!insert.inCache()) - continue; - - if (movedItems && insert.isMove()) { - QList items = movedItems->take(insert.moveId); - Q_ASSERT(items.count() == insert.count); - m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex); - } - if (insert.inGroup()) { - for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) { - QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); - cacheItem->groups |= insert.flags & Compositor::GroupMask; - - if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { - for (int i = 1; i < m_groupCount; ++i) - incubationTask->index[i] = cacheItem->groups & (1 << i) - ? insert.index[i] + offset - : insert.index[i]; - } - if (QQmlDelegateModelAttached *attached = cacheItem->attached) { - for (int i = 1; i < m_groupCount; ++i) - attached->m_currentIndex[i] = cacheItem->groups & (1 << i) - ? insert.index[i] + offset - : insert.index[i]; - } - } - } else { - cacheIndex = insert.cacheIndex + insert.count; - } - } - for (const QList cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) - incrementIndexes(cache.at(cacheIndex), m_groupCount, inserted); -} - -void QQmlDelegateModelPrivate::itemsInserted(const QVector &inserts) -{ - QVarLengthArray, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); - itemsInserted(inserts, &translatedInserts); - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); - if (!m_delegate) - return; - - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i)); -} - -void QQmlDelegateModel::_q_itemsInserted(int index, int count) -{ - - Q_D(QQmlDelegateModel); - if (count <= 0 || !d->m_complete) - return; - - d->m_count += count; - - const QList cache = d->m_cache; - for (int i = 0, c = cache.count(); i < c; ++i) { - QQmlDelegateModelItem *item = cache.at(i); - if (item->modelIndex() >= index) { - const int newIndex = item->modelIndex() + count; - const int row = newIndex; - const int column = 0; - item->setModelIndex(newIndex, row, column); - } - } - - QVector inserts; - d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts); - d->itemsInserted(inserts); - d->emitChanges(); -} - -//### This method should be split in two. It will remove delegates, and it will re-render the list. -// When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on -// QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on -// QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is -// that the destruction of an item will emit a changed signal that ends up at the delegate, which -// in turn will try to load the data from the model (which should have already freed it), resulting -// in a use-after-free. See QTBUG-59256. -void QQmlDelegateModelPrivate::itemsRemoved( - const QVector &removes, - QVarLengthArray, Compositor::MaximumGroupCount> *translatedRemoves, - QHash > *movedItems) -{ - int cacheIndex = 0; - int removedCache = 0; - - int removed[Compositor::MaximumGroupCount]; - for (int i = 1; i < m_groupCount; ++i) - removed[i] = 0; - - for (const Compositor::Remove &remove : removes) { - for (; cacheIndex < remove.cacheIndex; ++cacheIndex) - incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed); - - for (int i = 1; i < m_groupCount; ++i) { - if (remove.inGroup(i)) { - (*translatedRemoves)[i].append( - QQmlChangeSet::Change(remove.index[i], remove.count, remove.moveId)); - removed[i] -= remove.count; - } - } - - if (!remove.inCache()) - continue; - - if (movedItems && remove.isMove()) { - movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count)); - QList::iterator begin = m_cache.begin() + remove.cacheIndex; - QList::iterator end = begin + remove.count; - m_cache.erase(begin, end); - } else { - for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) { - QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); - if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) { - QObject *object = cacheItem->object; - cacheItem->destroyObject(); - if (QQuickPackage *package = qmlobject_cast(object)) - emitDestroyingPackage(package); - else - emitDestroyingItem(object); - cacheItem->scriptRef -= 1; - } - if (!cacheItem->isReferenced()) { - m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag); - m_cache.removeAt(cacheIndex); - delete cacheItem; - --cacheIndex; - ++removedCache; - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); - } else if (remove.groups() == cacheItem->groups) { - cacheItem->groups = 0; - if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { - for (int i = 1; i < m_groupCount; ++i) - incubationTask->index[i] = -1; - } - if (QQmlDelegateModelAttached *attached = cacheItem->attached) { - for (int i = 1; i < m_groupCount; ++i) - attached->m_currentIndex[i] = -1; - } - } else { - if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { - if (!cacheItem->isObjectReferenced()) { - releaseIncubator(cacheItem->incubationTask); - cacheItem->incubationTask = nullptr; - if (cacheItem->object) { - QObject *object = cacheItem->object; - cacheItem->destroyObject(); - if (QQuickPackage *package = qmlobject_cast(object)) - emitDestroyingPackage(package); - else - emitDestroyingItem(object); - } - cacheItem->scriptRef -= 1; - } else { - for (int i = 1; i < m_groupCount; ++i) { - if (remove.inGroup(i)) - incubationTask->index[i] = remove.index[i]; - } - } - } - if (QQmlDelegateModelAttached *attached = cacheItem->attached) { - for (int i = 1; i < m_groupCount; ++i) { - if (remove.inGroup(i)) - attached->m_currentIndex[i] = remove.index[i]; - } - } - cacheItem->groups &= ~remove.flags; - } - } - } - } - - for (const QList cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) - incrementIndexes(cache.at(cacheIndex), m_groupCount, removed); -} - -void QQmlDelegateModelPrivate::itemsRemoved(const QVector &removes) -{ - QVarLengthArray, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); - itemsRemoved(removes, &translatedRemoves); - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); - if (!m_delegate) - return; - - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i)); -} - -void QQmlDelegateModel::_q_itemsRemoved(int index, int count) -{ - Q_D(QQmlDelegateModel); - if (count <= 0|| !d->m_complete) - return; - - d->m_count -= count; - const QList cache = d->m_cache; - for (int i = 0, c = cache.count(); i < c; ++i) { - QQmlDelegateModelItem *item = cache.at(i); - // layout change triggered by removal of a previous item might have - // already invalidated this item in d->m_cache and deleted it - if (!d->m_cache.contains(item)) - continue; - - if (item->modelIndex() >= index + count) { - const int newIndex = item->modelIndex() - count; - const int row = newIndex; - const int column = 0; - item->setModelIndex(newIndex, row, column); - } else if (item->modelIndex() >= index) { - item->setModelIndex(-1, -1, -1); - } - } - - QVector removes; - d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes); - d->itemsRemoved(removes); - - d->emitChanges(); -} - -void QQmlDelegateModelPrivate::itemsMoved( - const QVector &removes, const QVector &inserts) -{ - QHash > movedItems; - - QVarLengthArray, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); - itemsRemoved(removes, &translatedRemoves, &movedItems); - - QVarLengthArray, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); - itemsInserted(inserts, &translatedInserts, &movedItems); - Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); - Q_ASSERT(movedItems.isEmpty()); - if (!m_delegate) - return; - - for (int i = 1; i < m_groupCount; ++i) { - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move( - translatedRemoves.at(i), - translatedInserts.at(i)); - } -} - -void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count) -{ - Q_D(QQmlDelegateModel); - if (count <= 0 || !d->m_complete) - return; - - const int minimum = qMin(from, to); - const int maximum = qMax(from, to) + count; - const int difference = from > to ? count : -count; - - const QList cache = d->m_cache; - for (int i = 0, c = cache.count(); i < c; ++i) { - QQmlDelegateModelItem *item = cache.at(i); - if (item->modelIndex() >= from && item->modelIndex() < from + count) { - const int newIndex = item->modelIndex() - from + to; - const int row = newIndex; - const int column = 0; - item->setModelIndex(newIndex, row, column); - } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) { - const int newIndex = item->modelIndex() + difference; - const int row = newIndex; - const int column = 0; - item->setModelIndex(newIndex, row, column); - } - } - - QVector removes; - QVector inserts; - d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts); - d->itemsMoved(removes, inserts); - d->emitChanges(); -} - -void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) -{ - Q_Q(QQmlDelegateModel); - emit q->modelUpdated(changeSet, reset); - if (changeSet.difference() != 0) - emit q->countChanged(); -} - -void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove) -{ - Q_Q(QQmlDelegateModel); - if (!m_complete) - return; - - if (m_transaction) { - qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated."); - return; - } - - if (remove) { - for (int i = 1; i < m_groupCount; ++i) { - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove( - 0, m_compositor.count(Compositor::Group(i))); - } - } - if (add) { - for (int i = 1; i < m_groupCount; ++i) { - QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert( - 0, m_compositor.count(Compositor::Group(i))); - } - } - emitChanges(); -} - -void QQmlDelegateModelPrivate::emitChanges() -{ - if (m_transaction || !m_complete || !m_context || !m_context->isValid()) - return; - - m_transaction = true; - QV4::ExecutionEngine *engine = m_context->engine()->handle(); - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine); - m_transaction = false; - - const bool reset = m_reset; - m_reset = false; - for (int i = 1; i < m_groupCount; ++i) - QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); - - auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache - for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) { - if (cacheItem->attached) - cacheItem->attached->emitChanges(); - } -} - -void QQmlDelegateModel::_q_modelReset() -{ - Q_D(QQmlDelegateModel); - if (!d->m_delegate) - return; - - int oldCount = d->m_count; - d->m_adaptorModel.rootIndex = QModelIndex(); - - if (d->m_complete) { - d->m_count = d->adaptorModelCount(); - - const QList cache = d->m_cache; - for (int i = 0, c = cache.count(); i < c; ++i) { - QQmlDelegateModelItem *item = cache.at(i); - if (item->modelIndex() != -1) - item->setModelIndex(-1, -1, -1); - } - - QVector removes; - QVector inserts; - if (oldCount) - d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); - if (d->m_count) - d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts); - d->itemsMoved(removes, inserts); - d->m_reset = true; - - if (d->m_adaptorModel.canFetchMore()) - d->m_adaptorModel.fetchMore(); - - d->emitChanges(); - } - emit rootIndexChanged(); -} - -void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) -{ - Q_D(QQmlDelegateModel); - if (parent == d->m_adaptorModel.rootIndex) - _q_itemsInserted(begin, end - begin + 1); -} - -void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) -{ - Q_D(QQmlDelegateModel); - if (!d->m_adaptorModel.rootIndex.isValid()) - return; - const QModelIndex index = d->m_adaptorModel.rootIndex; - if (index.parent() == parent && index.row() >= begin && index.row() <= end) { - const int oldCount = d->m_count; - d->m_count = 0; - d->disconnectFromAbstractItemModel(); - d->m_adaptorModel.invalidateModel(); - - if (d->m_complete && oldCount > 0) { - QVector removes; - d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); - d->itemsRemoved(removes); - d->emitChanges(); - } - } -} - -void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) -{ - Q_D(QQmlDelegateModel); - if (parent == d->m_adaptorModel.rootIndex) - _q_itemsRemoved(begin, end - begin + 1); -} - -void QQmlDelegateModel::_q_rowsMoved( - const QModelIndex &sourceParent, int sourceStart, int sourceEnd, - const QModelIndex &destinationParent, int destinationRow) -{ - Q_D(QQmlDelegateModel); - const int count = sourceEnd - sourceStart + 1; - if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) { - _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count); - } else if (sourceParent == d->m_adaptorModel.rootIndex) { - _q_itemsRemoved(sourceStart, count); - } else if (destinationParent == d->m_adaptorModel.rootIndex) { - _q_itemsInserted(destinationRow, count); - } -} - -void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector &roles) -{ - Q_D(QQmlDelegateModel); - if (begin.parent() == d->m_adaptorModel.rootIndex) - _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles); -} - -bool QQmlDelegateModel::isDescendantOf(const QPersistentModelIndex& desc, const QList< QPersistentModelIndex >& parents) const -{ - for (int i = 0, c = parents.count(); i < c; ++i) { - for (QPersistentModelIndex parent = desc; parent.isValid(); parent = parent.parent()) { - if (parent == parents[i]) - return true; - } - } - - return false; -} - -void QQmlDelegateModel::_q_layoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) -{ - Q_D(QQmlDelegateModel); - if (!d->m_complete) - return; - - if (hint == QAbstractItemModel::VerticalSortHint) { - if (!parents.isEmpty() && d->m_adaptorModel.rootIndex.isValid() && !isDescendantOf(d->m_adaptorModel.rootIndex, parents)) { - return; - } - - // mark all items as changed - _q_itemsChanged(0, d->m_count, QVector()); - - } else if (hint == QAbstractItemModel::HorizontalSortHint) { - // Ignored - } else { - // We don't know what's going on, so reset the model - _q_modelReset(); - } -} - -QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj) -{ - if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) { - if (cacheItem->object == obj) { // Don't create attached item for child objects. - cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj); - return cacheItem->attached; - } - } - return new QQmlDelegateModelAttached(obj); -} - -bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups) -{ - if (!m_context || !m_context->isValid()) - return false; - - QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1); - if (!cacheItem) - return false; - if (!object.isObject()) - return false; - - QV4::ExecutionEngine *v4 = object.as()->engine(); - QV4::Scope scope(v4); - QV4::ScopedObject o(scope, object); - if (!o) - return false; - - QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); - QV4::ScopedValue propertyName(scope); - QV4::ScopedValue v(scope); - while (1) { - propertyName = it.nextPropertyNameAsString(v); - if (propertyName->isNull()) - break; - cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QVariant::Invalid)); - } - - cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag; - - // Must be before the new object is inserted into the cache or its indexes will be adjusted too. - itemsInserted(QVector(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag))); - - before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups); - m_cache.insert(before.cacheIndex, cacheItem); - - return true; -} - -//============================================================================ - -QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType( - QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames) - : model(model) - , groupCount(groupNames.count() + 1) - , v4Engine(engine) - , metaObject(nullptr) - , groupNames(groupNames) -{ -} - -QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType() -{ - if (metaObject) - metaObject->release(); -} - -void QQmlDelegateModelItemMetaType::initializeMetaObject() -{ - QMetaObjectBuilder builder; - builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); - builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className()); - builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject); - - int notifierId = 0; - for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { - QString propertyName = QLatin1String("in") + groupNames.at(i); - propertyName.replace(2, 1, propertyName.at(2).toUpper()); - builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); - QMetaPropertyBuilder propertyBuilder = builder.addProperty( - propertyName.toUtf8(), "bool", notifierId); - propertyBuilder.setWritable(true); - } - for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { - const QString propertyName = groupNames.at(i) + QLatin1String("Index"); - builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); - QMetaPropertyBuilder propertyBuilder = builder.addProperty( - propertyName.toUtf8(), "int", notifierId); - propertyBuilder.setWritable(true); - } - - metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject()); -} - -void QQmlDelegateModelItemMetaType::initializePrototype() -{ - QV4::Scope scope(v4Engine); - - QV4::ScopedObject proto(scope, v4Engine->newObject()); - proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr); - proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups); - QV4::ScopedString s(scope); - QV4::ScopedProperty p(scope); - - s = v4Engine->newString(QStringLiteral("isUnresolved")); - QV4::ScopedFunctionObject f(scope); - QV4::ExecutionContext *global = scope.engine->rootContext(); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member))); - p->setSetter(nullptr); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - - s = v4Engine->newString(QStringLiteral("inItems")); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member))); - p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member))); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - - s = v4Engine->newString(QStringLiteral("inPersistedItems")); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member))); - p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member))); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - - s = v4Engine->newString(QStringLiteral("itemsIndex")); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index))); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - - s = v4Engine->newString(QStringLiteral("persistedItemsIndex")); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index))); - p->setSetter(nullptr); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - - for (int i = 2; i < groupNames.count(); ++i) { - QString propertyName = QLatin1String("in") + groupNames.at(i); - propertyName.replace(2, 1, propertyName.at(2).toUpper()); - s = v4Engine->newString(propertyName); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member))); - p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member))); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - } - for (int i = 2; i < groupNames.count(); ++i) { - const QString propertyName = groupNames.at(i) + QLatin1String("Index"); - s = v4Engine->newString(propertyName); - p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index))); - p->setSetter(nullptr); - proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); - } - modelItemProto.set(v4Engine, proto); -} - -int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const -{ - int groupFlags = 0; - for (const QString &groupName : groups) { - int index = groupNames.indexOf(groupName); - if (index != -1) - groupFlags |= 2 << index; - } - return groupFlags; -} - -int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const -{ - int groupFlags = 0; - QV4::Scope scope(v4Engine); - - QV4::ScopedString s(scope, groups); - if (s) { - const QString groupName = s->toQString(); - int index = groupNames.indexOf(groupName); - if (index != -1) - groupFlags |= 2 << index; - return groupFlags; - } - - QV4::ScopedArrayObject array(scope, groups); - if (array) { - QV4::ScopedValue v(scope); - uint arrayLength = array->getLength(); - for (uint i = 0; i < arrayLength; ++i) { - v = array->get(i); - const QString groupName = v->toQString(); - int index = groupNames.indexOf(groupName); - if (index != -1) - groupFlags |= 2 << index; - } - } - return groupFlags; -} - -QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) -{ - QV4::Scope scope(b); - QV4::Scoped o(scope, thisObject->as()); - if (!o) - return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - if (!o->d()->item->metaType->model) - RETURN_UNDEFINED(); - - return o->d()->item->get(); -} - -QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) -{ - QV4::Scope scope(b); - QV4::Scoped o(scope, thisObject->as()); - if (!o) - return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - - QStringList groups; - for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) { - if (o->d()->item->groups & (1 << i)) - groups.append(o->d()->item->metaType->groupNames.at(i - 1)); - } - - return scope.engine->fromVariant(groups); -} - -QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) -{ - QV4::Scope scope(b); - QV4::Scoped o(scope, thisObject->as()); - if (!o) - return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - - if (!argc) - THROW_TYPE_ERROR(); - - if (!o->d()->item->metaType->model) - RETURN_UNDEFINED(); - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model); - - const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]); - const int cacheIndex = model->m_cache.indexOf(o->d()->item); - Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); - model->setGroups(it, 1, Compositor::Cache, groupFlags); - return QV4::Encode::undefined(); -} - -QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) -{ - return QV4::Encode(bool(thisItem->groups & (1 << flag))); -} - -QV4::ReturnedValue QQmlDelegateModelItem::set_member(QQmlDelegateModelItem *cacheItem, uint flag, const QV4::Value &arg) -{ - if (!cacheItem->metaType->model) - return QV4::Encode::undefined(); - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model); - - bool member = arg.toBoolean(); - uint groupFlag = (1 << flag); - if (member == ((cacheItem->groups & groupFlag) != 0)) - return QV4::Encode::undefined(); - - const int cacheIndex = model->m_cache.indexOf(cacheItem); - Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); - if (member) - model->addGroups(it, 1, Compositor::Cache, groupFlag); - else - model->removeGroups(it, 1, Compositor::Cache, groupFlag); - return QV4::Encode::undefined(); -} - -QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) -{ - return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag))); -} - -void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject) -{ - if (!contextData) - return; - - for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) { - if (ctxt->contextObject == childContextObject) - ctxt->contextObject = nullptr; - } -} - - -//--------------------------------------------------------------------------- - -DEFINE_OBJECT_VTABLE(QQmlDelegateModelItemObject); - -void QV4::Heap::QQmlDelegateModelItemObject::destroy() -{ - item->Dispose(); - Object::destroy(); -} - - -QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, - QQmlAdaptorModel::Accessors *accessor, - int modelIndex, int row, int column) - : v4(metaType->v4Engine) - , metaType(metaType) - , contextData(nullptr) - , object(nullptr) - , attached(nullptr) - , incubationTask(nullptr) - , delegate(nullptr) - , poolTime(0) - , objectRef(0) - , scriptRef(0) - , groups(0) - , index(modelIndex) - , row(row) - , column(column) -{ - metaType->addref(); - - if (accessor->propertyCache) { - // The property cache in the accessor is common for all the model - // items in the model it wraps. It describes available model roles, - // together with revisioned properties like row, column and index, all - // which should be available in the delegate. We assign this cache to the - // model item so that the QML engine can use the revision information - // when resolving the properties (rather than falling back to just - // inspecting the QObject in the model item directly). - QQmlData *qmldata = QQmlData::get(this, true); - if (qmldata->propertyCache) - qmldata->propertyCache->release(); - qmldata->propertyCache = accessor->propertyCache.data(); - qmldata->propertyCache->addref(); - } -} - -QQmlDelegateModelItem::~QQmlDelegateModelItem() -{ - Q_ASSERT(scriptRef == 0); - Q_ASSERT(objectRef == 0); - Q_ASSERT(!object); - - if (incubationTask) { - if (metaType->model) - QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask); - else - delete incubationTask; - } - - metaType->release(); - -} - -void QQmlDelegateModelItem::Dispose() -{ - --scriptRef; - if (isReferenced()) - return; - - if (metaType->model) { - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); - model->removeCacheItem(this); - } - delete this; -} - -void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn) -{ - const int prevIndex = index; - const int prevRow = row; - const int prevColumn = column; - - index = idx; - row = newRow; - column = newColumn; - - if (idx != prevIndex) - emit modelIndexChanged(); - if (row != prevRow) - emit rowChanged(); - if (column != prevColumn) - emit columnChanged(); -} - -void QQmlDelegateModelItem::destroyObject() -{ - Q_ASSERT(object); - Q_ASSERT(contextData); - - QQmlData *data = QQmlData::get(object); - Q_ASSERT(data); - if (data->ownContext) { - data->ownContext->clearContext(); - if (data->ownContext->contextObject == object) - data->ownContext->contextObject = nullptr; - data->ownContext = nullptr; - data->context = nullptr; - } - object->deleteLater(); - - if (attached) { - attached->m_cacheItem = nullptr; - attached = nullptr; - } - - contextData->invalidate(); - contextData = nullptr; - object = nullptr; -} - -QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object) -{ - QQmlData *d = QQmlData::get(object); - QQmlContextData *context = d ? d->context : nullptr; - for (context = context ? context->parent : nullptr; context; context = context->parent) { - if (QQmlDelegateModelItem *cacheItem = qobject_cast( - context->contextObject)) { - return cacheItem; - } - } - return nullptr; -} - -int QQmlDelegateModelItem::groupIndex(Compositor::Group group) -{ - if (QQmlDelegateModelPrivate * const model = metaType->model - ? QQmlDelegateModelPrivate::get(metaType->model) - : nullptr) { - return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group]; - } - return -1; -} - -//--------------------------------------------------------------------------- - -QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject( - QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject) - : metaType(metaType) - , metaObject(metaObject) - , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount()) - , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count()) -{ - // Don't reference count the meta-type here as that would create a circular reference. - // Instead we rely the fact that the meta-type's reference count can't reach 0 without first - // destroying all delegates with attached objects. - *static_cast(this) = *metaObject; -} - -QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject() -{ - ::free(metaObject); -} - -void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *) -{ - release(); -} - -int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments) -{ - QQmlDelegateModelAttached *attached = static_cast(object); - if (call == QMetaObject::ReadProperty) { - if (_id >= indexPropertyOffset) { - Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1); - *static_cast(arguments[0]) = attached->m_currentIndex[group]; - return -1; - } else if (_id >= memberPropertyOffset) { - Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); - *static_cast(arguments[0]) = attached->m_cacheItem->groups & (1 << group); - return -1; - } - } else if (call == QMetaObject::WriteProperty) { - if (_id >= memberPropertyOffset) { - if (!metaType->model) - return -1; - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); - Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); - const int groupFlag = 1 << group; - const bool member = attached->m_cacheItem->groups & groupFlag; - if (member && !*static_cast(arguments[0])) { - Compositor::iterator it = model->m_compositor.find( - group, attached->m_currentIndex[group]); - model->removeGroups(it, 1, group, groupFlag); - } else if (!member && *static_cast(arguments[0])) { - for (int i = 1; i < metaType->groupCount; ++i) { - if (attached->m_cacheItem->groups & (1 << i)) { - Compositor::iterator it = model->m_compositor.find( - Compositor::Group(i), attached->m_currentIndex[i]); - model->addGroups(it, 1, Compositor::Group(i), groupFlag); - break; - } - } - } - return -1; - } - } - return attached->qt_metacall(call, _id, arguments); -} - -QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent) - : m_cacheItem(nullptr) - , m_previousGroups(0) -{ - QQml_setParent_noEvent(this, parent); -} - -QQmlDelegateModelAttached::QQmlDelegateModelAttached( - QQmlDelegateModelItem *cacheItem, QObject *parent) - : m_cacheItem(cacheItem) - , m_previousGroups(cacheItem->groups) -{ - QQml_setParent_noEvent(this, parent); - resetCurrentIndex(); - // Let m_previousIndex be equal to m_currentIndex - std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex)); - - if (!cacheItem->metaType->metaObject) - cacheItem->metaType->initializeMetaObject(); - - QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject; - cacheItem->metaType->metaObject->addref(); -} - -void QQmlDelegateModelAttached::resetCurrentIndex() -{ - if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) { - for (int i = 1; i < qMin(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i) - m_currentIndex[i] = incubationTask->index[i]; - } else { - QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); - Compositor::iterator it = model->m_compositor.find( - Compositor::Cache, model->m_cache.indexOf(m_cacheItem)); - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) - m_currentIndex[i] = it.index[i]; - } -} - -/*! - \qmlattachedproperty model QtQml.Models::DelegateModel::model - - This attached property holds the data model this delegate instance belongs to. - - It is attached to each instance of the delegate. -*/ - -QQmlDelegateModel *QQmlDelegateModelAttached::model() const -{ - return m_cacheItem ? m_cacheItem->metaType->model : nullptr; -} - -/*! - \qmlattachedproperty stringlist QtQml.Models::DelegateModel::groups - - This attached property holds the name of DelegateModelGroups the item belongs to. - - It is attached to each instance of the delegate. -*/ - -QStringList QQmlDelegateModelAttached::groups() const -{ - QStringList groups; - - if (!m_cacheItem) - return groups; - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { - if (m_cacheItem->groups & (1 << i)) - groups.append(m_cacheItem->metaType->groupNames.at(i - 1)); - } - return groups; -} - -void QQmlDelegateModelAttached::setGroups(const QStringList &groups) -{ - if (!m_cacheItem) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); - - const int groupFlags = model->m_cacheMetaType->parseGroups(groups); - const int cacheIndex = model->m_cache.indexOf(m_cacheItem); - Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); - model->setGroups(it, 1, Compositor::Cache, groupFlags); -} - -/*! - \qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved - - This attached property indicates whether the visual item is bound to a data model index. - Returns true if the item is not bound to the model, and false if it is. - - An unresolved item can be bound to the data model using the DelegateModelGroup::resolve() - function. - - It is attached to each instance of the delegate. -*/ - -bool QQmlDelegateModelAttached::isUnresolved() const -{ - if (!m_cacheItem) - return false; - - return m_cacheItem->groups & Compositor::UnresolvedFlag; -} - -/*! - \qmlattachedproperty int QtQml.Models::DelegateModel::inItems - - This attached property holds whether the item belongs to the default \l items DelegateModelGroup. - - Changing this property will add or remove the item from the items group. - - It is attached to each instance of the delegate. -*/ - -/*! - \qmlattachedproperty int QtQml.Models::DelegateModel::itemsIndex - - This attached property holds the index of the item in the default \l items DelegateModelGroup. - - It is attached to each instance of the delegate. -*/ - -/*! - \qmlattachedproperty int QtQml.Models::DelegateModel::inPersistedItems - - This attached property holds whether the item belongs to the \l persistedItems DelegateModelGroup. - - Changing this property will add or remove the item from the items group. Change with caution - as removing an item from the persistedItems group will destroy the current instance if it is - not referenced by a model. - - It is attached to each instance of the delegate. -*/ - -/*! - \qmlattachedproperty int QtQml.Models::DelegateModel::persistedItemsIndex - - This attached property holds the index of the item in the \l persistedItems DelegateModelGroup. - - It is attached to each instance of the delegate. -*/ - -void QQmlDelegateModelAttached::emitChanges() -{ - const int groupChanges = m_previousGroups ^ m_cacheItem->groups; - m_previousGroups = m_cacheItem->groups; - - int indexChanges = 0; - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { - if (m_previousIndex[i] != m_currentIndex[i]) { - m_previousIndex[i] = m_currentIndex[i]; - indexChanges |= (1 << i); - } - } - - int notifierId = 0; - const QMetaObject *meta = metaObject(); - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { - if (groupChanges & (1 << i)) - QMetaObject::activate(this, meta, notifierId, nullptr); - } - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { - if (indexChanges & (1 << i)) - QMetaObject::activate(this, meta, notifierId, nullptr); - } - - if (groupChanges) - emit groupsChanged(); -} - -//============================================================================ - -void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g) -{ - Q_ASSERT(!model); - model = m; - group = g; -} - -bool QQmlDelegateModelGroupPrivate::isChangedConnected() -{ - Q_Q(QQmlDelegateModelGroup); - IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &)); -} - -void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4) -{ - Q_Q(QQmlDelegateModelGroup); - if (isChangedConnected() && !changeSet.isEmpty()) { - emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())), - QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts()))); - } - if (changeSet.difference() != 0) - emit q->countChanged(); -} - -void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset) -{ - for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) - it->emitModelUpdated(changeSet, reset); - changeSet.clear(); -} - -typedef QQmlDelegateModelGroupEmitterList::iterator GroupEmitterListIt; - -void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package) -{ - for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) - it->createdPackage(index, package); -} - -void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package) -{ - for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) - it->initPackage(index, package); -} - -void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package) -{ - for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) - it->destroyingPackage(package); -} - -/*! - \qmltype DelegateModelGroup - \instantiates QQmlDelegateModelGroup - \inqmlmodule QtQml.Models - \ingroup qtquick-models - \brief Encapsulates a filtered set of visual data items. - - The DelegateModelGroup type provides a means to address the model data of a - DelegateModel's delegate items, as well as sort and filter these delegate - items. - - The initial set of instantiable delegate items in a DelegateModel is represented - by its \l {QtQml.Models::DelegateModel::items}{items} group, which normally directly reflects - the contents of the model assigned to DelegateModel::model. This set can be changed to - the contents of any other member of DelegateModel::groups by assigning the \l name of that - DelegateModelGroup to the DelegateModel::filterOnGroup property. - - The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns - information about group membership and indexes as well as model data. In combination - with the move() function this can be used to implement view sorting, with remove() to filter - items out of a view, or with setGroups() and \l Package delegates to categorize items into - different views. Different groups can only be sorted independently if they are disjunct. Moving - an item in one group will also move it in all other groups it is a part of. - - Data from models can be supplemented by inserting data directly into a DelegateModelGroup - with the insert() function. This can be used to introduce mock items into a view, or - placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes - available. - - Delegate items can also be instantiated directly from a DelegateModelGroup using the - create() function, making it possible to use DelegateModel without an accompanying view - type or to cherry-pick specific items that should be instantiated irregardless of whether - they're currently within a view's visible area. - - \sa {QML Dynamic View Ordering Tutorial} -*/ -QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent) - : QObject(*new QQmlDelegateModelGroupPrivate, parent) -{ -} - -QQmlDelegateModelGroup::QQmlDelegateModelGroup( - const QString &name, QQmlDelegateModel *model, int index, QObject *parent) - : QQmlDelegateModelGroup(parent) -{ - Q_D(QQmlDelegateModelGroup); - d->name = name; - d->setModel(model, Compositor::Group(index)); -} - -QQmlDelegateModelGroup::~QQmlDelegateModelGroup() -{ -} - -/*! - \qmlproperty string QtQml.Models::DelegateModelGroup::name - - This property holds the name of the group. - - Each group in a model must have a unique name starting with a lower case letter. -*/ - -QString QQmlDelegateModelGroup::name() const -{ - Q_D(const QQmlDelegateModelGroup); - return d->name; -} - -void QQmlDelegateModelGroup::setName(const QString &name) -{ - Q_D(QQmlDelegateModelGroup); - if (d->model) - return; - if (d->name != name) { - d->name = name; - emit nameChanged(); - } -} - -/*! - \qmlproperty int QtQml.Models::DelegateModelGroup::count - - This property holds the number of items in the group. -*/ - -int QQmlDelegateModelGroup::count() const -{ - Q_D(const QQmlDelegateModelGroup); - if (!d->model) - return 0; - return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group); -} - -/*! - \qmlproperty bool QtQml.Models::DelegateModelGroup::includeByDefault - - This property holds whether new items are assigned to this group by default. -*/ - -bool QQmlDelegateModelGroup::defaultInclude() const -{ - Q_D(const QQmlDelegateModelGroup); - return d->defaultInclude; -} - -void QQmlDelegateModelGroup::setDefaultInclude(bool include) -{ - Q_D(QQmlDelegateModelGroup); - if (d->defaultInclude != include) { - d->defaultInclude = include; - - if (d->model) { - if (include) - QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group); - else - QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group); - } - emit defaultIncludeChanged(); - } -} - -/*! - \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index) - - Returns a javascript object describing the item at \a index in the group. - - The returned object contains the same information that is available to a delegate from the - DelegateModel attached as well as the model for that item. It has the properties: - - \list - \li \b model The model data of the item. This is the same as the model context property in - a delegate - \li \b groups A list the of names of groups the item is a member of. This property can be - written to change the item's membership. - \li \b inItems Whether the item belongs to the \l {QtQml.Models::DelegateModel::items}{items} group. - Writing to this property will add or remove the item from the group. - \li \b itemsIndex The index of the item within the \l {QtQml.Models::DelegateModel::items}{items} group. - \li \b {in} Whether the item belongs to the dynamic group \e groupName. Writing to - this property will add or remove the item from the group. - \li \b {Index} The index of the item within the dynamic group \e groupName. - \li \b isUnresolved Whether the item is bound to an index in the model assigned to - DelegateModel::model. Returns true if the item is not bound to the model, and false if it is. - \endlist -*/ - -QJSValue QQmlDelegateModelGroup::get(int index) -{ - Q_D(QQmlDelegateModelGroup); - if (!d->model) - return QJSValue(); - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - if (!model->m_context || !model->m_context->isValid()) { - return QJSValue(); - } else if (index < 0 || index >= model->m_compositor.count(d->group)) { - qmlWarning(this) << tr("get: index out of range"); - return QJSValue(); - } - - Compositor::iterator it = model->m_compositor.find(d->group, index); - QQmlDelegateModelItem *cacheItem = it->inCache() - ? model->m_cache.at(it.cacheIndex) - : 0; - - if (!cacheItem) { - cacheItem = model->m_adaptorModel.createItem( - model->m_cacheMetaType, it.modelIndex()); - if (!cacheItem) - return QJSValue(); - cacheItem->groups = it->flags; - - model->m_cache.insert(it.cacheIndex, cacheItem); - model->m_compositor.setFlags(it, 1, Compositor::CacheFlag); - } - - if (model->m_cacheMetaType->modelItemProto.isUndefined()) - model->m_cacheMetaType->initializePrototype(); - QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine; - QV4::Scope scope(v4); - QV4::ScopedObject o(scope, v4->memoryManager->allocate(cacheItem)); - QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value()); - o->setPrototypeOf(p); - ++cacheItem->scriptRef; - - return QJSValue(v4, o->asReturnedValue()); -} - -bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const -{ - if (value.isNumber()) { - *index = value.toInt32(); - return true; - } - - if (!value.isObject()) - return false; - - QV4::ExecutionEngine *v4 = value.as()->engine(); - QV4::Scope scope(v4); - QV4::Scoped object(scope, value); - - if (object) { - QQmlDelegateModelItem * const cacheItem = object->d()->item; - if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model - ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model) - : nullptr) { - *index = model->m_cache.indexOf(cacheItem); - *group = Compositor::Cache; - return true; - } - } - return false; -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined) - \qmlmethod QtQml.Models::DelegateModelGroup::insert(jsdict data, var groups = undefined) - - Creates a new entry at \a index in a DelegateModel with the values from \a data that - correspond to roles in the model assigned to DelegateModel::model. - - If no index is supplied the data is appended to the model. - - The optional \a groups parameter identifies the groups the new entry should belong to, - if unspecified this is equal to the group insert was called on. - - Data inserted into a DelegateModel can later be merged with an existing entry in - DelegateModel::model using the \l resolve() function. This can be used to create placeholder - items that are later replaced by actual data. -*/ - -void QQmlDelegateModelGroup::insert(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - - int index = model->m_compositor.count(d->group); - Compositor::Group group = d->group; - - if (args->length() == 0) - return; - - int i = 0; - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[i]); - if (d->parseIndex(v, &index, &group)) { - if (index < 0 || index > model->m_compositor.count(group)) { - qmlWarning(this) << tr("insert: index out of range"); - return; - } - if (++i == args->length()) - return; - v = (*args)[i]; - } - - Compositor::insert_iterator before = index < model->m_compositor.count(group) - ? model->m_compositor.findInsertPosition(group, index) - : model->m_compositor.end(); - - int groups = 1 << d->group; - if (++i < args->length()) { - QV4::ScopedValue val(scope, (*args)[i]); - groups |= model->m_cacheMetaType->parseGroups(val); - } - - if (v->as()) { - return; - } else if (v->as()) { - model->insert(before, v, groups); - model->emitChanges(); - } -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::create(int index) - \qmlmethod QtQml.Models::DelegateModelGroup::create(int index, jsdict data, array groups = undefined) - \qmlmethod QtQml.Models::DelegateModelGroup::create(jsdict data, array groups = undefined) - - Returns a reference to the instantiated item at \a index in the group. - - If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item - referencing this new entry will be returned. The optional \a groups parameter identifies - the groups the new entry should belong to, if unspecified this is equal to the group create() - was called on. - - All items returned by create are added to the - \l {QtQml.Models::DelegateModel::persistedItems}{persistedItems} group. Items in this - group remain instantiated when not referenced by any view. -*/ - -void QQmlDelegateModelGroup::create(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - if (!d->model) - return; - - if (args->length() == 0) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - - int index = model->m_compositor.count(d->group); - Compositor::Group group = d->group; - - int i = 0; - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[i]); - if (d->parseIndex(v, &index, &group)) - ++i; - - if (i < args->length() && index >= 0 && index <= model->m_compositor.count(group)) { - v = (*args)[i]; - if (v->as()) { - int groups = 1 << d->group; - if (++i < args->length()) { - QV4::ScopedValue val(scope, (*args)[i]); - groups |= model->m_cacheMetaType->parseGroups(val); - } - - Compositor::insert_iterator before = index < model->m_compositor.count(group) - ? model->m_compositor.findInsertPosition(group, index) - : model->m_compositor.end(); - - index = before.index[d->group]; - group = d->group; - - if (!model->insert(before, v, groups)) { - return; - } - } - } - if (index < 0 || index >= model->m_compositor.count(group)) { - qmlWarning(this) << tr("create: index out of range"); - return; - } - - QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested); - if (object) { - QVector inserts; - Compositor::iterator it = model->m_compositor.find(group, index); - model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts); - model->itemsInserted(inserts); - model->m_cache.at(it.cacheIndex)->releaseObject(); - } - - args->setReturnValue(QV4::QObjectWrapper::wrap(args->v4engine(), object)); - model->emitChanges(); -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::resolve(int from, int to) - - Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to. - - Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup - instead of being derived from a DelegateModel::model index. Resolving an item will replace - the item at the target index with the unresolved item. A resolved an item will reflect the data - of the source model at its bound index and will move when that index moves like any other item. - - If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and - replacement will be communicated to views as an atomic operation, creating the appearance - that the model contents have not changed, or if the unresolved and model item are not adjacent - that the previously unresolved item has simply moved. - -*/ -void QQmlDelegateModelGroup::resolve(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - if (!d->model) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - - if (args->length() < 2) - return; - - int from = -1; - int to = -1; - Compositor::Group fromGroup = d->group; - Compositor::Group toGroup = d->group; - - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[0]); - if (d->parseIndex(v, &from, &fromGroup)) { - if (from < 0 || from >= model->m_compositor.count(fromGroup)) { - qmlWarning(this) << tr("resolve: from index out of range"); - return; - } - } else { - qmlWarning(this) << tr("resolve: from index invalid"); - return; - } - - v = (*args)[1]; - if (d->parseIndex(v, &to, &toGroup)) { - if (to < 0 || to >= model->m_compositor.count(toGroup)) { - qmlWarning(this) << tr("resolve: to index out of range"); - return; - } - } else { - qmlWarning(this) << tr("resolve: to index invalid"); - return; - } - - Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from); - Compositor::iterator toIt = model->m_compositor.find(toGroup, to); - - if (!fromIt->isUnresolved()) { - qmlWarning(this) << tr("resolve: from is not an unresolved item"); - return; - } - if (!toIt->list) { - qmlWarning(this) << tr("resolve: to is not a model item"); - return; - } - - const int unresolvedFlags = fromIt->flags; - const int resolvedFlags = toIt->flags; - const int resolvedIndex = toIt.modelIndex(); - void * const resolvedList = toIt->list; - - QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex); - cacheItem->groups &= ~Compositor::UnresolvedFlag; - - if (toIt.cacheIndex > fromIt.cacheIndex) - toIt.decrementIndexes(1, unresolvedFlags); - if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from) - from += 1; - - model->itemsMoved( - QVector(1, Compositor::Remove(fromIt, 1, unresolvedFlags, 0)), - QVector(1, Compositor::Insert(toIt, 1, unresolvedFlags, 0))); - model->itemsInserted( - QVector(1, Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag))); - toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags); - model->itemsRemoved(QVector(1, Compositor::Remove(toIt, 1, resolvedFlags))); - - model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag); - model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags); - - if (resolvedFlags & Compositor::CacheFlag) - model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag); - - Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); - - if (!cacheItem->isReferenced()) { - Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem)); - model->m_cache.removeAt(toIt.cacheIndex); - model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag); - delete cacheItem; - Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); - } else { - cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex); - if (cacheItem->attached) - cacheItem->attached->emitUnresolvedChanged(); - } - - model->emitChanges(); -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::remove(int index, int count) - - Removes \a count items starting at \a index from the group. -*/ - -void QQmlDelegateModelGroup::remove(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - if (!d->model) - return; - Compositor::Group group = d->group; - int index = -1; - int count = 1; - - if (args->length() == 0) - return; - - int i = 0; - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[0]); - if (!d->parseIndex(v, &index, &group)) { - qmlWarning(this) << tr("remove: invalid index"); - return; - } - - if (++i < args->length()) { - v = (*args)[i]; - if (v->isNumber()) - count = v->toInt32(); - } - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - if (index < 0 || index >= model->m_compositor.count(group)) { - qmlWarning(this) << tr("remove: index out of range"); - } else if (count != 0) { - Compositor::iterator it = model->m_compositor.find(group, index); - if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { - qmlWarning(this) << tr("remove: invalid count"); - } else { - model->removeGroups(it, count, d->group, 1 << d->group); - } - } -} - -bool QQmlDelegateModelGroupPrivate::parseGroupArgs( - QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const -{ - if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType) - return false; - - if (args->length() < 2) - return false; - - int i = 0; - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[i]); - if (!parseIndex(v, index, group)) - return false; - - v = (*args)[++i]; - if (v->isNumber()) { - *count = v->toInt32(); - - if (++i == args->length()) - return false; - v = (*args)[i]; - } - - *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v); - - return true; -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::addGroups(int index, int count, stringlist groups) - - Adds \a count items starting at \a index to \a groups. -*/ - -void QQmlDelegateModelGroup::addGroups(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - Compositor::Group group = d->group; - int index = -1; - int count = 1; - int groups = 0; - - if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - if (index < 0 || index >= model->m_compositor.count(group)) { - qmlWarning(this) << tr("addGroups: index out of range"); - } else if (count != 0) { - Compositor::iterator it = model->m_compositor.find(group, index); - if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { - qmlWarning(this) << tr("addGroups: invalid count"); - } else { - model->addGroups(it, count, d->group, groups); - } - } -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::removeGroups(int index, int count, stringlist groups) - - Removes \a count items starting at \a index from \a groups. -*/ - -void QQmlDelegateModelGroup::removeGroups(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - Compositor::Group group = d->group; - int index = -1; - int count = 1; - int groups = 0; - - if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - if (index < 0 || index >= model->m_compositor.count(group)) { - qmlWarning(this) << tr("removeGroups: index out of range"); - } else if (count != 0) { - Compositor::iterator it = model->m_compositor.find(group, index); - if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { - qmlWarning(this) << tr("removeGroups: invalid count"); - } else { - model->removeGroups(it, count, d->group, groups); - } - } -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) - - Sets the \a groups \a count items starting at \a index belong to. -*/ - -void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - Compositor::Group group = d->group; - int index = -1; - int count = 1; - int groups = 0; - - if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) - return; - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - if (index < 0 || index >= model->m_compositor.count(group)) { - qmlWarning(this) << tr("setGroups: index out of range"); - } else if (count != 0) { - Compositor::iterator it = model->m_compositor.find(group, index); - if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { - qmlWarning(this) << tr("setGroups: invalid count"); - } else { - model->setGroups(it, count, d->group, groups); - } - } -} - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) - - Sets the \a groups \a count items starting at \a index belong to. -*/ - -/*! - \qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count) - - Moves \a count at \a from in a group \a to a new position. - - \note The DelegateModel acts as a proxy model: it holds the delegates in a - different order than the \l{dm-model-property}{underlying model} has them. - Any subsequent changes to the underlying model will not undo whatever - reordering you have done via this function. -*/ - -void QQmlDelegateModelGroup::move(QQmlV4Function *args) -{ - Q_D(QQmlDelegateModelGroup); - - if (args->length() < 2) - return; - - Compositor::Group fromGroup = d->group; - Compositor::Group toGroup = d->group; - int from = -1; - int to = -1; - int count = 1; - - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue v(scope, (*args)[0]); - if (!d->parseIndex(v, &from, &fromGroup)) { - qmlWarning(this) << tr("move: invalid from index"); - return; - } - - v = (*args)[1]; - if (!d->parseIndex(v, &to, &toGroup)) { - qmlWarning(this) << tr("move: invalid to index"); - return; - } - - if (args->length() > 2) { - v = (*args)[2]; - if (v->isNumber()) - count = v->toInt32(); - } - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); - - if (count < 0) { - qmlWarning(this) << tr("move: invalid count"); - } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) { - qmlWarning(this) << tr("move: from index out of range"); - } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) { - qmlWarning(this) << tr("move: to index out of range"); - } else if (count > 0) { - QVector removes; - QVector inserts; - - model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts); - model->itemsMoved(removes, inserts); - model->emitChanges(); - } - -} - -/*! - \qmlsignal QtQml.Models::DelegateModelGroup::changed(array removed, array inserted) - - This signal is emitted when items have been removed from or inserted into the group. - - Each object in the \a removed and \a inserted arrays has two values; the \e index of the first - item inserted or removed and a \e count of the number of consecutive items inserted or removed. - - Each index is adjusted for previous changes with all removed items preceding any inserted - items. - - The corresponding handler is \c onChanged. -*/ - -//============================================================================ - -QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent) - : QQmlInstanceModel(*new QObjectPrivate, parent) - , m_model(model) - , m_part(part) - , m_compositorGroup(Compositor::Cache) - , m_inheritGroup(true) -{ - QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model); - if (d->m_cacheMetaType) { - QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this); - m_compositorGroup = Compositor::Default; - } else { - d->m_pendingParts.insert(this); - } -} - -QQmlPartsModel::~QQmlPartsModel() -{ -} - -QString QQmlPartsModel::filterGroup() const -{ - if (m_inheritGroup) - return m_model->filterGroup(); - return m_filterGroup; -} - -void QQmlPartsModel::setFilterGroup(const QString &group) -{ - if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) { - qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); - return; - } - - if (m_filterGroup != group || m_inheritGroup) { - m_filterGroup = group; - m_inheritGroup = false; - updateFilterGroup(); - - emit filterGroupChanged(); - } -} - -void QQmlPartsModel::resetFilterGroup() -{ - if (!m_inheritGroup) { - m_inheritGroup = true; - updateFilterGroup(); - emit filterGroupChanged(); - } -} - -void QQmlPartsModel::updateFilterGroup() -{ - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - if (!model->m_cacheMetaType) - return; - - if (m_inheritGroup) { - if (m_filterGroup == model->m_filterGroup) - return; - m_filterGroup = model->m_filterGroup; - } - - QQmlListCompositor::Group previousGroup = m_compositorGroup; - m_compositorGroup = Compositor::Default; - QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this); - for (int i = 1; i < model->m_groupCount; ++i) { - if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) { - m_compositorGroup = Compositor::Group(i); - break; - } - } - - QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this); - if (m_compositorGroup != previousGroup) { - QVector removes; - QVector inserts; - model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); - - QQmlChangeSet changeSet; - changeSet.move(removes, inserts); - if (!changeSet.isEmpty()) - emit modelUpdated(changeSet, false); - - if (changeSet.difference() != 0) - emit countChanged(); - } -} - -void QQmlPartsModel::updateFilterGroup( - Compositor::Group group, const QQmlChangeSet &changeSet) -{ - if (!m_inheritGroup) - return; - - m_compositorGroup = group; - QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this); - - if (!changeSet.isEmpty()) - emit modelUpdated(changeSet, false); - - if (changeSet.difference() != 0) - emit countChanged(); - - emit filterGroupChanged(); -} - -int QQmlPartsModel::count() const -{ - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - return model->m_delegate - ? model->m_compositor.count(m_compositorGroup) - : 0; -} - -bool QQmlPartsModel::isValid() const -{ - return m_model->isValid(); -} - -QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode) -{ - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - - if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) { - qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup); - return nullptr; - } - - QObject *object = model->object(m_compositorGroup, index, incubationMode); - - if (QQuickPackage *package = qmlobject_cast(object)) { - QObject *part = package->part(m_part); - if (!part) - return nullptr; - m_packaged.insertMulti(part, package); - return part; - } - - model->release(object); - if (!model->m_delegateValidated) { - if (object) - qmlWarning(model->m_delegate) << tr("Delegate component must be Package type."); - model->m_delegateValidated = true; - } - - return nullptr; -} - -QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) -{ - QQmlInstanceModel::ReleaseFlags flags = nullptr; - - QHash::iterator it = m_packaged.find(item); - if (it != m_packaged.end()) { - QQuickPackage *package = *it; - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - flags = model->release(package); - m_packaged.erase(it); - if (!m_packaged.contains(item)) - flags &= ~Referenced; - if (flags & Destroyed) - QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package); - } - return flags; -} - -QVariant QQmlPartsModel::variantValue(int index, const QString &role) -{ - return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role); -} - -void QQmlPartsModel::setWatchedRoles(const QList &roles) -{ - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles); - m_watchedRoles = roles; -} - -QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index) -{ - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index); - if (!it->inCache()) - return QQmlIncubator::Null; - - if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask) - return incubationTask->status(); - - return QQmlIncubator::Ready; -} - -int QQmlPartsModel::indexOf(QObject *item, QObject *) const -{ - QHash::const_iterator it = m_packaged.find(item); - if (it != m_packaged.end()) { - if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it)) - return cacheItem->groupIndex(m_compositorGroup); - } - return -1; -} - -void QQmlPartsModel::createdPackage(int index, QQuickPackage *package) -{ - emit createdItem(index, package->part(m_part)); -} - -void QQmlPartsModel::initPackage(int index, QQuickPackage *package) -{ - if (m_modelUpdatePending) - m_pendingPackageInitializations << index; - else - emit initItem(index, package->part(m_part)); -} - -void QQmlPartsModel::destroyingPackage(QQuickPackage *package) -{ - QObject *item = package->part(m_part); - Q_ASSERT(!m_packaged.contains(item)); - emit destroyingItem(item); -} - -void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) -{ - m_modelUpdatePending = false; - emit modelUpdated(changeSet, reset); - if (changeSet.difference() != 0) - emit countChanged(); - - QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); - QVector pendingPackageInitializations; - qSwap(pendingPackageInitializations, m_pendingPackageInitializations); - for (int index : pendingPackageInitializations) { - if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) - continue; - QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous); - if (QQuickPackage *package = qmlobject_cast(object)) - emit initItem(index, package->part(m_part)); - model->release(object); - } -} - -//============================================================================ - -struct QQmlDelegateModelGroupChange : QV4::Object -{ - V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object) - - static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) { - return e->memoryManager->allocate(); - } - - static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { - QV4::Scope scope(b); - QV4::Scoped that(scope, thisObject->as()); - if (!that) - THROW_TYPE_ERROR(); - return QV4::Encode(that->d()->change.index); - } - static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { - QV4::Scope scope(b); - QV4::Scoped that(scope, thisObject->as()); - if (!that) - THROW_TYPE_ERROR(); - return QV4::Encode(that->d()->change.count); - } - static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { - QV4::Scope scope(b); - QV4::Scoped that(scope, thisObject->as()); - if (!that) - THROW_TYPE_ERROR(); - if (that->d()->change.moveId < 0) - RETURN_UNDEFINED(); - return QV4::Encode(that->d()->change.moveId); - } -}; - -DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChange); - -struct QQmlDelegateModelGroupChangeArray : public QV4::Object -{ - V4_OBJECT2(QQmlDelegateModelGroupChangeArray, QV4::Object) - V4_NEEDS_DESTROY -public: - static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector &changes) - { - return engine->memoryManager->allocate(changes); - } - - quint32 count() const { return d()->changes->count(); } - const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); } - - static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty) - { - if (id.isArrayIndex()) { - uint index = id.asArrayIndex(); - Q_ASSERT(m->as()); - QV4::ExecutionEngine *v4 = static_cast(m)->engine(); - QV4::Scope scope(v4); - QV4::Scoped array(scope, static_cast(m)); - - if (index >= array->count()) { - if (hasProperty) - *hasProperty = false; - return QV4::Value::undefinedValue().asReturnedValue(); - } - - const QQmlChangeSet::Change &change = array->at(index); - - QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value()); - QV4::Scoped object(scope, QQmlDelegateModelGroupChange::create(v4)); - object->setPrototypeOf(changeProto); - object->d()->change = change; - - if (hasProperty) - *hasProperty = true; - return object.asReturnedValue(); - } - - Q_ASSERT(m->as()); - const QQmlDelegateModelGroupChangeArray *array = static_cast(m); - - if (id == array->engine()->id_length()->propertyKey()) { - if (hasProperty) - *hasProperty = true; - return QV4::Encode(array->count()); - } - - return Object::virtualGet(m, id, receiver, hasProperty); - } -}; - -void QV4::Heap::QQmlDelegateModelGroupChangeArray::init(const QVector &changes) -{ - Object::init(); - this->changes = new QVector(changes); - QV4::Scope scope(internalClass->engine); - QV4::ScopedObject o(scope, this); - o->setArrayType(QV4::Heap::ArrayData::Custom); -} - -DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChangeArray); - -QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4) -{ - QV4::Scope scope(v4); - - QV4::ScopedObject proto(scope, v4->newObject()); - proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr); - proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr); - proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr); - changeProto.set(v4, proto); -} - -QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData() -{ -} - -QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4, - const QVector &changes) -{ - QV4::Scope scope(v4); - QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes)); - return o.asReturnedValue(); -} - -QT_END_NAMESPACE - -#include "moc_qqmldelegatemodel_p.cpp" diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h deleted file mode 100644 index 2684162514..0000000000 --- a/src/qml/types/qqmldelegatemodel_p.h +++ /dev/null @@ -1,248 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLDATAMODEL_P_H -#define QQMLDATAMODEL_P_H - -// -// 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. -// - -#include -#include -#include -#include - -#include -#include - -#include - -QT_REQUIRE_CONFIG(qml_delegate_model); - -QT_BEGIN_NAMESPACE - -class QQmlChangeSet; -class QQuickPackage; -class QQmlV4Function; -class QQmlDelegateModelGroup; -class QQmlDelegateModelAttached; -class QQmlDelegateModelPrivate; - - -class Q_QML_PRIVATE_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQmlParserStatus -{ - Q_OBJECT - Q_DECLARE_PRIVATE(QQmlDelegateModel) - - Q_PROPERTY(QVariant model READ model WRITE setModel) - Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate) - Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup) - Q_PROPERTY(QQmlDelegateModelGroup *items READ items CONSTANT) //TODO : worth renaming? - Q_PROPERTY(QQmlDelegateModelGroup *persistedItems READ persistedItems CONSTANT) - Q_PROPERTY(QQmlListProperty groups READ groups CONSTANT) - Q_PROPERTY(QObject *parts READ parts CONSTANT) - Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged) - Q_CLASSINFO("DefaultProperty", "delegate") - Q_INTERFACES(QQmlParserStatus) -public: - QQmlDelegateModel(); - QQmlDelegateModel(QQmlContext *, QObject *parent=nullptr); - ~QQmlDelegateModel(); - - void classBegin() override; - void componentComplete() override; - - QVariant model() const; - void setModel(const QVariant &); - - QQmlComponent *delegate() const; - void setDelegate(QQmlComponent *); - - QVariant rootIndex() const; - void setRootIndex(const QVariant &root); - - Q_INVOKABLE QVariant modelIndex(int idx) const; - Q_INVOKABLE QVariant parentModelIndex() const; - - int count() const override; - bool isValid() const override { return delegate() != nullptr; } - QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override; - void cancel(int index) override; - QVariant variantValue(int index, const QString &role) override; - void setWatchedRoles(const QList &roles) override; - QQmlIncubator::Status incubationStatus(int index) override; - - int indexOf(QObject *object, QObject *objectContext) const override; - - QString filterGroup() const; - void setFilterGroup(const QString &group); - void resetFilterGroup(); - - QQmlDelegateModelGroup *items(); - QQmlDelegateModelGroup *persistedItems(); - QQmlListProperty groups(); - QObject *parts(); - - const QAbstractItemModel *abstractItemModel() const override; - - bool event(QEvent *) override; - - static QQmlDelegateModelAttached *qmlAttachedProperties(QObject *obj); - -Q_SIGNALS: - void filterGroupChanged(); - void defaultGroupsChanged(); - void rootIndexChanged(); - -private Q_SLOTS: - void _q_itemsChanged(int index, int count, const QVector &roles); - void _q_itemsInserted(int index, int count); - void _q_itemsRemoved(int index, int count); - void _q_itemsMoved(int from, int to, int count); - void _q_modelReset(); - void _q_rowsInserted(const QModelIndex &,int,int); - void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); - void _q_rowsRemoved(const QModelIndex &,int,int); - void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); - void _q_dataChanged(const QModelIndex&,const QModelIndex&,const QVector &); - void _q_layoutChanged(const QList&, QAbstractItemModel::LayoutChangeHint); - -private: - bool isDescendantOf(const QPersistentModelIndex &desc, const QList &parents) const; - - Q_DISABLE_COPY(QQmlDelegateModel) -}; - -class QQmlDelegateModelGroupPrivate; -class Q_QML_PRIVATE_EXPORT QQmlDelegateModelGroup : public QObject -{ - Q_OBJECT - Q_PROPERTY(int count READ count NOTIFY countChanged) - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - Q_PROPERTY(bool includeByDefault READ defaultInclude WRITE setDefaultInclude NOTIFY defaultIncludeChanged) -public: - QQmlDelegateModelGroup(QObject *parent = nullptr); - QQmlDelegateModelGroup(const QString &name, QQmlDelegateModel *model, int compositorType, QObject *parent = nullptr); - ~QQmlDelegateModelGroup(); - - QString name() const; - void setName(const QString &name); - - int count() const; - - bool defaultInclude() const; - void setDefaultInclude(bool include); - - Q_INVOKABLE QJSValue get(int index); - -public Q_SLOTS: - void insert(QQmlV4Function *); - void create(QQmlV4Function *); - void resolve(QQmlV4Function *); - void remove(QQmlV4Function *); - void addGroups(QQmlV4Function *); - void removeGroups(QQmlV4Function *); - void setGroups(QQmlV4Function *); - void move(QQmlV4Function *); - -Q_SIGNALS: - void countChanged(); - void nameChanged(); - void defaultIncludeChanged(); - void changed(const QJSValue &removed, const QJSValue &inserted); -private: - Q_DECLARE_PRIVATE(QQmlDelegateModelGroup) -}; - -class QQmlDelegateModelItem; -class QQmlDelegateModelAttachedMetaObject; -class QQmlDelegateModelAttached : public QObject -{ - Q_OBJECT - Q_PROPERTY(QQmlDelegateModel *model READ model CONSTANT) - Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged) - Q_PROPERTY(bool isUnresolved READ isUnresolved NOTIFY unresolvedChanged) -public: - QQmlDelegateModelAttached(QObject *parent); - QQmlDelegateModelAttached(QQmlDelegateModelItem *cacheItem, QObject *parent); - ~QQmlDelegateModelAttached() {} - - void resetCurrentIndex(); - void setCacheItem(QQmlDelegateModelItem *item); - - QQmlDelegateModel *model() const; - - QStringList groups() const; - void setGroups(const QStringList &groups); - - bool isUnresolved() const; - - void emitChanges(); - - void emitUnresolvedChanged() { Q_EMIT unresolvedChanged(); } - -Q_SIGNALS: - void groupsChanged(); - void unresolvedChanged(); - -public: - QQmlDelegateModelItem *m_cacheItem; - int m_previousGroups; - int m_currentIndex[QQmlListCompositor::MaximumGroupCount]; - int m_previousIndex[QQmlListCompositor::MaximumGroupCount]; - - friend class QQmlDelegateModelAttachedMetaObject; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlDelegateModel) -QML_DECLARE_TYPEINFO(QQmlDelegateModel, QML_HAS_ATTACHED_PROPERTIES) -QML_DECLARE_TYPE(QQmlDelegateModelGroup) - -#endif // QQMLDATAMODEL_P_H diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h deleted file mode 100644 index 7f10bbf370..0000000000 --- a/src/qml/types/qqmldelegatemodel_p_p.h +++ /dev/null @@ -1,450 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLDATAMODEL_P_P_H -#define QQMLDATAMODEL_P_P_H - -#include "qqmldelegatemodel_p.h" -#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 - -typedef QQmlListCompositor Compositor; - -class QQmlDelegateModelAttachedMetaObject; -class QQmlAbstractDelegateComponent; - -class Q_QML_PRIVATE_EXPORT QQmlDelegateModelItemMetaType : public QQmlRefCount -{ -public: - QQmlDelegateModelItemMetaType(QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames); - ~QQmlDelegateModelItemMetaType(); - - void initializeMetaObject(); - void initializePrototype(); - - int parseGroups(const QStringList &groupNames) const; - int parseGroups(const QV4::Value &groupNames) const; - - QPointer model; - const int groupCount; - QV4::ExecutionEngine * const v4Engine; - QQmlDelegateModelAttachedMetaObject *metaObject; - const QStringList groupNames; - QV4::PersistentValue modelItemProto; -}; - -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 12) - Q_PROPERTY(int column READ modelColumn NOTIFY columnChanged REVISION 12) - Q_PROPERTY(QObject *model READ modelObject CONSTANT) -public: - QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, - QQmlAdaptorModel::Accessors *accessor, int modelIndex, - int row, int column); - ~QQmlDelegateModelItem(); - - void referenceObject() { ++objectRef; } - bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); } - bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); } - void childContextObjectDestroyed(QObject *childContextObject); - - bool isReferenced() const { - return scriptRef - || incubationTask - || ((groups & Compositor::UnresolvedFlag) && (groups & Compositor::GroupMask)); - } - - void Dispose(); - - QObject *modelObject() { return this; } - - void destroyObject(); - - static QQmlDelegateModelItem *dataForObject(QObject *object); - - int groupIndex(Compositor::Group group); - - int modelRow() const { return row; } - int modelColumn() const { return column; } - int modelIndex() const { return index; } - virtual void setModelIndex(int idx, int newRow, int newColumn); - - virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); } - - virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); } - virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; } - - 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); - - QV4::ExecutionEngine *v4; - QQmlDelegateModelItemMetaType * const metaType; - QQmlContextDataRef contextData; - QPointer object; - QPointer attached; - QQDMIncubationTask *incubationTask; - QQmlComponent *delegate; - int poolTime; - int objectRef; - int scriptRef; - int groups; - int index; - -Q_SIGNALS: - void modelIndexChanged(); - Q_REVISION(12) void rowChanged(); - Q_REVISION(12) void columnChanged(); - -protected: - void objectDestroyed(QObject *); - int row; - int column; -}; - -namespace QV4 { -namespace Heap { -struct QQmlDelegateModelItemObject : Object { - inline void init(QQmlDelegateModelItem *item); - void destroy(); - QQmlDelegateModelItem *item; -}; - -} -} - -struct QQmlDelegateModelItemObject : QV4::Object -{ - V4_OBJECT2(QQmlDelegateModelItemObject, QV4::Object) - V4_NEEDS_DESTROY -}; - -void QV4::Heap::QQmlDelegateModelItemObject::init(QQmlDelegateModelItem *item) -{ - Object::init(); - this->item = item; -} - - - -class QQmlDelegateModelPrivate; -class QQDMIncubationTask : public QQmlIncubator -{ -public: - QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode) - : QQmlIncubator(mode) - , incubating(nullptr) - , vdm(l) {} - - void statusChanged(Status) override; - void setInitialState(QObject *) override; - - QQmlDelegateModelItem *incubating = nullptr; - QQmlDelegateModelPrivate *vdm = nullptr; - 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( - QQmlV4Function *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); - 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 updateFilterGroup(); - - 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); - - bool insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups); - - int adaptorModelCount() const; - - static void group_append(QQmlListProperty *property, QQmlDelegateModelGroup *group); - static int group_count(QQmlListProperty *property); - static QQmlDelegateModelGroup *group_at(QQmlListProperty *property, int 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; - QQmlDelegateModelItemMetaType *m_cacheMetaType; - QPointer m_context; - QQmlDelegateModelParts *m_parts; - QQmlDelegateModelGroupEmitterList m_pendingParts; - - QList m_cache; - 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) -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) 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; - QHash 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 : public QAbstractDynamicMetaObject, public QQmlRefCount -{ -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 diff --git a/src/qml/types/qqmlinstantiator.cpp b/src/qml/types/qqmlinstantiator.cpp deleted file mode 100644 index a23ec0f2b4..0000000000 --- a/src/qml/types/qqmlinstantiator.cpp +++ /dev/null @@ -1,509 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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 "qqmlinstantiator_p.h" -#include "qqmlinstantiator_p_p.h" -#include -#include -#include -#include -#include -#if QT_CONFIG(qml_delegate_model) -#include -#endif - -QT_BEGIN_NAMESPACE - -QQmlInstantiatorPrivate::QQmlInstantiatorPrivate() - : componentComplete(true) - , effectiveReset(false) - , active(true) - , async(false) -#if QT_CONFIG(qml_delegate_model) - , ownModel(false) -#endif - , requestedIndex(-1) - , model(QVariant(1)) - , instanceModel(nullptr) - , delegate(nullptr) -{ -} - -QQmlInstantiatorPrivate::~QQmlInstantiatorPrivate() -{ - qDeleteAll(objects); -} - -void QQmlInstantiatorPrivate::clear() -{ - Q_Q(QQmlInstantiator); - if (!instanceModel) - return; - if (!objects.count()) - return; - - for (int i=0; i < objects.count(); i++) { - q->objectRemoved(i, objects[i]); - instanceModel->release(objects[i]); - } - objects.clear(); - q->objectChanged(); -} - -QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async) -{ - requestedIndex = index; - QObject *o = instanceModel->object(index, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); - requestedIndex = -1; - return o; -} - - -void QQmlInstantiatorPrivate::regenerate() -{ - Q_Q(QQmlInstantiator); - if (!componentComplete) - return; - - int prevCount = q->count(); - - clear(); - - if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) { - if (prevCount) - q->countChanged(); - return; - } - - for (int i = 0; i < instanceModel->count(); i++) { - QObject *object = modelObject(i, async); - // If the item was already created we won't get a createdItem - if (object) - _q_createdItem(i, object); - } - if (q->count() != prevCount) - q->countChanged(); -} - -void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item) -{ - Q_Q(QQmlInstantiator); - if (objects.contains(item)) //Case when it was created synchronously in regenerate - return; - if (requestedIndex != idx) // Asynchronous creation, reference the object - (void)instanceModel->object(idx); - item->setParent(q); - if (objects.size() < idx + 1) { - int modelCount = instanceModel->count(); - if (objects.capacity() < modelCount) - objects.reserve(modelCount); - objects.resize(idx + 1); - } - if (QObject *o = objects.at(idx)) - instanceModel->release(o); - objects.replace(idx, item); - if (objects.count() == 1) - q->objectChanged(); - q->objectAdded(idx, item); -} - -void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset) -{ - Q_Q(QQmlInstantiator); - - if (!componentComplete || effectiveReset) - return; - - if (reset) { - regenerate(); - if (changeSet.difference() != 0) - q->countChanged(); - return; - } - - int difference = 0; - QHash > > moved; - const QVector &removes = changeSet.removes(); - for (const QQmlChangeSet::Change &remove : removes) { - int index = qMin(remove.index, objects.count()); - int count = qMin(remove.index + remove.count, objects.count()) - index; - if (remove.isMove()) { - moved.insert(remove.moveId, objects.mid(index, count)); - objects.erase( - objects.begin() + index, - objects.begin() + index + count); - } else while (count--) { - QObject *obj = objects.at(index); - objects.remove(index); - q->objectRemoved(index, obj); - if (obj) - instanceModel->release(obj); - } - - difference -= remove.count; - } - - const QVector &inserts = changeSet.inserts(); - for (const QQmlChangeSet::Change &insert : inserts) { - int index = qMin(insert.index, objects.count()); - if (insert.isMove()) { - QVector > movedObjects = moved.value(insert.moveId); - objects = objects.mid(0, index) + movedObjects + objects.mid(index); - } else { - if (insert.index <= objects.size()) - objects.insert(insert.index, insert.count, nullptr); - for (int i = 0; i < insert.count; ++i) { - int modelIndex = index + i; - QObject* obj = modelObject(modelIndex, async); - if (obj) - _q_createdItem(modelIndex, obj); - } - } - difference += insert.count; - } - - if (difference != 0) - q->countChanged(); -} - -#if QT_CONFIG(qml_delegate_model) -void QQmlInstantiatorPrivate::makeModel() -{ - Q_Q(QQmlInstantiator); - QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q), q); - instanceModel = delegateModel; - ownModel = true; - delegateModel->setDelegate(delegate); - delegateModel->classBegin(); //Pretend it was made in QML - if (componentComplete) - delegateModel->componentComplete(); -} -#endif - - -/*! - \qmltype Instantiator - \instantiates QQmlInstantiator - \inqmlmodule QtQml - \brief Dynamically creates objects. - - A Instantiator can be used to control the dynamic creation of objects, or to dynamically - create multiple objects from a template. - - The Instantiator element will manage the objects it creates. Those objects are parented to the - Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects - can also be destroyed dynamically through other means, and the Instantiator will not recreate - them unless the properties of the Instantiator change. - -*/ -QQmlInstantiator::QQmlInstantiator(QObject *parent) - : QObject(*(new QQmlInstantiatorPrivate), parent) -{ -} - -QQmlInstantiator::~QQmlInstantiator() -{ -} - -/*! - \qmlsignal QtQml::Instantiator::objectAdded(int index, QtObject object) - - This signal is emitted when an object is added to the Instantiator. The \a index - parameter holds the index which the object has been given, and the \a object - parameter holds the \l QtObject that has been added. - - The corresponding handler is \c onObjectAdded. -*/ - -/*! - \qmlsignal QtQml::Instantiator::objectRemoved(int index, QtObject object) - - This signal is emitted when an object is removed from the Instantiator. The \a index - parameter holds the index which the object had been given, and the \a object - parameter holds the \l QtObject that has been removed. - - Do not keep a reference to \a object if it was created by this Instantiator, as - in these cases it will be deleted shortly after the signal is handled. - - The corresponding handler is \c onObjectRemoved. -*/ -/*! - \qmlproperty bool QtQml::Instantiator::active - - When active is true, and the delegate component is ready, the Instantiator will - create objects according to the model. When active is false, no objects - will be created and any previously created objects will be destroyed. - - Default is true. -*/ -bool QQmlInstantiator::isActive() const -{ - Q_D(const QQmlInstantiator); - return d->active; -} - -void QQmlInstantiator::setActive(bool newVal) -{ - Q_D(QQmlInstantiator); - if (newVal == d->active) - return; - d->active = newVal; - emit activeChanged(); - d->regenerate(); -} - -/*! - \qmlproperty bool QtQml::Instantiator::asynchronous - - When asynchronous is true the Instantiator will attempt to create objects - asynchronously. This means that objects may not be available immediately, - even if active is set to true. - - You can use the objectAdded signal to respond to items being created. - - Default is false. -*/ -bool QQmlInstantiator::isAsync() const -{ - Q_D(const QQmlInstantiator); - return d->async; -} - -void QQmlInstantiator::setAsync(bool newVal) -{ - Q_D(QQmlInstantiator); - if (newVal == d->async) - return; - d->async = newVal; - emit asynchronousChanged(); -} - - -/*! - \qmlproperty int QtQml::Instantiator::count - - The number of objects the Instantiator is currently managing. -*/ - -int QQmlInstantiator::count() const -{ - Q_D(const QQmlInstantiator); - return d->objects.count(); -} - -/*! - \qmlproperty QtQml::Component QtQml::Instantiator::delegate - \default - - The component used to create all objects. - - Note that an extra variable, index, will be available inside instances of the - delegate. This variable refers to the index of the instance inside the Instantiator, - and can be used to obtain the object through the objectAt method of the Instantiator. - - If this property is changed, all instances using the old delegate will be destroyed - and new instances will be created using the new delegate. -*/ -QQmlComponent* QQmlInstantiator::delegate() -{ - Q_D(QQmlInstantiator); - return d->delegate; -} - -void QQmlInstantiator::setDelegate(QQmlComponent* c) -{ - Q_D(QQmlInstantiator); - if (c == d->delegate) - return; - - d->delegate = c; - emit delegateChanged(); - -#if QT_CONFIG(qml_delegate_model) - if (!d->ownModel) - return; - - if (QQmlDelegateModel *dModel = qobject_cast(d->instanceModel)) - dModel->setDelegate(c); - if (d->componentComplete) - d->regenerate(); -#endif -} - -/*! - \qmlproperty variant QtQml::Instantiator::model - - This property can be set to any of the supported \l {qml-data-models}{data models}: - - \list - \li A number that indicates the number of delegates to be created by the repeater - \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass) - \li A string list - \li An object list - \endlist - - The type of model affects the properties that are exposed to the \l delegate. - - Default value is 1, which creates a single delegate instance. - - \sa {qml-data-models}{Data Models} -*/ - -QVariant QQmlInstantiator::model() const -{ - Q_D(const QQmlInstantiator); - return d->model; -} - -void QQmlInstantiator::setModel(const QVariant &v) -{ - Q_D(QQmlInstantiator); - if (d->model == v) - return; - - d->model = v; - //Don't actually set model until componentComplete in case it wants to create its delegates immediately - if (!d->componentComplete) - return; - - QQmlInstanceModel *prevModel = d->instanceModel; - QObject *object = qvariant_cast(v); - QQmlInstanceModel *vim = nullptr; - if (object && (vim = qobject_cast(object))) { -#if QT_CONFIG(qml_delegate_model) - if (d->ownModel) { - delete d->instanceModel; - prevModel = nullptr; - d->ownModel = false; - } -#endif - d->instanceModel = vim; -#if QT_CONFIG(qml_delegate_model) - } else if (v != QVariant(0)){ - if (!d->ownModel) - d->makeModel(); - - if (QQmlDelegateModel *dataModel = qobject_cast(d->instanceModel)) { - d->effectiveReset = true; - dataModel->setModel(v); - d->effectiveReset = false; - } -#endif - } - - if (d->instanceModel != prevModel) { - if (prevModel) { - disconnect(prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), - this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); - disconnect(prevModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); - //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); - } - - if (d->instanceModel) { - connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), - this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); - connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); - //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); - } - } - - d->regenerate(); - emit modelChanged(); -} - -/*! - \qmlproperty QtObject QtQml::Instantiator::object - - This is a reference to the first created object, intended as a convenience - for the case where only one object has been created. -*/ -QObject *QQmlInstantiator::object() const -{ - Q_D(const QQmlInstantiator); - if (d->objects.count()) - return d->objects[0]; - return nullptr; -} - -/*! - \qmlmethod QtObject QtQml::Instantiator::objectAt(int index) - - Returns a reference to the object with the given \a index. -*/ -QObject *QQmlInstantiator::objectAt(int index) const -{ - Q_D(const QQmlInstantiator); - if (index >= 0 && index < d->objects.count()) - return d->objects[index]; - return nullptr; -} - -/*! - \internal -*/ -void QQmlInstantiator::classBegin() -{ - Q_D(QQmlInstantiator); - d->componentComplete = false; -} - -/*! - \internal -*/ -void QQmlInstantiator::componentComplete() -{ - Q_D(QQmlInstantiator); - d->componentComplete = true; -#if QT_CONFIG(qml_delegate_model) - if (d->ownModel) { - static_cast(d->instanceModel)->componentComplete(); - d->regenerate(); - } else -#endif - { - QVariant realModel = d->model; - d->model = QVariant(0); - setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0 - //setModel calls regenerate - } -} - -QT_END_NAMESPACE - -#include "moc_qqmlinstantiator_p.cpp" diff --git a/src/qml/types/qqmlinstantiator_p.h b/src/qml/types/qqmlinstantiator_p.h deleted file mode 100644 index ca371adc23..0000000000 --- a/src/qml/types/qqmlinstantiator_p.h +++ /dev/null @@ -1,119 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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$ -** -****************************************************************************/ - -#ifndef QQMLINSTANTIATOR_P_H -#define QQMLINSTANTIATOR_P_H - -// -// 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. -// - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QQmlInstantiatorPrivate; -class Q_QML_PRIVATE_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus -{ - Q_OBJECT - Q_INTERFACES(QQmlParserStatus) - - Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) - Q_PROPERTY(bool asynchronous READ isAsync WRITE setAsync NOTIFY asynchronousChanged) - Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) - Q_PROPERTY(int count READ count NOTIFY countChanged) - Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) - Q_PROPERTY(QObject *object READ object NOTIFY objectChanged) - Q_CLASSINFO("DefaultProperty", "delegate") - -public: - QQmlInstantiator(QObject *parent = nullptr); - ~QQmlInstantiator(); - - bool isActive() const; - void setActive(bool newVal); - - bool isAsync() const; - void setAsync(bool newVal); - - int count() const; - - QQmlComponent* delegate(); - void setDelegate(QQmlComponent* c); - - QVariant model() const; - void setModel(const QVariant &v); - - QObject *object() const; - - Q_INVOKABLE QObject *objectAt(int index) const; - - void classBegin() override; - void componentComplete() override; - -Q_SIGNALS: - void modelChanged(); - void delegateChanged(); - void countChanged(); - void objectChanged(); - void activeChanged(); - void asynchronousChanged(); - - void objectAdded(int index, QObject* object); - void objectRemoved(int index, QObject* object); - -private: - Q_DISABLE_COPY(QQmlInstantiator) - Q_DECLARE_PRIVATE(QQmlInstantiator) - Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *)) - Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool)) -}; - -QT_END_NAMESPACE - -#endif // QQMLCREATOR_P_H diff --git a/src/qml/types/qqmlinstantiator_p_p.h b/src/qml/types/qqmlinstantiator_p_p.h deleted file mode 100644 index 4c76d5c689..0000000000 --- a/src/qml/types/qqmlinstantiator_p_p.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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$ -** -****************************************************************************/ - -#ifndef QQMLINSTANTIATOR_P_P_H -#define QQMLINSTANTIATOR_P_P_H - -// -// 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. -// - -#include "qqmlinstantiator_p.h" -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Q_QML_PRIVATE_EXPORT QQmlInstantiatorPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QQmlInstantiator) - -public: - QQmlInstantiatorPrivate(); - ~QQmlInstantiatorPrivate(); - - void clear(); - void regenerate(); -#if QT_CONFIG(qml_delegate_model) - void makeModel(); -#endif - void _q_createdItem(int, QObject *); - void _q_modelUpdated(const QQmlChangeSet &, bool); - QObject *modelObject(int index, bool async); - - static QQmlInstantiatorPrivate *get(QQmlInstantiator *instantiator) { return instantiator->d_func(); } - static const QQmlInstantiatorPrivate *get(const QQmlInstantiator *instantiator) { return instantiator->d_func(); } - - bool componentComplete:1; - bool effectiveReset:1; - bool active:1; - bool async:1; -#if QT_CONFIG(qml_delegate_model) - bool ownModel:1; -#endif - int requestedIndex; - QVariant model; - QQmlInstanceModel *instanceModel; - QQmlComponent *delegate; - QVector > objects; -}; - -QT_END_NAMESPACE - -#endif // QQMLCREATOR_P_P_H diff --git a/src/qml/types/qqmlitemmodels.qdoc b/src/qml/types/qqmlitemmodels.qdoc deleted file mode 100644 index f6e1b0b1b9..0000000000 --- a/src/qml/types/qqmlitemmodels.qdoc +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:FDL$ -** 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 Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! - \page qmodelindex-and-related-classes-in-qml.html - \title QModelIndex and related Classes in QML - - Since Qt 5.5, QModelIndex and QPersistentModelIndex are exposed in QML as - value-based types. Also exposed in a similar fashion are QModelIndexList, - QItemSelectionRange and QItemSelection. All objects from these types can - be passed back and forth between QML and C++ as \c var properties or plain - JavaScript variables. - - Below you will find an overview of the API exposed to QML for these classes. - For more information, refer to their C++ documentation. - - \note Since all these types are exposed as \l{Q_GADGET}{gadgets}, there are no property - change notification signals emitted. Therefore binding to their properties - may not give the expected results. This is especially true for QPersistentModelIndex. - - \section1 QModelIndex and QPersistentModelIndex Types - - \list - \li \b row : int - \li \b column : int - \li \b parent : QModelIndex - \li \b valid : bool - \li \b model : QAbstractItemModel - \li \b internalId : quint64 - \endlist - - All these properties are read-only, as are their C++ counterparts. - - \note The usual caveats apply to QModelIndex in QML. If the underlying model changes - or gets deleted, it may become dangerous to access its properties. Therefore, you - should not store any QModelIndex objects. You can, however, store QPersistentModelIndexe - objects in a safe way. - - \section1 QModelIndexList Type - - \l QModelIndexList is exposed in QML as a JavaScript array. Conversions are - automatically made from and to C++. In fact, any JavaScript array can be - converted back to QModelIndexList, with non-QModelIndex objects replaced by - invalid \l{QModelIndex}es. - - \note QModelIndex to QPersistentModelIndex conversion happens when accessing - the array elements because any QModelIndexList property retains reference - semantics when exposed this way. - - \section1 \l QItemSelectionRange Type - - \list - \li \b top : int - \li \b left : int - \li \b bottom : int - \li \b right : int - \li \b width : int - \li \b height : int - \li \b topLeft : QPersistentModelIndex - \li \b bottomRight : QPersistentModelIndex - \li \b parent : QModelIndex - \li \b valid : bool - \li \b empty : bool - \li \b model : QAbstractItemModel - \endlist - - All these properties are read-only, as are their C++ counterparts. In addition, - we also expose the following functions: - - \list - \li bool \b{contains}(QModelIndex \e index) - \li bool \b{contains}(int \e row, int \e column, QModelIndex \e parentIndex) - \li bool \b{intersects}(QItemSelectionRange \e other) - \li QItemSelectionRange \b{intersected}(QItemSelectionRange \e other) - \endlist - - \section1 QItemSelection Type - - Similarly to QModelIndexList, \l QItemSelection is exposed in QML as a JavaScript - array of QItemSelectionRange objects. Conversions are automatically made from and to C++. - In fact, any JavaScript array can be converted back to QItemSelection, with - non-QItemSelectionRange objects replaced by empty \l {QItemSelectionRange}s. - - - \sa ItemSelectionModel -*/ diff --git a/src/qml/types/qqmlitemselectionmodel.qdoc b/src/qml/types/qqmlitemselectionmodel.qdoc deleted file mode 100644 index 43da4f7a55..0000000000 --- a/src/qml/types/qqmlitemselectionmodel.qdoc +++ /dev/null @@ -1,239 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:FDL$ -** 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 Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! - \qmltype ItemSelectionModel - \instantiates QItemSelectionModel - \inqmlmodule QtQml.Models - \since 5.5 - \ingroup qtquick-models - - \brief Instantiates a QItemSelectionModel to be used in conjunction - with a QAbstractItemModel and any view supporting it. - - \sa QItemSelectionModel, {Models and Views in Qt Quick} -*/ - - -/*! - \qmlproperty QAbstractItemModel ItemSelectionModel::model - - This property's value must match the view's model. -*/ - -/*! - \qmlproperty bool ItemSelectionModel::hasSelection - \readonly - - It will trigger property binding updates every time \l selectionChanged() - is emitted, even though its value hasn't changed. - - \sa selection, selectedIndexes, select(), selectionChanged() -*/ - -/*! - \qmlproperty QModelIndex ItemSelectionModel::currentIndex - \readonly - - Use \l setCurrentIndex() to set its value. - - \sa setCurrentIndex(), currentChanged() -*/ - -/*! - \qmlproperty QModelIndexList ItemSelectionModel::selectedIndexes - \readonly - - Contains the list of all the indexes in the selection model. -*/ - -/*! - \qmlmethod bool ItemSelectionModel::isSelected(QModelIndex index) - - Returns \c true if the given model item \a index is selected. -*/ - -/*! - \qmlmethod bool ItemSelectionModel::isRowSelected(int row, QModelIndex parent) - - Returns \c true if all items are selected in the \a row with the given - \a parent. - - Note that this function is usually faster than calling isSelected() - on all items in the same row, and that unselectable items are ignored. -*/ - -/*! - \qmlmethod bool ItemSelectionModel::isColumnSelected(int column, QModelIndex parent) - - Returns \c true if all items are selected in the \a column with the given - \a parent. - - Note that this function is usually faster than calling isSelected() - on all items in the same column, and that unselectable items are ignored. -*/ - -/*! - \qmlmethod bool ItemSelectionModel::rowIntersectsSelection(int row, QModelIndex parent) - - Returns \c true if there are any items selected in the \a row with the - given \a parent. -*/ - -/*! - \qmlmethod bool ItemSelectionModel::columnIntersectsSelection(int column, QModelIndex parent) - - Returns \c true if there are any items selected in the \a column with the - given \a parent. -*/ - -/*! - \qmlmethod QModelIndexList ItemSelectionModel::selectedRows(int column) - - Returns the indexes in the given \a column for the rows where all columns - are selected. - - \sa selectedColumns() -*/ - -/*! - \qmlmethod QModelIndexList ItemSelectionModel::selectedColumns(int row) - - Returns the indexes in the given \a row for columns where all rows are - selected. - - \sa selectedRows() -*/ - -/*! - \qmlproperty object ItemSelectionModel::selection - \readonly - - Holds the selection ranges stored in the selection model. -*/ - -/*! - \qmlmethod void ItemSelectionModel::setCurrentIndex(QModelIndex index, SelectionFlags command) - - Sets the model item \a index to be the current item, and emits - currentChanged(). The current item is used for keyboard navigation and - focus indication; it is independent of any selected items, although a - selected item can also be the current item. - - Depending on the specified \a command, the \a index can also become part - of the current selection. - - Valid \a command values are described in \l {itemselectionmodelselectindex} - {select(\e index, \e command)}. - - \sa select() -*/ - -/*! - \qmlmethod void ItemSelectionModel::select(QModelIndex index, SelectionFlags command) - \keyword itemselectionmodelselectindex - - Selects the model item \a index using the specified \a command, and emits - selectionChanged(). - - Valid values for the \a command parameter, are: - - \value NoUpdate No selection will be made. - \value Clear The complete selection will be cleared. - \value Select All specified indexes will be selected. - \value Deselect All specified indexes will be deselected. - \value Toggle All specified indexes will be selected or - deselected depending on their current state. - \value Current The current selection will be updated. - \value Rows All indexes will be expanded to span rows. - \value Columns All indexes will be expanded to span columns. - \value SelectCurrent A combination of Select and Current, provided for - convenience. - \value ToggleCurrent A combination of Toggle and Current, provided for - convenience. - \value ClearAndSelect A combination of Clear and Select, provided for - convenience. -*/ - -/*! - \qmlmethod void ItemSelectionModel::select(QItemSelection selection, SelectionFlags command) - - Selects the item \a selection using the specified \a command, and emits - selectionChanged(). - - Valid \a command values are described in \l {itemselectionmodelselectindex} - {select(\e index, \e command)}. -*/ - -/*! - \qmlmethod void ItemSelectionModel::clear() - - Clears the selection model. Emits selectionChanged() and currentChanged(). -*/ - -/*! - \qmlmethod void ItemSelectionModel::reset() - - Clears the selection model. Does not emit any signals. -*/ - -/*! - \qmlmethod void ItemSelectionModel::clearSelection() - - Clears the selection in the selection model. Emits selectionChanged(). -*/ - -/*! - \qmlmethod void ItemSelectionModel::clearCurrentIndex() - - Clears the current index. Emits currentChanged(). -*/ - -/*! - \qmlsignal ItemSelectionModel::selectionChanged(QItemSelection selected, QItemSelection deselected) - - This signal is emitted whenever the selection changes. The change in the - selection is represented as an item selection of \a deselected items and - an item selection of \a selected items. - - Note the that the current index changes independently from the selection. - Also note that this signal will not be emitted when the item model is reset. - - \sa select(), currentChanged() -*/ - -/*! - \qmlsignal ItemSelectionModel::currentChanged(QModelIndex current, QModelIndex previous) - - This signal is emitted whenever the current item changes. The \a previous - model item index is replaced by the \a current index as the selection's - current item. - - Note that this signal will not be emitted when the item model is reset. - - \sa currentIndex, setCurrentIndex(), selectionChanged() -*/ diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp deleted file mode 100644 index 5b5bcd8464..0000000000 --- a/src/qml/types/qqmllistmodel.cpp +++ /dev/null @@ -1,2900 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmllistmodel_p_p.h" -#include "qqmllistmodelworkeragent_p.h" -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*); - -QT_BEGIN_NAMESPACE - -// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models. -enum { MIN_LISTMODEL_UID = 1024 }; - -static QAtomicInt uidCounter(MIN_LISTMODEL_UID); - -template -static bool isMemoryUsed(const char *mem) -{ - for (size_t i=0 ; i < sizeof(T) ; ++i) { - if (mem[i] != 0) - return true; - } - - return false; -} - -static QString roleTypeName(ListLayout::Role::DataType t) -{ - static const QString roleTypeNames[] = { - QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"), - QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"), - QStringLiteral("DateTime"), QStringLiteral("Function") - }; - - if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType) - return roleTypeNames[t]; - - return QString(); -} - -const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type) -{ - QStringHash::Node *node = roleHash.findNode(key); - if (node) { - const Role &r = *node->value; - if (type != r.type) - qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); - return r; - } - - return createRole(key, type); -} - -const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type) -{ - QStringHash::Node *node = roleHash.findNode(key); - if (node) { - const Role &r = *node->value; - if (type != r.type) - qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); - return r; - } - - QString qkey = key->toQString(); - - return createRole(qkey, type); -} - -const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type) -{ - const int dataSizes[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) }; - const int dataAlignments[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) }; - - Role *r = new Role; - r->name = key; - r->type = type; - - if (type == Role::List) { - r->subLayout = new ListLayout; - } else { - r->subLayout = nullptr; - } - - int dataSize = dataSizes[type]; - int dataAlignment = dataAlignments[type]; - - int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1); - if (dataOffset + dataSize > ListElement::BLOCK_SIZE) { - r->blockIndex = ++currentBlock; - r->blockOffset = 0; - currentBlockOffset = dataSize; - } else { - r->blockIndex = currentBlock; - r->blockOffset = dataOffset; - currentBlockOffset = dataOffset + dataSize; - } - - int roleIndex = roles.count(); - r->index = roleIndex; - - roles.append(r); - roleHash.insert(key, r); - - return *r; -} - -ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0) -{ - const int otherRolesCount = other->roles.count(); - roles.reserve(otherRolesCount); - for (int i=0 ; i < otherRolesCount; ++i) { - Role *role = new Role(other->roles[i]); - roles.append(role); - roleHash.insert(role->name, role); - } - currentBlockOffset = other->currentBlockOffset; - currentBlock = other->currentBlock; -} - -ListLayout::~ListLayout() -{ - qDeleteAll(roles); -} - -void ListLayout::sync(ListLayout *src, ListLayout *target) -{ - int roleOffset = target->roles.count(); - int newRoleCount = src->roles.count() - roleOffset; - - for (int i=0 ; i < newRoleCount ; ++i) { - Role *role = new Role(src->roles[roleOffset + i]); - target->roles.append(role); - target->roleHash.insert(role->name, role); - } - - target->currentBlockOffset = src->currentBlockOffset; - target->currentBlock = src->currentBlock; -} - -ListLayout::Role::Role(const Role *other) -{ - name = other->name; - type = other->type; - blockIndex = other->blockIndex; - blockOffset = other->blockOffset; - index = other->index; - if (other->subLayout) - subLayout = new ListLayout(other->subLayout); - else - subLayout = nullptr; -} - -ListLayout::Role::~Role() -{ - delete subLayout; -} - -const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data) -{ - Role::DataType type; - - switch (data.type()) { - case QVariant::Double: type = Role::Number; break; - case QVariant::Int: type = Role::Number; break; - case QVariant::Bool: type = Role::Bool; break; - case QVariant::String: type = Role::String; break; - case QVariant::Map: type = Role::VariantMap; break; - case QVariant::DateTime: type = Role::DateTime; break; - case QVariant::UserType: { - if (data.userType() == qMetaTypeId() && - data.value().isCallable()) { - type = Role::Function; - break; - } else if (data.userType() == qMetaTypeId() - && data.value()->isTranslationBinding()) { - type = Role::String; - break; - } else { - type = Role::List; - break; - } - } - default: type = Role::Invalid; break; - } - - if (type == Role::Invalid) { - qmlWarning(nullptr) << "Can't create role for unsupported data type"; - return nullptr; - } - - return &getRoleOrCreate(key, type); -} - -const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const -{ - Role *r = nullptr; - QStringHash::Node *node = roleHash.findNode(key); - if (node) - r = node->value; - return r; -} - -const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const -{ - Role *r = nullptr; - QStringHash::Node *node = roleHash.findNode(key); - if (node) - r = node->value; - return r; -} - -StringOrTranslation::StringOrTranslation(const QString &s) -{ - d.setFlag(); - setString(s); -} - -StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding) -{ - d.setFlag(); - clear(); - d = binding; -} - -StringOrTranslation::~StringOrTranslation() -{ - clear(); -} - -void StringOrTranslation::setString(const QString &s) -{ - d.setFlag(); - clear(); - QStringData *stringData = const_cast(s).data_ptr(); - d = stringData; - if (stringData) - stringData->ref.ref(); -} - -void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding) -{ - d.setFlag(); - clear(); - d = binding; -} - -QString StringOrTranslation::toString(const QQmlListModel *owner) const -{ - if (d.isNull()) - return QString(); - if (d.isT1()) { - QStringDataPtr holder = { d.asT1() }; - holder.ptr->ref.ref(); - return QString(holder); - } - if (!owner) - return QString(); - return d.asT2()->valueAsString(owner->m_compilationUnit.data()); -} - -QString StringOrTranslation::asString() const -{ - if (d.isNull()) - return QString(); - if (!d.isT1()) - return QString(); - QStringDataPtr holder = { d.asT1() }; - holder.ptr->ref.ref(); - return QString(holder); -} - -void StringOrTranslation::clear() -{ - if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) { - if (!strData->ref.deref()) - QStringData::deallocate(strData); - } - d = static_cast(nullptr); -} - -QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex) -{ - ListElement *e = elements[elementIndex]; - if (e->m_objectCache == nullptr) { - void *memory = operator new(sizeof(QObject) + sizeof(QQmlData)); - void *ddataMemory = ((char *)memory) + sizeof(QObject); - e->m_objectCache = new (memory) QObject; - QQmlData *ddata = new (ddataMemory) QQmlData; - ddata->ownMemory = false; - QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata; - (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex); - } - return e->m_objectCache; -} - -bool ListModel::sync(ListModel *src, ListModel *target) -{ - // Sanity check - - bool hasChanges = false; - - // Build hash of elements <-> uid for each of the lists - QHash elementHash; - for (int i = 0; i < target->elements.count(); ++i) { - ListElement *e = target->elements.at(i); - int uid = e->getUid(); - ElementSync sync; - sync.target = e; - sync.targetIndex = i; - elementHash.insert(uid, sync); - } - for (int i = 0; i < src->elements.count(); ++i) { - ListElement *e = src->elements.at(i); - int uid = e->getUid(); - - QHash::iterator it = elementHash.find(uid); - if (it == elementHash.end()) { - ElementSync sync; - sync.src = e; - sync.srcIndex = i; - elementHash.insert(uid, sync); - } else { - ElementSync &sync = it.value(); - sync.src = e; - sync.srcIndex = i; - } - } - - QQmlListModel *targetModel = target->m_modelCache; - - // Get list of elements that are in the target but no longer in the source. These get deleted first. - int rowsRemoved = 0; - for (int i = 0 ; i < target->elements.count() ; ++i) { - ListElement *element = target->elements.at(i); - ElementSync &s = elementHash.find(element->getUid()).value(); - Q_ASSERT(s.targetIndex >= 0); - // need to update the targetIndex, to keep it correct after removals - s.targetIndex -= rowsRemoved; - if (s.src == nullptr) { - Q_ASSERT(s.targetIndex == i); - hasChanges = true; - if (targetModel) - targetModel->beginRemoveRows(QModelIndex(), i, i); - s.target->destroy(target->m_layout); - target->elements.removeOne(s.target); - delete s.target; - if (targetModel) - targetModel->endRemoveRows(); - ++rowsRemoved; - --i; - continue; - } - } - - // Sync the layouts - ListLayout::sync(src->m_layout, target->m_layout); - - // Clear the target list, and append in correct order from the source - target->elements.clear(); - for (int i = 0; i < src->elements.count(); ++i) { - ListElement *srcElement = src->elements.at(i); - ElementSync &s = elementHash.find(srcElement->getUid()).value(); - Q_ASSERT(s.srcIndex >= 0); - ListElement *targetElement = s.target; - if (targetElement == nullptr) { - targetElement = new ListElement(srcElement->getUid()); - } - s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout); - target->elements.append(targetElement); - } - - target->updateCacheIndices(); - - // Update values stored in target meta objects - for (int i=0 ; i < target->elements.count() ; ++i) { - ListElement *e = target->elements[i]; - if (ModelNodeMetaObject *mo = e->objectCache()) - mo->updateValues(); - } - - // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts, - // so the model indices can't be out of bounds - // - // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent - // model indices are updated correctly - int rowsInserted = 0; - for (int i = 0 ; i < target->elements.count() ; ++i) { - ListElement *element = target->elements.at(i); - ElementSync &s = elementHash.find(element->getUid()).value(); - Q_ASSERT(s.srcIndex >= 0); - s.srcIndex += rowsInserted; - if (s.srcIndex != s.targetIndex) { - if (targetModel) { - if (s.targetIndex == -1) { - targetModel->beginInsertRows(QModelIndex(), i, i); - targetModel->endInsertRows(); - } else { - targetModel->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex); - targetModel->endMoveRows(); - } - } - hasChanges = true; - ++rowsInserted; - } - if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) { - QModelIndex idx = targetModel->createIndex(i, 0); - if (targetModel) - targetModel->dataChanged(idx, idx, s.changedRoles); - hasChanges = true; - } - } - return hasChanges; -} - -ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache) -{ -} - -void ListModel::destroy() -{ - for (const auto &destroyer : remove(0, elements.count())) - destroyer(); - - m_layout = nullptr; - if (m_modelCache && m_modelCache->m_primary == false) - delete m_modelCache; - m_modelCache = nullptr; -} - -int ListModel::appendElement() -{ - int elementIndex = elements.count(); - newElement(elementIndex); - return elementIndex; -} - -void ListModel::insertElement(int index) -{ - newElement(index); - updateCacheIndices(index); -} - -void ListModel::move(int from, int to, int n) -{ - if (from > to) { - // Only move forwards - flip if backwards moving - int tfrom = from; - int tto = to; - from = tto; - to = tto+n; - n = tfrom-tto; - } - - QPODVector store; - for (int i=0 ; i < (to-from) ; ++i) - store.append(elements[from+n+i]); - for (int i=0 ; i < n ; ++i) - store.append(elements[from+i]); - for (int i=0 ; i < store.count() ; ++i) - elements[from+i] = store[i]; - - updateCacheIndices(from, to + n); -} - -void ListModel::newElement(int index) -{ - ListElement *e = new ListElement; - elements.insert(index, e); -} - -void ListModel::updateCacheIndices(int start, int end) -{ - int count = elements.count(); - - if (end < 0 || end > count) - end = count; - - for (int i = start; i < end; ++i) { - ListElement *e = elements.at(i); - if (ModelNodeMetaObject *mo = e->objectCache()) - mo->m_elementIndex = i; - } -} - -QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng) -{ - if (roleIndex >= m_layout->roleCount()) - return QVariant(); - ListElement *e = elements[elementIndex]; - const ListLayout::Role &r = m_layout->getExistingRole(roleIndex); - return e->getProperty(r, owner, eng); -} - -ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role) -{ - ListElement *e = elements[elementIndex]; - return e->getListProperty(role); -} - -void ListModel::set(int elementIndex, QV4::Object *object, QVector *roles) -{ - ListElement *e = elements[elementIndex]; - - QV4::ExecutionEngine *v4 = object->engine(); - QV4::Scope scope(v4); - QV4::ScopedObject o(scope); - - QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); - QV4::ScopedString propertyName(scope); - QV4::ScopedValue propertyValue(scope); - while (1) { - propertyName = it.nextPropertyNameAsString(propertyValue); - if (!propertyName) - break; - - // Check if this key exists yet - int roleIndex = -1; - - // Add the value now - if (const QV4::String *s = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); - roleIndex = e->setStringProperty(r, s->toQString()); - } else if (propertyValue->isNumber()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); - roleIndex = e->setDoubleProperty(r, propertyValue->asDouble()); - } else if (QV4::ArrayObject *a = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List); - ListModel *subModel = new ListModel(r.subLayout, nullptr); - - int arrayLength = a->getLength(); - for (int j=0 ; j < arrayLength ; ++j) { - o = a->get(j); - subModel->append(o); - } - - roleIndex = e->setListProperty(r, subModel); - } else if (propertyValue->isBoolean()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); - roleIndex = e->setBoolProperty(r, propertyValue->booleanValue()); - } else if (QV4::DateObject *dd = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); - QDateTime dt = dd->toQDateTime(); - roleIndex = e->setDateTimeProperty(r, dt); - } else if (QV4::FunctionObject *f = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function); - QV4::ScopedFunctionObject func(scope, f); - QJSValue jsv; - QJSValuePrivate::setValue(&jsv, v4, func); - roleIndex = e->setFunctionProperty(r, jsv); - } else if (QV4::Object *o = propertyValue->as()) { - if (QV4::QObjectWrapper *wrapper = o->as()) { - QObject *o = wrapper->object(); - const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); - if (role.type == ListLayout::Role::QObject) - roleIndex = e->setQObjectProperty(role, o); - } else { - const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); - if (role.type == ListLayout::Role::VariantMap) { - QV4::ScopedObject obj(scope, o); - roleIndex = e->setVariantMapProperty(role, obj); - } - } - } else if (propertyValue->isNullOrUndefined()) { - const ListLayout::Role *r = m_layout->getExistingRole(propertyName); - if (r) - e->clearProperty(*r); - } - - if (roleIndex != -1) - roles->append(roleIndex); - } - - if (ModelNodeMetaObject *mo = e->objectCache()) - mo->updateValues(*roles); -} - -void ListModel::set(int elementIndex, QV4::Object *object) -{ - if (!object) - return; - - ListElement *e = elements[elementIndex]; - - QV4::ExecutionEngine *v4 = object->engine(); - QV4::Scope scope(v4); - - QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); - QV4::ScopedString propertyName(scope); - QV4::ScopedValue propertyValue(scope); - QV4::ScopedObject o(scope); - while (1) { - propertyName = it.nextPropertyNameAsString(propertyValue); - if (!propertyName) - break; - - // Add the value now - if (QV4::String *s = propertyValue->stringValue()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); - if (r.type == ListLayout::Role::String) - e->setStringPropertyFast(r, s->toQString()); - } else if (propertyValue->isNumber()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); - if (r.type == ListLayout::Role::Number) { - e->setDoublePropertyFast(r, propertyValue->asDouble()); - } - } else if (QV4::ArrayObject *a = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List); - if (r.type == ListLayout::Role::List) { - ListModel *subModel = new ListModel(r.subLayout, nullptr); - - int arrayLength = a->getLength(); - for (int j=0 ; j < arrayLength ; ++j) { - o = a->get(j); - subModel->append(o); - } - - e->setListPropertyFast(r, subModel); - } - } else if (propertyValue->isBoolean()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); - if (r.type == ListLayout::Role::Bool) { - e->setBoolPropertyFast(r, propertyValue->booleanValue()); - } - } else if (QV4::DateObject *date = propertyValue->as()) { - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); - if (r.type == ListLayout::Role::DateTime) { - QDateTime dt = date->toQDateTime();; - e->setDateTimePropertyFast(r, dt); - } - } else if (QV4::Object *o = propertyValue->as()) { - if (QV4::QObjectWrapper *wrapper = o->as()) { - QObject *o = wrapper->object(); - const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); - if (r.type == ListLayout::Role::QObject) - e->setQObjectPropertyFast(r, o); - } else { - const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); - if (role.type == ListLayout::Role::VariantMap) - e->setVariantMapFast(role, o); - } - } else if (propertyValue->isNullOrUndefined()) { - const ListLayout::Role *r = m_layout->getExistingRole(propertyName); - if (r) - e->clearProperty(*r); - } - } -} - -QVector> ListModel::remove(int index, int count) -{ - QVector> toDestroy; - auto layout = m_layout; - for (int i=0 ; i < count ; ++i) { - auto element = elements[index+i]; - toDestroy.append([element, layout](){ - element->destroy(layout); - delete element; - }); - } - elements.remove(index, count); - updateCacheIndices(index); - return toDestroy; -} - -void ListModel::insert(int elementIndex, QV4::Object *object) -{ - insertElement(elementIndex); - set(elementIndex, object); -} - -int ListModel::append(QV4::Object *object) -{ - int elementIndex = appendElement(); - set(elementIndex, object); - return elementIndex; -} - -int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data) -{ - int roleIndex = -1; - - if (elementIndex >= 0 && elementIndex < elements.count()) { - ListElement *e = elements[elementIndex]; - - const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data); - if (r) { - roleIndex = e->setVariantProperty(*r, data); - - ModelNodeMetaObject *cache = e->objectCache(); - - if (roleIndex != -1 && cache) - cache->updateValues(QVector(1, roleIndex)); - } - } - - return roleIndex; -} - -int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng) -{ - int roleIndex = -1; - - if (elementIndex >= 0 && elementIndex < elements.count()) { - ListElement *e = elements[elementIndex]; - const ListLayout::Role *r = m_layout->getExistingRole(key); - if (r) - roleIndex = e->setJsProperty(*r, data, eng); - } - - return roleIndex; -} - -inline char *ListElement::getPropertyMemory(const ListLayout::Role &role) -{ - ListElement *e = this; - int blockIndex = 0; - while (blockIndex < role.blockIndex) { - if (e->next == nullptr) { - e->next = new ListElement; - e->next->uid = uid; - } - e = e->next; - ++blockIndex; - } - - char *mem = &e->data[role.blockOffset]; - return mem; -} - -ModelNodeMetaObject *ListElement::objectCache() -{ - if (!m_objectCache) - return nullptr; - return ModelNodeMetaObject::get(m_objectCache); -} - -StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role) -{ - char *mem = getPropertyMemory(role); - StringOrTranslation *s = reinterpret_cast(mem); - return s; -} - -QObject *ListElement::getQObjectProperty(const ListLayout::Role &role) -{ - char *mem = getPropertyMemory(role); - QPointer *o = reinterpret_cast *>(mem); - return o->data(); -} - -QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role) -{ - QVariantMap *map = nullptr; - - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) - map = reinterpret_cast(mem); - - return map; -} - -QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role) -{ - QDateTime *dt = nullptr; - - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) - dt = reinterpret_cast(mem); - - return dt; -} - -QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role) -{ - QJSValue *f = nullptr; - - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) - f = reinterpret_cast(mem); - - return f; -} - -QPointer *ListElement::getGuardProperty(const ListLayout::Role &role) -{ - char *mem = getPropertyMemory(role); - - bool existingGuard = false; - for (size_t i=0 ; i < sizeof(QPointer) ; ++i) { - if (mem[i] != 0) { - existingGuard = true; - break; - } - } - - QPointer *o = nullptr; - - if (existingGuard) - o = reinterpret_cast *>(mem); - - return o; -} - -ListModel *ListElement::getListProperty(const ListLayout::Role &role) -{ - char *mem = getPropertyMemory(role); - ListModel **value = reinterpret_cast(mem); - return *value; -} - -QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng) -{ - char *mem = getPropertyMemory(role); - - QVariant data; - - switch (role.type) { - case ListLayout::Role::Number: - { - double *value = reinterpret_cast(mem); - data = *value; - } - break; - case ListLayout::Role::String: - { - StringOrTranslation *value = reinterpret_cast(mem); - if (value->isSet()) - data = value->toString(owner); - } - break; - case ListLayout::Role::Bool: - { - bool *value = reinterpret_cast(mem); - data = *value; - } - break; - case ListLayout::Role::List: - { - ListModel **value = reinterpret_cast(mem); - ListModel *model = *value; - - if (model) { - if (model->m_modelCache == nullptr) { - model->m_modelCache = new QQmlListModel(owner, model, eng); - QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner)); - } - - QObject *object = model->m_modelCache; - data = QVariant::fromValue(object); - } - } - break; - case ListLayout::Role::QObject: - { - QPointer *guard = reinterpret_cast *>(mem); - QObject *object = guard->data(); - if (object) - data = QVariant::fromValue(object); - } - break; - case ListLayout::Role::VariantMap: - { - if (isMemoryUsed(mem)) { - QVariantMap *map = reinterpret_cast(mem); - data = *map; - } - } - break; - case ListLayout::Role::DateTime: - { - if (isMemoryUsed(mem)) { - QDateTime *dt = reinterpret_cast(mem); - data = *dt; - } - } - break; - case ListLayout::Role::Function: - { - if (isMemoryUsed(mem)) { - QJSValue *func = reinterpret_cast(mem); - data = QVariant::fromValue(*func); - } - } - break; - default: - break; - } - - return data; -} - -int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::String) { - char *mem = getPropertyMemory(role); - StringOrTranslation *c = reinterpret_cast(mem); - bool changed; - if (!c->isSet() || c->isTranslation()) - changed = true; - else - changed = c->asString().compare(s) != 0; - c->setString(s); - if (changed) - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setDoubleProperty(const ListLayout::Role &role, double d) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::Number) { - char *mem = getPropertyMemory(role); - double *value = reinterpret_cast(mem); - bool changed = *value != d; - *value = d; - if (changed) - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setBoolProperty(const ListLayout::Role &role, bool b) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::Bool) { - char *mem = getPropertyMemory(role); - bool *value = reinterpret_cast(mem); - bool changed = *value != b; - *value = b; - if (changed) - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::List) { - char *mem = getPropertyMemory(role); - ListModel **value = reinterpret_cast(mem); - if (*value && *value != m) { - (*value)->destroy(); - delete *value; - } - *value = m; - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::QObject) { - char *mem = getPropertyMemory(role); - QPointer *g = reinterpret_cast *>(mem); - bool existingGuard = false; - for (size_t i=0 ; i < sizeof(QPointer) ; ++i) { - if (mem[i] != 0) { - existingGuard = true; - break; - } - } - bool changed; - if (existingGuard) { - changed = g->data() != o; - g->~QPointer(); - } else { - changed = true; - } - new (mem) QPointer(o); - if (changed) - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::VariantMap) { - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) { - QVariantMap *map = reinterpret_cast(mem); - map->~QMap(); - } - new (mem) QVariantMap(o->engine()->variantMapFromJS(o)); - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::VariantMap) { - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) { - QVariantMap *map = reinterpret_cast(mem); - if (m && map->isSharedWith(*m)) - return roleIndex; - map->~QMap(); - } else if (!m) { - return roleIndex; - } - if (m) - new (mem) QVariantMap(*m); - else - new (mem) QVariantMap; - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::DateTime) { - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) { - QDateTime *dt = reinterpret_cast(mem); - dt->~QDateTime(); - } - new (mem) QDateTime(dt); - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::Function) { - char *mem = getPropertyMemory(role); - if (isMemoryUsed(mem)) { - QJSValue *f = reinterpret_cast(mem); - f->~QJSValue(); - } - new (mem) QJSValue(f); - roleIndex = role.index; - } - - return roleIndex; -} - -int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b) -{ - int roleIndex = -1; - - if (role.type == ListLayout::Role::String) { - char *mem = getPropertyMemory(role); - StringOrTranslation *s = reinterpret_cast(mem); - s->setTranslation(b); - roleIndex = role.index; - } - - return roleIndex; -} - - -void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s) -{ - char *mem = getPropertyMemory(role); - new (mem) StringOrTranslation(s); -} - -void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d) -{ - char *mem = getPropertyMemory(role); - double *value = new (mem) double; - *value = d; -} - -void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b) -{ - char *mem = getPropertyMemory(role); - bool *value = new (mem) bool; - *value = b; -} - -void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o) -{ - char *mem = getPropertyMemory(role); - new (mem) QPointer(o); -} - -void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m) -{ - char *mem = getPropertyMemory(role); - ListModel **value = new (mem) ListModel *; - *value = m; -} - -void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o) -{ - char *mem = getPropertyMemory(role); - QVariantMap *map = new (mem) QVariantMap; - *map = o->engine()->variantMapFromJS(o); -} - -void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt) -{ - char *mem = getPropertyMemory(role); - new (mem) QDateTime(dt); -} - -void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f) -{ - char *mem = getPropertyMemory(role); - new (mem) QJSValue(f); -} - -void ListElement::clearProperty(const ListLayout::Role &role) -{ - switch (role.type) { - case ListLayout::Role::String: - setStringProperty(role, QString()); - break; - case ListLayout::Role::Number: - setDoubleProperty(role, 0.0); - break; - case ListLayout::Role::Bool: - setBoolProperty(role, false); - break; - case ListLayout::Role::List: - setListProperty(role, nullptr); - break; - case ListLayout::Role::QObject: - setQObjectProperty(role, nullptr); - break; - case ListLayout::Role::DateTime: - setDateTimeProperty(role, QDateTime()); - break; - case ListLayout::Role::VariantMap: - setVariantMapProperty(role, (QVariantMap *)nullptr); - break; - case ListLayout::Role::Function: - setFunctionProperty(role, QJSValue()); - break; - default: - break; - } -} - -ListElement::ListElement() -{ - m_objectCache = nullptr; - uid = uidCounter.fetchAndAddOrdered(1); - next = nullptr; - memset(data, 0, sizeof(data)); -} - -ListElement::ListElement(int existingUid) -{ - m_objectCache = nullptr; - uid = existingUid; - next = nullptr; - memset(data, 0, sizeof(data)); -} - -ListElement::~ListElement() -{ - delete next; -} - -QVector ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout) -{ - QVector changedRoles; - for (int i=0 ; i < srcLayout->roleCount() ; ++i) { - const ListLayout::Role &srcRole = srcLayout->getExistingRole(i); - const ListLayout::Role &targetRole = targetLayout->getExistingRole(i); - - int roleIndex = -1; - switch (srcRole.type) { - case ListLayout::Role::List: - { - ListModel *srcSubModel = src->getListProperty(srcRole); - ListModel *targetSubModel = target->getListProperty(targetRole); - - if (srcSubModel) { - if (targetSubModel == nullptr) { - targetSubModel = new ListModel(targetRole.subLayout, nullptr); - target->setListPropertyFast(targetRole, targetSubModel); - } - if (ListModel::sync(srcSubModel, targetSubModel)) - roleIndex = targetRole.index; - } - } - break; - case ListLayout::Role::QObject: - { - QObject *object = src->getQObjectProperty(srcRole); - roleIndex = target->setQObjectProperty(targetRole, object); - } - break; - case ListLayout::Role::String: - case ListLayout::Role::Number: - case ListLayout::Role::Bool: - case ListLayout::Role::DateTime: - case ListLayout::Role::Function: - { - QVariant v = src->getProperty(srcRole, nullptr, nullptr); - roleIndex = target->setVariantProperty(targetRole, v); - } - break; - case ListLayout::Role::VariantMap: - { - QVariantMap *map = src->getVariantMapProperty(srcRole); - roleIndex = target->setVariantMapProperty(targetRole, map); - } - break; - default: - break; - } - if (roleIndex >= 0) - changedRoles << roleIndex; - } - - return changedRoles; -} - -void ListElement::destroy(ListLayout *layout) -{ - if (layout) { - for (int i=0 ; i < layout->roleCount() ; ++i) { - const ListLayout::Role &r = layout->getExistingRole(i); - - switch (r.type) { - case ListLayout::Role::String: - { - StringOrTranslation *string = getStringProperty(r); - if (string) - string->~StringOrTranslation(); - } - break; - case ListLayout::Role::List: - { - ListModel *model = getListProperty(r); - if (model) { - model->destroy(); - delete model; - } - } - break; - case ListLayout::Role::QObject: - { - QPointer *guard = getGuardProperty(r); - if (guard) - guard->~QPointer(); - } - break; - case ListLayout::Role::VariantMap: - { - QVariantMap *map = getVariantMapProperty(r); - if (map) - map->~QMap(); - } - break; - case ListLayout::Role::DateTime: - { - QDateTime *dt = getDateTimeProperty(r); - if (dt) - dt->~QDateTime(); - } - break; - case ListLayout::Role::Function: - { - QJSValue *f = getFunctionProperty(r); - if (f) - f->~QJSValue(); - } - break; - default: - // other types don't need explicit cleanup. - break; - } - } - - if (m_objectCache) { - m_objectCache->~QObject(); - operator delete(m_objectCache); - } - } - - if (next) - next->destroy(nullptr); - uid = -1; -} - -int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d) -{ - int roleIndex = -1; - - switch (role.type) { - case ListLayout::Role::Number: - roleIndex = setDoubleProperty(role, d.toDouble()); - break; - case ListLayout::Role::String: - if (d.userType() == qMetaTypeId()) - roleIndex = setTranslationProperty(role, d.value()); - else - roleIndex = setStringProperty(role, d.toString()); - break; - case ListLayout::Role::Bool: - roleIndex = setBoolProperty(role, d.toBool()); - break; - case ListLayout::Role::List: - roleIndex = setListProperty(role, d.value()); - break; - case ListLayout::Role::VariantMap: { - QVariantMap map = d.toMap(); - roleIndex = setVariantMapProperty(role, &map); - } - break; - case ListLayout::Role::DateTime: - roleIndex = setDateTimeProperty(role, d.toDateTime()); - break; - case ListLayout::Role::Function: - roleIndex = setFunctionProperty(role, d.value()); - break; - default: - break; - } - - return roleIndex; -} - -int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng) -{ - // Check if this key exists yet - int roleIndex = -1; - - QV4::Scope scope(eng); - - // Add the value now - if (d.isString()) { - QString qstr = d.toQString(); - roleIndex = setStringProperty(role, qstr); - } else if (d.isNumber()) { - roleIndex = setDoubleProperty(role, d.asDouble()); - } else if (d.as()) { - QV4::ScopedArrayObject a(scope, d); - if (role.type == ListLayout::Role::List) { - QV4::Scope scope(a->engine()); - QV4::ScopedObject o(scope); - - ListModel *subModel = new ListModel(role.subLayout, nullptr); - int arrayLength = a->getLength(); - for (int j=0 ; j < arrayLength ; ++j) { - o = a->get(j); - subModel->append(o); - } - roleIndex = setListProperty(role, subModel); - } else { - qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List)); - } - } else if (d.isBoolean()) { - roleIndex = setBoolProperty(role, d.booleanValue()); - } else if (d.as()) { - QV4::Scoped dd(scope, d); - QDateTime dt = dd->toQDateTime(); - roleIndex = setDateTimeProperty(role, dt); - } else if (d.as()) { - QV4::ScopedFunctionObject f(scope, d); - QJSValue jsv; - QJSValuePrivate::setValue(&jsv, eng, f); - roleIndex = setFunctionProperty(role, jsv); - } else if (d.isObject()) { - QV4::ScopedObject o(scope, d); - QV4::QObjectWrapper *wrapper = o->as(); - if (role.type == ListLayout::Role::QObject && wrapper) { - QObject *o = wrapper->object(); - roleIndex = setQObjectProperty(role, o); - } else if (role.type == ListLayout::Role::VariantMap) { - roleIndex = setVariantMapProperty(role, o); - } - } else if (d.isNullOrUndefined()) { - clearProperty(role); - } - - return roleIndex; -} - -ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex) -: QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false) -{} - -void ModelNodeMetaObject::initialize() -{ - const int roleCount = m_model->m_listModel->roleCount(); - QVector properties; - properties.reserve(roleCount); - for (int i = 0 ; i < roleCount ; ++i) { - const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i); - QByteArray name = role.name.toUtf8(); - properties << name; - } - type()->createProperties(properties); - updateValues(); - m_enabled = true; -} - -ModelNodeMetaObject::~ModelNodeMetaObject() -{ -} - -QAbstractDynamicMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object) -{ - if (!m_initialized) { - m_initialized = true; - initialize(); - } - return QQmlOpenMetaObject::toDynamicMetaObject(object); -} - -ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj) -{ - QObjectPrivate *op = QObjectPrivate::get(obj); - return static_cast(op->metaObject); -} - -void ModelNodeMetaObject::updateValues() -{ - const int roleCount = m_model->m_listModel->roleCount(); - if (!m_initialized) { - if (roleCount) { - Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int)); - for (int i = 0; i < roleCount; ++i) - changedRoles[i] = i; - emitDirectNotifies(changedRoles, roleCount); - } - return; - } - for (int i=0 ; i < roleCount ; ++i) { - const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i); - QByteArray name = role.name.toUtf8(); - const QVariant &data = m_model->data(m_elementIndex, i); - setValue(name, data, role.type == ListLayout::Role::List); - } -} - -void ModelNodeMetaObject::updateValues(const QVector &roles) -{ - if (!m_initialized) { - emitDirectNotifies(roles.constData(), roles.count()); - return; - } - int roleCount = roles.count(); - for (int i=0 ; i < roleCount ; ++i) { - int roleIndex = roles.at(i); - const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex); - QByteArray name = role.name.toUtf8(); - const QVariant &data = m_model->data(m_elementIndex, roleIndex); - setValue(name, data, role.type == ListLayout::Role::List); - } -} - -void ModelNodeMetaObject::propertyWritten(int index) -{ - if (!m_enabled) - return; - - QString propName = QString::fromUtf8(name(index)); - const QVariant value = this->value(index); - - QV4::Scope scope(m_model->engine()); - QV4::ScopedValue v(scope, scope.engine->fromVariant(value)); - - int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine); - if (roleIndex != -1) - m_model->emitItemsChanged(m_elementIndex, 1, QVector(1, roleIndex)); -} - -// Does the emission of the notifiers when we haven't created the meta-object yet -void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount) -{ - Q_ASSERT(!m_initialized); - QQmlData *ddata = QQmlData::get(object(), /*create*/false); - if (!ddata) - return; - // There's nothing to emit if we're a list model in a worker thread. - if (!qmlEngine(m_model)) - return; - for (int i = 0; i < roleCount; ++i) { - const int changedRole = changedRoles[i]; - QQmlNotifier::notify(ddata, changedRole); - } -} - -namespace QV4 { - -bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) -{ - if (!id.isString()) - return Object::virtualPut(m, id, value, receiver); - QString propName = id.toQString(); - - ModelObject *that = static_cast(m); - - ExecutionEngine *eng = that->engine(); - const int elementIndex = that->d()->elementIndex(); - int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng); - if (roleIndex != -1) - that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector(1, roleIndex)); - - ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object()); - if (mo->initialized()) - mo->emitPropertyNotification(propName.toUtf8()); - return true; -} - -ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) -{ - if (!id.isString()) - return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); - - const ModelObject *that = static_cast(m); - Scope scope(that); - ScopedString name(scope, id.asStringOrSymbol()); - const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name); - if (!role) - return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); - if (hasProperty) - *hasProperty = true; - - if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) { - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine); - if (ep && ep->propertyCapture) - ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false); - } - - const int elementIndex = that->d()->elementIndex(); - QVariant value = that->d()->m_model->data(elementIndex, role->index); - return that->engine()->fromVariant(value); -} - -ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) -{ - lookup->getter = Lookup::getterFallback; - return lookup->getter(lookup, engine, *object); -} - -struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator -{ - int roleNameIndex = 0; - ~ModelObjectOwnPropertyKeyIterator() override = default; - PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; - -}; - -PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) -{ - const ModelObject *that = static_cast(o); - - ExecutionEngine *v4 = that->engine(); - if (roleNameIndex < that->listModel()->roleCount()) { - Scope scope(that->engine()); - const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex); - ++roleNameIndex; - ScopedString roleName(scope, v4->newString(role.name)); - if (attrs) - *attrs = QV4::Attr_Data; - if (pd) { - QVariant value = that->d()->m_model->data(that->d()->elementIndex(), role.index); - pd->value = v4->fromVariant(value); - } - return roleName->toPropertyKey(); - } - - // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add - // unnecessary entries that relate to the roles used. These just create extra work - // later on as they will just be ignored. - return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); -} - -OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target) -{ - *target = *m; - return new ModelObjectOwnPropertyKeyIterator; -} - -DEFINE_OBJECT_VTABLE(ModelObject); - -} // namespace QV4 - -DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this)) -{ - setNodeUpdatesEnabled(true); -} - -DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner) -{ - DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1)); - QVector roles; - object->updateValues(obj, roles); - return object; -} - -QVector DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target) -{ - QVector changedRoles; - for (int i = 0; i < src->m_meta->count(); ++i) { - const QByteArray &name = src->m_meta->name(i); - QVariant value = src->m_meta->value(i); - - QQmlListModel *srcModel = qobject_cast(value.value()); - QQmlListModel *targetModel = qobject_cast(target->m_meta->value(i).value()); - - bool modelHasChanges = false; - if (srcModel) { - if (targetModel == nullptr) - targetModel = QQmlListModel::createWithOwner(target->m_owner); - - modelHasChanges = QQmlListModel::sync(srcModel, targetModel); - - QObject *targetModelObject = targetModel; - value = QVariant::fromValue(targetModelObject); - } else if (targetModel) { - delete targetModel; - } - - if (target->setValue(name, value) || modelHasChanges) - changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name)); - } - return changedRoles; -} - -void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector &roles) -{ - for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) { - const QString &key = it.key(); - - int roleIndex = m_owner->m_roles.indexOf(key); - if (roleIndex == -1) { - roleIndex = m_owner->m_roles.count(); - m_owner->m_roles.append(key); - } - - QVariant value = it.value(); - - // A JS array/object is translated into a (hierarchical) QQmlListModel, - // so translate to a variant map/list first with toVariant(). - if (value.userType() == qMetaTypeId()) - value = value.value().toVariant(); - - if (value.type() == QVariant::List) { - QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner); - - QVariantList subArray = value.toList(); - QVariantList::const_iterator subIt = subArray.cbegin(); - QVariantList::const_iterator subEnd = subArray.cend(); - while (subIt != subEnd) { - const QVariantMap &subObject = subIt->toMap(); - subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); - ++subIt; - } - - QObject *subModelObject = subModel; - value = QVariant::fromValue(subModelObject); - } - - const QByteArray &keyUtf8 = key.toUtf8(); - - QQmlListModel *existingModel = qobject_cast(m_meta->value(keyUtf8).value()); - delete existingModel; - - if (m_meta->setValue(keyUtf8, value)) - roles << roleIndex; - } -} - -DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object) - : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object) -{ -} - -DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject() -{ - for (int i=0 ; i < count() ; ++i) { - QQmlListModel *subModel = qobject_cast(value(i).value()); - delete subModel; - } -} - -void DynamicRoleModelNodeMetaObject::propertyWrite(int index) -{ - if (!m_enabled) - return; - - QVariant v = value(index); - QQmlListModel *model = qobject_cast(v.value()); - delete model; -} - -void DynamicRoleModelNodeMetaObject::propertyWritten(int index) -{ - if (!m_enabled) - return; - - QQmlListModel *parentModel = m_owner->m_owner; - - QVariant v = value(index); - - // A JS array/object is translated into a (hierarchical) QQmlListModel, - // so translate to a variant map/list first with toVariant(). - if (v.userType() == qMetaTypeId()) - v= v.value().toVariant(); - - if (v.type() == QVariant::List) { - QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel); - - QVariantList subArray = v.toList(); - QVariantList::const_iterator subIt = subArray.cbegin(); - QVariantList::const_iterator subEnd = subArray.cend(); - while (subIt != subEnd) { - const QVariantMap &subObject = subIt->toMap(); - subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); - ++subIt; - } - - QObject *subModelObject = subModel; - v = QVariant::fromValue(subModelObject); - - setValue(index, v); - } - - int elementIndex = parentModel->m_modelObjects.indexOf(m_owner); - if (elementIndex != -1) { - int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData())); - if (roleIndex != -1) - parentModel->emitItemsChanged(elementIndex, 1, QVector(1, roleIndex)); - } -} - -/*! - \qmltype ListModel - \instantiates QQmlListModel - \inqmlmodule QtQml.Models - \ingroup qtquick-models - \brief Defines a free-form list data source. - - The ListModel is a simple container of ListElement definitions, each - containing data roles. The contents can be defined dynamically, or - explicitly in QML. - - The number of elements in the model can be obtained from its \l count property. - A number of familiar methods are also provided to manipulate the contents of the - model, including append(), insert(), move(), remove() and set(). These methods - accept dictionaries as their arguments; these are translated to ListElement objects - by the model. - - Elements can be manipulated via the model using the setProperty() method, which - allows the roles of the specified element to be set and changed. - - \section1 Example Usage - - The following example shows a ListModel containing three elements, with the roles - "name" and "cost". - - \div {class="float-right"} - \inlineimage listmodel.png - \enddiv - - \snippet qml/listmodel/listmodel.qml 0 - - Roles (properties) in each element must begin with a lower-case letter and - should be common to all elements in a model. The ListElement documentation - provides more guidelines for how elements should be defined. - - Since the example model contains an \c id property, it can be referenced - by views, such as the ListView in this example: - - \snippet qml/listmodel/listmodel-simple.qml 0 - \dots 8 - \snippet qml/listmodel/listmodel-simple.qml 1 - - It is possible for roles to contain list data. In the following example we - create a list of fruit attributes: - - \snippet qml/listmodel/listmodel-nested.qml model - - The delegate displays all the fruit attributes: - - \div {class="float-right"} - \inlineimage listmodel-nested.png - \enddiv - - \snippet qml/listmodel/listmodel-nested.qml delegate - - \section1 Modifying List Models - - The content of a ListModel may be created and modified using the clear(), - append(), set(), insert() and setProperty() methods. For example: - - \snippet qml/listmodel/listmodel-modify.qml delegate - - Note that when creating content dynamically the set of available properties - cannot be changed once set. Whatever properties are first added to the model - are the only permitted properties in the model. - - \section1 Using Threaded List Models with WorkerScript - - ListModel can be used together with WorkerScript access a list model - from multiple threads. This is useful if list modifications are - synchronous and take some time: the list operations can be moved to a - different thread to avoid blocking of the main GUI thread. - - Here is an example that uses WorkerScript to periodically append the - current time to a list model: - - \snippet ../quick/threading/threadedlistmodel/timedisplay.qml 0 - - The included file, \tt dataloader.mjs, looks like this: - - \snippet ../quick/threading/threadedlistmodel/dataloader.mjs 0 - - The timer in the main example sends messages to the worker script by calling - \l WorkerScript::sendMessage(). When this message is received, - \c WorkerScript.onMessage() is invoked in \c dataloader.mjs, - which appends the current time to the list model. - - Note the call to sync() from the external thread. - You must call sync() or else the changes made to the list from that - thread will not be reflected in the list model in the main thread. - - \sa {qml-data-models}{Data Models}, {Qt Quick Examples - Threading}, {Qt QML} -*/ - -QQmlListModel::QQmlListModel(QObject *parent) -: QAbstractListModel(parent) -{ - m_mainThread = true; - m_primary = true; - m_agent = nullptr; - m_dynamicRoles = false; - - m_layout = new ListLayout; - m_listModel = new ListModel(m_layout, this); - - m_engine = nullptr; -} - -QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent) -: QAbstractListModel(parent) -{ - m_mainThread = owner->m_mainThread; - m_primary = false; - m_agent = owner->m_agent; - - Q_ASSERT(owner->m_dynamicRoles == false); - m_dynamicRoles = false; - m_layout = nullptr; - m_listModel = data; - - m_engine = engine; - m_compilationUnit = owner->m_compilationUnit; -} - -QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent) -: QAbstractListModel(agent) -{ - m_mainThread = false; - m_primary = true; - m_agent = agent; - m_dynamicRoles = orig->m_dynamicRoles; - - m_layout = new ListLayout(orig->m_layout); - m_listModel = new ListModel(m_layout, this); - - if (m_dynamicRoles) - sync(orig, this); - else - ListModel::sync(orig->m_listModel, m_listModel); - - m_engine = nullptr; - m_compilationUnit = orig->m_compilationUnit; -} - -QQmlListModel::~QQmlListModel() -{ - qDeleteAll(m_modelObjects); - - if (m_primary) { - m_listModel->destroy(); - delete m_listModel; - - if (m_mainThread && m_agent) { - m_agent->modelDestroyed(); - m_agent->release(); - } - } - - m_listModel = nullptr; - - delete m_layout; - m_layout = nullptr; -} - -QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner) -{ - QQmlListModel *model = new QQmlListModel; - - model->m_mainThread = newOwner->m_mainThread; - model->m_engine = newOwner->m_engine; - model->m_agent = newOwner->m_agent; - model->m_dynamicRoles = newOwner->m_dynamicRoles; - - if (model->m_mainThread && model->m_agent) - model->m_agent->addref(); - - QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner)); - - return model; -} - -QV4::ExecutionEngine *QQmlListModel::engine() const -{ - if (m_engine == nullptr) { - m_engine = qmlEngine(this)->handle(); - } - - return m_engine; -} - -bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target) -{ - Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles); - - bool hasChanges = false; - - target->m_roles = src->m_roles; - - // Build hash of elements <-> uid for each of the lists - QHash elementHash; - for (int i = 0 ; i < target->m_modelObjects.count(); ++i) { - DynamicRoleModelNode *e = target->m_modelObjects.at(i); - int uid = e->getUid(); - ElementSync sync; - sync.target = e; - sync.targetIndex = i; - elementHash.insert(uid, sync); - } - for (int i = 0 ; i < src->m_modelObjects.count(); ++i) { - DynamicRoleModelNode *e = src->m_modelObjects.at(i); - int uid = e->getUid(); - - QHash::iterator it = elementHash.find(uid); - if (it == elementHash.end()) { - ElementSync sync; - sync.src = e; - sync.srcIndex = i; - elementHash.insert(uid, sync); - } else { - ElementSync &sync = it.value(); - sync.src = e; - sync.srcIndex = i; - } - } - - // Get list of elements that are in the target but no longer in the source. These get deleted first. - int rowsRemoved = 0; - for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) { - DynamicRoleModelNode *element = target->m_modelObjects.at(i); - ElementSync &s = elementHash.find(element->getUid()).value(); - Q_ASSERT(s.targetIndex >= 0); - // need to update the targetIndex, to keep it correct after removals - s.targetIndex -= rowsRemoved; - if (s.src == nullptr) { - Q_ASSERT(s.targetIndex == i); - hasChanges = true; - target->beginRemoveRows(QModelIndex(), i, i); - target->m_modelObjects.remove(i, 1); - target->endRemoveRows(); - delete s.target; - ++rowsRemoved; - --i; - continue; - } - } - - // Clear the target list, and append in correct order from the source - target->m_modelObjects.clear(); - for (int i = 0 ; i < src->m_modelObjects.count() ; ++i) { - DynamicRoleModelNode *element = src->m_modelObjects.at(i); - ElementSync &s = elementHash.find(element->getUid()).value(); - Q_ASSERT(s.srcIndex >= 0); - DynamicRoleModelNode *targetElement = s.target; - if (targetElement == nullptr) { - targetElement = new DynamicRoleModelNode(target, element->getUid()); - } - s.changedRoles = DynamicRoleModelNode::sync(element, targetElement); - target->m_modelObjects.append(targetElement); - } - - // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts, - // so the model indices can't be out of bounds - // - // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent - // model indices are updated correctly - int rowsInserted = 0; - for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) { - DynamicRoleModelNode *element = target->m_modelObjects.at(i); - ElementSync &s = elementHash.find(element->getUid()).value(); - Q_ASSERT(s.srcIndex >= 0); - s.srcIndex += rowsInserted; - if (s.srcIndex != s.targetIndex) { - if (s.targetIndex == -1) { - target->beginInsertRows(QModelIndex(), i, i); - target->endInsertRows(); - } else { - target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex); - target->endMoveRows(); - } - hasChanges = true; - ++rowsInserted; - } - if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) { - QModelIndex idx = target->createIndex(i, 0); - emit target->dataChanged(idx, idx, s.changedRoles); - hasChanges = true; - } - } - return hasChanges; -} - -void QQmlListModel::emitItemsChanged(int index, int count, const QVector &roles) -{ - if (count <= 0) - return; - - if (m_mainThread) - emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);; -} - -void QQmlListModel::emitItemsAboutToBeInserted(int index, int count) -{ - Q_ASSERT(index >= 0 && count >= 0); - if (m_mainThread) - beginInsertRows(QModelIndex(), index, index + count - 1); -} - -void QQmlListModel::emitItemsInserted() -{ - if (m_mainThread) { - endInsertRows(); - emit countChanged(); - } -} - -QQmlListModelWorkerAgent *QQmlListModel::agent() -{ - if (m_agent) - return m_agent; - - m_agent = new QQmlListModelWorkerAgent(this); - return m_agent; -} - -QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const -{ - return row >= 0 && row < count() && column == 0 && !parent.isValid() - ? createIndex(row, column) - : QModelIndex(); -} - -int QQmlListModel::rowCount(const QModelIndex &parent) const -{ - return !parent.isValid() ? count() : 0; -} - -QVariant QQmlListModel::data(const QModelIndex &index, int role) const -{ - return data(index.row(), role); -} - -bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - const int row = index.row(); - if (row >= count() || row < 0) - return false; - - if (m_dynamicRoles) { - const QByteArray property = m_roles.at(role).toUtf8(); - if (m_modelObjects[row]->setValue(property, value)) { - emitItemsChanged(row, 1, QVector(1, role)); - return true; - } - } else { - const ListLayout::Role &r = m_listModel->getExistingRole(role); - const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value); - if (roleIndex != -1) { - emitItemsChanged(row, 1, QVector(1, role)); - return true; - } - } - - return false; -} - -QVariant QQmlListModel::data(int index, int role) const -{ - QVariant v; - - if (index >= count() || index < 0) - return v; - - if (m_dynamicRoles) - v = m_modelObjects[index]->getValue(m_roles[role]); - else - v = m_listModel->getProperty(index, role, this, engine()); - - return v; -} - -QHash QQmlListModel::roleNames() const -{ - QHash roleNames; - - if (m_dynamicRoles) { - for (int i = 0 ; i < m_roles.count() ; ++i) - roleNames.insert(i, m_roles.at(i).toUtf8()); - } else { - for (int i = 0 ; i < m_listModel->roleCount() ; ++i) { - const ListLayout::Role &r = m_listModel->getExistingRole(i); - roleNames.insert(i, r.name.toUtf8()); - } - } - - return roleNames; -} - -/*! - \qmlproperty bool ListModel::dynamicRoles - - By default, the type of a role is fixed the first time - the role is used. For example, if you create a role called - "data" and assign a number to it, you can no longer assign - a string to the "data" role. However, when the dynamicRoles - property is enabled, the type of a given role is not fixed - and can be different between elements. - - The dynamicRoles property must be set before any data is - added to the ListModel, and must be set from the main - thread. - - A ListModel that has data statically defined (via the - ListElement QML syntax) cannot have the dynamicRoles - property enabled. - - There is a significant performance cost to using a - ListModel with dynamic roles enabled. The cost varies - from platform to platform but is typically somewhere - between 4-6x slower than using static role types. - - Due to the performance cost of using dynamic roles, - they are disabled by default. -*/ -void QQmlListModel::setDynamicRoles(bool enableDynamicRoles) -{ - if (m_mainThread && m_agent == nullptr) { - if (enableDynamicRoles) { - if (m_layout->roleCount()) - qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty"); - else - m_dynamicRoles = true; - } else { - if (m_roles.count()) { - qmlWarning(this) << tr("unable to enable static roles as this model is not empty"); - } else { - m_dynamicRoles = false; - } - } - } else { - qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created"); - } -} - -/*! - \qmlproperty int ListModel::count - The number of data entries in the model. -*/ -int QQmlListModel::count() const -{ - return m_dynamicRoles ? m_modelObjects.count() : m_listModel->elementCount(); -} - -/*! - \qmlmethod ListModel::clear() - - Deletes all content from the model. - - \sa append(), remove() -*/ -void QQmlListModel::clear() -{ - removeElements(0, count()); -} - -/*! - \qmlmethod ListModel::remove(int index, int count = 1) - - Deletes the content at \a index from the model. - - \sa clear() -*/ -void QQmlListModel::remove(QQmlV4Function *args) -{ - int argLength = args->length(); - - if (argLength == 1 || argLength == 2) { - QV4::Scope scope(args->v4engine()); - int index = QV4::ScopedValue(scope, (*args)[0])->toInt32(); - int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1); - - if (index < 0 || index+removeCount > count() || removeCount <= 0) { - qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count()); - return; - } - - removeElements(index, removeCount); - } else { - qmlWarning(this) << tr("remove: incorrect number of arguments"); - } -} - -void QQmlListModel::removeElements(int index, int removeCount) -{ - Q_ASSERT(index >= 0 && removeCount >= 0); - - if (!removeCount) - return; - - if (m_mainThread) - beginRemoveRows(QModelIndex(), index, index + removeCount - 1); - - QVector> toDestroy; - if (m_dynamicRoles) { - for (int i=0 ; i < removeCount ; ++i) { - auto modelObject = m_modelObjects[index+i]; - toDestroy.append([modelObject](){ - delete modelObject; - }); - } - m_modelObjects.remove(index, removeCount); - } else { - toDestroy = m_listModel->remove(index, removeCount); - } - - if (m_mainThread) { - endRemoveRows(); - emit countChanged(); - } - for (const auto &destroyer : toDestroy) - destroyer(); -} - -/*! - \qmlmethod ListModel::insert(int index, jsobject dict) - - Adds a new item to the list model at position \a index, with the - values in \a dict. - - \code - fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"}) - \endcode - - The \a index must be to an existing item in the list, or one past - the end of the list (equivalent to append). - - \sa set(), append() -*/ - -void QQmlListModel::insert(QQmlV4Function *args) -{ - if (args->length() == 2) { - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue arg0(scope, (*args)[0]); - int index = arg0->toInt32(); - - if (index < 0 || index > count()) { - qmlWarning(this) << tr("insert: index %1 out of range").arg(index); - return; - } - - QV4::ScopedObject argObject(scope, (*args)[1]); - QV4::ScopedArrayObject objectArray(scope, (*args)[1]); - if (objectArray) { - QV4::ScopedObject argObject(scope); - - int objectArrayLength = objectArray->getLength(); - emitItemsAboutToBeInserted(index, objectArrayLength); - for (int i=0 ; i < objectArrayLength ; ++i) { - argObject = objectArray->get(i); - - if (m_dynamicRoles) { - m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); - } else { - m_listModel->insert(index+i, argObject); - } - } - emitItemsInserted(); - } else if (argObject) { - emitItemsAboutToBeInserted(index, 1); - - if (m_dynamicRoles) { - m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); - } else { - m_listModel->insert(index, argObject); - } - - emitItemsInserted(); - } else { - qmlWarning(this) << tr("insert: value is not an object"); - } - } else { - qmlWarning(this) << tr("insert: value is not an object"); - } -} - -/*! - \qmlmethod ListModel::move(int from, int to, int n) - - Moves \a n items \a from one position \a to another. - - The from and to ranges must exist; for example, to move the first 3 items - to the end of the list: - - \code - fruitModel.move(0, fruitModel.count - 3, 3) - \endcode - - \sa append() -*/ -void QQmlListModel::move(int from, int to, int n) -{ - if (n == 0 || from == to) - return; - if (!canMove(from, to, n)) { - qmlWarning(this) << tr("move: out of range"); - return; - } - - if (m_mainThread) - beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to); - - if (m_dynamicRoles) { - - int realFrom = from; - int realTo = to; - int realN = n; - - if (from > to) { - // Only move forwards - flip if backwards moving - int tfrom = from; - int tto = to; - realFrom = tto; - realTo = tto+n; - realN = tfrom-tto; - } - - QPODVector store; - for (int i=0 ; i < (realTo-realFrom) ; ++i) - store.append(m_modelObjects[realFrom+realN+i]); - for (int i=0 ; i < realN ; ++i) - store.append(m_modelObjects[realFrom+i]); - for (int i=0 ; i < store.count() ; ++i) - m_modelObjects[realFrom+i] = store[i]; - - } else { - m_listModel->move(from, to, n); - } - - if (m_mainThread) - endMoveRows(); -} - -/*! - \qmlmethod ListModel::append(jsobject dict) - - Adds a new item to the end of the list model, with the - values in \a dict. - - \code - fruitModel.append({"cost": 5.95, "name":"Pizza"}) - \endcode - - \sa set(), remove() -*/ -void QQmlListModel::append(QQmlV4Function *args) -{ - if (args->length() == 1) { - QV4::Scope scope(args->v4engine()); - QV4::ScopedObject argObject(scope, (*args)[0]); - QV4::ScopedArrayObject objectArray(scope, (*args)[0]); - - if (objectArray) { - QV4::ScopedObject argObject(scope); - - int objectArrayLength = objectArray->getLength(); - if (objectArrayLength > 0) { - int index = count(); - emitItemsAboutToBeInserted(index, objectArrayLength); - - for (int i=0 ; i < objectArrayLength ; ++i) { - argObject = objectArray->get(i); - - if (m_dynamicRoles) { - m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); - } else { - m_listModel->append(argObject); - } - } - - emitItemsInserted(); - } - } else if (argObject) { - int index; - - if (m_dynamicRoles) { - index = m_modelObjects.count(); - emitItemsAboutToBeInserted(index, 1); - m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); - } else { - index = m_listModel->elementCount(); - emitItemsAboutToBeInserted(index, 1); - m_listModel->append(argObject); - } - - emitItemsInserted(); - } else { - qmlWarning(this) << tr("append: value is not an object"); - } - } else { - qmlWarning(this) << tr("append: value is not an object"); - } -} - -/*! - \qmlmethod object ListModel::get(int index) - - Returns the item at \a index in the list model. This allows the item - data to be accessed or modified from JavaScript: - - \code - Component.onCompleted: { - fruitModel.append({"cost": 5.95, "name":"Jackfruit"}); - console.log(fruitModel.get(0).cost); - fruitModel.get(0).cost = 10.95; - } - \endcode - - The \a index must be an element in the list. - - Note that properties of the returned object that are themselves objects - will also be models, and this get() method is used to access elements: - - \code - fruitModel.append(..., "attributes": - [{"name":"spikes","value":"7mm"}, - {"name":"color","value":"green"}]); - fruitModel.get(0).attributes.get(1).value; // == "green" - \endcode - - \warning The returned object is not guaranteed to remain valid. It - should not be used in \l{Property Binding}{property bindings}. - - \sa append() -*/ -QJSValue QQmlListModel::get(int index) const -{ - QV4::Scope scope(engine()); - QV4::ScopedValue result(scope, QV4::Value::undefinedValue()); - - if (index >= 0 && index < count()) { - - if (m_dynamicRoles) { - DynamicRoleModelNode *object = m_modelObjects[index]; - result = QV4::QObjectWrapper::wrap(scope.engine, object); - } else { - QObject *object = m_listModel->getOrCreateModelObject(const_cast(this), index); - QQmlData *ddata = QQmlData::get(object); - if (ddata->jsWrapper.isNullOrUndefined()) { - result = scope.engine->memoryManager->allocate(object, const_cast(this)); - // Keep track of the QObjectWrapper in persistent value storage - ddata->jsWrapper.set(scope.engine, result); - } else { - result = ddata->jsWrapper.value(); - } - } - } - - return QJSValue(engine(), result->asReturnedValue()); -} - -/*! - \qmlmethod ListModel::set(int index, jsobject dict) - - Changes the item at \a index in the list model with the - values in \a dict. Properties not appearing in \a dict - are left unchanged. - - \code - fruitModel.set(3, {"cost": 5.95, "name":"Pizza"}) - \endcode - - If \a index is equal to count() then a new item is appended to the - list. Otherwise, \a index must be an element in the list. - - \sa append() -*/ -void QQmlListModel::set(int index, const QJSValue &value) -{ - QV4::Scope scope(engine()); - QV4::ScopedObject object(scope, QJSValuePrivate::getValue(&value)); - - if (!object) { - qmlWarning(this) << tr("set: value is not an object"); - return; - } - if (index > count() || index < 0) { - qmlWarning(this) << tr("set: index %1 out of range").arg(index); - return; - } - - - if (index == count()) { - emitItemsAboutToBeInserted(index, 1); - - if (m_dynamicRoles) { - m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this)); - } else { - m_listModel->insert(index, object); - } - - emitItemsInserted(); - } else { - - QVector roles; - - if (m_dynamicRoles) { - m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles); - } else { - m_listModel->set(index, object, &roles); - } - - if (roles.count()) - emitItemsChanged(index, 1, roles); - } -} - -/*! - \qmlmethod ListModel::setProperty(int index, string property, variant value) - - Changes the \a property of the item at \a index in the list model to \a value. - - \code - fruitModel.setProperty(3, "cost", 5.95) - \endcode - - The \a index must be an element in the list. - - \sa append() -*/ -void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value) -{ - if (count() == 0 || index >= count() || index < 0) { - qmlWarning(this) << tr("set: index %1 out of range").arg(index); - return; - } - - if (m_dynamicRoles) { - int roleIndex = m_roles.indexOf(property); - if (roleIndex == -1) { - roleIndex = m_roles.count(); - m_roles.append(property); - } - if (m_modelObjects[index]->setValue(property.toUtf8(), value)) - emitItemsChanged(index, 1, QVector(1, roleIndex)); - } else { - int roleIndex = m_listModel->setOrCreateProperty(index, property, value); - if (roleIndex != -1) - emitItemsChanged(index, 1, QVector(1, roleIndex)); - } -} - -/*! - \qmlmethod ListModel::sync() - - Writes any unsaved changes to the list model after it has been modified - from a worker script. -*/ -void QQmlListModel::sync() -{ - // This is just a dummy method to make it look like sync() exists in - // ListModel (and not just QQmlListModelWorkerAgent) and to let - // us document sync(). - qmlWarning(this) << "List sync() can only be called from a WorkerScript"; -} - -bool QQmlListModelParser::verifyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding) -{ - if (binding->type >= QV4::CompiledData::Binding::Type_Object) { - const quint32 targetObjectIndex = binding->value.objectIndex; - const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex); - QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex); - if (objName != listElementTypeName) { - const QMetaObject *mo = resolveType(objName); - if (mo != &QQmlListElement::staticMetaObject) { - error(target, QQmlListModel::tr("ListElement: cannot contain nested elements")); - return false; - } - listElementTypeName = objName; // cache right name for next time - } - - if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) { - error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property")); - return false; - } - - const QV4::CompiledData::Binding *binding = target->bindingTable(); - for (quint32 i = 0; i < target->nBindings; ++i, ++binding) { - QString propName = compilationUnit->stringAt(binding->propertyNameIndex); - if (propName.isEmpty()) { - error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements")); - return false; - } - if (!verifyProperty(compilationUnit, binding)) - return false; - } - } else if (binding->type == QV4::CompiledData::Binding::Type_Script) { - QString scriptStr = binding->valueAsScriptString(compilationUnit.data()); - if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) { - QByteArray script = scriptStr.toUtf8(); - bool ok; - evaluateEnum(script, &ok); - if (!ok) { - error(binding, QQmlListModel::tr("ListElement: cannot use script for property value")); - return false; - } - } - } - - return true; -} - -bool QQmlListModelParser::applyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex) -{ - const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex); - - bool roleSet = false; - if (binding->type >= QV4::CompiledData::Binding::Type_Object) { - const quint32 targetObjectIndex = binding->value.objectIndex; - const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex); - - ListModel *subModel = nullptr; - if (outterElementIndex == -1) { - subModel = model; - } else { - const ListLayout::Role &role = model->getOrCreateListRole(elementName); - if (role.type == ListLayout::Role::List) { - subModel = model->getListProperty(outterElementIndex, role); - if (subModel == nullptr) { - subModel = new ListModel(role.subLayout, nullptr); - QVariant vModel = QVariant::fromValue(subModel); - model->setOrCreateProperty(outterElementIndex, elementName, vModel); - } - } - } - - int elementIndex = subModel ? subModel->appendElement() : -1; - - const QV4::CompiledData::Binding *subBinding = target->bindingTable(); - for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) { - roleSet |= applyProperty(compilationUnit, subBinding, subModel, elementIndex); - } - - } else { - QVariant value; - - if (binding->isTranslationBinding()) { - value = QVariant::fromValue(binding); - } else if (binding->evaluatesToString()) { - value = binding->valueAsString(compilationUnit.data()); - } else if (binding->type == QV4::CompiledData::Binding::Type_Number) { - value = binding->valueAsNumber(compilationUnit->constants); - } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) { - value = binding->valueAsBoolean(); - } else if (binding->type == QV4::CompiledData::Binding::Type_Null) { - value = QVariant::fromValue(nullptr); - } else if (binding->type == QV4::CompiledData::Binding::Type_Script) { - QString scriptStr = binding->valueAsScriptString(compilationUnit.data()); - if (definesEmptyList(scriptStr)) { - const ListLayout::Role &role = model->getOrCreateListRole(elementName); - ListModel *emptyModel = new ListModel(role.subLayout, nullptr); - value = QVariant::fromValue(emptyModel); - } else if (binding->isFunctionExpression()) { - QQmlBinding::Identifier id = binding->value.compiledScriptIndex; - Q_ASSERT(id != QQmlBinding::Invalid); - - auto v4 = compilationUnit->engine; - QV4::Scope scope(v4); - // for now we do not provide a context object; data from the ListElement must be passed to the function - QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr)); - QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id])); - - QV4::ReturnedValue result = function->call(v4->globalObject, nullptr, 0); - - QJSValue v; - QJSValuePrivate::setValue(&v, v4, result); - value.setValue(v); - } else { - QByteArray script = scriptStr.toUtf8(); - bool ok; - value = evaluateEnum(script, &ok); - } - } else { - Q_UNREACHABLE(); - } - - model->setOrCreateProperty(outterElementIndex, elementName, value); - roleSet = true; - } - return roleSet; -} - -void QQmlListModelParser::verifyBindings(const QQmlRefPointer &compilationUnit, const QList &bindings) -{ - listElementTypeName = QString(); // unknown - - for (const QV4::CompiledData::Binding *binding : bindings) { - QString propName = compilationUnit->stringAt(binding->propertyNameIndex); - if (!propName.isEmpty()) { // isn't default property - error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName)); - return; - } - if (!verifyProperty(compilationUnit, binding)) - return; - } -} - -void QQmlListModelParser::applyBindings(QObject *obj, const QQmlRefPointer &compilationUnit, const QList &bindings) -{ - QQmlListModel *rv = static_cast(obj); - - rv->m_engine = qmlEngine(rv)->handle(); - rv->m_compilationUnit = compilationUnit; - - bool setRoles = false; - - for (const QV4::CompiledData::Binding *binding : bindings) { - if (binding->type != QV4::CompiledData::Binding::Type_Object) - continue; - setRoles |= applyProperty(compilationUnit, binding, rv->m_listModel, /*outter element index*/-1); - } - - if (setRoles == false) - qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set."; -} - -bool QQmlListModelParser::definesEmptyList(const QString &s) -{ - if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) { - for (int i=1; i -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -QT_REQUIRE_CONFIG(qml_list_model); - -QT_BEGIN_NAMESPACE - - -class QQmlListModelWorkerAgent; -class ListModel; -class ListLayout; - -namespace QV4 { -struct ModelObject; -} - -class Q_QML_PRIVATE_EXPORT QQmlListModel : public QAbstractListModel -{ - Q_OBJECT - Q_PROPERTY(int count READ count NOTIFY countChanged) - Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles) - -public: - QQmlListModel(QObject *parent=nullptr); - ~QQmlListModel(); - - QModelIndex index(int row, int column, const QModelIndex &parent) const override; - int rowCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QHash roleNames() const override; - - QVariant data(int index, int role) const; - int count() const; - - Q_INVOKABLE void clear(); - Q_INVOKABLE void remove(QQmlV4Function *args); - Q_INVOKABLE void append(QQmlV4Function *args); - Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QJSValue get(int index) const; - Q_INVOKABLE void set(int index, const QJSValue &value); - Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); - Q_INVOKABLE void move(int from, int to, int count); - Q_INVOKABLE void sync(); - - QQmlListModelWorkerAgent *agent(); - - bool dynamicRoles() const { return m_dynamicRoles; } - void setDynamicRoles(bool enableDynamicRoles); - -Q_SIGNALS: - void countChanged(); - -private: - friend class QQmlListModelParser; - friend class QQmlListModelWorkerAgent; - friend class ModelObject; - friend struct QV4::ModelObject; - friend class ModelNodeMetaObject; - friend class ListModel; - friend class ListElement; - friend class DynamicRoleModelNode; - friend class DynamicRoleModelNodeMetaObject; - friend struct StringOrTranslation; - - // Constructs a flat list model for a worker agent - QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent); - QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent=nullptr); - - QV4::ExecutionEngine *engine() const; - - inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); } - - QQmlListModelWorkerAgent *m_agent; - mutable QV4::ExecutionEngine *m_engine; - QQmlRefPointer m_compilationUnit; - bool m_mainThread; - bool m_primary; - - bool m_dynamicRoles; - - ListLayout *m_layout; - ListModel *m_listModel; - - QVector m_modelObjects; - QVector m_roles; - - struct ElementSync - { - DynamicRoleModelNode *src = nullptr; - DynamicRoleModelNode *target = nullptr; - int srcIndex = -1; - int targetIndex = -1; - QVector changedRoles; - }; - - static bool sync(QQmlListModel *src, QQmlListModel *target); - static QQmlListModel *createWithOwner(QQmlListModel *newOwner); - - void emitItemsChanged(int index, int count, const QVector &roles); - void emitItemsAboutToBeInserted(int index, int count); - void emitItemsInserted(); - - void removeElements(int index, int removeCount); -}; - -// ### FIXME -class QQmlListElement : public QObject -{ -Q_OBJECT -}; - -class QQmlListModelParser : public QQmlCustomParser -{ -public: - enum PropertyType { - Invalid, - Boolean, - Number, - String, - Script - }; - - - QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {} - - void verifyBindings(const QQmlRefPointer &compilationUnit, const QList &bindings) override; - void applyBindings(QObject *obj, const QQmlRefPointer &compilationUnit, const QList &bindings) override; - -private: - bool verifyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding); - // returns true if a role was set - bool applyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex); - - static bool definesEmptyList(const QString &); - - QString listElementTypeName; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlListModel) -QML_DECLARE_TYPE(QQmlListElement) - -#endif // QQMLLISTMODEL_H diff --git a/src/qml/types/qqmllistmodel_p_p.h b/src/qml/types/qqmllistmodel_p_p.h deleted file mode 100644 index 2876c71de6..0000000000 --- a/src/qml/types/qqmllistmodel_p_p.h +++ /dev/null @@ -1,428 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLLISTMODEL_P_P_H -#define QQMLLISTMODEL_P_P_H - -// -// 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. -// - -#include "qqmllistmodel_p.h" -#include -#include -#include -#include - -QT_REQUIRE_CONFIG(qml_list_model); - -QT_BEGIN_NAMESPACE - - -class DynamicRoleModelNode; - -class DynamicRoleModelNodeMetaObject : public QQmlOpenMetaObject -{ -public: - DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object); - ~DynamicRoleModelNodeMetaObject(); - - bool m_enabled; - -protected: - void propertyWrite(int index) override; - void propertyWritten(int index) override; - -private: - DynamicRoleModelNode *m_owner; -}; - -class DynamicRoleModelNode : public QObject -{ - Q_OBJECT -public: - DynamicRoleModelNode(QQmlListModel *owner, int uid); - - static DynamicRoleModelNode *create(const QVariantMap &obj, QQmlListModel *owner); - - void updateValues(const QVariantMap &object, QVector &roles); - - QVariant getValue(const QString &name) const - { - return m_meta->value(name.toUtf8()); - } - - bool setValue(const QByteArray &name, const QVariant &val) - { - return m_meta->setValue(name, val); - } - - void setNodeUpdatesEnabled(bool enable) - { - m_meta->m_enabled = enable; - } - - int getUid() const - { - return m_uid; - } - - static QVector sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target); - -private: - QQmlListModel *m_owner; - int m_uid; - DynamicRoleModelNodeMetaObject *m_meta; - - friend class DynamicRoleModelNodeMetaObject; -}; - -class ModelNodeMetaObject : public QQmlOpenMetaObject -{ -public: - ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex); - ~ModelNodeMetaObject(); - - QAbstractDynamicMetaObject *toDynamicMetaObject(QObject *object) override; - - static ModelNodeMetaObject *get(QObject *obj); - - bool m_enabled; - QQmlListModel *m_model; - int m_elementIndex; - - void updateValues(); - void updateValues(const QVector &roles); - - bool initialized() const { return m_initialized; } - -protected: - void propertyWritten(int index) override; - -private: - using QQmlOpenMetaObject::setValue; - - void emitDirectNotifies(const int *changedRoles, int roleCount); - - void initialize(); - bool m_initialized; -}; - -namespace QV4 { - -namespace Heap { - -struct ModelObject : public QObjectWrapper { - void init(QObject *object, QQmlListModel *model) - { - QObjectWrapper::init(object); - m_model = model; - QObjectPrivate *op = QObjectPrivate::get(object); - m_nodeModelMetaObject = static_cast(op->metaObject); - } - void destroy() { QObjectWrapper::destroy(); } - int elementIndex() const { return m_nodeModelMetaObject->m_elementIndex; } - QQmlListModel *m_model; - ModelNodeMetaObject *m_nodeModelMetaObject; -}; - -} - -struct ModelObject : public QObjectWrapper -{ - V4_OBJECT2(ModelObject, QObjectWrapper) - V4_NEEDS_DESTROY - - ListModel *listModel() const { return d()->m_model->m_listModel; } - -protected: - static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver); - static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); - static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); - static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); - static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); -}; - -} // namespace QV4 - -class ListLayout -{ -public: - ListLayout() : currentBlock(0), currentBlockOffset(0) {} - ListLayout(const ListLayout *other); - ~ListLayout(); - - class Role - { - public: - - Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {} - explicit Role(const Role *other); - ~Role(); - - // This enum must be kept in sync with the roleTypeNames variable in qqmllistmodel.cpp - enum DataType - { - Invalid = -1, - - String, - Number, - Bool, - List, - QObject, - VariantMap, - DateTime, - Function, - - MaxDataType - }; - - QString name; - DataType type; - int blockIndex; - int blockOffset; - int index; - ListLayout *subLayout; - }; - - const Role *getRoleOrCreate(const QString &key, const QVariant &data); - const Role &getRoleOrCreate(QV4::String *key, Role::DataType type); - const Role &getRoleOrCreate(const QString &key, Role::DataType type); - - const Role &getExistingRole(int index) const { return *roles.at(index); } - const Role *getExistingRole(const QString &key) const; - const Role *getExistingRole(QV4::String *key) const; - - int roleCount() const { return roles.count(); } - - static void sync(ListLayout *src, ListLayout *target); - -private: - const Role &createRole(const QString &key, Role::DataType type); - - int currentBlock; - int currentBlockOffset; - QVector roles; - QStringHash roleHash; -}; - -struct StringOrTranslation -{ - explicit StringOrTranslation(const QString &s); - explicit StringOrTranslation(const QV4::CompiledData::Binding *binding); - ~StringOrTranslation(); - bool isSet() const { return d.flag(); } - bool isTranslation() const { return d.isT2(); } - void setString(const QString &s); - void setTranslation(const QV4::CompiledData::Binding *binding); - QString toString(const QQmlListModel *owner) const; - QString asString() const; -private: - void clear(); - QBiPointer d; -}; - -/*! -\internal -*/ -class ListElement -{ -public: - - ListElement(); - ListElement(int existingUid); - ~ListElement(); - - static QVector sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout); - - enum - { - BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelNodeMetaObject *) - }; - -private: - - void destroy(ListLayout *layout); - - int setVariantProperty(const ListLayout::Role &role, const QVariant &d); - - int setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng); - - int setStringProperty(const ListLayout::Role &role, const QString &s); - int setDoubleProperty(const ListLayout::Role &role, double n); - int setBoolProperty(const ListLayout::Role &role, bool b); - int setListProperty(const ListLayout::Role &role, ListModel *m); - int setQObjectProperty(const ListLayout::Role &role, QObject *o); - int setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o); - int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m); - int setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt); - int setFunctionProperty(const ListLayout::Role &role, const QJSValue &f); - int setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b); - - void setStringPropertyFast(const ListLayout::Role &role, const QString &s); - void setDoublePropertyFast(const ListLayout::Role &role, double n); - void setBoolPropertyFast(const ListLayout::Role &role, bool b); - void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o); - void setListPropertyFast(const ListLayout::Role &role, ListModel *m); - void setVariantMapFast(const ListLayout::Role &role, QV4::Object *o); - void setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt); - void setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f); - - void clearProperty(const ListLayout::Role &role); - - QVariant getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng); - ListModel *getListProperty(const ListLayout::Role &role); - StringOrTranslation *getStringProperty(const ListLayout::Role &role); - QObject *getQObjectProperty(const ListLayout::Role &role); - QPointer *getGuardProperty(const ListLayout::Role &role); - QVariantMap *getVariantMapProperty(const ListLayout::Role &role); - QDateTime *getDateTimeProperty(const ListLayout::Role &role); - QJSValue *getFunctionProperty(const ListLayout::Role &role); - - inline char *getPropertyMemory(const ListLayout::Role &role); - - int getUid() const { return uid; } - - ModelNodeMetaObject *objectCache(); - - char data[BLOCK_SIZE]; - ListElement *next; - - int uid; - QObject *m_objectCache; - - friend class ListModel; -}; - -/*! -\internal -*/ -class ListModel -{ -public: - - ListModel(ListLayout *layout, QQmlListModel *modelCache); - ~ListModel() {} - - void destroy(); - - int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data); - int setExistingProperty(int uid, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng); - - QVariant getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng); - ListModel *getListProperty(int elementIndex, const ListLayout::Role &role); - - int roleCount() const - { - return m_layout->roleCount(); - } - - const ListLayout::Role &getExistingRole(int index) const - { - return m_layout->getExistingRole(index); - } - - const ListLayout::Role *getExistingRole(QV4::String *key) const - { - return m_layout->getExistingRole(key); - } - - const ListLayout::Role &getOrCreateListRole(const QString &name) - { - return m_layout->getRoleOrCreate(name, ListLayout::Role::List); - } - - int elementCount() const - { - return elements.count(); - } - - void set(int elementIndex, QV4::Object *object, QVector *roles); - void set(int elementIndex, QV4::Object *object); - - int append(QV4::Object *object); - void insert(int elementIndex, QV4::Object *object); - - Q_REQUIRED_RESULT QVector> remove(int index, int count); - - int appendElement(); - void insertElement(int index); - - void move(int from, int to, int n); - - static bool sync(ListModel *src, ListModel *target); - - QObject *getOrCreateModelObject(QQmlListModel *model, int elementIndex); - -private: - QPODVector elements; - ListLayout *m_layout; - - QQmlListModel *m_modelCache; - - struct ElementSync - { - ListElement *src = nullptr; - ListElement *target = nullptr; - int srcIndex = -1; - int targetIndex = -1; - QVector changedRoles; - }; - - void newElement(int index); - - void updateCacheIndices(int start = 0, int end = -1); - - friend class ListElement; - friend class QQmlListModelWorkerAgent; - friend class QQmlListModelParser; -}; - -QT_END_NAMESPACE - -Q_DECLARE_METATYPE(ListModel *); - -#endif // QQUICKLISTMODEL_P_P_H diff --git a/src/qml/types/qqmllistmodelworkeragent.cpp b/src/qml/types/qqmllistmodelworkeragent.cpp deleted file mode 100644 index f7cb08dcf4..0000000000 --- a/src/qml/types/qqmllistmodelworkeragent.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmllistmodelworkeragent_p.h" -#include "qqmllistmodel_p_p.h" -#include -#include -#include - -#include -#include -#include - - -QT_BEGIN_NAMESPACE - -QQmlListModelWorkerAgent::Sync::~Sync() -{ -} - -QQmlListModelWorkerAgent::QQmlListModelWorkerAgent(QQmlListModel *model) -: m_ref(1), m_orig(model), m_copy(new QQmlListModel(model, this)) -{ -} - -QQmlListModelWorkerAgent::~QQmlListModelWorkerAgent() -{ - mutex.lock(); - syncDone.wakeAll(); - mutex.unlock(); -} - -void QQmlListModelWorkerAgent::setEngine(QV4::ExecutionEngine *eng) -{ - m_copy->m_engine = eng; -} - -void QQmlListModelWorkerAgent::addref() -{ - m_ref.ref(); -} - -void QQmlListModelWorkerAgent::release() -{ - bool del = !m_ref.deref(); - - if (del) - deleteLater(); -} - -void QQmlListModelWorkerAgent::modelDestroyed() -{ - m_orig = nullptr; -} - -int QQmlListModelWorkerAgent::count() const -{ - return m_copy->count(); -} - -void QQmlListModelWorkerAgent::clear() -{ - m_copy->clear(); -} - -void QQmlListModelWorkerAgent::remove(QQmlV4Function *args) -{ - m_copy->remove(args); -} - -void QQmlListModelWorkerAgent::append(QQmlV4Function *args) -{ - m_copy->append(args); -} - -void QQmlListModelWorkerAgent::insert(QQmlV4Function *args) -{ - m_copy->insert(args); -} - -QJSValue QQmlListModelWorkerAgent::get(int index) const -{ - return m_copy->get(index); -} - -void QQmlListModelWorkerAgent::set(int index, const QJSValue &value) -{ - m_copy->set(index, value); -} - -void QQmlListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value) -{ - m_copy->setProperty(index, property, value); -} - -void QQmlListModelWorkerAgent::move(int from, int to, int count) -{ - m_copy->move(from, to, count); -} - -void QQmlListModelWorkerAgent::sync() -{ - Sync *s = new Sync(m_copy); - - mutex.lock(); - QCoreApplication::postEvent(this, s); - syncDone.wait(&mutex); - mutex.unlock(); -} - -bool QQmlListModelWorkerAgent::event(QEvent *e) -{ - if (e->type() == QEvent::User) { - bool cc = false; - QMutexLocker locker(&mutex); - if (m_orig) { - Sync *s = static_cast(e); - - cc = (m_orig->count() != s->list->count()); - - Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles); - if (m_orig->m_dynamicRoles) - QQmlListModel::sync(s->list, m_orig); - else - ListModel::sync(s->list->m_listModel, m_orig->m_listModel); - } - - syncDone.wakeAll(); - locker.unlock(); - - if (cc) - emit m_orig->countChanged(); - return true; - } - - return QObject::event(e); -} - -QT_END_NAMESPACE - -#include "moc_qqmllistmodelworkeragent_p.cpp" diff --git a/src/qml/types/qqmllistmodelworkeragent_p.h b/src/qml/types/qqmllistmodelworkeragent_p.h deleted file mode 100644 index 69d1785618..0000000000 --- a/src/qml/types/qqmllistmodelworkeragent_p.h +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQUICKLISTMODELWORKERAGENT_P_H -#define QQUICKLISTMODELWORKERAGENT_P_H - -// -// 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. -// - -#include - -#include -#include -#include - -#include - -QT_REQUIRE_CONFIG(qml_list_model); - -QT_BEGIN_NAMESPACE - - -class QQmlListModel; - -class QQmlListModelWorkerAgent : public QObject -{ - Q_OBJECT - Q_PROPERTY(int count READ count) - -public: - QQmlListModelWorkerAgent(QQmlListModel *); - ~QQmlListModelWorkerAgent(); - void setEngine(QV4::ExecutionEngine *eng); - - void addref(); - void release(); - - int count() const; - - Q_INVOKABLE void clear(); - Q_INVOKABLE void remove(QQmlV4Function *args); - Q_INVOKABLE void append(QQmlV4Function *args); - Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QJSValue get(int index) const; - Q_INVOKABLE void set(int index, const QJSValue &value); - Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); - Q_INVOKABLE void move(int from, int to, int count); - Q_INVOKABLE void sync(); - - struct VariantRef - { - VariantRef() : a(nullptr) {} - VariantRef(const VariantRef &r) : a(r.a) { if (a) a->addref(); } - VariantRef(QQmlListModelWorkerAgent *_a) : a(_a) { if (a) a->addref(); } - ~VariantRef() { if (a) a->release(); } - - VariantRef &operator=(const VariantRef &o) { - if (o.a) o.a->addref(); - if (a) a->release(); - a = o.a; - return *this; - } - - QQmlListModelWorkerAgent *a; - }; - - void modelDestroyed(); -protected: - bool event(QEvent *) override; - -private: - friend class QQuickWorkerScriptEnginePrivate; - friend class QQmlListModel; - - struct Sync : public QEvent { - Sync(QQmlListModel *l) - : QEvent(QEvent::User) - , list(l) - {} - ~Sync(); - QQmlListModel *list; - }; - - QAtomicInt m_ref; - QQmlListModel *m_orig; - QQmlListModel *m_copy; - QMutex mutex; - QWaitCondition syncDone; -}; - -QT_END_NAMESPACE - -Q_DECLARE_METATYPE(QQmlListModelWorkerAgent::VariantRef) - -#endif // QQUICKLISTMODELWORKERAGENT_P_H - diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp deleted file mode 100644 index 840b435d18..0000000000 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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 "qqmlmodelsmodule_p.h" -#include -#if QT_CONFIG(qml_list_model) -#include -#endif -#if QT_CONFIG(qml_delegate_model) -#include -#include -#endif -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - -void QQmlModelsModule::registerQmlTypes() -{ - // Don't add anything here. These are only for backwards compatibility. - qmlRegisterType("QtQml", 2, 1, "Instantiator"); // Only available in >= 2.1 - qmlRegisterType(); -} - -void QQmlModelsModule::registerQuickTypes() -{ - // Don't add anything here. These are only for backwards compatibility. - - const char uri[] = "QtQuick"; - - qmlRegisterType(uri, 2, 1, "Instantiator"); - qmlRegisterType(); -#if QT_CONFIG(qml_list_model) - qmlRegisterType(uri, 2, 0, "ListElement"); - qmlRegisterCustomType(uri, 2, 0, "ListModel", new QQmlListModelParser); -#endif - qmlRegisterType(uri, 2, 0, "Package"); -#if QT_CONFIG(qml_delegate_model) - qmlRegisterType(uri, 2, 0, "VisualDataModel"); - qmlRegisterType(uri, 2, 0, "VisualDataGroup"); -#endif - qmlRegisterType(uri, 2, 0, "VisualItemModel"); -} - -#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - -void QQmlModelsModule::defineModule() -{ - const char uri[] = "QtQml.Models"; - -#if QT_CONFIG(qml_list_model) - qmlRegisterType(uri, 2, 1, "ListElement"); - qmlRegisterCustomType(uri, 2, 1, "ListModel", new QQmlListModelParser); -#endif -#if QT_CONFIG(qml_delegate_model) - qmlRegisterType(uri, 2, 1, "DelegateModel"); - qmlRegisterType(uri, 2, 1, "DelegateModelGroup"); -#endif - qmlRegisterType(uri, 2, 1, "ObjectModel"); - qmlRegisterType(uri, 2, 3, "ObjectModel"); - - qmlRegisterType(uri, 2, 2, "ItemSelectionModel"); - - qmlRegisterType(uri, 2, 14, "Package"); - qmlRegisterType(uri, 2, 14, "Instantiator"); - qmlRegisterType(); -} - -void QQmlModelsModule::defineLabsModule() -{ - const char uri[] = "Qt.labs.qmlmodels"; - -#if QT_CONFIG(qml_delegate_model) - qmlRegisterUncreatableType(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent.")); - qmlRegisterType(uri, 1, 0, "DelegateChooser"); - qmlRegisterType(uri, 1, 0, "DelegateChoice"); -#endif - qmlRegisterType(uri, 1, 0, "TableModel"); - qmlRegisterType(uri, 1, 0, "TableModelColumn"); -} - -QT_END_NAMESPACE diff --git a/src/qml/types/qqmlmodelsmodule_p.h b/src/qml/types/qqmlmodelsmodule_p.h deleted file mode 100644 index 2bb04f1e11..0000000000 --- a/src/qml/types/qqmlmodelsmodule_p.h +++ /dev/null @@ -1,72 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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$ -** -****************************************************************************/ - -#ifndef QQMLMODELSMODULE_H -#define QQMLMODELSMODULE_H - -// -// 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. -// - -#include - -QT_BEGIN_NAMESPACE - -class Q_QML_PRIVATE_EXPORT QQmlModelsModule -{ -public: -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - static void registerQmlTypes(); - static void registerQuickTypes(); -#endif - - static void defineModule(); - static void defineLabsModule(); -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/qml/types/qqmlobjectmodel.cpp b/src/qml/types/qqmlobjectmodel.cpp deleted file mode 100644 index b6330b4295..0000000000 --- a/src/qml/types/qqmlobjectmodel.cpp +++ /dev/null @@ -1,431 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmlobjectmodel_p.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -QHash QQmlObjectModelAttached::attachedProperties; - - -class QQmlObjectModelPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QQmlObjectModel) -public: - class Item { - public: - Item(QObject *i) : item(i), ref(0) {} - - void addRef() { ++ref; } - bool deref() { return --ref == 0; } - - QObject *item; - int ref; - }; - - QQmlObjectModelPrivate() : QObjectPrivate(), moveId(0) {} - - static void children_append(QQmlListProperty *prop, QObject *item) { - int index = static_cast(prop->data)->children.count(); - static_cast(prop->data)->insert(index, item); - } - - static int children_count(QQmlListProperty *prop) { - return static_cast(prop->data)->children.count(); - } - - static QObject *children_at(QQmlListProperty *prop, int index) { - return static_cast(prop->data)->children.at(index).item; - } - - static void children_clear(QQmlListProperty *prop) { - static_cast(prop->data)->clear(); - } - - void insert(int index, QObject *item) { - Q_Q(QQmlObjectModel); - children.insert(index, Item(item)); - for (int i = index; i < children.count(); ++i) { - QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); - attached->setIndex(i); - } - QQmlChangeSet changeSet; - changeSet.insert(index, 1); - emit q->modelUpdated(changeSet, false); - emit q->countChanged(); - emit q->childrenChanged(); - } - - void move(int from, int to, int n) { - Q_Q(QQmlObjectModel); - if (from > to) { - // Only move forwards - flip if backwards moving - int tfrom = from; - int tto = to; - from = tto; - to = tto+n; - n = tfrom-tto; - } - - QPODVector store; - for (int i = 0; i < to - from; ++i) - store.append(children[from + n + i]); - for (int i = 0; i < n; ++i) - store.append(children[from + i]); - - for (int i = 0; i < store.count(); ++i) { - children[from + i] = store[i]; - QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(from + i).item); - attached->setIndex(from + i); - } - - QQmlChangeSet changeSet; - changeSet.move(from, to, n, ++moveId); - emit q->modelUpdated(changeSet, false); - emit q->childrenChanged(); - } - - void remove(int index, int n) { - Q_Q(QQmlObjectModel); - for (int i = index; i < index + n; ++i) { - QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); - attached->setIndex(-1); - } - children.erase(children.begin() + index, children.begin() + index + n); - for (int i = index; i < children.count(); ++i) { - QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); - attached->setIndex(i); - } - QQmlChangeSet changeSet; - changeSet.remove(index, n); - emit q->modelUpdated(changeSet, false); - emit q->countChanged(); - emit q->childrenChanged(); - } - - void clear() { - Q_Q(QQmlObjectModel); - for (const Item &child : qAsConst(children)) - emit q->destroyingItem(child.item); - remove(0, children.count()); - } - - int indexOf(QObject *item) const { - for (int i = 0; i < children.count(); ++i) - if (children.at(i).item == item) - return i; - return -1; - } - - uint moveId; - QList children; -}; - - -/*! - \qmltype ObjectModel - \instantiates QQmlObjectModel - \inqmlmodule QtQml.Models - \ingroup qtquick-models - \brief Defines a set of items to be used as a model. - - An ObjectModel contains the visual items to be used in a view. - When an ObjectModel is used in a view, the view does not require - a delegate since the ObjectModel already contains the visual - delegate (items). - - An item can determine its index within the - model via the \l{ObjectModel::index}{index} attached property. - - The example below places three colored rectangles in a ListView. - \code - import QtQuick 2.0 - import QtQml.Models 2.1 - - Rectangle { - ObjectModel { - id: itemModel - Rectangle { height: 30; width: 80; color: "red" } - Rectangle { height: 30; width: 80; color: "green" } - Rectangle { height: 30; width: 80; color: "blue" } - } - - ListView { - anchors.fill: parent - model: itemModel - } - } - \endcode - - \image objectmodel.png - - \sa {Qt Quick Examples - Views} -*/ - -QQmlObjectModel::QQmlObjectModel(QObject *parent) - : QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent) -{ -} - -/*! - \qmlattachedproperty int QtQml.Models::ObjectModel::index - This attached property holds the index of this delegate's item within the model. - - It is attached to each instance of the delegate. -*/ - -QQmlListProperty QQmlObjectModel::children() -{ - Q_D(QQmlObjectModel); - return QQmlListProperty(this, - d, - d->children_append, - d->children_count, - d->children_at, - d->children_clear); -} - -/*! - \qmlproperty int QtQml.Models::ObjectModel::count - - The number of items in the model. This property is readonly. -*/ -int QQmlObjectModel::count() const -{ - Q_D(const QQmlObjectModel); - return d->children.count(); -} - -bool QQmlObjectModel::isValid() const -{ - return true; -} - -QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode) -{ - Q_D(QQmlObjectModel); - QQmlObjectModelPrivate::Item &item = d->children[index]; - item.addRef(); - if (item.ref == 1) { - emit initItem(index, item.item); - emit createdItem(index, item.item); - } - return item.item; -} - -QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) -{ - Q_D(QQmlObjectModel); - int idx = d->indexOf(item); - if (idx >= 0) { - if (!d->children[idx].deref()) - return QQmlInstanceModel::Referenced; - } - return nullptr; -} - -QVariant QQmlObjectModel::variantValue(int index, const QString &role) -{ - Q_D(QQmlObjectModel); - if (index < 0 || index >= d->children.count()) - return QString(); - return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(role); -} - -QQmlIncubator::Status QQmlObjectModel::incubationStatus(int) -{ - return QQmlIncubator::Ready; -} - -int QQmlObjectModel::indexOf(QObject *item, QObject *) const -{ - Q_D(const QQmlObjectModel); - return d->indexOf(item); -} - -QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj) -{ - return QQmlObjectModelAttached::properties(obj); -} - -/*! - \qmlmethod object QtQml.Models::ObjectModel::get(int index) - \since 5.6 - - Returns the item at \a index in the model. This allows the item - to be accessed or modified from JavaScript: - - \code - Component.onCompleted: { - objectModel.append(objectComponent.createObject()) - console.log(objectModel.get(0).objectName); - objectModel.get(0).objectName = "first"; - } - \endcode - - The \a index must be an element in the list. - - \sa append() -*/ -QObject *QQmlObjectModel::get(int index) const -{ - Q_D(const QQmlObjectModel); - if (index < 0 || index >= d->children.count()) - return nullptr; - return d->children.at(index).item; -} - -/*! - \qmlmethod QtQml.Models::ObjectModel::append(object item) - \since 5.6 - - Appends a new item to the end of the model. - - \code - objectModel.append(objectComponent.createObject()) - \endcode - - \sa insert(), remove() -*/ -void QQmlObjectModel::append(QObject *object) -{ - Q_D(QQmlObjectModel); - d->insert(count(), object); -} - -/*! - \qmlmethod QtQml.Models::ObjectModel::insert(int index, object item) - \since 5.6 - - Inserts a new item to the model at position \a index. - - \code - objectModel.insert(2, objectComponent.createObject()) - \endcode - - The \a index must be to an existing item in the list, or one past - the end of the list (equivalent to append). - - \sa append(), remove() -*/ -void QQmlObjectModel::insert(int index, QObject *object) -{ - Q_D(QQmlObjectModel); - if (index < 0 || index > count()) { - qmlWarning(this) << tr("insert: index %1 out of range").arg(index); - return; - } - d->insert(index, object); -} - -/*! - \qmlmethod QtQml.Models::ObjectModel::move(int from, int to, int n = 1) - \since 5.6 - - Moves \a n items \a from one position \a to another. - - The from and to ranges must exist; for example, to move the first 3 items - to the end of the model: - - \code - objectModel.move(0, objectModel.count - 3, 3) - \endcode - - \sa append() -*/ -void QQmlObjectModel::move(int from, int to, int n) -{ - Q_D(QQmlObjectModel); - if (n <= 0 || from == to) - return; - if (from < 0 || to < 0 || from + n > count() || to + n > count()) { - qmlWarning(this) << tr("move: out of range"); - return; - } - d->move(from, to, n); -} - -/*! - \qmlmethod QtQml.Models::ObjectModel::remove(int index, int n = 1) - \since 5.6 - - Removes the items at \a index from the model. - - \sa clear() -*/ -void QQmlObjectModel::remove(int index, int n) -{ - Q_D(QQmlObjectModel); - if (index < 0 || n <= 0 || index + n > count()) { - qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+n).arg(count()); - return; - } - d->remove(index, n); -} - -/*! - \qmlmethod QtQml.Models::ObjectModel::clear() - \since 5.6 - - Clears all items from the model. - - \sa append(), remove() -*/ -void QQmlObjectModel::clear() -{ - Q_D(QQmlObjectModel); - d->clear(); -} - -QT_END_NAMESPACE - -#include "moc_qqmlobjectmodel_p.cpp" diff --git a/src/qml/types/qqmlobjectmodel_p.h b/src/qml/types/qqmlobjectmodel_p.h deleted file mode 100644 index 1284ba1780..0000000000 --- a/src/qml/types/qqmlobjectmodel_p.h +++ /dev/null @@ -1,194 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLINSTANCEMODEL_P_H -#define QQMLINSTANCEMODEL_P_H - -// -// 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. -// - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QObject; -class QQmlChangeSet; -class QAbstractItemModel; - -class Q_QML_PRIVATE_EXPORT QQmlInstanceModel : public QObject -{ - Q_OBJECT - - Q_PROPERTY(int count READ count NOTIFY countChanged) - -public: - virtual ~QQmlInstanceModel() {} - - enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; - Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) - - virtual int count() const = 0; - virtual bool isValid() const = 0; - virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0; - virtual ReleaseFlags release(QObject *object) = 0; - virtual void cancel(int) {} - QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); } - virtual QVariant variantValue(int, const QString &) = 0; - virtual void setWatchedRoles(const QList &roles) = 0; - virtual QQmlIncubator::Status incubationStatus(int index) = 0; - - virtual int indexOf(QObject *object, QObject *objectContext) const = 0; - virtual const QAbstractItemModel *abstractItemModel() const { return nullptr; } - -Q_SIGNALS: - void countChanged(); - void modelUpdated(const QQmlChangeSet &changeSet, bool reset); - void createdItem(int index, QObject *object); - void initItem(int index, QObject *object); - void destroyingItem(QObject *object); - -protected: - QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = nullptr) - : QObject(dd, parent) {} - -private: - Q_DISABLE_COPY(QQmlInstanceModel) -}; - -class QQmlObjectModelAttached; -class QQmlObjectModelPrivate; -class Q_QML_PRIVATE_EXPORT QQmlObjectModel : public QQmlInstanceModel -{ - Q_OBJECT - Q_DECLARE_PRIVATE(QQmlObjectModel) - - Q_PROPERTY(QQmlListProperty children READ children NOTIFY childrenChanged DESIGNABLE false) - Q_CLASSINFO("DefaultProperty", "children") - -public: - QQmlObjectModel(QObject *parent=nullptr); - ~QQmlObjectModel() {} - - int count() const override; - bool isValid() const override; - QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override; - QVariant variantValue(int index, const QString &role) override; - void setWatchedRoles(const QList &) override {} - QQmlIncubator::Status incubationStatus(int index) override; - - int indexOf(QObject *object, QObject *objectContext) const override; - - QQmlListProperty children(); - - static QQmlObjectModelAttached *qmlAttachedProperties(QObject *obj); - - Q_REVISION(3) Q_INVOKABLE QObject *get(int index) const; - Q_REVISION(3) Q_INVOKABLE void append(QObject *object); - Q_REVISION(3) Q_INVOKABLE void insert(int index, QObject *object); - Q_REVISION(3) Q_INVOKABLE void move(int from, int to, int n = 1); - Q_REVISION(3) Q_INVOKABLE void remove(int index, int n = 1); - -public Q_SLOTS: - Q_REVISION(3) void clear(); - -Q_SIGNALS: - void childrenChanged(); - -private: - Q_DISABLE_COPY(QQmlObjectModel) -}; - -class QQmlObjectModelAttached : public QObject -{ - Q_OBJECT - -public: - QQmlObjectModelAttached(QObject *parent) - : QObject(parent), m_index(-1) {} - ~QQmlObjectModelAttached() { - attachedProperties.remove(parent()); - } - - Q_PROPERTY(int index READ index NOTIFY indexChanged) - int index() const { return m_index; } - void setIndex(int idx) { - if (m_index != idx) { - m_index = idx; - Q_EMIT indexChanged(); - } - } - - static QQmlObjectModelAttached *properties(QObject *obj) { - QQmlObjectModelAttached *rv = attachedProperties.value(obj); - if (!rv) { - rv = new QQmlObjectModelAttached(obj); - attachedProperties.insert(obj, rv); - } - return rv; - } - -Q_SIGNALS: - void indexChanged(); - -public: - int m_index; - - static QHash attachedProperties; -}; - - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlInstanceModel) -QML_DECLARE_TYPE(QQmlObjectModel) -QML_DECLARE_TYPEINFO(QQmlObjectModel, QML_HAS_ATTACHED_PROPERTIES) - -#endif // QQMLINSTANCEMODEL_P_H diff --git a/src/qml/types/qqmltableinstancemodel.cpp b/src/qml/types/qqmltableinstancemodel.cpp deleted file mode 100644 index 2170e2daec..0000000000 --- a/src/qml/types/qqmltableinstancemodel.cpp +++ /dev/null @@ -1,547 +0,0 @@ -/**************************************************************************** -** -** 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 "qqmltableinstancemodel_p.h" -#include "qqmldelegatecomponent_p.h" - -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -const char* kModelItemTag = "_tableinstancemodel_modelItem"; - -bool QQmlTableInstanceModel::isDoneIncubating(QQmlDelegateModelItem *modelItem) -{ - if (!modelItem->incubationTask) - return true; - - const auto status = modelItem->incubationTask->status(); - return (status == QQmlIncubator::Ready) || (status == QQmlIncubator::Error); -} - -void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelItem) -{ - Q_ASSERT(modelItem); - - delete modelItem->object; - modelItem->object = nullptr; - - if (modelItem->contextData) { - modelItem->contextData->invalidate(); - Q_ASSERT(modelItem->contextData->refCount == 1); - modelItem->contextData = nullptr; - } - - modelItem->deleteLater(); -} - -QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent) - : QQmlInstanceModel(*(new QObjectPrivate()), parent) - , m_qmlContext(qmlContext) - , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList())) -{ -} - -void QQmlTableInstanceModel::useImportVersion(int minorVersion) -{ - m_adaptorModel.useImportVersion(minorVersion); -} - -QQmlTableInstanceModel::~QQmlTableInstanceModel() -{ - for (const auto modelItem : m_modelItems) { - // No item in m_modelItems should be referenced at this point. The view - // should release all its items before it deletes this model. Only model items - // that are still being incubated should be left for us to delete. - Q_ASSERT(modelItem->objectRef == 0); - Q_ASSERT(modelItem->incubationTask); - // Check that we are not being deleted while we're - // in the process of e.g emitting a created signal. - Q_ASSERT(modelItem->scriptRef == 0); - - if (modelItem->object) { - delete modelItem->object; - modelItem->object = nullptr; - modelItem->contextData->invalidate(); - modelItem->contextData = nullptr; - } - } - - deleteAllFinishedIncubationTasks(); - qDeleteAll(m_modelItems); - drainReusableItemsPool(0); -} - -QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index) -{ - if (m_delegateChooser) { - const int row = m_adaptorModel.rowAt(index); - const int column = m_adaptorModel.columnAt(index); - QQmlComponent *delegate = nullptr; - QQmlAbstractDelegateComponent *chooser = m_delegateChooser; - do { - delegate = chooser->delegate(&m_adaptorModel, row, column); - chooser = qobject_cast(delegate); - } while (chooser); - return delegate; - } - - return m_delegate; -} - -QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) -{ - // Check if an item for the given index is already loaded and ready - QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr); - if (modelItem) - return modelItem; - - QQmlComponent *delegate = resolveDelegate(index); - if (!delegate) - return nullptr; - - // Check if the pool contains an item that can be reused - modelItem = takeFromReusableItemsPool(delegate); - if (modelItem) { - reuseItem(modelItem, index); - m_modelItems.insert(index, modelItem); - return modelItem; - } - - // Create a new item from scratch - modelItem = m_adaptorModel.createItem(m_metaType, index); - if (modelItem) { - modelItem->delegate = delegate; - m_modelItems.insert(index, modelItem); - return modelItem; - } - - qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index; - return nullptr; -} - -QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode) -{ - Q_ASSERT(m_delegate); - Q_ASSERT(index >= 0 && index < m_adaptorModel.count()); - Q_ASSERT(m_qmlContext && m_qmlContext->isValid()); - - QQmlDelegateModelItem *modelItem = resolveModelItem(index); - if (!modelItem) - return nullptr; - - if (modelItem->object) { - // The model item has already been incubated. So - // just bump the ref-count and return it. - modelItem->referenceObject(); - return modelItem->object; - } - - // The object is not ready, and needs to be incubated - incubateModelItem(modelItem, incubationMode); - if (!isDoneIncubating(modelItem)) - return nullptr; - - // Incubation is done, so the task should be removed - Q_ASSERT(!modelItem->incubationTask); - - if (!modelItem->object) { - // The object was incubated synchronously (otherwise we would return above). But since - // we have no object, the incubation must have failed. And when we have no object, there - // should be no object references either. And there should also not be any internal script - // refs at this point. So we delete the model item. - Q_ASSERT(!modelItem->isObjectReferenced()); - Q_ASSERT(!modelItem->isReferenced()); - m_modelItems.remove(modelItem->index); - delete modelItem; - return nullptr; - } - - // Incubation was completed sync and successful - modelItem->referenceObject(); - return modelItem->object; -} - -QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable) -{ - Q_ASSERT(object); - auto modelItem = qvariant_cast(object->property(kModelItemTag)); - Q_ASSERT(modelItem); - - if (!modelItem->releaseObject()) - return QQmlDelegateModel::Referenced; - - if (modelItem->isReferenced()) { - // We still have an internal reference to this object, which means that we are told to release an - // object while the createdItem signal for it is still on the stack. This can happen when objects - // are e.g delivered async, and the user flicks back and forth quicker than the loading can catch - // up with. The view might then find that the object is no longer visible and should be released. - // We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers - // point of view, it should consider it destroyed. - return QQmlDelegateModel::Destroyed; - } - - // The item is not referenced by anyone - m_modelItems.remove(modelItem->index); - - if (reusable == Reusable) { - insertIntoReusableItemsPool(modelItem); - return QQmlInstanceModel::Referenced; - } - - // The item is not reused or referenced by anyone, so just delete it - modelItem->destroyObject(); - emit destroyingItem(object); - - delete modelItem; - return QQmlInstanceModel::Destroyed; -} - -void QQmlTableInstanceModel::cancel(int index) -{ - auto modelItem = m_modelItems.value(index); - Q_ASSERT(modelItem); - - // Since the view expects the item to be incubating, there should be - // an incubation task. And since the incubation is not done, no-one - // should yet have received, and therfore hold a reference to, the object. - Q_ASSERT(modelItem->incubationTask); - Q_ASSERT(!modelItem->isObjectReferenced()); - - m_modelItems.remove(index); - - if (modelItem->object) - delete modelItem->object; - - // modelItem->incubationTask will be deleted from the modelItems destructor - delete modelItem; -} - -void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem) -{ - // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release() - // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released - // item is no longer referenced, it will be added to the pool. Reusing of items can be specified - // per item, in case certain items cannot be recycled. - // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are - // about to create a new item, we first check if the pool contains an item based on the same - // delegate from before. If so, we take it out of the pool (instead of creating a new item), and - // update all its context-, and attached properties. - // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool() - // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only - // meant to rest there for a short while, ideally only from the time e.g a row is unloaded - // on one side of the view, and until a new row is loaded on the opposite side. In-between - // this time, the application will see the item as fully functional and 'alive' (just not - // visible on screen). Since this time is supposed to be short, we don't take any action to - // notify the application about it, since we don't want to trigger any bindings that can - // disturb performance. - // A recommended time for calling drainReusableItemsPool() is each time a view has finished - // loading e.g a new row or column. If there are more items in the pool after that, it means - // that the view most likely doesn't need them anytime soon. Those items should be destroyed to - // not consume resources. - // Depending on if a view is a list or a table, it can sometimes be performant to keep - // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the - // number of visible rows in a view is much larger than the number of visible columns. - // In that case, if you flick out a row, and then flick in a column, you would throw away a lot - // of items in the pool if completely draining it. The reason is that unloading a row places more - // items in the pool than what ends up being recycled when loading a new column. And then, when you - // next flick in a new row, you would need to load all those drained items again from scratch. For - // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep - // items in the pool for a bit longer, effectively keeping more items in circulation. - // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which - // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained. - Q_ASSERT(!modelItem->incubationTask); - Q_ASSERT(!modelItem->isObjectReferenced()); - Q_ASSERT(!modelItem->isReferenced()); - Q_ASSERT(modelItem->object); - - modelItem->poolTime = 0; - m_reusableItemsPool.append(modelItem); - emit itemPooled(modelItem->index, modelItem->object); -} - -QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate) -{ - // Find the oldest item in the pool that was made from the same delegate as - // the given argument, remove it from the pool, and return it. - if (m_reusableItemsPool.isEmpty()) - return nullptr; - - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { - if ((*it)->delegate != delegate) - continue; - auto modelItem = *it; - m_reusableItemsPool.erase(it); - return modelItem; - } - - return nullptr; -} - -void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime) -{ - // Rather than releasing all pooled items upon a call to this function, each - // item has a poolTime. The poolTime specifies for how many loading cycles an item - // has been resting in the pool. And for each invocation of this function, poolTime - // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed - // from the pool and released. This way, the view can tweak a bit for how long - // items should stay in "circulation", even if they are not recycled right away. - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) { - auto modelItem = *it; - modelItem->poolTime++; - if (modelItem->poolTime <= maxPoolTime) { - ++it; - } else { - it = m_reusableItemsPool.erase(it); - release(modelItem->object, NotReusable); - } - } -} - -void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex) -{ - // Update the context properties index, row and column on - // the delegate item, and inform the application about it. - const int newRow = m_adaptorModel.rowAt(newModelIndex); - const int newColumn = m_adaptorModel.columnAt(newModelIndex); - item->setModelIndex(newModelIndex, newRow, newColumn); - - // Notify the application that all 'dynamic'/role-based context data has - // changed as well (their getter function will use the updated index). - auto const itemAsList = QList() << item; - auto const updateAllRoles = QVector(); - m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles); - - // Inform the view that the item is recycled. This will typically result - // in the view updating its own attached delegate item properties. - emit itemReused(newModelIndex, item->object); -} - -void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode) -{ - // Guard the model item temporarily so that it's not deleted from - // incubatorStatusChanged(), in case the incubation is done synchronously. - modelItem->scriptRef++; - - if (modelItem->incubationTask) { - // We're already incubating the model item from a previous request. If the previous call requested - // the item async, but the current request needs it sync, we need to force-complete the incubation. - const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); - if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) - modelItem->incubationTask->forceCompletion(); - } else { - modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode); - - QQmlContextData *ctxt = new QQmlContextData; - QQmlContext *creationContext = modelItem->delegate->creationContext(); - ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data())); - ctxt->contextObject = modelItem; - modelItem->contextData = ctxt; - - QQmlComponentPrivate::get(modelItem->delegate)->incubateObject( - modelItem->incubationTask, - modelItem->delegate, - m_qmlContext->engine(), - ctxt, - QQmlContextData::get(m_qmlContext)); - } - - // Remove the temporary guard - modelItem->scriptRef--; -} - -void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status) -{ - QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate; - Q_ASSERT(modelItem->incubationTask); - - modelItem->incubationTask = nullptr; - incubationTask->modelItemToIncubate = nullptr; - - if (status == QQmlIncubator::Ready) { - // Tag the incubated object with the model item for easy retrieval upon release etc. - modelItem->object->setProperty(kModelItemTag, QVariant::fromValue(modelItem)); - - // Emit that the item has been created. What normally happens next is that the view - // upon receiving the signal asks for the model item once more. And since the item is - // now in the map, it will be returned directly. - Q_ASSERT(modelItem->object); - modelItem->scriptRef++; - emit createdItem(modelItem->index, modelItem->object); - modelItem->scriptRef--; - } else if (status == QQmlIncubator::Error) { - qWarning() << "Error incubating delegate:" << incubationTask->errors(); - } - - if (!modelItem->isReferenced() && !modelItem->isObjectReferenced()) { - // We have no internal reference to the model item, and the view has no - // reference to the incubated object. So just delete the model item. - // Note that being here means that the object was incubated _async_ - // (otherwise modelItem->isReferenced() would be true). - m_modelItems.remove(modelItem->index); - - if (modelItem->object) { - modelItem->scriptRef++; - emit destroyingItem(modelItem->object); - modelItem->scriptRef--; - Q_ASSERT(!modelItem->isReferenced()); - } - - deleteModelItemLater(modelItem); - } - - deleteIncubationTaskLater(incubationTask); -} - -QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) { - const auto modelItem = m_modelItems.value(index, nullptr); - if (!modelItem) - return QQmlIncubator::Null; - - if (modelItem->incubationTask) - return modelItem->incubationTask->status(); - - // Since we clear the incubation task when we're done - // incubating, it means that the status is Ready. - return QQmlIncubator::Ready; -} - -void QQmlTableInstanceModel::deleteIncubationTaskLater(QQmlIncubator *incubationTask) -{ - // We often need to post-delete incubation tasks, since we cannot - // delete them while we're in the middle of an incubation change callback. - Q_ASSERT(!m_finishedIncubationTasks.contains(incubationTask)); - m_finishedIncubationTasks.append(incubationTask); - if (m_finishedIncubationTasks.count() == 1) - QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks); -} - -void QQmlTableInstanceModel::deleteAllFinishedIncubationTasks() -{ - qDeleteAll(m_finishedIncubationTasks); - m_finishedIncubationTasks.clear(); -} - -QVariant QQmlTableInstanceModel::model() const -{ - return m_adaptorModel.model(); -} - -void QQmlTableInstanceModel::setModel(const QVariant &model) -{ - // Pooled items are still accessible/alive for the application, and - // needs to stay in sync with the model. So we need to drain the pool - // completely when the model changes. - drainReusableItemsPool(0); - if (auto const aim = abstractItemModel()) - disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback); - m_adaptorModel.setModel(model, this, m_qmlContext->engine()); - if (auto const aim = abstractItemModel()) - connect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback); -} - -void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector &roles) -{ - // This function is called when model data has changed. In that case, we tell the adaptor model - // to go through all the items we have created, find the ones that are affected, and notify that - // their model data has changed. This will in turn update QML bindings inside the delegate items. - int numberOfRowsChanged = end.row() - begin.row() + 1; - int numberOfColumnsChanged = end.column() - begin.column() + 1; - - for (int column = 0; column < numberOfColumnsChanged; ++column) { - const int columnIndex = begin.column() + column; - const int rowIndex = begin.row() + (columnIndex * rows()); - m_adaptorModel.notify(m_modelItems.values(), rowIndex, numberOfRowsChanged, roles); - } -} - -QQmlComponent *QQmlTableInstanceModel::delegate() const -{ - return m_delegate; -} - -void QQmlTableInstanceModel::setDelegate(QQmlComponent *delegate) -{ - if (m_delegate == delegate) - return; - - m_delegateChooser = nullptr; - if (delegate) { - QQmlAbstractDelegateComponent *adc = - qobject_cast(delegate); - if (adc) - m_delegateChooser = adc; - } - - m_delegate = delegate; -} - -const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const -{ - return m_adaptorModel.adaptsAim() ? m_adaptorModel.aim() : nullptr; -} - -// -------------------------------------------------------- - -void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object) -{ - modelItemToIncubate->object = object; - emit tableInstanceModel->initItem(modelItemToIncubate->index, object); -} - -void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status) -{ - if (!QQmlTableInstanceModel::isDoneIncubating(modelItemToIncubate)) - return; - - // We require the view to cancel any ongoing load - // requests before the tableInstanceModel is destructed. - Q_ASSERT(tableInstanceModel); - - tableInstanceModel->incubatorStatusChanged(this, status); -} - -#include "moc_qqmltableinstancemodel_p.cpp" - -QT_END_NAMESPACE - diff --git a/src/qml/types/qqmltableinstancemodel_p.h b/src/qml/types/qqmltableinstancemodel_p.h deleted file mode 100644 index 39ec66d136..0000000000 --- a/src/qml/types/qqmltableinstancemodel_p.h +++ /dev/null @@ -1,162 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QQMLTABLEINSTANCEMODEL_P_H -#define QQMLTABLEINSTANCEMODEL_P_H - -// -// 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. -// - -#include -#include - -QT_BEGIN_NAMESPACE - -class QQmlTableInstanceModel; -class QQmlAbstractDelegateComponent; - -class QQmlTableInstanceModelIncubationTask : public QQDMIncubationTask -{ -public: - QQmlTableInstanceModelIncubationTask( - QQmlTableInstanceModel *tableInstanceModel - , QQmlDelegateModelItem* modelItemToIncubate - , IncubationMode mode) - : QQDMIncubationTask(nullptr, mode) - , modelItemToIncubate(modelItemToIncubate) - , tableInstanceModel(tableInstanceModel) { - clear(); - } - - void statusChanged(Status status) override; - void setInitialState(QObject *object) override; - - QQmlDelegateModelItem *modelItemToIncubate = nullptr; - QQmlTableInstanceModel *tableInstanceModel = nullptr; -}; - -class Q_QML_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel -{ - Q_OBJECT - -public: - - enum ReusableFlag { - NotReusable, - Reusable - }; - - QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr); - ~QQmlTableInstanceModel() override; - - void useImportVersion(int minorVersion); - - int count() const override { return m_adaptorModel.count(); } - int rows() const { return m_adaptorModel.rowCount(); } - int columns() const { return m_adaptorModel.columnCount(); } - - bool isValid() const override { return true; } - - QVariant model() const; - void setModel(const QVariant &model); - - QQmlComponent *delegate() const; - void setDelegate(QQmlComponent *); - - const QAbstractItemModel *abstractItemModel() const override; - - QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; - ReleaseFlags release(QObject *object) override { return release(object, NotReusable); } - ReleaseFlags release(QObject *object, ReusableFlag reusable); - void cancel(int) override; - - void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem); - QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate); - void drainReusableItemsPool(int maxPoolTime); - int poolSize() { return m_reusableItemsPool.size(); } - void reuseItem(QQmlDelegateModelItem *item, int newModelIndex); - - QQmlIncubator::Status incubationStatus(int index) override; - - QVariant variantValue(int, const QString &) override { Q_UNREACHABLE(); return QVariant(); } - void setWatchedRoles(const QList &) override { Q_UNREACHABLE(); } - int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; } - -Q_SIGNALS: - void itemPooled(int index, QObject *object); - void itemReused(int index, QObject *object); - -private: - QQmlComponent *resolveDelegate(int index); - - QQmlAdaptorModel m_adaptorModel; - QQmlAbstractDelegateComponent *m_delegateChooser = nullptr; - QQmlComponent *m_delegate = nullptr; - QPointer m_qmlContext; - QQmlDelegateModelItemMetaType *m_metaType; - - QHash m_modelItems; - QList m_reusableItemsPool; - QList m_finishedIncubationTasks; - - void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode); - void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status); - void deleteIncubationTaskLater(QQmlIncubator *incubationTask); - void deleteAllFinishedIncubationTasks(); - QQmlDelegateModelItem *resolveModelItem(int index); - - void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector &roles); - - static bool isDoneIncubating(QQmlDelegateModelItem *modelItem); - static void deleteModelItemLater(QQmlDelegateModelItem *modelItem); - - friend class QQmlTableInstanceModelIncubationTask; -}; - -QT_END_NAMESPACE - -#endif // QQMLTABLEINSTANCEMODEL_P_H diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp deleted file mode 100644 index 4a96e7a46b..0000000000 --- a/src/qml/types/qqmltablemodel.cpp +++ /dev/null @@ -1,1059 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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 "qqmltablemodel_p.h" - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") - -/*! - \qmltype TableModel - \instantiates QQmlTableModel - \inqmlmodule Qt.labs.qmlmodels - \brief Encapsulates a simple table model. - \since 5.14 - - The TableModel type stores JavaScript/JSON objects as data for a table - model that can be used with \l TableView. It is intended to support - very simple models without requiring the creation of a custom - QAbstractTableModel subclass in C++. - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml file - - The model's initial row data is set with either the \l rows property or by - calling \l appendRow(). Each column in the model is specified by declaring - a \l TableModelColumn instance, where the order of each instance determines - its column index. Once the model's \l Component.completed() signal has been - emitted, the columns and roles will have been established and are then - fixed for the lifetime of the model. - - To access a specific row, the \l getRow() function can be used. - It's also possible to access the model's JavaScript data - directly via the \l rows property, but it is not possible to - modify the model data this way. - - To add new rows, use \l appendRow() and \l insertRow(). To modify - existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and - \l clear(). - - It is also possible to modify the model's data via the delegate, - as shown in the example above: - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate - - If the type of the data at the modified role does not match the type of the - data that is set, it will be automatically converted via - \l {QVariant::canConvert()}{QVariant}. - - \section1 Supported Row Data Structures - - TableModel is designed to work with JavaScript/JSON data, where each row - is a simple key-pair object: - - \code - { - // Each property is one cell/column. - checked: false, - amount: 1, - fruitType: "Apple", - fruitName: "Granny Smith", - fruitPrice: 1.50 - }, - // ... - \endcode - - As model manipulation in Qt is done via row and column indices, - and because object keys are unordered, each column must be specified via - TableModelColumn. This allows mapping Qt's built-in roles to any property - in each row object. - - Complex row structures are supported, but with limited functionality. - As TableModel has no way of knowing how each row is structured, - it cannot manipulate it. As a consequence of this, the copy of the - model data that TableModel has stored in \l rows is not kept in sync - with the source data that was set in QML. For these reasons, TableModel - relies on the user to handle simple data manipulation. - - For example, suppose you wanted to have several roles per column. One way - of doing this is to use a data source where each row is an array and each - cell is an object. To use this data source with TableModel, define a - getter and setter: - - \code - TableModel { - TableModelColumn { - display: function(modelIndex) { return rows[modelIndex.row][0].checked } - setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData } - } - // ... - - rows: [ - [ - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Apple" }, - { fruitName: "Granny Smith" }, - { fruitPrice: 1.50 } - ] - // ... - ] - } - \endcode - - The row above is one example of a complex row. - - \note Row manipulation functions such as \l appendRow(), \l removeRow(), - etc. are not supported when using complex rows. - - \section1 Using DelegateChooser with TableModel - - For most real world use cases, it is recommended to use DelegateChooser - as the delegate of a TableView that uses TableModel. This allows you to - use specific roles in the relevant delegates. For example, the snippet - above can be rewritten to use DelegateChooser like so: - - \snippet qml/tablemodel/fruit-example-delegatechooser.qml file - - The most specific delegates are declared first: the columns at index \c 0 - and \c 1 have \c bool and \c integer data types, so they use a - \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, - respectively. The remaining columns can simply use a - \l [QtQuickControls2]{TextField}, and so that delegate is declared - last as a fallback. - - \sa TableModelColumn, TableView, QAbstractTableModel -*/ - -QQmlTableModel::QQmlTableModel(QObject *parent) - : QAbstractTableModel(parent) -{ -} - -QQmlTableModel::~QQmlTableModel() -{ -} - -/*! - \qmlproperty object TableModel::rows - - This property holds the model data in the form of an array of rows: - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows - - \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount -*/ -QVariant QQmlTableModel::rows() const -{ - return mRows; -} - -void QQmlTableModel::setRows(const QVariant &rows) -{ - if (rows.userType() != qMetaTypeId()) { - qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); - return; - } - - const QJSValue rowsAsJSValue = rows.value(); - const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); - if (rowsAsVariantList == mRows) { - // No change. - return; - } - - if (!componentCompleted) { - // Store the rows until we can call doSetRows() after component completion. - mRows = rowsAsVariantList; - return; - } - - doSetRows(rowsAsVariantList); -} - -void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList) -{ - Q_ASSERT(componentCompleted); - - // By now, all TableModelColumns should have been set. - if (mColumns.isEmpty()) { - qmlWarning(this) << "No TableModelColumns were set; model will be empty"; - return; - } - - const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); - if (!firstTimeValidRowsHaveBeenSet) { - // This is not the first time rows have been set; validate each one. - for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { - // validateNewRow() expects a QVariant wrapping a QJSValue, so to - // simplify the code, just create one here. - const QVariant row = QVariant::fromValue(rowsAsVariantList.at(rowIndex)); - if (!validateNewRow("setRows()", row, rowIndex, SetRowsOperation)) - return; - } - } - - const int oldRowCount = mRowCount; - const int oldColumnCount = mColumnCount; - - beginResetModel(); - - // We don't clear the column or role data, because a TableModel should not be reused in that way. - // Once it has valid data, its columns and roles are fixed. - mRows = rowsAsVariantList; - mRowCount = mRows.size(); - - // Gather metadata the first time rows is set. - if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) - fetchColumnMetadata(); - - endResetModel(); - - emit rowsChanged(); - - if (mRowCount != oldRowCount) - emit rowCountChanged(); - if (mColumnCount != oldColumnCount) - emit columnCountChanged(); -} - -QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey, - QQmlTableModelColumn *tableModelColumn, int columnIndex) const -{ - const QVariant firstRow = mRows.first(); - ColumnRoleMetadata roleData; - - QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey); - if (columnRoleGetter.isUndefined()) { - // This role is not defined, which is fine; just skip it. - return roleData; - } - - if (columnRoleGetter.isString()) { - // The role is set as a string, so we assume the row is a simple object. - if (firstRow.type() != QVariant::Map) { - qmlWarning(this).quote() << "expected row for role " - << roleNameKey << " of TableModelColumn at index " - << columnIndex << " to be a simple object, but it's " - << firstRow.typeName() << " instead: " << firstRow; - return roleData; - } - const QVariantMap firstRowAsMap = firstRow.toMap(); - const QString rolePropertyName = columnRoleGetter.toString(); - const QVariant roleProperty = firstRowAsMap.value(rolePropertyName); - - roleData.isStringRole = true; - roleData.name = rolePropertyName; - roleData.type = roleProperty.type(); - roleData.typeName = QString::fromLatin1(roleProperty.typeName()); - } else if (columnRoleGetter.isCallable()) { - // The role is provided via a function, which means the row is complex and - // the user needs to provide the data for it. - const auto modelIndex = index(0, columnIndex); - const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(modelIndex); - const QVariant cellData = columnRoleGetter.call(args).toVariant(); - - // We don't know the property name since it's provided through the function. - // roleData.name = ??? - roleData.isStringRole = false; - roleData.type = cellData.type(); - roleData.typeName = QString::fromLatin1(cellData.typeName()); - } else { - // Invalid role. - qmlWarning(this) << "TableModelColumn role for column at index " - << columnIndex << " must be either a string or a function; actual type is: " - << columnRoleGetter.toString(); - } - - return roleData; -} - -void QQmlTableModel::fetchColumnMetadata() -{ - qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:"; - - static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames(); - - // Since we support different data structures at the row level, we require that there - // is a TableModelColumn for each column. - // Collect and cache metadata for each column. This makes data lookup faster. - for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { - QQmlTableModelColumn *column = mColumns.at(columnIndex); - qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":"; - - ColumnMetadata metaData; - const auto builtInRoleKeys = supportedRoleNames.keys(); - for (const int builtInRoleKey : builtInRoleKeys) { - const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey); - ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex); - if (roleData.type == QVariant::Invalid) { - // This built-in role was not specified in this column. - continue; - } - - qCDebug(lcTableModel).nospace() << " - added metadata for built-in role " - << builtInRoleName << " at column index " << columnIndex - << ": name=" << roleData.name << " typeName=" << roleData.typeName - << " type=" << roleData.type; - - // This column now supports this specific built-in role. - metaData.roles.insert(builtInRoleName, roleData); - // Add it if it doesn't already exist. - mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1(); - } - mColumnMetadata.insert(columnIndex, metaData); - } -} - -/*! - \qmlmethod TableModel::appendRow(object row) - - Adds a new row to the end of the model, with the - values (cells) in \a row. - - \code - model.appendRow({ - checkable: true, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - \sa insertRow(), setRow(), removeRow() -*/ -void QQmlTableModel::appendRow(const QVariant &row) -{ - if (!validateNewRow("appendRow()", row, -1, AppendOperation)) - return; - - doInsert(mRowCount, row); -} - -/*! - \qmlmethod TableModel::clear() - - Removes all rows from the model. - - \sa removeRow() -*/ -void QQmlTableModel::clear() -{ - QQmlEngine *engine = qmlEngine(this); - Q_ASSERT(engine); - setRows(QVariant::fromValue(engine->newArray())); -} - -/*! - \qmlmethod object TableModel::getRow(int rowIndex) - - Returns the row at \a rowIndex in the model. - - Note that this equivalent to accessing the row directly - through the \l rows property: - - \code - Component.onCompleted: { - // These two lines are equivalent. - console.log(model.getRow(0).display); - console.log(model.rows[0].fruitName); - } - \endcode - - \note the returned object cannot be used to modify the contents of the - model; use setRow() instead. - - \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() -*/ -QVariant QQmlTableModel::getRow(int rowIndex) -{ - if (!validateRowIndex("getRow()", "rowIndex", rowIndex)) - return QVariant(); - - return mRows.at(rowIndex); -} - -/*! - \qmlmethod TableModel::insertRow(int rowIndex, object row) - - Adds a new row to the list model at position \a rowIndex, with the - values (cells) in \a row. - - \code - model.insertRow(2, { - checkable: true, checked: false, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - The \a rowIndex must be to an existing item in the list, or one past - the end of the list (equivalent to \l appendRow()). - - \sa appendRow(), setRow(), removeRow(), rowCount -*/ -void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) -{ - if (!validateNewRow("insertRow()", row, rowIndex)) - return; - - doInsert(rowIndex, row); -} - -void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) -{ - beginInsertRows(QModelIndex(), rowIndex, rowIndex); - - // Adding rowAsVariant.toList() will add each invidual variant in the list, - // which is definitely not what we want. - const QVariant rowAsVariant = row.value().toVariant(); - mRows.insert(rowIndex, rowAsVariant); - ++mRowCount; - - qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index " - << rowIndex << ":\n" << rowAsVariant.toMap(); - - // Gather metadata the first time a row is added. - if (mColumnMetadata.isEmpty()) - fetchColumnMetadata(); - - endInsertRows(); - emit rowCountChanged(); -} - -void QQmlTableModel::classBegin() -{ -} - -void QQmlTableModel::componentComplete() -{ - componentCompleted = true; - - mColumnCount = mColumns.size(); - if (mColumnCount > 0) - emit columnCountChanged(); - - doSetRows(mRows); -} - -/*! - \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) - - Moves \a rows from the index at \a fromRowIndex to the index at - \a toRowIndex. - - The from and to ranges must exist; for example, to move the first 3 items - to the end of the list: - - \code - model.moveRow(0, model.rowCount - 3, 3) - \endcode - - \sa appendRow(), insertRow(), removeRow(), rowCount -*/ -void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) -{ - if (fromRowIndex == toRowIndex) { - qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\""; - return; - } - - if (rows <= 0) { - qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0"; - return; - } - - if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex)) - return; - - if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex)) - return; - - if (fromRowIndex + rows > mRowCount) { - qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex - << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows) - << ", which is greater than rowCount() of " << mRowCount; - return; - } - - if (toRowIndex + rows > mRowCount) { - qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex - << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows) - << ", which is greater than rowCount() of " << mRowCount; - return; - } - - qCDebug(lcTableModel).nospace() << "moving " << rows - << " row(s) from index " << fromRowIndex - << " to index " << toRowIndex; - - // Based on the same call in QQmlListModel::moveRow(). - beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(), - toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); - - // Based on ListModel::moveRow(). - if (fromRowIndex > toRowIndex) { - // Only move forwards - flip if moving backwards. - const int from = fromRowIndex; - const int to = toRowIndex; - fromRowIndex = to; - toRowIndex = to + rows; - rows = from - to; - } - - QVector store; - store.reserve(rows); - for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) - store.append(mRows.at(fromRowIndex + rows + i)); - for (int i = 0; i < rows; ++i) - store.append(mRows.at(fromRowIndex + i)); - for (int i = 0; i < store.size(); ++i) - mRows[fromRowIndex + i] = store[i]; - - qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows; - - endMoveRows(); -} - -/*! - \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) - - Removes the row at \a rowIndex from the model. - - \sa clear(), rowCount -*/ -void QQmlTableModel::removeRow(int rowIndex, int rows) -{ - if (!validateRowIndex("removeRow()", "rowIndex", rowIndex)) - return; - - if (rows <= 0) { - qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero"; - return; - } - - if (rowIndex + rows - 1 >= mRowCount) { - qmlWarning(this) << "removeRow(): \"rows\" " << rows - << " exceeds available rowCount() of " << mRowCount - << " when removing from \"rowIndex\" " << rowIndex; - return; - } - - beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1); - - auto firstIterator = mRows.begin() + rowIndex; - // The "last" argument to erase() is exclusive, so we go one past the last item. - auto lastIterator = firstIterator + rows; - mRows.erase(firstIterator, lastIterator); - mRowCount -= rows; - - endRemoveRows(); - emit rowCountChanged(); - - qCDebug(lcTableModel).nospace() << "removed " << rows - << " items from the model, starting at index " << rowIndex; -} - -/*! - \qmlmethod TableModel::setRow(int rowIndex, object row) - - Changes the row at \a rowIndex in the model with \a row. - - All columns/cells must be present in \c row, and in the correct order. - - \code - model.setRow(0, { - checkable: true, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - If \a rowIndex is equal to \c rowCount(), then a new row is appended to the - model. Otherwise, \a rowIndex must point to an existing row in the model. - - \sa appendRow(), insertRow(), rowCount -*/ -void QQmlTableModel::setRow(int rowIndex, const QVariant &row) -{ - if (!validateNewRow("setRow()", row, rowIndex)) - return; - - if (rowIndex != mRowCount) { - // Setting an existing row. - mRows[rowIndex] = row; - - // For now we just assume the whole row changed, as it's simpler. - const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0)); - const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1)); - emit dataChanged(topLeftModelIndex, bottomRightModelIndex); - } else { - // Appending a row. - doInsert(rowIndex, row); - } -} - -QQmlListProperty QQmlTableModel::columns() -{ - return QQmlListProperty(this, nullptr, - &QQmlTableModel::columns_append, - &QQmlTableModel::columns_count, - &QQmlTableModel::columns_at, - &QQmlTableModel::columns_clear); -} - -void QQmlTableModel::columns_append(QQmlListProperty *property, - QQmlTableModelColumn *value) -{ - QQmlTableModel *model = static_cast(property->object); - QQmlTableModelColumn *column = qobject_cast(value); - if (column) - model->mColumns.append(column); -} - -int QQmlTableModel::columns_count(QQmlListProperty *property) -{ - const QQmlTableModel *model = static_cast(property->object); - return model->mColumns.count(); -} - -QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty *property, int index) -{ - const QQmlTableModel *model = static_cast(property->object); - return model->mColumns.at(index); -} - -void QQmlTableModel::columns_clear(QQmlListProperty *property) -{ - QQmlTableModel *model = static_cast(property->object); - return model->mColumns.clear(); -} - -/*! - \qmlmethod QModelIndex TableModel::index(int row, int column) - - Returns a \l QModelIndex object referencing the given \a row and \a column, - which can be passed to the data() function to get the data from that cell, - or to setData() to edit the contents of that cell. - - \code - import QtQml 2.14 - import Qt.labs.qmlmodels 1.0 - - TableModel { - id: model - - TableModelColumn { display: "fruitType" } - TableModelColumn { display: "fruitPrice" } - - rows: [ - { fruitType: "Apple", fruitPrice: 1.50 }, - { fruitType: "Orange", fruitPrice: 2.50 } - ] - - Component.onCompleted: { - for (var r = 0; r < model.rowCount; ++r) { - console.log("An " + model.data(model.index(r, 0)).display + - " costs " + model.data(model.index(r, 1)).display.toFixed(2)) - } - } - } - \endcode - - \sa {QModelIndex and related Classes in QML}, data() -*/ -// Note: we don't document the parent argument, because you never need it, because -// cells in a TableModel don't have parents. But it is there because this function is an override. -QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const -{ - return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() - ? createIndex(row, column) - : QModelIndex(); -} - -/*! - \qmlproperty int TableModel::rowCount - \readonly - - This read-only property holds the number of rows in the model. - - This value changes whenever rows are added or removed from the model. -*/ -int QQmlTableModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return mRowCount; -} - -/*! - \qmlproperty int TableModel::columnCount - \readonly - - This read-only property holds the number of columns in the model. - - The number of columns is fixed for the lifetime of the model - after the \l rows property is set or \l appendRow() is called for the first - time. -*/ -int QQmlTableModel::columnCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return mColumnCount; -} - -/*! - \qmlmethod variant TableModel::data(QModelIndex index, string role) - - Returns the data from the table cell at the given \a index belonging to the - given \a role. - - \sa index() -*/ -QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const -{ - const int iRole = mRoleNames.key(role.toUtf8(), -1); - if (iRole >= 0) - return data(index, iRole); - return QVariant(); -} - -QVariant QQmlTableModel::data(const QModelIndex &index, int role) const -{ - const int row = index.row(); - if (row < 0 || row >= rowCount()) - return QVariant(); - - const int column = index.column(); - if (column < 0 || column >= columnCount()) - return QVariant(); - - const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); - const QString roleName = QString::fromUtf8(mRoleNames.value(role)); - if (!columnMetadata.roles.contains(roleName)) { - qmlWarning(this) << "setData(): no role named " << roleName - << " at column index " << column << ". The available roles for that column are: " - << columnMetadata.roles.keys(); - return QVariant(); - } - - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - if (roleData.isStringRole) { - // We know the data structure, so we can get the data for the user. - const QVariantMap rowData = mRows.at(row).toMap(); - const QString propertyName = columnMetadata.roles.value(roleName).name; - const QVariant value = rowData.value(propertyName); - return value; - } - - // We don't know the data structure, so the user has to modify their data themselves. - // First, find the getter for this column and role. - QJSValue getter = mColumns.at(column)->getterAtRole(roleName); - - // Then, call it and return what it returned. - const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(index); - return getter.call(args).toVariant(); -} - -/*! - \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value) - - Inserts or updates the data field named by \a role in the table cell at the - given \a index with \a value. Returns true if sucessful, false if not. - - \sa index() -*/ -bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) -{ - const int intRole = mRoleNames.key(role.toUtf8(), -1); - if (intRole >= 0) - return setData(index, value, intRole); - return false; -} - -bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - const int row = index.row(); - if (row < 0 || row >= rowCount()) - return false; - - const int column = index.column(); - if (column < 0 || column >= columnCount()) - return false; - - const QString roleName = QString::fromUtf8(mRoleNames.value(role)); - - qCDebug(lcTableModel).nospace() << "setData() called with index " - << index << ", value " << value << " and role " << roleName; - - // Verify that the role exists for this column. - const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); - if (!columnMetadata.roles.contains(roleName)) { - qmlWarning(this) << "setData(): no role named \"" << roleName - << "\" at column index " << column << ". The available roles for that column are: " - << columnMetadata.roles.keys(); - return false; - } - - // Verify that the type of the value is what we expect. - // If the value set is not of the expected type, we can try to convert it automatically. - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - QVariant effectiveValue = value; - if (value.type() != roleData.type) { - if (!value.canConvert(int(roleData.type))) { - qmlWarning(this).nospace() << "setData(): the value " << value - << " set at row " << row << " column " << column << " with role " << roleName - << " cannot be converted to " << roleData.typeName; - return false; - } - - if (!effectiveValue.convert(int(roleData.type))) { - qmlWarning(this).nospace() << "setData(): failed converting value " << value - << " set at row " << row << " column " << column << " with role " << roleName - << " to " << roleData.typeName; - return false; - } - } - - if (roleData.isStringRole) { - // We know the data structure, so we can set it for the user. - QVariantMap modifiedRow = mRows.at(row).toMap(); - modifiedRow[roleData.name] = value; - - mRows[row] = modifiedRow; - } else { - // We don't know the data structure, so the user has to modify their data themselves. - auto engine = qmlEngine(this); - auto args = QJSValueList() - // arg 0: modelIndex. - << engine->toScriptValue(index) - // arg 1: cellData. - << engine->toScriptValue(value); - // Do the actual setting. - QJSValue setter = mColumns.at(column)->setterAtRole(roleName); - setter.call(args); - - /* - The chain of events so far: - - - User did e.g.: model.edit = textInput.text - - setData() is called - - setData() calls the setter - (remember that we need to emit the dataChanged() signal, - which is why the user can't just set the data directly in the delegate) - - Now the user's setter function has modified *their* copy of the - data, but *our* copy of the data is old. Imagine the getters and setters looked like this: - - display: function(modelIndex) { return rows[modelIndex.row][1].amount } - setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } - - We don't know the structure of the user's data, so we can't just do - what we do above for the isStringRole case: - - modifiedRow[column][roleName] = value - - This means that, besides getting the implicit row count when rows is initially set, - our copy of the data is unused when it comes to complex columns. - - Another point to note is that we can't pass rowData in to the getter as a convenience, - because we would be passing in *our* copy of the row, which is not up-to-date. - Since the user already has access to the data, it's not a big deal for them to do: - - display: function(modelIndex) { return rows[modelIndex.row][1].amount } - - instead of: - - display: function(modelIndex, rowData) { return rowData[1].amount } - */ - } - - QVector rolesChanged; - rolesChanged.append(role); - emit dataChanged(index, index, rolesChanged); - - return true; -} - -QHash QQmlTableModel::roleNames() const -{ - return mRoleNames; -} - -QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() -{ -} - -QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( - bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) : - isStringRole(isStringRole), - name(name), - type(type), - typeName(typeName) -{ -} - -bool QQmlTableModel::ColumnRoleMetadata::isValid() const -{ - return !name.isEmpty(); -} - -bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const -{ - if (!row.canConvert()) { - qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," - << " but got " << row.typeName() << " instead:\n" << row; - return false; - } - - const QJSValue rowAsJSValue = row.value(); - if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { - qmlWarning(this) << functionName << ": expected \"row\" argument " - << "to be an object or array, but got:\n" << rowAsJSValue.toString(); - return false; - } - - return true; -} - -bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, - int rowIndex, NewRowOperationFlag operation) const -{ - if (mColumnMetadata.isEmpty()) { - // There is no column metadata, so we have nothing to validate the row against. - // Rows have to be added before we can gather metadata from them, so just this - // once we'll return true to allow the rows to be added. - return true; - } - - // Don't require each row to be a QJSValue when setting all rows, - // as they won't be; they'll be QVariantMap. - if (operation != SetRowsOperation && !validateRowType(functionName, row)) - return false; - - if (operation == OtherOperation) { - // Inserting/setting. - if (rowIndex < 0) { - qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative"; - return false; - } - - if (rowIndex > mRowCount) { - qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex - << " is greater than rowCount() of " << mRowCount; - return false; - } - } - - const QVariant rowAsVariant = operation == SetRowsOperation - ? row : row.value().toVariant(); - if (rowAsVariant.type() != QVariant::Map) { - qmlWarning(this) << functionName << ": row manipulation functions " - << "do not support complex rows (row index: " << rowIndex << ")"; - return false; - } - - const QVariantMap rowAsMap = rowAsVariant.toMap(); - const int columnCount = rowAsMap.size(); - if (columnCount < mColumnCount) { - qmlWarning(this) << functionName << ": expected " << mColumnCount - << " columns, but only got " << columnCount; - return false; - } - - // We can't validate complex structures, but we can make sure that - // each simple string-based role in each column is correct. - for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { - QQmlTableModelColumn *column = mColumns.at(columnIndex); - const QHash getters = column->getters(); - const auto roleNames = getters.keys(); - const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex); - for (const QString &roleName : roleNames) { - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - if (!roleData.isStringRole) - continue; - - if (!rowAsMap.contains(roleData.name)) { - qmlWarning(this).quote() << functionName << ": expected a property named " - << roleData.name << " in row at index " << rowIndex << ", but couldn't find one"; - return false; - } - - const QVariant rolePropertyValue = rowAsMap.value(roleData.name); - if (rolePropertyValue.type() != roleData.type) { - qmlWarning(this).quote() << functionName << ": expected the property named " - << roleData.name << " to be of type " << roleData.typeName - << ", but got " << QString::fromLatin1(rolePropertyValue.typeName()) << " instead"; - return false; - } - } - } - - return true; -} - -bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const -{ - if (rowIndex < 0) { - qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative"; - return false; - } - - if (rowIndex >= mRowCount) { - qmlWarning(this) << functionName << ": \"" << argumentName - << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount; - return false; - } - - return true; -} - -QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodel_p.h b/src/qml/types/qqmltablemodel_p.h deleted file mode 100644 index a1bb97e7d4..0000000000 --- a/src/qml/types/qqmltablemodel_p.h +++ /dev/null @@ -1,170 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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$ -** -****************************************************************************/ - -#ifndef QQMLTABLEMODEL_P_H -#define QQMLTABLEMODEL_P_H - -// -// 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. -// - -#include -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel, public QQmlParserStatus -{ - Q_OBJECT - Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL) - Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL) - Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL) - Q_PROPERTY(QQmlListProperty columns READ columns CONSTANT FINAL) - Q_INTERFACES(QQmlParserStatus) - Q_CLASSINFO("DefaultProperty", "columns") - -public: - QQmlTableModel(QObject *parent = nullptr); - ~QQmlTableModel() override; - - QVariant rows() const; - void setRows(const QVariant &rows); - - Q_INVOKABLE void appendRow(const QVariant &row); - Q_INVOKABLE void clear(); - Q_INVOKABLE QVariant getRow(int rowIndex); - Q_INVOKABLE void insertRow(int rowIndex, const QVariant &row); - Q_INVOKABLE void moveRow(int fromRowIndex, int toRowIndex, int rows = 1); - Q_INVOKABLE void removeRow(int rowIndex, int rows = 1); - Q_INVOKABLE void setRow(int rowIndex, const QVariant &row); - - QQmlListProperty columns(); - - static void columns_append(QQmlListProperty *property, QQmlTableModelColumn *value); - static int columns_count(QQmlListProperty *property); - static QQmlTableModelColumn *columns_at(QQmlListProperty *property, int index); - static void columns_clear(QQmlListProperty *property); - - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - Q_INVOKABLE QVariant data(const QModelIndex &index, const QString &role) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE bool setData(const QModelIndex &index, const QString &role, const QVariant &value); - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; - QHash roleNames() const override; - -Q_SIGNALS: - void columnCountChanged(); - void rowCountChanged(); - void rowsChanged(); - -private: - class ColumnRoleMetadata - { - public: - ColumnRoleMetadata(); - ColumnRoleMetadata(bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName); - - bool isValid() const; - - // If this is false, it's a function role. - bool isStringRole = false; - QString name; - QVariant::Type type = QVariant::Invalid; - QString typeName; - }; - - struct ColumnMetadata - { - // Key = role name that will be made visible to the delegate - // Value = metadata about that role, including actual name in the model data, type, etc. - QHash roles; - }; - - enum NewRowOperationFlag { - OtherOperation, // insert(), set(), etc. - SetRowsOperation, - AppendOperation - }; - - void doSetRows(const QVariantList &rowsAsVariantList); - ColumnRoleMetadata fetchColumnRoleData(const QString &roleNameKey, - QQmlTableModelColumn *tableModelColumn, int columnIndex) const; - void fetchColumnMetadata(); - - bool validateRowType(const char *functionName, const QVariant &row) const; - bool validateNewRow(const char *functionName, const QVariant &row, - int rowIndex, NewRowOperationFlag operation = OtherOperation) const; - bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const; - - void doInsert(int rowIndex, const QVariant &row); - - void classBegin() override; - void componentComplete() override; - - bool componentCompleted = false; - QVariantList mRows; - QList mColumns; - int mRowCount = 0; - int mColumnCount = 0; - // Each entry contains information about the properties of the column at that index. - QVector mColumnMetadata; - // key = property index (0 to number of properties across all columns) - // value = role name - QHash mRoleNames; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlTableModel) - -#endif // QQMLTABLEMODEL_P_H diff --git a/src/qml/types/qqmltablemodelcolumn.cpp b/src/qml/types/qqmltablemodelcolumn.cpp deleted file mode 100644 index 93da0642de..0000000000 --- a/src/qml/types/qqmltablemodelcolumn.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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 "qqmltablemodelcolumn_p.h" - -#include - -QT_BEGIN_NAMESPACE - -/*! - \qmltype TableModelColumn - \instantiates QQmlTableModelColumn - \inqmlmodule Qt.labs.qmlmodels - \brief Represents a column in a model. - \since 5.14 - - \section1 Supported Roles - - TableModelColumn supports all of \l {Qt::ItemDataRole}{Qt's roles}, - with the exception of \c Qt::InitialSortOrderRole. - - \sa TableModel, TableView -*/ - -static const QString displayRoleName = QStringLiteral("display"); -static const QString decorationRoleName = QStringLiteral("decoration"); -static const QString editRoleName = QStringLiteral("edit"); -static const QString toolTipRoleName = QStringLiteral("toolTip"); -static const QString statusTipRoleName = QStringLiteral("statusTip"); -static const QString whatsThisRoleName = QStringLiteral("whatsThis"); - -static const QString fontRoleName = QStringLiteral("font"); -static const QString textAlignmentRoleName = QStringLiteral("textAlignment"); -static const QString backgroundRoleName = QStringLiteral("background"); -static const QString foregroundRoleName = QStringLiteral("foreground"); -static const QString checkStateRoleName = QStringLiteral("checkState"); - -static const QString accessibleTextRoleName = QStringLiteral("accessibleText"); -static const QString accessibleDescriptionRoleName = QStringLiteral("accessibleDescription"); - -static const QString sizeHintRoleName = QStringLiteral("sizeHint"); - - -QQmlTableModelColumn::QQmlTableModelColumn(QObject *parent) - : QObject(parent) -{ -} - -QQmlTableModelColumn::~QQmlTableModelColumn() -{ -} - -#define DEFINE_ROLE_PROPERTIES(getterGetterName, getterSetterName, getterSignal, setterGetterName, setterSetterName, setterSignal, roleName) \ -QJSValue QQmlTableModelColumn::getterGetterName() const \ -{ \ - return mGetters.value(roleName); \ -} \ -\ -void QQmlTableModelColumn::getterSetterName(const QJSValue &stringOrFunction) \ -{ \ - if (!stringOrFunction.isString() && !stringOrFunction.isCallable()) { \ - qmlWarning(this).quote() << "getter for " << roleName << " must be a function"; \ - return; \ - } \ - if (stringOrFunction.strictlyEquals(decoration())) \ - return; \ -\ - mGetters[roleName] = stringOrFunction; \ - emit decorationChanged(); \ -} \ -\ -QJSValue QQmlTableModelColumn::setterGetterName() const \ -{ \ - return mSetters.value(roleName); \ -} \ -\ -void QQmlTableModelColumn::setterSetterName(const QJSValue &function) \ -{ \ - if (!function.isCallable()) { \ - qmlWarning(this).quote() << "setter for " << roleName << " must be a function"; \ - return; \ - } \ -\ - if (function.strictlyEquals(getSetDisplay())) \ - return; \ -\ - mSetters[roleName] = function; \ - emit setDisplayChanged(); \ -} - -DEFINE_ROLE_PROPERTIES(display, setDisplay, displayChanged, - getSetDisplay, setSetDisplay, setDisplayChanged, displayRoleName) -DEFINE_ROLE_PROPERTIES(decoration, setDecoration, decorationChanged, - getSetDecoration, setSetDecoration, setDecorationChanged, decorationRoleName) -DEFINE_ROLE_PROPERTIES(edit, setEdit, editChanged, - getSetEdit, setSetEdit, setEditChanged, editRoleName) -DEFINE_ROLE_PROPERTIES(toolTip, setToolTip, toolTipChanged, - getSetToolTip, setSetToolTip, setToolTipChanged, toolTipRoleName) -DEFINE_ROLE_PROPERTIES(statusTip, setStatusTip, statusTipChanged, - getSetStatusTip, setSetStatusTip, setStatusTipChanged, statusTipRoleName) -DEFINE_ROLE_PROPERTIES(whatsThis, setWhatsThis, whatsThisChanged, - getSetWhatsThis, setSetWhatsThis, setWhatsThisChanged, whatsThisRoleName) - -DEFINE_ROLE_PROPERTIES(font, setFont, fontChanged, - getSetFont, setSetFont, setFontChanged, fontRoleName) -DEFINE_ROLE_PROPERTIES(textAlignment, setTextAlignment, textAlignmentChanged, - getSetTextAlignment, setSetTextAlignment, setTextAlignmentChanged, textAlignmentRoleName) -DEFINE_ROLE_PROPERTIES(background, setBackground, backgroundChanged, - getSetBackground, setSetBackground, setBackgroundChanged, backgroundRoleName) -DEFINE_ROLE_PROPERTIES(foreground, setForeground, foregroundChanged, - getSetForeground, setSetForeground, setForegroundChanged, foregroundRoleName) -DEFINE_ROLE_PROPERTIES(checkState, setCheckState, checkStateChanged, - getSetCheckState, setSetCheckState, setCheckStateChanged, checkStateRoleName) - -DEFINE_ROLE_PROPERTIES(accessibleText, setAccessibleText, accessibleTextChanged, - getSetAccessibleText, setSetAccessibleText, setAccessibleTextChanged, accessibleTextRoleName) -DEFINE_ROLE_PROPERTIES(accessibleDescription, setAccessibleDescription, accessibleDescriptionChanged, - getSetAccessibleDescription, setSetAccessibleDescription, setAccessibleDescriptionChanged, accessibleDescriptionRoleName) - -DEFINE_ROLE_PROPERTIES(sizeHint, setSizeHint, sizeHintChanged, - getSetSizeHint, setSetSizeHint, setSizeHintChanged, sizeHintRoleName) - -QJSValue QQmlTableModelColumn::getterAtRole(const QString &roleName) -{ - auto it = mGetters.find(roleName); - if (it == mGetters.end()) - return QJSValue(); - return *it; -} - -QJSValue QQmlTableModelColumn::setterAtRole(const QString &roleName) -{ - auto it = mSetters.find(roleName); - if (it == mSetters.end()) - return QJSValue(); - return *it; -} - -const QHash QQmlTableModelColumn::getters() const -{ - return mGetters; -} - -const QHash QQmlTableModelColumn::supportedRoleNames() -{ - QHash names; - names[Qt::DisplayRole] = QLatin1String("display"); - names[Qt::DecorationRole] = QLatin1String("decoration"); - names[Qt::EditRole] = QLatin1String("edit"); - names[Qt::ToolTipRole] = QLatin1String("toolTip"); - names[Qt::StatusTipRole] = QLatin1String("statusTip"); - names[Qt::WhatsThisRole] = QLatin1String("whatsThis"); - names[Qt::FontRole] = QLatin1String("font"); - names[Qt::TextAlignmentRole] = QLatin1String("textAlignment"); - names[Qt::BackgroundRole] = QLatin1String("background"); - names[Qt::ForegroundRole] = QLatin1String("foreground"); - names[Qt::CheckStateRole] = QLatin1String("checkState"); - names[Qt::AccessibleTextRole] = QLatin1String("accessibleText"); - names[Qt::AccessibleDescriptionRole] = QLatin1String("accessibleDescription"); - names[Qt::SizeHintRole] = QLatin1String("sizeHint"); - return names; -} - -QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodelcolumn_p.h b/src/qml/types/qqmltablemodelcolumn_p.h deleted file mode 100644 index 41c02482c0..0000000000 --- a/src/qml/types/qqmltablemodelcolumn_p.h +++ /dev/null @@ -1,224 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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$ -** -****************************************************************************/ - -#ifndef QQMLTABLEMODELCOLUMN_P_H -#define QQMLTABLEMODELCOLUMN_P_H - -// -// 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. -// - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Q_QML_AUTOTEST_EXPORT QQmlTableModelColumn : public QObject -{ - Q_OBJECT - Q_PROPERTY(QJSValue display READ display WRITE setDisplay NOTIFY displayChanged FINAL) - Q_PROPERTY(QJSValue setDisplay READ getSetDisplay WRITE setSetDisplay NOTIFY setDisplayChanged) - Q_PROPERTY(QJSValue decoration READ decoration WRITE setDecoration NOTIFY decorationChanged FINAL) - Q_PROPERTY(QJSValue setDecoration READ getSetDecoration WRITE setSetDecoration NOTIFY setDecorationChanged FINAL) - Q_PROPERTY(QJSValue edit READ edit WRITE setEdit NOTIFY editChanged FINAL) - Q_PROPERTY(QJSValue setEdit READ getSetEdit WRITE setSetEdit NOTIFY setEditChanged FINAL) - Q_PROPERTY(QJSValue toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged FINAL) - Q_PROPERTY(QJSValue setToolTip READ getSetToolTip WRITE setSetToolTip NOTIFY setToolTipChanged FINAL) - Q_PROPERTY(QJSValue statusTip READ statusTip WRITE setStatusTip NOTIFY statusTipChanged FINAL) - Q_PROPERTY(QJSValue setStatusTip READ getSetStatusTip WRITE setSetStatusTip NOTIFY setStatusTipChanged FINAL) - Q_PROPERTY(QJSValue whatsThis READ whatsThis WRITE setWhatsThis NOTIFY whatsThisChanged FINAL) - Q_PROPERTY(QJSValue setWhatsThis READ getSetWhatsThis WRITE setSetWhatsThis NOTIFY setWhatsThisChanged FINAL) - - Q_PROPERTY(QJSValue font READ font WRITE setFont NOTIFY fontChanged FINAL) - Q_PROPERTY(QJSValue setFont READ getSetFont WRITE setSetFont NOTIFY setFontChanged FINAL) - Q_PROPERTY(QJSValue textAlignment READ textAlignment WRITE setTextAlignment NOTIFY textAlignmentChanged FINAL) - Q_PROPERTY(QJSValue setTextAlignment READ getSetTextAlignment WRITE setSetTextAlignment NOTIFY setTextAlignmentChanged FINAL) - Q_PROPERTY(QJSValue background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) - Q_PROPERTY(QJSValue setBackground READ getSetBackground WRITE setSetBackground NOTIFY setBackgroundChanged FINAL) - Q_PROPERTY(QJSValue foreground READ foreground WRITE setForeground NOTIFY foregroundChanged FINAL) - Q_PROPERTY(QJSValue setForeground READ getSetForeground WRITE setSetForeground NOTIFY setForegroundChanged FINAL) - Q_PROPERTY(QJSValue checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) - Q_PROPERTY(QJSValue setCheckState READ getSetCheckState WRITE setSetCheckState NOTIFY setCheckStateChanged FINAL) - - Q_PROPERTY(QJSValue accessibleText READ accessibleText WRITE setAccessibleText NOTIFY accessibleTextChanged FINAL) - Q_PROPERTY(QJSValue setAccessibleText READ getSetAccessibleText WRITE setSetAccessibleText NOTIFY setAccessibleTextChanged FINAL) - Q_PROPERTY(QJSValue accessibleDescription READ accessibleDescription - WRITE setAccessibleDescription NOTIFY accessibleDescriptionChanged FINAL) - Q_PROPERTY(QJSValue setAccessibleDescription READ getSetAccessibleDescription - WRITE setSetAccessibleDescription NOTIFY setAccessibleDescriptionChanged FINAL) - - Q_PROPERTY(QJSValue sizeHint READ sizeHint WRITE setSizeHint NOTIFY sizeHintChanged FINAL) - Q_PROPERTY(QJSValue setSizeHint READ getSetSizeHint WRITE setSetSizeHint NOTIFY setSizeHintChanged FINAL) - -public: - QQmlTableModelColumn(QObject *parent = nullptr); - ~QQmlTableModelColumn() override; - - QJSValue display() const; - void setDisplay(const QJSValue &stringOrFunction); - QJSValue getSetDisplay() const; - void setSetDisplay(const QJSValue &function); - - QJSValue decoration() const; - void setDecoration(const QJSValue &stringOrFunction); - QJSValue getSetDecoration() const; - void setSetDecoration(const QJSValue &function); - - QJSValue edit() const; - void setEdit(const QJSValue &stringOrFunction); - QJSValue getSetEdit() const; - void setSetEdit(const QJSValue &function); - - QJSValue toolTip() const; - void setToolTip(const QJSValue &stringOrFunction); - QJSValue getSetToolTip() const; - void setSetToolTip(const QJSValue &function); - - QJSValue statusTip() const; - void setStatusTip(const QJSValue &stringOrFunction); - QJSValue getSetStatusTip() const; - void setSetStatusTip(const QJSValue &function); - - QJSValue whatsThis() const; - void setWhatsThis(const QJSValue &stringOrFunction); - QJSValue getSetWhatsThis() const; - void setSetWhatsThis(const QJSValue &function); - - QJSValue font() const; - void setFont(const QJSValue &stringOrFunction); - QJSValue getSetFont() const; - void setSetFont(const QJSValue &function); - - QJSValue textAlignment() const; - void setTextAlignment(const QJSValue &stringOrFunction); - QJSValue getSetTextAlignment() const; - void setSetTextAlignment(const QJSValue &function); - - QJSValue background() const; - void setBackground(const QJSValue &stringOrFunction); - QJSValue getSetBackground() const; - void setSetBackground(const QJSValue &function); - - QJSValue foreground() const; - void setForeground(const QJSValue &stringOrFunction); - QJSValue getSetForeground() const; - void setSetForeground(const QJSValue &function); - - QJSValue checkState() const; - void setCheckState(const QJSValue &stringOrFunction); - QJSValue getSetCheckState() const; - void setSetCheckState(const QJSValue &function); - - QJSValue accessibleText() const; - void setAccessibleText(const QJSValue &stringOrFunction); - QJSValue getSetAccessibleText() const; - void setSetAccessibleText(const QJSValue &function); - - QJSValue accessibleDescription() const; - void setAccessibleDescription(const QJSValue &stringOrFunction); - QJSValue getSetAccessibleDescription() const; - void setSetAccessibleDescription(const QJSValue &function); - - QJSValue sizeHint() const; - void setSizeHint(const QJSValue &stringOrFunction); - QJSValue getSetSizeHint() const; - void setSetSizeHint(const QJSValue &function); - - QJSValue getterAtRole(const QString &roleName); - QJSValue setterAtRole(const QString &roleName); - - const QHash getters() const; - - static const QHash supportedRoleNames(); - -Q_SIGNALS: - void indexChanged(); - void displayChanged(); - void setDisplayChanged(); - void decorationChanged(); - void setDecorationChanged(); - void editChanged(); - void setEditChanged(); - void toolTipChanged(); - void setToolTipChanged(); - void statusTipChanged(); - void setStatusTipChanged(); - void whatsThisChanged(); - void setWhatsThisChanged(); - - void fontChanged(); - void setFontChanged(); - void textAlignmentChanged(); - void setTextAlignmentChanged(); - void backgroundChanged(); - void setBackgroundChanged(); - void foregroundChanged(); - void setForegroundChanged(); - void checkStateChanged(); - void setCheckStateChanged(); - - void accessibleTextChanged(); - void setAccessibleTextChanged(); - void accessibleDescriptionChanged(); - void setAccessibleDescriptionChanged(); - void sizeHintChanged(); - void setSizeHintChanged(); - -private: - int mIndex = -1; - - // We store these in hashes because QQuickTableModel needs string-based lookup in certain situations. - QHash mGetters; - QHash mSetters; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQmlTableModelColumn) - -#endif // QQMLTABLEMODELCOLUMN_P_H diff --git a/src/qml/types/qquickpackage.cpp b/src/qml/types/qquickpackage.cpp deleted file mode 100644 index 03539d8737..0000000000 --- a/src/qml/types/qquickpackage.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qquickpackage_p.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -/*! - \qmltype Package - \instantiates QQuickPackage - \inqmlmodule QtQuick - \ingroup qtquick-views - \brief Specifies a collection of named items. - - The Package type is used in conjunction with - DelegateModel to enable delegates with a shared context - to be provided to multiple views. - - Any item within a Package may be assigned a name via the - \l{Package::name}{Package.name} attached property. - - The example below creates a Package containing two named items; - \e list and \e grid. The third item in the package (the \l Rectangle) is parented to whichever - delegate it should appear in. This allows an item to move - between views. - - \snippet package/Delegate.qml 0 - - These named items are used as the delegates by the two views who - reference the special \l{DelegateModel::parts} property to select - a model which provides the chosen delegate. - - \snippet package/view.qml 0 - - \sa {Qt Quick Examples - Views}, {Qt Quick Demo - Photo Viewer}, {Qt QML} -*/ - -/*! - \qmlattachedproperty string QtQuick::Package::name - This attached property holds the name of an item within a Package. -*/ - - -class QQuickPackagePrivate : public QObjectPrivate -{ -public: - QQuickPackagePrivate() {} - - struct DataGuard : public QQmlGuard - { - DataGuard(QObject *obj, QList *l) : list(l) { (QQmlGuard&)*this = obj; } - QList *list; - void objectDestroyed(QObject *) override { - // we assume priv will always be destroyed after objectDestroyed calls - list->removeOne(*this); - } - }; - - QList dataList; - static void data_append(QQmlListProperty *prop, QObject *o) { - QList *list = static_cast *>(prop->data); - list->append(DataGuard(o, list)); - } - static void data_clear(QQmlListProperty *prop) { - QList *list = static_cast *>(prop->data); - list->clear(); - } - static QObject *data_at(QQmlListProperty *prop, int index) { - QList *list = static_cast *>(prop->data); - return list->at(index); - } - static int data_count(QQmlListProperty *prop) { - QList *list = static_cast *>(prop->data); - return list->count(); - } -}; - -QHash QQuickPackageAttached::attached; - -QQuickPackageAttached::QQuickPackageAttached(QObject *parent) -: QObject(parent) -{ - attached.insert(parent, this); -} - -QQuickPackageAttached::~QQuickPackageAttached() -{ - attached.remove(parent()); -} - -QString QQuickPackageAttached::name() const -{ - return _name; -} - -void QQuickPackageAttached::setName(const QString &n) -{ - _name = n; -} - -QQuickPackage::QQuickPackage(QObject *parent) - : QObject(*(new QQuickPackagePrivate), parent) -{ -} - -QQuickPackage::~QQuickPackage() -{ -} - -QQmlListProperty QQuickPackage::data() -{ - Q_D(QQuickPackage); - return QQmlListProperty(this, &d->dataList, QQuickPackagePrivate::data_append, - QQuickPackagePrivate::data_count, - QQuickPackagePrivate::data_at, - QQuickPackagePrivate::data_clear); -} - -bool QQuickPackage::hasPart(const QString &name) -{ - Q_D(QQuickPackage); - for (int ii = 0; ii < d->dataList.count(); ++ii) { - QObject *obj = d->dataList.at(ii); - QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); - if (a && a->name() == name) - return true; - } - return false; -} - -QObject *QQuickPackage::part(const QString &name) -{ - Q_D(QQuickPackage); - if (name.isEmpty() && !d->dataList.isEmpty()) - return d->dataList.at(0); - - for (int ii = 0; ii < d->dataList.count(); ++ii) { - QObject *obj = d->dataList.at(ii); - QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); - if (a && a->name() == name) - return obj; - } - - if (name == QLatin1String("default") && !d->dataList.isEmpty()) - return d->dataList.at(0); - - return nullptr; -} - -QQuickPackageAttached *QQuickPackage::qmlAttachedProperties(QObject *o) -{ - return new QQuickPackageAttached(o); -} - - - -QT_END_NAMESPACE - -#include "moc_qquickpackage_p.cpp" diff --git a/src/qml/types/qquickpackage_p.h b/src/qml/types/qquickpackage_p.h deleted file mode 100644 index 122c7fcb30..0000000000 --- a/src/qml/types/qquickpackage_p.h +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQUICKPACKAGE_H -#define QQUICKPACKAGE_H - -// -// 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. -// - -#include - -QT_BEGIN_NAMESPACE - -class QQuickPackagePrivate; -class QQuickPackageAttached; -class Q_AUTOTEST_EXPORT QQuickPackage : public QObject -{ - Q_OBJECT - Q_DECLARE_PRIVATE(QQuickPackage) - - Q_CLASSINFO("DefaultProperty", "data") - Q_PROPERTY(QQmlListProperty data READ data) - -public: - QQuickPackage(QObject *parent=nullptr); - virtual ~QQuickPackage(); - - QQmlListProperty data(); - - QObject *part(const QString & = QString()); - bool hasPart(const QString &); - - static QQuickPackageAttached *qmlAttachedProperties(QObject *); -}; - -class QQuickPackageAttached : public QObject -{ -Q_OBJECT -Q_PROPERTY(QString name READ name WRITE setName) -public: - QQuickPackageAttached(QObject *parent); - virtual ~QQuickPackageAttached(); - - QString name() const; - void setName(const QString &n); - - static QHash attached; -private: - QString _name; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQuickPackage) -QML_DECLARE_TYPEINFO(QQuickPackage, QML_HAS_ATTACHED_PROPERTIES) - -#endif // QQUICKPACKAGE_H diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri index 5a56208dc4..ba11271d66 100644 --- a/src/qml/types/types.pri +++ b/src/qml/types/types.pri @@ -1,27 +1,12 @@ SOURCES += \ $$PWD/qqmlbind.cpp \ $$PWD/qqmlconnections.cpp \ - $$PWD/qqmlmodelsmodule.cpp \ - $$PWD/qqmlmodelindexvaluetype.cpp \ - $$PWD/qqmlobjectmodel.cpp \ - $$PWD/qquickpackage.cpp \ - $$PWD/qqmlinstantiator.cpp \ - $$PWD/qqmltableinstancemodel.cpp \ - $$PWD/qqmltablemodel.cpp \ - $$PWD/qqmltablemodelcolumn.cpp + $$PWD/qqmlmodelindexvaluetype.cpp HEADERS += \ $$PWD/qqmlbind_p.h \ $$PWD/qqmlconnections_p.h \ - $$PWD/qqmlmodelsmodule_p.h \ - $$PWD/qqmlmodelindexvaluetype_p.h \ - $$PWD/qqmlobjectmodel_p.h \ - $$PWD/qquickpackage_p.h \ - $$PWD/qqmlinstantiator_p.h \ - $$PWD/qqmlinstantiator_p_p.h \ - $$PWD/qqmltableinstancemodel_p.h \ - $$PWD/qqmltablemodel_p.h \ - $$PWD/qqmltablemodelcolumn_p.h + $$PWD/qqmlmodelindexvaluetype_p.h qtConfig(qml-worker-script) { SOURCES += \ @@ -30,28 +15,6 @@ qtConfig(qml-worker-script) { $$PWD/qquickworkerscript_p.h } -qtConfig(qml-list-model) { - SOURCES += \ - $$PWD/qqmllistmodel.cpp \ - $$PWD/qqmllistmodelworkeragent.cpp - - HEADERS += \ - $$PWD/qqmllistmodel_p.h \ - $$PWD/qqmllistmodel_p_p.h \ - $$PWD/qqmllistmodelworkeragent_p.h -} - -qtConfig(qml-delegate-model) { - SOURCES += \ - $$PWD/qqmldelegatemodel.cpp \ - $$PWD/qqmldelegatecomponent.cpp - - HEADERS += \ - $$PWD/qqmldelegatemodel_p.h \ - $$PWD/qqmldelegatemodel_p_p.h \ - $$PWD/qqmldelegatecomponent_p.h -} - qtConfig(qml-animation) { SOURCES += \ $$PWD/qqmltimer.cpp diff --git a/src/qml/util/qqmladaptormodel.cpp b/src/qml/util/qqmladaptormodel.cpp deleted file mode 100644 index f991ae0a69..0000000000 --- a/src/qml/util/qqmladaptormodel.cpp +++ /dev/null @@ -1,1037 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -class QQmlAdaptorModelEngineData : public QV8Engine::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 o(scope, thisObject->as()); - if (!o) - RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); - - RETURN_RESULT(QV4::Encode(o->d()->item->index)); -} - -template 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( - 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 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 &items, - int index, - int count, - const QVector &roles) const override - { - bool changed = roles.isEmpty() && !watchedRoles.isEmpty(); - if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) { - QList roleIds; - for (const QByteArray &r : watchedRoles) { - QHash::const_iterator it = roleNames.find(r); - if (it != roleNames.end()) - roleIds << it.value(); - } - const_cast(this)->watchedRoleIds = roleIds; - } - - QVector 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); - } - - for (int i = 0, c = items.count(); i < c; ++i) { - QQmlDelegateModelItem *item = items.at(i); - 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 &oldRoles, - const QList &newRoles) const override - { - VDMModelDelegateDataType *dataType = const_cast(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 o(scope, thisObject->as()); - if (!o) - RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); - - const QQmlAdaptorModel *const model = static_cast(o->d()->item)->type->model; - if (o->d()->item->index >= 0 && *model) { - const QAbstractItemModel * const aim = model->aim(); - RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); - } else { - 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::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(global, propertyId, QQmlDMCachedModelData::get_property)); - QV4::ScopedFunctionObject s(scope, v4->memoryManager->allocate(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(object)->metaCall(call, id, arguments); - } - - QV4::PersistentValue prototype; - QList propertyRoles; - QList watchedRoleIds; - QList watchedRoles; - QHash roleNames; - QQmlAdaptorModel *model; - int propertyOffset; - int signalOffset; - bool hasModelData; -}; - -QQmlDMCachedModelData::QQmlDMCachedModelData(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(arguments[0]) = cachedData.at( - type->hasModelData ? 0 : propertyIndex); - } - } else if (*type->model) { - *static_cast(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(arguments[0]); - QMetaObject::activate(this, meta, propertyIndex, nullptr); - } else if (cachedData.count() == 1) { - cachedData[0] = *static_cast(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(arguments[0])); - } - return -1; - } else { - return qt_metacall(call, id, arguments); - } -} - -void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value) -{ - QHash::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 o(scope, thisObject->as()); - if (!o) - return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - - uint propertyId = static_cast(b)->d()->index; - - QQmlDMCachedModelData *modelData = static_cast(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 o(scope, thisObject->as()); - if (!o) - return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - if (!argc) - return scope.engine->throwTypeError(); - - uint propertyId = static_cast(b)->d()->index; - - if (o->d()->item->index == -1) { - QQmlDMCachedModelData *modelData = static_cast(o->d()->item); - if (!modelData->cachedData.isEmpty()) { - if (modelData->cachedData.count() > 1) { - modelData->cachedData[propertyId] = scope.engine->toVariant(argv[0], QVariant::Invalid); - 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], QVariant::Invalid); - 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( - QQmlDelegateModelItemMetaType *metaType, - VDMModelDelegateDataType *dataType, - int index, int row, int column) - : QQmlDMCachedModelData(metaType, dataType, index, row, column) - { - } - - bool hasModelChildren() const - { - if (index >= 0 && *type->model) { - const QAbstractItemModel * const model = type->model->aim(); - return model->hasChildren(model->index(row, column, type->model->rootIndex)); - } else { - return false; - } - } - - QVariant value(int role) const override - { - return type->model->aim()->index(row, column, type->model->rootIndex).data(role); - } - - void setValue(int role, const QVariant &value) override - { - type->model->aim()->setData( - type->model->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(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 - { - return model.aim()->rowCount(model.rootIndex); - } - - int columnCount(const QQmlAdaptorModel &model) const override - { - return model.aim()->columnCount(model.rootIndex); - } - - void cleanup(QQmlAdaptorModel &) const override - { - const_cast(this)->release(); - } - - QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override - { - QHash::const_iterator it = roleNames.find(role.toUtf8()); - if (it != roleNames.end()) { - return model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex).data(*it); - } else if (role == QLatin1String("hasModelChildren")) { - return QVariant(model.aim()->hasChildren(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex))); - } else { - return QVariant(); - } - } - - QVariant parentModelIndex(const QQmlAdaptorModel &model) const override - { - return model - ? QVariant::fromValue(model.aim()->parent(model.rootIndex)) - : QVariant(); - } - - QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override - { - return model - ? QVariant::fromValue(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex)) - : QVariant(); - } - - bool canFetchMore(const QQmlAdaptorModel &model) const override - { - return model && model.aim()->canFetchMore(model.rootIndex); - } - - void fetchMore(QQmlAdaptorModel &model) const override - { - if (model) - model.aim()->fetchMore(model.rootIndex); - } - - QQmlDelegateModelItem *createItem( - QQmlAdaptorModel &model, - QQmlDelegateModelItemMetaType *metaType, - int index, int row, int column) const override - { - VDMAbstractItemModelDataType *dataType = const_cast(this); - if (!metaObject) - dataType->initializeMetaType(model); - return new QQmlDMAbstractItemModelData(metaType, dataType, index, row, column); - } - - void initializeMetaType(QQmlAdaptorModel &model) - { - QMetaObjectBuilder builder; - setModelDataType(&builder, this); - - const QByteArray propertyType = QByteArrayLiteral("QVariant"); - const QHash names = model.aim()->roleNames(); - for (QHash::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(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(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(); - if (!o) - return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - - return v4->fromVariant(static_cast(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(); - if (!o) - return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); - if (!argc) - return v4->throwTypeError(); - - static_cast(o->d()->item)->setModelData(v4->toVariant(argv[0], QVariant::Invalid)); - return QV4::Encode::undefined(); - } - - QV4::ReturnedValue get() override - { - QQmlAdaptorModelEngineData *data = engineData(v4); - QV4::Scope scope(v4); - QV4::ScopedObject o(scope, v4->memoryManager->allocate(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(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, - QQmlDelegateModelItemMetaType *metaType, - int index, int row, int column) const override - { - VDMListDelegateDataType *dataType = const_cast(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 &items, int index, int count, const QVector &) const override - { - for (auto modelItem : items) { - const int modelItemIndex = modelItem->index; - if (modelItemIndex < index || modelItemIndex >= index + count) - continue; - - auto listModelItem = static_cast(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( - 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 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()) - return object->property(role.toUtf8()); - return QVariant(); - } - - QQmlDelegateModelItem *createItem( - QQmlAdaptorModel &model, - QQmlDelegateModelItemMetaType *metaType, - int index, int row, int column) const override - { - VDMObjectDelegateDataType *dataType = const_cast(this); - if (!metaObject) - dataType->initializeMetaType(model); - return index >= 0 && index < model.list.count() - ? new QQmlDMObjectData(metaType, dataType, index, row, column, qvariant_cast(model.list.at(index))) - : nullptr; - } - - void initializeMetaType(QQmlAdaptorModel &model) - { - Q_UNUSED(model); - setModelDataType(&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(this)->release(); - } - - bool notify(const QQmlAdaptorModel &model, const QList &items, int index, int count, const QVector &) const override - { - for (auto modelItem : items) { - const int modelItemIndex = modelItem->index; - if (modelItemIndex < index || modelItemIndex >= index + count) - continue; - - auto objectModelItem = static_cast(modelItem); - QObject *updatedModelData = qvariant_cast(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(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(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(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(list.list())) { - setObject(object, parent); - if (qobject_cast(object)) - accessors = new VDMAbstractItemModelDataType(this); - else - accessors = new VDMObjectDelegateDataType; - } else if (list.type() == QQmlListAccessor::ListProperty) { - setObject(static_cast(variant.constData())->object(), 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 diff --git a/src/qml/util/qqmladaptormodel_p.h b/src/qml/util/qqmladaptormodel_p.h deleted file mode 100644 index 8c18466ab5..0000000000 --- a/src/qml/util/qqmladaptormodel_p.h +++ /dev/null @@ -1,179 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLADAPTORMODEL_P_H -#define QQMLADAPTORMODEL_P_H - -// -// 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. -// - -#include - -#include "private/qqmllistaccessor_p.h" -#include -#include -#include - -QT_REQUIRE_CONFIG(qml_delegate_model); - -QT_BEGIN_NAMESPACE - -class QQmlEngine; - -class QQmlDelegateModel; -class QQmlDelegateModelItem; -class QQmlDelegateModelItemMetaType; - -class Q_QML_PRIVATE_EXPORT QQmlAdaptorModel : public QQmlStrongJSQObjectReference -{ -public: - class Accessors - { - public: - inline Accessors() {} - virtual ~Accessors(); - virtual int rowCount(const QQmlAdaptorModel &) const { return 0; } - virtual int columnCount(const QQmlAdaptorModel &) const { return 0; } - virtual void cleanup(QQmlAdaptorModel &) const {} - - virtual QVariant value(const QQmlAdaptorModel &, int, const QString &) const { - return QVariant(); } - - virtual QQmlDelegateModelItem *createItem( - QQmlAdaptorModel &, - QQmlDelegateModelItemMetaType *, - int, int, int) const { return nullptr; } - - virtual bool notify( - const QQmlAdaptorModel &, - const QList &, - int, - int, - const QVector &) const { return false; } - virtual void replaceWatchedRoles( - QQmlAdaptorModel &, - const QList &, - const QList &) const {} - virtual QVariant parentModelIndex(const QQmlAdaptorModel &) const { - return QVariant(); } - virtual QVariant modelIndex(const QQmlAdaptorModel &, int) const { - return QVariant(); } - virtual bool canFetchMore(const QQmlAdaptorModel &) const { return false; } - virtual void fetchMore(QQmlAdaptorModel &) const {} - - QScopedPointer metaObject; - QQmlRefPointer propertyCache; - }; - - const Accessors *accessors; - QPersistentModelIndex rootIndex; - QQmlListAccessor list; - - int modelItemRevision = 0; - - QQmlAdaptorModel(); - ~QQmlAdaptorModel(); - - inline QVariant model() const { return list.list(); } - void setModel(const QVariant &variant, QObject *parent, QQmlEngine *engine); - void invalidateModel(); - - bool isValid() const; - int count() const; - int rowCount() const; - int columnCount() const; - int rowAt(int index) const; - int columnAt(int index) const; - int indexAt(int row, int column) const; - - void useImportVersion(int minorVersion); - - inline bool adaptsAim() const { return qobject_cast(object()); } - inline QAbstractItemModel *aim() { return static_cast(object()); } - inline const QAbstractItemModel *aim() const { return static_cast(object()); } - - inline QVariant value(int index, const QString &role) const { - return accessors->value(*this, index, role); } - inline QQmlDelegateModelItem *createItem(QQmlDelegateModelItemMetaType *metaType, int index) { - return accessors->createItem(*this, metaType, index, rowAt(index), columnAt(index)); } - inline bool hasProxyObject() const { - return list.type() == QQmlListAccessor::Instance || list.type() == QQmlListAccessor::ListProperty; } - - inline bool notify( - const QList &items, - int index, - int count, - const QVector &roles) const { - return accessors->notify(*this, items, index, count, roles); } - inline void replaceWatchedRoles( - const QList &oldRoles, const QList &newRoles) { - accessors->replaceWatchedRoles(*this, oldRoles, newRoles); } - - inline QVariant modelIndex(int index) const { return accessors->modelIndex(*this, index); } - inline QVariant parentModelIndex() const { return accessors->parentModelIndex(*this); } - inline bool canFetchMore() const { return accessors->canFetchMore(*this); } - inline void fetchMore() { return accessors->fetchMore(*this); } - -protected: - void objectDestroyed(QObject *) override; -}; - -class QQmlAdaptorModelProxyInterface -{ -public: - virtual ~QQmlAdaptorModelProxyInterface() {} - - virtual QObject *proxiedObject() = 0; -}; - -#define QQmlAdaptorModelProxyInterface_iid "org.qt-project.Qt.QQmlAdaptorModelProxyInterface" - -Q_DECLARE_INTERFACE(QQmlAdaptorModelProxyInterface, QQmlAdaptorModelProxyInterface_iid) - -QT_END_NAMESPACE - -#endif diff --git a/src/qml/util/qqmlchangeset.cpp b/src/qml/util/qqmlchangeset.cpp deleted file mode 100644 index ba876b42e2..0000000000 --- a/src/qml/util/qqmlchangeset.cpp +++ /dev/null @@ -1,583 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmlchangeset_p.h" - -QT_BEGIN_NAMESPACE - - -/*! - \class QQmlChangeSet - \brief The QQmlChangeSet class stores an ordered list of notifications about - changes to a linear data set. - \internal - - QQmlChangeSet can be used to record a series of notifications about items in an indexed list - being inserted, removed, moved, and changed. Notifications in the set are re-ordered so that - all notifications of a single type are grouped together and sorted in order of ascending index, - with remove notifications preceding all others, followed by insert notification, and then - change notifications. - - Moves in a change set are represented by a remove notification paired with an insert - notification by way of a shared unique moveId. Re-ordering may result in one or both of the - paired notifications being divided, when this happens the offset member of the notification - will indicate the relative offset of the divided notification from the beginning of the - original. -*/ - -/*! - Constructs an empty change set. -*/ - -QQmlChangeSet::QQmlChangeSet() - : m_difference(0) -{ -} - -/*! - Constructs a copy of a \a changeSet. -*/ - -QQmlChangeSet::QQmlChangeSet(const QQmlChangeSet &changeSet) - : m_removes(changeSet.m_removes) - , m_inserts(changeSet.m_inserts) - , m_changes(changeSet.m_changes) - , m_difference(changeSet.m_difference) -{ -} - -/*! - Destroys a change set. -*/ - -QQmlChangeSet::~QQmlChangeSet() -{ -} - -/*! - Assigns the value of a \a changeSet to another. -*/ - -QQmlChangeSet &QQmlChangeSet::operator =(const QQmlChangeSet &changeSet) -{ - m_removes = changeSet.m_removes; - m_inserts = changeSet.m_inserts; - m_changes = changeSet.m_changes; - m_difference = changeSet.m_difference; - return *this; -} - -/*! - Appends a notification that \a count items were inserted at \a index. -*/ - -void QQmlChangeSet::insert(int index, int count) -{ - insert(QVector() << Change(index, count)); -} - -/*! - Appends a notification that \a count items were removed at \a index. -*/ - -void QQmlChangeSet::remove(int index, int count) -{ - QVector removes; - removes.append(Change(index, count)); - remove(&removes, nullptr); -} - -/*! - Appends a notification that \a count items were moved \a from one index \a to another. - - The \a moveId must be unique across the lifetime of the change set and any related - change sets. -*/ - -void QQmlChangeSet::move(int from, int to, int count, int moveId) -{ - QVector removes; - removes.append(Change(from, count, moveId)); - QVector inserts; - inserts.append(Change(to, count, moveId)); - remove(&removes, &inserts); - insert(inserts); -} - -/*! - Appends a notification that \a count items were changed at \a index. -*/ - -void QQmlChangeSet::change(int index, int count) -{ - QVector changes; - changes.append(Change(index, count)); - change(changes); -} - -/*! - Applies the changes in a \a changeSet to another. -*/ - -void QQmlChangeSet::apply(const QQmlChangeSet &changeSet) -{ - QVector r = changeSet.m_removes; - QVector i = changeSet.m_inserts; - QVector c = changeSet.m_changes; - remove(&r, &i); - insert(i); - change(c); -} - -/*! - Applies a list of \a removes to a change set. - - If a remove contains a moveId then any intersecting insert in the set will replace the - corresponding intersection in the optional \a inserts list. -*/ - -void QQmlChangeSet::remove(const QVector &removes, QVector *inserts) -{ - QVector r = removes; - remove(&r, inserts); -} - -void QQmlChangeSet::remove(QVector *removes, QVector *inserts) -{ - int removeCount = 0; - int insertCount = 0; - QVector::iterator insert = m_inserts.begin(); - QVector::iterator change = m_changes.begin(); - QVector::iterator rit = removes->begin(); - for (; rit != removes->end(); ++rit) { - int index = rit->index + removeCount; - int count = rit->count; - - // Decrement the accumulated remove count from the indexes of any changes prior to the - // current remove. - for (; change != m_changes.end() && change->end() < rit->index; ++change) - change->index -= removeCount; - // Remove any portion of a change notification that intersects the current remove. - for (; change != m_changes.end() && change->index > rit->end(); ++change) { - change->count -= qMin(change->end(), rit->end()) - qMax(change->index, rit->index); - if (change->count == 0) { - change = m_changes.erase(change); - } else if (rit->index < change->index) { - change->index = rit->index; - } - } - - // Decrement the accumulated remove count from the indexes of any inserts prior to the - // current remove. - for (; insert != m_inserts.end() && insert->end() <= index; ++insert) { - insertCount += insert->count; - insert->index -= removeCount; - } - - rit->index -= insertCount; - - // Remove any portion of a insert notification that intersects the current remove. - while (insert != m_inserts.end() && insert->index < index + count) { - int offset = index - insert->index; - const int difference = qMin(insert->end(), index + count) - qMax(insert->index, index); - - // If part of the remove or insert that precedes the intersection has a moveId create - // a new delta for that portion and subtract the size of that delta from the current - // one. - if (offset < 0 && rit->moveId != -1) { - rit = removes->insert(rit, Change( - rit->index, -offset, rit->moveId, rit->offset)); - ++rit; - rit->count -= -offset; - rit->offset += -offset; - index += -offset; - count -= -offset; - removeCount += -offset; - offset = 0; - } else if (offset > 0 && insert->moveId != -1) { - insert = m_inserts.insert(insert, Change( - insert->index - removeCount, offset, insert->moveId, insert->offset)); - ++insert; - insert->index += offset; - insert->count -= offset; - insert->offset += offset; - rit->index -= offset; - insertCount += offset; - } - - // If the current remove has a move id, find any inserts with the same move id and - // replace the corresponding sections with the insert removed from the change set. - if (rit->moveId != -1 && difference > 0 && inserts) { - for (QVector::iterator iit = inserts->begin(); iit != inserts->end(); ++iit) { - if (iit->moveId != rit->moveId - || rit->offset > iit->offset + iit->count - || iit->offset > rit->offset + difference) { - continue; - } - // If the intersecting insert starts before the replacement one create - // a new insert for the portion prior to the replacement insert. - const int overlapOffset = rit->offset - iit->offset; - if (overlapOffset > 0) { - iit = inserts->insert(iit, Change( - iit->index, overlapOffset, iit->moveId, iit->offset)); - ++iit; - iit->index += overlapOffset; - iit->count -= overlapOffset; - iit->offset += overlapOffset; - } - if (iit->offset >= rit->offset - && iit->offset + iit->count <= rit->offset + difference) { - // If the replacement insert completely encapsulates the existing - // one just change the moveId. - iit->moveId = insert->moveId; - iit->offset = insert->offset + qMax(0, -overlapOffset); - } else { - // Create a new insertion before the intersecting one with the number of intersecting - // items and remove that number from that insert. - const int count - = qMin(iit->offset + iit->count, rit->offset + difference) - - qMax(iit->offset, rit->offset); - iit = inserts->insert(iit, Change( - iit->index, - count, - insert->moveId, - insert->offset + qMax(0, -overlapOffset))); - ++iit; - iit->index += count; - iit->count -= count; - iit->offset += count; - } - } - } - - // Subtract the number of intersecting items from the current remove and insert. - insert->count -= difference; - insert->offset += difference; - rit->count -= difference; - rit->offset += difference; - - index += difference; - count -= difference; - removeCount += difference; - - if (insert->count == 0) { - insert = m_inserts.erase(insert); - } else if (rit->count == -offset || rit->count == 0) { - insert->index += difference; - break; - } else { - insert->index -= removeCount - difference; - rit->index -= insert->count; - insertCount += insert->count; - ++insert; - } - } - removeCount += rit->count; - } - for (; insert != m_inserts.end(); ++insert) - insert->index -= removeCount; - - removeCount = 0; - QVector::iterator remove = m_removes.begin(); - for (rit = removes->begin(); rit != removes->end(); ++rit) { - if (rit->count == 0) - continue; - // Accumulate consecutive removes into a single delta before attempting to apply. - for (QVector::iterator next = rit + 1; next != removes->end() - && next->index == rit->index - && next->moveId == -1 - && rit->moveId == -1; ++next) { - next->count += rit->count; - rit = next; - } - int index = rit->index + removeCount; - // Decrement the accumulated remove count from the indexes of any inserts prior to the - // current remove. - for (; remove != m_removes.end() && index > remove->index; ++remove) - remove->index -= removeCount; - while (remove != m_removes.end() && index + rit->count >= remove->index) { - int count = 0; - const int offset = remove->index - index; - QVector::iterator rend = remove; - for (; rend != m_removes.end() - && rit->moveId == -1 - && rend->moveId == -1 - && index + rit->count >= rend->index; ++rend) { - count += rend->count; - } - if (remove != rend) { - // Accumulate all existing non-move removes that are encapsulated by or immediately - // follow the current remove into it. - int difference = 0; - if (rend == m_removes.end()) { - difference = rit->count; - } else if (rit->index + rit->count < rend->index - removeCount) { - difference = rit->count; - } else if (rend->moveId != -1) { - difference = rend->index - removeCount - rit->index; - index += difference; - } - count += difference; - - rit->count -= difference; - removeCount += difference; - remove->index = rit->index; - remove->count = count; - remove = m_removes.erase(++remove, rend); - } else { - // Insert a remove for the portion of the unmergable current remove prior to the - // point of intersection. - if (offset > 0) { - remove = m_removes.insert(remove, Change( - rit->index, offset, rit->moveId, rit->offset)); - ++remove; - rit->count -= offset; - rit->offset += offset; - removeCount += offset; - index += offset; - } - remove->index = rit->index; - - ++remove; - } - } - - if (rit->count > 0) { - remove = m_removes.insert(remove, *rit); - ++remove; - } - removeCount += rit->count; - } - for (; remove != m_removes.end(); ++remove) - remove->index -= removeCount; - m_difference -= removeCount; -} - -/*! - Applies a list of \a inserts to a change set. -*/ - -void QQmlChangeSet::insert(const QVector &inserts) -{ - int insertCount = 0; - QVector::iterator insert = m_inserts.begin(); - QVector::iterator change = m_changes.begin(); - for (QVector::const_iterator iit = inserts.begin(); iit != inserts.end(); ++iit) { - if (iit->count == 0) - continue; - int index = iit->index - insertCount; - - Change current = *iit; - // Accumulate consecutive inserts into a single delta before attempting to insert. - for (QVector::const_iterator next = iit + 1; next != inserts.end() - && next->index == iit->index + iit->count - && next->moveId == -1 - && iit->moveId == -1; ++next) { - current.count += next->count; - iit = next; - } - - // Increment the index of any changes before the current insert by the accumlated insert - // count. - for (; change != m_changes.end() && change->index >= index; ++change) - change->index += insertCount; - // If the current insert index is in the middle of a change split it in two at that - // point and increment the index of the latter half. - if (change != m_changes.end() && change->index < index + iit->count) { - int offset = index - change->index; - change = m_changes.insert(change, Change(change->index + insertCount, offset)); - ++change; - change->index += iit->count + offset; - change->count -= offset; - } - - // Increment the index of any inserts before the current insert by the accumlated insert - // count. - for (; insert != m_inserts.end() && index > insert->index + insert->count; ++insert) - insert->index += insertCount; - if (insert == m_inserts.end()) { - insert = m_inserts.insert(insert, current); - ++insert; - } else { - const int offset = index - insert->index; - - if (offset < 0) { - // If the current insert is before an existing insert and not adjacent just insert - // it into the list. - insert = m_inserts.insert(insert, current); - ++insert; - } else if (iit->moveId == -1 && insert->moveId == -1) { - // If neither the current nor existing insert has a moveId add the current insert - // to the existing one. - if (offset < insert->count) { - insert->index -= current.count; - insert->count += current.count; - } else { - insert->index += insertCount; - insert->count += current.count; - ++insert; - } - } else if (offset < insert->count) { - // If either insert has a moveId then split the existing insert and insert the - // current one in the middle. - if (offset > 0) { - insert = m_inserts.insert(insert, Change( - insert->index + insertCount, offset, insert->moveId, insert->offset)); - ++insert; - insert->index += offset; - insert->count -= offset; - insert->offset += offset; - } - insert = m_inserts.insert(insert, current); - ++insert; - } else { - insert->index += insertCount; - ++insert; - insert = m_inserts.insert(insert, current); - ++insert; - } - } - insertCount += current.count; - } - for (; insert != m_inserts.end(); ++insert) - insert->index += insertCount; - m_difference += insertCount; -} - -/*! - Applies a combined list of \a removes and \a inserts to a change set. This is equivalent - calling \l remove() followed by \l insert() with the same lists. -*/ - -void QQmlChangeSet::move(const QVector &removes, const QVector &inserts) -{ - QVector r = removes; - QVector i = inserts; - remove(&r, &i); - insert(i); -} - -/*! - Applies a list of \a changes to a change set. -*/ - -void QQmlChangeSet::change(const QVector &changes) -{ - QVector c = changes; - change(&c); -} - -void QQmlChangeSet::change(QVector *changes) -{ - QVector::iterator insert = m_inserts.begin(); - QVector::iterator change = m_changes.begin(); - for (QVector::iterator cit = changes->begin(); cit != changes->end(); ++cit) { - for (; insert != m_inserts.end() && insert->end() < cit->index; ++insert) {} - for (; insert != m_inserts.end() && insert->index < cit->end(); ++insert) { - const int offset = insert->index - cit->index; - const int count = cit->count + cit->index - insert->index - insert->count; - if (offset == 0) { - cit->index = insert->index + insert->count; - cit->count = count; - } else { - cit = changes->insert(++cit, Change(insert->index + insert->count, count)); - --cit; - cit->count = offset; - } - } - - for (; change != m_changes.end() && change->index + change->count < cit->index; ++change) {} - if (change == m_changes.end() || change->index > cit->index + cit->count) { - if (cit->count > 0) { - change = m_changes.insert(change, *cit); - ++change; - } - } else { - if (cit->index < change->index) { - change->count += change->index - cit->index; - change->index = cit->index; - } - - if (cit->index + cit->count > change->index + change->count) { - change->count = cit->index + cit->count - change->index; - QVector::iterator cbegin = change; - QVector::iterator cend = ++cbegin; - for (; cend != m_changes.end() && cend->index <= change->index + change->count; ++cend) { - if (cend->index + cend->count > change->index + change->count) - change->count = cend->index + cend->count - change->index; - } - if (cbegin != cend) { - change = m_changes.erase(cbegin, cend); - --change; - } - } - } - } -} - -/*! - Prints the contents of a change \a set to the \a debug stream. -*/ - -QDebug operator <<(QDebug debug, const QQmlChangeSet &set) -{ - debug.nospace() << "QQmlChangeSet("; - const QVector &removes = set.removes(); - for (const QQmlChangeSet::Change &remove : removes) - debug << remove; - const QVector &inserts = set.inserts(); - for (const QQmlChangeSet::Change &insert : inserts) - debug << insert; - const QVector &changes = set.changes(); - for (const QQmlChangeSet::Change &change : changes) - debug << change; - return debug.nospace() << ')'; -} - -/*! - Prints a \a change to the \a debug stream. -*/ - -QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change) -{ - return (debug.nospace() << "Change(" << change.index << ',' << change.count << ')').space(); -} - -QT_END_NAMESPACE - diff --git a/src/qml/util/qqmlchangeset_p.h b/src/qml/util/qqmlchangeset_p.h deleted file mode 100644 index 8347a3ff19..0000000000 --- a/src/qml/util/qqmlchangeset_p.h +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLCHANGESET_P_H -#define QQMLCHANGESET_P_H - -// -// 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. -// - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Q_QML_PRIVATE_EXPORT QQmlChangeSet -{ -public: - struct MoveKey - { - MoveKey() {} - MoveKey(int moveId, int offset) : moveId(moveId), offset(offset) {} - int moveId = -1; - int offset = 0; - }; - - // The storrage for Change (below). This struct is trivial, which it has to be in order to store - // it in a QV4::Heap::Base object. The Change struct doesn't add any storage fields, so it is - // safe to cast ChangeData to/from Change. - struct ChangeData - { - int index; - int count; - int moveId; - int offset; - }; - - struct Change: ChangeData - { - Change() { - index = 0; - count = 0; - moveId = -1; - offset = 0; - } - Change(int index, int count, int moveId = -1, int offset = 0) { - this->index = index; - this->count = count; - this->moveId = moveId; - this->offset = offset; - } - - bool isMove() const { return moveId >= 0; } - - MoveKey moveKey(int index) const { - return MoveKey(moveId, index - Change::index + offset); } - - int start() const { return index; } - int end() const { return index + count; } - }; - - QQmlChangeSet(); - QQmlChangeSet(const QQmlChangeSet &changeSet); - ~QQmlChangeSet(); - - QQmlChangeSet &operator =(const QQmlChangeSet &changeSet); - - const QVector &removes() const { return m_removes; } - const QVector &inserts() const { return m_inserts; } - const QVector &changes() const { return m_changes; } - - void insert(int index, int count); - void remove(int index, int count); - void move(int from, int to, int count, int moveId); - void change(int index, int count); - - void insert(const QVector &inserts); - void remove(const QVector &removes, QVector *inserts = nullptr); - void move(const QVector &removes, const QVector &inserts); - void change(const QVector &changes); - void apply(const QQmlChangeSet &changeSet); - - bool isEmpty() const { return m_removes.empty() && m_inserts.empty() && m_changes.isEmpty(); } - - void clear() - { - m_removes.clear(); - m_inserts.clear(); - m_changes.clear(); - m_difference = 0; - } - - int difference() const { return m_difference; } - -private: - void remove(QVector *removes, QVector *inserts); - void change(QVector *changes); - - QVector m_removes; - QVector m_inserts; - QVector m_changes; - int m_difference; -}; - -Q_DECLARE_TYPEINFO(QQmlChangeSet::Change, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(QQmlChangeSet::MoveKey, Q_PRIMITIVE_TYPE); - -inline uint qHash(const QQmlChangeSet::MoveKey &key) { return qHash(qMakePair(key.moveId, key.offset)); } -inline bool operator ==(const QQmlChangeSet::MoveKey &l, const QQmlChangeSet::MoveKey &r) { - return l.moveId == r.moveId && l.offset == r.offset; } - -Q_QML_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change); -Q_QML_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet &change); - -QT_END_NAMESPACE - -#endif diff --git a/src/qml/util/qqmllistaccessor.cpp b/src/qml/util/qqmllistaccessor.cpp deleted file mode 100644 index 46a11e2bc2..0000000000 --- a/src/qml/util/qqmllistaccessor.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmllistaccessor_p.h" - -#include - -#include -#include - -// ### Remove me -#include - -QT_BEGIN_NAMESPACE - -QQmlListAccessor::QQmlListAccessor() -: m_type(Invalid) -{ -} - -QQmlListAccessor::~QQmlListAccessor() -{ -} - -QVariant QQmlListAccessor::list() const -{ - return d; -} - -void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine) -{ - d = v; - - // An incoming JS array as model is treated as a variant list, so we need to - // convert it first with toVariant(). - if (d.userType() == qMetaTypeId()) - d = d.value().toVariant(); - - QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):nullptr; - - if (!d.isValid()) { - m_type = Invalid; - } else if (d.userType() == QVariant::StringList) { - m_type = StringList; - } else if (d.userType() == QMetaType::QVariantList) { - m_type = VariantList; - } else if (d.canConvert(QVariant::Int)) { - // Here we have to check for an upper limit, because down the line code might (well, will) - // allocate memory depending on the number of elements. The upper limit cannot be INT_MAX: - // QVector> something; - // something.resize(count()); - // (See e.g. QQuickRepeater::regenerate()) - // This will allocate data along the lines of: - // sizeof(QPointer) * count() + QVector::headerSize - // So, doing an approximate round-down-to-nice-number, we get: - const int upperLimit = 100 * 1000 * 1000; - - int i = v.toInt(); - if (i < 0) { - qWarning("Model size of %d is less than 0", i); - m_type = Invalid; - } else if (i > upperLimit) { - qWarning("Model size of %d is bigger than the upper limit %d", i, upperLimit); - m_type = Invalid; - } else { - m_type = Integer; - } - } else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) || - (enginePrivate && enginePrivate->isQObject(d.userType()))) { - QObject *data = enginePrivate?enginePrivate->toQObject(d):QQmlMetaType::toQObject(d); - d = QVariant::fromValue(data); - m_type = Instance; - } else if (d.userType() == qMetaTypeId()) { - m_type = ListProperty; - } else { - m_type = Instance; - } -} - -int QQmlListAccessor::count() const -{ - switch(m_type) { - case StringList: - return qvariant_cast(d).count(); - case VariantList: - return qvariant_cast(d).count(); - case ListProperty: - return ((const QQmlListReference *)d.constData())->count(); - case Instance: - return 1; - case Integer: - return d.toInt(); - default: - case Invalid: - return 0; - } -} - -QVariant QQmlListAccessor::at(int idx) const -{ - Q_ASSERT(idx >= 0 && idx < count()); - switch(m_type) { - case StringList: - return QVariant::fromValue(qvariant_cast(d).at(idx)); - case VariantList: - return qvariant_cast(d).at(idx); - case ListProperty: - return QVariant::fromValue(((const QQmlListReference *)d.constData())->at(idx)); - case Instance: - return d; - case Integer: - return QVariant(idx); - default: - case Invalid: - return QVariant(); - } -} - -bool QQmlListAccessor::isValid() const -{ - return m_type != Invalid; -} - -QT_END_NAMESPACE diff --git a/src/qml/util/qqmllistaccessor_p.h b/src/qml/util/qqmllistaccessor_p.h deleted file mode 100644 index bcd079adef..0000000000 --- a/src/qml/util/qqmllistaccessor_p.h +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLLISTACCESSOR_H -#define QQMLLISTACCESSOR_H - -// -// 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. -// - -#include - -QT_BEGIN_NAMESPACE - -class QQmlEngine; -class Q_AUTOTEST_EXPORT QQmlListAccessor -{ -public: - QQmlListAccessor(); - ~QQmlListAccessor(); - - QVariant list() const; - void setList(const QVariant &, QQmlEngine * = nullptr); - - bool isValid() const; - - int count() const; - QVariant at(int) const; - - enum Type { Invalid, StringList, VariantList, ListProperty, Instance, Integer }; - Type type() const { return m_type; } - -private: - Type m_type; - QVariant d; -}; - -QT_END_NAMESPACE - -#endif // QQMLLISTACCESSOR_H diff --git a/src/qml/util/qqmllistcompositor.cpp b/src/qml/util/qqmllistcompositor.cpp deleted file mode 100644 index 921e86f355..0000000000 --- a/src/qml/util/qqmllistcompositor.cpp +++ /dev/null @@ -1,1482 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "qqmllistcompositor_p.h" - -#include - -//#define QT_QML_VERIFY_MINIMAL -//#define QT_QML_VERIFY_INTEGRITY - -QT_BEGIN_NAMESPACE - -/*! - \class QQmlListCompositor - \brief The QQmlListCompositor class provides a lookup table for filtered, or re-ordered list - indexes. - \internal - - QQmlListCompositor is intended as an aid for developing proxy models. It doesn't however - directly proxy a list or model, instead a range of indexes from one or many lists can be - inserted into the compositor and then categorized and shuffled around and it will manage the - task of translating from an index in the combined space into an index in a particular list. - - Within a compositor indexes are categorized into groups where a group is a sub-set of the - total indexes referenced by the compositor, each with an address space ranging from 0 to - the number of indexes in the group - 1. Group memberships are independent of each other with - the one exception that items always retain the same order so if an index is moved within a - group, its position in other groups will change as well. - - The iterator classes encapsulate information about a specific position in a compositor group. - This includes a source list, the index of an item within that list and the groups that item - is a member of. The iterator for a specific position in a group can be retrieved with the - find() function and the addition and subtraction operators of the iterators can be used to - navigate to adjacent items in the same group. - - Items can be added to the compositor with the append() and insert() functions, group - membership can be changed with the setFlags() and clearFlags() functions, and the position - of items in the compositor can be changed with the move() function. Each of these functions - optionally returns a list of the changes made to indexes within each group which can then - be propagated to view so that it can correctly refresh its contents; e.g. 3 items - removed at index 6, and 5 items inserted at index 1. The notification changes are always - ordered from the start of the list to the end and accumulate, so if 5 items are removed at - index 4, one is skipped and then 3 move are removed, the changes returned are 5 items removed - at index 4, followed by 3 items removed at index 4. - - When the contents of a source list change, the mappings within the compositor can be updated - with the listItemsInserted(), listItemsRemoved(), listItemsMoved(), and listItemsChanged() - functions. Like the direct manipulation functions these too return a list of group indexes - affected by the change. If items are removed from a source list they are also removed from - any groups they belong to with the one exception being items belonging to the \l Cache group. - When items belonging to this group are removed the list, index, and other group membership - information are discarded but Cache membership is retained until explicitly removed. This - allows the cache index to be retained until cached resources for that item are actually - released. - - Internally the index mapping is stored as a list of Range objects, each has a list identifier, - a start index, a count, and a set of flags which represent group membership and some other - properties. The group index of a range is the sum of all preceding ranges that are members of - that group. To avoid the inefficiency of iterating over potentially all ranges when looking - for a specific index, each time a lookup is done the range and its indexes are cached and the - next lookup is done relative to this. This works out to near constant time in most relevant - use cases because successive index lookups are most frequently adjacent. The total number of - ranges is often quite small, which helps as well. If there is a need for faster random access - then a skip list like index may be an appropriate addition. - - \sa DelegateModel -*/ - -#ifdef QT_QML_VERIFY_MINIMAL -#define QT_QML_VERIFY_INTEGRITY -/* - Diagnostic to verify there are no consecutive ranges, or that the compositor contains the - most compact representation possible. - - Returns false and prints a warning if any range has a starting index equal to the end - (index + count) index of the previous range, and both ranges also have the same flags and list - property. - - If there are no consecutive ranges this will return true. -*/ - -static bool qt_verifyMinimal( - const QQmlListCompositor::iterator &begin, - const QQmlListCompositor::iterator &end) -{ - bool minimal = true; - int index = 0; - - for (const QQmlListCompositor::Range *range = begin->next; range != *end; range = range->next, ++index) { - if (range->previous->list == range->list - && range->previous->flags == (range->flags & ~QQmlListCompositor::AppendFlag) - && range->previous->end() == range->index) { - qWarning() << index << "Consecutive ranges"; - qWarning() << *range->previous; - qWarning() << *range; - minimal = false; - } - } - - return minimal; -} - -#endif - -#ifdef QT_QML_VERIFY_INTEGRITY -static bool qt_printInfo(const QQmlListCompositor &compositor) -{ - qWarning() << compositor; - return true; -} - -/* - Diagnostic to verify the integrity of a compositor. - - Per range this verifies there are no invalid range combinations, that non-append ranges have - positive non-zero counts, and that list ranges have non-negative indexes. - - Accumulatively this verifies that the cached total group counts match the sum of counts - of member ranges. -*/ - -static bool qt_verifyIntegrity( - const QQmlListCompositor::iterator &begin, - const QQmlListCompositor::iterator &end, - const QQmlListCompositor::iterator &cachedIt) -{ - bool valid = true; - - int index = 0; - QQmlListCompositor::iterator it; - for (it = begin; *it != *end; *it = it->next) { - if (it->count == 0 && !it->append()) { - qWarning() << index << "Empty non-append range"; - valid = false; - } - if (it->count < 0) { - qWarning() << index << "Negative count"; - valid = false; - } - if (it->list && it->flags != QQmlListCompositor::CacheFlag && it->index < 0) { - qWarning() << index <<"Negative index"; - valid = false; - } - if (it->previous->next != it.range) { - qWarning() << index << "broken list: it->previous->next != it.range"; - valid = false; - } - if (it->next->previous != it.range) { - qWarning() << index << "broken list: it->next->previous != it.range"; - valid = false; - } - if (*it == *cachedIt) { - for (int i = 0; i < end.groupCount; ++i) { - int groupIndex = it.index[i]; - if (cachedIt->flags & (1 << i)) - groupIndex += cachedIt.offset; - if (groupIndex != cachedIt.index[i]) { - qWarning() << index - << "invalid cached index" - << QQmlListCompositor::Group(i) - << "Expected:" - << groupIndex - << "Actual" - << cachedIt.index[i] - << cachedIt; - valid = false; - } - } - } - it.incrementIndexes(it->count); - ++index; - } - - for (int i = 0; i < end.groupCount; ++i) { - if (end.index[i] != it.index[i]) { - qWarning() << "Group" << i << "count invalid. Expected:" << end.index[i] << "Actual:" << it.index[i]; - valid = false; - } - } - return valid; -} -#endif - -#if defined(QT_QML_VERIFY_MINIMAL) -# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!(qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \ - && qt_verifyMinimal(iterator(m_ranges.next, 0, Default, m_groupCount), m_end)) \ - && qt_printInfo(*this))); -#elif defined(QT_QML_VERIFY_INTEGRITY) -# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \ - && qt_printInfo(*this))); -#else -# define QT_QML_VERIFY_LISTCOMPOSITOR -#endif - -//#define QT_QML_TRACE_LISTCOMPOSITOR(args) qDebug() << m_end.index[1] << m_end.index[0] << Q_FUNC_INFO args; -#define QT_QML_TRACE_LISTCOMPOSITOR(args) - -QQmlListCompositor::iterator &QQmlListCompositor::iterator::operator +=(int difference) -{ - // Update all indexes to the start of the range. - decrementIndexes(offset); - - // If the iterator group isn't a member of the current range ignore the current offset. - if (!(range->flags & groupFlag)) - offset = 0; - - offset += difference; - - // Iterate backwards looking for a range with a positive offset. - while (offset <= 0 && range->previous->flags) { - range = range->previous; - if (range->flags & groupFlag) - offset += range->count; - decrementIndexes(range->count); - } - - // Iterate forwards looking for the first range which contains both the offset and the - // iterator group. - while (range->flags && (offset >= range->count || !(range->flags & groupFlag))) { - if (range->flags & groupFlag) - offset -= range->count; - incrementIndexes(range->count); - range = range->next; - } - - // Update all the indexes to inclue the remaining offset. - incrementIndexes(offset); - - return *this; -} - -QQmlListCompositor::insert_iterator &QQmlListCompositor::insert_iterator::operator +=(int difference) -{ - iterator::operator +=(difference); - - // If the previous range contains the append flag move the iterator to the tail of the previous - // range so that appended appear after the insert position. - if (offset == 0 && range->previous->append()) { - range = range->previous; - offset = range->inGroup() ? range->count : 0; - } - - return *this; -} - - -/*! - Constructs an empty list compositor. -*/ - -QQmlListCompositor::QQmlListCompositor() - : m_end(m_ranges.next, 0, Default, 2) - , m_cacheIt(m_end) - , m_groupCount(2) - , m_defaultFlags(PrependFlag | DefaultFlag) - , m_removeFlags(AppendFlag | PrependFlag | GroupMask) - , m_moveId(0) -{ -} - -/*! - Destroys a list compositor. -*/ - -QQmlListCompositor::~QQmlListCompositor() -{ - for (Range *next, *range = m_ranges.next; range != &m_ranges; range = next) { - next = range->next; - delete range; - } -} - -/*! - Inserts a range with the given source \a list, start \a index, \a count and \a flags, in front - of the existing range \a before. -*/ - -inline QQmlListCompositor::Range *QQmlListCompositor::insert( - Range *before, void *list, int index, int count, uint flags) -{ - return new Range(before, list, index, count, flags); -} - -/*! - Erases a \a range from the compositor. - - Returns a pointer to the next range in the compositor. -*/ - -inline QQmlListCompositor::Range *QQmlListCompositor::erase( - Range *range) -{ - Range *next = range->next; - next->previous = range->previous; - next->previous->next = range->next; - delete range; - return next; -} - -/*! - Sets the number (\a count) of possible groups that items may belong to in a compositor. -*/ - -void QQmlListCompositor::setGroupCount(int count) -{ - m_groupCount = count; - m_end = iterator(&m_ranges, 0, Default, m_groupCount); - m_cacheIt = m_end; -} - -/*! - Returns the number of items that belong to a \a group. -*/ - -int QQmlListCompositor::count(Group group) const -{ - return m_end.index[group]; -} - -/*! - Returns an iterator representing the item at \a index in a \a group. - - The index must be between 0 and count(group) - 1. -*/ - -QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< group << index) - Q_ASSERT(index >=0 && index < count(group)); - if (m_cacheIt == m_end) { - m_cacheIt = iterator(m_ranges.next, 0, group, m_groupCount); - m_cacheIt += index; - } else { - const int offset = index - m_cacheIt.index[group]; - m_cacheIt.setGroup(group); - m_cacheIt += offset; - } - Q_ASSERT(m_cacheIt.index[group] == index); - Q_ASSERT(m_cacheIt->inGroup(group)); - QT_QML_VERIFY_LISTCOMPOSITOR - return m_cacheIt; -} - -/*! - Returns an iterator representing the item at \a index in a \a group. - - The index must be between 0 and count(group) - 1. -*/ - -QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) const -{ - return const_cast(this)->find(group, index); -} - -/*! - Returns an iterator representing an insert position in front of the item at \a index in a - \a group. - - The iterator for an insert position can sometimes resolve to a different Range than a regular - iterator. This is because when items are inserted on a boundary between Ranges, if the first - range has the Append flag set then the items should be inserted into that range to ensure - that the append position for the existing range remains after the insert position. - - The index must be between 0 and count(group) - 1. -*/ - -QQmlListCompositor::insert_iterator QQmlListCompositor::findInsertPosition(Group group, int index) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< group << index) - Q_ASSERT(index >=0 && index <= count(group)); - insert_iterator it; - if (m_cacheIt == m_end) { - it = iterator(m_ranges.next, 0, group, m_groupCount); - it += index; - } else { - const int offset = index - m_cacheIt.index[group]; - it = m_cacheIt; - it.setGroup(group); - it += offset; - } - Q_ASSERT(it.index[group] == index); - return it; -} - -/*! - Appends a range of \a count indexes starting at \a index from a \a list into a compositor - with the given \a flags. - - If supplied the \a inserts list will be populated with the positions of the inserted items - in each group. -*/ - -void QQmlListCompositor::append( - void *list, int index, int count, uint flags, QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count << flags) - insert(m_end, list, index, count, flags, inserts); -} - -/*! - Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags - into a \a group at index \a before. - - If supplied the \a inserts list will be populated with the positions of items inserted into - each group. -*/ - -void QQmlListCompositor::insert( - Group group, int before, void *list, int index, int count, uint flags, QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< group << before << list << index << count << flags) - insert(findInsertPosition(group, before), list, index, count, flags, inserts); -} - -/*! - Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags - into a compositor at position \a before. - - If supplied the \a inserts list will be populated with the positions of items inserted into - each group. -*/ - -QQmlListCompositor::iterator QQmlListCompositor::insert( - iterator before, void *list, int index, int count, uint flags, QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< before << list << index << count << flags) - if (inserts) { - inserts->append(Insert(before, count, flags & GroupMask)); - } - if (before.offset > 0) { - // Inserting into the middle of a range. Split it two and update the iterator so it's - // positioned at the start of the second half. - *before = insert( - *before, before->list, before->index, before.offset, before->flags & ~AppendFlag)->next; - before->index += before.offset; - before->count -= before.offset; - before.offset = 0; - } - - - if (!(flags & AppendFlag) && *before != m_ranges.next - && before->previous->list == list - && before->previous->flags == flags - && (!list || before->previous->end() == index)) { - // The insert arguments represent a continuation of the previous range so increment - // its count instead of inserting a new range. - before->previous->count += count; - before.incrementIndexes(count, flags); - } else { - *before = insert(*before, list, index, count, flags); - before.offset = 0; - } - - if (!(flags & AppendFlag) && before->next != &m_ranges - && before->list == before->next->list - && before->flags == before->next->flags - && (!list || before->end() == before->next->index)) { - // The current range and the next are continuous so add their counts and delete one. - before->next->index = before->index; - before->next->count += before->count; - *before = erase(*before); - } - - m_end.incrementIndexes(count, flags); - m_cacheIt = before; - QT_QML_VERIFY_LISTCOMPOSITOR - return before; -} - -/*! - Sets the given flags \a flags on \a count items belonging to \a group starting at the position - identified by \a fromGroup and the index \a from. - - If supplied the \a inserts list will be populated with insert notifications for affected groups. -*/ - -void QQmlListCompositor::setFlags( - Group fromGroup, int from, int count, Group group, int flags, QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags) - setFlags(find(fromGroup, from), count, group, flags, inserts); -} - -/*! - Sets the given flags \a flags on \a count items belonging to \a group starting at the position - \a from. - - If supplied the \a inserts list will be populated with insert notifications for affected groups. -*/ - -void QQmlListCompositor::setFlags( - iterator from, int count, Group group, uint flags, QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags) - if (!flags || !count) - return; - - if (from != group) { - // Skip to the next full range if the start one is not a member of the target group. - from.incrementIndexes(from->count - from.offset); - from.offset = 0; - *from = from->next; - } else if (from.offset > 0) { - // If the start position is mid range split off the portion unaffected. - *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next; - from->index += from.offset; - from->count -= from.offset; - from.offset = 0; - } - - for (; count > 0; *from = from->next) { - if (from != from.group) { - // Skip ranges that are not members of the target group. - from.incrementIndexes(from->count); - continue; - } - // Find the number of items affected in the current range. - const int difference = qMin(count, from->count); - count -= difference; - - // Determine the actual changes made to the range and increment counts accordingly. - const uint insertFlags = ~from->flags & flags; - const uint setFlags = (from->flags | flags) & ~AppendFlag; - if (insertFlags && inserts) - inserts->append(Insert(from, difference, insertFlags | (from->flags & CacheFlag))); - m_end.incrementIndexes(difference, insertFlags); - from.incrementIndexes(difference, setFlags); - - if (from->previous != &m_ranges - && from->previous->list == from->list - && (!from->list || from->previous->end() == from->index) - && from->previous->flags == setFlags) { - // If the additional flags make the current range a continuation of the previous - // then move the affected items over to the previous range. - from->previous->count += difference; - from->index += difference; - from->count -= difference; - if (from->count == 0) { - // Delete the current range if it is now empty, preserving the append flag - // in the previous range. - if (from->append()) - from->previous->flags |= AppendFlag; - *from = erase(*from)->previous; - continue; - } else { - break; - } - } else if (!insertFlags) { - // No new flags, so roll onto the next range. - from.incrementIndexes(from->count - difference); - continue; - } else if (difference < from->count) { - // Create a new range with the updated flags, and remove the affected items - // from the current range. - *from = insert(*from, from->list, from->index, difference, setFlags)->next; - from->index += difference; - from->count -= difference; - } else { - // The whole range is affected so simply update the flags. - from->flags |= flags; - continue; - } - from.incrementIndexes(from->count); - } - - if (from->previous != &m_ranges - && from->previous->list == from->list - && (!from->list || from->previous->end() == from->index) - && from->previous->flags == (from->flags & ~AppendFlag)) { - // If the following range is now a continuation, merge it with its previous range. - from.offset = from->previous->count; - from->previous->count += from->count; - from->previous->flags = from->flags; - *from = erase(*from)->previous; - } - m_cacheIt = from; - QT_QML_VERIFY_LISTCOMPOSITOR -} - -/*! - Clears the given flags \a flags on \a count items belonging to \a group starting at the position - \a from. - - If supplied the \a removes list will be populated with remove notifications for affected groups. -*/ - -void QQmlListCompositor::clearFlags( - Group fromGroup, int from, int count, Group group, uint flags, QVector *removes) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags) - clearFlags(find(fromGroup, from), count, group, flags, removes); -} - -/*! - Clears the given flags \a flags on \a count items belonging to \a group starting at the position - identified by \a fromGroup and the index \a from. - - If supplied the \a removes list will be populated with remove notifications for affected groups. -*/ - -void QQmlListCompositor::clearFlags( - iterator from, int count, Group group, uint flags, QVector *removes) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags) - if (!flags || !count) - return; - - const bool clearCache = flags & CacheFlag; - - if (from != group) { - // Skip to the next full range if the start one is not a member of the target group. - from.incrementIndexes(from->count - from.offset); - from.offset = 0; - *from = from->next; - } else if (from.offset > 0) { - // If the start position is mid range split off the portion unaffected. - *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next; - from->index += from.offset; - from->count -= from.offset; - from.offset = 0; - } - - for (; count > 0; *from = from->next) { - if (from != group) { - // Skip ranges that are not members of the target group. - from.incrementIndexes(from->count); - continue; - } - // Find the number of items affected in the current range. - const int difference = qMin(count, from->count); - count -= difference; - - - // Determine the actual changes made to the range and decrement counts accordingly. - const uint removeFlags = from->flags & flags & ~(AppendFlag | PrependFlag); - const uint clearedFlags = from->flags & ~(flags | AppendFlag | UnresolvedFlag); - if (removeFlags && removes) { - const int maskedFlags = clearCache - ? (removeFlags & ~CacheFlag) - : (removeFlags | (from->flags & CacheFlag)); - if (maskedFlags) - removes->append(Remove(from, difference, maskedFlags)); - } - m_end.decrementIndexes(difference, removeFlags); - from.incrementIndexes(difference, clearedFlags); - - if (from->previous != &m_ranges - && from->previous->list == from->list - && (!from->list || clearedFlags == CacheFlag || from->previous->end() == from->index) - && from->previous->flags == clearedFlags) { - // If the removed flags make the current range a continuation of the previous - // then move the affected items over to the previous range. - from->previous->count += difference; - from->index += difference; - from->count -= difference; - if (from->count == 0) { - // Delete the current range if it is now empty, preserving the append flag - if (from->append()) - from->previous->flags |= AppendFlag; - *from = erase(*from)->previous; - } else { - from.incrementIndexes(from->count); - } - } else if (difference < from->count) { - // Create a new range with the reduced flags, and remove the affected items from - // the current range. - if (clearedFlags) - *from = insert(*from, from->list, from->index, difference, clearedFlags)->next; - from->index += difference; - from->count -= difference; - from.incrementIndexes(from->count); - } else if (clearedFlags) { - // The whole range is affected so simply update the flags. - from->flags &= ~flags; - } else { - // All flags have been removed from the range so remove it. - *from = erase(*from)->previous; - } - } - - if (*from != &m_ranges && from->previous != &m_ranges - && from->previous->list == from->list - && (!from->list || from->previous->end() == from->index) - && from->previous->flags == (from->flags & ~AppendFlag)) { - // If the following range is now a continuation, merge it with its previous range. - from.offset = from->previous->count; - from->previous->count += from->count; - from->previous->flags = from->flags; - *from = erase(*from)->previous; - } - m_cacheIt = from; - QT_QML_VERIFY_LISTCOMPOSITOR -} - -bool QQmlListCompositor::verifyMoveTo( - Group fromGroup, int from, Group toGroup, int to, int count, Group group) const -{ - if (group != toGroup) { - // determine how many items from the destination group intersect with the source group. - iterator fromIt = find(fromGroup, from); - - int intersectingCount = 0; - - for (; count > 0; *fromIt = fromIt->next) { - if (*fromIt == &m_ranges) - return false; - if (!fromIt->inGroup(group)) - continue; - if (fromIt->inGroup(toGroup)) - intersectingCount += qMin(count, fromIt->count - fromIt.offset); - count -= fromIt->count - fromIt.offset; - fromIt.offset = 0; - } - count = intersectingCount; - } - - return to >= 0 && to + count <= m_end.index[toGroup]; -} - -/*! - \internal - - Moves \a count items belonging to \a moveGroup from the index \a from in \a fromGroup - to the index \a to in \a toGroup. - - If \a removes and \a inserts are not null they will be populated with per group notifications - of the items moved. - */ - -void QQmlListCompositor::move( - Group fromGroup, - int from, - Group toGroup, - int to, - int count, - Group moveGroup, - QVector *removes, - QVector *inserts) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << toGroup << to << count) - Q_ASSERT(count > 0); - Q_ASSERT(from >=0); - Q_ASSERT(verifyMoveTo(fromGroup, from, toGroup, to, count, moveGroup)); - - // Find the position of the first item to move. - iterator fromIt = find(fromGroup, from); - - if (fromIt != moveGroup) { - // If the range at the from index doesn't contain items from the move group; skip - // to the next range. - fromIt.incrementIndexes(fromIt->count - fromIt.offset); - fromIt.offset = 0; - *fromIt = fromIt->next; - } else if (fromIt.offset > 0) { - // If the range at the from index contains items from the move group and the index isn't - // at the start of the range; split the range at the index and move the iterator to start - // of the second range. - *fromIt = insert( - *fromIt, fromIt->list, fromIt->index, fromIt.offset, fromIt->flags & ~AppendFlag)->next; - fromIt->index += fromIt.offset; - fromIt->count -= fromIt.offset; - fromIt.offset = 0; - } - - // Remove count items belonging to the move group from the list. - Range movedFlags; - for (int moveId = m_moveId; count > 0;) { - if (fromIt != moveGroup) { - // Skip ranges not containing items from the move group. - fromIt.incrementIndexes(fromIt->count); - *fromIt = fromIt->next; - continue; - } - int difference = qMin(count, fromIt->count); - - // Create a new static range containing the moved items from an existing range. - new Range( - &movedFlags, - fromIt->list, - fromIt->index, - difference, - fromIt->flags & ~(PrependFlag | AppendFlag)); - // Remove moved items from the count, the existing range, and a remove notification. - if (removes) - removes->append(Remove(fromIt, difference, fromIt->flags, ++moveId)); - count -= difference; - fromIt->count -= difference; - - // If the existing range contains the prepend flag replace the removed items with - // a placeholder range for new items inserted into the source model. - int removeIndex = fromIt->index; - if (fromIt->prepend() - && fromIt->previous != &m_ranges - && fromIt->previous->flags == PrependFlag - && fromIt->previous->list == fromIt->list - && fromIt->previous->end() == fromIt->index) { - // Grow the previous range instead of creating a new one if possible. - fromIt->previous->count += difference; - } else if (fromIt->prepend()) { - *fromIt = insert(*fromIt, fromIt->list, removeIndex, difference, PrependFlag)->next; - } - fromIt->index += difference; - - if (fromIt->count == 0) { - // If the existing range has no items remaining; remove it from the list. - if (fromIt->append()) - fromIt->previous->flags |= AppendFlag; - *fromIt = erase(*fromIt); - - // If the ranges before and after the removed range can be joined, do so. - if (*fromIt != m_ranges.next && fromIt->flags == PrependFlag - && fromIt->previous != &m_ranges - && fromIt->previous->flags == PrependFlag - && fromIt->previous->list == fromIt->list - && fromIt->previous->end() == fromIt->index) { - fromIt.incrementIndexes(fromIt->count); - fromIt->previous->count += fromIt->count; - *fromIt = erase(*fromIt); - } - } else if (count > 0) { - *fromIt = fromIt->next; - } - } - - // Try and join the range following the removed items to the range preceding it. - if (*fromIt != m_ranges.next - && *fromIt != &m_ranges - && fromIt->previous->list == fromIt->list - && (!fromIt->list || fromIt->previous->end() == fromIt->index) - && fromIt->previous->flags == (fromIt->flags & ~AppendFlag)) { - if (fromIt == fromIt.group) - fromIt.offset = fromIt->previous->count; - fromIt.offset = fromIt->previous->count; - fromIt->previous->count += fromIt->count; - fromIt->previous->flags = fromIt->flags; - *fromIt = erase(*fromIt)->previous; - } - - // Find the destination position of the move. - insert_iterator toIt = fromIt; - toIt.setGroup(toGroup); - - const int difference = to - toIt.index[toGroup]; - toIt += difference; - - // If the insert position is part way through a range; split it and move the iterator to the - // start of the second range. - if (toIt.offset > 0) { - *toIt = insert(*toIt, toIt->list, toIt->index, toIt.offset, toIt->flags & ~AppendFlag)->next; - toIt->index += toIt.offset; - toIt->count -= toIt.offset; - toIt.offset = 0; - } - - // Insert the moved ranges before the insert iterator, growing the previous range if that - // is an option. - for (Range *range = movedFlags.previous; range != &movedFlags; range = range->previous) { - if (*toIt != &m_ranges - && range->list == toIt->list - && (!range->list || range->end() == toIt->index) - && range->flags == (toIt->flags & ~AppendFlag)) { - toIt->index -= range->count; - toIt->count += range->count; - } else { - *toIt = insert(*toIt, range->list, range->index, range->count, range->flags); - } - } - - // Try and join the range after the inserted ranges to the last range inserted. - if (*toIt != m_ranges.next - && toIt->previous->list == toIt->list - && (!toIt->list || (toIt->previous->end() == toIt->index && toIt->previous->flags == (toIt->flags & ~AppendFlag)))) { - toIt.offset = toIt->previous->count; - toIt->previous->count += toIt->count; - toIt->previous->flags = toIt->flags; - *toIt = erase(*toIt)->previous; - } - // Create insert notification for the ranges moved. - Insert insert(toIt, 0, 0, 0); - for (Range *next, *range = movedFlags.next; range != &movedFlags; range = next) { - insert.count = range->count; - insert.flags = range->flags; - if (inserts) { - insert.moveId = ++m_moveId; - inserts->append(insert); - } - for (int i = 0; i < m_groupCount; ++i) { - if (insert.inGroup(i)) - insert.index[i] += range->count; - } - - next = range->next; - delete range; - } - - m_cacheIt = toIt; - - QT_QML_VERIFY_LISTCOMPOSITOR -} - -/*! - Clears the contents of a compositor. -*/ - -void QQmlListCompositor::clear() -{ - QT_QML_TRACE_LISTCOMPOSITOR("") - for (Range *range = m_ranges.next; range != &m_ranges; range = erase(range)) {} - m_end = iterator(m_ranges.next, 0, Default, m_groupCount); - m_cacheIt = m_end; -} - -void QQmlListCompositor::listItemsInserted( - QVector *translatedInsertions, - void *list, - const QVector &insertions, - const QVector *movedFlags) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << insertions) - for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { - if (it->list != list || it->flags == CacheFlag) { - // Skip ranges that don't reference list. - it.incrementIndexes(it->count); - continue; - } else if (it->flags & MovedFlag) { - // Skip ranges that were already moved in listItemsRemoved. - it->flags &= ~MovedFlag; - it.incrementIndexes(it->count); - continue; - } - for (const QQmlChangeSet::Change &insertion : insertions) { - int offset = insertion.index - it->index; - if ((offset > 0 && offset < it->count) - || (offset == 0 && it->prepend()) - || (offset == it->count && it->append())) { - // The insert index is within the current range. - if (it->prepend()) { - // The range has the prepend flag set so we insert new items into the range. - uint flags = m_defaultFlags; - if (insertion.isMove()) { - // If the insert was part of a move replace the default flags with - // the flags from the source range. - for (QVector::const_iterator move = movedFlags->begin(); - move != movedFlags->end(); - ++move) { - if (move->moveId == insertion.moveId) { - flags = move->flags; - break; - } - } - } - if (flags & ~(AppendFlag | PrependFlag)) { - // If any items are added to groups append an insert notification. - Insert translatedInsert(it, insertion.count, flags, insertion.moveId); - for (int i = 0; i < m_groupCount; ++i) { - if (it->inGroup(i)) - translatedInsert.index[i] += offset; - } - translatedInsertions->append(translatedInsert); - } - if ((it->flags & ~AppendFlag) == flags) { - // Accumulate items on the current range it its flags are the same as - // the insert flags. - it->count += insertion.count; - } else if (offset == 0 - && it->previous != &m_ranges - && it->previous->list == list - && it->previous->end() == insertion.index - && it->previous->flags == flags) { - // Attempt to append to the previous range if the insert position is at - // the start of the current range. - it->previous->count += insertion.count; - it->index += insertion.count; - it.incrementIndexes(insertion.count); - } else { - if (offset > 0) { - // Divide the current range at the insert position. - it.incrementIndexes(offset); - *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; - } - // Insert a new range, and increment the start index of the current range. - *it = insert(*it, it->list, insertion.index, insertion.count, flags)->next; - it.incrementIndexes(insertion.count, flags); - it->index += offset + insertion.count; - it->count -= offset; - } - m_end.incrementIndexes(insertion.count, flags); - } else { - // The range doesn't have the prepend flag set so split the range into parts; - // one before the insert position and one after the last inserted item. - if (offset > 0) { - *it = insert(*it, it->list, it->index, offset, it->flags)->next; - it->index += offset; - it->count -= offset; - } - it->index += insertion.count; - } - } else if (offset <= 0) { - // The insert position was before the current range so increment the start index. - it->index += insertion.count; - } - } - it.incrementIndexes(it->count); - } - m_cacheIt = m_end; - QT_QML_VERIFY_LISTCOMPOSITOR -} - -/*! - Updates the contents of a compositor when \a count items are inserted into a \a list at - \a index. - - This corrects the indexes of each range for that list in the compositor, splitting the range - in two if the insert index is in the middle of that range. If a range at the insert position - has the Prepend flag set then a new range will be inserted at that position with the groups - specified in defaultGroups(). If the insert index corresponds to the end of a range with - the Append flag set a new range will be inserted before the end of the append range. - - The \a translatedInsertions list is populated with insert notifications for affected - groups. -*/ - -void QQmlListCompositor::listItemsInserted( - void *list, int index, int count, QVector *translatedInsertions) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) - Q_ASSERT(count > 0); - - QVector insertions; - insertions.append(QQmlChangeSet::Change(index, count)); - - listItemsInserted(translatedInsertions, list, insertions); -} - -void QQmlListCompositor::listItemsRemoved( - QVector *translatedRemovals, - void *list, - QVector *removals, - QVector *insertions, - QVector *movedFlags) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << *removals) - - for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { - if (it->list != list || it->flags == CacheFlag) { - // Skip ranges that don't reference list. - it.incrementIndexes(it->count); - continue; - } - bool removed = false; - for (QVector::iterator removal = removals->begin(); - !removed && removal != removals->end(); - ++removal) { - int relativeIndex = removal->index - it->index; - int itemsRemoved = removal->count; - if (relativeIndex + removal->count > 0 && relativeIndex < it->count) { - // If the current range intersects the remove; remove the intersecting items. - const int offset = qMax(0, relativeIndex); - int removeCount = qMin(it->count, relativeIndex + removal->count) - offset; - it->count -= removeCount; - int removeFlags = it->flags & m_removeFlags; - Remove translatedRemoval(it, removeCount, it->flags); - for (int i = 0; i < m_groupCount; ++i) { - if (it->inGroup(i)) - translatedRemoval.index[i] += offset; - } - if (removal->isMove()) { - // If the removal was part of a move find the corresponding insert. - QVector::iterator insertion = insertions->begin(); - for (; insertion != insertions->end() && insertion->moveId != removal->moveId; - ++insertion) {} - Q_ASSERT(insertion != insertions->end()); - Q_ASSERT(insertion->count == removal->count); - - if (relativeIndex < 0) { - // If the remove started before the current range, split it and the - // corresponding insert so we're only working with intersecting part. - int splitMoveId = ++m_moveId; - removal = removals->insert(removal, QQmlChangeSet::Change( - removal->index, -relativeIndex, splitMoveId)); - ++removal; - removal->count -= -relativeIndex; - insertion = insertions->insert(insertion, QQmlChangeSet::Change( - insertion->index, -relativeIndex, splitMoveId)); - ++insertion; - insertion->index += -relativeIndex; - insertion->count -= -relativeIndex; - } - - if (it->prepend()) { - // If the current range has the prepend flag preserve its flags to transfer - // to its new location. - removeFlags |= it->flags & CacheFlag; - translatedRemoval.moveId = ++m_moveId; - movedFlags->append(MovedFlags(m_moveId, it->flags & ~AppendFlag)); - - if (removeCount < removal->count) { - // If the remove doesn't encompass all of the current range, - // split it and the corresponding insert. - removal = removals->insert(removal, QQmlChangeSet::Change( - removal->index, removeCount, translatedRemoval.moveId)); - ++removal; - insertion = insertions->insert(insertion, QQmlChangeSet::Change( - insertion->index, removeCount, translatedRemoval.moveId)); - ++insertion; - - removal->count -= removeCount; - insertion->index += removeCount; - insertion->count -= removeCount; - } else { - // If there's no need to split the move simply replace its moveId - // with that of the translated move. - removal->moveId = translatedRemoval.moveId; - insertion->moveId = translatedRemoval.moveId; - } - } else { - // If the current range doesn't have prepend flags then insert a new range - // with list indexes from the corresponding insert location. The MoveFlag - // is to notify listItemsInserted that it can skip evaluation of that range. - if (offset > 0) { - *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; - it->index += offset; - it->count -= offset; - it.incrementIndexes(offset); - } - if (it->previous != &m_ranges - && it->previous->list == it->list - && it->end() == insertion->index - && it->previous->flags == (it->flags | MovedFlag)) { - it->previous->count += removeCount; - } else { - *it = insert(*it, it->list, insertion->index, removeCount, it->flags | MovedFlag)->next; - } - // Clear the changed flags as the item hasn't been removed. - translatedRemoval.flags = 0; - removeFlags = 0; - } - } else if (it->inCache()) { - // If not moving and the current range has the cache flag, insert a new range - // with just the cache flag set to retain caching information for the removed - // range. - if (offset > 0) { - *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; - it->index += offset; - it->count -= offset; - it.incrementIndexes(offset); - } - if (it->previous != &m_ranges - && it->previous->list == it->list - && it->previous->flags == CacheFlag) { - it->previous->count += removeCount; - } else { - *it = insert(*it, it->list, -1, removeCount, CacheFlag)->next; - } - it.index[Cache] += removeCount; - } - if (removeFlags & GroupMask) - translatedRemovals->append(translatedRemoval); - m_end.decrementIndexes(removeCount, removeFlags); - if (it->count == 0 && !it->append()) { - // Erase empty non-append ranges. - *it = erase(*it)->previous; - removed = true; - } else if (relativeIndex <= 0) { - // If the remove started before the current range move the start index of - // the range to the remove index. - it->index = removal->index; - } - } else if (relativeIndex < 0) { - // If the remove was before the current range decrement the start index by the - // number of items removed. - it->index -= itemsRemoved; - - if (it->previous != &m_ranges - && it->previous->list == it->list - && it->previous->end() == it->index - && it->previous->flags == (it->flags & ~AppendFlag)) { - // Compress ranges made continuous by the removal of separating ranges. - it.decrementIndexes(it->previous->count); - it->previous->count += it->count; - it->previous->flags = it->flags; - *it = erase(*it)->previous; - } - } - } - if (it->flags == CacheFlag && it->next->flags == CacheFlag && it->next->list == it->list) { - // Compress consecutive cache only ranges. - it.index[Cache] += it->next->count; - it->count += it->next->count; - erase(it->next); - } else if (!removed) { - it.incrementIndexes(it->count); - } - } - m_cacheIt = m_end; - QT_QML_VERIFY_LISTCOMPOSITOR -} - - -/*! - Updates the contents of a compositor when \a count items are removed from a \a list at - \a index. - - Ranges that intersect the specified range are removed from the compositor and the indexes of - ranges after index + count are updated. - - The \a translatedRemovals list is populated with remove notifications for the affected - groups. -*/ - - -void QQmlListCompositor::listItemsRemoved( - void *list, int index, int count, QVector *translatedRemovals) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) - Q_ASSERT(count >= 0); - - QVector removals; - removals.append(QQmlChangeSet::Change(index, count)); - listItemsRemoved(translatedRemovals, list, &removals, nullptr, nullptr); -} - -/*! - Updates the contents of a compositor when \a count items are removed from a \a list at - \a index. - - Ranges that intersect the specified range are removed from the compositor and the indexes of - ranges after index + count are updated. - - The \a translatedRemovals and translatedInserts lists are populated with move - notifications for the affected groups. -*/ - -void QQmlListCompositor::listItemsMoved( - void *list, - int from, - int to, - int count, - QVector *translatedRemovals, - QVector *translatedInsertions) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << from << to << count) - Q_ASSERT(count >= 0); - - QVector removals; - QVector insertions; - QVector movedFlags; - removals.append(QQmlChangeSet::Change(from, count, 0)); - insertions.append(QQmlChangeSet::Change(to, count, 0)); - - listItemsRemoved(translatedRemovals, list, &removals, &insertions, &movedFlags); - listItemsInserted(translatedInsertions, list, insertions, &movedFlags); -} - -void QQmlListCompositor::listItemsChanged( - QVector *translatedChanges, - void *list, - const QVector &changes) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << changes) - for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { - if (it->list != list || it->flags == CacheFlag) { - it.incrementIndexes(it->count); - continue; - } else if (!it->inGroup()) { - continue; - } - for (const QQmlChangeSet::Change &change : changes) { - const int offset = change.index - it->index; - if (offset + change.count > 0 && offset < it->count) { - const int changeOffset = qMax(0, offset); - const int changeCount = qMin(it->count, offset + change.count) - changeOffset; - - Change translatedChange(it, changeCount, it->flags); - for (int i = 0; i < m_groupCount; ++i) { - if (it->inGroup(i)) - translatedChange.index[i] += changeOffset; - } - translatedChanges->append(translatedChange); - } - } - it.incrementIndexes(it->count); - } -} - - -/*! - Translates the positions of \a count changed items at \a index in a \a list. - - The \a translatedChanges list is populated with change notifications for the - affected groups. -*/ - -void QQmlListCompositor::listItemsChanged( - void *list, int index, int count, QVector *translatedChanges) -{ - QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) - Q_ASSERT(count >= 0); - QVector changes; - changes.append(QQmlChangeSet::Change(index, count)); - listItemsChanged(translatedChanges, list, changes); -} - -void QQmlListCompositor::transition( - Group from, - Group to, - QVector *removes, - QVector *inserts) -{ - int removeCount = 0; - for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { - if (it == from && it != to) { - removes->append(QQmlChangeSet::Change(it.index[from]- removeCount, it->count)); - removeCount += it->count; - } else if (it != from && it == to) { - inserts->append(QQmlChangeSet::Change(it.index[to], it->count)); - } - it.incrementIndexes(it->count); - } -} - -/*! - \internal - Writes the name of \a group to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group) -{ - switch (group) { - case QQmlListCompositor::Cache: return debug << "Cache"; - case QQmlListCompositor::Default: return debug << "Default"; - default: return (debug.nospace() << "Group" << int(group)).space(); - } - -} - -/*! - \internal - Writes the contents of \a range to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range) -{ - (debug.nospace() - << "Range(" - << range.list) << ' ' - << range.index << ' ' - << range.count << ' ' - << (range.isUnresolved() ? 'U' : '0') - << (range.append() ? 'A' : '0') - << (range.prepend() ? 'P' : '0'); - for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i) - debug << (range.inGroup(i) ? '1' : '0'); - return (debug - << (range.inGroup(QQmlListCompositor::Default) ? 'D' : '0') - << (range.inGroup(QQmlListCompositor::Cache) ? 'C' : '0')); -} - -static void qt_print_indexes(QDebug &debug, int count, const int *indexes) -{ - for (int i = count - 1; i >= 0; --i) - debug << indexes[i]; -} - -/*! - \internal - Writes the contents of \a it to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it) -{ - (debug.nospace() << "iterator(" << it.group).space() << "offset:" << it.offset; - qt_print_indexes(debug, it.groupCount, it.index); - return ((debug << **it).nospace() << ')').space(); -} - -static QDebug qt_print_change(QDebug debug, const char *name, const QQmlListCompositor::Change &change) -{ - debug.nospace() << name << '(' << change.moveId << ' ' << change.count << ' '; - for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i) - debug << (change.inGroup(i) ? '1' : '0'); - debug << (change.inGroup(QQmlListCompositor::Default) ? 'D' : '0') - << (change.inGroup(QQmlListCompositor::Cache) ? 'C' : '0'); - int i = QQmlListCompositor::MaximumGroupCount - 1; - for (; i >= 0 && !change.inGroup(i); --i) {} - for (; i >= 0; --i) - debug << ' ' << change.index[i]; - return (debug << ')').maybeSpace(); -} - -/*! - \internal - Writes the contents of \a change to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change) -{ - return qt_print_change(debug, "Change", change); -} - -/*! - \internal - Writes the contents of \a remove to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove) -{ - return qt_print_change(debug, "Remove", remove); -} - -/*! - \internal - Writes the contents of \a insert to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert) -{ - return qt_print_change(debug, "Insert", insert); -} - -/*! - \internal - Writes the contents of \a list to \a debug. -*/ - -QDebug operator <<(QDebug debug, const QQmlListCompositor &list) -{ - int indexes[QQmlListCompositor::MaximumGroupCount]; - for (int i = 0; i < QQmlListCompositor::MaximumGroupCount; ++i) - indexes[i] = 0; - debug.nospace() << "QQmlListCompositor("; - qt_print_indexes(debug, list.m_groupCount, list.m_end.index); - for (QQmlListCompositor::Range *range = list.m_ranges.next; range != &list.m_ranges; range = range->next) { - (debug << '\n').space(); - qt_print_indexes(debug, list.m_groupCount, indexes); - debug << ' ' << *range; - - for (int i = 0; i < list.m_groupCount; ++i) { - if (range->inGroup(i)) - indexes[i] += range->count; - } - } - return (debug << ')').maybeSpace(); -} - -QT_END_NAMESPACE diff --git a/src/qml/util/qqmllistcompositor_p.h b/src/qml/util/qqmllistcompositor_p.h deleted file mode 100644 index 172040559c..0000000000 --- a/src/qml/util/qqmllistcompositor_p.h +++ /dev/null @@ -1,372 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQMLLISTCOMPOSITOR_P_H -#define QQMLLISTCOMPOSITOR_P_H - -// -// 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. -// - -#include -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -class Q_AUTOTEST_EXPORT QQmlListCompositor -{ -public: - enum { MinimumGroupCount = 3, MaximumGroupCount = 11 }; - - enum Group - { - Cache = 0, - Default = 1, - Persisted = 2 - }; - - enum Flag - { - CacheFlag = 1 << Cache, - DefaultFlag = 1 << Default, - PersistedFlag = 1 << Persisted, - PrependFlag = 0x10000000, - AppendFlag = 0x20000000, - UnresolvedFlag = 0x40000000, - MovedFlag = 0x80000000, - GroupMask = ~(PrependFlag | AppendFlag | UnresolvedFlag | MovedFlag | CacheFlag) - }; - - class Range - { - public: - Range() : next(this), previous(this) {} - Range(Range *next, void *list, int index, int count, uint flags) - : next(next), previous(next->previous), list(list), index(index), count(count), flags(flags) { - next->previous = this; previous->next = this; } - - Range *next; - Range *previous; - void *list = nullptr; - int index = 0; - int count = 0; - uint flags = 0; - - inline int start() const { return index; } - inline int end() const { return index + count; } - - inline int groups() const { return flags & GroupMask; } - - inline bool inGroup() const { return flags & GroupMask; } - inline bool inCache() const { return flags & CacheFlag; } - inline bool inGroup(int group) const { return flags & (1 << group); } - inline bool isUnresolved() const { return flags & UnresolvedFlag; } - - inline bool prepend() const { return flags & PrependFlag; } - inline bool append() const { return flags & AppendFlag; } - }; - - class Q_AUTOTEST_EXPORT iterator - { - public: - inline iterator(); - inline iterator(const iterator &it); - inline iterator(Range *range, int offset, Group group, int groupCount); - inline ~iterator() {} - - bool operator ==(const iterator &it) const { return range == it.range && offset == it.offset; } - bool operator !=(const iterator &it) const { return range != it.range || offset != it.offset; } - - bool operator ==(Group group) const { return range->flags & (1 << group); } - bool operator !=(Group group) const { return !(range->flags & (1 << group)); } - - Range *&operator *() { return range; } - Range * const &operator *() const { return range; } - Range *operator ->() { return range; } - const Range *operator ->() const { return range; } - - iterator &operator +=(int difference); - - template T *list() const { return static_cast(range->list); } - int modelIndex() const { return range->index + offset; } - - void incrementIndexes(int difference) { incrementIndexes(difference, range->flags); } - void decrementIndexes(int difference) { decrementIndexes(difference, range->flags); } - - inline void incrementIndexes(int difference, uint flags); - inline void decrementIndexes(int difference, uint flags); - - void setGroup(Group g) { group = g; groupFlag = 1 << g; } - - Range *range = nullptr; - int offset = 0; - Group group = Default; - int groupFlag; - int groupCount = 0; - union { - struct { - int cacheIndex; - }; - int index[MaximumGroupCount]; - }; - }; - - class Q_AUTOTEST_EXPORT insert_iterator : public iterator - { - public: - inline insert_iterator() {} - inline insert_iterator(const iterator &it) : iterator(it) {} - inline insert_iterator(Range *, int, Group, int); - inline ~insert_iterator() {} - - insert_iterator &operator +=(int difference); - }; - - struct Change - { - inline Change() {} - inline Change(const iterator &it, int count, uint flags, int moveId = -1); - int count; - uint flags; - int moveId; - union { - struct { - int cacheIndex; - }; - int index[MaximumGroupCount]; - }; - - inline bool isMove() const { return moveId >= 0; } - inline bool inCache() const { return flags & CacheFlag; } - inline bool inGroup() const { return flags & GroupMask; } - inline bool inGroup(int group) const { return flags & (CacheFlag << group); } - - inline int groups() const { return flags & GroupMask; } - }; - - struct Insert : public Change - { - Insert() {} - Insert(const iterator &it, int count, uint flags, int moveId = -1) - : Change(it, count, flags, moveId) {} - }; - - struct Remove : public Change - { - Remove() {} - Remove(const iterator &it, int count, uint flags, int moveId = -1) - : Change(it, count, flags, moveId) {} - }; - - QQmlListCompositor(); - ~QQmlListCompositor(); - - int defaultGroups() const { return m_defaultFlags & ~PrependFlag; } - void setDefaultGroups(int groups) { m_defaultFlags = groups | PrependFlag; } - void setDefaultGroup(Group group) { m_defaultFlags |= (1 << group); } - void clearDefaultGroup(Group group) { m_defaultFlags &= ~(1 << group); } - void setRemoveGroups(int groups) { m_removeFlags = PrependFlag | AppendFlag | groups; } - void setGroupCount(int count); - - int count(Group group) const; - iterator find(Group group, int index); - iterator find(Group group, int index) const; - insert_iterator findInsertPosition(Group group, int index); - - const iterator &end() { return m_end; } - - void append(void *list, int index, int count, uint flags, QVector *inserts = nullptr); - void insert(Group group, int before, void *list, int index, int count, uint flags, QVector *inserts = nullptr); - iterator insert(iterator before, void *list, int index, int count, uint flags, QVector *inserts = nullptr); - - void setFlags(Group fromGroup, int from, int count, Group group, int flags, QVector *inserts = nullptr); - void setFlags(iterator from, int count, Group group, uint flags, QVector *inserts = nullptr); - void setFlags(Group fromGroup, int from, int count, uint flags, QVector *inserts = nullptr) { - setFlags(fromGroup, from, count, fromGroup, flags, inserts); } - void setFlags(const iterator from, int count, uint flags, QVector *inserts = nullptr) { - setFlags(from, count, from.group, flags, inserts); } - - void clearFlags(Group fromGroup, int from, int count, Group group, uint flags, QVector *removals = nullptr); - void clearFlags(iterator from, int count, Group group, uint flags, QVector *removals = nullptr); - void clearFlags(Group fromGroup, int from, int count, uint flags, QVector *removals = nullptr) { - clearFlags(fromGroup, from, count, fromGroup, flags, removals); } - void clearFlags(const iterator &from, int count, uint flags, QVector *removals = nullptr) { - clearFlags(from, count, from.group, flags, removals); } - - bool verifyMoveTo(Group fromGroup, int from, Group toGroup, int to, int count, Group group) const; - - void move( - Group fromGroup, - int from, - Group toGroup, - int to, - int count, - Group group, - QVector *removals = nullptr, - QVector *inserts = nullptr); - void clear(); - - void listItemsInserted(void *list, int index, int count, QVector *inserts); - void listItemsRemoved(void *list, int index, int count, QVector *removals); - void listItemsMoved(void *list, int from, int to, int count, QVector *removals, QVector *inserts); - void listItemsChanged(void *list, int index, int count, QVector *changes); - - void transition( - Group from, - Group to, - QVector *removes, - QVector *inserts); - -private: - Range m_ranges; - iterator m_end; - iterator m_cacheIt; - int m_groupCount; - int m_defaultFlags; - int m_removeFlags; - int m_moveId; - - inline Range *insert(Range *before, void *list, int index, int count, uint flags); - inline Range *erase(Range *range); - - struct MovedFlags - { - MovedFlags() {} - MovedFlags(int moveId, uint flags) : moveId(moveId), flags(flags) {} - - int moveId; - uint flags; - }; - - void listItemsRemoved( - QVector *translatedRemovals, - void *list, - QVector *removals, - QVector *insertions = nullptr, - QVector *movedFlags = nullptr); - void listItemsInserted( - QVector *translatedInsertions, - void *list, - const QVector &insertions, - const QVector *movedFlags = nullptr); - void listItemsChanged( - QVector *translatedChanges, - void *list, - const QVector &changes); - - friend Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list); -}; - -Q_DECLARE_TYPEINFO(QQmlListCompositor::Change, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(QQmlListCompositor::Remove, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(QQmlListCompositor::Insert, Q_PRIMITIVE_TYPE); - -inline QQmlListCompositor::iterator::iterator() {} -inline QQmlListCompositor::iterator::iterator(const iterator &it) - : range(it.range) - , offset(it.offset) - , group(it.group) - , groupFlag(it.groupFlag) - , groupCount(it.groupCount) -{ - for (int i = 0; i < groupCount; ++i) - index[i] = it.index[i]; -} - -inline QQmlListCompositor::iterator::iterator( - Range *range, int offset, Group group, int groupCount) - : range(range) - , offset(offset) - , group(group) - , groupFlag(1 << group) - , groupCount(groupCount) -{ - for (int i = 0; i < groupCount; ++i) - index[i] = 0; -} - -inline void QQmlListCompositor::iterator::incrementIndexes(int difference, uint flags) -{ - for (int i = 0; i < groupCount; ++i) { - if (flags & (1 << i)) - index[i] += difference; - } -} - -inline void QQmlListCompositor::iterator::decrementIndexes(int difference, uint flags) -{ - for (int i = 0; i < groupCount; ++i) { - if (flags & (1 << i)) - index[i] -= difference; - } -} - -inline QQmlListCompositor::insert_iterator::insert_iterator( - Range *range, int offset, Group group, int groupCount) - : iterator(range, offset, group, groupCount) {} - -inline QQmlListCompositor::Change::Change(const iterator &it, int count, uint flags, int moveId) - : count(count), flags(flags), moveId(moveId) -{ - for (int i = 0; i < MaximumGroupCount; ++i) - index[i] = it.index[i]; -} - -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert); -Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list); - -QT_END_NAMESPACE - -#endif diff --git a/src/qml/util/util.pri b/src/qml/util/util.pri index bebb271f1b..3b121ba3cb 100644 --- a/src/qml/util/util.pri +++ b/src/qml/util/util.pri @@ -1,19 +1,5 @@ SOURCES += \ - $$PWD/qqmlchangeset.cpp \ - $$PWD/qqmllistaccessor.cpp \ - $$PWD/qqmllistcompositor.cpp \ $$PWD/qqmlpropertymap.cpp HEADERS += \ - $$PWD/qqmlchangeset_p.h \ - $$PWD/qqmllistaccessor_p.h \ - $$PWD/qqmllistcompositor_p.h \ $$PWD/qqmlpropertymap.h - -qtConfig(qml-delegate-model) { - SOURCES += \ - $$PWD/qqmladaptormodel.cpp - - HEADERS += \ - $$PWD/qqmladaptormodel_p.h -} diff --git a/src/qmlmodels/configure.json b/src/qmlmodels/configure.json new file mode 100644 index 0000000000..2aa8a50e69 --- /dev/null +++ b/src/qmlmodels/configure.json @@ -0,0 +1,31 @@ +{ + "module": "qmlmodels", + "depends": [ + "core-private", + "qml-private" + ], + + "features": { + "qml-list-model": { + "label": "QML list model", + "purpose": "Provides the ListModel QML type.", + "section": "QML", + "output": [ "privateFeature" ] + }, + "qml-delegate-model": { + "label": "QML delegate model", + "purpose": "Provides the DelegateModel QML type.", + "section": "QML", + "output": [ "privateFeature" ] + } + }, + "summary": [ + { + "section": "Qt QML Models", + "entries": [ + "qml-list-model", + "qml-delegate-model" + ] + } + ] +} diff --git a/src/qmlmodels/qmlmodels.pro b/src/qmlmodels/qmlmodels.pro new file mode 100644 index 0000000000..84f87f8bb1 --- /dev/null +++ b/src/qmlmodels/qmlmodels.pro @@ -0,0 +1,57 @@ +TARGET = QtQmlModels +QT = core-private qml-private + +DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_INTEGER_EVENT_COORDINATES QT_NO_FOREACH + +HEADERS += \ + $$PWD/qqmlchangeset_p.h \ + $$PWD/qqmlinstantiator_p.h \ + $$PWD/qqmlinstantiator_p_p.h \ + $$PWD/qqmllistaccessor_p.h \ + $$PWD/qqmllistcompositor_p.h \ + $$PWD/qqmlmodelsmodule_p.h \ + $$PWD/qqmlobjectmodel_p.h \ + $$PWD/qqmltableinstancemodel_p.h \ + $$PWD/qqmltablemodel_p.h \ + $$PWD/qqmltablemodelcolumn_p.h \ + $$PWD/qquickpackage_p.h \ + $$PWD/qtqmlmodelsglobal_p.h \ + $$PWD/qtqmlmodelsglobal.h \ + +SOURCES += \ + $$PWD/qqmlchangeset.cpp \ + $$PWD/qqmlinstantiator.cpp \ + $$PWD/qqmllistaccessor.cpp \ + $$PWD/qqmllistcompositor.cpp \ + $$PWD/qqmlmodelsmodule.cpp \ + $$PWD/qqmlobjectmodel.cpp \ + $$PWD/qqmltableinstancemodel.cpp \ + $$PWD/qqmltablemodel.cpp \ + $$PWD/qqmltablemodelcolumn.cpp \ + $$PWD/qquickpackage.cpp + +qtConfig(qml-list-model) { + SOURCES += \ + $$PWD/qqmllistmodel.cpp \ + $$PWD/qqmllistmodelworkeragent.cpp + + HEADERS += \ + $$PWD/qqmllistmodel_p.h \ + $$PWD/qqmllistmodel_p_p.h \ + $$PWD/qqmllistmodelworkeragent_p.h +} + +qtConfig(qml-delegate-model) { + SOURCES += \ + $$PWD/qqmladaptormodel.cpp \ + $$PWD/qqmldelegatemodel.cpp \ + $$PWD/qqmldelegatecomponent.cpp + + HEADERS += \ + $$PWD/qqmladaptormodel_p.h \ + $$PWD/qqmldelegatemodel_p.h \ + $$PWD/qqmldelegatemodel_p_p.h \ + $$PWD/qqmldelegatecomponent_p.h +} + +load(qt_module) diff --git a/src/qmlmodels/qqmladaptormodel.cpp b/src/qmlmodels/qqmladaptormodel.cpp new file mode 100644 index 0000000000..f991ae0a69 --- /dev/null +++ b/src/qmlmodels/qqmladaptormodel.cpp @@ -0,0 +1,1037 @@ +/**************************************************************************** +** +** 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 +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlAdaptorModelEngineData : public QV8Engine::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 o(scope, thisObject->as()); + if (!o) + RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); + + RETURN_RESULT(QV4::Encode(o->d()->item->index)); +} + +template 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( + 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 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 &items, + int index, + int count, + const QVector &roles) const override + { + bool changed = roles.isEmpty() && !watchedRoles.isEmpty(); + if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) { + QList roleIds; + for (const QByteArray &r : watchedRoles) { + QHash::const_iterator it = roleNames.find(r); + if (it != roleNames.end()) + roleIds << it.value(); + } + const_cast(this)->watchedRoleIds = roleIds; + } + + QVector 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); + } + + for (int i = 0, c = items.count(); i < c; ++i) { + QQmlDelegateModelItem *item = items.at(i); + 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 &oldRoles, + const QList &newRoles) const override + { + VDMModelDelegateDataType *dataType = const_cast(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 o(scope, thisObject->as()); + if (!o) + RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"))); + + const QQmlAdaptorModel *const model = static_cast(o->d()->item)->type->model; + if (o->d()->item->index >= 0 && *model) { + const QAbstractItemModel * const aim = model->aim(); + RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex)))); + } else { + 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::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(global, propertyId, QQmlDMCachedModelData::get_property)); + QV4::ScopedFunctionObject s(scope, v4->memoryManager->allocate(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(object)->metaCall(call, id, arguments); + } + + QV4::PersistentValue prototype; + QList propertyRoles; + QList watchedRoleIds; + QList watchedRoles; + QHash roleNames; + QQmlAdaptorModel *model; + int propertyOffset; + int signalOffset; + bool hasModelData; +}; + +QQmlDMCachedModelData::QQmlDMCachedModelData(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(arguments[0]) = cachedData.at( + type->hasModelData ? 0 : propertyIndex); + } + } else if (*type->model) { + *static_cast(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(arguments[0]); + QMetaObject::activate(this, meta, propertyIndex, nullptr); + } else if (cachedData.count() == 1) { + cachedData[0] = *static_cast(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(arguments[0])); + } + return -1; + } else { + return qt_metacall(call, id, arguments); + } +} + +void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value) +{ + QHash::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 o(scope, thisObject->as()); + if (!o) + return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + + uint propertyId = static_cast(b)->d()->index; + + QQmlDMCachedModelData *modelData = static_cast(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 o(scope, thisObject->as()); + if (!o) + return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + if (!argc) + return scope.engine->throwTypeError(); + + uint propertyId = static_cast(b)->d()->index; + + if (o->d()->item->index == -1) { + QQmlDMCachedModelData *modelData = static_cast(o->d()->item); + if (!modelData->cachedData.isEmpty()) { + if (modelData->cachedData.count() > 1) { + modelData->cachedData[propertyId] = scope.engine->toVariant(argv[0], QVariant::Invalid); + 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], QVariant::Invalid); + 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( + QQmlDelegateModelItemMetaType *metaType, + VDMModelDelegateDataType *dataType, + int index, int row, int column) + : QQmlDMCachedModelData(metaType, dataType, index, row, column) + { + } + + bool hasModelChildren() const + { + if (index >= 0 && *type->model) { + const QAbstractItemModel * const model = type->model->aim(); + return model->hasChildren(model->index(row, column, type->model->rootIndex)); + } else { + return false; + } + } + + QVariant value(int role) const override + { + return type->model->aim()->index(row, column, type->model->rootIndex).data(role); + } + + void setValue(int role, const QVariant &value) override + { + type->model->aim()->setData( + type->model->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(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 + { + return model.aim()->rowCount(model.rootIndex); + } + + int columnCount(const QQmlAdaptorModel &model) const override + { + return model.aim()->columnCount(model.rootIndex); + } + + void cleanup(QQmlAdaptorModel &) const override + { + const_cast(this)->release(); + } + + QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override + { + QHash::const_iterator it = roleNames.find(role.toUtf8()); + if (it != roleNames.end()) { + return model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex).data(*it); + } else if (role == QLatin1String("hasModelChildren")) { + return QVariant(model.aim()->hasChildren(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex))); + } else { + return QVariant(); + } + } + + QVariant parentModelIndex(const QQmlAdaptorModel &model) const override + { + return model + ? QVariant::fromValue(model.aim()->parent(model.rootIndex)) + : QVariant(); + } + + QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override + { + return model + ? QVariant::fromValue(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex)) + : QVariant(); + } + + bool canFetchMore(const QQmlAdaptorModel &model) const override + { + return model && model.aim()->canFetchMore(model.rootIndex); + } + + void fetchMore(QQmlAdaptorModel &model) const override + { + if (model) + model.aim()->fetchMore(model.rootIndex); + } + + QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &model, + QQmlDelegateModelItemMetaType *metaType, + int index, int row, int column) const override + { + VDMAbstractItemModelDataType *dataType = const_cast(this); + if (!metaObject) + dataType->initializeMetaType(model); + return new QQmlDMAbstractItemModelData(metaType, dataType, index, row, column); + } + + void initializeMetaType(QQmlAdaptorModel &model) + { + QMetaObjectBuilder builder; + setModelDataType(&builder, this); + + const QByteArray propertyType = QByteArrayLiteral("QVariant"); + const QHash names = model.aim()->roleNames(); + for (QHash::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(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(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(); + if (!o) + return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + + return v4->fromVariant(static_cast(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(); + if (!o) + return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + if (!argc) + return v4->throwTypeError(); + + static_cast(o->d()->item)->setModelData(v4->toVariant(argv[0], QVariant::Invalid)); + return QV4::Encode::undefined(); + } + + QV4::ReturnedValue get() override + { + QQmlAdaptorModelEngineData *data = engineData(v4); + QV4::Scope scope(v4); + QV4::ScopedObject o(scope, v4->memoryManager->allocate(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(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, + QQmlDelegateModelItemMetaType *metaType, + int index, int row, int column) const override + { + VDMListDelegateDataType *dataType = const_cast(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 &items, int index, int count, const QVector &) const override + { + for (auto modelItem : items) { + const int modelItemIndex = modelItem->index; + if (modelItemIndex < index || modelItemIndex >= index + count) + continue; + + auto listModelItem = static_cast(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( + 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 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()) + return object->property(role.toUtf8()); + return QVariant(); + } + + QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &model, + QQmlDelegateModelItemMetaType *metaType, + int index, int row, int column) const override + { + VDMObjectDelegateDataType *dataType = const_cast(this); + if (!metaObject) + dataType->initializeMetaType(model); + return index >= 0 && index < model.list.count() + ? new QQmlDMObjectData(metaType, dataType, index, row, column, qvariant_cast(model.list.at(index))) + : nullptr; + } + + void initializeMetaType(QQmlAdaptorModel &model) + { + Q_UNUSED(model); + setModelDataType(&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(this)->release(); + } + + bool notify(const QQmlAdaptorModel &model, const QList &items, int index, int count, const QVector &) const override + { + for (auto modelItem : items) { + const int modelItemIndex = modelItem->index; + if (modelItemIndex < index || modelItemIndex >= index + count) + continue; + + auto objectModelItem = static_cast(modelItem); + QObject *updatedModelData = qvariant_cast(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(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(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(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(list.list())) { + setObject(object, parent); + if (qobject_cast(object)) + accessors = new VDMAbstractItemModelDataType(this); + else + accessors = new VDMObjectDelegateDataType; + } else if (list.type() == QQmlListAccessor::ListProperty) { + setObject(static_cast(variant.constData())->object(), 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 diff --git a/src/qmlmodels/qqmladaptormodel_p.h b/src/qmlmodels/qqmladaptormodel_p.h new file mode 100644 index 0000000000..a4549127af --- /dev/null +++ b/src/qmlmodels/qqmladaptormodel_p.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLADAPTORMODEL_P_H +#define QQMLADAPTORMODEL_P_H + +// +// 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. +// + +#include + +#include +#include +#include +#include +#include +#include + +QT_REQUIRE_CONFIG(qml_delegate_model); + +QT_BEGIN_NAMESPACE + +class QQmlEngine; + +class QQmlDelegateModel; +class QQmlDelegateModelItem; +class QQmlDelegateModelItemMetaType; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlAdaptorModel : public QQmlStrongJSQObjectReference +{ +public: + class Accessors + { + public: + inline Accessors() {} + virtual ~Accessors(); + virtual int rowCount(const QQmlAdaptorModel &) const { return 0; } + virtual int columnCount(const QQmlAdaptorModel &) const { return 0; } + virtual void cleanup(QQmlAdaptorModel &) const {} + + virtual QVariant value(const QQmlAdaptorModel &, int, const QString &) const { + return QVariant(); } + + virtual QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &, + QQmlDelegateModelItemMetaType *, + int, int, int) const { return nullptr; } + + virtual bool notify( + const QQmlAdaptorModel &, + const QList &, + int, + int, + const QVector &) const { return false; } + virtual void replaceWatchedRoles( + QQmlAdaptorModel &, + const QList &, + const QList &) const {} + virtual QVariant parentModelIndex(const QQmlAdaptorModel &) const { + return QVariant(); } + virtual QVariant modelIndex(const QQmlAdaptorModel &, int) const { + return QVariant(); } + virtual bool canFetchMore(const QQmlAdaptorModel &) const { return false; } + virtual void fetchMore(QQmlAdaptorModel &) const {} + + QScopedPointer metaObject; + QQmlRefPointer propertyCache; + }; + + const Accessors *accessors; + QPersistentModelIndex rootIndex; + QQmlListAccessor list; + + int modelItemRevision = 0; + + QQmlAdaptorModel(); + ~QQmlAdaptorModel(); + + inline QVariant model() const { return list.list(); } + void setModel(const QVariant &variant, QObject *parent, QQmlEngine *engine); + void invalidateModel(); + + bool isValid() const; + int count() const; + int rowCount() const; + int columnCount() const; + int rowAt(int index) const; + int columnAt(int index) const; + int indexAt(int row, int column) const; + + void useImportVersion(int minorVersion); + + inline bool adaptsAim() const { return qobject_cast(object()); } + inline QAbstractItemModel *aim() { return static_cast(object()); } + inline const QAbstractItemModel *aim() const { return static_cast(object()); } + + inline QVariant value(int index, const QString &role) const { + return accessors->value(*this, index, role); } + inline QQmlDelegateModelItem *createItem(QQmlDelegateModelItemMetaType *metaType, int index) { + return accessors->createItem(*this, metaType, index, rowAt(index), columnAt(index)); } + inline bool hasProxyObject() const { + return list.type() == QQmlListAccessor::Instance || list.type() == QQmlListAccessor::ListProperty; } + + inline bool notify( + const QList &items, + int index, + int count, + const QVector &roles) const { + return accessors->notify(*this, items, index, count, roles); } + inline void replaceWatchedRoles( + const QList &oldRoles, const QList &newRoles) { + accessors->replaceWatchedRoles(*this, oldRoles, newRoles); } + + inline QVariant modelIndex(int index) const { return accessors->modelIndex(*this, index); } + inline QVariant parentModelIndex() const { return accessors->parentModelIndex(*this); } + inline bool canFetchMore() const { return accessors->canFetchMore(*this); } + inline void fetchMore() { return accessors->fetchMore(*this); } + +protected: + void objectDestroyed(QObject *) override; +}; + +class QQmlAdaptorModelProxyInterface +{ +public: + virtual ~QQmlAdaptorModelProxyInterface() {} + + virtual QObject *proxiedObject() = 0; +}; + +#define QQmlAdaptorModelProxyInterface_iid "org.qt-project.Qt.QQmlAdaptorModelProxyInterface" + +Q_DECLARE_INTERFACE(QQmlAdaptorModelProxyInterface, QQmlAdaptorModelProxyInterface_iid) + +QT_END_NAMESPACE + +#endif diff --git a/src/qmlmodels/qqmlchangeset.cpp b/src/qmlmodels/qqmlchangeset.cpp new file mode 100644 index 0000000000..ba876b42e2 --- /dev/null +++ b/src/qmlmodels/qqmlchangeset.cpp @@ -0,0 +1,583 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmlchangeset_p.h" + +QT_BEGIN_NAMESPACE + + +/*! + \class QQmlChangeSet + \brief The QQmlChangeSet class stores an ordered list of notifications about + changes to a linear data set. + \internal + + QQmlChangeSet can be used to record a series of notifications about items in an indexed list + being inserted, removed, moved, and changed. Notifications in the set are re-ordered so that + all notifications of a single type are grouped together and sorted in order of ascending index, + with remove notifications preceding all others, followed by insert notification, and then + change notifications. + + Moves in a change set are represented by a remove notification paired with an insert + notification by way of a shared unique moveId. Re-ordering may result in one or both of the + paired notifications being divided, when this happens the offset member of the notification + will indicate the relative offset of the divided notification from the beginning of the + original. +*/ + +/*! + Constructs an empty change set. +*/ + +QQmlChangeSet::QQmlChangeSet() + : m_difference(0) +{ +} + +/*! + Constructs a copy of a \a changeSet. +*/ + +QQmlChangeSet::QQmlChangeSet(const QQmlChangeSet &changeSet) + : m_removes(changeSet.m_removes) + , m_inserts(changeSet.m_inserts) + , m_changes(changeSet.m_changes) + , m_difference(changeSet.m_difference) +{ +} + +/*! + Destroys a change set. +*/ + +QQmlChangeSet::~QQmlChangeSet() +{ +} + +/*! + Assigns the value of a \a changeSet to another. +*/ + +QQmlChangeSet &QQmlChangeSet::operator =(const QQmlChangeSet &changeSet) +{ + m_removes = changeSet.m_removes; + m_inserts = changeSet.m_inserts; + m_changes = changeSet.m_changes; + m_difference = changeSet.m_difference; + return *this; +} + +/*! + Appends a notification that \a count items were inserted at \a index. +*/ + +void QQmlChangeSet::insert(int index, int count) +{ + insert(QVector() << Change(index, count)); +} + +/*! + Appends a notification that \a count items were removed at \a index. +*/ + +void QQmlChangeSet::remove(int index, int count) +{ + QVector removes; + removes.append(Change(index, count)); + remove(&removes, nullptr); +} + +/*! + Appends a notification that \a count items were moved \a from one index \a to another. + + The \a moveId must be unique across the lifetime of the change set and any related + change sets. +*/ + +void QQmlChangeSet::move(int from, int to, int count, int moveId) +{ + QVector removes; + removes.append(Change(from, count, moveId)); + QVector inserts; + inserts.append(Change(to, count, moveId)); + remove(&removes, &inserts); + insert(inserts); +} + +/*! + Appends a notification that \a count items were changed at \a index. +*/ + +void QQmlChangeSet::change(int index, int count) +{ + QVector changes; + changes.append(Change(index, count)); + change(changes); +} + +/*! + Applies the changes in a \a changeSet to another. +*/ + +void QQmlChangeSet::apply(const QQmlChangeSet &changeSet) +{ + QVector r = changeSet.m_removes; + QVector i = changeSet.m_inserts; + QVector c = changeSet.m_changes; + remove(&r, &i); + insert(i); + change(c); +} + +/*! + Applies a list of \a removes to a change set. + + If a remove contains a moveId then any intersecting insert in the set will replace the + corresponding intersection in the optional \a inserts list. +*/ + +void QQmlChangeSet::remove(const QVector &removes, QVector *inserts) +{ + QVector r = removes; + remove(&r, inserts); +} + +void QQmlChangeSet::remove(QVector *removes, QVector *inserts) +{ + int removeCount = 0; + int insertCount = 0; + QVector::iterator insert = m_inserts.begin(); + QVector::iterator change = m_changes.begin(); + QVector::iterator rit = removes->begin(); + for (; rit != removes->end(); ++rit) { + int index = rit->index + removeCount; + int count = rit->count; + + // Decrement the accumulated remove count from the indexes of any changes prior to the + // current remove. + for (; change != m_changes.end() && change->end() < rit->index; ++change) + change->index -= removeCount; + // Remove any portion of a change notification that intersects the current remove. + for (; change != m_changes.end() && change->index > rit->end(); ++change) { + change->count -= qMin(change->end(), rit->end()) - qMax(change->index, rit->index); + if (change->count == 0) { + change = m_changes.erase(change); + } else if (rit->index < change->index) { + change->index = rit->index; + } + } + + // Decrement the accumulated remove count from the indexes of any inserts prior to the + // current remove. + for (; insert != m_inserts.end() && insert->end() <= index; ++insert) { + insertCount += insert->count; + insert->index -= removeCount; + } + + rit->index -= insertCount; + + // Remove any portion of a insert notification that intersects the current remove. + while (insert != m_inserts.end() && insert->index < index + count) { + int offset = index - insert->index; + const int difference = qMin(insert->end(), index + count) - qMax(insert->index, index); + + // If part of the remove or insert that precedes the intersection has a moveId create + // a new delta for that portion and subtract the size of that delta from the current + // one. + if (offset < 0 && rit->moveId != -1) { + rit = removes->insert(rit, Change( + rit->index, -offset, rit->moveId, rit->offset)); + ++rit; + rit->count -= -offset; + rit->offset += -offset; + index += -offset; + count -= -offset; + removeCount += -offset; + offset = 0; + } else if (offset > 0 && insert->moveId != -1) { + insert = m_inserts.insert(insert, Change( + insert->index - removeCount, offset, insert->moveId, insert->offset)); + ++insert; + insert->index += offset; + insert->count -= offset; + insert->offset += offset; + rit->index -= offset; + insertCount += offset; + } + + // If the current remove has a move id, find any inserts with the same move id and + // replace the corresponding sections with the insert removed from the change set. + if (rit->moveId != -1 && difference > 0 && inserts) { + for (QVector::iterator iit = inserts->begin(); iit != inserts->end(); ++iit) { + if (iit->moveId != rit->moveId + || rit->offset > iit->offset + iit->count + || iit->offset > rit->offset + difference) { + continue; + } + // If the intersecting insert starts before the replacement one create + // a new insert for the portion prior to the replacement insert. + const int overlapOffset = rit->offset - iit->offset; + if (overlapOffset > 0) { + iit = inserts->insert(iit, Change( + iit->index, overlapOffset, iit->moveId, iit->offset)); + ++iit; + iit->index += overlapOffset; + iit->count -= overlapOffset; + iit->offset += overlapOffset; + } + if (iit->offset >= rit->offset + && iit->offset + iit->count <= rit->offset + difference) { + // If the replacement insert completely encapsulates the existing + // one just change the moveId. + iit->moveId = insert->moveId; + iit->offset = insert->offset + qMax(0, -overlapOffset); + } else { + // Create a new insertion before the intersecting one with the number of intersecting + // items and remove that number from that insert. + const int count + = qMin(iit->offset + iit->count, rit->offset + difference) + - qMax(iit->offset, rit->offset); + iit = inserts->insert(iit, Change( + iit->index, + count, + insert->moveId, + insert->offset + qMax(0, -overlapOffset))); + ++iit; + iit->index += count; + iit->count -= count; + iit->offset += count; + } + } + } + + // Subtract the number of intersecting items from the current remove and insert. + insert->count -= difference; + insert->offset += difference; + rit->count -= difference; + rit->offset += difference; + + index += difference; + count -= difference; + removeCount += difference; + + if (insert->count == 0) { + insert = m_inserts.erase(insert); + } else if (rit->count == -offset || rit->count == 0) { + insert->index += difference; + break; + } else { + insert->index -= removeCount - difference; + rit->index -= insert->count; + insertCount += insert->count; + ++insert; + } + } + removeCount += rit->count; + } + for (; insert != m_inserts.end(); ++insert) + insert->index -= removeCount; + + removeCount = 0; + QVector::iterator remove = m_removes.begin(); + for (rit = removes->begin(); rit != removes->end(); ++rit) { + if (rit->count == 0) + continue; + // Accumulate consecutive removes into a single delta before attempting to apply. + for (QVector::iterator next = rit + 1; next != removes->end() + && next->index == rit->index + && next->moveId == -1 + && rit->moveId == -1; ++next) { + next->count += rit->count; + rit = next; + } + int index = rit->index + removeCount; + // Decrement the accumulated remove count from the indexes of any inserts prior to the + // current remove. + for (; remove != m_removes.end() && index > remove->index; ++remove) + remove->index -= removeCount; + while (remove != m_removes.end() && index + rit->count >= remove->index) { + int count = 0; + const int offset = remove->index - index; + QVector::iterator rend = remove; + for (; rend != m_removes.end() + && rit->moveId == -1 + && rend->moveId == -1 + && index + rit->count >= rend->index; ++rend) { + count += rend->count; + } + if (remove != rend) { + // Accumulate all existing non-move removes that are encapsulated by or immediately + // follow the current remove into it. + int difference = 0; + if (rend == m_removes.end()) { + difference = rit->count; + } else if (rit->index + rit->count < rend->index - removeCount) { + difference = rit->count; + } else if (rend->moveId != -1) { + difference = rend->index - removeCount - rit->index; + index += difference; + } + count += difference; + + rit->count -= difference; + removeCount += difference; + remove->index = rit->index; + remove->count = count; + remove = m_removes.erase(++remove, rend); + } else { + // Insert a remove for the portion of the unmergable current remove prior to the + // point of intersection. + if (offset > 0) { + remove = m_removes.insert(remove, Change( + rit->index, offset, rit->moveId, rit->offset)); + ++remove; + rit->count -= offset; + rit->offset += offset; + removeCount += offset; + index += offset; + } + remove->index = rit->index; + + ++remove; + } + } + + if (rit->count > 0) { + remove = m_removes.insert(remove, *rit); + ++remove; + } + removeCount += rit->count; + } + for (; remove != m_removes.end(); ++remove) + remove->index -= removeCount; + m_difference -= removeCount; +} + +/*! + Applies a list of \a inserts to a change set. +*/ + +void QQmlChangeSet::insert(const QVector &inserts) +{ + int insertCount = 0; + QVector::iterator insert = m_inserts.begin(); + QVector::iterator change = m_changes.begin(); + for (QVector::const_iterator iit = inserts.begin(); iit != inserts.end(); ++iit) { + if (iit->count == 0) + continue; + int index = iit->index - insertCount; + + Change current = *iit; + // Accumulate consecutive inserts into a single delta before attempting to insert. + for (QVector::const_iterator next = iit + 1; next != inserts.end() + && next->index == iit->index + iit->count + && next->moveId == -1 + && iit->moveId == -1; ++next) { + current.count += next->count; + iit = next; + } + + // Increment the index of any changes before the current insert by the accumlated insert + // count. + for (; change != m_changes.end() && change->index >= index; ++change) + change->index += insertCount; + // If the current insert index is in the middle of a change split it in two at that + // point and increment the index of the latter half. + if (change != m_changes.end() && change->index < index + iit->count) { + int offset = index - change->index; + change = m_changes.insert(change, Change(change->index + insertCount, offset)); + ++change; + change->index += iit->count + offset; + change->count -= offset; + } + + // Increment the index of any inserts before the current insert by the accumlated insert + // count. + for (; insert != m_inserts.end() && index > insert->index + insert->count; ++insert) + insert->index += insertCount; + if (insert == m_inserts.end()) { + insert = m_inserts.insert(insert, current); + ++insert; + } else { + const int offset = index - insert->index; + + if (offset < 0) { + // If the current insert is before an existing insert and not adjacent just insert + // it into the list. + insert = m_inserts.insert(insert, current); + ++insert; + } else if (iit->moveId == -1 && insert->moveId == -1) { + // If neither the current nor existing insert has a moveId add the current insert + // to the existing one. + if (offset < insert->count) { + insert->index -= current.count; + insert->count += current.count; + } else { + insert->index += insertCount; + insert->count += current.count; + ++insert; + } + } else if (offset < insert->count) { + // If either insert has a moveId then split the existing insert and insert the + // current one in the middle. + if (offset > 0) { + insert = m_inserts.insert(insert, Change( + insert->index + insertCount, offset, insert->moveId, insert->offset)); + ++insert; + insert->index += offset; + insert->count -= offset; + insert->offset += offset; + } + insert = m_inserts.insert(insert, current); + ++insert; + } else { + insert->index += insertCount; + ++insert; + insert = m_inserts.insert(insert, current); + ++insert; + } + } + insertCount += current.count; + } + for (; insert != m_inserts.end(); ++insert) + insert->index += insertCount; + m_difference += insertCount; +} + +/*! + Applies a combined list of \a removes and \a inserts to a change set. This is equivalent + calling \l remove() followed by \l insert() with the same lists. +*/ + +void QQmlChangeSet::move(const QVector &removes, const QVector &inserts) +{ + QVector r = removes; + QVector i = inserts; + remove(&r, &i); + insert(i); +} + +/*! + Applies a list of \a changes to a change set. +*/ + +void QQmlChangeSet::change(const QVector &changes) +{ + QVector c = changes; + change(&c); +} + +void QQmlChangeSet::change(QVector *changes) +{ + QVector::iterator insert = m_inserts.begin(); + QVector::iterator change = m_changes.begin(); + for (QVector::iterator cit = changes->begin(); cit != changes->end(); ++cit) { + for (; insert != m_inserts.end() && insert->end() < cit->index; ++insert) {} + for (; insert != m_inserts.end() && insert->index < cit->end(); ++insert) { + const int offset = insert->index - cit->index; + const int count = cit->count + cit->index - insert->index - insert->count; + if (offset == 0) { + cit->index = insert->index + insert->count; + cit->count = count; + } else { + cit = changes->insert(++cit, Change(insert->index + insert->count, count)); + --cit; + cit->count = offset; + } + } + + for (; change != m_changes.end() && change->index + change->count < cit->index; ++change) {} + if (change == m_changes.end() || change->index > cit->index + cit->count) { + if (cit->count > 0) { + change = m_changes.insert(change, *cit); + ++change; + } + } else { + if (cit->index < change->index) { + change->count += change->index - cit->index; + change->index = cit->index; + } + + if (cit->index + cit->count > change->index + change->count) { + change->count = cit->index + cit->count - change->index; + QVector::iterator cbegin = change; + QVector::iterator cend = ++cbegin; + for (; cend != m_changes.end() && cend->index <= change->index + change->count; ++cend) { + if (cend->index + cend->count > change->index + change->count) + change->count = cend->index + cend->count - change->index; + } + if (cbegin != cend) { + change = m_changes.erase(cbegin, cend); + --change; + } + } + } + } +} + +/*! + Prints the contents of a change \a set to the \a debug stream. +*/ + +QDebug operator <<(QDebug debug, const QQmlChangeSet &set) +{ + debug.nospace() << "QQmlChangeSet("; + const QVector &removes = set.removes(); + for (const QQmlChangeSet::Change &remove : removes) + debug << remove; + const QVector &inserts = set.inserts(); + for (const QQmlChangeSet::Change &insert : inserts) + debug << insert; + const QVector &changes = set.changes(); + for (const QQmlChangeSet::Change &change : changes) + debug << change; + return debug.nospace() << ')'; +} + +/*! + Prints a \a change to the \a debug stream. +*/ + +QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change) +{ + return (debug.nospace() << "Change(" << change.index << ',' << change.count << ')').space(); +} + +QT_END_NAMESPACE + diff --git a/src/qmlmodels/qqmlchangeset_p.h b/src/qmlmodels/qqmlchangeset_p.h new file mode 100644 index 0000000000..5b44d2958c --- /dev/null +++ b/src/qmlmodels/qqmlchangeset_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLCHANGESET_P_H +#define QQMLCHANGESET_P_H + +// +// 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. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlChangeSet +{ +public: + struct MoveKey + { + MoveKey() {} + MoveKey(int moveId, int offset) : moveId(moveId), offset(offset) {} + int moveId = -1; + int offset = 0; + }; + + // The storrage for Change (below). This struct is trivial, which it has to be in order to store + // it in a QV4::Heap::Base object. The Change struct doesn't add any storage fields, so it is + // safe to cast ChangeData to/from Change. + struct ChangeData + { + int index; + int count; + int moveId; + int offset; + }; + + struct Change: ChangeData + { + Change() { + index = 0; + count = 0; + moveId = -1; + offset = 0; + } + Change(int index, int count, int moveId = -1, int offset = 0) { + this->index = index; + this->count = count; + this->moveId = moveId; + this->offset = offset; + } + + bool isMove() const { return moveId >= 0; } + + MoveKey moveKey(int index) const { + return MoveKey(moveId, index - Change::index + offset); } + + int start() const { return index; } + int end() const { return index + count; } + }; + + QQmlChangeSet(); + QQmlChangeSet(const QQmlChangeSet &changeSet); + ~QQmlChangeSet(); + + QQmlChangeSet &operator =(const QQmlChangeSet &changeSet); + + const QVector &removes() const { return m_removes; } + const QVector &inserts() const { return m_inserts; } + const QVector &changes() const { return m_changes; } + + void insert(int index, int count); + void remove(int index, int count); + void move(int from, int to, int count, int moveId); + void change(int index, int count); + + void insert(const QVector &inserts); + void remove(const QVector &removes, QVector *inserts = nullptr); + void move(const QVector &removes, const QVector &inserts); + void change(const QVector &changes); + void apply(const QQmlChangeSet &changeSet); + + bool isEmpty() const { return m_removes.empty() && m_inserts.empty() && m_changes.isEmpty(); } + + void clear() + { + m_removes.clear(); + m_inserts.clear(); + m_changes.clear(); + m_difference = 0; + } + + int difference() const { return m_difference; } + +private: + void remove(QVector *removes, QVector *inserts); + void change(QVector *changes); + + QVector m_removes; + QVector m_inserts; + QVector m_changes; + int m_difference; +}; + +Q_DECLARE_TYPEINFO(QQmlChangeSet::Change, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(QQmlChangeSet::MoveKey, Q_PRIMITIVE_TYPE); + +inline uint qHash(const QQmlChangeSet::MoveKey &key) { return qHash(qMakePair(key.moveId, key.offset)); } +inline bool operator ==(const QQmlChangeSet::MoveKey &l, const QQmlChangeSet::MoveKey &r) { + return l.moveId == r.moveId && l.offset == r.offset; } + +Q_QMLMODELS_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change); +Q_QMLMODELS_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet &change); + +QT_END_NAMESPACE + +#endif diff --git a/src/qmlmodels/qqmldelegatecomponent.cpp b/src/qmlmodels/qqmldelegatecomponent.cpp new file mode 100644 index 0000000000..a7e9536917 --- /dev/null +++ b/src/qmlmodels/qqmldelegatecomponent.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** 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 "qqmldelegatecomponent_p.h" +#include + +QT_BEGIN_NAMESPACE + +QQmlAbstractDelegateComponent::QQmlAbstractDelegateComponent(QObject *parent) + : QQmlComponent(parent) +{ +} + +QQmlAbstractDelegateComponent::~QQmlAbstractDelegateComponent() +{ +} + +QVariant QQmlAbstractDelegateComponent::value(QQmlAdaptorModel *adaptorModel, int row, int column, const QString &role) const +{ + if (!adaptorModel) + return QVariant(); + return adaptorModel->value(adaptorModel->indexAt(row, column), role); +} + +/*! + \qmlmodule Qt.labs.qmlmodels 1.0 + \title Qt Labs QML Models - QML Types + \ingroup qmlmodules + \brief The Qt Labs QML Models module provides various model-related types for use with views. + + To use this module, import the module with the following line: + + \qml + import Qt.labs.qmlmodels 1.0 + \endqml +*/ + +/*! + \qmltype DelegateChoice + \instantiates QQmlDelegateChoice + \inqmlmodule Qt.labs.qmlmodels + \brief Encapsulates a delegate and when to use it. + + The DelegateChoice type wraps a delegate and defines the circumstances + in which it should be chosen. + + DelegateChoices can be nested inside a DelegateChooser. + + \sa DelegateChooser +*/ + +/*! + \qmlproperty string QtQml.Models::DelegateChoice::roleValue + This property holds the value used to match the role data for the role provided by \l DelegateChooser::role. +*/ +QVariant QQmlDelegateChoice::roleValue() const +{ + return m_value; +} + +void QQmlDelegateChoice::setRoleValue(const QVariant &value) +{ + if (m_value == value) + return; + m_value = value; + emit roleValueChanged(); + emit changed(); +} + +/*! + \qmlproperty index QtQml.Models::DelegateChoice::row + This property holds the value used to match the row value of model elements. + With models that have only the index property (and thus only one column), this property + should be intended as an index, and set to the desired index value. + + \note Setting both row and index has undefined behavior. The two are equivalent and only + one should be used. + + \sa index +*/ + +/*! + \qmlproperty index QtQml.Models::DelegateChoice::index + This property holds the value used to match the index value of model elements. + This is effectively an alias for \l row. + + \sa row +*/ +int QQmlDelegateChoice::row() const +{ + return m_row; +} + +void QQmlDelegateChoice::setRow(int r) +{ + if (m_row == r) + return; + m_row = r; + emit rowChanged(); + emit indexChanged(); + emit changed(); +} + +/*! + \qmlproperty index QtQml.Models::DelegateChoice::column + This property holds the value used to match the column value of model elements. +*/ +int QQmlDelegateChoice::column() const +{ + return m_column; +} + +void QQmlDelegateChoice::setColumn(int c) +{ + if (m_column == c) + return; + m_column = c; + emit columnChanged(); + emit changed(); +} + +QQmlComponent *QQmlDelegateChoice::delegate() const +{ + return m_delegate; +} + +/*! + \qmlproperty Component QtQml.Models::DelegateChoice::delegate + This property holds the delegate to use if this choice matches the model item. +*/ +void QQmlDelegateChoice::setDelegate(QQmlComponent *delegate) +{ + if (m_delegate == delegate) + return; + QQmlAbstractDelegateComponent *adc = static_cast(m_delegate); + if (adc) + disconnect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged); + m_delegate = delegate; + adc = static_cast(delegate); + if (adc) + connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged); + emit delegateChanged(); + emit changed(); +} + +bool QQmlDelegateChoice::match(int row, int column, const QVariant &value) const +{ + if (!m_value.isValid() && m_row < 0 && m_column < 0) + return true; + + const bool roleMatched = (m_value.isValid()) ? value == m_value : true; + const bool rowMatched = (m_row < 0 ) ? true : m_row == row; + const bool columnMatched = (m_column < 0 ) ? true : m_column == column; + return roleMatched && rowMatched && columnMatched; +} + +/*! + \qmltype DelegateChooser + \instantiates QQmlDelegateChooser + \inqmlmodule Qt.labs.qmlmodels + \brief Allows a view to use different delegates for different types of items in the model. + + The DelegateChooser is a special \l Component type intended for those scenarios where a Component is required + by a view and used as a delegate. + DelegateChooser encapsulates a set of \l {DelegateChoice}s. + These choices are used determine the delegate that will be instantiated for each + item in the model. + The selection of the choice is performed based on the value that a model item has for \l role, + and also based on index. + + \note This type is intended to transparently work only with TableView and any DelegateModel-based view. + Views (including user-defined views) that aren't internally based on a DelegateModel need to explicitly support + this type of component to make it function as described. + + \sa DelegateChoice +*/ + +/*! + \qmlproperty string QtQml.Models::DelegateChooser::role + This property holds the role used to determine the delegate for a given model item. + + \sa DelegateChoice +*/ +void QQmlDelegateChooser::setRole(const QString &role) +{ + if (m_role == role) + return; + m_role = role; + emit roleChanged(); +} + +/*! + \qmlproperty list QtQml.Models::DelegateChooser::choices + \default + + The list of DelegateChoices for the chooser. + + The list is treated as an ordered list, where the first DelegateChoice to match + will be used be a view. + + It should not generally be necessary to refer to the \c choices property, + as it is the default property for DelegateChooser and thus all child items are + automatically assigned to this property. +*/ + +QQmlListProperty QQmlDelegateChooser::choices() +{ + return QQmlListProperty(this, nullptr, + QQmlDelegateChooser::choices_append, + QQmlDelegateChooser::choices_count, + QQmlDelegateChooser::choices_at, + QQmlDelegateChooser::choices_clear); +} + +void QQmlDelegateChooser::choices_append(QQmlListProperty *prop, QQmlDelegateChoice *choice) +{ + QQmlDelegateChooser *q = static_cast(prop->object); + q->m_choices.append(choice); + connect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged); + q->delegateChanged(); +} + +int QQmlDelegateChooser::choices_count(QQmlListProperty *prop) +{ + QQmlDelegateChooser *q = static_cast(prop->object); + return q->m_choices.count(); +} + +QQmlDelegateChoice *QQmlDelegateChooser::choices_at(QQmlListProperty *prop, int index) +{ + QQmlDelegateChooser *q = static_cast(prop->object); + return q->m_choices.at(index); +} + +void QQmlDelegateChooser::choices_clear(QQmlListProperty *prop) +{ + QQmlDelegateChooser *q = static_cast(prop->object); + for (QQmlDelegateChoice *choice : q->m_choices) + disconnect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged); + q->m_choices.clear(); + q->delegateChanged(); +} + +QQmlComponent *QQmlDelegateChooser::delegate(QQmlAdaptorModel *adaptorModel, int row, int column) const +{ + QVariant v; + if (!m_role.isNull()) + v = value(adaptorModel, row, column, m_role); + if (!v.isValid()) { // check if the row only has modelData, for example if the row is a QVariantMap + v = value(adaptorModel, row, column, QStringLiteral("modelData")); + if (v.isValid()) + v = v.toMap().value(m_role); + } + // loop through choices, finding first one that fits + for (int i = 0; i < m_choices.count(); ++i) { + const QQmlDelegateChoice *choice = m_choices.at(i); + if (choice->match(row, column, v)) + return choice->delegate(); + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmldelegatecomponent_p.h b/src/qmlmodels/qqmldelegatecomponent_p.h new file mode 100644 index 0000000000..1d20f0327b --- /dev/null +++ b/src/qmlmodels/qqmldelegatecomponent_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQMLDELEGATECOMPONENT_P_H +#define QQMLDELEGATECOMPONENT_P_H + +// +// 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. +// + +#include +#include + +QT_REQUIRE_CONFIG(qml_delegate_model); + +QT_BEGIN_NAMESPACE + +// TODO: consider making QQmlAbstractDelegateComponent public API +class QQmlAbstractDelegateComponentPrivate; +class QQmlAdaptorModel; +class Q_QMLMODELS_PRIVATE_EXPORT QQmlAbstractDelegateComponent : public QQmlComponent +{ + Q_OBJECT +public: + QQmlAbstractDelegateComponent(QObject *parent = nullptr); + ~QQmlAbstractDelegateComponent() override; + + virtual QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = 0) const = 0; + +signals: + void delegateChanged(); + +protected: + QVariant value(QQmlAdaptorModel *adaptorModel,int row, int column, const QString &role) const; + +private: + Q_DECLARE_PRIVATE(QQmlAbstractDelegateComponent) + Q_DISABLE_COPY(QQmlAbstractDelegateComponent) +}; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateChoice : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) + Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged) + Q_PROPERTY(int index READ row WRITE setRow NOTIFY indexChanged) + Q_PROPERTY(int column READ column WRITE setColumn NOTIFY columnChanged) + Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_CLASSINFO("DefaultProperty", "delegate") +public: + QVariant roleValue() const; + void setRoleValue(const QVariant &roleValue); + + int row() const; + void setRow(int r); + + int column() const; + void setColumn(int c); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + virtual bool match(int row, int column, const QVariant &value) const; + +signals: + void roleValueChanged(); + void rowChanged(); + void indexChanged(); + void columnChanged(); + void delegateChanged(); + void changed(); + +private: + QVariant m_value; + int m_row = -1; + int m_column = -1; + QQmlComponent *m_delegate = nullptr; +}; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateChooser : public QQmlAbstractDelegateComponent +{ + Q_OBJECT + Q_PROPERTY(QString role READ role WRITE setRole NOTIFY roleChanged) + Q_PROPERTY(QQmlListProperty choices READ choices CONSTANT) + Q_CLASSINFO("DefaultProperty", "choices") + +public: + QString role() const { return m_role; } + void setRole(const QString &role); + + virtual QQmlListProperty choices(); + static void choices_append(QQmlListProperty *, QQmlDelegateChoice *); + static int choices_count(QQmlListProperty *); + static QQmlDelegateChoice *choices_at(QQmlListProperty *, int); + static void choices_clear(QQmlListProperty *); + + QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = -1) const override; + +signals: + void roleChanged(); + +private: + QString m_role; + QList m_choices; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlDelegateChoice) +QML_DECLARE_TYPE(QQmlDelegateChooser) + +#endif // QQMLDELEGATECOMPONENT_P_H diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp new file mode 100644 index 0000000000..742c164508 --- /dev/null +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -0,0 +1,3540 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmldelegatemodel_p_p.h" +#include "qqmldelegatecomponent_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlDelegateModelItem; + +namespace QV4 { + +namespace Heap { + +struct DelegateModelGroupFunction : FunctionObject { + void init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)); + + QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg); + uint flag; +}; + +struct QQmlDelegateModelGroupChange : Object { + void init() { Object::init(); } + + QQmlChangeSet::ChangeData change; +}; + +struct QQmlDelegateModelGroupChangeArray : Object { + void init(const QVector &changes); + void destroy() { + delete changes; + Object::destroy(); + } + + QVector *changes; +}; + + +} + +struct DelegateModelGroupFunction : QV4::FunctionObject +{ + V4_OBJECT2(DelegateModelGroupFunction, FunctionObject) + + static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) + { + return scope->engine()->memoryManager->allocate(scope, flag, code); + } + + static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc) + { + QV4::Scope scope(that->engine()); + QV4::Scoped f(scope, static_cast(that)); + QV4::Scoped o(scope, thisObject); + if (!o) + return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + + QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue()); + return f->d()->code(o->d()->item, f->d()->flag, v); + } +}; + +void Heap::DelegateModelGroupFunction::init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) +{ + QV4::Heap::FunctionObject::init(scope, QStringLiteral("DelegateModelGroupFunction")); + this->flag = flag; + this->code = code; +} + +} + +DEFINE_OBJECT_VTABLE(QV4::DelegateModelGroupFunction); + + + +class QQmlDelegateModelEngineData : public QV8Engine::Deletable +{ +public: + QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4); + ~QQmlDelegateModelEngineData(); + + QV4::ReturnedValue array(QV4::ExecutionEngine *engine, + const QVector &changes); + + QV4::PersistentValue changeProto; +}; + +V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData) + + +void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) +{ + prop.setWritable(false); +} + +QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id) +{ + QQmlDelegateModelParts *parts = static_cast(object()); + QQmlPartsModel *m = new QQmlPartsModel( + parts->model, QString::fromUtf8(name(id)), parts); + parts->models.append(m); + return QVariant::fromValue(static_cast(m)); +} + +QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent) +: QObject(parent), model(parent) +{ + new QQmlDelegateModelPartsMetaObject(this); +} + +//--------------------------------------------------------------------------- + +/*! + \qmltype DelegateModel + \instantiates QQmlDelegateModel + \inqmlmodule QtQml.Models + \brief Encapsulates a model and delegate. + + The DelegateModel type encapsulates a model and the delegate that will + be instantiated for items in the model. + + It is usually not necessary to create a DelegateModel. + However, it can be useful for manipulating and accessing the \l modelIndex + when a QAbstractItemModel subclass is used as the + model. Also, DelegateModel is used together with \l Package to + provide delegates to multiple views, and with DelegateModelGroup to sort and filter + delegate items. + + The example below illustrates using a DelegateModel with a ListView. + + \snippet delegatemodel/delegatemodel.qml 0 +*/ + +QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt) + : m_delegateChooser(nullptr) + , m_cacheMetaType(nullptr) + , m_context(ctxt) + , m_parts(nullptr) + , m_filterGroup(QStringLiteral("items")) + , m_count(0) + , m_groupCount(Compositor::MinimumGroupCount) + , m_compositorGroup(Compositor::Cache) + , m_complete(false) + , m_delegateValidated(false) + , m_reset(false) + , m_transaction(false) + , m_incubatorCleanupScheduled(false) + , m_waitingToFetchMore(false) + , m_cacheItems(nullptr) + , m_items(nullptr) + , m_persistedItems(nullptr) +{ +} + +QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() +{ + qDeleteAll(m_finishedIncubating); + + if (m_cacheMetaType) + m_cacheMetaType->release(); +} + +int QQmlDelegateModelPrivate::adaptorModelCount() const +{ + // QQmlDelegateModel currently only support list models. + // So even if a model is a table model, only the first + // column will be used. + return m_adaptorModel.rowCount(); +} + +void QQmlDelegateModelPrivate::requestMoreIfNecessary() +{ + Q_Q(QQmlDelegateModel); + if (!m_waitingToFetchMore && m_adaptorModel.canFetchMore()) { + m_waitingToFetchMore = true; + QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); + } +} + +void QQmlDelegateModelPrivate::init() +{ + Q_Q(QQmlDelegateModel); + m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag); + + m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q); + m_items->setDefaultInclude(true); + m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q); + QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this); +} + +QQmlDelegateModel::QQmlDelegateModel() + : QQmlDelegateModel(nullptr, nullptr) +{ +} + +QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent) +: QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent) +{ + Q_D(QQmlDelegateModel); + d->init(); +} + +QQmlDelegateModel::~QQmlDelegateModel() +{ + Q_D(QQmlDelegateModel); + d->disconnectFromAbstractItemModel(); + d->m_adaptorModel.setObject(nullptr, this); + + for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) { + if (cacheItem->object) { + delete cacheItem->object; + + cacheItem->object = nullptr; + cacheItem->contextData->invalidate(); + Q_ASSERT(cacheItem->contextData->refCount == 1); + cacheItem->contextData = nullptr; + cacheItem->scriptRef -= 1; + } + cacheItem->groups &= ~Compositor::UnresolvedFlag; + cacheItem->objectRef = 0; + if (!cacheItem->isReferenced()) + delete cacheItem; + else if (cacheItem->incubationTask) + cacheItem->incubationTask->vdm = nullptr; + } +} + + +void QQmlDelegateModel::classBegin() +{ + Q_D(QQmlDelegateModel); + if (!d->m_context) + d->m_context = qmlContext(this); +} + +void QQmlDelegateModel::componentComplete() +{ + Q_D(QQmlDelegateModel); + d->m_complete = true; + + int defaultGroups = 0; + QStringList groupNames; + groupNames.append(QStringLiteral("items")); + groupNames.append(QStringLiteral("persistedItems")); + if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude) + defaultGroups |= Compositor::DefaultFlag; + if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude) + defaultGroups |= Compositor::PersistedFlag; + for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) { + QString name = d->m_groups[i]->name(); + if (name.isEmpty()) { + d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; + --d->m_groupCount; + --i; + } else if (name.at(0).isUpper()) { + qmlWarning(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter"); + d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; + --d->m_groupCount; + --i; + } else { + groupNames.append(name); + + QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]); + group->setModel(this, Compositor::Group(i)); + if (group->defaultInclude) + defaultGroups |= (1 << i); + } + } + + d->m_cacheMetaType = new QQmlDelegateModelItemMetaType( + d->m_context->engine()->handle(), this, groupNames); + + d->m_compositor.setGroupCount(d->m_groupCount); + d->m_compositor.setDefaultGroups(defaultGroups); + d->updateFilterGroup(); + + while (!d->m_pendingParts.isEmpty()) + static_cast(d->m_pendingParts.first())->updateFilterGroup(); + + QVector inserts; + d->m_count = d->adaptorModelCount(); + d->m_compositor.append( + &d->m_adaptorModel, + 0, + d->m_count, + defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag, + &inserts); + d->itemsInserted(inserts); + d->emitChanges(); + d->requestMoreIfNecessary(); +} + +/*! + \qmlproperty model QtQml.Models::DelegateModel::model + This property holds the model providing data for the DelegateModel. + + The model provides a set of data that is used to create the items + for a view. For large or dynamic datasets the model is usually + provided by a C++ model object. The C++ model object must be a \l + {QAbstractItemModel} subclass or a simple list. + + Models can also be created directly in QML, using a \l{ListModel} or + \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}. + + \sa {qml-data-models}{Data Models} + \keyword dm-model-property +*/ +QVariant QQmlDelegateModel::model() const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.model(); +} + +void QQmlDelegateModelPrivate::connectToAbstractItemModel() +{ + Q_Q(QQmlDelegateModel); + if (!m_adaptorModel.adaptsAim()) + return; + + auto aim = m_adaptorModel.aim(); + + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()), + q, QQmlDelegateModel, SLOT(_q_modelReset())); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); +} + +void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() +{ + Q_Q(QQmlDelegateModel); + if (!m_adaptorModel.adaptsAim()) + return; + + auto aim = m_adaptorModel.aim(); + + QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(_q_rowsInserted(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)), + q, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); + QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); + QObject::disconnect(aim, SIGNAL(modelReset()), + q, SLOT(_q_modelReset())); + QObject::disconnect(aim, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + q, SLOT(_q_layoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); +} + +void QQmlDelegateModel::setModel(const QVariant &model) +{ + Q_D(QQmlDelegateModel); + + if (d->m_complete) + _q_itemsRemoved(0, d->m_count); + + d->disconnectFromAbstractItemModel(); + d->m_adaptorModel.setModel(model, this, d->m_context->engine()); + d->connectToAbstractItemModel(); + + d->m_adaptorModel.replaceWatchedRoles(QList(), d->m_watchedRoles); + for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) { + d->m_adaptorModel.replaceWatchedRoles( + QList(), d->m_parts->models.at(i)->watchedRoles()); + } + + if (d->m_complete) { + _q_itemsInserted(0, d->adaptorModelCount()); + d->requestMoreIfNecessary(); + } +} + +/*! + \qmlproperty Component QtQml.Models::DelegateModel::delegate + + The delegate provides a template defining each item instantiated by a view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qml-data-models}{Data Model}. +*/ +QQmlComponent *QQmlDelegateModel::delegate() const +{ + Q_D(const QQmlDelegateModel); + return d->m_delegate; +} + +void QQmlDelegateModel::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQmlDelegateModel); + if (d->m_transaction) { + qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated."); + return; + } + if (d->m_delegate == delegate) + return; + bool wasValid = d->m_delegate != nullptr; + d->m_delegate.setObject(delegate, this); + d->m_delegateValidated = false; + if (d->m_delegateChooser) + QObject::disconnect(d->m_delegateChooserChanged); + + d->m_delegateChooser = nullptr; + if (delegate) { + QQmlAbstractDelegateComponent *adc = + qobject_cast(delegate); + if (adc) { + d->m_delegateChooser = adc; + d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, + [d](){ d->delegateChanged(); }); + } + } + d->delegateChanged(d->m_delegate, wasValid); +} + +/*! + \qmlproperty QModelIndex QtQml.Models::DelegateModel::rootIndex + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. \c rootIndex allows the children of + any node in a QAbstractItemModel to be provided by this model. + + This property only affects models of type QAbstractItemModel that + are hierarchical (e.g, a tree model). + + For example, here is a simple interactive file system browser. + When a directory name is clicked, the view's \c rootIndex is set to the + QModelIndex node of the clicked directory, thus updating the view to show + the new directory's contents. + + \c main.cpp: + \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0 + + \c view.qml: + \snippet delegatemodel/delegatemodel_rootindex/view.qml 0 + + If the \l {dm-model-property}{model} is a QAbstractItemModel subclass, + the delegate can also reference a \c hasModelChildren property (optionally + qualified by a \e model. prefix) that indicates whether the delegate's + model item has any child nodes. + + \sa modelIndex(), parentModelIndex() +*/ +QVariant QQmlDelegateModel::rootIndex() const +{ + Q_D(const QQmlDelegateModel); + return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex)); +} + +void QQmlDelegateModel::setRootIndex(const QVariant &root) +{ + Q_D(QQmlDelegateModel); + + QModelIndex modelIndex = qvariant_cast(root); + const bool changed = d->m_adaptorModel.rootIndex != modelIndex; + if (changed || !d->m_adaptorModel.isValid()) { + const int oldCount = d->m_count; + d->m_adaptorModel.rootIndex = modelIndex; + if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) { + // The previous root index was invalidated, so we need to reconnect the model. + d->disconnectFromAbstractItemModel(); + d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine()); + d->connectToAbstractItemModel(); + } + if (d->m_adaptorModel.canFetchMore()) + d->m_adaptorModel.fetchMore(); + if (d->m_complete) { + const int newCount = d->adaptorModelCount(); + if (oldCount) + _q_itemsRemoved(0, oldCount); + if (newCount) + _q_itemsInserted(0, newCount); + } + if (changed) + emit rootIndexChanged(); + } +} + +/*! + \qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index) + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the specified index. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ +QVariant QQmlDelegateModel::modelIndex(int idx) const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.modelIndex(idx); +} + +/*! + \qmlmethod QModelIndex QtQml.Models::DelegateModel::parentModelIndex() + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the parent of the current rootIndex. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ +QVariant QQmlDelegateModel::parentModelIndex() const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.parentModelIndex(); +} + +/*! + \qmlproperty int QtQml.Models::DelegateModel::count +*/ + +int QQmlDelegateModel::count() const +{ + Q_D(const QQmlDelegateModel); + if (!d->m_delegate) + return 0; + return d->m_compositor.count(d->m_compositorGroup); +} + +QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object) +{ + if (!object) + return QQmlDelegateModel::ReleaseFlags(0); + + QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object); + if (!cacheItem) + return QQmlDelegateModel::ReleaseFlags(0); + + if (!cacheItem->releaseObject()) + return QQmlDelegateModel::Referenced; + + cacheItem->destroyObject(); + emitDestroyingItem(object); + if (cacheItem->incubationTask) { + releaseIncubator(cacheItem->incubationTask); + cacheItem->incubationTask = nullptr; + } + cacheItem->Dispose(); + return QQmlInstanceModel::Destroyed; +} + +/* + Returns ReleaseStatus flags. +*/ + +QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) +{ + Q_D(QQmlDelegateModel); + QQmlInstanceModel::ReleaseFlags stat = d->release(item); + return stat; +} + +// Cancel a requested async item +void QQmlDelegateModel::cancel(int index) +{ + Q_D(QQmlDelegateModel); + if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { + qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup); + return; + } + + Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); + QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0; + if (cacheItem) { + if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) { + d->releaseIncubator(cacheItem->incubationTask); + cacheItem->incubationTask = nullptr; + + if (cacheItem->object) { + QObject *object = cacheItem->object; + cacheItem->destroyObject(); + if (QQuickPackage *package = qmlobject_cast(object)) + d->emitDestroyingPackage(package); + else + d->emitDestroyingItem(object); + } + + cacheItem->scriptRef -= 1; + } + if (!cacheItem->isReferenced()) { + d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag); + d->m_cache.removeAt(it.cacheIndex); + delete cacheItem; + Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache)); + } + } +} + +void QQmlDelegateModelPrivate::group_append( + QQmlListProperty *property, QQmlDelegateModelGroup *group) +{ + QQmlDelegateModelPrivate *d = static_cast(property->data); + if (d->m_complete) + return; + if (d->m_groupCount == Compositor::MaximumGroupCount) { + qmlWarning(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8"); + return; + } + d->m_groups[d->m_groupCount] = group; + d->m_groupCount += 1; +} + +int QQmlDelegateModelPrivate::group_count( + QQmlListProperty *property) +{ + QQmlDelegateModelPrivate *d = static_cast(property->data); + return d->m_groupCount - 1; +} + +QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at( + QQmlListProperty *property, int index) +{ + QQmlDelegateModelPrivate *d = static_cast(property->data); + return index >= 0 && index < d->m_groupCount - 1 + ? d->m_groups[index + 1] + : nullptr; +} + +/*! + \qmlproperty list QtQml.Models::DelegateModel::groups + + This property holds a delegate model's group definitions. + + Groups define a sub-set of the items in a delegate model and can be used to filter + a model. + + For every group defined in a DelegateModel two attached properties are added to each + delegate item. The first of the form DelegateModel.in\e{GroupName} holds whether the + item belongs to the group and the second DelegateModel.\e{groupName}Index holds the + index of the item in that group. + + The following example illustrates using groups to select items in a model. + + \snippet delegatemodel/delegatemodelgroup.qml 0 + \keyword dm-groups-property +*/ + +QQmlListProperty QQmlDelegateModel::groups() +{ + Q_D(QQmlDelegateModel); + return QQmlListProperty( + this, + d, + QQmlDelegateModelPrivate::group_append, + QQmlDelegateModelPrivate::group_count, + QQmlDelegateModelPrivate::group_at, + nullptr); +} + +/*! + \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items + + This property holds default group to which all new items are added. +*/ + +QQmlDelegateModelGroup *QQmlDelegateModel::items() +{ + Q_D(QQmlDelegateModel); + return d->m_items; +} + +/*! + \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems + + This property holds delegate model's persisted items group. + + Items in this group are not destroyed when released by a view, instead they are persisted + until removed from the group. + + An item can be removed from the persistedItems group by setting the + DelegateModel.inPersistedItems property to false. If the item is not referenced by a view + at that time it will be destroyed. Adding an item to this group will not create a new + instance. + + Items returned by the \l QtQml.Models::DelegateModelGroup::create() function are automatically added + to this group. +*/ + +QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems() +{ + Q_D(QQmlDelegateModel); + return d->m_persistedItems; +} + +/*! + \qmlproperty string QtQml.Models::DelegateModel::filterOnGroup + + This property holds name of the group that is used to filter the delegate model. + + Only items that belong to this group are visible to a view. + + By default this is the \l items group. +*/ + +QString QQmlDelegateModel::filterGroup() const +{ + Q_D(const QQmlDelegateModel); + return d->m_filterGroup; +} + +void QQmlDelegateModel::setFilterGroup(const QString &group) +{ + Q_D(QQmlDelegateModel); + + if (d->m_transaction) { + qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); + return; + } + + if (d->m_filterGroup != group) { + d->m_filterGroup = group; + d->updateFilterGroup(); + emit filterGroupChanged(); + } +} + +void QQmlDelegateModel::resetFilterGroup() +{ + setFilterGroup(QStringLiteral("items")); +} + +void QQmlDelegateModelPrivate::updateFilterGroup() +{ + Q_Q(QQmlDelegateModel); + if (!m_cacheMetaType) + return; + + QQmlListCompositor::Group previousGroup = m_compositorGroup; + m_compositorGroup = Compositor::Default; + for (int i = 1; i < m_groupCount; ++i) { + if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) { + m_compositorGroup = Compositor::Group(i); + break; + } + } + + QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this); + if (m_compositorGroup != previousGroup) { + QVector removes; + QVector inserts; + m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); + + QQmlChangeSet changeSet; + changeSet.move(removes, inserts); + emit q->modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit q->countChanged(); + + if (m_parts) { + auto partsCopy = m_parts->models; // deliberate; this may alter m_parts + for (QQmlPartsModel *model : qAsConst(partsCopy)) + model->updateFilterGroup(m_compositorGroup, changeSet); + } + } +} + +/*! + \qmlproperty object QtQml.Models::DelegateModel::parts + + The \a parts property selects a DelegateModel which creates + delegates from the part named. This is used in conjunction with + the \l Package type. + + For example, the code below selects a model which creates + delegates named \e list from a \l Package: + + \code + DelegateModel { + id: visualModel + delegate: Package { + Item { Package.name: "list" } + } + model: myModel + } + + ListView { + width: 200; height:200 + model: visualModel.parts.list + } + \endcode + + \sa Package +*/ + +QObject *QQmlDelegateModel::parts() +{ + Q_D(QQmlDelegateModel); + if (!d->m_parts) + d->m_parts = new QQmlDelegateModelParts(this); + return d->m_parts; +} + +const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr; +} + +void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package); +} + +void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package); +} + +void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package); +} + +static bool isDoneIncubating(QQmlIncubator::Status status) +{ + return status == QQmlIncubator::Ready || status == QQmlIncubator::Error; +} + +void QQDMIncubationTask::statusChanged(Status status) +{ + if (vdm) { + vdm->incubatorStatusChanged(this, status); + } else if (isDoneIncubating(status)) { + Q_ASSERT(incubating); + // The model was deleted from under our feet, cleanup ourselves + delete incubating->object; + incubating->object = nullptr; + if (incubating->contextData) { + incubating->contextData->invalidate(); + Q_ASSERT(incubating->contextData->refCount == 1); + incubating->contextData = nullptr; + } + incubating->scriptRef = 0; + incubating->deleteLater(); + } +} + +void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask) +{ + Q_Q(QQmlDelegateModel); + if (!incubationTask->isError()) + incubationTask->clear(); + m_finishedIncubating.append(incubationTask); + if (!m_incubatorCleanupScheduled) { + m_incubatorCleanupScheduled = true; + QCoreApplication::postEvent(q, new QEvent(QEvent::User)); + } +} + +void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it) +{ + m_cache.insert(it.cacheIndex, item); + m_compositor.setFlags(it, 1, Compositor::CacheFlag); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); +} + +void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem) +{ + int cidx = m_cache.lastIndexOf(cacheItem); + if (cidx >= 0) { + m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag); + m_cache.removeAt(cidx); + } + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); +} + +void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status) +{ + if (!isDoneIncubating(status)) + return; + + const QList incubationTaskErrors = incubationTask->errors(); + + QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + cacheItem->incubationTask = nullptr; + incubationTask->incubating = nullptr; + releaseIncubator(incubationTask); + + if (status == QQmlIncubator::Ready) { + cacheItem->referenceObject(); + if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) + emitCreatedPackage(incubationTask, package); + else + emitCreatedItem(incubationTask, cacheItem->object); + cacheItem->releaseObject(); + } else if (status == QQmlIncubator::Error) { + qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate"; + } + + if (!cacheItem->isObjectReferenced()) { + if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) + emitDestroyingPackage(package); + else + emitDestroyingItem(cacheItem->object); + delete cacheItem->object; + cacheItem->object = nullptr; + cacheItem->scriptRef -= 1; + if (cacheItem->contextData) { + cacheItem->contextData->invalidate(); + Q_ASSERT(cacheItem->contextData->refCount == 1); + } + cacheItem->contextData = nullptr; + + if (!cacheItem->isReferenced()) { + removeCacheItem(cacheItem); + delete cacheItem; + } + } +} + +void QQDMIncubationTask::setInitialState(QObject *o) +{ + vdm->setInitialState(this, o); +} + +void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) +{ + QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + cacheItem->object = o; + + if (QQuickPackage *package = qmlobject_cast(cacheItem->object)) + emitInitPackage(incubationTask, package); + else + emitInitItem(incubationTask, cacheItem->object); +} + +QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode) +{ + if (!m_delegate || index < 0 || index >= m_compositor.count(group)) { + qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group); + return nullptr; + } else if (!m_context || !m_context->isValid()) { + return nullptr; + } + + Compositor::iterator it = m_compositor.find(group, index); + + QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0; + + if (!cacheItem) { + cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex()); + if (!cacheItem) + return nullptr; + + cacheItem->groups = it->flags; + addCacheItem(cacheItem, it); + } + + // Bump the reference counts temporarily so neither the content data or the delegate object + // are deleted if incubatorStatusChanged() is called synchronously. + cacheItem->scriptRef += 1; + cacheItem->referenceObject(); + + if (cacheItem->incubationTask) { + bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); + if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) { + // previously requested async - now needed immediately + cacheItem->incubationTask->forceCompletion(); + } + } else if (!cacheItem->object) { + QQmlComponent *delegate = m_delegate; + if (m_delegateChooser) { + QQmlAbstractDelegateComponent *chooser = m_delegateChooser; + do { + delegate = chooser->delegate(&m_adaptorModel, index); + chooser = qobject_cast(delegate); + } while (chooser); + if (!delegate) + return nullptr; + } + + QQmlContext *creationContext = delegate->creationContext(); + + cacheItem->scriptRef += 1; + + cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode); + cacheItem->incubationTask->incubating = cacheItem; + cacheItem->incubationTask->clear(); + + for (int i = 1; i < m_groupCount; ++i) + cacheItem->incubationTask->index[i] = it.index[i]; + + QQmlContextData *ctxt = new QQmlContextData; + ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); + ctxt->contextObject = cacheItem; + cacheItem->contextData = ctxt; + + if (m_adaptorModel.hasProxyObject()) { + if (QQmlAdaptorModelProxyInterface *proxy + = qobject_cast(cacheItem)) { + ctxt = new QQmlContextData; + ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); + QObject *proxied = proxy->proxiedObject(); + ctxt->contextObject = proxied; + // We don't own the proxied object. We need to clear it if it goes away. + QObject::connect(proxied, &QObject::destroyed, + cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); + } + } + + QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate); + cp->incubateObject( + cacheItem->incubationTask, + delegate, + m_context->engine(), + ctxt, + QQmlContextData::get(m_context)); + } + + if (index == m_compositor.count(group) - 1) + requestMoreIfNecessary(); + + // Remove the temporary reference count. + cacheItem->scriptRef -= 1; + if (cacheItem->object && (!cacheItem->incubationTask || isDoneIncubating(cacheItem->incubationTask->status()))) + return cacheItem->object; + + cacheItem->releaseObject(); + if (!cacheItem->isReferenced()) { + removeCacheItem(cacheItem); + delete cacheItem; + } + + return nullptr; +} + +/* + If asynchronous is true or the component is being loaded asynchronously due + to an ancestor being loaded asynchronously, object() may return 0. In this + case createdItem() will be emitted when the object is available. The object + at this stage does not have any references, so object() must be called again + to ensure a reference is held. Any call to object() which returns a valid object + must be matched by a call to release() in order to destroy the object. +*/ +QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode) +{ + Q_D(QQmlDelegateModel); + if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { + qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup); + return nullptr; + } + + return d->object(d->m_compositorGroup, index, incubationMode); +} + +QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index) +{ + Q_D(QQmlDelegateModel); + Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); + if (!it->inCache()) + return QQmlIncubator::Null; + + if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask) + return incubationTask->status(); + + return QQmlIncubator::Ready; +} + +QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name) +{ + Compositor::iterator it = m_compositor.find(group, index); + if (QQmlAdaptorModel *model = it.list()) { + QString role = name; + int dot = name.indexOf(QLatin1Char('.')); + if (dot > 0) + role = name.left(dot); + QVariant value = model->value(it.modelIndex(), role); + while (dot > 0) { + QObject *obj = qvariant_cast(value); + if (!obj) + return QVariant(); + const int from = dot + 1; + dot = name.indexOf(QLatin1Char('.'), from); + value = obj->property(name.midRef(from, dot - from).toUtf8()); + } + return value; + } + return QVariant(); +} + +QVariant QQmlDelegateModel::variantValue(int index, const QString &role) +{ + Q_D(QQmlDelegateModel); + return d->variantValue(d->m_compositorGroup, index, role); +} + +int QQmlDelegateModel::indexOf(QObject *item, QObject *) const +{ + Q_D(const QQmlDelegateModel); + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item)) + return cacheItem->groupIndex(d->m_compositorGroup); + return -1; +} + +void QQmlDelegateModel::setWatchedRoles(const QList &roles) +{ + Q_D(QQmlDelegateModel); + d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles); + d->m_watchedRoles = roles; +} + +void QQmlDelegateModelPrivate::addGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector inserts; + m_compositor.setFlags(from, count, group, groupFlags, &inserts); + itemsInserted(inserts); + emitChanges(); +} + +void QQmlDelegateModelPrivate::removeGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector removes; + m_compositor.clearFlags(from, count, group, groupFlags, &removes); + itemsRemoved(removes); + emitChanges(); +} + +void QQmlDelegateModelPrivate::setGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector removes; + QVector inserts; + + m_compositor.setFlags(from, count, group, groupFlags, &inserts); + itemsInserted(inserts); + const int removeFlags = ~groupFlags & Compositor::GroupMask; + + from = m_compositor.find(from.group, from.index[from.group]); + m_compositor.clearFlags(from, count, group, removeFlags, &removes); + itemsRemoved(removes); + emitChanges(); +} + +bool QQmlDelegateModel::event(QEvent *e) +{ + Q_D(QQmlDelegateModel); + if (e->type() == QEvent::UpdateRequest) { + d->m_waitingToFetchMore = false; + d->m_adaptorModel.fetchMore(); + } else if (e->type() == QEvent::User) { + d->m_incubatorCleanupScheduled = false; + qDeleteAll(d->m_finishedIncubating); + d->m_finishedIncubating.clear(); + } + return QQmlInstanceModel::event(e); +} + +void QQmlDelegateModelPrivate::itemsChanged(const QVector &changes) +{ + if (!m_delegate) + return; + + QVarLengthArray, Compositor::MaximumGroupCount> translatedChanges(m_groupCount); + + for (const Compositor::Change &change : changes) { + for (int i = 1; i < m_groupCount; ++i) { + if (change.inGroup(i)) { + translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count)); + } + } + } + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i)); +} + +void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector &roles) +{ + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) { + QVector changes; + d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes); + d->itemsChanged(changes); + d->emitChanges(); + } +} + +static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas) +{ + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < count; ++i) + incubationTask->index[i] += deltas[i]; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < qMin(count, Compositor::MaximumGroupCount); ++i) + attached->m_currentIndex[i] += deltas[i]; + } +} + +void QQmlDelegateModelPrivate::itemsInserted( + const QVector &inserts, + QVarLengthArray, Compositor::MaximumGroupCount> *translatedInserts, + QHash > *movedItems) +{ + int cacheIndex = 0; + + int inserted[Compositor::MaximumGroupCount]; + for (int i = 1; i < m_groupCount; ++i) + inserted[i] = 0; + + for (const Compositor::Insert &insert : inserts) { + for (; cacheIndex < insert.cacheIndex; ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted); + + for (int i = 1; i < m_groupCount; ++i) { + if (insert.inGroup(i)) { + (*translatedInserts)[i].append( + QQmlChangeSet::Change(insert.index[i], insert.count, insert.moveId)); + inserted[i] += insert.count; + } + } + + if (!insert.inCache()) + continue; + + if (movedItems && insert.isMove()) { + QList items = movedItems->take(insert.moveId); + Q_ASSERT(items.count() == insert.count); + m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex); + } + if (insert.inGroup()) { + for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) { + QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); + cacheItem->groups |= insert.flags & Compositor::GroupMask; + + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < m_groupCount; ++i) + incubationTask->index[i] = cacheItem->groups & (1 << i) + ? insert.index[i] + offset + : insert.index[i]; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) + attached->m_currentIndex[i] = cacheItem->groups & (1 << i) + ? insert.index[i] + offset + : insert.index[i]; + } + } + } else { + cacheIndex = insert.cacheIndex + insert.count; + } + } + for (const QList cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) + incrementIndexes(cache.at(cacheIndex), m_groupCount, inserted); +} + +void QQmlDelegateModelPrivate::itemsInserted(const QVector &inserts) +{ + QVarLengthArray, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); + itemsInserted(inserts, &translatedInserts); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i)); +} + +void QQmlDelegateModel::_q_itemsInserted(int index, int count) +{ + + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + d->m_count += count; + + const QList cache = d->m_cache; + for (int i = 0, c = cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = cache.at(i); + if (item->modelIndex() >= index) { + const int newIndex = item->modelIndex() + count; + const int row = newIndex; + const int column = 0; + item->setModelIndex(newIndex, row, column); + } + } + + QVector inserts; + d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts); + d->itemsInserted(inserts); + d->emitChanges(); +} + +//### This method should be split in two. It will remove delegates, and it will re-render the list. +// When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on +// QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on +// QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is +// that the destruction of an item will emit a changed signal that ends up at the delegate, which +// in turn will try to load the data from the model (which should have already freed it), resulting +// in a use-after-free. See QTBUG-59256. +void QQmlDelegateModelPrivate::itemsRemoved( + const QVector &removes, + QVarLengthArray, Compositor::MaximumGroupCount> *translatedRemoves, + QHash > *movedItems) +{ + int cacheIndex = 0; + int removedCache = 0; + + int removed[Compositor::MaximumGroupCount]; + for (int i = 1; i < m_groupCount; ++i) + removed[i] = 0; + + for (const Compositor::Remove &remove : removes) { + for (; cacheIndex < remove.cacheIndex; ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed); + + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) { + (*translatedRemoves)[i].append( + QQmlChangeSet::Change(remove.index[i], remove.count, remove.moveId)); + removed[i] -= remove.count; + } + } + + if (!remove.inCache()) + continue; + + if (movedItems && remove.isMove()) { + movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count)); + QList::iterator begin = m_cache.begin() + remove.cacheIndex; + QList::iterator end = begin + remove.count; + m_cache.erase(begin, end); + } else { + for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) { + QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); + if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) { + QObject *object = cacheItem->object; + cacheItem->destroyObject(); + if (QQuickPackage *package = qmlobject_cast(object)) + emitDestroyingPackage(package); + else + emitDestroyingItem(object); + cacheItem->scriptRef -= 1; + } + if (!cacheItem->isReferenced()) { + m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag); + m_cache.removeAt(cacheIndex); + delete cacheItem; + --cacheIndex; + ++removedCache; + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + } else if (remove.groups() == cacheItem->groups) { + cacheItem->groups = 0; + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < m_groupCount; ++i) + incubationTask->index[i] = -1; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) + attached->m_currentIndex[i] = -1; + } + } else { + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + if (!cacheItem->isObjectReferenced()) { + releaseIncubator(cacheItem->incubationTask); + cacheItem->incubationTask = nullptr; + if (cacheItem->object) { + QObject *object = cacheItem->object; + cacheItem->destroyObject(); + if (QQuickPackage *package = qmlobject_cast(object)) + emitDestroyingPackage(package); + else + emitDestroyingItem(object); + } + cacheItem->scriptRef -= 1; + } else { + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) + incubationTask->index[i] = remove.index[i]; + } + } + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) + attached->m_currentIndex[i] = remove.index[i]; + } + } + cacheItem->groups &= ~remove.flags; + } + } + } + } + + for (const QList cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) + incrementIndexes(cache.at(cacheIndex), m_groupCount, removed); +} + +void QQmlDelegateModelPrivate::itemsRemoved(const QVector &removes) +{ + QVarLengthArray, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); + itemsRemoved(removes, &translatedRemoves); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i)); +} + +void QQmlDelegateModel::_q_itemsRemoved(int index, int count) +{ + Q_D(QQmlDelegateModel); + if (count <= 0|| !d->m_complete) + return; + + d->m_count -= count; + const QList cache = d->m_cache; + for (int i = 0, c = cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = cache.at(i); + // layout change triggered by removal of a previous item might have + // already invalidated this item in d->m_cache and deleted it + if (!d->m_cache.contains(item)) + continue; + + if (item->modelIndex() >= index + count) { + const int newIndex = item->modelIndex() - count; + const int row = newIndex; + const int column = 0; + item->setModelIndex(newIndex, row, column); + } else if (item->modelIndex() >= index) { + item->setModelIndex(-1, -1, -1); + } + } + + QVector removes; + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes); + d->itemsRemoved(removes); + + d->emitChanges(); +} + +void QQmlDelegateModelPrivate::itemsMoved( + const QVector &removes, const QVector &inserts) +{ + QHash > movedItems; + + QVarLengthArray, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); + itemsRemoved(removes, &translatedRemoves, &movedItems); + + QVarLengthArray, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); + itemsInserted(inserts, &translatedInserts, &movedItems); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + Q_ASSERT(movedItems.isEmpty()); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) { + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move( + translatedRemoves.at(i), + translatedInserts.at(i)); + } +} + +void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count) +{ + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + const int minimum = qMin(from, to); + const int maximum = qMax(from, to) + count; + const int difference = from > to ? count : -count; + + const QList cache = d->m_cache; + for (int i = 0, c = cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = cache.at(i); + if (item->modelIndex() >= from && item->modelIndex() < from + count) { + const int newIndex = item->modelIndex() - from + to; + const int row = newIndex; + const int column = 0; + item->setModelIndex(newIndex, row, column); + } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) { + const int newIndex = item->modelIndex() + difference; + const int row = newIndex; + const int column = 0; + item->setModelIndex(newIndex, row, column); + } + } + + QVector removes; + QVector inserts; + d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts); + d->itemsMoved(removes, inserts); + d->emitChanges(); +} + +void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + Q_Q(QQmlDelegateModel); + emit q->modelUpdated(changeSet, reset); + if (changeSet.difference() != 0) + emit q->countChanged(); +} + +void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove) +{ + Q_Q(QQmlDelegateModel); + if (!m_complete) + return; + + if (m_transaction) { + qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated."); + return; + } + + if (remove) { + for (int i = 1; i < m_groupCount; ++i) { + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove( + 0, m_compositor.count(Compositor::Group(i))); + } + } + if (add) { + for (int i = 1; i < m_groupCount; ++i) { + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert( + 0, m_compositor.count(Compositor::Group(i))); + } + } + emitChanges(); +} + +void QQmlDelegateModelPrivate::emitChanges() +{ + if (m_transaction || !m_complete || !m_context || !m_context->isValid()) + return; + + m_transaction = true; + QV4::ExecutionEngine *engine = m_context->engine()->handle(); + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine); + m_transaction = false; + + const bool reset = m_reset; + m_reset = false; + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); + + auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache + for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) { + if (cacheItem->attached) + cacheItem->attached->emitChanges(); + } +} + +void QQmlDelegateModel::_q_modelReset() +{ + Q_D(QQmlDelegateModel); + if (!d->m_delegate) + return; + + int oldCount = d->m_count; + d->m_adaptorModel.rootIndex = QModelIndex(); + + if (d->m_complete) { + d->m_count = d->adaptorModelCount(); + + const QList cache = d->m_cache; + for (int i = 0, c = cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = cache.at(i); + if (item->modelIndex() != -1) + item->setModelIndex(-1, -1, -1); + } + + QVector removes; + QVector inserts; + if (oldCount) + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); + if (d->m_count) + d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts); + d->itemsMoved(removes, inserts); + d->m_reset = true; + + if (d->m_adaptorModel.canFetchMore()) + d->m_adaptorModel.fetchMore(); + + d->emitChanges(); + } + emit rootIndexChanged(); +} + +void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (parent == d->m_adaptorModel.rootIndex) + _q_itemsInserted(begin, end - begin + 1); +} + +void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (!d->m_adaptorModel.rootIndex.isValid()) + return; + const QModelIndex index = d->m_adaptorModel.rootIndex; + if (index.parent() == parent && index.row() >= begin && index.row() <= end) { + const int oldCount = d->m_count; + d->m_count = 0; + d->disconnectFromAbstractItemModel(); + d->m_adaptorModel.invalidateModel(); + + if (d->m_complete && oldCount > 0) { + QVector removes; + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); + d->itemsRemoved(removes); + d->emitChanges(); + } + } +} + +void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (parent == d->m_adaptorModel.rootIndex) + _q_itemsRemoved(begin, end - begin + 1); +} + +void QQmlDelegateModel::_q_rowsMoved( + const QModelIndex &sourceParent, int sourceStart, int sourceEnd, + const QModelIndex &destinationParent, int destinationRow) +{ + Q_D(QQmlDelegateModel); + const int count = sourceEnd - sourceStart + 1; + if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) { + _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count); + } else if (sourceParent == d->m_adaptorModel.rootIndex) { + _q_itemsRemoved(sourceStart, count); + } else if (destinationParent == d->m_adaptorModel.rootIndex) { + _q_itemsInserted(destinationRow, count); + } +} + +void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector &roles) +{ + Q_D(QQmlDelegateModel); + if (begin.parent() == d->m_adaptorModel.rootIndex) + _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles); +} + +bool QQmlDelegateModel::isDescendantOf(const QPersistentModelIndex& desc, const QList< QPersistentModelIndex >& parents) const +{ + for (int i = 0, c = parents.count(); i < c; ++i) { + for (QPersistentModelIndex parent = desc; parent.isValid(); parent = parent.parent()) { + if (parent == parents[i]) + return true; + } + } + + return false; +} + +void QQmlDelegateModel::_q_layoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) +{ + Q_D(QQmlDelegateModel); + if (!d->m_complete) + return; + + if (hint == QAbstractItemModel::VerticalSortHint) { + if (!parents.isEmpty() && d->m_adaptorModel.rootIndex.isValid() && !isDescendantOf(d->m_adaptorModel.rootIndex, parents)) { + return; + } + + // mark all items as changed + _q_itemsChanged(0, d->m_count, QVector()); + + } else if (hint == QAbstractItemModel::HorizontalSortHint) { + // Ignored + } else { + // We don't know what's going on, so reset the model + _q_modelReset(); + } +} + +QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj) +{ + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) { + if (cacheItem->object == obj) { // Don't create attached item for child objects. + cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj); + return cacheItem->attached; + } + } + return new QQmlDelegateModelAttached(obj); +} + +bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups) +{ + if (!m_context || !m_context->isValid()) + return false; + + QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1); + if (!cacheItem) + return false; + if (!object.isObject()) + return false; + + QV4::ExecutionEngine *v4 = object.as()->engine(); + QV4::Scope scope(v4); + QV4::ScopedObject o(scope, object); + if (!o) + return false; + + QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedValue propertyName(scope); + QV4::ScopedValue v(scope); + while (1) { + propertyName = it.nextPropertyNameAsString(v); + if (propertyName->isNull()) + break; + cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QVariant::Invalid)); + } + + cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag; + + // Must be before the new object is inserted into the cache or its indexes will be adjusted too. + itemsInserted(QVector(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag))); + + before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups); + m_cache.insert(before.cacheIndex, cacheItem); + + return true; +} + +//============================================================================ + +QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType( + QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames) + : model(model) + , groupCount(groupNames.count() + 1) + , v4Engine(engine) + , metaObject(nullptr) + , groupNames(groupNames) +{ +} + +QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType() +{ + if (metaObject) + metaObject->release(); +} + +void QQmlDelegateModelItemMetaType::initializeMetaObject() +{ + QMetaObjectBuilder builder; + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className()); + builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject); + + int notifierId = 0; + for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { + QString propertyName = QLatin1String("in") + groupNames.at(i); + propertyName.replace(2, 1, propertyName.at(2).toUpper()); + builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); + QMetaPropertyBuilder propertyBuilder = builder.addProperty( + propertyName.toUtf8(), "bool", notifierId); + propertyBuilder.setWritable(true); + } + for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { + const QString propertyName = groupNames.at(i) + QLatin1String("Index"); + builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); + QMetaPropertyBuilder propertyBuilder = builder.addProperty( + propertyName.toUtf8(), "int", notifierId); + propertyBuilder.setWritable(true); + } + + metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject()); +} + +void QQmlDelegateModelItemMetaType::initializePrototype() +{ + QV4::Scope scope(v4Engine); + + QV4::ScopedObject proto(scope, v4Engine->newObject()); + proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr); + proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups); + QV4::ScopedString s(scope); + QV4::ScopedProperty p(scope); + + s = v4Engine->newString(QStringLiteral("isUnresolved")); + QV4::ScopedFunctionObject f(scope); + QV4::ExecutionContext *global = scope.engine->rootContext(); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member))); + p->setSetter(nullptr); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + + s = v4Engine->newString(QStringLiteral("inItems")); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member))); + p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member))); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + + s = v4Engine->newString(QStringLiteral("inPersistedItems")); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member))); + p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member))); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + + s = v4Engine->newString(QStringLiteral("itemsIndex")); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index))); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + + s = v4Engine->newString(QStringLiteral("persistedItemsIndex")); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index))); + p->setSetter(nullptr); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + + for (int i = 2; i < groupNames.count(); ++i) { + QString propertyName = QLatin1String("in") + groupNames.at(i); + propertyName.replace(2, 1, propertyName.at(2).toUpper()); + s = v4Engine->newString(propertyName); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member))); + p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member))); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + } + for (int i = 2; i < groupNames.count(); ++i) { + const QString propertyName = groupNames.at(i) + QLatin1String("Index"); + s = v4Engine->newString(propertyName); + p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index))); + p->setSetter(nullptr); + proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + } + modelItemProto.set(v4Engine, proto); +} + +int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const +{ + int groupFlags = 0; + for (const QString &groupName : groups) { + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + } + return groupFlags; +} + +int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const +{ + int groupFlags = 0; + QV4::Scope scope(v4Engine); + + QV4::ScopedString s(scope, groups); + if (s) { + const QString groupName = s->toQString(); + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + return groupFlags; + } + + QV4::ScopedArrayObject array(scope, groups); + if (array) { + QV4::ScopedValue v(scope); + uint arrayLength = array->getLength(); + for (uint i = 0; i < arrayLength; ++i) { + v = array->get(i); + const QString groupName = v->toQString(); + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + } + } + return groupFlags; +} + +QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) +{ + QV4::Scope scope(b); + QV4::Scoped o(scope, thisObject->as()); + if (!o) + return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + if (!o->d()->item->metaType->model) + RETURN_UNDEFINED(); + + return o->d()->item->get(); +} + +QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) +{ + QV4::Scope scope(b); + QV4::Scoped o(scope, thisObject->as()); + if (!o) + return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + + QStringList groups; + for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) { + if (o->d()->item->groups & (1 << i)) + groups.append(o->d()->item->metaType->groupNames.at(i - 1)); + } + + return scope.engine->fromVariant(groups); +} + +QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) +{ + QV4::Scope scope(b); + QV4::Scoped o(scope, thisObject->as()); + if (!o) + return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); + + if (!argc) + THROW_TYPE_ERROR(); + + if (!o->d()->item->metaType->model) + RETURN_UNDEFINED(); + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model); + + const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]); + const int cacheIndex = model->m_cache.indexOf(o->d()->item); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + model->setGroups(it, 1, Compositor::Cache, groupFlags); + return QV4::Encode::undefined(); +} + +QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) +{ + return QV4::Encode(bool(thisItem->groups & (1 << flag))); +} + +QV4::ReturnedValue QQmlDelegateModelItem::set_member(QQmlDelegateModelItem *cacheItem, uint flag, const QV4::Value &arg) +{ + if (!cacheItem->metaType->model) + return QV4::Encode::undefined(); + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model); + + bool member = arg.toBoolean(); + uint groupFlag = (1 << flag); + if (member == ((cacheItem->groups & groupFlag) != 0)) + return QV4::Encode::undefined(); + + const int cacheIndex = model->m_cache.indexOf(cacheItem); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + if (member) + model->addGroups(it, 1, Compositor::Cache, groupFlag); + else + model->removeGroups(it, 1, Compositor::Cache, groupFlag); + return QV4::Encode::undefined(); +} + +QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) +{ + return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag))); +} + +void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject) +{ + if (!contextData) + return; + + for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) { + if (ctxt->contextObject == childContextObject) + ctxt->contextObject = nullptr; + } +} + + +//--------------------------------------------------------------------------- + +DEFINE_OBJECT_VTABLE(QQmlDelegateModelItemObject); + +void QV4::Heap::QQmlDelegateModelItemObject::destroy() +{ + item->Dispose(); + Object::destroy(); +} + + +QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, + QQmlAdaptorModel::Accessors *accessor, + int modelIndex, int row, int column) + : v4(metaType->v4Engine) + , metaType(metaType) + , contextData(nullptr) + , object(nullptr) + , attached(nullptr) + , incubationTask(nullptr) + , delegate(nullptr) + , poolTime(0) + , objectRef(0) + , scriptRef(0) + , groups(0) + , index(modelIndex) + , row(row) + , column(column) +{ + metaType->addref(); + + if (accessor->propertyCache) { + // The property cache in the accessor is common for all the model + // items in the model it wraps. It describes available model roles, + // together with revisioned properties like row, column and index, all + // which should be available in the delegate. We assign this cache to the + // model item so that the QML engine can use the revision information + // when resolving the properties (rather than falling back to just + // inspecting the QObject in the model item directly). + QQmlData *qmldata = QQmlData::get(this, true); + if (qmldata->propertyCache) + qmldata->propertyCache->release(); + qmldata->propertyCache = accessor->propertyCache.data(); + qmldata->propertyCache->addref(); + } +} + +QQmlDelegateModelItem::~QQmlDelegateModelItem() +{ + Q_ASSERT(scriptRef == 0); + Q_ASSERT(objectRef == 0); + Q_ASSERT(!object); + + if (incubationTask) { + if (metaType->model) + QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask); + else + delete incubationTask; + } + + metaType->release(); + +} + +void QQmlDelegateModelItem::Dispose() +{ + --scriptRef; + if (isReferenced()) + return; + + if (metaType->model) { + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); + model->removeCacheItem(this); + } + delete this; +} + +void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn) +{ + const int prevIndex = index; + const int prevRow = row; + const int prevColumn = column; + + index = idx; + row = newRow; + column = newColumn; + + if (idx != prevIndex) + emit modelIndexChanged(); + if (row != prevRow) + emit rowChanged(); + if (column != prevColumn) + emit columnChanged(); +} + +void QQmlDelegateModelItem::destroyObject() +{ + Q_ASSERT(object); + Q_ASSERT(contextData); + + QQmlData *data = QQmlData::get(object); + Q_ASSERT(data); + if (data->ownContext) { + data->ownContext->clearContext(); + if (data->ownContext->contextObject == object) + data->ownContext->contextObject = nullptr; + data->ownContext = nullptr; + data->context = nullptr; + } + object->deleteLater(); + + if (attached) { + attached->m_cacheItem = nullptr; + attached = nullptr; + } + + contextData->invalidate(); + contextData = nullptr; + object = nullptr; +} + +QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object) +{ + QQmlData *d = QQmlData::get(object); + QQmlContextData *context = d ? d->context : nullptr; + for (context = context ? context->parent : nullptr; context; context = context->parent) { + if (QQmlDelegateModelItem *cacheItem = qobject_cast( + context->contextObject)) { + return cacheItem; + } + } + return nullptr; +} + +int QQmlDelegateModelItem::groupIndex(Compositor::Group group) +{ + if (QQmlDelegateModelPrivate * const model = metaType->model + ? QQmlDelegateModelPrivate::get(metaType->model) + : nullptr) { + return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group]; + } + return -1; +} + +//--------------------------------------------------------------------------- + +QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject( + QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject) + : metaType(metaType) + , metaObject(metaObject) + , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount()) + , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count()) +{ + // Don't reference count the meta-type here as that would create a circular reference. + // Instead we rely the fact that the meta-type's reference count can't reach 0 without first + // destroying all delegates with attached objects. + *static_cast(this) = *metaObject; +} + +QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject() +{ + ::free(metaObject); +} + +void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *) +{ + release(); +} + +int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments) +{ + QQmlDelegateModelAttached *attached = static_cast(object); + if (call == QMetaObject::ReadProperty) { + if (_id >= indexPropertyOffset) { + Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1); + *static_cast(arguments[0]) = attached->m_currentIndex[group]; + return -1; + } else if (_id >= memberPropertyOffset) { + Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); + *static_cast(arguments[0]) = attached->m_cacheItem->groups & (1 << group); + return -1; + } + } else if (call == QMetaObject::WriteProperty) { + if (_id >= memberPropertyOffset) { + if (!metaType->model) + return -1; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); + Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); + const int groupFlag = 1 << group; + const bool member = attached->m_cacheItem->groups & groupFlag; + if (member && !*static_cast(arguments[0])) { + Compositor::iterator it = model->m_compositor.find( + group, attached->m_currentIndex[group]); + model->removeGroups(it, 1, group, groupFlag); + } else if (!member && *static_cast(arguments[0])) { + for (int i = 1; i < metaType->groupCount; ++i) { + if (attached->m_cacheItem->groups & (1 << i)) { + Compositor::iterator it = model->m_compositor.find( + Compositor::Group(i), attached->m_currentIndex[i]); + model->addGroups(it, 1, Compositor::Group(i), groupFlag); + break; + } + } + } + return -1; + } + } + return attached->qt_metacall(call, _id, arguments); +} + +QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent) + : m_cacheItem(nullptr) + , m_previousGroups(0) +{ + QQml_setParent_noEvent(this, parent); +} + +QQmlDelegateModelAttached::QQmlDelegateModelAttached( + QQmlDelegateModelItem *cacheItem, QObject *parent) + : m_cacheItem(cacheItem) + , m_previousGroups(cacheItem->groups) +{ + QQml_setParent_noEvent(this, parent); + resetCurrentIndex(); + // Let m_previousIndex be equal to m_currentIndex + std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex)); + + if (!cacheItem->metaType->metaObject) + cacheItem->metaType->initializeMetaObject(); + + QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject; + cacheItem->metaType->metaObject->addref(); +} + +void QQmlDelegateModelAttached::resetCurrentIndex() +{ + if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) { + for (int i = 1; i < qMin(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i) + m_currentIndex[i] = incubationTask->index[i]; + } else { + QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); + Compositor::iterator it = model->m_compositor.find( + Compositor::Cache, model->m_cache.indexOf(m_cacheItem)); + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) + m_currentIndex[i] = it.index[i]; + } +} + +/*! + \qmlattachedproperty model QtQml.Models::DelegateModel::model + + This attached property holds the data model this delegate instance belongs to. + + It is attached to each instance of the delegate. +*/ + +QQmlDelegateModel *QQmlDelegateModelAttached::model() const +{ + return m_cacheItem ? m_cacheItem->metaType->model : nullptr; +} + +/*! + \qmlattachedproperty stringlist QtQml.Models::DelegateModel::groups + + This attached property holds the name of DelegateModelGroups the item belongs to. + + It is attached to each instance of the delegate. +*/ + +QStringList QQmlDelegateModelAttached::groups() const +{ + QStringList groups; + + if (!m_cacheItem) + return groups; + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + if (m_cacheItem->groups & (1 << i)) + groups.append(m_cacheItem->metaType->groupNames.at(i - 1)); + } + return groups; +} + +void QQmlDelegateModelAttached::setGroups(const QStringList &groups) +{ + if (!m_cacheItem) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); + + const int groupFlags = model->m_cacheMetaType->parseGroups(groups); + const int cacheIndex = model->m_cache.indexOf(m_cacheItem); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + model->setGroups(it, 1, Compositor::Cache, groupFlags); +} + +/*! + \qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved + + This attached property indicates whether the visual item is bound to a data model index. + Returns true if the item is not bound to the model, and false if it is. + + An unresolved item can be bound to the data model using the DelegateModelGroup::resolve() + function. + + It is attached to each instance of the delegate. +*/ + +bool QQmlDelegateModelAttached::isUnresolved() const +{ + if (!m_cacheItem) + return false; + + return m_cacheItem->groups & Compositor::UnresolvedFlag; +} + +/*! + \qmlattachedproperty int QtQml.Models::DelegateModel::inItems + + This attached property holds whether the item belongs to the default \l items DelegateModelGroup. + + Changing this property will add or remove the item from the items group. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models::DelegateModel::itemsIndex + + This attached property holds the index of the item in the default \l items DelegateModelGroup. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models::DelegateModel::inPersistedItems + + This attached property holds whether the item belongs to the \l persistedItems DelegateModelGroup. + + Changing this property will add or remove the item from the items group. Change with caution + as removing an item from the persistedItems group will destroy the current instance if it is + not referenced by a model. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models::DelegateModel::persistedItemsIndex + + This attached property holds the index of the item in the \l persistedItems DelegateModelGroup. + + It is attached to each instance of the delegate. +*/ + +void QQmlDelegateModelAttached::emitChanges() +{ + const int groupChanges = m_previousGroups ^ m_cacheItem->groups; + m_previousGroups = m_cacheItem->groups; + + int indexChanges = 0; + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + if (m_previousIndex[i] != m_currentIndex[i]) { + m_previousIndex[i] = m_currentIndex[i]; + indexChanges |= (1 << i); + } + } + + int notifierId = 0; + const QMetaObject *meta = metaObject(); + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + if (groupChanges & (1 << i)) + QMetaObject::activate(this, meta, notifierId, nullptr); + } + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + if (indexChanges & (1 << i)) + QMetaObject::activate(this, meta, notifierId, nullptr); + } + + if (groupChanges) + emit groupsChanged(); +} + +//============================================================================ + +void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g) +{ + Q_ASSERT(!model); + model = m; + group = g; +} + +bool QQmlDelegateModelGroupPrivate::isChangedConnected() +{ + Q_Q(QQmlDelegateModelGroup); + IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &)); +} + +void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4) +{ + Q_Q(QQmlDelegateModelGroup); + if (isChangedConnected() && !changeSet.isEmpty()) { + emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())), + QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts()))); + } + if (changeSet.difference() != 0) + emit q->countChanged(); +} + +void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset) +{ + for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) + it->emitModelUpdated(changeSet, reset); + changeSet.clear(); +} + +typedef QQmlDelegateModelGroupEmitterList::iterator GroupEmitterListIt; + +void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package) +{ + for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) + it->createdPackage(index, package); +} + +void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package) +{ + for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) + it->initPackage(index, package); +} + +void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package) +{ + for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) + it->destroyingPackage(package); +} + +/*! + \qmltype DelegateModelGroup + \instantiates QQmlDelegateModelGroup + \inqmlmodule QtQml.Models + \ingroup qtquick-models + \brief Encapsulates a filtered set of visual data items. + + The DelegateModelGroup type provides a means to address the model data of a + DelegateModel's delegate items, as well as sort and filter these delegate + items. + + The initial set of instantiable delegate items in a DelegateModel is represented + by its \l {QtQml.Models::DelegateModel::items}{items} group, which normally directly reflects + the contents of the model assigned to DelegateModel::model. This set can be changed to + the contents of any other member of DelegateModel::groups by assigning the \l name of that + DelegateModelGroup to the DelegateModel::filterOnGroup property. + + The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns + information about group membership and indexes as well as model data. In combination + with the move() function this can be used to implement view sorting, with remove() to filter + items out of a view, or with setGroups() and \l Package delegates to categorize items into + different views. Different groups can only be sorted independently if they are disjunct. Moving + an item in one group will also move it in all other groups it is a part of. + + Data from models can be supplemented by inserting data directly into a DelegateModelGroup + with the insert() function. This can be used to introduce mock items into a view, or + placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes + available. + + Delegate items can also be instantiated directly from a DelegateModelGroup using the + create() function, making it possible to use DelegateModel without an accompanying view + type or to cherry-pick specific items that should be instantiated irregardless of whether + they're currently within a view's visible area. + + \sa {QML Dynamic View Ordering Tutorial} +*/ +QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent) + : QObject(*new QQmlDelegateModelGroupPrivate, parent) +{ +} + +QQmlDelegateModelGroup::QQmlDelegateModelGroup( + const QString &name, QQmlDelegateModel *model, int index, QObject *parent) + : QQmlDelegateModelGroup(parent) +{ + Q_D(QQmlDelegateModelGroup); + d->name = name; + d->setModel(model, Compositor::Group(index)); +} + +QQmlDelegateModelGroup::~QQmlDelegateModelGroup() +{ +} + +/*! + \qmlproperty string QtQml.Models::DelegateModelGroup::name + + This property holds the name of the group. + + Each group in a model must have a unique name starting with a lower case letter. +*/ + +QString QQmlDelegateModelGroup::name() const +{ + Q_D(const QQmlDelegateModelGroup); + return d->name; +} + +void QQmlDelegateModelGroup::setName(const QString &name) +{ + Q_D(QQmlDelegateModelGroup); + if (d->model) + return; + if (d->name != name) { + d->name = name; + emit nameChanged(); + } +} + +/*! + \qmlproperty int QtQml.Models::DelegateModelGroup::count + + This property holds the number of items in the group. +*/ + +int QQmlDelegateModelGroup::count() const +{ + Q_D(const QQmlDelegateModelGroup); + if (!d->model) + return 0; + return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group); +} + +/*! + \qmlproperty bool QtQml.Models::DelegateModelGroup::includeByDefault + + This property holds whether new items are assigned to this group by default. +*/ + +bool QQmlDelegateModelGroup::defaultInclude() const +{ + Q_D(const QQmlDelegateModelGroup); + return d->defaultInclude; +} + +void QQmlDelegateModelGroup::setDefaultInclude(bool include) +{ + Q_D(QQmlDelegateModelGroup); + if (d->defaultInclude != include) { + d->defaultInclude = include; + + if (d->model) { + if (include) + QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group); + else + QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group); + } + emit defaultIncludeChanged(); + } +} + +/*! + \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index) + + Returns a javascript object describing the item at \a index in the group. + + The returned object contains the same information that is available to a delegate from the + DelegateModel attached as well as the model for that item. It has the properties: + + \list + \li \b model The model data of the item. This is the same as the model context property in + a delegate + \li \b groups A list the of names of groups the item is a member of. This property can be + written to change the item's membership. + \li \b inItems Whether the item belongs to the \l {QtQml.Models::DelegateModel::items}{items} group. + Writing to this property will add or remove the item from the group. + \li \b itemsIndex The index of the item within the \l {QtQml.Models::DelegateModel::items}{items} group. + \li \b {in} Whether the item belongs to the dynamic group \e groupName. Writing to + this property will add or remove the item from the group. + \li \b {Index} The index of the item within the dynamic group \e groupName. + \li \b isUnresolved Whether the item is bound to an index in the model assigned to + DelegateModel::model. Returns true if the item is not bound to the model, and false if it is. + \endlist +*/ + +QJSValue QQmlDelegateModelGroup::get(int index) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return QJSValue(); + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (!model->m_context || !model->m_context->isValid()) { + return QJSValue(); + } else if (index < 0 || index >= model->m_compositor.count(d->group)) { + qmlWarning(this) << tr("get: index out of range"); + return QJSValue(); + } + + Compositor::iterator it = model->m_compositor.find(d->group, index); + QQmlDelegateModelItem *cacheItem = it->inCache() + ? model->m_cache.at(it.cacheIndex) + : 0; + + if (!cacheItem) { + cacheItem = model->m_adaptorModel.createItem( + model->m_cacheMetaType, it.modelIndex()); + if (!cacheItem) + return QJSValue(); + cacheItem->groups = it->flags; + + model->m_cache.insert(it.cacheIndex, cacheItem); + model->m_compositor.setFlags(it, 1, Compositor::CacheFlag); + } + + if (model->m_cacheMetaType->modelItemProto.isUndefined()) + model->m_cacheMetaType->initializePrototype(); + QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine; + QV4::Scope scope(v4); + QV4::ScopedObject o(scope, v4->memoryManager->allocate(cacheItem)); + QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value()); + o->setPrototypeOf(p); + ++cacheItem->scriptRef; + + return QJSValue(v4, o->asReturnedValue()); +} + +bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const +{ + if (value.isNumber()) { + *index = value.toInt32(); + return true; + } + + if (!value.isObject()) + return false; + + QV4::ExecutionEngine *v4 = value.as()->engine(); + QV4::Scope scope(v4); + QV4::Scoped object(scope, value); + + if (object) { + QQmlDelegateModelItem * const cacheItem = object->d()->item; + if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model + ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model) + : nullptr) { + *index = model->m_cache.indexOf(cacheItem); + *group = Compositor::Cache; + return true; + } + } + return false; +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined) + \qmlmethod QtQml.Models::DelegateModelGroup::insert(jsdict data, var groups = undefined) + + Creates a new entry at \a index in a DelegateModel with the values from \a data that + correspond to roles in the model assigned to DelegateModel::model. + + If no index is supplied the data is appended to the model. + + The optional \a groups parameter identifies the groups the new entry should belong to, + if unspecified this is equal to the group insert was called on. + + Data inserted into a DelegateModel can later be merged with an existing entry in + DelegateModel::model using the \l resolve() function. This can be used to create placeholder + items that are later replaced by actual data. +*/ + +void QQmlDelegateModelGroup::insert(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + int index = model->m_compositor.count(d->group); + Compositor::Group group = d->group; + + if (args->length() == 0) + return; + + int i = 0; + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[i]); + if (d->parseIndex(v, &index, &group)) { + if (index < 0 || index > model->m_compositor.count(group)) { + qmlWarning(this) << tr("insert: index out of range"); + return; + } + if (++i == args->length()) + return; + v = (*args)[i]; + } + + Compositor::insert_iterator before = index < model->m_compositor.count(group) + ? model->m_compositor.findInsertPosition(group, index) + : model->m_compositor.end(); + + int groups = 1 << d->group; + if (++i < args->length()) { + QV4::ScopedValue val(scope, (*args)[i]); + groups |= model->m_cacheMetaType->parseGroups(val); + } + + if (v->as()) { + return; + } else if (v->as()) { + model->insert(before, v, groups); + model->emitChanges(); + } +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::create(int index) + \qmlmethod QtQml.Models::DelegateModelGroup::create(int index, jsdict data, array groups = undefined) + \qmlmethod QtQml.Models::DelegateModelGroup::create(jsdict data, array groups = undefined) + + Returns a reference to the instantiated item at \a index in the group. + + If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item + referencing this new entry will be returned. The optional \a groups parameter identifies + the groups the new entry should belong to, if unspecified this is equal to the group create() + was called on. + + All items returned by create are added to the + \l {QtQml.Models::DelegateModel::persistedItems}{persistedItems} group. Items in this + group remain instantiated when not referenced by any view. +*/ + +void QQmlDelegateModelGroup::create(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + + if (args->length() == 0) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + int index = model->m_compositor.count(d->group); + Compositor::Group group = d->group; + + int i = 0; + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[i]); + if (d->parseIndex(v, &index, &group)) + ++i; + + if (i < args->length() && index >= 0 && index <= model->m_compositor.count(group)) { + v = (*args)[i]; + if (v->as()) { + int groups = 1 << d->group; + if (++i < args->length()) { + QV4::ScopedValue val(scope, (*args)[i]); + groups |= model->m_cacheMetaType->parseGroups(val); + } + + Compositor::insert_iterator before = index < model->m_compositor.count(group) + ? model->m_compositor.findInsertPosition(group, index) + : model->m_compositor.end(); + + index = before.index[d->group]; + group = d->group; + + if (!model->insert(before, v, groups)) { + return; + } + } + } + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlWarning(this) << tr("create: index out of range"); + return; + } + + QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested); + if (object) { + QVector inserts; + Compositor::iterator it = model->m_compositor.find(group, index); + model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts); + model->itemsInserted(inserts); + model->m_cache.at(it.cacheIndex)->releaseObject(); + } + + args->setReturnValue(QV4::QObjectWrapper::wrap(args->v4engine(), object)); + model->emitChanges(); +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::resolve(int from, int to) + + Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to. + + Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup + instead of being derived from a DelegateModel::model index. Resolving an item will replace + the item at the target index with the unresolved item. A resolved an item will reflect the data + of the source model at its bound index and will move when that index moves like any other item. + + If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and + replacement will be communicated to views as an atomic operation, creating the appearance + that the model contents have not changed, or if the unresolved and model item are not adjacent + that the previously unresolved item has simply moved. + +*/ +void QQmlDelegateModelGroup::resolve(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + if (args->length() < 2) + return; + + int from = -1; + int to = -1; + Compositor::Group fromGroup = d->group; + Compositor::Group toGroup = d->group; + + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[0]); + if (d->parseIndex(v, &from, &fromGroup)) { + if (from < 0 || from >= model->m_compositor.count(fromGroup)) { + qmlWarning(this) << tr("resolve: from index out of range"); + return; + } + } else { + qmlWarning(this) << tr("resolve: from index invalid"); + return; + } + + v = (*args)[1]; + if (d->parseIndex(v, &to, &toGroup)) { + if (to < 0 || to >= model->m_compositor.count(toGroup)) { + qmlWarning(this) << tr("resolve: to index out of range"); + return; + } + } else { + qmlWarning(this) << tr("resolve: to index invalid"); + return; + } + + Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from); + Compositor::iterator toIt = model->m_compositor.find(toGroup, to); + + if (!fromIt->isUnresolved()) { + qmlWarning(this) << tr("resolve: from is not an unresolved item"); + return; + } + if (!toIt->list) { + qmlWarning(this) << tr("resolve: to is not a model item"); + return; + } + + const int unresolvedFlags = fromIt->flags; + const int resolvedFlags = toIt->flags; + const int resolvedIndex = toIt.modelIndex(); + void * const resolvedList = toIt->list; + + QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex); + cacheItem->groups &= ~Compositor::UnresolvedFlag; + + if (toIt.cacheIndex > fromIt.cacheIndex) + toIt.decrementIndexes(1, unresolvedFlags); + if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from) + from += 1; + + model->itemsMoved( + QVector(1, Compositor::Remove(fromIt, 1, unresolvedFlags, 0)), + QVector(1, Compositor::Insert(toIt, 1, unresolvedFlags, 0))); + model->itemsInserted( + QVector(1, Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag))); + toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags); + model->itemsRemoved(QVector(1, Compositor::Remove(toIt, 1, resolvedFlags))); + + model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag); + model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags); + + if (resolvedFlags & Compositor::CacheFlag) + model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag); + + Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); + + if (!cacheItem->isReferenced()) { + Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem)); + model->m_cache.removeAt(toIt.cacheIndex); + model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag); + delete cacheItem; + Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); + } else { + cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex); + if (cacheItem->attached) + cacheItem->attached->emitUnresolvedChanged(); + } + + model->emitChanges(); +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::remove(int index, int count) + + Removes \a count items starting at \a index from the group. +*/ + +void QQmlDelegateModelGroup::remove(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + Compositor::Group group = d->group; + int index = -1; + int count = 1; + + if (args->length() == 0) + return; + + int i = 0; + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[0]); + if (!d->parseIndex(v, &index, &group)) { + qmlWarning(this) << tr("remove: invalid index"); + return; + } + + if (++i < args->length()) { + v = (*args)[i]; + if (v->isNumber()) + count = v->toInt32(); + } + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlWarning(this) << tr("remove: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlWarning(this) << tr("remove: invalid count"); + } else { + model->removeGroups(it, count, d->group, 1 << d->group); + } + } +} + +bool QQmlDelegateModelGroupPrivate::parseGroupArgs( + QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const +{ + if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType) + return false; + + if (args->length() < 2) + return false; + + int i = 0; + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[i]); + if (!parseIndex(v, index, group)) + return false; + + v = (*args)[++i]; + if (v->isNumber()) { + *count = v->toInt32(); + + if (++i == args->length()) + return false; + v = (*args)[i]; + } + + *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v); + + return true; +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::addGroups(int index, int count, stringlist groups) + + Adds \a count items starting at \a index to \a groups. +*/ + +void QQmlDelegateModelGroup::addGroups(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlWarning(this) << tr("addGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlWarning(this) << tr("addGroups: invalid count"); + } else { + model->addGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::removeGroups(int index, int count, stringlist groups) + + Removes \a count items starting at \a index from \a groups. +*/ + +void QQmlDelegateModelGroup::removeGroups(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlWarning(this) << tr("removeGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlWarning(this) << tr("removeGroups: invalid count"); + } else { + model->removeGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) + + Sets the \a groups \a count items starting at \a index belong to. +*/ + +void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlWarning(this) << tr("setGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlWarning(this) << tr("setGroups: invalid count"); + } else { + model->setGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) + + Sets the \a groups \a count items starting at \a index belong to. +*/ + +/*! + \qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count) + + Moves \a count at \a from in a group \a to a new position. + + \note The DelegateModel acts as a proxy model: it holds the delegates in a + different order than the \l{dm-model-property}{underlying model} has them. + Any subsequent changes to the underlying model will not undo whatever + reordering you have done via this function. +*/ + +void QQmlDelegateModelGroup::move(QQmlV4Function *args) +{ + Q_D(QQmlDelegateModelGroup); + + if (args->length() < 2) + return; + + Compositor::Group fromGroup = d->group; + Compositor::Group toGroup = d->group; + int from = -1; + int to = -1; + int count = 1; + + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue v(scope, (*args)[0]); + if (!d->parseIndex(v, &from, &fromGroup)) { + qmlWarning(this) << tr("move: invalid from index"); + return; + } + + v = (*args)[1]; + if (!d->parseIndex(v, &to, &toGroup)) { + qmlWarning(this) << tr("move: invalid to index"); + return; + } + + if (args->length() > 2) { + v = (*args)[2]; + if (v->isNumber()) + count = v->toInt32(); + } + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + if (count < 0) { + qmlWarning(this) << tr("move: invalid count"); + } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) { + qmlWarning(this) << tr("move: from index out of range"); + } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) { + qmlWarning(this) << tr("move: to index out of range"); + } else if (count > 0) { + QVector removes; + QVector inserts; + + model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts); + model->itemsMoved(removes, inserts); + model->emitChanges(); + } + +} + +/*! + \qmlsignal QtQml.Models::DelegateModelGroup::changed(array removed, array inserted) + + This signal is emitted when items have been removed from or inserted into the group. + + Each object in the \a removed and \a inserted arrays has two values; the \e index of the first + item inserted or removed and a \e count of the number of consecutive items inserted or removed. + + Each index is adjusted for previous changes with all removed items preceding any inserted + items. + + The corresponding handler is \c onChanged. +*/ + +//============================================================================ + +QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent) + : QQmlInstanceModel(*new QObjectPrivate, parent) + , m_model(model) + , m_part(part) + , m_compositorGroup(Compositor::Cache) + , m_inheritGroup(true) +{ + QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model); + if (d->m_cacheMetaType) { + QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this); + m_compositorGroup = Compositor::Default; + } else { + d->m_pendingParts.insert(this); + } +} + +QQmlPartsModel::~QQmlPartsModel() +{ +} + +QString QQmlPartsModel::filterGroup() const +{ + if (m_inheritGroup) + return m_model->filterGroup(); + return m_filterGroup; +} + +void QQmlPartsModel::setFilterGroup(const QString &group) +{ + if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) { + qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); + return; + } + + if (m_filterGroup != group || m_inheritGroup) { + m_filterGroup = group; + m_inheritGroup = false; + updateFilterGroup(); + + emit filterGroupChanged(); + } +} + +void QQmlPartsModel::resetFilterGroup() +{ + if (!m_inheritGroup) { + m_inheritGroup = true; + updateFilterGroup(); + emit filterGroupChanged(); + } +} + +void QQmlPartsModel::updateFilterGroup() +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + if (!model->m_cacheMetaType) + return; + + if (m_inheritGroup) { + if (m_filterGroup == model->m_filterGroup) + return; + m_filterGroup = model->m_filterGroup; + } + + QQmlListCompositor::Group previousGroup = m_compositorGroup; + m_compositorGroup = Compositor::Default; + QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this); + for (int i = 1; i < model->m_groupCount; ++i) { + if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) { + m_compositorGroup = Compositor::Group(i); + break; + } + } + + QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this); + if (m_compositorGroup != previousGroup) { + QVector removes; + QVector inserts; + model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); + + QQmlChangeSet changeSet; + changeSet.move(removes, inserts); + if (!changeSet.isEmpty()) + emit modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit countChanged(); + } +} + +void QQmlPartsModel::updateFilterGroup( + Compositor::Group group, const QQmlChangeSet &changeSet) +{ + if (!m_inheritGroup) + return; + + m_compositorGroup = group; + QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this); + + if (!changeSet.isEmpty()) + emit modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit countChanged(); + + emit filterGroupChanged(); +} + +int QQmlPartsModel::count() const +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + return model->m_delegate + ? model->m_compositor.count(m_compositorGroup) + : 0; +} + +bool QQmlPartsModel::isValid() const +{ + return m_model->isValid(); +} + +QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode) +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + + if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) { + qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup); + return nullptr; + } + + QObject *object = model->object(m_compositorGroup, index, incubationMode); + + if (QQuickPackage *package = qmlobject_cast(object)) { + QObject *part = package->part(m_part); + if (!part) + return nullptr; + m_packaged.insertMulti(part, package); + return part; + } + + model->release(object); + if (!model->m_delegateValidated) { + if (object) + qmlWarning(model->m_delegate) << tr("Delegate component must be Package type."); + model->m_delegateValidated = true; + } + + return nullptr; +} + +QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) +{ + QQmlInstanceModel::ReleaseFlags flags = nullptr; + + QHash::iterator it = m_packaged.find(item); + if (it != m_packaged.end()) { + QQuickPackage *package = *it; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + flags = model->release(package); + m_packaged.erase(it); + if (!m_packaged.contains(item)) + flags &= ~Referenced; + if (flags & Destroyed) + QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package); + } + return flags; +} + +QVariant QQmlPartsModel::variantValue(int index, const QString &role) +{ + return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role); +} + +void QQmlPartsModel::setWatchedRoles(const QList &roles) +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles); + m_watchedRoles = roles; +} + +QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index) +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index); + if (!it->inCache()) + return QQmlIncubator::Null; + + if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask) + return incubationTask->status(); + + return QQmlIncubator::Ready; +} + +int QQmlPartsModel::indexOf(QObject *item, QObject *) const +{ + QHash::const_iterator it = m_packaged.find(item); + if (it != m_packaged.end()) { + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it)) + return cacheItem->groupIndex(m_compositorGroup); + } + return -1; +} + +void QQmlPartsModel::createdPackage(int index, QQuickPackage *package) +{ + emit createdItem(index, package->part(m_part)); +} + +void QQmlPartsModel::initPackage(int index, QQuickPackage *package) +{ + if (m_modelUpdatePending) + m_pendingPackageInitializations << index; + else + emit initItem(index, package->part(m_part)); +} + +void QQmlPartsModel::destroyingPackage(QQuickPackage *package) +{ + QObject *item = package->part(m_part); + Q_ASSERT(!m_packaged.contains(item)); + emit destroyingItem(item); +} + +void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + m_modelUpdatePending = false; + emit modelUpdated(changeSet, reset); + if (changeSet.difference() != 0) + emit countChanged(); + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + QVector pendingPackageInitializations; + qSwap(pendingPackageInitializations, m_pendingPackageInitializations); + for (int index : pendingPackageInitializations) { + if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) + continue; + QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous); + if (QQuickPackage *package = qmlobject_cast(object)) + emit initItem(index, package->part(m_part)); + model->release(object); + } +} + +//============================================================================ + +struct QQmlDelegateModelGroupChange : QV4::Object +{ + V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object) + + static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) { + return e->memoryManager->allocate(); + } + + static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { + QV4::Scope scope(b); + QV4::Scoped that(scope, thisObject->as()); + if (!that) + THROW_TYPE_ERROR(); + return QV4::Encode(that->d()->change.index); + } + static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { + QV4::Scope scope(b); + QV4::Scoped that(scope, thisObject->as()); + if (!that) + THROW_TYPE_ERROR(); + return QV4::Encode(that->d()->change.count); + } + static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { + QV4::Scope scope(b); + QV4::Scoped that(scope, thisObject->as()); + if (!that) + THROW_TYPE_ERROR(); + if (that->d()->change.moveId < 0) + RETURN_UNDEFINED(); + return QV4::Encode(that->d()->change.moveId); + } +}; + +DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChange); + +struct QQmlDelegateModelGroupChangeArray : public QV4::Object +{ + V4_OBJECT2(QQmlDelegateModelGroupChangeArray, QV4::Object) + V4_NEEDS_DESTROY +public: + static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector &changes) + { + return engine->memoryManager->allocate(changes); + } + + quint32 count() const { return d()->changes->count(); } + const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); } + + static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty) + { + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + Q_ASSERT(m->as()); + QV4::ExecutionEngine *v4 = static_cast(m)->engine(); + QV4::Scope scope(v4); + QV4::Scoped array(scope, static_cast(m)); + + if (index >= array->count()) { + if (hasProperty) + *hasProperty = false; + return QV4::Value::undefinedValue().asReturnedValue(); + } + + const QQmlChangeSet::Change &change = array->at(index); + + QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value()); + QV4::Scoped object(scope, QQmlDelegateModelGroupChange::create(v4)); + object->setPrototypeOf(changeProto); + object->d()->change = change; + + if (hasProperty) + *hasProperty = true; + return object.asReturnedValue(); + } + + Q_ASSERT(m->as()); + const QQmlDelegateModelGroupChangeArray *array = static_cast(m); + + if (id == array->engine()->id_length()->propertyKey()) { + if (hasProperty) + *hasProperty = true; + return QV4::Encode(array->count()); + } + + return Object::virtualGet(m, id, receiver, hasProperty); + } +}; + +void QV4::Heap::QQmlDelegateModelGroupChangeArray::init(const QVector &changes) +{ + Object::init(); + this->changes = new QVector(changes); + QV4::Scope scope(internalClass->engine); + QV4::ScopedObject o(scope, this); + o->setArrayType(QV4::Heap::ArrayData::Custom); +} + +DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChangeArray); + +QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4) +{ + QV4::Scope scope(v4); + + QV4::ScopedObject proto(scope, v4->newObject()); + proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr); + proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr); + proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr); + changeProto.set(v4, proto); +} + +QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData() +{ +} + +QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4, + const QVector &changes) +{ + QV4::Scope scope(v4); + QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes)); + return o.asReturnedValue(); +} + +QT_END_NAMESPACE + +#include "moc_qqmldelegatemodel_p.cpp" diff --git a/src/qmlmodels/qqmldelegatemodel_p.h b/src/qmlmodels/qqmldelegatemodel_p.h new file mode 100644 index 0000000000..21eaef02e0 --- /dev/null +++ b/src/qmlmodels/qqmldelegatemodel_p.h @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLDATAMODEL_P_H +#define QQMLDATAMODEL_P_H + +// +// 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. +// + +#include +#include +#include +#include + +#include +#include + +QT_REQUIRE_CONFIG(qml_delegate_model); + +QT_BEGIN_NAMESPACE + +class QQmlChangeSet; +class QQuickPackage; +class QQmlV4Function; +class QQmlDelegateModelGroup; +class QQmlDelegateModelAttached; +class QQmlDelegateModelPrivate; + + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQmlParserStatus +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlDelegateModel) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup) + Q_PROPERTY(QQmlDelegateModelGroup *items READ items CONSTANT) //TODO : worth renaming? + Q_PROPERTY(QQmlDelegateModelGroup *persistedItems READ persistedItems CONSTANT) + Q_PROPERTY(QQmlListProperty groups READ groups CONSTANT) + Q_PROPERTY(QObject *parts READ parts CONSTANT) + Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + Q_INTERFACES(QQmlParserStatus) +public: + QQmlDelegateModel(); + QQmlDelegateModel(QQmlContext *, QObject *parent=nullptr); + ~QQmlDelegateModel(); + + void classBegin() override; + void componentComplete() override; + + QVariant model() const; + void setModel(const QVariant &); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *); + + QVariant rootIndex() const; + void setRootIndex(const QVariant &root); + + Q_INVOKABLE QVariant modelIndex(int idx) const; + Q_INVOKABLE QVariant parentModelIndex() const; + + int count() const override; + bool isValid() const override { return delegate() != nullptr; } + QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; + ReleaseFlags release(QObject *object) override; + void cancel(int index) override; + QVariant variantValue(int index, const QString &role) override; + void setWatchedRoles(const QList &roles) override; + QQmlIncubator::Status incubationStatus(int index) override; + + int indexOf(QObject *object, QObject *objectContext) const override; + + QString filterGroup() const; + void setFilterGroup(const QString &group); + void resetFilterGroup(); + + QQmlDelegateModelGroup *items(); + QQmlDelegateModelGroup *persistedItems(); + QQmlListProperty groups(); + QObject *parts(); + + const QAbstractItemModel *abstractItemModel() const override; + + bool event(QEvent *) override; + + static QQmlDelegateModelAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void filterGroupChanged(); + void defaultGroupsChanged(); + void rootIndexChanged(); + +private Q_SLOTS: + void _q_itemsChanged(int index, int count, const QVector &roles); + void _q_itemsInserted(int index, int count); + void _q_itemsRemoved(int index, int count); + void _q_itemsMoved(int from, int to, int count); + void _q_modelReset(); + void _q_rowsInserted(const QModelIndex &,int,int); + void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); + void _q_rowsRemoved(const QModelIndex &,int,int); + void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); + void _q_dataChanged(const QModelIndex&,const QModelIndex&,const QVector &); + void _q_layoutChanged(const QList&, QAbstractItemModel::LayoutChangeHint); + +private: + bool isDescendantOf(const QPersistentModelIndex &desc, const QList &parents) const; + + Q_DISABLE_COPY(QQmlDelegateModel) +}; + +class QQmlDelegateModelGroupPrivate; +class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModelGroup : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(bool includeByDefault READ defaultInclude WRITE setDefaultInclude NOTIFY defaultIncludeChanged) +public: + QQmlDelegateModelGroup(QObject *parent = nullptr); + QQmlDelegateModelGroup(const QString &name, QQmlDelegateModel *model, int compositorType, QObject *parent = nullptr); + ~QQmlDelegateModelGroup(); + + QString name() const; + void setName(const QString &name); + + int count() const; + + bool defaultInclude() const; + void setDefaultInclude(bool include); + + Q_INVOKABLE QJSValue get(int index); + +public Q_SLOTS: + void insert(QQmlV4Function *); + void create(QQmlV4Function *); + void resolve(QQmlV4Function *); + void remove(QQmlV4Function *); + void addGroups(QQmlV4Function *); + void removeGroups(QQmlV4Function *); + void setGroups(QQmlV4Function *); + void move(QQmlV4Function *); + +Q_SIGNALS: + void countChanged(); + void nameChanged(); + void defaultIncludeChanged(); + void changed(const QJSValue &removed, const QJSValue &inserted); +private: + Q_DECLARE_PRIVATE(QQmlDelegateModelGroup) +}; + +class QQmlDelegateModelItem; +class QQmlDelegateModelAttachedMetaObject; +class QQmlDelegateModelAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlDelegateModel *model READ model CONSTANT) + Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged) + Q_PROPERTY(bool isUnresolved READ isUnresolved NOTIFY unresolvedChanged) +public: + QQmlDelegateModelAttached(QObject *parent); + QQmlDelegateModelAttached(QQmlDelegateModelItem *cacheItem, QObject *parent); + ~QQmlDelegateModelAttached() {} + + void resetCurrentIndex(); + void setCacheItem(QQmlDelegateModelItem *item); + + QQmlDelegateModel *model() const; + + QStringList groups() const; + void setGroups(const QStringList &groups); + + bool isUnresolved() const; + + void emitChanges(); + + void emitUnresolvedChanged() { Q_EMIT unresolvedChanged(); } + +Q_SIGNALS: + void groupsChanged(); + void unresolvedChanged(); + +public: + QQmlDelegateModelItem *m_cacheItem; + int m_previousGroups; + int m_currentIndex[QQmlListCompositor::MaximumGroupCount]; + int m_previousIndex[QQmlListCompositor::MaximumGroupCount]; + + friend class QQmlDelegateModelAttachedMetaObject; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlDelegateModel) +QML_DECLARE_TYPEINFO(QQmlDelegateModel, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QQmlDelegateModelGroup) + +#endif // QQMLDATAMODEL_P_H diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h new file mode 100644 index 0000000000..92362b8876 --- /dev/null +++ b/src/qmlmodels/qqmldelegatemodel_p_p.h @@ -0,0 +1,450 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLDATAMODEL_P_P_H +#define QQMLDATAMODEL_P_P_H + +#include "qqmldelegatemodel_p.h" +#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 + +typedef QQmlListCompositor Compositor; + +class QQmlDelegateModelAttachedMetaObject; +class QQmlAbstractDelegateComponent; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModelItemMetaType : public QQmlRefCount +{ +public: + QQmlDelegateModelItemMetaType(QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames); + ~QQmlDelegateModelItemMetaType(); + + void initializeMetaObject(); + void initializePrototype(); + + int parseGroups(const QStringList &groupNames) const; + int parseGroups(const QV4::Value &groupNames) const; + + QPointer model; + const int groupCount; + QV4::ExecutionEngine * const v4Engine; + QQmlDelegateModelAttachedMetaObject *metaObject; + const QStringList groupNames; + QV4::PersistentValue modelItemProto; +}; + +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 12) + Q_PROPERTY(int column READ modelColumn NOTIFY columnChanged REVISION 12) + Q_PROPERTY(QObject *model READ modelObject CONSTANT) +public: + QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, + QQmlAdaptorModel::Accessors *accessor, int modelIndex, + int row, int column); + ~QQmlDelegateModelItem(); + + void referenceObject() { ++objectRef; } + bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); } + bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); } + void childContextObjectDestroyed(QObject *childContextObject); + + bool isReferenced() const { + return scriptRef + || incubationTask + || ((groups & Compositor::UnresolvedFlag) && (groups & Compositor::GroupMask)); + } + + void Dispose(); + + QObject *modelObject() { return this; } + + void destroyObject(); + + static QQmlDelegateModelItem *dataForObject(QObject *object); + + int groupIndex(Compositor::Group group); + + int modelRow() const { return row; } + int modelColumn() const { return column; } + int modelIndex() const { return index; } + virtual void setModelIndex(int idx, int newRow, int newColumn); + + virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); } + + virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); } + virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; } + + 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); + + QV4::ExecutionEngine *v4; + QQmlDelegateModelItemMetaType * const metaType; + QQmlContextDataRef contextData; + QPointer object; + QPointer attached; + QQDMIncubationTask *incubationTask; + QQmlComponent *delegate; + int poolTime; + int objectRef; + int scriptRef; + int groups; + int index; + +Q_SIGNALS: + void modelIndexChanged(); + Q_REVISION(12) void rowChanged(); + Q_REVISION(12) void columnChanged(); + +protected: + void objectDestroyed(QObject *); + int row; + int column; +}; + +namespace QV4 { +namespace Heap { +struct QQmlDelegateModelItemObject : Object { + inline void init(QQmlDelegateModelItem *item); + void destroy(); + QQmlDelegateModelItem *item; +}; + +} +} + +struct QQmlDelegateModelItemObject : QV4::Object +{ + V4_OBJECT2(QQmlDelegateModelItemObject, QV4::Object) + V4_NEEDS_DESTROY +}; + +void QV4::Heap::QQmlDelegateModelItemObject::init(QQmlDelegateModelItem *item) +{ + Object::init(); + this->item = item; +} + + + +class QQmlDelegateModelPrivate; +class QQDMIncubationTask : public QQmlIncubator +{ +public: + QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode) + : QQmlIncubator(mode) + , incubating(nullptr) + , vdm(l) {} + + void statusChanged(Status) override; + void setInitialState(QObject *) override; + + QQmlDelegateModelItem *incubating = nullptr; + QQmlDelegateModelPrivate *vdm = nullptr; + 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( + QQmlV4Function *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); + 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 updateFilterGroup(); + + 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); + + bool insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups); + + int adaptorModelCount() const; + + static void group_append(QQmlListProperty *property, QQmlDelegateModelGroup *group); + static int group_count(QQmlListProperty *property); + static QQmlDelegateModelGroup *group_at(QQmlListProperty *property, int 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; + QQmlDelegateModelItemMetaType *m_cacheMetaType; + QPointer m_context; + QQmlDelegateModelParts *m_parts; + QQmlDelegateModelGroupEmitterList m_pendingParts; + + QList m_cache; + 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) +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) 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; + QHash 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 : public QAbstractDynamicMetaObject, public QQmlRefCount +{ +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 diff --git a/src/qmlmodels/qqmlinstantiator.cpp b/src/qmlmodels/qqmlinstantiator.cpp new file mode 100644 index 0000000000..af1b526e1d --- /dev/null +++ b/src/qmlmodels/qqmlinstantiator.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion. +** 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 "qqmlinstantiator_p.h" +#include "qqmlinstantiator_p_p.h" +#include +#include +#include +#include +#include +#if QT_CONFIG(qml_delegate_model) +#include +#endif + +QT_BEGIN_NAMESPACE + +QQmlInstantiatorPrivate::QQmlInstantiatorPrivate() + : componentComplete(true) + , effectiveReset(false) + , active(true) + , async(false) +#if QT_CONFIG(qml_delegate_model) + , ownModel(false) +#endif + , requestedIndex(-1) + , model(QVariant(1)) + , instanceModel(nullptr) + , delegate(nullptr) +{ +} + +QQmlInstantiatorPrivate::~QQmlInstantiatorPrivate() +{ + qDeleteAll(objects); +} + +void QQmlInstantiatorPrivate::clear() +{ + Q_Q(QQmlInstantiator); + if (!instanceModel) + return; + if (!objects.count()) + return; + + for (int i=0; i < objects.count(); i++) { + q->objectRemoved(i, objects[i]); + instanceModel->release(objects[i]); + } + objects.clear(); + q->objectChanged(); +} + +QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async) +{ + requestedIndex = index; + QObject *o = instanceModel->object(index, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); + requestedIndex = -1; + return o; +} + + +void QQmlInstantiatorPrivate::regenerate() +{ + Q_Q(QQmlInstantiator); + if (!componentComplete) + return; + + int prevCount = q->count(); + + clear(); + + if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) { + if (prevCount) + q->countChanged(); + return; + } + + for (int i = 0; i < instanceModel->count(); i++) { + QObject *object = modelObject(i, async); + // If the item was already created we won't get a createdItem + if (object) + _q_createdItem(i, object); + } + if (q->count() != prevCount) + q->countChanged(); +} + +void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item) +{ + Q_Q(QQmlInstantiator); + if (objects.contains(item)) //Case when it was created synchronously in regenerate + return; + if (requestedIndex != idx) // Asynchronous creation, reference the object + (void)instanceModel->object(idx); + item->setParent(q); + if (objects.size() < idx + 1) { + int modelCount = instanceModel->count(); + if (objects.capacity() < modelCount) + objects.reserve(modelCount); + objects.resize(idx + 1); + } + if (QObject *o = objects.at(idx)) + instanceModel->release(o); + objects.replace(idx, item); + if (objects.count() == 1) + q->objectChanged(); + q->objectAdded(idx, item); +} + +void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + Q_Q(QQmlInstantiator); + + if (!componentComplete || effectiveReset) + return; + + if (reset) { + regenerate(); + if (changeSet.difference() != 0) + q->countChanged(); + return; + } + + int difference = 0; + QHash > > moved; + const QVector &removes = changeSet.removes(); + for (const QQmlChangeSet::Change &remove : removes) { + int index = qMin(remove.index, objects.count()); + int count = qMin(remove.index + remove.count, objects.count()) - index; + if (remove.isMove()) { + moved.insert(remove.moveId, objects.mid(index, count)); + objects.erase( + objects.begin() + index, + objects.begin() + index + count); + } else while (count--) { + QObject *obj = objects.at(index); + objects.remove(index); + q->objectRemoved(index, obj); + if (obj) + instanceModel->release(obj); + } + + difference -= remove.count; + } + + const QVector &inserts = changeSet.inserts(); + for (const QQmlChangeSet::Change &insert : inserts) { + int index = qMin(insert.index, objects.count()); + if (insert.isMove()) { + QVector > movedObjects = moved.value(insert.moveId); + objects = objects.mid(0, index) + movedObjects + objects.mid(index); + } else { + if (insert.index <= objects.size()) + objects.insert(insert.index, insert.count, nullptr); + for (int i = 0; i < insert.count; ++i) { + int modelIndex = index + i; + QObject* obj = modelObject(modelIndex, async); + if (obj) + _q_createdItem(modelIndex, obj); + } + } + difference += insert.count; + } + + if (difference != 0) + q->countChanged(); +} + +#if QT_CONFIG(qml_delegate_model) +void QQmlInstantiatorPrivate::makeModel() +{ + Q_Q(QQmlInstantiator); + QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q), q); + instanceModel = delegateModel; + ownModel = true; + delegateModel->setDelegate(delegate); + delegateModel->classBegin(); //Pretend it was made in QML + if (componentComplete) + delegateModel->componentComplete(); +} +#endif + + +/*! + \qmltype Instantiator + \instantiates QQmlInstantiator + \inqmlmodule QtQml + \brief Dynamically creates objects. + + A Instantiator can be used to control the dynamic creation of objects, or to dynamically + create multiple objects from a template. + + The Instantiator element will manage the objects it creates. Those objects are parented to the + Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects + can also be destroyed dynamically through other means, and the Instantiator will not recreate + them unless the properties of the Instantiator change. + +*/ +QQmlInstantiator::QQmlInstantiator(QObject *parent) + : QObject(*(new QQmlInstantiatorPrivate), parent) +{ +} + +QQmlInstantiator::~QQmlInstantiator() +{ +} + +/*! + \qmlsignal QtQml::Instantiator::objectAdded(int index, QtObject object) + + This signal is emitted when an object is added to the Instantiator. The \a index + parameter holds the index which the object has been given, and the \a object + parameter holds the \l QtObject that has been added. + + The corresponding handler is \c onObjectAdded. +*/ + +/*! + \qmlsignal QtQml::Instantiator::objectRemoved(int index, QtObject object) + + This signal is emitted when an object is removed from the Instantiator. The \a index + parameter holds the index which the object had been given, and the \a object + parameter holds the \l QtObject that has been removed. + + Do not keep a reference to \a object if it was created by this Instantiator, as + in these cases it will be deleted shortly after the signal is handled. + + The corresponding handler is \c onObjectRemoved. +*/ +/*! + \qmlproperty bool QtQml::Instantiator::active + + When active is true, and the delegate component is ready, the Instantiator will + create objects according to the model. When active is false, no objects + will be created and any previously created objects will be destroyed. + + Default is true. +*/ +bool QQmlInstantiator::isActive() const +{ + Q_D(const QQmlInstantiator); + return d->active; +} + +void QQmlInstantiator::setActive(bool newVal) +{ + Q_D(QQmlInstantiator); + if (newVal == d->active) + return; + d->active = newVal; + emit activeChanged(); + d->regenerate(); +} + +/*! + \qmlproperty bool QtQml::Instantiator::asynchronous + + When asynchronous is true the Instantiator will attempt to create objects + asynchronously. This means that objects may not be available immediately, + even if active is set to true. + + You can use the objectAdded signal to respond to items being created. + + Default is false. +*/ +bool QQmlInstantiator::isAsync() const +{ + Q_D(const QQmlInstantiator); + return d->async; +} + +void QQmlInstantiator::setAsync(bool newVal) +{ + Q_D(QQmlInstantiator); + if (newVal == d->async) + return; + d->async = newVal; + emit asynchronousChanged(); +} + + +/*! + \qmlproperty int QtQml::Instantiator::count + + The number of objects the Instantiator is currently managing. +*/ + +int QQmlInstantiator::count() const +{ + Q_D(const QQmlInstantiator); + return d->objects.count(); +} + +/*! + \qmlproperty QtQml::Component QtQml::Instantiator::delegate + \default + + The component used to create all objects. + + Note that an extra variable, index, will be available inside instances of the + delegate. This variable refers to the index of the instance inside the Instantiator, + and can be used to obtain the object through the objectAt method of the Instantiator. + + If this property is changed, all instances using the old delegate will be destroyed + and new instances will be created using the new delegate. +*/ +QQmlComponent* QQmlInstantiator::delegate() +{ + Q_D(QQmlInstantiator); + return d->delegate; +} + +void QQmlInstantiator::setDelegate(QQmlComponent* c) +{ + Q_D(QQmlInstantiator); + if (c == d->delegate) + return; + + d->delegate = c; + emit delegateChanged(); + +#if QT_CONFIG(qml_delegate_model) + if (!d->ownModel) + return; + + if (QQmlDelegateModel *dModel = qobject_cast(d->instanceModel)) + dModel->setDelegate(c); + if (d->componentComplete) + d->regenerate(); +#endif +} + +/*! + \qmlproperty variant QtQml::Instantiator::model + + This property can be set to any of the supported \l {qml-data-models}{data models}: + + \list + \li A number that indicates the number of delegates to be created by the repeater + \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass) + \li A string list + \li An object list + \endlist + + The type of model affects the properties that are exposed to the \l delegate. + + Default value is 1, which creates a single delegate instance. + + \sa {qml-data-models}{Data Models} +*/ + +QVariant QQmlInstantiator::model() const +{ + Q_D(const QQmlInstantiator); + return d->model; +} + +void QQmlInstantiator::setModel(const QVariant &v) +{ + Q_D(QQmlInstantiator); + if (d->model == v) + return; + + d->model = v; + //Don't actually set model until componentComplete in case it wants to create its delegates immediately + if (!d->componentComplete) + return; + + QQmlInstanceModel *prevModel = d->instanceModel; + QObject *object = qvariant_cast(v); + QQmlInstanceModel *vim = nullptr; + if (object && (vim = qobject_cast(object))) { +#if QT_CONFIG(qml_delegate_model) + if (d->ownModel) { + delete d->instanceModel; + prevModel = nullptr; + d->ownModel = false; + } +#endif + d->instanceModel = vim; +#if QT_CONFIG(qml_delegate_model) + } else if (v != QVariant(0)){ + if (!d->ownModel) + d->makeModel(); + + if (QQmlDelegateModel *dataModel = qobject_cast(d->instanceModel)) { + d->effectiveReset = true; + dataModel->setModel(v); + d->effectiveReset = false; + } +#endif + } + + if (d->instanceModel != prevModel) { + if (prevModel) { + disconnect(prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), + this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); + disconnect(prevModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); + //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); + } + + if (d->instanceModel) { + connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), + this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); + connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); + //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); + } + } + + d->regenerate(); + emit modelChanged(); +} + +/*! + \qmlproperty QtObject QtQml::Instantiator::object + + This is a reference to the first created object, intended as a convenience + for the case where only one object has been created. +*/ +QObject *QQmlInstantiator::object() const +{ + Q_D(const QQmlInstantiator); + if (d->objects.count()) + return d->objects[0]; + return nullptr; +} + +/*! + \qmlmethod QtObject QtQml::Instantiator::objectAt(int index) + + Returns a reference to the object with the given \a index. +*/ +QObject *QQmlInstantiator::objectAt(int index) const +{ + Q_D(const QQmlInstantiator); + if (index >= 0 && index < d->objects.count()) + return d->objects[index]; + return nullptr; +} + +/*! + \internal +*/ +void QQmlInstantiator::classBegin() +{ + Q_D(QQmlInstantiator); + d->componentComplete = false; +} + +/*! + \internal +*/ +void QQmlInstantiator::componentComplete() +{ + Q_D(QQmlInstantiator); + d->componentComplete = true; +#if QT_CONFIG(qml_delegate_model) + if (d->ownModel) { + static_cast(d->instanceModel)->componentComplete(); + d->regenerate(); + } else +#endif + { + QVariant realModel = d->model; + d->model = QVariant(0); + setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0 + //setModel calls regenerate + } +} + +QT_END_NAMESPACE + +#include "moc_qqmlinstantiator_p.cpp" diff --git a/src/qmlmodels/qqmlinstantiator_p.h b/src/qmlmodels/qqmlinstantiator_p.h new file mode 100644 index 0000000000..8b00a1e033 --- /dev/null +++ b/src/qmlmodels/qqmlinstantiator_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion. +** 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$ +** +****************************************************************************/ + +#ifndef QQMLINSTANTIATOR_P_H +#define QQMLINSTANTIATOR_P_H + +// +// 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. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlInstantiatorPrivate; +class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool asynchronous READ isAsync WRITE setAsync NOTIFY asynchronousChanged) + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(QObject *object READ object NOTIFY objectChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + +public: + QQmlInstantiator(QObject *parent = nullptr); + ~QQmlInstantiator(); + + bool isActive() const; + void setActive(bool newVal); + + bool isAsync() const; + void setAsync(bool newVal); + + int count() const; + + QQmlComponent* delegate(); + void setDelegate(QQmlComponent* c); + + QVariant model() const; + void setModel(const QVariant &v); + + QObject *object() const; + + Q_INVOKABLE QObject *objectAt(int index) const; + + void classBegin() override; + void componentComplete() override; + +Q_SIGNALS: + void modelChanged(); + void delegateChanged(); + void countChanged(); + void objectChanged(); + void activeChanged(); + void asynchronousChanged(); + + void objectAdded(int index, QObject* object); + void objectRemoved(int index, QObject* object); + +private: + Q_DISABLE_COPY(QQmlInstantiator) + Q_DECLARE_PRIVATE(QQmlInstantiator) + Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *)) + Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool)) +}; + +QT_END_NAMESPACE + +#endif // QQMLCREATOR_P_H diff --git a/src/qmlmodels/qqmlinstantiator_p_p.h b/src/qmlmodels/qqmlinstantiator_p_p.h new file mode 100644 index 0000000000..bf153d8723 --- /dev/null +++ b/src/qmlmodels/qqmlinstantiator_p_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion. +** 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$ +** +****************************************************************************/ + +#ifndef QQMLINSTANTIATOR_P_P_H +#define QQMLINSTANTIATOR_P_P_H + +// +// 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. +// + +#include "qqmlinstantiator_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstantiatorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlInstantiator) + +public: + QQmlInstantiatorPrivate(); + ~QQmlInstantiatorPrivate(); + + void clear(); + void regenerate(); +#if QT_CONFIG(qml_delegate_model) + void makeModel(); +#endif + void _q_createdItem(int, QObject *); + void _q_modelUpdated(const QQmlChangeSet &, bool); + QObject *modelObject(int index, bool async); + + static QQmlInstantiatorPrivate *get(QQmlInstantiator *instantiator) { return instantiator->d_func(); } + static const QQmlInstantiatorPrivate *get(const QQmlInstantiator *instantiator) { return instantiator->d_func(); } + + bool componentComplete:1; + bool effectiveReset:1; + bool active:1; + bool async:1; +#if QT_CONFIG(qml_delegate_model) + bool ownModel:1; +#endif + int requestedIndex; + QVariant model; + QQmlInstanceModel *instanceModel; + QQmlComponent *delegate; + QVector > objects; +}; + +QT_END_NAMESPACE + +#endif // QQMLCREATOR_P_P_H diff --git a/src/qmlmodels/qqmlitemmodels.qdoc b/src/qmlmodels/qqmlitemmodels.qdoc new file mode 100644 index 0000000000..f6e1b0b1b9 --- /dev/null +++ b/src/qmlmodels/qqmlitemmodels.qdoc @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qmodelindex-and-related-classes-in-qml.html + \title QModelIndex and related Classes in QML + + Since Qt 5.5, QModelIndex and QPersistentModelIndex are exposed in QML as + value-based types. Also exposed in a similar fashion are QModelIndexList, + QItemSelectionRange and QItemSelection. All objects from these types can + be passed back and forth between QML and C++ as \c var properties or plain + JavaScript variables. + + Below you will find an overview of the API exposed to QML for these classes. + For more information, refer to their C++ documentation. + + \note Since all these types are exposed as \l{Q_GADGET}{gadgets}, there are no property + change notification signals emitted. Therefore binding to their properties + may not give the expected results. This is especially true for QPersistentModelIndex. + + \section1 QModelIndex and QPersistentModelIndex Types + + \list + \li \b row : int + \li \b column : int + \li \b parent : QModelIndex + \li \b valid : bool + \li \b model : QAbstractItemModel + \li \b internalId : quint64 + \endlist + + All these properties are read-only, as are their C++ counterparts. + + \note The usual caveats apply to QModelIndex in QML. If the underlying model changes + or gets deleted, it may become dangerous to access its properties. Therefore, you + should not store any QModelIndex objects. You can, however, store QPersistentModelIndexe + objects in a safe way. + + \section1 QModelIndexList Type + + \l QModelIndexList is exposed in QML as a JavaScript array. Conversions are + automatically made from and to C++. In fact, any JavaScript array can be + converted back to QModelIndexList, with non-QModelIndex objects replaced by + invalid \l{QModelIndex}es. + + \note QModelIndex to QPersistentModelIndex conversion happens when accessing + the array elements because any QModelIndexList property retains reference + semantics when exposed this way. + + \section1 \l QItemSelectionRange Type + + \list + \li \b top : int + \li \b left : int + \li \b bottom : int + \li \b right : int + \li \b width : int + \li \b height : int + \li \b topLeft : QPersistentModelIndex + \li \b bottomRight : QPersistentModelIndex + \li \b parent : QModelIndex + \li \b valid : bool + \li \b empty : bool + \li \b model : QAbstractItemModel + \endlist + + All these properties are read-only, as are their C++ counterparts. In addition, + we also expose the following functions: + + \list + \li bool \b{contains}(QModelIndex \e index) + \li bool \b{contains}(int \e row, int \e column, QModelIndex \e parentIndex) + \li bool \b{intersects}(QItemSelectionRange \e other) + \li QItemSelectionRange \b{intersected}(QItemSelectionRange \e other) + \endlist + + \section1 QItemSelection Type + + Similarly to QModelIndexList, \l QItemSelection is exposed in QML as a JavaScript + array of QItemSelectionRange objects. Conversions are automatically made from and to C++. + In fact, any JavaScript array can be converted back to QItemSelection, with + non-QItemSelectionRange objects replaced by empty \l {QItemSelectionRange}s. + + + \sa ItemSelectionModel +*/ diff --git a/src/qmlmodels/qqmlitemselectionmodel.qdoc b/src/qmlmodels/qqmlitemselectionmodel.qdoc new file mode 100644 index 0000000000..43da4f7a55 --- /dev/null +++ b/src/qmlmodels/qqmlitemselectionmodel.qdoc @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmltype ItemSelectionModel + \instantiates QItemSelectionModel + \inqmlmodule QtQml.Models + \since 5.5 + \ingroup qtquick-models + + \brief Instantiates a QItemSelectionModel to be used in conjunction + with a QAbstractItemModel and any view supporting it. + + \sa QItemSelectionModel, {Models and Views in Qt Quick} +*/ + + +/*! + \qmlproperty QAbstractItemModel ItemSelectionModel::model + + This property's value must match the view's model. +*/ + +/*! + \qmlproperty bool ItemSelectionModel::hasSelection + \readonly + + It will trigger property binding updates every time \l selectionChanged() + is emitted, even though its value hasn't changed. + + \sa selection, selectedIndexes, select(), selectionChanged() +*/ + +/*! + \qmlproperty QModelIndex ItemSelectionModel::currentIndex + \readonly + + Use \l setCurrentIndex() to set its value. + + \sa setCurrentIndex(), currentChanged() +*/ + +/*! + \qmlproperty QModelIndexList ItemSelectionModel::selectedIndexes + \readonly + + Contains the list of all the indexes in the selection model. +*/ + +/*! + \qmlmethod bool ItemSelectionModel::isSelected(QModelIndex index) + + Returns \c true if the given model item \a index is selected. +*/ + +/*! + \qmlmethod bool ItemSelectionModel::isRowSelected(int row, QModelIndex parent) + + Returns \c true if all items are selected in the \a row with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same row, and that unselectable items are ignored. +*/ + +/*! + \qmlmethod bool ItemSelectionModel::isColumnSelected(int column, QModelIndex parent) + + Returns \c true if all items are selected in the \a column with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same column, and that unselectable items are ignored. +*/ + +/*! + \qmlmethod bool ItemSelectionModel::rowIntersectsSelection(int row, QModelIndex parent) + + Returns \c true if there are any items selected in the \a row with the + given \a parent. +*/ + +/*! + \qmlmethod bool ItemSelectionModel::columnIntersectsSelection(int column, QModelIndex parent) + + Returns \c true if there are any items selected in the \a column with the + given \a parent. +*/ + +/*! + \qmlmethod QModelIndexList ItemSelectionModel::selectedRows(int column) + + Returns the indexes in the given \a column for the rows where all columns + are selected. + + \sa selectedColumns() +*/ + +/*! + \qmlmethod QModelIndexList ItemSelectionModel::selectedColumns(int row) + + Returns the indexes in the given \a row for columns where all rows are + selected. + + \sa selectedRows() +*/ + +/*! + \qmlproperty object ItemSelectionModel::selection + \readonly + + Holds the selection ranges stored in the selection model. +*/ + +/*! + \qmlmethod void ItemSelectionModel::setCurrentIndex(QModelIndex index, SelectionFlags command) + + Sets the model item \a index to be the current item, and emits + currentChanged(). The current item is used for keyboard navigation and + focus indication; it is independent of any selected items, although a + selected item can also be the current item. + + Depending on the specified \a command, the \a index can also become part + of the current selection. + + Valid \a command values are described in \l {itemselectionmodelselectindex} + {select(\e index, \e command)}. + + \sa select() +*/ + +/*! + \qmlmethod void ItemSelectionModel::select(QModelIndex index, SelectionFlags command) + \keyword itemselectionmodelselectindex + + Selects the model item \a index using the specified \a command, and emits + selectionChanged(). + + Valid values for the \a command parameter, are: + + \value NoUpdate No selection will be made. + \value Clear The complete selection will be cleared. + \value Select All specified indexes will be selected. + \value Deselect All specified indexes will be deselected. + \value Toggle All specified indexes will be selected or + deselected depending on their current state. + \value Current The current selection will be updated. + \value Rows All indexes will be expanded to span rows. + \value Columns All indexes will be expanded to span columns. + \value SelectCurrent A combination of Select and Current, provided for + convenience. + \value ToggleCurrent A combination of Toggle and Current, provided for + convenience. + \value ClearAndSelect A combination of Clear and Select, provided for + convenience. +*/ + +/*! + \qmlmethod void ItemSelectionModel::select(QItemSelection selection, SelectionFlags command) + + Selects the item \a selection using the specified \a command, and emits + selectionChanged(). + + Valid \a command values are described in \l {itemselectionmodelselectindex} + {select(\e index, \e command)}. +*/ + +/*! + \qmlmethod void ItemSelectionModel::clear() + + Clears the selection model. Emits selectionChanged() and currentChanged(). +*/ + +/*! + \qmlmethod void ItemSelectionModel::reset() + + Clears the selection model. Does not emit any signals. +*/ + +/*! + \qmlmethod void ItemSelectionModel::clearSelection() + + Clears the selection in the selection model. Emits selectionChanged(). +*/ + +/*! + \qmlmethod void ItemSelectionModel::clearCurrentIndex() + + Clears the current index. Emits currentChanged(). +*/ + +/*! + \qmlsignal ItemSelectionModel::selectionChanged(QItemSelection selected, QItemSelection deselected) + + This signal is emitted whenever the selection changes. The change in the + selection is represented as an item selection of \a deselected items and + an item selection of \a selected items. + + Note the that the current index changes independently from the selection. + Also note that this signal will not be emitted when the item model is reset. + + \sa select(), currentChanged() +*/ + +/*! + \qmlsignal ItemSelectionModel::currentChanged(QModelIndex current, QModelIndex previous) + + This signal is emitted whenever the current item changes. The \a previous + model item index is replaced by the \a current index as the selection's + current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentIndex, setCurrentIndex(), selectionChanged() +*/ diff --git a/src/qmlmodels/qqmllistaccessor.cpp b/src/qmlmodels/qqmllistaccessor.cpp new file mode 100644 index 0000000000..46a11e2bc2 --- /dev/null +++ b/src/qmlmodels/qqmllistaccessor.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmllistaccessor_p.h" + +#include + +#include +#include + +// ### Remove me +#include + +QT_BEGIN_NAMESPACE + +QQmlListAccessor::QQmlListAccessor() +: m_type(Invalid) +{ +} + +QQmlListAccessor::~QQmlListAccessor() +{ +} + +QVariant QQmlListAccessor::list() const +{ + return d; +} + +void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine) +{ + d = v; + + // An incoming JS array as model is treated as a variant list, so we need to + // convert it first with toVariant(). + if (d.userType() == qMetaTypeId()) + d = d.value().toVariant(); + + QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):nullptr; + + if (!d.isValid()) { + m_type = Invalid; + } else if (d.userType() == QVariant::StringList) { + m_type = StringList; + } else if (d.userType() == QMetaType::QVariantList) { + m_type = VariantList; + } else if (d.canConvert(QVariant::Int)) { + // Here we have to check for an upper limit, because down the line code might (well, will) + // allocate memory depending on the number of elements. The upper limit cannot be INT_MAX: + // QVector> something; + // something.resize(count()); + // (See e.g. QQuickRepeater::regenerate()) + // This will allocate data along the lines of: + // sizeof(QPointer) * count() + QVector::headerSize + // So, doing an approximate round-down-to-nice-number, we get: + const int upperLimit = 100 * 1000 * 1000; + + int i = v.toInt(); + if (i < 0) { + qWarning("Model size of %d is less than 0", i); + m_type = Invalid; + } else if (i > upperLimit) { + qWarning("Model size of %d is bigger than the upper limit %d", i, upperLimit); + m_type = Invalid; + } else { + m_type = Integer; + } + } else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) || + (enginePrivate && enginePrivate->isQObject(d.userType()))) { + QObject *data = enginePrivate?enginePrivate->toQObject(d):QQmlMetaType::toQObject(d); + d = QVariant::fromValue(data); + m_type = Instance; + } else if (d.userType() == qMetaTypeId()) { + m_type = ListProperty; + } else { + m_type = Instance; + } +} + +int QQmlListAccessor::count() const +{ + switch(m_type) { + case StringList: + return qvariant_cast(d).count(); + case VariantList: + return qvariant_cast(d).count(); + case ListProperty: + return ((const QQmlListReference *)d.constData())->count(); + case Instance: + return 1; + case Integer: + return d.toInt(); + default: + case Invalid: + return 0; + } +} + +QVariant QQmlListAccessor::at(int idx) const +{ + Q_ASSERT(idx >= 0 && idx < count()); + switch(m_type) { + case StringList: + return QVariant::fromValue(qvariant_cast(d).at(idx)); + case VariantList: + return qvariant_cast(d).at(idx); + case ListProperty: + return QVariant::fromValue(((const QQmlListReference *)d.constData())->at(idx)); + case Instance: + return d; + case Integer: + return QVariant(idx); + default: + case Invalid: + return QVariant(); + } +} + +bool QQmlListAccessor::isValid() const +{ + return m_type != Invalid; +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmllistaccessor_p.h b/src/qmlmodels/qqmllistaccessor_p.h new file mode 100644 index 0000000000..bcd079adef --- /dev/null +++ b/src/qmlmodels/qqmllistaccessor_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLLISTACCESSOR_H +#define QQMLLISTACCESSOR_H + +// +// 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. +// + +#include + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class Q_AUTOTEST_EXPORT QQmlListAccessor +{ +public: + QQmlListAccessor(); + ~QQmlListAccessor(); + + QVariant list() const; + void setList(const QVariant &, QQmlEngine * = nullptr); + + bool isValid() const; + + int count() const; + QVariant at(int) const; + + enum Type { Invalid, StringList, VariantList, ListProperty, Instance, Integer }; + Type type() const { return m_type; } + +private: + Type m_type; + QVariant d; +}; + +QT_END_NAMESPACE + +#endif // QQMLLISTACCESSOR_H diff --git a/src/qmlmodels/qqmllistcompositor.cpp b/src/qmlmodels/qqmllistcompositor.cpp new file mode 100644 index 0000000000..921e86f355 --- /dev/null +++ b/src/qmlmodels/qqmllistcompositor.cpp @@ -0,0 +1,1482 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmllistcompositor_p.h" + +#include + +//#define QT_QML_VERIFY_MINIMAL +//#define QT_QML_VERIFY_INTEGRITY + +QT_BEGIN_NAMESPACE + +/*! + \class QQmlListCompositor + \brief The QQmlListCompositor class provides a lookup table for filtered, or re-ordered list + indexes. + \internal + + QQmlListCompositor is intended as an aid for developing proxy models. It doesn't however + directly proxy a list or model, instead a range of indexes from one or many lists can be + inserted into the compositor and then categorized and shuffled around and it will manage the + task of translating from an index in the combined space into an index in a particular list. + + Within a compositor indexes are categorized into groups where a group is a sub-set of the + total indexes referenced by the compositor, each with an address space ranging from 0 to + the number of indexes in the group - 1. Group memberships are independent of each other with + the one exception that items always retain the same order so if an index is moved within a + group, its position in other groups will change as well. + + The iterator classes encapsulate information about a specific position in a compositor group. + This includes a source list, the index of an item within that list and the groups that item + is a member of. The iterator for a specific position in a group can be retrieved with the + find() function and the addition and subtraction operators of the iterators can be used to + navigate to adjacent items in the same group. + + Items can be added to the compositor with the append() and insert() functions, group + membership can be changed with the setFlags() and clearFlags() functions, and the position + of items in the compositor can be changed with the move() function. Each of these functions + optionally returns a list of the changes made to indexes within each group which can then + be propagated to view so that it can correctly refresh its contents; e.g. 3 items + removed at index 6, and 5 items inserted at index 1. The notification changes are always + ordered from the start of the list to the end and accumulate, so if 5 items are removed at + index 4, one is skipped and then 3 move are removed, the changes returned are 5 items removed + at index 4, followed by 3 items removed at index 4. + + When the contents of a source list change, the mappings within the compositor can be updated + with the listItemsInserted(), listItemsRemoved(), listItemsMoved(), and listItemsChanged() + functions. Like the direct manipulation functions these too return a list of group indexes + affected by the change. If items are removed from a source list they are also removed from + any groups they belong to with the one exception being items belonging to the \l Cache group. + When items belonging to this group are removed the list, index, and other group membership + information are discarded but Cache membership is retained until explicitly removed. This + allows the cache index to be retained until cached resources for that item are actually + released. + + Internally the index mapping is stored as a list of Range objects, each has a list identifier, + a start index, a count, and a set of flags which represent group membership and some other + properties. The group index of a range is the sum of all preceding ranges that are members of + that group. To avoid the inefficiency of iterating over potentially all ranges when looking + for a specific index, each time a lookup is done the range and its indexes are cached and the + next lookup is done relative to this. This works out to near constant time in most relevant + use cases because successive index lookups are most frequently adjacent. The total number of + ranges is often quite small, which helps as well. If there is a need for faster random access + then a skip list like index may be an appropriate addition. + + \sa DelegateModel +*/ + +#ifdef QT_QML_VERIFY_MINIMAL +#define QT_QML_VERIFY_INTEGRITY +/* + Diagnostic to verify there are no consecutive ranges, or that the compositor contains the + most compact representation possible. + + Returns false and prints a warning if any range has a starting index equal to the end + (index + count) index of the previous range, and both ranges also have the same flags and list + property. + + If there are no consecutive ranges this will return true. +*/ + +static bool qt_verifyMinimal( + const QQmlListCompositor::iterator &begin, + const QQmlListCompositor::iterator &end) +{ + bool minimal = true; + int index = 0; + + for (const QQmlListCompositor::Range *range = begin->next; range != *end; range = range->next, ++index) { + if (range->previous->list == range->list + && range->previous->flags == (range->flags & ~QQmlListCompositor::AppendFlag) + && range->previous->end() == range->index) { + qWarning() << index << "Consecutive ranges"; + qWarning() << *range->previous; + qWarning() << *range; + minimal = false; + } + } + + return minimal; +} + +#endif + +#ifdef QT_QML_VERIFY_INTEGRITY +static bool qt_printInfo(const QQmlListCompositor &compositor) +{ + qWarning() << compositor; + return true; +} + +/* + Diagnostic to verify the integrity of a compositor. + + Per range this verifies there are no invalid range combinations, that non-append ranges have + positive non-zero counts, and that list ranges have non-negative indexes. + + Accumulatively this verifies that the cached total group counts match the sum of counts + of member ranges. +*/ + +static bool qt_verifyIntegrity( + const QQmlListCompositor::iterator &begin, + const QQmlListCompositor::iterator &end, + const QQmlListCompositor::iterator &cachedIt) +{ + bool valid = true; + + int index = 0; + QQmlListCompositor::iterator it; + for (it = begin; *it != *end; *it = it->next) { + if (it->count == 0 && !it->append()) { + qWarning() << index << "Empty non-append range"; + valid = false; + } + if (it->count < 0) { + qWarning() << index << "Negative count"; + valid = false; + } + if (it->list && it->flags != QQmlListCompositor::CacheFlag && it->index < 0) { + qWarning() << index <<"Negative index"; + valid = false; + } + if (it->previous->next != it.range) { + qWarning() << index << "broken list: it->previous->next != it.range"; + valid = false; + } + if (it->next->previous != it.range) { + qWarning() << index << "broken list: it->next->previous != it.range"; + valid = false; + } + if (*it == *cachedIt) { + for (int i = 0; i < end.groupCount; ++i) { + int groupIndex = it.index[i]; + if (cachedIt->flags & (1 << i)) + groupIndex += cachedIt.offset; + if (groupIndex != cachedIt.index[i]) { + qWarning() << index + << "invalid cached index" + << QQmlListCompositor::Group(i) + << "Expected:" + << groupIndex + << "Actual" + << cachedIt.index[i] + << cachedIt; + valid = false; + } + } + } + it.incrementIndexes(it->count); + ++index; + } + + for (int i = 0; i < end.groupCount; ++i) { + if (end.index[i] != it.index[i]) { + qWarning() << "Group" << i << "count invalid. Expected:" << end.index[i] << "Actual:" << it.index[i]; + valid = false; + } + } + return valid; +} +#endif + +#if defined(QT_QML_VERIFY_MINIMAL) +# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!(qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \ + && qt_verifyMinimal(iterator(m_ranges.next, 0, Default, m_groupCount), m_end)) \ + && qt_printInfo(*this))); +#elif defined(QT_QML_VERIFY_INTEGRITY) +# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \ + && qt_printInfo(*this))); +#else +# define QT_QML_VERIFY_LISTCOMPOSITOR +#endif + +//#define QT_QML_TRACE_LISTCOMPOSITOR(args) qDebug() << m_end.index[1] << m_end.index[0] << Q_FUNC_INFO args; +#define QT_QML_TRACE_LISTCOMPOSITOR(args) + +QQmlListCompositor::iterator &QQmlListCompositor::iterator::operator +=(int difference) +{ + // Update all indexes to the start of the range. + decrementIndexes(offset); + + // If the iterator group isn't a member of the current range ignore the current offset. + if (!(range->flags & groupFlag)) + offset = 0; + + offset += difference; + + // Iterate backwards looking for a range with a positive offset. + while (offset <= 0 && range->previous->flags) { + range = range->previous; + if (range->flags & groupFlag) + offset += range->count; + decrementIndexes(range->count); + } + + // Iterate forwards looking for the first range which contains both the offset and the + // iterator group. + while (range->flags && (offset >= range->count || !(range->flags & groupFlag))) { + if (range->flags & groupFlag) + offset -= range->count; + incrementIndexes(range->count); + range = range->next; + } + + // Update all the indexes to inclue the remaining offset. + incrementIndexes(offset); + + return *this; +} + +QQmlListCompositor::insert_iterator &QQmlListCompositor::insert_iterator::operator +=(int difference) +{ + iterator::operator +=(difference); + + // If the previous range contains the append flag move the iterator to the tail of the previous + // range so that appended appear after the insert position. + if (offset == 0 && range->previous->append()) { + range = range->previous; + offset = range->inGroup() ? range->count : 0; + } + + return *this; +} + + +/*! + Constructs an empty list compositor. +*/ + +QQmlListCompositor::QQmlListCompositor() + : m_end(m_ranges.next, 0, Default, 2) + , m_cacheIt(m_end) + , m_groupCount(2) + , m_defaultFlags(PrependFlag | DefaultFlag) + , m_removeFlags(AppendFlag | PrependFlag | GroupMask) + , m_moveId(0) +{ +} + +/*! + Destroys a list compositor. +*/ + +QQmlListCompositor::~QQmlListCompositor() +{ + for (Range *next, *range = m_ranges.next; range != &m_ranges; range = next) { + next = range->next; + delete range; + } +} + +/*! + Inserts a range with the given source \a list, start \a index, \a count and \a flags, in front + of the existing range \a before. +*/ + +inline QQmlListCompositor::Range *QQmlListCompositor::insert( + Range *before, void *list, int index, int count, uint flags) +{ + return new Range(before, list, index, count, flags); +} + +/*! + Erases a \a range from the compositor. + + Returns a pointer to the next range in the compositor. +*/ + +inline QQmlListCompositor::Range *QQmlListCompositor::erase( + Range *range) +{ + Range *next = range->next; + next->previous = range->previous; + next->previous->next = range->next; + delete range; + return next; +} + +/*! + Sets the number (\a count) of possible groups that items may belong to in a compositor. +*/ + +void QQmlListCompositor::setGroupCount(int count) +{ + m_groupCount = count; + m_end = iterator(&m_ranges, 0, Default, m_groupCount); + m_cacheIt = m_end; +} + +/*! + Returns the number of items that belong to a \a group. +*/ + +int QQmlListCompositor::count(Group group) const +{ + return m_end.index[group]; +} + +/*! + Returns an iterator representing the item at \a index in a \a group. + + The index must be between 0 and count(group) - 1. +*/ + +QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< group << index) + Q_ASSERT(index >=0 && index < count(group)); + if (m_cacheIt == m_end) { + m_cacheIt = iterator(m_ranges.next, 0, group, m_groupCount); + m_cacheIt += index; + } else { + const int offset = index - m_cacheIt.index[group]; + m_cacheIt.setGroup(group); + m_cacheIt += offset; + } + Q_ASSERT(m_cacheIt.index[group] == index); + Q_ASSERT(m_cacheIt->inGroup(group)); + QT_QML_VERIFY_LISTCOMPOSITOR + return m_cacheIt; +} + +/*! + Returns an iterator representing the item at \a index in a \a group. + + The index must be between 0 and count(group) - 1. +*/ + +QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) const +{ + return const_cast(this)->find(group, index); +} + +/*! + Returns an iterator representing an insert position in front of the item at \a index in a + \a group. + + The iterator for an insert position can sometimes resolve to a different Range than a regular + iterator. This is because when items are inserted on a boundary between Ranges, if the first + range has the Append flag set then the items should be inserted into that range to ensure + that the append position for the existing range remains after the insert position. + + The index must be between 0 and count(group) - 1. +*/ + +QQmlListCompositor::insert_iterator QQmlListCompositor::findInsertPosition(Group group, int index) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< group << index) + Q_ASSERT(index >=0 && index <= count(group)); + insert_iterator it; + if (m_cacheIt == m_end) { + it = iterator(m_ranges.next, 0, group, m_groupCount); + it += index; + } else { + const int offset = index - m_cacheIt.index[group]; + it = m_cacheIt; + it.setGroup(group); + it += offset; + } + Q_ASSERT(it.index[group] == index); + return it; +} + +/*! + Appends a range of \a count indexes starting at \a index from a \a list into a compositor + with the given \a flags. + + If supplied the \a inserts list will be populated with the positions of the inserted items + in each group. +*/ + +void QQmlListCompositor::append( + void *list, int index, int count, uint flags, QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count << flags) + insert(m_end, list, index, count, flags, inserts); +} + +/*! + Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags + into a \a group at index \a before. + + If supplied the \a inserts list will be populated with the positions of items inserted into + each group. +*/ + +void QQmlListCompositor::insert( + Group group, int before, void *list, int index, int count, uint flags, QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< group << before << list << index << count << flags) + insert(findInsertPosition(group, before), list, index, count, flags, inserts); +} + +/*! + Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags + into a compositor at position \a before. + + If supplied the \a inserts list will be populated with the positions of items inserted into + each group. +*/ + +QQmlListCompositor::iterator QQmlListCompositor::insert( + iterator before, void *list, int index, int count, uint flags, QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< before << list << index << count << flags) + if (inserts) { + inserts->append(Insert(before, count, flags & GroupMask)); + } + if (before.offset > 0) { + // Inserting into the middle of a range. Split it two and update the iterator so it's + // positioned at the start of the second half. + *before = insert( + *before, before->list, before->index, before.offset, before->flags & ~AppendFlag)->next; + before->index += before.offset; + before->count -= before.offset; + before.offset = 0; + } + + + if (!(flags & AppendFlag) && *before != m_ranges.next + && before->previous->list == list + && before->previous->flags == flags + && (!list || before->previous->end() == index)) { + // The insert arguments represent a continuation of the previous range so increment + // its count instead of inserting a new range. + before->previous->count += count; + before.incrementIndexes(count, flags); + } else { + *before = insert(*before, list, index, count, flags); + before.offset = 0; + } + + if (!(flags & AppendFlag) && before->next != &m_ranges + && before->list == before->next->list + && before->flags == before->next->flags + && (!list || before->end() == before->next->index)) { + // The current range and the next are continuous so add their counts and delete one. + before->next->index = before->index; + before->next->count += before->count; + *before = erase(*before); + } + + m_end.incrementIndexes(count, flags); + m_cacheIt = before; + QT_QML_VERIFY_LISTCOMPOSITOR + return before; +} + +/*! + Sets the given flags \a flags on \a count items belonging to \a group starting at the position + identified by \a fromGroup and the index \a from. + + If supplied the \a inserts list will be populated with insert notifications for affected groups. +*/ + +void QQmlListCompositor::setFlags( + Group fromGroup, int from, int count, Group group, int flags, QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags) + setFlags(find(fromGroup, from), count, group, flags, inserts); +} + +/*! + Sets the given flags \a flags on \a count items belonging to \a group starting at the position + \a from. + + If supplied the \a inserts list will be populated with insert notifications for affected groups. +*/ + +void QQmlListCompositor::setFlags( + iterator from, int count, Group group, uint flags, QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags) + if (!flags || !count) + return; + + if (from != group) { + // Skip to the next full range if the start one is not a member of the target group. + from.incrementIndexes(from->count - from.offset); + from.offset = 0; + *from = from->next; + } else if (from.offset > 0) { + // If the start position is mid range split off the portion unaffected. + *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next; + from->index += from.offset; + from->count -= from.offset; + from.offset = 0; + } + + for (; count > 0; *from = from->next) { + if (from != from.group) { + // Skip ranges that are not members of the target group. + from.incrementIndexes(from->count); + continue; + } + // Find the number of items affected in the current range. + const int difference = qMin(count, from->count); + count -= difference; + + // Determine the actual changes made to the range and increment counts accordingly. + const uint insertFlags = ~from->flags & flags; + const uint setFlags = (from->flags | flags) & ~AppendFlag; + if (insertFlags && inserts) + inserts->append(Insert(from, difference, insertFlags | (from->flags & CacheFlag))); + m_end.incrementIndexes(difference, insertFlags); + from.incrementIndexes(difference, setFlags); + + if (from->previous != &m_ranges + && from->previous->list == from->list + && (!from->list || from->previous->end() == from->index) + && from->previous->flags == setFlags) { + // If the additional flags make the current range a continuation of the previous + // then move the affected items over to the previous range. + from->previous->count += difference; + from->index += difference; + from->count -= difference; + if (from->count == 0) { + // Delete the current range if it is now empty, preserving the append flag + // in the previous range. + if (from->append()) + from->previous->flags |= AppendFlag; + *from = erase(*from)->previous; + continue; + } else { + break; + } + } else if (!insertFlags) { + // No new flags, so roll onto the next range. + from.incrementIndexes(from->count - difference); + continue; + } else if (difference < from->count) { + // Create a new range with the updated flags, and remove the affected items + // from the current range. + *from = insert(*from, from->list, from->index, difference, setFlags)->next; + from->index += difference; + from->count -= difference; + } else { + // The whole range is affected so simply update the flags. + from->flags |= flags; + continue; + } + from.incrementIndexes(from->count); + } + + if (from->previous != &m_ranges + && from->previous->list == from->list + && (!from->list || from->previous->end() == from->index) + && from->previous->flags == (from->flags & ~AppendFlag)) { + // If the following range is now a continuation, merge it with its previous range. + from.offset = from->previous->count; + from->previous->count += from->count; + from->previous->flags = from->flags; + *from = erase(*from)->previous; + } + m_cacheIt = from; + QT_QML_VERIFY_LISTCOMPOSITOR +} + +/*! + Clears the given flags \a flags on \a count items belonging to \a group starting at the position + \a from. + + If supplied the \a removes list will be populated with remove notifications for affected groups. +*/ + +void QQmlListCompositor::clearFlags( + Group fromGroup, int from, int count, Group group, uint flags, QVector *removes) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags) + clearFlags(find(fromGroup, from), count, group, flags, removes); +} + +/*! + Clears the given flags \a flags on \a count items belonging to \a group starting at the position + identified by \a fromGroup and the index \a from. + + If supplied the \a removes list will be populated with remove notifications for affected groups. +*/ + +void QQmlListCompositor::clearFlags( + iterator from, int count, Group group, uint flags, QVector *removes) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags) + if (!flags || !count) + return; + + const bool clearCache = flags & CacheFlag; + + if (from != group) { + // Skip to the next full range if the start one is not a member of the target group. + from.incrementIndexes(from->count - from.offset); + from.offset = 0; + *from = from->next; + } else if (from.offset > 0) { + // If the start position is mid range split off the portion unaffected. + *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next; + from->index += from.offset; + from->count -= from.offset; + from.offset = 0; + } + + for (; count > 0; *from = from->next) { + if (from != group) { + // Skip ranges that are not members of the target group. + from.incrementIndexes(from->count); + continue; + } + // Find the number of items affected in the current range. + const int difference = qMin(count, from->count); + count -= difference; + + + // Determine the actual changes made to the range and decrement counts accordingly. + const uint removeFlags = from->flags & flags & ~(AppendFlag | PrependFlag); + const uint clearedFlags = from->flags & ~(flags | AppendFlag | UnresolvedFlag); + if (removeFlags && removes) { + const int maskedFlags = clearCache + ? (removeFlags & ~CacheFlag) + : (removeFlags | (from->flags & CacheFlag)); + if (maskedFlags) + removes->append(Remove(from, difference, maskedFlags)); + } + m_end.decrementIndexes(difference, removeFlags); + from.incrementIndexes(difference, clearedFlags); + + if (from->previous != &m_ranges + && from->previous->list == from->list + && (!from->list || clearedFlags == CacheFlag || from->previous->end() == from->index) + && from->previous->flags == clearedFlags) { + // If the removed flags make the current range a continuation of the previous + // then move the affected items over to the previous range. + from->previous->count += difference; + from->index += difference; + from->count -= difference; + if (from->count == 0) { + // Delete the current range if it is now empty, preserving the append flag + if (from->append()) + from->previous->flags |= AppendFlag; + *from = erase(*from)->previous; + } else { + from.incrementIndexes(from->count); + } + } else if (difference < from->count) { + // Create a new range with the reduced flags, and remove the affected items from + // the current range. + if (clearedFlags) + *from = insert(*from, from->list, from->index, difference, clearedFlags)->next; + from->index += difference; + from->count -= difference; + from.incrementIndexes(from->count); + } else if (clearedFlags) { + // The whole range is affected so simply update the flags. + from->flags &= ~flags; + } else { + // All flags have been removed from the range so remove it. + *from = erase(*from)->previous; + } + } + + if (*from != &m_ranges && from->previous != &m_ranges + && from->previous->list == from->list + && (!from->list || from->previous->end() == from->index) + && from->previous->flags == (from->flags & ~AppendFlag)) { + // If the following range is now a continuation, merge it with its previous range. + from.offset = from->previous->count; + from->previous->count += from->count; + from->previous->flags = from->flags; + *from = erase(*from)->previous; + } + m_cacheIt = from; + QT_QML_VERIFY_LISTCOMPOSITOR +} + +bool QQmlListCompositor::verifyMoveTo( + Group fromGroup, int from, Group toGroup, int to, int count, Group group) const +{ + if (group != toGroup) { + // determine how many items from the destination group intersect with the source group. + iterator fromIt = find(fromGroup, from); + + int intersectingCount = 0; + + for (; count > 0; *fromIt = fromIt->next) { + if (*fromIt == &m_ranges) + return false; + if (!fromIt->inGroup(group)) + continue; + if (fromIt->inGroup(toGroup)) + intersectingCount += qMin(count, fromIt->count - fromIt.offset); + count -= fromIt->count - fromIt.offset; + fromIt.offset = 0; + } + count = intersectingCount; + } + + return to >= 0 && to + count <= m_end.index[toGroup]; +} + +/*! + \internal + + Moves \a count items belonging to \a moveGroup from the index \a from in \a fromGroup + to the index \a to in \a toGroup. + + If \a removes and \a inserts are not null they will be populated with per group notifications + of the items moved. + */ + +void QQmlListCompositor::move( + Group fromGroup, + int from, + Group toGroup, + int to, + int count, + Group moveGroup, + QVector *removes, + QVector *inserts) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << toGroup << to << count) + Q_ASSERT(count > 0); + Q_ASSERT(from >=0); + Q_ASSERT(verifyMoveTo(fromGroup, from, toGroup, to, count, moveGroup)); + + // Find the position of the first item to move. + iterator fromIt = find(fromGroup, from); + + if (fromIt != moveGroup) { + // If the range at the from index doesn't contain items from the move group; skip + // to the next range. + fromIt.incrementIndexes(fromIt->count - fromIt.offset); + fromIt.offset = 0; + *fromIt = fromIt->next; + } else if (fromIt.offset > 0) { + // If the range at the from index contains items from the move group and the index isn't + // at the start of the range; split the range at the index and move the iterator to start + // of the second range. + *fromIt = insert( + *fromIt, fromIt->list, fromIt->index, fromIt.offset, fromIt->flags & ~AppendFlag)->next; + fromIt->index += fromIt.offset; + fromIt->count -= fromIt.offset; + fromIt.offset = 0; + } + + // Remove count items belonging to the move group from the list. + Range movedFlags; + for (int moveId = m_moveId; count > 0;) { + if (fromIt != moveGroup) { + // Skip ranges not containing items from the move group. + fromIt.incrementIndexes(fromIt->count); + *fromIt = fromIt->next; + continue; + } + int difference = qMin(count, fromIt->count); + + // Create a new static range containing the moved items from an existing range. + new Range( + &movedFlags, + fromIt->list, + fromIt->index, + difference, + fromIt->flags & ~(PrependFlag | AppendFlag)); + // Remove moved items from the count, the existing range, and a remove notification. + if (removes) + removes->append(Remove(fromIt, difference, fromIt->flags, ++moveId)); + count -= difference; + fromIt->count -= difference; + + // If the existing range contains the prepend flag replace the removed items with + // a placeholder range for new items inserted into the source model. + int removeIndex = fromIt->index; + if (fromIt->prepend() + && fromIt->previous != &m_ranges + && fromIt->previous->flags == PrependFlag + && fromIt->previous->list == fromIt->list + && fromIt->previous->end() == fromIt->index) { + // Grow the previous range instead of creating a new one if possible. + fromIt->previous->count += difference; + } else if (fromIt->prepend()) { + *fromIt = insert(*fromIt, fromIt->list, removeIndex, difference, PrependFlag)->next; + } + fromIt->index += difference; + + if (fromIt->count == 0) { + // If the existing range has no items remaining; remove it from the list. + if (fromIt->append()) + fromIt->previous->flags |= AppendFlag; + *fromIt = erase(*fromIt); + + // If the ranges before and after the removed range can be joined, do so. + if (*fromIt != m_ranges.next && fromIt->flags == PrependFlag + && fromIt->previous != &m_ranges + && fromIt->previous->flags == PrependFlag + && fromIt->previous->list == fromIt->list + && fromIt->previous->end() == fromIt->index) { + fromIt.incrementIndexes(fromIt->count); + fromIt->previous->count += fromIt->count; + *fromIt = erase(*fromIt); + } + } else if (count > 0) { + *fromIt = fromIt->next; + } + } + + // Try and join the range following the removed items to the range preceding it. + if (*fromIt != m_ranges.next + && *fromIt != &m_ranges + && fromIt->previous->list == fromIt->list + && (!fromIt->list || fromIt->previous->end() == fromIt->index) + && fromIt->previous->flags == (fromIt->flags & ~AppendFlag)) { + if (fromIt == fromIt.group) + fromIt.offset = fromIt->previous->count; + fromIt.offset = fromIt->previous->count; + fromIt->previous->count += fromIt->count; + fromIt->previous->flags = fromIt->flags; + *fromIt = erase(*fromIt)->previous; + } + + // Find the destination position of the move. + insert_iterator toIt = fromIt; + toIt.setGroup(toGroup); + + const int difference = to - toIt.index[toGroup]; + toIt += difference; + + // If the insert position is part way through a range; split it and move the iterator to the + // start of the second range. + if (toIt.offset > 0) { + *toIt = insert(*toIt, toIt->list, toIt->index, toIt.offset, toIt->flags & ~AppendFlag)->next; + toIt->index += toIt.offset; + toIt->count -= toIt.offset; + toIt.offset = 0; + } + + // Insert the moved ranges before the insert iterator, growing the previous range if that + // is an option. + for (Range *range = movedFlags.previous; range != &movedFlags; range = range->previous) { + if (*toIt != &m_ranges + && range->list == toIt->list + && (!range->list || range->end() == toIt->index) + && range->flags == (toIt->flags & ~AppendFlag)) { + toIt->index -= range->count; + toIt->count += range->count; + } else { + *toIt = insert(*toIt, range->list, range->index, range->count, range->flags); + } + } + + // Try and join the range after the inserted ranges to the last range inserted. + if (*toIt != m_ranges.next + && toIt->previous->list == toIt->list + && (!toIt->list || (toIt->previous->end() == toIt->index && toIt->previous->flags == (toIt->flags & ~AppendFlag)))) { + toIt.offset = toIt->previous->count; + toIt->previous->count += toIt->count; + toIt->previous->flags = toIt->flags; + *toIt = erase(*toIt)->previous; + } + // Create insert notification for the ranges moved. + Insert insert(toIt, 0, 0, 0); + for (Range *next, *range = movedFlags.next; range != &movedFlags; range = next) { + insert.count = range->count; + insert.flags = range->flags; + if (inserts) { + insert.moveId = ++m_moveId; + inserts->append(insert); + } + for (int i = 0; i < m_groupCount; ++i) { + if (insert.inGroup(i)) + insert.index[i] += range->count; + } + + next = range->next; + delete range; + } + + m_cacheIt = toIt; + + QT_QML_VERIFY_LISTCOMPOSITOR +} + +/*! + Clears the contents of a compositor. +*/ + +void QQmlListCompositor::clear() +{ + QT_QML_TRACE_LISTCOMPOSITOR("") + for (Range *range = m_ranges.next; range != &m_ranges; range = erase(range)) {} + m_end = iterator(m_ranges.next, 0, Default, m_groupCount); + m_cacheIt = m_end; +} + +void QQmlListCompositor::listItemsInserted( + QVector *translatedInsertions, + void *list, + const QVector &insertions, + const QVector *movedFlags) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << insertions) + for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { + if (it->list != list || it->flags == CacheFlag) { + // Skip ranges that don't reference list. + it.incrementIndexes(it->count); + continue; + } else if (it->flags & MovedFlag) { + // Skip ranges that were already moved in listItemsRemoved. + it->flags &= ~MovedFlag; + it.incrementIndexes(it->count); + continue; + } + for (const QQmlChangeSet::Change &insertion : insertions) { + int offset = insertion.index - it->index; + if ((offset > 0 && offset < it->count) + || (offset == 0 && it->prepend()) + || (offset == it->count && it->append())) { + // The insert index is within the current range. + if (it->prepend()) { + // The range has the prepend flag set so we insert new items into the range. + uint flags = m_defaultFlags; + if (insertion.isMove()) { + // If the insert was part of a move replace the default flags with + // the flags from the source range. + for (QVector::const_iterator move = movedFlags->begin(); + move != movedFlags->end(); + ++move) { + if (move->moveId == insertion.moveId) { + flags = move->flags; + break; + } + } + } + if (flags & ~(AppendFlag | PrependFlag)) { + // If any items are added to groups append an insert notification. + Insert translatedInsert(it, insertion.count, flags, insertion.moveId); + for (int i = 0; i < m_groupCount; ++i) { + if (it->inGroup(i)) + translatedInsert.index[i] += offset; + } + translatedInsertions->append(translatedInsert); + } + if ((it->flags & ~AppendFlag) == flags) { + // Accumulate items on the current range it its flags are the same as + // the insert flags. + it->count += insertion.count; + } else if (offset == 0 + && it->previous != &m_ranges + && it->previous->list == list + && it->previous->end() == insertion.index + && it->previous->flags == flags) { + // Attempt to append to the previous range if the insert position is at + // the start of the current range. + it->previous->count += insertion.count; + it->index += insertion.count; + it.incrementIndexes(insertion.count); + } else { + if (offset > 0) { + // Divide the current range at the insert position. + it.incrementIndexes(offset); + *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; + } + // Insert a new range, and increment the start index of the current range. + *it = insert(*it, it->list, insertion.index, insertion.count, flags)->next; + it.incrementIndexes(insertion.count, flags); + it->index += offset + insertion.count; + it->count -= offset; + } + m_end.incrementIndexes(insertion.count, flags); + } else { + // The range doesn't have the prepend flag set so split the range into parts; + // one before the insert position and one after the last inserted item. + if (offset > 0) { + *it = insert(*it, it->list, it->index, offset, it->flags)->next; + it->index += offset; + it->count -= offset; + } + it->index += insertion.count; + } + } else if (offset <= 0) { + // The insert position was before the current range so increment the start index. + it->index += insertion.count; + } + } + it.incrementIndexes(it->count); + } + m_cacheIt = m_end; + QT_QML_VERIFY_LISTCOMPOSITOR +} + +/*! + Updates the contents of a compositor when \a count items are inserted into a \a list at + \a index. + + This corrects the indexes of each range for that list in the compositor, splitting the range + in two if the insert index is in the middle of that range. If a range at the insert position + has the Prepend flag set then a new range will be inserted at that position with the groups + specified in defaultGroups(). If the insert index corresponds to the end of a range with + the Append flag set a new range will be inserted before the end of the append range. + + The \a translatedInsertions list is populated with insert notifications for affected + groups. +*/ + +void QQmlListCompositor::listItemsInserted( + void *list, int index, int count, QVector *translatedInsertions) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count > 0); + + QVector insertions; + insertions.append(QQmlChangeSet::Change(index, count)); + + listItemsInserted(translatedInsertions, list, insertions); +} + +void QQmlListCompositor::listItemsRemoved( + QVector *translatedRemovals, + void *list, + QVector *removals, + QVector *insertions, + QVector *movedFlags) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << *removals) + + for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { + if (it->list != list || it->flags == CacheFlag) { + // Skip ranges that don't reference list. + it.incrementIndexes(it->count); + continue; + } + bool removed = false; + for (QVector::iterator removal = removals->begin(); + !removed && removal != removals->end(); + ++removal) { + int relativeIndex = removal->index - it->index; + int itemsRemoved = removal->count; + if (relativeIndex + removal->count > 0 && relativeIndex < it->count) { + // If the current range intersects the remove; remove the intersecting items. + const int offset = qMax(0, relativeIndex); + int removeCount = qMin(it->count, relativeIndex + removal->count) - offset; + it->count -= removeCount; + int removeFlags = it->flags & m_removeFlags; + Remove translatedRemoval(it, removeCount, it->flags); + for (int i = 0; i < m_groupCount; ++i) { + if (it->inGroup(i)) + translatedRemoval.index[i] += offset; + } + if (removal->isMove()) { + // If the removal was part of a move find the corresponding insert. + QVector::iterator insertion = insertions->begin(); + for (; insertion != insertions->end() && insertion->moveId != removal->moveId; + ++insertion) {} + Q_ASSERT(insertion != insertions->end()); + Q_ASSERT(insertion->count == removal->count); + + if (relativeIndex < 0) { + // If the remove started before the current range, split it and the + // corresponding insert so we're only working with intersecting part. + int splitMoveId = ++m_moveId; + removal = removals->insert(removal, QQmlChangeSet::Change( + removal->index, -relativeIndex, splitMoveId)); + ++removal; + removal->count -= -relativeIndex; + insertion = insertions->insert(insertion, QQmlChangeSet::Change( + insertion->index, -relativeIndex, splitMoveId)); + ++insertion; + insertion->index += -relativeIndex; + insertion->count -= -relativeIndex; + } + + if (it->prepend()) { + // If the current range has the prepend flag preserve its flags to transfer + // to its new location. + removeFlags |= it->flags & CacheFlag; + translatedRemoval.moveId = ++m_moveId; + movedFlags->append(MovedFlags(m_moveId, it->flags & ~AppendFlag)); + + if (removeCount < removal->count) { + // If the remove doesn't encompass all of the current range, + // split it and the corresponding insert. + removal = removals->insert(removal, QQmlChangeSet::Change( + removal->index, removeCount, translatedRemoval.moveId)); + ++removal; + insertion = insertions->insert(insertion, QQmlChangeSet::Change( + insertion->index, removeCount, translatedRemoval.moveId)); + ++insertion; + + removal->count -= removeCount; + insertion->index += removeCount; + insertion->count -= removeCount; + } else { + // If there's no need to split the move simply replace its moveId + // with that of the translated move. + removal->moveId = translatedRemoval.moveId; + insertion->moveId = translatedRemoval.moveId; + } + } else { + // If the current range doesn't have prepend flags then insert a new range + // with list indexes from the corresponding insert location. The MoveFlag + // is to notify listItemsInserted that it can skip evaluation of that range. + if (offset > 0) { + *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; + it->index += offset; + it->count -= offset; + it.incrementIndexes(offset); + } + if (it->previous != &m_ranges + && it->previous->list == it->list + && it->end() == insertion->index + && it->previous->flags == (it->flags | MovedFlag)) { + it->previous->count += removeCount; + } else { + *it = insert(*it, it->list, insertion->index, removeCount, it->flags | MovedFlag)->next; + } + // Clear the changed flags as the item hasn't been removed. + translatedRemoval.flags = 0; + removeFlags = 0; + } + } else if (it->inCache()) { + // If not moving and the current range has the cache flag, insert a new range + // with just the cache flag set to retain caching information for the removed + // range. + if (offset > 0) { + *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next; + it->index += offset; + it->count -= offset; + it.incrementIndexes(offset); + } + if (it->previous != &m_ranges + && it->previous->list == it->list + && it->previous->flags == CacheFlag) { + it->previous->count += removeCount; + } else { + *it = insert(*it, it->list, -1, removeCount, CacheFlag)->next; + } + it.index[Cache] += removeCount; + } + if (removeFlags & GroupMask) + translatedRemovals->append(translatedRemoval); + m_end.decrementIndexes(removeCount, removeFlags); + if (it->count == 0 && !it->append()) { + // Erase empty non-append ranges. + *it = erase(*it)->previous; + removed = true; + } else if (relativeIndex <= 0) { + // If the remove started before the current range move the start index of + // the range to the remove index. + it->index = removal->index; + } + } else if (relativeIndex < 0) { + // If the remove was before the current range decrement the start index by the + // number of items removed. + it->index -= itemsRemoved; + + if (it->previous != &m_ranges + && it->previous->list == it->list + && it->previous->end() == it->index + && it->previous->flags == (it->flags & ~AppendFlag)) { + // Compress ranges made continuous by the removal of separating ranges. + it.decrementIndexes(it->previous->count); + it->previous->count += it->count; + it->previous->flags = it->flags; + *it = erase(*it)->previous; + } + } + } + if (it->flags == CacheFlag && it->next->flags == CacheFlag && it->next->list == it->list) { + // Compress consecutive cache only ranges. + it.index[Cache] += it->next->count; + it->count += it->next->count; + erase(it->next); + } else if (!removed) { + it.incrementIndexes(it->count); + } + } + m_cacheIt = m_end; + QT_QML_VERIFY_LISTCOMPOSITOR +} + + +/*! + Updates the contents of a compositor when \a count items are removed from a \a list at + \a index. + + Ranges that intersect the specified range are removed from the compositor and the indexes of + ranges after index + count are updated. + + The \a translatedRemovals list is populated with remove notifications for the affected + groups. +*/ + + +void QQmlListCompositor::listItemsRemoved( + void *list, int index, int count, QVector *translatedRemovals) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count >= 0); + + QVector removals; + removals.append(QQmlChangeSet::Change(index, count)); + listItemsRemoved(translatedRemovals, list, &removals, nullptr, nullptr); +} + +/*! + Updates the contents of a compositor when \a count items are removed from a \a list at + \a index. + + Ranges that intersect the specified range are removed from the compositor and the indexes of + ranges after index + count are updated. + + The \a translatedRemovals and translatedInserts lists are populated with move + notifications for the affected groups. +*/ + +void QQmlListCompositor::listItemsMoved( + void *list, + int from, + int to, + int count, + QVector *translatedRemovals, + QVector *translatedInsertions) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << from << to << count) + Q_ASSERT(count >= 0); + + QVector removals; + QVector insertions; + QVector movedFlags; + removals.append(QQmlChangeSet::Change(from, count, 0)); + insertions.append(QQmlChangeSet::Change(to, count, 0)); + + listItemsRemoved(translatedRemovals, list, &removals, &insertions, &movedFlags); + listItemsInserted(translatedInsertions, list, insertions, &movedFlags); +} + +void QQmlListCompositor::listItemsChanged( + QVector *translatedChanges, + void *list, + const QVector &changes) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << changes) + for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { + if (it->list != list || it->flags == CacheFlag) { + it.incrementIndexes(it->count); + continue; + } else if (!it->inGroup()) { + continue; + } + for (const QQmlChangeSet::Change &change : changes) { + const int offset = change.index - it->index; + if (offset + change.count > 0 && offset < it->count) { + const int changeOffset = qMax(0, offset); + const int changeCount = qMin(it->count, offset + change.count) - changeOffset; + + Change translatedChange(it, changeCount, it->flags); + for (int i = 0; i < m_groupCount; ++i) { + if (it->inGroup(i)) + translatedChange.index[i] += changeOffset; + } + translatedChanges->append(translatedChange); + } + } + it.incrementIndexes(it->count); + } +} + + +/*! + Translates the positions of \a count changed items at \a index in a \a list. + + The \a translatedChanges list is populated with change notifications for the + affected groups. +*/ + +void QQmlListCompositor::listItemsChanged( + void *list, int index, int count, QVector *translatedChanges) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count >= 0); + QVector changes; + changes.append(QQmlChangeSet::Change(index, count)); + listItemsChanged(translatedChanges, list, changes); +} + +void QQmlListCompositor::transition( + Group from, + Group to, + QVector *removes, + QVector *inserts) +{ + int removeCount = 0; + for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) { + if (it == from && it != to) { + removes->append(QQmlChangeSet::Change(it.index[from]- removeCount, it->count)); + removeCount += it->count; + } else if (it != from && it == to) { + inserts->append(QQmlChangeSet::Change(it.index[to], it->count)); + } + it.incrementIndexes(it->count); + } +} + +/*! + \internal + Writes the name of \a group to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group) +{ + switch (group) { + case QQmlListCompositor::Cache: return debug << "Cache"; + case QQmlListCompositor::Default: return debug << "Default"; + default: return (debug.nospace() << "Group" << int(group)).space(); + } + +} + +/*! + \internal + Writes the contents of \a range to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range) +{ + (debug.nospace() + << "Range(" + << range.list) << ' ' + << range.index << ' ' + << range.count << ' ' + << (range.isUnresolved() ? 'U' : '0') + << (range.append() ? 'A' : '0') + << (range.prepend() ? 'P' : '0'); + for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i) + debug << (range.inGroup(i) ? '1' : '0'); + return (debug + << (range.inGroup(QQmlListCompositor::Default) ? 'D' : '0') + << (range.inGroup(QQmlListCompositor::Cache) ? 'C' : '0')); +} + +static void qt_print_indexes(QDebug &debug, int count, const int *indexes) +{ + for (int i = count - 1; i >= 0; --i) + debug << indexes[i]; +} + +/*! + \internal + Writes the contents of \a it to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it) +{ + (debug.nospace() << "iterator(" << it.group).space() << "offset:" << it.offset; + qt_print_indexes(debug, it.groupCount, it.index); + return ((debug << **it).nospace() << ')').space(); +} + +static QDebug qt_print_change(QDebug debug, const char *name, const QQmlListCompositor::Change &change) +{ + debug.nospace() << name << '(' << change.moveId << ' ' << change.count << ' '; + for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i) + debug << (change.inGroup(i) ? '1' : '0'); + debug << (change.inGroup(QQmlListCompositor::Default) ? 'D' : '0') + << (change.inGroup(QQmlListCompositor::Cache) ? 'C' : '0'); + int i = QQmlListCompositor::MaximumGroupCount - 1; + for (; i >= 0 && !change.inGroup(i); --i) {} + for (; i >= 0; --i) + debug << ' ' << change.index[i]; + return (debug << ')').maybeSpace(); +} + +/*! + \internal + Writes the contents of \a change to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change) +{ + return qt_print_change(debug, "Change", change); +} + +/*! + \internal + Writes the contents of \a remove to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove) +{ + return qt_print_change(debug, "Remove", remove); +} + +/*! + \internal + Writes the contents of \a insert to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert) +{ + return qt_print_change(debug, "Insert", insert); +} + +/*! + \internal + Writes the contents of \a list to \a debug. +*/ + +QDebug operator <<(QDebug debug, const QQmlListCompositor &list) +{ + int indexes[QQmlListCompositor::MaximumGroupCount]; + for (int i = 0; i < QQmlListCompositor::MaximumGroupCount; ++i) + indexes[i] = 0; + debug.nospace() << "QQmlListCompositor("; + qt_print_indexes(debug, list.m_groupCount, list.m_end.index); + for (QQmlListCompositor::Range *range = list.m_ranges.next; range != &list.m_ranges; range = range->next) { + (debug << '\n').space(); + qt_print_indexes(debug, list.m_groupCount, indexes); + debug << ' ' << *range; + + for (int i = 0; i < list.m_groupCount; ++i) { + if (range->inGroup(i)) + indexes[i] += range->count; + } + } + return (debug << ')').maybeSpace(); +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmllistcompositor_p.h b/src/qmlmodels/qqmllistcompositor_p.h new file mode 100644 index 0000000000..172040559c --- /dev/null +++ b/src/qmlmodels/qqmllistcompositor_p.h @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLLISTCOMPOSITOR_P_H +#define QQMLLISTCOMPOSITOR_P_H + +// +// 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. +// + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQmlListCompositor +{ +public: + enum { MinimumGroupCount = 3, MaximumGroupCount = 11 }; + + enum Group + { + Cache = 0, + Default = 1, + Persisted = 2 + }; + + enum Flag + { + CacheFlag = 1 << Cache, + DefaultFlag = 1 << Default, + PersistedFlag = 1 << Persisted, + PrependFlag = 0x10000000, + AppendFlag = 0x20000000, + UnresolvedFlag = 0x40000000, + MovedFlag = 0x80000000, + GroupMask = ~(PrependFlag | AppendFlag | UnresolvedFlag | MovedFlag | CacheFlag) + }; + + class Range + { + public: + Range() : next(this), previous(this) {} + Range(Range *next, void *list, int index, int count, uint flags) + : next(next), previous(next->previous), list(list), index(index), count(count), flags(flags) { + next->previous = this; previous->next = this; } + + Range *next; + Range *previous; + void *list = nullptr; + int index = 0; + int count = 0; + uint flags = 0; + + inline int start() const { return index; } + inline int end() const { return index + count; } + + inline int groups() const { return flags & GroupMask; } + + inline bool inGroup() const { return flags & GroupMask; } + inline bool inCache() const { return flags & CacheFlag; } + inline bool inGroup(int group) const { return flags & (1 << group); } + inline bool isUnresolved() const { return flags & UnresolvedFlag; } + + inline bool prepend() const { return flags & PrependFlag; } + inline bool append() const { return flags & AppendFlag; } + }; + + class Q_AUTOTEST_EXPORT iterator + { + public: + inline iterator(); + inline iterator(const iterator &it); + inline iterator(Range *range, int offset, Group group, int groupCount); + inline ~iterator() {} + + bool operator ==(const iterator &it) const { return range == it.range && offset == it.offset; } + bool operator !=(const iterator &it) const { return range != it.range || offset != it.offset; } + + bool operator ==(Group group) const { return range->flags & (1 << group); } + bool operator !=(Group group) const { return !(range->flags & (1 << group)); } + + Range *&operator *() { return range; } + Range * const &operator *() const { return range; } + Range *operator ->() { return range; } + const Range *operator ->() const { return range; } + + iterator &operator +=(int difference); + + template T *list() const { return static_cast(range->list); } + int modelIndex() const { return range->index + offset; } + + void incrementIndexes(int difference) { incrementIndexes(difference, range->flags); } + void decrementIndexes(int difference) { decrementIndexes(difference, range->flags); } + + inline void incrementIndexes(int difference, uint flags); + inline void decrementIndexes(int difference, uint flags); + + void setGroup(Group g) { group = g; groupFlag = 1 << g; } + + Range *range = nullptr; + int offset = 0; + Group group = Default; + int groupFlag; + int groupCount = 0; + union { + struct { + int cacheIndex; + }; + int index[MaximumGroupCount]; + }; + }; + + class Q_AUTOTEST_EXPORT insert_iterator : public iterator + { + public: + inline insert_iterator() {} + inline insert_iterator(const iterator &it) : iterator(it) {} + inline insert_iterator(Range *, int, Group, int); + inline ~insert_iterator() {} + + insert_iterator &operator +=(int difference); + }; + + struct Change + { + inline Change() {} + inline Change(const iterator &it, int count, uint flags, int moveId = -1); + int count; + uint flags; + int moveId; + union { + struct { + int cacheIndex; + }; + int index[MaximumGroupCount]; + }; + + inline bool isMove() const { return moveId >= 0; } + inline bool inCache() const { return flags & CacheFlag; } + inline bool inGroup() const { return flags & GroupMask; } + inline bool inGroup(int group) const { return flags & (CacheFlag << group); } + + inline int groups() const { return flags & GroupMask; } + }; + + struct Insert : public Change + { + Insert() {} + Insert(const iterator &it, int count, uint flags, int moveId = -1) + : Change(it, count, flags, moveId) {} + }; + + struct Remove : public Change + { + Remove() {} + Remove(const iterator &it, int count, uint flags, int moveId = -1) + : Change(it, count, flags, moveId) {} + }; + + QQmlListCompositor(); + ~QQmlListCompositor(); + + int defaultGroups() const { return m_defaultFlags & ~PrependFlag; } + void setDefaultGroups(int groups) { m_defaultFlags = groups | PrependFlag; } + void setDefaultGroup(Group group) { m_defaultFlags |= (1 << group); } + void clearDefaultGroup(Group group) { m_defaultFlags &= ~(1 << group); } + void setRemoveGroups(int groups) { m_removeFlags = PrependFlag | AppendFlag | groups; } + void setGroupCount(int count); + + int count(Group group) const; + iterator find(Group group, int index); + iterator find(Group group, int index) const; + insert_iterator findInsertPosition(Group group, int index); + + const iterator &end() { return m_end; } + + void append(void *list, int index, int count, uint flags, QVector *inserts = nullptr); + void insert(Group group, int before, void *list, int index, int count, uint flags, QVector *inserts = nullptr); + iterator insert(iterator before, void *list, int index, int count, uint flags, QVector *inserts = nullptr); + + void setFlags(Group fromGroup, int from, int count, Group group, int flags, QVector *inserts = nullptr); + void setFlags(iterator from, int count, Group group, uint flags, QVector *inserts = nullptr); + void setFlags(Group fromGroup, int from, int count, uint flags, QVector *inserts = nullptr) { + setFlags(fromGroup, from, count, fromGroup, flags, inserts); } + void setFlags(const iterator from, int count, uint flags, QVector *inserts = nullptr) { + setFlags(from, count, from.group, flags, inserts); } + + void clearFlags(Group fromGroup, int from, int count, Group group, uint flags, QVector *removals = nullptr); + void clearFlags(iterator from, int count, Group group, uint flags, QVector *removals = nullptr); + void clearFlags(Group fromGroup, int from, int count, uint flags, QVector *removals = nullptr) { + clearFlags(fromGroup, from, count, fromGroup, flags, removals); } + void clearFlags(const iterator &from, int count, uint flags, QVector *removals = nullptr) { + clearFlags(from, count, from.group, flags, removals); } + + bool verifyMoveTo(Group fromGroup, int from, Group toGroup, int to, int count, Group group) const; + + void move( + Group fromGroup, + int from, + Group toGroup, + int to, + int count, + Group group, + QVector *removals = nullptr, + QVector *inserts = nullptr); + void clear(); + + void listItemsInserted(void *list, int index, int count, QVector *inserts); + void listItemsRemoved(void *list, int index, int count, QVector *removals); + void listItemsMoved(void *list, int from, int to, int count, QVector *removals, QVector *inserts); + void listItemsChanged(void *list, int index, int count, QVector *changes); + + void transition( + Group from, + Group to, + QVector *removes, + QVector *inserts); + +private: + Range m_ranges; + iterator m_end; + iterator m_cacheIt; + int m_groupCount; + int m_defaultFlags; + int m_removeFlags; + int m_moveId; + + inline Range *insert(Range *before, void *list, int index, int count, uint flags); + inline Range *erase(Range *range); + + struct MovedFlags + { + MovedFlags() {} + MovedFlags(int moveId, uint flags) : moveId(moveId), flags(flags) {} + + int moveId; + uint flags; + }; + + void listItemsRemoved( + QVector *translatedRemovals, + void *list, + QVector *removals, + QVector *insertions = nullptr, + QVector *movedFlags = nullptr); + void listItemsInserted( + QVector *translatedInsertions, + void *list, + const QVector &insertions, + const QVector *movedFlags = nullptr); + void listItemsChanged( + QVector *translatedChanges, + void *list, + const QVector &changes); + + friend Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list); +}; + +Q_DECLARE_TYPEINFO(QQmlListCompositor::Change, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(QQmlListCompositor::Remove, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(QQmlListCompositor::Insert, Q_PRIMITIVE_TYPE); + +inline QQmlListCompositor::iterator::iterator() {} +inline QQmlListCompositor::iterator::iterator(const iterator &it) + : range(it.range) + , offset(it.offset) + , group(it.group) + , groupFlag(it.groupFlag) + , groupCount(it.groupCount) +{ + for (int i = 0; i < groupCount; ++i) + index[i] = it.index[i]; +} + +inline QQmlListCompositor::iterator::iterator( + Range *range, int offset, Group group, int groupCount) + : range(range) + , offset(offset) + , group(group) + , groupFlag(1 << group) + , groupCount(groupCount) +{ + for (int i = 0; i < groupCount; ++i) + index[i] = 0; +} + +inline void QQmlListCompositor::iterator::incrementIndexes(int difference, uint flags) +{ + for (int i = 0; i < groupCount; ++i) { + if (flags & (1 << i)) + index[i] += difference; + } +} + +inline void QQmlListCompositor::iterator::decrementIndexes(int difference, uint flags) +{ + for (int i = 0; i < groupCount; ++i) { + if (flags & (1 << i)) + index[i] -= difference; + } +} + +inline QQmlListCompositor::insert_iterator::insert_iterator( + Range *range, int offset, Group group, int groupCount) + : iterator(range, offset, group, groupCount) {} + +inline QQmlListCompositor::Change::Change(const iterator &it, int count, uint flags, int moveId) + : count(count), flags(flags), moveId(moveId) +{ + for (int i = 0; i < MaximumGroupCount; ++i) + index[i] = it.index[i]; +} + +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert); +Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list); + +QT_END_NAMESPACE + +#endif diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp new file mode 100644 index 0000000000..5b5bcd8464 --- /dev/null +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -0,0 +1,2900 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmllistmodel_p_p.h" +#include "qqmllistmodelworkeragent_p.h" +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*); + +QT_BEGIN_NAMESPACE + +// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models. +enum { MIN_LISTMODEL_UID = 1024 }; + +static QAtomicInt uidCounter(MIN_LISTMODEL_UID); + +template +static bool isMemoryUsed(const char *mem) +{ + for (size_t i=0 ; i < sizeof(T) ; ++i) { + if (mem[i] != 0) + return true; + } + + return false; +} + +static QString roleTypeName(ListLayout::Role::DataType t) +{ + static const QString roleTypeNames[] = { + QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"), + QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"), + QStringLiteral("DateTime"), QStringLiteral("Function") + }; + + if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType) + return roleTypeNames[t]; + + return QString(); +} + +const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type) +{ + QStringHash::Node *node = roleHash.findNode(key); + if (node) { + const Role &r = *node->value; + if (type != r.type) + qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); + return r; + } + + return createRole(key, type); +} + +const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type) +{ + QStringHash::Node *node = roleHash.findNode(key); + if (node) { + const Role &r = *node->value; + if (type != r.type) + qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); + return r; + } + + QString qkey = key->toQString(); + + return createRole(qkey, type); +} + +const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type) +{ + const int dataSizes[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) }; + const int dataAlignments[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) }; + + Role *r = new Role; + r->name = key; + r->type = type; + + if (type == Role::List) { + r->subLayout = new ListLayout; + } else { + r->subLayout = nullptr; + } + + int dataSize = dataSizes[type]; + int dataAlignment = dataAlignments[type]; + + int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1); + if (dataOffset + dataSize > ListElement::BLOCK_SIZE) { + r->blockIndex = ++currentBlock; + r->blockOffset = 0; + currentBlockOffset = dataSize; + } else { + r->blockIndex = currentBlock; + r->blockOffset = dataOffset; + currentBlockOffset = dataOffset + dataSize; + } + + int roleIndex = roles.count(); + r->index = roleIndex; + + roles.append(r); + roleHash.insert(key, r); + + return *r; +} + +ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0) +{ + const int otherRolesCount = other->roles.count(); + roles.reserve(otherRolesCount); + for (int i=0 ; i < otherRolesCount; ++i) { + Role *role = new Role(other->roles[i]); + roles.append(role); + roleHash.insert(role->name, role); + } + currentBlockOffset = other->currentBlockOffset; + currentBlock = other->currentBlock; +} + +ListLayout::~ListLayout() +{ + qDeleteAll(roles); +} + +void ListLayout::sync(ListLayout *src, ListLayout *target) +{ + int roleOffset = target->roles.count(); + int newRoleCount = src->roles.count() - roleOffset; + + for (int i=0 ; i < newRoleCount ; ++i) { + Role *role = new Role(src->roles[roleOffset + i]); + target->roles.append(role); + target->roleHash.insert(role->name, role); + } + + target->currentBlockOffset = src->currentBlockOffset; + target->currentBlock = src->currentBlock; +} + +ListLayout::Role::Role(const Role *other) +{ + name = other->name; + type = other->type; + blockIndex = other->blockIndex; + blockOffset = other->blockOffset; + index = other->index; + if (other->subLayout) + subLayout = new ListLayout(other->subLayout); + else + subLayout = nullptr; +} + +ListLayout::Role::~Role() +{ + delete subLayout; +} + +const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data) +{ + Role::DataType type; + + switch (data.type()) { + case QVariant::Double: type = Role::Number; break; + case QVariant::Int: type = Role::Number; break; + case QVariant::Bool: type = Role::Bool; break; + case QVariant::String: type = Role::String; break; + case QVariant::Map: type = Role::VariantMap; break; + case QVariant::DateTime: type = Role::DateTime; break; + case QVariant::UserType: { + if (data.userType() == qMetaTypeId() && + data.value().isCallable()) { + type = Role::Function; + break; + } else if (data.userType() == qMetaTypeId() + && data.value()->isTranslationBinding()) { + type = Role::String; + break; + } else { + type = Role::List; + break; + } + } + default: type = Role::Invalid; break; + } + + if (type == Role::Invalid) { + qmlWarning(nullptr) << "Can't create role for unsupported data type"; + return nullptr; + } + + return &getRoleOrCreate(key, type); +} + +const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const +{ + Role *r = nullptr; + QStringHash::Node *node = roleHash.findNode(key); + if (node) + r = node->value; + return r; +} + +const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const +{ + Role *r = nullptr; + QStringHash::Node *node = roleHash.findNode(key); + if (node) + r = node->value; + return r; +} + +StringOrTranslation::StringOrTranslation(const QString &s) +{ + d.setFlag(); + setString(s); +} + +StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding) +{ + d.setFlag(); + clear(); + d = binding; +} + +StringOrTranslation::~StringOrTranslation() +{ + clear(); +} + +void StringOrTranslation::setString(const QString &s) +{ + d.setFlag(); + clear(); + QStringData *stringData = const_cast(s).data_ptr(); + d = stringData; + if (stringData) + stringData->ref.ref(); +} + +void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding) +{ + d.setFlag(); + clear(); + d = binding; +} + +QString StringOrTranslation::toString(const QQmlListModel *owner) const +{ + if (d.isNull()) + return QString(); + if (d.isT1()) { + QStringDataPtr holder = { d.asT1() }; + holder.ptr->ref.ref(); + return QString(holder); + } + if (!owner) + return QString(); + return d.asT2()->valueAsString(owner->m_compilationUnit.data()); +} + +QString StringOrTranslation::asString() const +{ + if (d.isNull()) + return QString(); + if (!d.isT1()) + return QString(); + QStringDataPtr holder = { d.asT1() }; + holder.ptr->ref.ref(); + return QString(holder); +} + +void StringOrTranslation::clear() +{ + if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) { + if (!strData->ref.deref()) + QStringData::deallocate(strData); + } + d = static_cast(nullptr); +} + +QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex) +{ + ListElement *e = elements[elementIndex]; + if (e->m_objectCache == nullptr) { + void *memory = operator new(sizeof(QObject) + sizeof(QQmlData)); + void *ddataMemory = ((char *)memory) + sizeof(QObject); + e->m_objectCache = new (memory) QObject; + QQmlData *ddata = new (ddataMemory) QQmlData; + ddata->ownMemory = false; + QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata; + (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex); + } + return e->m_objectCache; +} + +bool ListModel::sync(ListModel *src, ListModel *target) +{ + // Sanity check + + bool hasChanges = false; + + // Build hash of elements <-> uid for each of the lists + QHash elementHash; + for (int i = 0; i < target->elements.count(); ++i) { + ListElement *e = target->elements.at(i); + int uid = e->getUid(); + ElementSync sync; + sync.target = e; + sync.targetIndex = i; + elementHash.insert(uid, sync); + } + for (int i = 0; i < src->elements.count(); ++i) { + ListElement *e = src->elements.at(i); + int uid = e->getUid(); + + QHash::iterator it = elementHash.find(uid); + if (it == elementHash.end()) { + ElementSync sync; + sync.src = e; + sync.srcIndex = i; + elementHash.insert(uid, sync); + } else { + ElementSync &sync = it.value(); + sync.src = e; + sync.srcIndex = i; + } + } + + QQmlListModel *targetModel = target->m_modelCache; + + // Get list of elements that are in the target but no longer in the source. These get deleted first. + int rowsRemoved = 0; + for (int i = 0 ; i < target->elements.count() ; ++i) { + ListElement *element = target->elements.at(i); + ElementSync &s = elementHash.find(element->getUid()).value(); + Q_ASSERT(s.targetIndex >= 0); + // need to update the targetIndex, to keep it correct after removals + s.targetIndex -= rowsRemoved; + if (s.src == nullptr) { + Q_ASSERT(s.targetIndex == i); + hasChanges = true; + if (targetModel) + targetModel->beginRemoveRows(QModelIndex(), i, i); + s.target->destroy(target->m_layout); + target->elements.removeOne(s.target); + delete s.target; + if (targetModel) + targetModel->endRemoveRows(); + ++rowsRemoved; + --i; + continue; + } + } + + // Sync the layouts + ListLayout::sync(src->m_layout, target->m_layout); + + // Clear the target list, and append in correct order from the source + target->elements.clear(); + for (int i = 0; i < src->elements.count(); ++i) { + ListElement *srcElement = src->elements.at(i); + ElementSync &s = elementHash.find(srcElement->getUid()).value(); + Q_ASSERT(s.srcIndex >= 0); + ListElement *targetElement = s.target; + if (targetElement == nullptr) { + targetElement = new ListElement(srcElement->getUid()); + } + s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout); + target->elements.append(targetElement); + } + + target->updateCacheIndices(); + + // Update values stored in target meta objects + for (int i=0 ; i < target->elements.count() ; ++i) { + ListElement *e = target->elements[i]; + if (ModelNodeMetaObject *mo = e->objectCache()) + mo->updateValues(); + } + + // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts, + // so the model indices can't be out of bounds + // + // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent + // model indices are updated correctly + int rowsInserted = 0; + for (int i = 0 ; i < target->elements.count() ; ++i) { + ListElement *element = target->elements.at(i); + ElementSync &s = elementHash.find(element->getUid()).value(); + Q_ASSERT(s.srcIndex >= 0); + s.srcIndex += rowsInserted; + if (s.srcIndex != s.targetIndex) { + if (targetModel) { + if (s.targetIndex == -1) { + targetModel->beginInsertRows(QModelIndex(), i, i); + targetModel->endInsertRows(); + } else { + targetModel->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex); + targetModel->endMoveRows(); + } + } + hasChanges = true; + ++rowsInserted; + } + if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) { + QModelIndex idx = targetModel->createIndex(i, 0); + if (targetModel) + targetModel->dataChanged(idx, idx, s.changedRoles); + hasChanges = true; + } + } + return hasChanges; +} + +ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache) +{ +} + +void ListModel::destroy() +{ + for (const auto &destroyer : remove(0, elements.count())) + destroyer(); + + m_layout = nullptr; + if (m_modelCache && m_modelCache->m_primary == false) + delete m_modelCache; + m_modelCache = nullptr; +} + +int ListModel::appendElement() +{ + int elementIndex = elements.count(); + newElement(elementIndex); + return elementIndex; +} + +void ListModel::insertElement(int index) +{ + newElement(index); + updateCacheIndices(index); +} + +void ListModel::move(int from, int to, int n) +{ + if (from > to) { + // Only move forwards - flip if backwards moving + int tfrom = from; + int tto = to; + from = tto; + to = tto+n; + n = tfrom-tto; + } + + QPODVector store; + for (int i=0 ; i < (to-from) ; ++i) + store.append(elements[from+n+i]); + for (int i=0 ; i < n ; ++i) + store.append(elements[from+i]); + for (int i=0 ; i < store.count() ; ++i) + elements[from+i] = store[i]; + + updateCacheIndices(from, to + n); +} + +void ListModel::newElement(int index) +{ + ListElement *e = new ListElement; + elements.insert(index, e); +} + +void ListModel::updateCacheIndices(int start, int end) +{ + int count = elements.count(); + + if (end < 0 || end > count) + end = count; + + for (int i = start; i < end; ++i) { + ListElement *e = elements.at(i); + if (ModelNodeMetaObject *mo = e->objectCache()) + mo->m_elementIndex = i; + } +} + +QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng) +{ + if (roleIndex >= m_layout->roleCount()) + return QVariant(); + ListElement *e = elements[elementIndex]; + const ListLayout::Role &r = m_layout->getExistingRole(roleIndex); + return e->getProperty(r, owner, eng); +} + +ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role) +{ + ListElement *e = elements[elementIndex]; + return e->getListProperty(role); +} + +void ListModel::set(int elementIndex, QV4::Object *object, QVector *roles) +{ + ListElement *e = elements[elementIndex]; + + QV4::ExecutionEngine *v4 = object->engine(); + QV4::Scope scope(v4); + QV4::ScopedObject o(scope); + + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedString propertyName(scope); + QV4::ScopedValue propertyValue(scope); + while (1) { + propertyName = it.nextPropertyNameAsString(propertyValue); + if (!propertyName) + break; + + // Check if this key exists yet + int roleIndex = -1; + + // Add the value now + if (const QV4::String *s = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); + roleIndex = e->setStringProperty(r, s->toQString()); + } else if (propertyValue->isNumber()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); + roleIndex = e->setDoubleProperty(r, propertyValue->asDouble()); + } else if (QV4::ArrayObject *a = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List); + ListModel *subModel = new ListModel(r.subLayout, nullptr); + + int arrayLength = a->getLength(); + for (int j=0 ; j < arrayLength ; ++j) { + o = a->get(j); + subModel->append(o); + } + + roleIndex = e->setListProperty(r, subModel); + } else if (propertyValue->isBoolean()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); + roleIndex = e->setBoolProperty(r, propertyValue->booleanValue()); + } else if (QV4::DateObject *dd = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); + QDateTime dt = dd->toQDateTime(); + roleIndex = e->setDateTimeProperty(r, dt); + } else if (QV4::FunctionObject *f = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function); + QV4::ScopedFunctionObject func(scope, f); + QJSValue jsv; + QJSValuePrivate::setValue(&jsv, v4, func); + roleIndex = e->setFunctionProperty(r, jsv); + } else if (QV4::Object *o = propertyValue->as()) { + if (QV4::QObjectWrapper *wrapper = o->as()) { + QObject *o = wrapper->object(); + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); + if (role.type == ListLayout::Role::QObject) + roleIndex = e->setQObjectProperty(role, o); + } else { + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); + if (role.type == ListLayout::Role::VariantMap) { + QV4::ScopedObject obj(scope, o); + roleIndex = e->setVariantMapProperty(role, obj); + } + } + } else if (propertyValue->isNullOrUndefined()) { + const ListLayout::Role *r = m_layout->getExistingRole(propertyName); + if (r) + e->clearProperty(*r); + } + + if (roleIndex != -1) + roles->append(roleIndex); + } + + if (ModelNodeMetaObject *mo = e->objectCache()) + mo->updateValues(*roles); +} + +void ListModel::set(int elementIndex, QV4::Object *object) +{ + if (!object) + return; + + ListElement *e = elements[elementIndex]; + + QV4::ExecutionEngine *v4 = object->engine(); + QV4::Scope scope(v4); + + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedString propertyName(scope); + QV4::ScopedValue propertyValue(scope); + QV4::ScopedObject o(scope); + while (1) { + propertyName = it.nextPropertyNameAsString(propertyValue); + if (!propertyName) + break; + + // Add the value now + if (QV4::String *s = propertyValue->stringValue()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); + if (r.type == ListLayout::Role::String) + e->setStringPropertyFast(r, s->toQString()); + } else if (propertyValue->isNumber()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); + if (r.type == ListLayout::Role::Number) { + e->setDoublePropertyFast(r, propertyValue->asDouble()); + } + } else if (QV4::ArrayObject *a = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List); + if (r.type == ListLayout::Role::List) { + ListModel *subModel = new ListModel(r.subLayout, nullptr); + + int arrayLength = a->getLength(); + for (int j=0 ; j < arrayLength ; ++j) { + o = a->get(j); + subModel->append(o); + } + + e->setListPropertyFast(r, subModel); + } + } else if (propertyValue->isBoolean()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); + if (r.type == ListLayout::Role::Bool) { + e->setBoolPropertyFast(r, propertyValue->booleanValue()); + } + } else if (QV4::DateObject *date = propertyValue->as()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); + if (r.type == ListLayout::Role::DateTime) { + QDateTime dt = date->toQDateTime();; + e->setDateTimePropertyFast(r, dt); + } + } else if (QV4::Object *o = propertyValue->as()) { + if (QV4::QObjectWrapper *wrapper = o->as()) { + QObject *o = wrapper->object(); + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); + if (r.type == ListLayout::Role::QObject) + e->setQObjectPropertyFast(r, o); + } else { + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); + if (role.type == ListLayout::Role::VariantMap) + e->setVariantMapFast(role, o); + } + } else if (propertyValue->isNullOrUndefined()) { + const ListLayout::Role *r = m_layout->getExistingRole(propertyName); + if (r) + e->clearProperty(*r); + } + } +} + +QVector> ListModel::remove(int index, int count) +{ + QVector> toDestroy; + auto layout = m_layout; + for (int i=0 ; i < count ; ++i) { + auto element = elements[index+i]; + toDestroy.append([element, layout](){ + element->destroy(layout); + delete element; + }); + } + elements.remove(index, count); + updateCacheIndices(index); + return toDestroy; +} + +void ListModel::insert(int elementIndex, QV4::Object *object) +{ + insertElement(elementIndex); + set(elementIndex, object); +} + +int ListModel::append(QV4::Object *object) +{ + int elementIndex = appendElement(); + set(elementIndex, object); + return elementIndex; +} + +int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data) +{ + int roleIndex = -1; + + if (elementIndex >= 0 && elementIndex < elements.count()) { + ListElement *e = elements[elementIndex]; + + const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data); + if (r) { + roleIndex = e->setVariantProperty(*r, data); + + ModelNodeMetaObject *cache = e->objectCache(); + + if (roleIndex != -1 && cache) + cache->updateValues(QVector(1, roleIndex)); + } + } + + return roleIndex; +} + +int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng) +{ + int roleIndex = -1; + + if (elementIndex >= 0 && elementIndex < elements.count()) { + ListElement *e = elements[elementIndex]; + const ListLayout::Role *r = m_layout->getExistingRole(key); + if (r) + roleIndex = e->setJsProperty(*r, data, eng); + } + + return roleIndex; +} + +inline char *ListElement::getPropertyMemory(const ListLayout::Role &role) +{ + ListElement *e = this; + int blockIndex = 0; + while (blockIndex < role.blockIndex) { + if (e->next == nullptr) { + e->next = new ListElement; + e->next->uid = uid; + } + e = e->next; + ++blockIndex; + } + + char *mem = &e->data[role.blockOffset]; + return mem; +} + +ModelNodeMetaObject *ListElement::objectCache() +{ + if (!m_objectCache) + return nullptr; + return ModelNodeMetaObject::get(m_objectCache); +} + +StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + StringOrTranslation *s = reinterpret_cast(mem); + return s; +} + +QObject *ListElement::getQObjectProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + QPointer *o = reinterpret_cast *>(mem); + return o->data(); +} + +QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role) +{ + QVariantMap *map = nullptr; + + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) + map = reinterpret_cast(mem); + + return map; +} + +QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role) +{ + QDateTime *dt = nullptr; + + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) + dt = reinterpret_cast(mem); + + return dt; +} + +QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role) +{ + QJSValue *f = nullptr; + + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) + f = reinterpret_cast(mem); + + return f; +} + +QPointer *ListElement::getGuardProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + + bool existingGuard = false; + for (size_t i=0 ; i < sizeof(QPointer) ; ++i) { + if (mem[i] != 0) { + existingGuard = true; + break; + } + } + + QPointer *o = nullptr; + + if (existingGuard) + o = reinterpret_cast *>(mem); + + return o; +} + +ListModel *ListElement::getListProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + ListModel **value = reinterpret_cast(mem); + return *value; +} + +QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng) +{ + char *mem = getPropertyMemory(role); + + QVariant data; + + switch (role.type) { + case ListLayout::Role::Number: + { + double *value = reinterpret_cast(mem); + data = *value; + } + break; + case ListLayout::Role::String: + { + StringOrTranslation *value = reinterpret_cast(mem); + if (value->isSet()) + data = value->toString(owner); + } + break; + case ListLayout::Role::Bool: + { + bool *value = reinterpret_cast(mem); + data = *value; + } + break; + case ListLayout::Role::List: + { + ListModel **value = reinterpret_cast(mem); + ListModel *model = *value; + + if (model) { + if (model->m_modelCache == nullptr) { + model->m_modelCache = new QQmlListModel(owner, model, eng); + QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner)); + } + + QObject *object = model->m_modelCache; + data = QVariant::fromValue(object); + } + } + break; + case ListLayout::Role::QObject: + { + QPointer *guard = reinterpret_cast *>(mem); + QObject *object = guard->data(); + if (object) + data = QVariant::fromValue(object); + } + break; + case ListLayout::Role::VariantMap: + { + if (isMemoryUsed(mem)) { + QVariantMap *map = reinterpret_cast(mem); + data = *map; + } + } + break; + case ListLayout::Role::DateTime: + { + if (isMemoryUsed(mem)) { + QDateTime *dt = reinterpret_cast(mem); + data = *dt; + } + } + break; + case ListLayout::Role::Function: + { + if (isMemoryUsed(mem)) { + QJSValue *func = reinterpret_cast(mem); + data = QVariant::fromValue(*func); + } + } + break; + default: + break; + } + + return data; +} + +int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::String) { + char *mem = getPropertyMemory(role); + StringOrTranslation *c = reinterpret_cast(mem); + bool changed; + if (!c->isSet() || c->isTranslation()) + changed = true; + else + changed = c->asString().compare(s) != 0; + c->setString(s); + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setDoubleProperty(const ListLayout::Role &role, double d) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::Number) { + char *mem = getPropertyMemory(role); + double *value = reinterpret_cast(mem); + bool changed = *value != d; + *value = d; + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setBoolProperty(const ListLayout::Role &role, bool b) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::Bool) { + char *mem = getPropertyMemory(role); + bool *value = reinterpret_cast(mem); + bool changed = *value != b; + *value = b; + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::List) { + char *mem = getPropertyMemory(role); + ListModel **value = reinterpret_cast(mem); + if (*value && *value != m) { + (*value)->destroy(); + delete *value; + } + *value = m; + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::QObject) { + char *mem = getPropertyMemory(role); + QPointer *g = reinterpret_cast *>(mem); + bool existingGuard = false; + for (size_t i=0 ; i < sizeof(QPointer) ; ++i) { + if (mem[i] != 0) { + existingGuard = true; + break; + } + } + bool changed; + if (existingGuard) { + changed = g->data() != o; + g->~QPointer(); + } else { + changed = true; + } + new (mem) QPointer(o); + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::VariantMap) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) { + QVariantMap *map = reinterpret_cast(mem); + map->~QMap(); + } + new (mem) QVariantMap(o->engine()->variantMapFromJS(o)); + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::VariantMap) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) { + QVariantMap *map = reinterpret_cast(mem); + if (m && map->isSharedWith(*m)) + return roleIndex; + map->~QMap(); + } else if (!m) { + return roleIndex; + } + if (m) + new (mem) QVariantMap(*m); + else + new (mem) QVariantMap; + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::DateTime) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) { + QDateTime *dt = reinterpret_cast(mem); + dt->~QDateTime(); + } + new (mem) QDateTime(dt); + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::Function) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed(mem)) { + QJSValue *f = reinterpret_cast(mem); + f->~QJSValue(); + } + new (mem) QJSValue(f); + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::String) { + char *mem = getPropertyMemory(role); + StringOrTranslation *s = reinterpret_cast(mem); + s->setTranslation(b); + roleIndex = role.index; + } + + return roleIndex; +} + + +void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s) +{ + char *mem = getPropertyMemory(role); + new (mem) StringOrTranslation(s); +} + +void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d) +{ + char *mem = getPropertyMemory(role); + double *value = new (mem) double; + *value = d; +} + +void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b) +{ + char *mem = getPropertyMemory(role); + bool *value = new (mem) bool; + *value = b; +} + +void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o) +{ + char *mem = getPropertyMemory(role); + new (mem) QPointer(o); +} + +void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m) +{ + char *mem = getPropertyMemory(role); + ListModel **value = new (mem) ListModel *; + *value = m; +} + +void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o) +{ + char *mem = getPropertyMemory(role); + QVariantMap *map = new (mem) QVariantMap; + *map = o->engine()->variantMapFromJS(o); +} + +void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt) +{ + char *mem = getPropertyMemory(role); + new (mem) QDateTime(dt); +} + +void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f) +{ + char *mem = getPropertyMemory(role); + new (mem) QJSValue(f); +} + +void ListElement::clearProperty(const ListLayout::Role &role) +{ + switch (role.type) { + case ListLayout::Role::String: + setStringProperty(role, QString()); + break; + case ListLayout::Role::Number: + setDoubleProperty(role, 0.0); + break; + case ListLayout::Role::Bool: + setBoolProperty(role, false); + break; + case ListLayout::Role::List: + setListProperty(role, nullptr); + break; + case ListLayout::Role::QObject: + setQObjectProperty(role, nullptr); + break; + case ListLayout::Role::DateTime: + setDateTimeProperty(role, QDateTime()); + break; + case ListLayout::Role::VariantMap: + setVariantMapProperty(role, (QVariantMap *)nullptr); + break; + case ListLayout::Role::Function: + setFunctionProperty(role, QJSValue()); + break; + default: + break; + } +} + +ListElement::ListElement() +{ + m_objectCache = nullptr; + uid = uidCounter.fetchAndAddOrdered(1); + next = nullptr; + memset(data, 0, sizeof(data)); +} + +ListElement::ListElement(int existingUid) +{ + m_objectCache = nullptr; + uid = existingUid; + next = nullptr; + memset(data, 0, sizeof(data)); +} + +ListElement::~ListElement() +{ + delete next; +} + +QVector ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout) +{ + QVector changedRoles; + for (int i=0 ; i < srcLayout->roleCount() ; ++i) { + const ListLayout::Role &srcRole = srcLayout->getExistingRole(i); + const ListLayout::Role &targetRole = targetLayout->getExistingRole(i); + + int roleIndex = -1; + switch (srcRole.type) { + case ListLayout::Role::List: + { + ListModel *srcSubModel = src->getListProperty(srcRole); + ListModel *targetSubModel = target->getListProperty(targetRole); + + if (srcSubModel) { + if (targetSubModel == nullptr) { + targetSubModel = new ListModel(targetRole.subLayout, nullptr); + target->setListPropertyFast(targetRole, targetSubModel); + } + if (ListModel::sync(srcSubModel, targetSubModel)) + roleIndex = targetRole.index; + } + } + break; + case ListLayout::Role::QObject: + { + QObject *object = src->getQObjectProperty(srcRole); + roleIndex = target->setQObjectProperty(targetRole, object); + } + break; + case ListLayout::Role::String: + case ListLayout::Role::Number: + case ListLayout::Role::Bool: + case ListLayout::Role::DateTime: + case ListLayout::Role::Function: + { + QVariant v = src->getProperty(srcRole, nullptr, nullptr); + roleIndex = target->setVariantProperty(targetRole, v); + } + break; + case ListLayout::Role::VariantMap: + { + QVariantMap *map = src->getVariantMapProperty(srcRole); + roleIndex = target->setVariantMapProperty(targetRole, map); + } + break; + default: + break; + } + if (roleIndex >= 0) + changedRoles << roleIndex; + } + + return changedRoles; +} + +void ListElement::destroy(ListLayout *layout) +{ + if (layout) { + for (int i=0 ; i < layout->roleCount() ; ++i) { + const ListLayout::Role &r = layout->getExistingRole(i); + + switch (r.type) { + case ListLayout::Role::String: + { + StringOrTranslation *string = getStringProperty(r); + if (string) + string->~StringOrTranslation(); + } + break; + case ListLayout::Role::List: + { + ListModel *model = getListProperty(r); + if (model) { + model->destroy(); + delete model; + } + } + break; + case ListLayout::Role::QObject: + { + QPointer *guard = getGuardProperty(r); + if (guard) + guard->~QPointer(); + } + break; + case ListLayout::Role::VariantMap: + { + QVariantMap *map = getVariantMapProperty(r); + if (map) + map->~QMap(); + } + break; + case ListLayout::Role::DateTime: + { + QDateTime *dt = getDateTimeProperty(r); + if (dt) + dt->~QDateTime(); + } + break; + case ListLayout::Role::Function: + { + QJSValue *f = getFunctionProperty(r); + if (f) + f->~QJSValue(); + } + break; + default: + // other types don't need explicit cleanup. + break; + } + } + + if (m_objectCache) { + m_objectCache->~QObject(); + operator delete(m_objectCache); + } + } + + if (next) + next->destroy(nullptr); + uid = -1; +} + +int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d) +{ + int roleIndex = -1; + + switch (role.type) { + case ListLayout::Role::Number: + roleIndex = setDoubleProperty(role, d.toDouble()); + break; + case ListLayout::Role::String: + if (d.userType() == qMetaTypeId()) + roleIndex = setTranslationProperty(role, d.value()); + else + roleIndex = setStringProperty(role, d.toString()); + break; + case ListLayout::Role::Bool: + roleIndex = setBoolProperty(role, d.toBool()); + break; + case ListLayout::Role::List: + roleIndex = setListProperty(role, d.value()); + break; + case ListLayout::Role::VariantMap: { + QVariantMap map = d.toMap(); + roleIndex = setVariantMapProperty(role, &map); + } + break; + case ListLayout::Role::DateTime: + roleIndex = setDateTimeProperty(role, d.toDateTime()); + break; + case ListLayout::Role::Function: + roleIndex = setFunctionProperty(role, d.value()); + break; + default: + break; + } + + return roleIndex; +} + +int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng) +{ + // Check if this key exists yet + int roleIndex = -1; + + QV4::Scope scope(eng); + + // Add the value now + if (d.isString()) { + QString qstr = d.toQString(); + roleIndex = setStringProperty(role, qstr); + } else if (d.isNumber()) { + roleIndex = setDoubleProperty(role, d.asDouble()); + } else if (d.as()) { + QV4::ScopedArrayObject a(scope, d); + if (role.type == ListLayout::Role::List) { + QV4::Scope scope(a->engine()); + QV4::ScopedObject o(scope); + + ListModel *subModel = new ListModel(role.subLayout, nullptr); + int arrayLength = a->getLength(); + for (int j=0 ; j < arrayLength ; ++j) { + o = a->get(j); + subModel->append(o); + } + roleIndex = setListProperty(role, subModel); + } else { + qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List)); + } + } else if (d.isBoolean()) { + roleIndex = setBoolProperty(role, d.booleanValue()); + } else if (d.as()) { + QV4::Scoped dd(scope, d); + QDateTime dt = dd->toQDateTime(); + roleIndex = setDateTimeProperty(role, dt); + } else if (d.as()) { + QV4::ScopedFunctionObject f(scope, d); + QJSValue jsv; + QJSValuePrivate::setValue(&jsv, eng, f); + roleIndex = setFunctionProperty(role, jsv); + } else if (d.isObject()) { + QV4::ScopedObject o(scope, d); + QV4::QObjectWrapper *wrapper = o->as(); + if (role.type == ListLayout::Role::QObject && wrapper) { + QObject *o = wrapper->object(); + roleIndex = setQObjectProperty(role, o); + } else if (role.type == ListLayout::Role::VariantMap) { + roleIndex = setVariantMapProperty(role, o); + } + } else if (d.isNullOrUndefined()) { + clearProperty(role); + } + + return roleIndex; +} + +ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex) +: QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false) +{} + +void ModelNodeMetaObject::initialize() +{ + const int roleCount = m_model->m_listModel->roleCount(); + QVector properties; + properties.reserve(roleCount); + for (int i = 0 ; i < roleCount ; ++i) { + const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i); + QByteArray name = role.name.toUtf8(); + properties << name; + } + type()->createProperties(properties); + updateValues(); + m_enabled = true; +} + +ModelNodeMetaObject::~ModelNodeMetaObject() +{ +} + +QAbstractDynamicMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object) +{ + if (!m_initialized) { + m_initialized = true; + initialize(); + } + return QQmlOpenMetaObject::toDynamicMetaObject(object); +} + +ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj) +{ + QObjectPrivate *op = QObjectPrivate::get(obj); + return static_cast(op->metaObject); +} + +void ModelNodeMetaObject::updateValues() +{ + const int roleCount = m_model->m_listModel->roleCount(); + if (!m_initialized) { + if (roleCount) { + Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int)); + for (int i = 0; i < roleCount; ++i) + changedRoles[i] = i; + emitDirectNotifies(changedRoles, roleCount); + } + return; + } + for (int i=0 ; i < roleCount ; ++i) { + const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i); + QByteArray name = role.name.toUtf8(); + const QVariant &data = m_model->data(m_elementIndex, i); + setValue(name, data, role.type == ListLayout::Role::List); + } +} + +void ModelNodeMetaObject::updateValues(const QVector &roles) +{ + if (!m_initialized) { + emitDirectNotifies(roles.constData(), roles.count()); + return; + } + int roleCount = roles.count(); + for (int i=0 ; i < roleCount ; ++i) { + int roleIndex = roles.at(i); + const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex); + QByteArray name = role.name.toUtf8(); + const QVariant &data = m_model->data(m_elementIndex, roleIndex); + setValue(name, data, role.type == ListLayout::Role::List); + } +} + +void ModelNodeMetaObject::propertyWritten(int index) +{ + if (!m_enabled) + return; + + QString propName = QString::fromUtf8(name(index)); + const QVariant value = this->value(index); + + QV4::Scope scope(m_model->engine()); + QV4::ScopedValue v(scope, scope.engine->fromVariant(value)); + + int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine); + if (roleIndex != -1) + m_model->emitItemsChanged(m_elementIndex, 1, QVector(1, roleIndex)); +} + +// Does the emission of the notifiers when we haven't created the meta-object yet +void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount) +{ + Q_ASSERT(!m_initialized); + QQmlData *ddata = QQmlData::get(object(), /*create*/false); + if (!ddata) + return; + // There's nothing to emit if we're a list model in a worker thread. + if (!qmlEngine(m_model)) + return; + for (int i = 0; i < roleCount; ++i) { + const int changedRole = changedRoles[i]; + QQmlNotifier::notify(ddata, changedRole); + } +} + +namespace QV4 { + +bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) +{ + if (!id.isString()) + return Object::virtualPut(m, id, value, receiver); + QString propName = id.toQString(); + + ModelObject *that = static_cast(m); + + ExecutionEngine *eng = that->engine(); + const int elementIndex = that->d()->elementIndex(); + int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng); + if (roleIndex != -1) + that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector(1, roleIndex)); + + ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object()); + if (mo->initialized()) + mo->emitPropertyNotification(propName.toUtf8()); + return true; +} + +ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) +{ + if (!id.isString()) + return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); + + const ModelObject *that = static_cast(m); + Scope scope(that); + ScopedString name(scope, id.asStringOrSymbol()); + const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name); + if (!role) + return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); + if (hasProperty) + *hasProperty = true; + + if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) { + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine); + if (ep && ep->propertyCapture) + ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false); + } + + const int elementIndex = that->d()->elementIndex(); + QVariant value = that->d()->m_model->data(elementIndex, role->index); + return that->engine()->fromVariant(value); +} + +ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + lookup->getter = Lookup::getterFallback; + return lookup->getter(lookup, engine, *object); +} + +struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator +{ + int roleNameIndex = 0; + ~ModelObjectOwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) +{ + const ModelObject *that = static_cast(o); + + ExecutionEngine *v4 = that->engine(); + if (roleNameIndex < that->listModel()->roleCount()) { + Scope scope(that->engine()); + const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex); + ++roleNameIndex; + ScopedString roleName(scope, v4->newString(role.name)); + if (attrs) + *attrs = QV4::Attr_Data; + if (pd) { + QVariant value = that->d()->m_model->data(that->d()->elementIndex(), role.index); + pd->value = v4->fromVariant(value); + } + return roleName->toPropertyKey(); + } + + // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add + // unnecessary entries that relate to the roles used. These just create extra work + // later on as they will just be ignored. + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target) +{ + *target = *m; + return new ModelObjectOwnPropertyKeyIterator; +} + +DEFINE_OBJECT_VTABLE(ModelObject); + +} // namespace QV4 + +DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this)) +{ + setNodeUpdatesEnabled(true); +} + +DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner) +{ + DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1)); + QVector roles; + object->updateValues(obj, roles); + return object; +} + +QVector DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target) +{ + QVector changedRoles; + for (int i = 0; i < src->m_meta->count(); ++i) { + const QByteArray &name = src->m_meta->name(i); + QVariant value = src->m_meta->value(i); + + QQmlListModel *srcModel = qobject_cast(value.value()); + QQmlListModel *targetModel = qobject_cast(target->m_meta->value(i).value()); + + bool modelHasChanges = false; + if (srcModel) { + if (targetModel == nullptr) + targetModel = QQmlListModel::createWithOwner(target->m_owner); + + modelHasChanges = QQmlListModel::sync(srcModel, targetModel); + + QObject *targetModelObject = targetModel; + value = QVariant::fromValue(targetModelObject); + } else if (targetModel) { + delete targetModel; + } + + if (target->setValue(name, value) || modelHasChanges) + changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name)); + } + return changedRoles; +} + +void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector &roles) +{ + for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) { + const QString &key = it.key(); + + int roleIndex = m_owner->m_roles.indexOf(key); + if (roleIndex == -1) { + roleIndex = m_owner->m_roles.count(); + m_owner->m_roles.append(key); + } + + QVariant value = it.value(); + + // A JS array/object is translated into a (hierarchical) QQmlListModel, + // so translate to a variant map/list first with toVariant(). + if (value.userType() == qMetaTypeId()) + value = value.value().toVariant(); + + if (value.type() == QVariant::List) { + QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner); + + QVariantList subArray = value.toList(); + QVariantList::const_iterator subIt = subArray.cbegin(); + QVariantList::const_iterator subEnd = subArray.cend(); + while (subIt != subEnd) { + const QVariantMap &subObject = subIt->toMap(); + subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); + ++subIt; + } + + QObject *subModelObject = subModel; + value = QVariant::fromValue(subModelObject); + } + + const QByteArray &keyUtf8 = key.toUtf8(); + + QQmlListModel *existingModel = qobject_cast(m_meta->value(keyUtf8).value()); + delete existingModel; + + if (m_meta->setValue(keyUtf8, value)) + roles << roleIndex; + } +} + +DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object) + : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object) +{ +} + +DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject() +{ + for (int i=0 ; i < count() ; ++i) { + QQmlListModel *subModel = qobject_cast(value(i).value()); + delete subModel; + } +} + +void DynamicRoleModelNodeMetaObject::propertyWrite(int index) +{ + if (!m_enabled) + return; + + QVariant v = value(index); + QQmlListModel *model = qobject_cast(v.value()); + delete model; +} + +void DynamicRoleModelNodeMetaObject::propertyWritten(int index) +{ + if (!m_enabled) + return; + + QQmlListModel *parentModel = m_owner->m_owner; + + QVariant v = value(index); + + // A JS array/object is translated into a (hierarchical) QQmlListModel, + // so translate to a variant map/list first with toVariant(). + if (v.userType() == qMetaTypeId()) + v= v.value().toVariant(); + + if (v.type() == QVariant::List) { + QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel); + + QVariantList subArray = v.toList(); + QVariantList::const_iterator subIt = subArray.cbegin(); + QVariantList::const_iterator subEnd = subArray.cend(); + while (subIt != subEnd) { + const QVariantMap &subObject = subIt->toMap(); + subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); + ++subIt; + } + + QObject *subModelObject = subModel; + v = QVariant::fromValue(subModelObject); + + setValue(index, v); + } + + int elementIndex = parentModel->m_modelObjects.indexOf(m_owner); + if (elementIndex != -1) { + int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData())); + if (roleIndex != -1) + parentModel->emitItemsChanged(elementIndex, 1, QVector(1, roleIndex)); + } +} + +/*! + \qmltype ListModel + \instantiates QQmlListModel + \inqmlmodule QtQml.Models + \ingroup qtquick-models + \brief Defines a free-form list data source. + + The ListModel is a simple container of ListElement definitions, each + containing data roles. The contents can be defined dynamically, or + explicitly in QML. + + The number of elements in the model can be obtained from its \l count property. + A number of familiar methods are also provided to manipulate the contents of the + model, including append(), insert(), move(), remove() and set(). These methods + accept dictionaries as their arguments; these are translated to ListElement objects + by the model. + + Elements can be manipulated via the model using the setProperty() method, which + allows the roles of the specified element to be set and changed. + + \section1 Example Usage + + The following example shows a ListModel containing three elements, with the roles + "name" and "cost". + + \div {class="float-right"} + \inlineimage listmodel.png + \enddiv + + \snippet qml/listmodel/listmodel.qml 0 + + Roles (properties) in each element must begin with a lower-case letter and + should be common to all elements in a model. The ListElement documentation + provides more guidelines for how elements should be defined. + + Since the example model contains an \c id property, it can be referenced + by views, such as the ListView in this example: + + \snippet qml/listmodel/listmodel-simple.qml 0 + \dots 8 + \snippet qml/listmodel/listmodel-simple.qml 1 + + It is possible for roles to contain list data. In the following example we + create a list of fruit attributes: + + \snippet qml/listmodel/listmodel-nested.qml model + + The delegate displays all the fruit attributes: + + \div {class="float-right"} + \inlineimage listmodel-nested.png + \enddiv + + \snippet qml/listmodel/listmodel-nested.qml delegate + + \section1 Modifying List Models + + The content of a ListModel may be created and modified using the clear(), + append(), set(), insert() and setProperty() methods. For example: + + \snippet qml/listmodel/listmodel-modify.qml delegate + + Note that when creating content dynamically the set of available properties + cannot be changed once set. Whatever properties are first added to the model + are the only permitted properties in the model. + + \section1 Using Threaded List Models with WorkerScript + + ListModel can be used together with WorkerScript access a list model + from multiple threads. This is useful if list modifications are + synchronous and take some time: the list operations can be moved to a + different thread to avoid blocking of the main GUI thread. + + Here is an example that uses WorkerScript to periodically append the + current time to a list model: + + \snippet ../quick/threading/threadedlistmodel/timedisplay.qml 0 + + The included file, \tt dataloader.mjs, looks like this: + + \snippet ../quick/threading/threadedlistmodel/dataloader.mjs 0 + + The timer in the main example sends messages to the worker script by calling + \l WorkerScript::sendMessage(). When this message is received, + \c WorkerScript.onMessage() is invoked in \c dataloader.mjs, + which appends the current time to the list model. + + Note the call to sync() from the external thread. + You must call sync() or else the changes made to the list from that + thread will not be reflected in the list model in the main thread. + + \sa {qml-data-models}{Data Models}, {Qt Quick Examples - Threading}, {Qt QML} +*/ + +QQmlListModel::QQmlListModel(QObject *parent) +: QAbstractListModel(parent) +{ + m_mainThread = true; + m_primary = true; + m_agent = nullptr; + m_dynamicRoles = false; + + m_layout = new ListLayout; + m_listModel = new ListModel(m_layout, this); + + m_engine = nullptr; +} + +QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent) +: QAbstractListModel(parent) +{ + m_mainThread = owner->m_mainThread; + m_primary = false; + m_agent = owner->m_agent; + + Q_ASSERT(owner->m_dynamicRoles == false); + m_dynamicRoles = false; + m_layout = nullptr; + m_listModel = data; + + m_engine = engine; + m_compilationUnit = owner->m_compilationUnit; +} + +QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent) +: QAbstractListModel(agent) +{ + m_mainThread = false; + m_primary = true; + m_agent = agent; + m_dynamicRoles = orig->m_dynamicRoles; + + m_layout = new ListLayout(orig->m_layout); + m_listModel = new ListModel(m_layout, this); + + if (m_dynamicRoles) + sync(orig, this); + else + ListModel::sync(orig->m_listModel, m_listModel); + + m_engine = nullptr; + m_compilationUnit = orig->m_compilationUnit; +} + +QQmlListModel::~QQmlListModel() +{ + qDeleteAll(m_modelObjects); + + if (m_primary) { + m_listModel->destroy(); + delete m_listModel; + + if (m_mainThread && m_agent) { + m_agent->modelDestroyed(); + m_agent->release(); + } + } + + m_listModel = nullptr; + + delete m_layout; + m_layout = nullptr; +} + +QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner) +{ + QQmlListModel *model = new QQmlListModel; + + model->m_mainThread = newOwner->m_mainThread; + model->m_engine = newOwner->m_engine; + model->m_agent = newOwner->m_agent; + model->m_dynamicRoles = newOwner->m_dynamicRoles; + + if (model->m_mainThread && model->m_agent) + model->m_agent->addref(); + + QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner)); + + return model; +} + +QV4::ExecutionEngine *QQmlListModel::engine() const +{ + if (m_engine == nullptr) { + m_engine = qmlEngine(this)->handle(); + } + + return m_engine; +} + +bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target) +{ + Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles); + + bool hasChanges = false; + + target->m_roles = src->m_roles; + + // Build hash of elements <-> uid for each of the lists + QHash elementHash; + for (int i = 0 ; i < target->m_modelObjects.count(); ++i) { + DynamicRoleModelNode *e = target->m_modelObjects.at(i); + int uid = e->getUid(); + ElementSync sync; + sync.target = e; + sync.targetIndex = i; + elementHash.insert(uid, sync); + } + for (int i = 0 ; i < src->m_modelObjects.count(); ++i) { + DynamicRoleModelNode *e = src->m_modelObjects.at(i); + int uid = e->getUid(); + + QHash::iterator it = elementHash.find(uid); + if (it == elementHash.end()) { + ElementSync sync; + sync.src = e; + sync.srcIndex = i; + elementHash.insert(uid, sync); + } else { + ElementSync &sync = it.value(); + sync.src = e; + sync.srcIndex = i; + } + } + + // Get list of elements that are in the target but no longer in the source. These get deleted first. + int rowsRemoved = 0; + for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) { + DynamicRoleModelNode *element = target->m_modelObjects.at(i); + ElementSync &s = elementHash.find(element->getUid()).value(); + Q_ASSERT(s.targetIndex >= 0); + // need to update the targetIndex, to keep it correct after removals + s.targetIndex -= rowsRemoved; + if (s.src == nullptr) { + Q_ASSERT(s.targetIndex == i); + hasChanges = true; + target->beginRemoveRows(QModelIndex(), i, i); + target->m_modelObjects.remove(i, 1); + target->endRemoveRows(); + delete s.target; + ++rowsRemoved; + --i; + continue; + } + } + + // Clear the target list, and append in correct order from the source + target->m_modelObjects.clear(); + for (int i = 0 ; i < src->m_modelObjects.count() ; ++i) { + DynamicRoleModelNode *element = src->m_modelObjects.at(i); + ElementSync &s = elementHash.find(element->getUid()).value(); + Q_ASSERT(s.srcIndex >= 0); + DynamicRoleModelNode *targetElement = s.target; + if (targetElement == nullptr) { + targetElement = new DynamicRoleModelNode(target, element->getUid()); + } + s.changedRoles = DynamicRoleModelNode::sync(element, targetElement); + target->m_modelObjects.append(targetElement); + } + + // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts, + // so the model indices can't be out of bounds + // + // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent + // model indices are updated correctly + int rowsInserted = 0; + for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) { + DynamicRoleModelNode *element = target->m_modelObjects.at(i); + ElementSync &s = elementHash.find(element->getUid()).value(); + Q_ASSERT(s.srcIndex >= 0); + s.srcIndex += rowsInserted; + if (s.srcIndex != s.targetIndex) { + if (s.targetIndex == -1) { + target->beginInsertRows(QModelIndex(), i, i); + target->endInsertRows(); + } else { + target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex); + target->endMoveRows(); + } + hasChanges = true; + ++rowsInserted; + } + if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) { + QModelIndex idx = target->createIndex(i, 0); + emit target->dataChanged(idx, idx, s.changedRoles); + hasChanges = true; + } + } + return hasChanges; +} + +void QQmlListModel::emitItemsChanged(int index, int count, const QVector &roles) +{ + if (count <= 0) + return; + + if (m_mainThread) + emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);; +} + +void QQmlListModel::emitItemsAboutToBeInserted(int index, int count) +{ + Q_ASSERT(index >= 0 && count >= 0); + if (m_mainThread) + beginInsertRows(QModelIndex(), index, index + count - 1); +} + +void QQmlListModel::emitItemsInserted() +{ + if (m_mainThread) { + endInsertRows(); + emit countChanged(); + } +} + +QQmlListModelWorkerAgent *QQmlListModel::agent() +{ + if (m_agent) + return m_agent; + + m_agent = new QQmlListModelWorkerAgent(this); + return m_agent; +} + +QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const +{ + return row >= 0 && row < count() && column == 0 && !parent.isValid() + ? createIndex(row, column) + : QModelIndex(); +} + +int QQmlListModel::rowCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? count() : 0; +} + +QVariant QQmlListModel::data(const QModelIndex &index, int role) const +{ + return data(index.row(), role); +} + +bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int row = index.row(); + if (row >= count() || row < 0) + return false; + + if (m_dynamicRoles) { + const QByteArray property = m_roles.at(role).toUtf8(); + if (m_modelObjects[row]->setValue(property, value)) { + emitItemsChanged(row, 1, QVector(1, role)); + return true; + } + } else { + const ListLayout::Role &r = m_listModel->getExistingRole(role); + const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value); + if (roleIndex != -1) { + emitItemsChanged(row, 1, QVector(1, role)); + return true; + } + } + + return false; +} + +QVariant QQmlListModel::data(int index, int role) const +{ + QVariant v; + + if (index >= count() || index < 0) + return v; + + if (m_dynamicRoles) + v = m_modelObjects[index]->getValue(m_roles[role]); + else + v = m_listModel->getProperty(index, role, this, engine()); + + return v; +} + +QHash QQmlListModel::roleNames() const +{ + QHash roleNames; + + if (m_dynamicRoles) { + for (int i = 0 ; i < m_roles.count() ; ++i) + roleNames.insert(i, m_roles.at(i).toUtf8()); + } else { + for (int i = 0 ; i < m_listModel->roleCount() ; ++i) { + const ListLayout::Role &r = m_listModel->getExistingRole(i); + roleNames.insert(i, r.name.toUtf8()); + } + } + + return roleNames; +} + +/*! + \qmlproperty bool ListModel::dynamicRoles + + By default, the type of a role is fixed the first time + the role is used. For example, if you create a role called + "data" and assign a number to it, you can no longer assign + a string to the "data" role. However, when the dynamicRoles + property is enabled, the type of a given role is not fixed + and can be different between elements. + + The dynamicRoles property must be set before any data is + added to the ListModel, and must be set from the main + thread. + + A ListModel that has data statically defined (via the + ListElement QML syntax) cannot have the dynamicRoles + property enabled. + + There is a significant performance cost to using a + ListModel with dynamic roles enabled. The cost varies + from platform to platform but is typically somewhere + between 4-6x slower than using static role types. + + Due to the performance cost of using dynamic roles, + they are disabled by default. +*/ +void QQmlListModel::setDynamicRoles(bool enableDynamicRoles) +{ + if (m_mainThread && m_agent == nullptr) { + if (enableDynamicRoles) { + if (m_layout->roleCount()) + qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty"); + else + m_dynamicRoles = true; + } else { + if (m_roles.count()) { + qmlWarning(this) << tr("unable to enable static roles as this model is not empty"); + } else { + m_dynamicRoles = false; + } + } + } else { + qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created"); + } +} + +/*! + \qmlproperty int ListModel::count + The number of data entries in the model. +*/ +int QQmlListModel::count() const +{ + return m_dynamicRoles ? m_modelObjects.count() : m_listModel->elementCount(); +} + +/*! + \qmlmethod ListModel::clear() + + Deletes all content from the model. + + \sa append(), remove() +*/ +void QQmlListModel::clear() +{ + removeElements(0, count()); +} + +/*! + \qmlmethod ListModel::remove(int index, int count = 1) + + Deletes the content at \a index from the model. + + \sa clear() +*/ +void QQmlListModel::remove(QQmlV4Function *args) +{ + int argLength = args->length(); + + if (argLength == 1 || argLength == 2) { + QV4::Scope scope(args->v4engine()); + int index = QV4::ScopedValue(scope, (*args)[0])->toInt32(); + int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1); + + if (index < 0 || index+removeCount > count() || removeCount <= 0) { + qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count()); + return; + } + + removeElements(index, removeCount); + } else { + qmlWarning(this) << tr("remove: incorrect number of arguments"); + } +} + +void QQmlListModel::removeElements(int index, int removeCount) +{ + Q_ASSERT(index >= 0 && removeCount >= 0); + + if (!removeCount) + return; + + if (m_mainThread) + beginRemoveRows(QModelIndex(), index, index + removeCount - 1); + + QVector> toDestroy; + if (m_dynamicRoles) { + for (int i=0 ; i < removeCount ; ++i) { + auto modelObject = m_modelObjects[index+i]; + toDestroy.append([modelObject](){ + delete modelObject; + }); + } + m_modelObjects.remove(index, removeCount); + } else { + toDestroy = m_listModel->remove(index, removeCount); + } + + if (m_mainThread) { + endRemoveRows(); + emit countChanged(); + } + for (const auto &destroyer : toDestroy) + destroyer(); +} + +/*! + \qmlmethod ListModel::insert(int index, jsobject dict) + + Adds a new item to the list model at position \a index, with the + values in \a dict. + + \code + fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"}) + \endcode + + The \a index must be to an existing item in the list, or one past + the end of the list (equivalent to append). + + \sa set(), append() +*/ + +void QQmlListModel::insert(QQmlV4Function *args) +{ + if (args->length() == 2) { + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue arg0(scope, (*args)[0]); + int index = arg0->toInt32(); + + if (index < 0 || index > count()) { + qmlWarning(this) << tr("insert: index %1 out of range").arg(index); + return; + } + + QV4::ScopedObject argObject(scope, (*args)[1]); + QV4::ScopedArrayObject objectArray(scope, (*args)[1]); + if (objectArray) { + QV4::ScopedObject argObject(scope); + + int objectArrayLength = objectArray->getLength(); + emitItemsAboutToBeInserted(index, objectArrayLength); + for (int i=0 ; i < objectArrayLength ; ++i) { + argObject = objectArray->get(i); + + if (m_dynamicRoles) { + m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); + } else { + m_listModel->insert(index+i, argObject); + } + } + emitItemsInserted(); + } else if (argObject) { + emitItemsAboutToBeInserted(index, 1); + + if (m_dynamicRoles) { + m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); + } else { + m_listModel->insert(index, argObject); + } + + emitItemsInserted(); + } else { + qmlWarning(this) << tr("insert: value is not an object"); + } + } else { + qmlWarning(this) << tr("insert: value is not an object"); + } +} + +/*! + \qmlmethod ListModel::move(int from, int to, int n) + + Moves \a n items \a from one position \a to another. + + The from and to ranges must exist; for example, to move the first 3 items + to the end of the list: + + \code + fruitModel.move(0, fruitModel.count - 3, 3) + \endcode + + \sa append() +*/ +void QQmlListModel::move(int from, int to, int n) +{ + if (n == 0 || from == to) + return; + if (!canMove(from, to, n)) { + qmlWarning(this) << tr("move: out of range"); + return; + } + + if (m_mainThread) + beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to); + + if (m_dynamicRoles) { + + int realFrom = from; + int realTo = to; + int realN = n; + + if (from > to) { + // Only move forwards - flip if backwards moving + int tfrom = from; + int tto = to; + realFrom = tto; + realTo = tto+n; + realN = tfrom-tto; + } + + QPODVector store; + for (int i=0 ; i < (realTo-realFrom) ; ++i) + store.append(m_modelObjects[realFrom+realN+i]); + for (int i=0 ; i < realN ; ++i) + store.append(m_modelObjects[realFrom+i]); + for (int i=0 ; i < store.count() ; ++i) + m_modelObjects[realFrom+i] = store[i]; + + } else { + m_listModel->move(from, to, n); + } + + if (m_mainThread) + endMoveRows(); +} + +/*! + \qmlmethod ListModel::append(jsobject dict) + + Adds a new item to the end of the list model, with the + values in \a dict. + + \code + fruitModel.append({"cost": 5.95, "name":"Pizza"}) + \endcode + + \sa set(), remove() +*/ +void QQmlListModel::append(QQmlV4Function *args) +{ + if (args->length() == 1) { + QV4::Scope scope(args->v4engine()); + QV4::ScopedObject argObject(scope, (*args)[0]); + QV4::ScopedArrayObject objectArray(scope, (*args)[0]); + + if (objectArray) { + QV4::ScopedObject argObject(scope); + + int objectArrayLength = objectArray->getLength(); + if (objectArrayLength > 0) { + int index = count(); + emitItemsAboutToBeInserted(index, objectArrayLength); + + for (int i=0 ; i < objectArrayLength ; ++i) { + argObject = objectArray->get(i); + + if (m_dynamicRoles) { + m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); + } else { + m_listModel->append(argObject); + } + } + + emitItemsInserted(); + } + } else if (argObject) { + int index; + + if (m_dynamicRoles) { + index = m_modelObjects.count(); + emitItemsAboutToBeInserted(index, 1); + m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this)); + } else { + index = m_listModel->elementCount(); + emitItemsAboutToBeInserted(index, 1); + m_listModel->append(argObject); + } + + emitItemsInserted(); + } else { + qmlWarning(this) << tr("append: value is not an object"); + } + } else { + qmlWarning(this) << tr("append: value is not an object"); + } +} + +/*! + \qmlmethod object ListModel::get(int index) + + Returns the item at \a index in the list model. This allows the item + data to be accessed or modified from JavaScript: + + \code + Component.onCompleted: { + fruitModel.append({"cost": 5.95, "name":"Jackfruit"}); + console.log(fruitModel.get(0).cost); + fruitModel.get(0).cost = 10.95; + } + \endcode + + The \a index must be an element in the list. + + Note that properties of the returned object that are themselves objects + will also be models, and this get() method is used to access elements: + + \code + fruitModel.append(..., "attributes": + [{"name":"spikes","value":"7mm"}, + {"name":"color","value":"green"}]); + fruitModel.get(0).attributes.get(1).value; // == "green" + \endcode + + \warning The returned object is not guaranteed to remain valid. It + should not be used in \l{Property Binding}{property bindings}. + + \sa append() +*/ +QJSValue QQmlListModel::get(int index) const +{ + QV4::Scope scope(engine()); + QV4::ScopedValue result(scope, QV4::Value::undefinedValue()); + + if (index >= 0 && index < count()) { + + if (m_dynamicRoles) { + DynamicRoleModelNode *object = m_modelObjects[index]; + result = QV4::QObjectWrapper::wrap(scope.engine, object); + } else { + QObject *object = m_listModel->getOrCreateModelObject(const_cast(this), index); + QQmlData *ddata = QQmlData::get(object); + if (ddata->jsWrapper.isNullOrUndefined()) { + result = scope.engine->memoryManager->allocate(object, const_cast(this)); + // Keep track of the QObjectWrapper in persistent value storage + ddata->jsWrapper.set(scope.engine, result); + } else { + result = ddata->jsWrapper.value(); + } + } + } + + return QJSValue(engine(), result->asReturnedValue()); +} + +/*! + \qmlmethod ListModel::set(int index, jsobject dict) + + Changes the item at \a index in the list model with the + values in \a dict. Properties not appearing in \a dict + are left unchanged. + + \code + fruitModel.set(3, {"cost": 5.95, "name":"Pizza"}) + \endcode + + If \a index is equal to count() then a new item is appended to the + list. Otherwise, \a index must be an element in the list. + + \sa append() +*/ +void QQmlListModel::set(int index, const QJSValue &value) +{ + QV4::Scope scope(engine()); + QV4::ScopedObject object(scope, QJSValuePrivate::getValue(&value)); + + if (!object) { + qmlWarning(this) << tr("set: value is not an object"); + return; + } + if (index > count() || index < 0) { + qmlWarning(this) << tr("set: index %1 out of range").arg(index); + return; + } + + + if (index == count()) { + emitItemsAboutToBeInserted(index, 1); + + if (m_dynamicRoles) { + m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this)); + } else { + m_listModel->insert(index, object); + } + + emitItemsInserted(); + } else { + + QVector roles; + + if (m_dynamicRoles) { + m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles); + } else { + m_listModel->set(index, object, &roles); + } + + if (roles.count()) + emitItemsChanged(index, 1, roles); + } +} + +/*! + \qmlmethod ListModel::setProperty(int index, string property, variant value) + + Changes the \a property of the item at \a index in the list model to \a value. + + \code + fruitModel.setProperty(3, "cost", 5.95) + \endcode + + The \a index must be an element in the list. + + \sa append() +*/ +void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value) +{ + if (count() == 0 || index >= count() || index < 0) { + qmlWarning(this) << tr("set: index %1 out of range").arg(index); + return; + } + + if (m_dynamicRoles) { + int roleIndex = m_roles.indexOf(property); + if (roleIndex == -1) { + roleIndex = m_roles.count(); + m_roles.append(property); + } + if (m_modelObjects[index]->setValue(property.toUtf8(), value)) + emitItemsChanged(index, 1, QVector(1, roleIndex)); + } else { + int roleIndex = m_listModel->setOrCreateProperty(index, property, value); + if (roleIndex != -1) + emitItemsChanged(index, 1, QVector(1, roleIndex)); + } +} + +/*! + \qmlmethod ListModel::sync() + + Writes any unsaved changes to the list model after it has been modified + from a worker script. +*/ +void QQmlListModel::sync() +{ + // This is just a dummy method to make it look like sync() exists in + // ListModel (and not just QQmlListModelWorkerAgent) and to let + // us document sync(). + qmlWarning(this) << "List sync() can only be called from a WorkerScript"; +} + +bool QQmlListModelParser::verifyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding) +{ + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + const quint32 targetObjectIndex = binding->value.objectIndex; + const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex); + QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex); + if (objName != listElementTypeName) { + const QMetaObject *mo = resolveType(objName); + if (mo != &QQmlListElement::staticMetaObject) { + error(target, QQmlListModel::tr("ListElement: cannot contain nested elements")); + return false; + } + listElementTypeName = objName; // cache right name for next time + } + + if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) { + error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property")); + return false; + } + + const QV4::CompiledData::Binding *binding = target->bindingTable(); + for (quint32 i = 0; i < target->nBindings; ++i, ++binding) { + QString propName = compilationUnit->stringAt(binding->propertyNameIndex); + if (propName.isEmpty()) { + error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements")); + return false; + } + if (!verifyProperty(compilationUnit, binding)) + return false; + } + } else if (binding->type == QV4::CompiledData::Binding::Type_Script) { + QString scriptStr = binding->valueAsScriptString(compilationUnit.data()); + if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) { + QByteArray script = scriptStr.toUtf8(); + bool ok; + evaluateEnum(script, &ok); + if (!ok) { + error(binding, QQmlListModel::tr("ListElement: cannot use script for property value")); + return false; + } + } + } + + return true; +} + +bool QQmlListModelParser::applyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex) +{ + const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex); + + bool roleSet = false; + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + const quint32 targetObjectIndex = binding->value.objectIndex; + const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex); + + ListModel *subModel = nullptr; + if (outterElementIndex == -1) { + subModel = model; + } else { + const ListLayout::Role &role = model->getOrCreateListRole(elementName); + if (role.type == ListLayout::Role::List) { + subModel = model->getListProperty(outterElementIndex, role); + if (subModel == nullptr) { + subModel = new ListModel(role.subLayout, nullptr); + QVariant vModel = QVariant::fromValue(subModel); + model->setOrCreateProperty(outterElementIndex, elementName, vModel); + } + } + } + + int elementIndex = subModel ? subModel->appendElement() : -1; + + const QV4::CompiledData::Binding *subBinding = target->bindingTable(); + for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) { + roleSet |= applyProperty(compilationUnit, subBinding, subModel, elementIndex); + } + + } else { + QVariant value; + + if (binding->isTranslationBinding()) { + value = QVariant::fromValue(binding); + } else if (binding->evaluatesToString()) { + value = binding->valueAsString(compilationUnit.data()); + } else if (binding->type == QV4::CompiledData::Binding::Type_Number) { + value = binding->valueAsNumber(compilationUnit->constants); + } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) { + value = binding->valueAsBoolean(); + } else if (binding->type == QV4::CompiledData::Binding::Type_Null) { + value = QVariant::fromValue(nullptr); + } else if (binding->type == QV4::CompiledData::Binding::Type_Script) { + QString scriptStr = binding->valueAsScriptString(compilationUnit.data()); + if (definesEmptyList(scriptStr)) { + const ListLayout::Role &role = model->getOrCreateListRole(elementName); + ListModel *emptyModel = new ListModel(role.subLayout, nullptr); + value = QVariant::fromValue(emptyModel); + } else if (binding->isFunctionExpression()) { + QQmlBinding::Identifier id = binding->value.compiledScriptIndex; + Q_ASSERT(id != QQmlBinding::Invalid); + + auto v4 = compilationUnit->engine; + QV4::Scope scope(v4); + // for now we do not provide a context object; data from the ListElement must be passed to the function + QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr)); + QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id])); + + QV4::ReturnedValue result = function->call(v4->globalObject, nullptr, 0); + + QJSValue v; + QJSValuePrivate::setValue(&v, v4, result); + value.setValue(v); + } else { + QByteArray script = scriptStr.toUtf8(); + bool ok; + value = evaluateEnum(script, &ok); + } + } else { + Q_UNREACHABLE(); + } + + model->setOrCreateProperty(outterElementIndex, elementName, value); + roleSet = true; + } + return roleSet; +} + +void QQmlListModelParser::verifyBindings(const QQmlRefPointer &compilationUnit, const QList &bindings) +{ + listElementTypeName = QString(); // unknown + + for (const QV4::CompiledData::Binding *binding : bindings) { + QString propName = compilationUnit->stringAt(binding->propertyNameIndex); + if (!propName.isEmpty()) { // isn't default property + error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName)); + return; + } + if (!verifyProperty(compilationUnit, binding)) + return; + } +} + +void QQmlListModelParser::applyBindings(QObject *obj, const QQmlRefPointer &compilationUnit, const QList &bindings) +{ + QQmlListModel *rv = static_cast(obj); + + rv->m_engine = qmlEngine(rv)->handle(); + rv->m_compilationUnit = compilationUnit; + + bool setRoles = false; + + for (const QV4::CompiledData::Binding *binding : bindings) { + if (binding->type != QV4::CompiledData::Binding::Type_Object) + continue; + setRoles |= applyProperty(compilationUnit, binding, rv->m_listModel, /*outter element index*/-1); + } + + if (setRoles == false) + qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set."; +} + +bool QQmlListModelParser::definesEmptyList(const QString &s) +{ + if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) { + for (int i=1; i +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_REQUIRE_CONFIG(qml_list_model); + +QT_BEGIN_NAMESPACE + + +class QQmlListModelWorkerAgent; +class ListModel; +class ListLayout; + +namespace QV4 { +struct ModelObject; +} + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlListModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles) + Q_PROPERTY(QObject *agent READ agent CONSTANT REVISION(14)) + +public: + QQmlListModel(QObject *parent=nullptr); + ~QQmlListModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QHash roleNames() const override; + + QVariant data(int index, int role) const; + int count() const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE void remove(QQmlV4Function *args); + Q_INVOKABLE void append(QQmlV4Function *args); + Q_INVOKABLE void insert(QQmlV4Function *args); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); + Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); + Q_INVOKABLE void move(int from, int to, int count); + Q_INVOKABLE void sync(); + + QQmlListModelWorkerAgent *agent(); + + bool dynamicRoles() const { return m_dynamicRoles; } + void setDynamicRoles(bool enableDynamicRoles); + +Q_SIGNALS: + void countChanged(); + +private: + friend class QQmlListModelParser; + friend class QQmlListModelWorkerAgent; + friend class ModelObject; + friend struct QV4::ModelObject; + friend class ModelNodeMetaObject; + friend class ListModel; + friend class ListElement; + friend class DynamicRoleModelNode; + friend class DynamicRoleModelNodeMetaObject; + friend struct StringOrTranslation; + + // Constructs a flat list model for a worker agent + QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent); + QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent=nullptr); + + QV4::ExecutionEngine *engine() const; + + inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); } + + mutable QQmlListModelWorkerAgent *m_agent; + mutable QV4::ExecutionEngine *m_engine; + QQmlRefPointer m_compilationUnit; + bool m_mainThread; + bool m_primary; + + bool m_dynamicRoles; + + ListLayout *m_layout; + ListModel *m_listModel; + + QVector m_modelObjects; + QVector m_roles; + + struct ElementSync + { + DynamicRoleModelNode *src = nullptr; + DynamicRoleModelNode *target = nullptr; + int srcIndex = -1; + int targetIndex = -1; + QVector changedRoles; + }; + + static bool sync(QQmlListModel *src, QQmlListModel *target); + static QQmlListModel *createWithOwner(QQmlListModel *newOwner); + + void emitItemsChanged(int index, int count, const QVector &roles); + void emitItemsAboutToBeInserted(int index, int count); + void emitItemsInserted(); + + void removeElements(int index, int removeCount); +}; + +// ### FIXME +class QQmlListElement : public QObject +{ +Q_OBJECT +}; + +class QQmlListModelParser : public QQmlCustomParser +{ +public: + enum PropertyType { + Invalid, + Boolean, + Number, + String, + Script + }; + + + QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {} + + void verifyBindings(const QQmlRefPointer &compilationUnit, const QList &bindings) override; + void applyBindings(QObject *obj, const QQmlRefPointer &compilationUnit, const QList &bindings) override; + +private: + bool verifyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding); + // returns true if a role was set + bool applyProperty(const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex); + + static bool definesEmptyList(const QString &); + + QString listElementTypeName; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlListModel) +QML_DECLARE_TYPE(QQmlListElement) + +#endif // QQMLLISTMODEL_H diff --git a/src/qmlmodels/qqmllistmodel_p_p.h b/src/qmlmodels/qqmllistmodel_p_p.h new file mode 100644 index 0000000000..a0d0e9ad89 --- /dev/null +++ b/src/qmlmodels/qqmllistmodel_p_p.h @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLLISTMODEL_P_P_H +#define QQMLLISTMODEL_P_P_H + +// +// 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. +// + +#include "qqmllistmodel_p.h" +#include +#include +#include +#include +#include + +QT_REQUIRE_CONFIG(qml_list_model); + +QT_BEGIN_NAMESPACE + + +class DynamicRoleModelNode; + +class DynamicRoleModelNodeMetaObject : public QQmlOpenMetaObject +{ +public: + DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object); + ~DynamicRoleModelNodeMetaObject(); + + bool m_enabled; + +protected: + void propertyWrite(int index) override; + void propertyWritten(int index) override; + +private: + DynamicRoleModelNode *m_owner; +}; + +class DynamicRoleModelNode : public QObject +{ + Q_OBJECT +public: + DynamicRoleModelNode(QQmlListModel *owner, int uid); + + static DynamicRoleModelNode *create(const QVariantMap &obj, QQmlListModel *owner); + + void updateValues(const QVariantMap &object, QVector &roles); + + QVariant getValue(const QString &name) const + { + return m_meta->value(name.toUtf8()); + } + + bool setValue(const QByteArray &name, const QVariant &val) + { + return m_meta->setValue(name, val); + } + + void setNodeUpdatesEnabled(bool enable) + { + m_meta->m_enabled = enable; + } + + int getUid() const + { + return m_uid; + } + + static QVector sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target); + +private: + QQmlListModel *m_owner; + int m_uid; + DynamicRoleModelNodeMetaObject *m_meta; + + friend class DynamicRoleModelNodeMetaObject; +}; + +class ModelNodeMetaObject : public QQmlOpenMetaObject +{ +public: + ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex); + ~ModelNodeMetaObject(); + + QAbstractDynamicMetaObject *toDynamicMetaObject(QObject *object) override; + + static ModelNodeMetaObject *get(QObject *obj); + + bool m_enabled; + QQmlListModel *m_model; + int m_elementIndex; + + void updateValues(); + void updateValues(const QVector &roles); + + bool initialized() const { return m_initialized; } + +protected: + void propertyWritten(int index) override; + +private: + using QQmlOpenMetaObject::setValue; + + void emitDirectNotifies(const int *changedRoles, int roleCount); + + void initialize(); + bool m_initialized; +}; + +namespace QV4 { + +namespace Heap { + +struct ModelObject : public QObjectWrapper { + void init(QObject *object, QQmlListModel *model) + { + QObjectWrapper::init(object); + m_model = model; + QObjectPrivate *op = QObjectPrivate::get(object); + m_nodeModelMetaObject = static_cast(op->metaObject); + } + void destroy() { QObjectWrapper::destroy(); } + int elementIndex() const { return m_nodeModelMetaObject->m_elementIndex; } + QQmlListModel *m_model; + ModelNodeMetaObject *m_nodeModelMetaObject; +}; + +} + +struct ModelObject : public QObjectWrapper +{ + V4_OBJECT2(ModelObject, QObjectWrapper) + V4_NEEDS_DESTROY + + ListModel *listModel() const { return d()->m_model->m_listModel; } + +protected: + static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver); + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); +}; + +} // namespace QV4 + +class ListLayout +{ +public: + ListLayout() : currentBlock(0), currentBlockOffset(0) {} + ListLayout(const ListLayout *other); + ~ListLayout(); + + class Role + { + public: + + Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {} + explicit Role(const Role *other); + ~Role(); + + // This enum must be kept in sync with the roleTypeNames variable in qqmllistmodel.cpp + enum DataType + { + Invalid = -1, + + String, + Number, + Bool, + List, + QObject, + VariantMap, + DateTime, + Function, + + MaxDataType + }; + + QString name; + DataType type; + int blockIndex; + int blockOffset; + int index; + ListLayout *subLayout; + }; + + const Role *getRoleOrCreate(const QString &key, const QVariant &data); + const Role &getRoleOrCreate(QV4::String *key, Role::DataType type); + const Role &getRoleOrCreate(const QString &key, Role::DataType type); + + const Role &getExistingRole(int index) const { return *roles.at(index); } + const Role *getExistingRole(const QString &key) const; + const Role *getExistingRole(QV4::String *key) const; + + int roleCount() const { return roles.count(); } + + static void sync(ListLayout *src, ListLayout *target); + +private: + const Role &createRole(const QString &key, Role::DataType type); + + int currentBlock; + int currentBlockOffset; + QVector roles; + QStringHash roleHash; +}; + +struct StringOrTranslation +{ + explicit StringOrTranslation(const QString &s); + explicit StringOrTranslation(const QV4::CompiledData::Binding *binding); + ~StringOrTranslation(); + bool isSet() const { return d.flag(); } + bool isTranslation() const { return d.isT2(); } + void setString(const QString &s); + void setTranslation(const QV4::CompiledData::Binding *binding); + QString toString(const QQmlListModel *owner) const; + QString asString() const; +private: + void clear(); + QBiPointer d; +}; + +/*! +\internal +*/ +class ListElement +{ +public: + + ListElement(); + ListElement(int existingUid); + ~ListElement(); + + static QVector sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout); + + enum + { + BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelNodeMetaObject *) + }; + +private: + + void destroy(ListLayout *layout); + + int setVariantProperty(const ListLayout::Role &role, const QVariant &d); + + int setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng); + + int setStringProperty(const ListLayout::Role &role, const QString &s); + int setDoubleProperty(const ListLayout::Role &role, double n); + int setBoolProperty(const ListLayout::Role &role, bool b); + int setListProperty(const ListLayout::Role &role, ListModel *m); + int setQObjectProperty(const ListLayout::Role &role, QObject *o); + int setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o); + int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m); + int setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt); + int setFunctionProperty(const ListLayout::Role &role, const QJSValue &f); + int setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b); + + void setStringPropertyFast(const ListLayout::Role &role, const QString &s); + void setDoublePropertyFast(const ListLayout::Role &role, double n); + void setBoolPropertyFast(const ListLayout::Role &role, bool b); + void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o); + void setListPropertyFast(const ListLayout::Role &role, ListModel *m); + void setVariantMapFast(const ListLayout::Role &role, QV4::Object *o); + void setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt); + void setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f); + + void clearProperty(const ListLayout::Role &role); + + QVariant getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng); + ListModel *getListProperty(const ListLayout::Role &role); + StringOrTranslation *getStringProperty(const ListLayout::Role &role); + QObject *getQObjectProperty(const ListLayout::Role &role); + QPointer *getGuardProperty(const ListLayout::Role &role); + QVariantMap *getVariantMapProperty(const ListLayout::Role &role); + QDateTime *getDateTimeProperty(const ListLayout::Role &role); + QJSValue *getFunctionProperty(const ListLayout::Role &role); + + inline char *getPropertyMemory(const ListLayout::Role &role); + + int getUid() const { return uid; } + + ModelNodeMetaObject *objectCache(); + + char data[BLOCK_SIZE]; + ListElement *next; + + int uid; + QObject *m_objectCache; + + friend class ListModel; +}; + +/*! +\internal +*/ +class ListModel +{ +public: + + ListModel(ListLayout *layout, QQmlListModel *modelCache); + ~ListModel() {} + + void destroy(); + + int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data); + int setExistingProperty(int uid, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng); + + QVariant getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng); + ListModel *getListProperty(int elementIndex, const ListLayout::Role &role); + + int roleCount() const + { + return m_layout->roleCount(); + } + + const ListLayout::Role &getExistingRole(int index) const + { + return m_layout->getExistingRole(index); + } + + const ListLayout::Role *getExistingRole(QV4::String *key) const + { + return m_layout->getExistingRole(key); + } + + const ListLayout::Role &getOrCreateListRole(const QString &name) + { + return m_layout->getRoleOrCreate(name, ListLayout::Role::List); + } + + int elementCount() const + { + return elements.count(); + } + + void set(int elementIndex, QV4::Object *object, QVector *roles); + void set(int elementIndex, QV4::Object *object); + + int append(QV4::Object *object); + void insert(int elementIndex, QV4::Object *object); + + Q_REQUIRED_RESULT QVector> remove(int index, int count); + + int appendElement(); + void insertElement(int index); + + void move(int from, int to, int n); + + static bool sync(ListModel *src, ListModel *target); + + QObject *getOrCreateModelObject(QQmlListModel *model, int elementIndex); + +private: + QPODVector elements; + ListLayout *m_layout; + + QQmlListModel *m_modelCache; + + struct ElementSync + { + ListElement *src = nullptr; + ListElement *target = nullptr; + int srcIndex = -1; + int targetIndex = -1; + QVector changedRoles; + }; + + void newElement(int index); + + void updateCacheIndices(int start = 0, int end = -1); + + friend class ListElement; + friend class QQmlListModelWorkerAgent; + friend class QQmlListModelParser; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(ListModel *); + +#endif // QQUICKLISTMODEL_P_P_H diff --git a/src/qmlmodels/qqmllistmodelworkeragent.cpp b/src/qmlmodels/qqmllistmodelworkeragent.cpp new file mode 100644 index 0000000000..7e92810f78 --- /dev/null +++ b/src/qmlmodels/qqmllistmodelworkeragent.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmllistmodelworkeragent_p.h" +#include "qqmllistmodel_p_p.h" +#include +#include +#include + +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +QQmlListModelWorkerAgent::Sync::~Sync() +{ +} + +QQmlListModelWorkerAgent::QQmlListModelWorkerAgent(QQmlListModel *model) +: m_ref(1), m_orig(model), m_copy(new QQmlListModel(model, this)) +{ +} + +QQmlListModelWorkerAgent::~QQmlListModelWorkerAgent() +{ + mutex.lock(); + syncDone.wakeAll(); + mutex.unlock(); +} + +QV4::ExecutionEngine *QQmlListModelWorkerAgent::engine() const +{ + return m_copy->m_engine; +} + +void QQmlListModelWorkerAgent::setEngine(QV4::ExecutionEngine *eng) +{ + if (eng != m_copy->m_engine) { + m_copy->m_engine = eng; + emit engineChanged(eng); + } +} + +void QQmlListModelWorkerAgent::addref() +{ + m_ref.ref(); +} + +void QQmlListModelWorkerAgent::release() +{ + bool del = !m_ref.deref(); + + if (del) + deleteLater(); +} + +void QQmlListModelWorkerAgent::modelDestroyed() +{ + m_orig = nullptr; +} + +int QQmlListModelWorkerAgent::count() const +{ + return m_copy->count(); +} + +void QQmlListModelWorkerAgent::clear() +{ + m_copy->clear(); +} + +void QQmlListModelWorkerAgent::remove(QQmlV4Function *args) +{ + m_copy->remove(args); +} + +void QQmlListModelWorkerAgent::append(QQmlV4Function *args) +{ + m_copy->append(args); +} + +void QQmlListModelWorkerAgent::insert(QQmlV4Function *args) +{ + m_copy->insert(args); +} + +QJSValue QQmlListModelWorkerAgent::get(int index) const +{ + return m_copy->get(index); +} + +void QQmlListModelWorkerAgent::set(int index, const QJSValue &value) +{ + m_copy->set(index, value); +} + +void QQmlListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value) +{ + m_copy->setProperty(index, property, value); +} + +void QQmlListModelWorkerAgent::move(int from, int to, int count) +{ + m_copy->move(from, to, count); +} + +void QQmlListModelWorkerAgent::sync() +{ + Sync *s = new Sync(m_copy); + + mutex.lock(); + QCoreApplication::postEvent(this, s); + syncDone.wait(&mutex); + mutex.unlock(); +} + +bool QQmlListModelWorkerAgent::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + bool cc = false; + QMutexLocker locker(&mutex); + if (m_orig) { + Sync *s = static_cast(e); + + cc = (m_orig->count() != s->list->count()); + + Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles); + if (m_orig->m_dynamicRoles) + QQmlListModel::sync(s->list, m_orig); + else + ListModel::sync(s->list->m_listModel, m_orig->m_listModel); + } + + syncDone.wakeAll(); + locker.unlock(); + + if (cc) + emit m_orig->countChanged(); + return true; + } + + return QObject::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qqmllistmodelworkeragent_p.cpp" diff --git a/src/qmlmodels/qqmllistmodelworkeragent_p.h b/src/qmlmodels/qqmllistmodelworkeragent_p.h new file mode 100644 index 0000000000..f79c0c557a --- /dev/null +++ b/src/qmlmodels/qqmllistmodelworkeragent_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQUICKLISTMODELWORKERAGENT_P_H +#define QQUICKLISTMODELWORKERAGENT_P_H + +// +// 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. +// + +#include + +#include +#include +#include + +#include + +QT_REQUIRE_CONFIG(qml_list_model); + +QT_BEGIN_NAMESPACE + + +class QQmlListModel; + +class QQmlListModelWorkerAgent : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count) + Q_PROPERTY(QV4::ExecutionEngine *engine READ engine WRITE setEngine NOTIFY engineChanged) + +public: + QQmlListModelWorkerAgent(QQmlListModel *); + ~QQmlListModelWorkerAgent(); + + QV4::ExecutionEngine *engine() const; + void setEngine(QV4::ExecutionEngine *eng); + + Q_INVOKABLE void addref(); + Q_INVOKABLE void release(); + + int count() const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE void remove(QQmlV4Function *args); + Q_INVOKABLE void append(QQmlV4Function *args); + Q_INVOKABLE void insert(QQmlV4Function *args); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); + Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); + Q_INVOKABLE void move(int from, int to, int count); + Q_INVOKABLE void sync(); + + void modelDestroyed(); + +signals: + void engineChanged(QV4::ExecutionEngine *engine); + +protected: + bool event(QEvent *) override; + +private: + friend class QQuickWorkerScriptEnginePrivate; + friend class QQmlListModel; + + struct Sync : public QEvent { + Sync(QQmlListModel *l) + : QEvent(QEvent::User) + , list(l) + {} + ~Sync(); + QQmlListModel *list; + }; + + QAtomicInt m_ref; + QQmlListModel *m_orig; + QQmlListModel *m_copy; + QMutex mutex; + QWaitCondition syncDone; +}; + +QT_END_NAMESPACE + +#endif // QQUICKLISTMODELWORKERAGENT_P_H + diff --git a/src/qmlmodels/qqmlmodelsmodule.cpp b/src/qmlmodels/qqmlmodelsmodule.cpp new file mode 100644 index 0000000000..989fec9b7d --- /dev/null +++ b/src/qmlmodels/qqmlmodelsmodule.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion. +** 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 "qqmlmodelsmodule_p.h" +#include +#include +#if QT_CONFIG(qml_list_model) +#include +#endif +#if QT_CONFIG(qml_delegate_model) +#include +#include +#endif +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlModelsModule::registerQmlTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + qmlRegisterType("QtQml", 2, 1, "Instantiator"); // Only available in >= 2.1 + qmlRegisterType(); +} + +void QQmlModelsModule::registerQuickTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + + const char uri[] = "QtQuick"; + + qmlRegisterType(uri, 2, 1, "Instantiator"); + qmlRegisterType(); +#if QT_CONFIG(qml_list_model) + qmlRegisterType(uri, 2, 0, "ListElement"); + qmlRegisterCustomType(uri, 2, 0, "ListModel", new QQmlListModelParser); +#endif + qmlRegisterType(uri, 2, 0, "Package"); +#if QT_CONFIG(qml_delegate_model) + qmlRegisterType(uri, 2, 0, "VisualDataModel"); + qmlRegisterType(uri, 2, 0, "VisualDataGroup"); +#endif + qmlRegisterType(uri, 2, 0, "VisualItemModel"); +} + +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlModelsModule::defineModule() +{ + const char uri[] = "QtQml.Models"; + +#if QT_CONFIG(qml_list_model) + qmlRegisterType(uri, 2, 1, "ListElement"); + qmlRegisterCustomType(uri, 2, 1, "ListModel", new QQmlListModelParser); +#endif +#if QT_CONFIG(qml_delegate_model) + qmlRegisterType(uri, 2, 1, "DelegateModel"); + qmlRegisterType(uri, 2, 1, "DelegateModelGroup"); +#endif + qmlRegisterType(uri, 2, 1, "ObjectModel"); + qmlRegisterType(uri, 2, 3, "ObjectModel"); + + qmlRegisterType(uri, 2, 2, "ItemSelectionModel"); + + qmlRegisterType(uri, 2, 14, "Package"); + qmlRegisterType(uri, 2, 14, "Instantiator"); + qmlRegisterType(); +} + +void QQmlModelsModule::defineLabsModule() +{ + const char uri[] = "Qt.labs.qmlmodels"; + +#if QT_CONFIG(qml_delegate_model) + qmlRegisterUncreatableType(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent.")); + qmlRegisterType(uri, 1, 0, "DelegateChooser"); + qmlRegisterType(uri, 1, 0, "DelegateChoice"); +#endif + qmlRegisterType(uri, 1, 0, "TableModel"); + qmlRegisterType(uri, 1, 0, "TableModelColumn"); +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmlmodelsmodule_p.h b/src/qmlmodels/qqmlmodelsmodule_p.h new file mode 100644 index 0000000000..7e02578db9 --- /dev/null +++ b/src/qmlmodels/qqmlmodelsmodule_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion. +** 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$ +** +****************************************************************************/ + +#ifndef QQMLMODELSMODULE_H +#define QQMLMODELSMODULE_H + +// +// 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. +// + +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlModelsModule +{ +public: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + static void registerQmlTypes(); + static void registerQuickTypes(); +#endif + + static void defineModule(); + static void defineLabsModule(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qmlmodels/qqmlobjectmodel.cpp b/src/qmlmodels/qqmlobjectmodel.cpp new file mode 100644 index 0000000000..b6330b4295 --- /dev/null +++ b/src/qmlmodels/qqmlobjectmodel.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qqmlobjectmodel_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +QHash QQmlObjectModelAttached::attachedProperties; + + +class QQmlObjectModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlObjectModel) +public: + class Item { + public: + Item(QObject *i) : item(i), ref(0) {} + + void addRef() { ++ref; } + bool deref() { return --ref == 0; } + + QObject *item; + int ref; + }; + + QQmlObjectModelPrivate() : QObjectPrivate(), moveId(0) {} + + static void children_append(QQmlListProperty *prop, QObject *item) { + int index = static_cast(prop->data)->children.count(); + static_cast(prop->data)->insert(index, item); + } + + static int children_count(QQmlListProperty *prop) { + return static_cast(prop->data)->children.count(); + } + + static QObject *children_at(QQmlListProperty *prop, int index) { + return static_cast(prop->data)->children.at(index).item; + } + + static void children_clear(QQmlListProperty *prop) { + static_cast(prop->data)->clear(); + } + + void insert(int index, QObject *item) { + Q_Q(QQmlObjectModel); + children.insert(index, Item(item)); + for (int i = index; i < children.count(); ++i) { + QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); + attached->setIndex(i); + } + QQmlChangeSet changeSet; + changeSet.insert(index, 1); + emit q->modelUpdated(changeSet, false); + emit q->countChanged(); + emit q->childrenChanged(); + } + + void move(int from, int to, int n) { + Q_Q(QQmlObjectModel); + if (from > to) { + // Only move forwards - flip if backwards moving + int tfrom = from; + int tto = to; + from = tto; + to = tto+n; + n = tfrom-tto; + } + + QPODVector store; + for (int i = 0; i < to - from; ++i) + store.append(children[from + n + i]); + for (int i = 0; i < n; ++i) + store.append(children[from + i]); + + for (int i = 0; i < store.count(); ++i) { + children[from + i] = store[i]; + QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(from + i).item); + attached->setIndex(from + i); + } + + QQmlChangeSet changeSet; + changeSet.move(from, to, n, ++moveId); + emit q->modelUpdated(changeSet, false); + emit q->childrenChanged(); + } + + void remove(int index, int n) { + Q_Q(QQmlObjectModel); + for (int i = index; i < index + n; ++i) { + QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); + attached->setIndex(-1); + } + children.erase(children.begin() + index, children.begin() + index + n); + for (int i = index; i < children.count(); ++i) { + QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item); + attached->setIndex(i); + } + QQmlChangeSet changeSet; + changeSet.remove(index, n); + emit q->modelUpdated(changeSet, false); + emit q->countChanged(); + emit q->childrenChanged(); + } + + void clear() { + Q_Q(QQmlObjectModel); + for (const Item &child : qAsConst(children)) + emit q->destroyingItem(child.item); + remove(0, children.count()); + } + + int indexOf(QObject *item) const { + for (int i = 0; i < children.count(); ++i) + if (children.at(i).item == item) + return i; + return -1; + } + + uint moveId; + QList children; +}; + + +/*! + \qmltype ObjectModel + \instantiates QQmlObjectModel + \inqmlmodule QtQml.Models + \ingroup qtquick-models + \brief Defines a set of items to be used as a model. + + An ObjectModel contains the visual items to be used in a view. + When an ObjectModel is used in a view, the view does not require + a delegate since the ObjectModel already contains the visual + delegate (items). + + An item can determine its index within the + model via the \l{ObjectModel::index}{index} attached property. + + The example below places three colored rectangles in a ListView. + \code + import QtQuick 2.0 + import QtQml.Models 2.1 + + Rectangle { + ObjectModel { + id: itemModel + Rectangle { height: 30; width: 80; color: "red" } + Rectangle { height: 30; width: 80; color: "green" } + Rectangle { height: 30; width: 80; color: "blue" } + } + + ListView { + anchors.fill: parent + model: itemModel + } + } + \endcode + + \image objectmodel.png + + \sa {Qt Quick Examples - Views} +*/ + +QQmlObjectModel::QQmlObjectModel(QObject *parent) + : QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent) +{ +} + +/*! + \qmlattachedproperty int QtQml.Models::ObjectModel::index + This attached property holds the index of this delegate's item within the model. + + It is attached to each instance of the delegate. +*/ + +QQmlListProperty QQmlObjectModel::children() +{ + Q_D(QQmlObjectModel); + return QQmlListProperty(this, + d, + d->children_append, + d->children_count, + d->children_at, + d->children_clear); +} + +/*! + \qmlproperty int QtQml.Models::ObjectModel::count + + The number of items in the model. This property is readonly. +*/ +int QQmlObjectModel::count() const +{ + Q_D(const QQmlObjectModel); + return d->children.count(); +} + +bool QQmlObjectModel::isValid() const +{ + return true; +} + +QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode) +{ + Q_D(QQmlObjectModel); + QQmlObjectModelPrivate::Item &item = d->children[index]; + item.addRef(); + if (item.ref == 1) { + emit initItem(index, item.item); + emit createdItem(index, item.item); + } + return item.item; +} + +QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) +{ + Q_D(QQmlObjectModel); + int idx = d->indexOf(item); + if (idx >= 0) { + if (!d->children[idx].deref()) + return QQmlInstanceModel::Referenced; + } + return nullptr; +} + +QVariant QQmlObjectModel::variantValue(int index, const QString &role) +{ + Q_D(QQmlObjectModel); + if (index < 0 || index >= d->children.count()) + return QString(); + return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(role); +} + +QQmlIncubator::Status QQmlObjectModel::incubationStatus(int) +{ + return QQmlIncubator::Ready; +} + +int QQmlObjectModel::indexOf(QObject *item, QObject *) const +{ + Q_D(const QQmlObjectModel); + return d->indexOf(item); +} + +QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj) +{ + return QQmlObjectModelAttached::properties(obj); +} + +/*! + \qmlmethod object QtQml.Models::ObjectModel::get(int index) + \since 5.6 + + Returns the item at \a index in the model. This allows the item + to be accessed or modified from JavaScript: + + \code + Component.onCompleted: { + objectModel.append(objectComponent.createObject()) + console.log(objectModel.get(0).objectName); + objectModel.get(0).objectName = "first"; + } + \endcode + + The \a index must be an element in the list. + + \sa append() +*/ +QObject *QQmlObjectModel::get(int index) const +{ + Q_D(const QQmlObjectModel); + if (index < 0 || index >= d->children.count()) + return nullptr; + return d->children.at(index).item; +} + +/*! + \qmlmethod QtQml.Models::ObjectModel::append(object item) + \since 5.6 + + Appends a new item to the end of the model. + + \code + objectModel.append(objectComponent.createObject()) + \endcode + + \sa insert(), remove() +*/ +void QQmlObjectModel::append(QObject *object) +{ + Q_D(QQmlObjectModel); + d->insert(count(), object); +} + +/*! + \qmlmethod QtQml.Models::ObjectModel::insert(int index, object item) + \since 5.6 + + Inserts a new item to the model at position \a index. + + \code + objectModel.insert(2, objectComponent.createObject()) + \endcode + + The \a index must be to an existing item in the list, or one past + the end of the list (equivalent to append). + + \sa append(), remove() +*/ +void QQmlObjectModel::insert(int index, QObject *object) +{ + Q_D(QQmlObjectModel); + if (index < 0 || index > count()) { + qmlWarning(this) << tr("insert: index %1 out of range").arg(index); + return; + } + d->insert(index, object); +} + +/*! + \qmlmethod QtQml.Models::ObjectModel::move(int from, int to, int n = 1) + \since 5.6 + + Moves \a n items \a from one position \a to another. + + The from and to ranges must exist; for example, to move the first 3 items + to the end of the model: + + \code + objectModel.move(0, objectModel.count - 3, 3) + \endcode + + \sa append() +*/ +void QQmlObjectModel::move(int from, int to, int n) +{ + Q_D(QQmlObjectModel); + if (n <= 0 || from == to) + return; + if (from < 0 || to < 0 || from + n > count() || to + n > count()) { + qmlWarning(this) << tr("move: out of range"); + return; + } + d->move(from, to, n); +} + +/*! + \qmlmethod QtQml.Models::ObjectModel::remove(int index, int n = 1) + \since 5.6 + + Removes the items at \a index from the model. + + \sa clear() +*/ +void QQmlObjectModel::remove(int index, int n) +{ + Q_D(QQmlObjectModel); + if (index < 0 || n <= 0 || index + n > count()) { + qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+n).arg(count()); + return; + } + d->remove(index, n); +} + +/*! + \qmlmethod QtQml.Models::ObjectModel::clear() + \since 5.6 + + Clears all items from the model. + + \sa append(), remove() +*/ +void QQmlObjectModel::clear() +{ + Q_D(QQmlObjectModel); + d->clear(); +} + +QT_END_NAMESPACE + +#include "moc_qqmlobjectmodel_p.cpp" diff --git a/src/qmlmodels/qqmlobjectmodel_p.h b/src/qmlmodels/qqmlobjectmodel_p.h new file mode 100644 index 0000000000..99bfd86269 --- /dev/null +++ b/src/qmlmodels/qqmlobjectmodel_p.h @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQMLINSTANCEMODEL_P_H +#define QQMLINSTANCEMODEL_P_H + +// +// 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. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QObject; +class QQmlChangeSet; +class QAbstractItemModel; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstanceModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + virtual ~QQmlInstanceModel() {} + + enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; + Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) + + virtual int count() const = 0; + virtual bool isValid() const = 0; + virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0; + virtual ReleaseFlags release(QObject *object) = 0; + virtual void cancel(int) {} + QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); } + virtual QVariant variantValue(int, const QString &) = 0; + virtual void setWatchedRoles(const QList &roles) = 0; + virtual QQmlIncubator::Status incubationStatus(int index) = 0; + + virtual int indexOf(QObject *object, QObject *objectContext) const = 0; + virtual const QAbstractItemModel *abstractItemModel() const { return nullptr; } + +Q_SIGNALS: + void countChanged(); + void modelUpdated(const QQmlChangeSet &changeSet, bool reset); + void createdItem(int index, QObject *object); + void initItem(int index, QObject *object); + void destroyingItem(QObject *object); + +protected: + QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = nullptr) + : QObject(dd, parent) {} + +private: + Q_DISABLE_COPY(QQmlInstanceModel) +}; + +class QQmlObjectModelAttached; +class QQmlObjectModelPrivate; +class Q_QMLMODELS_PRIVATE_EXPORT QQmlObjectModel : public QQmlInstanceModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlObjectModel) + + Q_PROPERTY(QQmlListProperty children READ children NOTIFY childrenChanged DESIGNABLE false) + Q_CLASSINFO("DefaultProperty", "children") + +public: + QQmlObjectModel(QObject *parent=nullptr); + ~QQmlObjectModel() {} + + int count() const override; + bool isValid() const override; + QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; + ReleaseFlags release(QObject *object) override; + QVariant variantValue(int index, const QString &role) override; + void setWatchedRoles(const QList &) override {} + QQmlIncubator::Status incubationStatus(int index) override; + + int indexOf(QObject *object, QObject *objectContext) const override; + + QQmlListProperty children(); + + static QQmlObjectModelAttached *qmlAttachedProperties(QObject *obj); + + Q_REVISION(3) Q_INVOKABLE QObject *get(int index) const; + Q_REVISION(3) Q_INVOKABLE void append(QObject *object); + Q_REVISION(3) Q_INVOKABLE void insert(int index, QObject *object); + Q_REVISION(3) Q_INVOKABLE void move(int from, int to, int n = 1); + Q_REVISION(3) Q_INVOKABLE void remove(int index, int n = 1); + +public Q_SLOTS: + Q_REVISION(3) void clear(); + +Q_SIGNALS: + void childrenChanged(); + +private: + Q_DISABLE_COPY(QQmlObjectModel) +}; + +class QQmlObjectModelAttached : public QObject +{ + Q_OBJECT + +public: + QQmlObjectModelAttached(QObject *parent) + : QObject(parent), m_index(-1) {} + ~QQmlObjectModelAttached() { + attachedProperties.remove(parent()); + } + + Q_PROPERTY(int index READ index NOTIFY indexChanged) + int index() const { return m_index; } + void setIndex(int idx) { + if (m_index != idx) { + m_index = idx; + Q_EMIT indexChanged(); + } + } + + static QQmlObjectModelAttached *properties(QObject *obj) { + QQmlObjectModelAttached *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QQmlObjectModelAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; + } + +Q_SIGNALS: + void indexChanged(); + +public: + int m_index; + + static QHash attachedProperties; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlInstanceModel) +QML_DECLARE_TYPE(QQmlObjectModel) +QML_DECLARE_TYPEINFO(QQmlObjectModel, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQMLINSTANCEMODEL_P_H diff --git a/src/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp new file mode 100644 index 0000000000..b244a007e5 --- /dev/null +++ b/src/qmlmodels/qqmltableinstancemodel.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** 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 "qqmltableinstancemodel_p.h" +#include "qqmldelegatecomponent_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const char* kModelItemTag = "_tableinstancemodel_modelItem"; + +bool QQmlTableInstanceModel::isDoneIncubating(QQmlDelegateModelItem *modelItem) +{ + if (!modelItem->incubationTask) + return true; + + const auto status = modelItem->incubationTask->status(); + return (status == QQmlIncubator::Ready) || (status == QQmlIncubator::Error); +} + +void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelItem) +{ + Q_ASSERT(modelItem); + + delete modelItem->object; + modelItem->object = nullptr; + + if (modelItem->contextData) { + modelItem->contextData->invalidate(); + Q_ASSERT(modelItem->contextData->refCount == 1); + modelItem->contextData = nullptr; + } + + modelItem->deleteLater(); +} + +QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent) + : QQmlInstanceModel(*(new QObjectPrivate()), parent) + , m_qmlContext(qmlContext) + , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList())) +{ +} + +void QQmlTableInstanceModel::useImportVersion(int minorVersion) +{ + m_adaptorModel.useImportVersion(minorVersion); +} + +QQmlTableInstanceModel::~QQmlTableInstanceModel() +{ + for (const auto modelItem : m_modelItems) { + // No item in m_modelItems should be referenced at this point. The view + // should release all its items before it deletes this model. Only model items + // that are still being incubated should be left for us to delete. + Q_ASSERT(modelItem->objectRef == 0); + Q_ASSERT(modelItem->incubationTask); + // Check that we are not being deleted while we're + // in the process of e.g emitting a created signal. + Q_ASSERT(modelItem->scriptRef == 0); + + if (modelItem->object) { + delete modelItem->object; + modelItem->object = nullptr; + modelItem->contextData->invalidate(); + modelItem->contextData = nullptr; + } + } + + deleteAllFinishedIncubationTasks(); + qDeleteAll(m_modelItems); + drainReusableItemsPool(0); +} + +QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index) +{ + if (m_delegateChooser) { + const int row = m_adaptorModel.rowAt(index); + const int column = m_adaptorModel.columnAt(index); + QQmlComponent *delegate = nullptr; + QQmlAbstractDelegateComponent *chooser = m_delegateChooser; + do { + delegate = chooser->delegate(&m_adaptorModel, row, column); + chooser = qobject_cast(delegate); + } while (chooser); + return delegate; + } + + return m_delegate; +} + +QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) +{ + // Check if an item for the given index is already loaded and ready + QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr); + if (modelItem) + return modelItem; + + QQmlComponent *delegate = resolveDelegate(index); + if (!delegate) + return nullptr; + + // Check if the pool contains an item that can be reused + modelItem = takeFromReusableItemsPool(delegate); + if (modelItem) { + reuseItem(modelItem, index); + m_modelItems.insert(index, modelItem); + return modelItem; + } + + // Create a new item from scratch + modelItem = m_adaptorModel.createItem(m_metaType, index); + if (modelItem) { + modelItem->delegate = delegate; + m_modelItems.insert(index, modelItem); + return modelItem; + } + + qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index; + return nullptr; +} + +QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode) +{ + Q_ASSERT(m_delegate); + Q_ASSERT(index >= 0 && index < m_adaptorModel.count()); + Q_ASSERT(m_qmlContext && m_qmlContext->isValid()); + + QQmlDelegateModelItem *modelItem = resolveModelItem(index); + if (!modelItem) + return nullptr; + + if (modelItem->object) { + // The model item has already been incubated. So + // just bump the ref-count and return it. + modelItem->referenceObject(); + return modelItem->object; + } + + // The object is not ready, and needs to be incubated + incubateModelItem(modelItem, incubationMode); + if (!isDoneIncubating(modelItem)) + return nullptr; + + // Incubation is done, so the task should be removed + Q_ASSERT(!modelItem->incubationTask); + + if (!modelItem->object) { + // The object was incubated synchronously (otherwise we would return above). But since + // we have no object, the incubation must have failed. And when we have no object, there + // should be no object references either. And there should also not be any internal script + // refs at this point. So we delete the model item. + Q_ASSERT(!modelItem->isObjectReferenced()); + Q_ASSERT(!modelItem->isReferenced()); + m_modelItems.remove(modelItem->index); + delete modelItem; + return nullptr; + } + + // Incubation was completed sync and successful + modelItem->referenceObject(); + return modelItem->object; +} + +QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable) +{ + Q_ASSERT(object); + auto modelItem = qvariant_cast(object->property(kModelItemTag)); + Q_ASSERT(modelItem); + + if (!modelItem->releaseObject()) + return QQmlDelegateModel::Referenced; + + if (modelItem->isReferenced()) { + // We still have an internal reference to this object, which means that we are told to release an + // object while the createdItem signal for it is still on the stack. This can happen when objects + // are e.g delivered async, and the user flicks back and forth quicker than the loading can catch + // up with. The view might then find that the object is no longer visible and should be released. + // We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers + // point of view, it should consider it destroyed. + return QQmlDelegateModel::Destroyed; + } + + // The item is not referenced by anyone + m_modelItems.remove(modelItem->index); + + if (reusable == Reusable) { + insertIntoReusableItemsPool(modelItem); + return QQmlInstanceModel::Referenced; + } + + // The item is not reused or referenced by anyone, so just delete it + modelItem->destroyObject(); + emit destroyingItem(object); + + delete modelItem; + return QQmlInstanceModel::Destroyed; +} + +void QQmlTableInstanceModel::cancel(int index) +{ + auto modelItem = m_modelItems.value(index); + Q_ASSERT(modelItem); + + // Since the view expects the item to be incubating, there should be + // an incubation task. And since the incubation is not done, no-one + // should yet have received, and therfore hold a reference to, the object. + Q_ASSERT(modelItem->incubationTask); + Q_ASSERT(!modelItem->isObjectReferenced()); + + m_modelItems.remove(index); + + if (modelItem->object) + delete modelItem->object; + + // modelItem->incubationTask will be deleted from the modelItems destructor + delete modelItem; +} + +void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem) +{ + // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release() + // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released + // item is no longer referenced, it will be added to the pool. Reusing of items can be specified + // per item, in case certain items cannot be recycled. + // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are + // about to create a new item, we first check if the pool contains an item based on the same + // delegate from before. If so, we take it out of the pool (instead of creating a new item), and + // update all its context-, and attached properties. + // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool() + // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only + // meant to rest there for a short while, ideally only from the time e.g a row is unloaded + // on one side of the view, and until a new row is loaded on the opposite side. In-between + // this time, the application will see the item as fully functional and 'alive' (just not + // visible on screen). Since this time is supposed to be short, we don't take any action to + // notify the application about it, since we don't want to trigger any bindings that can + // disturb performance. + // A recommended time for calling drainReusableItemsPool() is each time a view has finished + // loading e.g a new row or column. If there are more items in the pool after that, it means + // that the view most likely doesn't need them anytime soon. Those items should be destroyed to + // not consume resources. + // Depending on if a view is a list or a table, it can sometimes be performant to keep + // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the + // number of visible rows in a view is much larger than the number of visible columns. + // In that case, if you flick out a row, and then flick in a column, you would throw away a lot + // of items in the pool if completely draining it. The reason is that unloading a row places more + // items in the pool than what ends up being recycled when loading a new column. And then, when you + // next flick in a new row, you would need to load all those drained items again from scratch. For + // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep + // items in the pool for a bit longer, effectively keeping more items in circulation. + // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which + // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained. + Q_ASSERT(!modelItem->incubationTask); + Q_ASSERT(!modelItem->isObjectReferenced()); + Q_ASSERT(!modelItem->isReferenced()); + Q_ASSERT(modelItem->object); + + modelItem->poolTime = 0; + m_reusableItemsPool.append(modelItem); + emit itemPooled(modelItem->index, modelItem->object); +} + +QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate) +{ + // Find the oldest item in the pool that was made from the same delegate as + // the given argument, remove it from the pool, and return it. + if (m_reusableItemsPool.isEmpty()) + return nullptr; + + for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { + if ((*it)->delegate != delegate) + continue; + auto modelItem = *it; + m_reusableItemsPool.erase(it); + return modelItem; + } + + return nullptr; +} + +void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime) +{ + // Rather than releasing all pooled items upon a call to this function, each + // item has a poolTime. The poolTime specifies for how many loading cycles an item + // has been resting in the pool. And for each invocation of this function, poolTime + // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed + // from the pool and released. This way, the view can tweak a bit for how long + // items should stay in "circulation", even if they are not recycled right away. + for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) { + auto modelItem = *it; + modelItem->poolTime++; + if (modelItem->poolTime <= maxPoolTime) { + ++it; + } else { + it = m_reusableItemsPool.erase(it); + release(modelItem->object, NotReusable); + } + } +} + +void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex) +{ + // Update the context properties index, row and column on + // the delegate item, and inform the application about it. + const int newRow = m_adaptorModel.rowAt(newModelIndex); + const int newColumn = m_adaptorModel.columnAt(newModelIndex); + item->setModelIndex(newModelIndex, newRow, newColumn); + + // Notify the application that all 'dynamic'/role-based context data has + // changed as well (their getter function will use the updated index). + auto const itemAsList = QList() << item; + auto const updateAllRoles = QVector(); + m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles); + + // Inform the view that the item is recycled. This will typically result + // in the view updating its own attached delegate item properties. + emit itemReused(newModelIndex, item->object); +} + +void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode) +{ + // Guard the model item temporarily so that it's not deleted from + // incubatorStatusChanged(), in case the incubation is done synchronously. + modelItem->scriptRef++; + + if (modelItem->incubationTask) { + // We're already incubating the model item from a previous request. If the previous call requested + // the item async, but the current request needs it sync, we need to force-complete the incubation. + const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); + if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) + modelItem->incubationTask->forceCompletion(); + } else { + modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode); + + QQmlContextData *ctxt = new QQmlContextData; + QQmlContext *creationContext = modelItem->delegate->creationContext(); + ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data())); + ctxt->contextObject = modelItem; + modelItem->contextData = ctxt; + + QQmlComponentPrivate::get(modelItem->delegate)->incubateObject( + modelItem->incubationTask, + modelItem->delegate, + m_qmlContext->engine(), + ctxt, + QQmlContextData::get(m_qmlContext)); + } + + // Remove the temporary guard + modelItem->scriptRef--; +} + +void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status) +{ + QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate; + Q_ASSERT(modelItem->incubationTask); + + modelItem->incubationTask = nullptr; + incubationTask->modelItemToIncubate = nullptr; + + if (status == QQmlIncubator::Ready) { + // Tag the incubated object with the model item for easy retrieval upon release etc. + modelItem->object->setProperty(kModelItemTag, QVariant::fromValue(modelItem)); + + // Emit that the item has been created. What normally happens next is that the view + // upon receiving the signal asks for the model item once more. And since the item is + // now in the map, it will be returned directly. + Q_ASSERT(modelItem->object); + modelItem->scriptRef++; + emit createdItem(modelItem->index, modelItem->object); + modelItem->scriptRef--; + } else if (status == QQmlIncubator::Error) { + qWarning() << "Error incubating delegate:" << incubationTask->errors(); + } + + if (!modelItem->isReferenced() && !modelItem->isObjectReferenced()) { + // We have no internal reference to the model item, and the view has no + // reference to the incubated object. So just delete the model item. + // Note that being here means that the object was incubated _async_ + // (otherwise modelItem->isReferenced() would be true). + m_modelItems.remove(modelItem->index); + + if (modelItem->object) { + modelItem->scriptRef++; + emit destroyingItem(modelItem->object); + modelItem->scriptRef--; + Q_ASSERT(!modelItem->isReferenced()); + } + + deleteModelItemLater(modelItem); + } + + deleteIncubationTaskLater(incubationTask); +} + +QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) { + const auto modelItem = m_modelItems.value(index, nullptr); + if (!modelItem) + return QQmlIncubator::Null; + + if (modelItem->incubationTask) + return modelItem->incubationTask->status(); + + // Since we clear the incubation task when we're done + // incubating, it means that the status is Ready. + return QQmlIncubator::Ready; +} + +void QQmlTableInstanceModel::deleteIncubationTaskLater(QQmlIncubator *incubationTask) +{ + // We often need to post-delete incubation tasks, since we cannot + // delete them while we're in the middle of an incubation change callback. + Q_ASSERT(!m_finishedIncubationTasks.contains(incubationTask)); + m_finishedIncubationTasks.append(incubationTask); + if (m_finishedIncubationTasks.count() == 1) + QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks); +} + +void QQmlTableInstanceModel::deleteAllFinishedIncubationTasks() +{ + qDeleteAll(m_finishedIncubationTasks); + m_finishedIncubationTasks.clear(); +} + +QVariant QQmlTableInstanceModel::model() const +{ + return m_adaptorModel.model(); +} + +void QQmlTableInstanceModel::setModel(const QVariant &model) +{ + // Pooled items are still accessible/alive for the application, and + // needs to stay in sync with the model. So we need to drain the pool + // completely when the model changes. + drainReusableItemsPool(0); + if (auto const aim = abstractItemModel()) + disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback); + m_adaptorModel.setModel(model, this, m_qmlContext->engine()); + if (auto const aim = abstractItemModel()) + connect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback); +} + +void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector &roles) +{ + // This function is called when model data has changed. In that case, we tell the adaptor model + // to go through all the items we have created, find the ones that are affected, and notify that + // their model data has changed. This will in turn update QML bindings inside the delegate items. + int numberOfRowsChanged = end.row() - begin.row() + 1; + int numberOfColumnsChanged = end.column() - begin.column() + 1; + + for (int column = 0; column < numberOfColumnsChanged; ++column) { + const int columnIndex = begin.column() + column; + const int rowIndex = begin.row() + (columnIndex * rows()); + m_adaptorModel.notify(m_modelItems.values(), rowIndex, numberOfRowsChanged, roles); + } +} + +QQmlComponent *QQmlTableInstanceModel::delegate() const +{ + return m_delegate; +} + +void QQmlTableInstanceModel::setDelegate(QQmlComponent *delegate) +{ + if (m_delegate == delegate) + return; + + m_delegateChooser = nullptr; + if (delegate) { + QQmlAbstractDelegateComponent *adc = + qobject_cast(delegate); + if (adc) + m_delegateChooser = adc; + } + + m_delegate = delegate; +} + +const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const +{ + return m_adaptorModel.adaptsAim() ? m_adaptorModel.aim() : nullptr; +} + +// -------------------------------------------------------- + +void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object) +{ + modelItemToIncubate->object = object; + emit tableInstanceModel->initItem(modelItemToIncubate->index, object); +} + +void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status) +{ + if (!QQmlTableInstanceModel::isDoneIncubating(modelItemToIncubate)) + return; + + // We require the view to cancel any ongoing load + // requests before the tableInstanceModel is destructed. + Q_ASSERT(tableInstanceModel); + + tableInstanceModel->incubatorStatusChanged(this, status); +} + +#include "moc_qqmltableinstancemodel_p.cpp" + +QT_END_NAMESPACE + diff --git a/src/qmlmodels/qqmltableinstancemodel_p.h b/src/qmlmodels/qqmltableinstancemodel_p.h new file mode 100644 index 0000000000..20331df5cc --- /dev/null +++ b/src/qmlmodels/qqmltableinstancemodel_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQMLTABLEINSTANCEMODEL_P_H +#define QQMLTABLEINSTANCEMODEL_P_H + +// +// 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. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlTableInstanceModel; +class QQmlAbstractDelegateComponent; + +class QQmlTableInstanceModelIncubationTask : public QQDMIncubationTask +{ +public: + QQmlTableInstanceModelIncubationTask( + QQmlTableInstanceModel *tableInstanceModel + , QQmlDelegateModelItem* modelItemToIncubate + , IncubationMode mode) + : QQDMIncubationTask(nullptr, mode) + , modelItemToIncubate(modelItemToIncubate) + , tableInstanceModel(tableInstanceModel) { + clear(); + } + + void statusChanged(Status status) override; + void setInitialState(QObject *object) override; + + QQmlDelegateModelItem *modelItemToIncubate = nullptr; + QQmlTableInstanceModel *tableInstanceModel = nullptr; +}; + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel +{ + Q_OBJECT + +public: + + enum ReusableFlag { + NotReusable, + Reusable + }; + + QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr); + ~QQmlTableInstanceModel() override; + + void useImportVersion(int minorVersion); + + int count() const override { return m_adaptorModel.count(); } + int rows() const { return m_adaptorModel.rowCount(); } + int columns() const { return m_adaptorModel.columnCount(); } + + bool isValid() const override { return true; } + + QVariant model() const; + void setModel(const QVariant &model); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *); + + const QAbstractItemModel *abstractItemModel() const override; + + QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; + ReleaseFlags release(QObject *object) override { return release(object, NotReusable); } + ReleaseFlags release(QObject *object, ReusableFlag reusable); + void cancel(int) override; + + void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem); + QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate); + void drainReusableItemsPool(int maxPoolTime); + int poolSize() { return m_reusableItemsPool.size(); } + void reuseItem(QQmlDelegateModelItem *item, int newModelIndex); + + QQmlIncubator::Status incubationStatus(int index) override; + + QVariant variantValue(int, const QString &) override { Q_UNREACHABLE(); return QVariant(); } + void setWatchedRoles(const QList &) override { Q_UNREACHABLE(); } + int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; } + +Q_SIGNALS: + void itemPooled(int index, QObject *object); + void itemReused(int index, QObject *object); + +private: + QQmlComponent *resolveDelegate(int index); + + QQmlAdaptorModel m_adaptorModel; + QQmlAbstractDelegateComponent *m_delegateChooser = nullptr; + QQmlComponent *m_delegate = nullptr; + QPointer m_qmlContext; + QQmlDelegateModelItemMetaType *m_metaType; + + QHash m_modelItems; + QList m_reusableItemsPool; + QList m_finishedIncubationTasks; + + void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode); + void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status); + void deleteIncubationTaskLater(QQmlIncubator *incubationTask); + void deleteAllFinishedIncubationTasks(); + QQmlDelegateModelItem *resolveModelItem(int index); + + void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector &roles); + + static bool isDoneIncubating(QQmlDelegateModelItem *modelItem); + static void deleteModelItemLater(QQmlDelegateModelItem *modelItem); + + friend class QQmlTableInstanceModelIncubationTask; +}; + +QT_END_NAMESPACE + +#endif // QQMLTABLEINSTANCEMODEL_P_H diff --git a/src/qmlmodels/qqmltablemodel.cpp b/src/qmlmodels/qqmltablemodel.cpp new file mode 100644 index 0000000000..4a96e7a46b --- /dev/null +++ b/src/qmlmodels/qqmltablemodel.cpp @@ -0,0 +1,1059 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qqmltablemodel_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") + +/*! + \qmltype TableModel + \instantiates QQmlTableModel + \inqmlmodule Qt.labs.qmlmodels + \brief Encapsulates a simple table model. + \since 5.14 + + The TableModel type stores JavaScript/JSON objects as data for a table + model that can be used with \l TableView. It is intended to support + very simple models without requiring the creation of a custom + QAbstractTableModel subclass in C++. + + \snippet qml/tablemodel/fruit-example-simpledelegate.qml file + + The model's initial row data is set with either the \l rows property or by + calling \l appendRow(). Each column in the model is specified by declaring + a \l TableModelColumn instance, where the order of each instance determines + its column index. Once the model's \l Component.completed() signal has been + emitted, the columns and roles will have been established and are then + fixed for the lifetime of the model. + + To access a specific row, the \l getRow() function can be used. + It's also possible to access the model's JavaScript data + directly via the \l rows property, but it is not possible to + modify the model data this way. + + To add new rows, use \l appendRow() and \l insertRow(). To modify + existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and + \l clear(). + + It is also possible to modify the model's data via the delegate, + as shown in the example above: + + \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate + + If the type of the data at the modified role does not match the type of the + data that is set, it will be automatically converted via + \l {QVariant::canConvert()}{QVariant}. + + \section1 Supported Row Data Structures + + TableModel is designed to work with JavaScript/JSON data, where each row + is a simple key-pair object: + + \code + { + // Each property is one cell/column. + checked: false, + amount: 1, + fruitType: "Apple", + fruitName: "Granny Smith", + fruitPrice: 1.50 + }, + // ... + \endcode + + As model manipulation in Qt is done via row and column indices, + and because object keys are unordered, each column must be specified via + TableModelColumn. This allows mapping Qt's built-in roles to any property + in each row object. + + Complex row structures are supported, but with limited functionality. + As TableModel has no way of knowing how each row is structured, + it cannot manipulate it. As a consequence of this, the copy of the + model data that TableModel has stored in \l rows is not kept in sync + with the source data that was set in QML. For these reasons, TableModel + relies on the user to handle simple data manipulation. + + For example, suppose you wanted to have several roles per column. One way + of doing this is to use a data source where each row is an array and each + cell is an object. To use this data source with TableModel, define a + getter and setter: + + \code + TableModel { + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][0].checked } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData } + } + // ... + + rows: [ + [ + { checked: false, checkable: true }, + { amount: 1 }, + { fruitType: "Apple" }, + { fruitName: "Granny Smith" }, + { fruitPrice: 1.50 } + ] + // ... + ] + } + \endcode + + The row above is one example of a complex row. + + \note Row manipulation functions such as \l appendRow(), \l removeRow(), + etc. are not supported when using complex rows. + + \section1 Using DelegateChooser with TableModel + + For most real world use cases, it is recommended to use DelegateChooser + as the delegate of a TableView that uses TableModel. This allows you to + use specific roles in the relevant delegates. For example, the snippet + above can be rewritten to use DelegateChooser like so: + + \snippet qml/tablemodel/fruit-example-delegatechooser.qml file + + The most specific delegates are declared first: the columns at index \c 0 + and \c 1 have \c bool and \c integer data types, so they use a + \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, + respectively. The remaining columns can simply use a + \l [QtQuickControls2]{TextField}, and so that delegate is declared + last as a fallback. + + \sa TableModelColumn, TableView, QAbstractTableModel +*/ + +QQmlTableModel::QQmlTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +QQmlTableModel::~QQmlTableModel() +{ +} + +/*! + \qmlproperty object TableModel::rows + + This property holds the model data in the form of an array of rows: + + \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows + + \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount +*/ +QVariant QQmlTableModel::rows() const +{ + return mRows; +} + +void QQmlTableModel::setRows(const QVariant &rows) +{ + if (rows.userType() != qMetaTypeId()) { + qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); + return; + } + + const QJSValue rowsAsJSValue = rows.value(); + const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); + if (rowsAsVariantList == mRows) { + // No change. + return; + } + + if (!componentCompleted) { + // Store the rows until we can call doSetRows() after component completion. + mRows = rowsAsVariantList; + return; + } + + doSetRows(rowsAsVariantList); +} + +void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList) +{ + Q_ASSERT(componentCompleted); + + // By now, all TableModelColumns should have been set. + if (mColumns.isEmpty()) { + qmlWarning(this) << "No TableModelColumns were set; model will be empty"; + return; + } + + const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); + if (!firstTimeValidRowsHaveBeenSet) { + // This is not the first time rows have been set; validate each one. + for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { + // validateNewRow() expects a QVariant wrapping a QJSValue, so to + // simplify the code, just create one here. + const QVariant row = QVariant::fromValue(rowsAsVariantList.at(rowIndex)); + if (!validateNewRow("setRows()", row, rowIndex, SetRowsOperation)) + return; + } + } + + const int oldRowCount = mRowCount; + const int oldColumnCount = mColumnCount; + + beginResetModel(); + + // We don't clear the column or role data, because a TableModel should not be reused in that way. + // Once it has valid data, its columns and roles are fixed. + mRows = rowsAsVariantList; + mRowCount = mRows.size(); + + // Gather metadata the first time rows is set. + if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) + fetchColumnMetadata(); + + endResetModel(); + + emit rowsChanged(); + + if (mRowCount != oldRowCount) + emit rowCountChanged(); + if (mColumnCount != oldColumnCount) + emit columnCountChanged(); +} + +QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey, + QQmlTableModelColumn *tableModelColumn, int columnIndex) const +{ + const QVariant firstRow = mRows.first(); + ColumnRoleMetadata roleData; + + QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey); + if (columnRoleGetter.isUndefined()) { + // This role is not defined, which is fine; just skip it. + return roleData; + } + + if (columnRoleGetter.isString()) { + // The role is set as a string, so we assume the row is a simple object. + if (firstRow.type() != QVariant::Map) { + qmlWarning(this).quote() << "expected row for role " + << roleNameKey << " of TableModelColumn at index " + << columnIndex << " to be a simple object, but it's " + << firstRow.typeName() << " instead: " << firstRow; + return roleData; + } + const QVariantMap firstRowAsMap = firstRow.toMap(); + const QString rolePropertyName = columnRoleGetter.toString(); + const QVariant roleProperty = firstRowAsMap.value(rolePropertyName); + + roleData.isStringRole = true; + roleData.name = rolePropertyName; + roleData.type = roleProperty.type(); + roleData.typeName = QString::fromLatin1(roleProperty.typeName()); + } else if (columnRoleGetter.isCallable()) { + // The role is provided via a function, which means the row is complex and + // the user needs to provide the data for it. + const auto modelIndex = index(0, columnIndex); + const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(modelIndex); + const QVariant cellData = columnRoleGetter.call(args).toVariant(); + + // We don't know the property name since it's provided through the function. + // roleData.name = ??? + roleData.isStringRole = false; + roleData.type = cellData.type(); + roleData.typeName = QString::fromLatin1(cellData.typeName()); + } else { + // Invalid role. + qmlWarning(this) << "TableModelColumn role for column at index " + << columnIndex << " must be either a string or a function; actual type is: " + << columnRoleGetter.toString(); + } + + return roleData; +} + +void QQmlTableModel::fetchColumnMetadata() +{ + qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:"; + + static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames(); + + // Since we support different data structures at the row level, we require that there + // is a TableModelColumn for each column. + // Collect and cache metadata for each column. This makes data lookup faster. + for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { + QQmlTableModelColumn *column = mColumns.at(columnIndex); + qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":"; + + ColumnMetadata metaData; + const auto builtInRoleKeys = supportedRoleNames.keys(); + for (const int builtInRoleKey : builtInRoleKeys) { + const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey); + ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex); + if (roleData.type == QVariant::Invalid) { + // This built-in role was not specified in this column. + continue; + } + + qCDebug(lcTableModel).nospace() << " - added metadata for built-in role " + << builtInRoleName << " at column index " << columnIndex + << ": name=" << roleData.name << " typeName=" << roleData.typeName + << " type=" << roleData.type; + + // This column now supports this specific built-in role. + metaData.roles.insert(builtInRoleName, roleData); + // Add it if it doesn't already exist. + mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1(); + } + mColumnMetadata.insert(columnIndex, metaData); + } +} + +/*! + \qmlmethod TableModel::appendRow(object row) + + Adds a new row to the end of the model, with the + values (cells) in \a row. + + \code + model.appendRow({ + checkable: true, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) + \endcode + + \sa insertRow(), setRow(), removeRow() +*/ +void QQmlTableModel::appendRow(const QVariant &row) +{ + if (!validateNewRow("appendRow()", row, -1, AppendOperation)) + return; + + doInsert(mRowCount, row); +} + +/*! + \qmlmethod TableModel::clear() + + Removes all rows from the model. + + \sa removeRow() +*/ +void QQmlTableModel::clear() +{ + QQmlEngine *engine = qmlEngine(this); + Q_ASSERT(engine); + setRows(QVariant::fromValue(engine->newArray())); +} + +/*! + \qmlmethod object TableModel::getRow(int rowIndex) + + Returns the row at \a rowIndex in the model. + + Note that this equivalent to accessing the row directly + through the \l rows property: + + \code + Component.onCompleted: { + // These two lines are equivalent. + console.log(model.getRow(0).display); + console.log(model.rows[0].fruitName); + } + \endcode + + \note the returned object cannot be used to modify the contents of the + model; use setRow() instead. + + \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() +*/ +QVariant QQmlTableModel::getRow(int rowIndex) +{ + if (!validateRowIndex("getRow()", "rowIndex", rowIndex)) + return QVariant(); + + return mRows.at(rowIndex); +} + +/*! + \qmlmethod TableModel::insertRow(int rowIndex, object row) + + Adds a new row to the list model at position \a rowIndex, with the + values (cells) in \a row. + + \code + model.insertRow(2, { + checkable: true, checked: false, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) + \endcode + + The \a rowIndex must be to an existing item in the list, or one past + the end of the list (equivalent to \l appendRow()). + + \sa appendRow(), setRow(), removeRow(), rowCount +*/ +void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) +{ + if (!validateNewRow("insertRow()", row, rowIndex)) + return; + + doInsert(rowIndex, row); +} + +void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) +{ + beginInsertRows(QModelIndex(), rowIndex, rowIndex); + + // Adding rowAsVariant.toList() will add each invidual variant in the list, + // which is definitely not what we want. + const QVariant rowAsVariant = row.value().toVariant(); + mRows.insert(rowIndex, rowAsVariant); + ++mRowCount; + + qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index " + << rowIndex << ":\n" << rowAsVariant.toMap(); + + // Gather metadata the first time a row is added. + if (mColumnMetadata.isEmpty()) + fetchColumnMetadata(); + + endInsertRows(); + emit rowCountChanged(); +} + +void QQmlTableModel::classBegin() +{ +} + +void QQmlTableModel::componentComplete() +{ + componentCompleted = true; + + mColumnCount = mColumns.size(); + if (mColumnCount > 0) + emit columnCountChanged(); + + doSetRows(mRows); +} + +/*! + \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) + + Moves \a rows from the index at \a fromRowIndex to the index at + \a toRowIndex. + + The from and to ranges must exist; for example, to move the first 3 items + to the end of the list: + + \code + model.moveRow(0, model.rowCount - 3, 3) + \endcode + + \sa appendRow(), insertRow(), removeRow(), rowCount +*/ +void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) +{ + if (fromRowIndex == toRowIndex) { + qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\""; + return; + } + + if (rows <= 0) { + qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0"; + return; + } + + if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex)) + return; + + if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex)) + return; + + if (fromRowIndex + rows > mRowCount) { + qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex + << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows) + << ", which is greater than rowCount() of " << mRowCount; + return; + } + + if (toRowIndex + rows > mRowCount) { + qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex + << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows) + << ", which is greater than rowCount() of " << mRowCount; + return; + } + + qCDebug(lcTableModel).nospace() << "moving " << rows + << " row(s) from index " << fromRowIndex + << " to index " << toRowIndex; + + // Based on the same call in QQmlListModel::moveRow(). + beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(), + toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); + + // Based on ListModel::moveRow(). + if (fromRowIndex > toRowIndex) { + // Only move forwards - flip if moving backwards. + const int from = fromRowIndex; + const int to = toRowIndex; + fromRowIndex = to; + toRowIndex = to + rows; + rows = from - to; + } + + QVector store; + store.reserve(rows); + for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) + store.append(mRows.at(fromRowIndex + rows + i)); + for (int i = 0; i < rows; ++i) + store.append(mRows.at(fromRowIndex + i)); + for (int i = 0; i < store.size(); ++i) + mRows[fromRowIndex + i] = store[i]; + + qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows; + + endMoveRows(); +} + +/*! + \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) + + Removes the row at \a rowIndex from the model. + + \sa clear(), rowCount +*/ +void QQmlTableModel::removeRow(int rowIndex, int rows) +{ + if (!validateRowIndex("removeRow()", "rowIndex", rowIndex)) + return; + + if (rows <= 0) { + qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero"; + return; + } + + if (rowIndex + rows - 1 >= mRowCount) { + qmlWarning(this) << "removeRow(): \"rows\" " << rows + << " exceeds available rowCount() of " << mRowCount + << " when removing from \"rowIndex\" " << rowIndex; + return; + } + + beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1); + + auto firstIterator = mRows.begin() + rowIndex; + // The "last" argument to erase() is exclusive, so we go one past the last item. + auto lastIterator = firstIterator + rows; + mRows.erase(firstIterator, lastIterator); + mRowCount -= rows; + + endRemoveRows(); + emit rowCountChanged(); + + qCDebug(lcTableModel).nospace() << "removed " << rows + << " items from the model, starting at index " << rowIndex; +} + +/*! + \qmlmethod TableModel::setRow(int rowIndex, object row) + + Changes the row at \a rowIndex in the model with \a row. + + All columns/cells must be present in \c row, and in the correct order. + + \code + model.setRow(0, { + checkable: true, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) + \endcode + + If \a rowIndex is equal to \c rowCount(), then a new row is appended to the + model. Otherwise, \a rowIndex must point to an existing row in the model. + + \sa appendRow(), insertRow(), rowCount +*/ +void QQmlTableModel::setRow(int rowIndex, const QVariant &row) +{ + if (!validateNewRow("setRow()", row, rowIndex)) + return; + + if (rowIndex != mRowCount) { + // Setting an existing row. + mRows[rowIndex] = row; + + // For now we just assume the whole row changed, as it's simpler. + const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0)); + const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1)); + emit dataChanged(topLeftModelIndex, bottomRightModelIndex); + } else { + // Appending a row. + doInsert(rowIndex, row); + } +} + +QQmlListProperty QQmlTableModel::columns() +{ + return QQmlListProperty(this, nullptr, + &QQmlTableModel::columns_append, + &QQmlTableModel::columns_count, + &QQmlTableModel::columns_at, + &QQmlTableModel::columns_clear); +} + +void QQmlTableModel::columns_append(QQmlListProperty *property, + QQmlTableModelColumn *value) +{ + QQmlTableModel *model = static_cast(property->object); + QQmlTableModelColumn *column = qobject_cast(value); + if (column) + model->mColumns.append(column); +} + +int QQmlTableModel::columns_count(QQmlListProperty *property) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.count(); +} + +QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty *property, int index) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.at(index); +} + +void QQmlTableModel::columns_clear(QQmlListProperty *property) +{ + QQmlTableModel *model = static_cast(property->object); + return model->mColumns.clear(); +} + +/*! + \qmlmethod QModelIndex TableModel::index(int row, int column) + + Returns a \l QModelIndex object referencing the given \a row and \a column, + which can be passed to the data() function to get the data from that cell, + or to setData() to edit the contents of that cell. + + \code + import QtQml 2.14 + import Qt.labs.qmlmodels 1.0 + + TableModel { + id: model + + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitPrice" } + + rows: [ + { fruitType: "Apple", fruitPrice: 1.50 }, + { fruitType: "Orange", fruitPrice: 2.50 } + ] + + Component.onCompleted: { + for (var r = 0; r < model.rowCount; ++r) { + console.log("An " + model.data(model.index(r, 0)).display + + " costs " + model.data(model.index(r, 1)).display.toFixed(2)) + } + } + } + \endcode + + \sa {QModelIndex and related Classes in QML}, data() +*/ +// Note: we don't document the parent argument, because you never need it, because +// cells in a TableModel don't have parents. But it is there because this function is an override. +QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const +{ + return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() + ? createIndex(row, column) + : QModelIndex(); +} + +/*! + \qmlproperty int TableModel::rowCount + \readonly + + This read-only property holds the number of rows in the model. + + This value changes whenever rows are added or removed from the model. +*/ +int QQmlTableModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return mRowCount; +} + +/*! + \qmlproperty int TableModel::columnCount + \readonly + + This read-only property holds the number of columns in the model. + + The number of columns is fixed for the lifetime of the model + after the \l rows property is set or \l appendRow() is called for the first + time. +*/ +int QQmlTableModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return mColumnCount; +} + +/*! + \qmlmethod variant TableModel::data(QModelIndex index, string role) + + Returns the data from the table cell at the given \a index belonging to the + given \a role. + + \sa index() +*/ +QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const +{ + const int iRole = mRoleNames.key(role.toUtf8(), -1); + if (iRole >= 0) + return data(index, iRole); + return QVariant(); +} + +QVariant QQmlTableModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + if (row < 0 || row >= rowCount()) + return QVariant(); + + const int column = index.column(); + if (column < 0 || column >= columnCount()) + return QVariant(); + + const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); + const QString roleName = QString::fromUtf8(mRoleNames.value(role)); + if (!columnMetadata.roles.contains(roleName)) { + qmlWarning(this) << "setData(): no role named " << roleName + << " at column index " << column << ". The available roles for that column are: " + << columnMetadata.roles.keys(); + return QVariant(); + } + + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); + if (roleData.isStringRole) { + // We know the data structure, so we can get the data for the user. + const QVariantMap rowData = mRows.at(row).toMap(); + const QString propertyName = columnMetadata.roles.value(roleName).name; + const QVariant value = rowData.value(propertyName); + return value; + } + + // We don't know the data structure, so the user has to modify their data themselves. + // First, find the getter for this column and role. + QJSValue getter = mColumns.at(column)->getterAtRole(roleName); + + // Then, call it and return what it returned. + const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(index); + return getter.call(args).toVariant(); +} + +/*! + \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value) + + Inserts or updates the data field named by \a role in the table cell at the + given \a index with \a value. Returns true if sucessful, false if not. + + \sa index() +*/ +bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) +{ + const int intRole = mRoleNames.key(role.toUtf8(), -1); + if (intRole >= 0) + return setData(index, value, intRole); + return false; +} + +bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int row = index.row(); + if (row < 0 || row >= rowCount()) + return false; + + const int column = index.column(); + if (column < 0 || column >= columnCount()) + return false; + + const QString roleName = QString::fromUtf8(mRoleNames.value(role)); + + qCDebug(lcTableModel).nospace() << "setData() called with index " + << index << ", value " << value << " and role " << roleName; + + // Verify that the role exists for this column. + const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); + if (!columnMetadata.roles.contains(roleName)) { + qmlWarning(this) << "setData(): no role named \"" << roleName + << "\" at column index " << column << ". The available roles for that column are: " + << columnMetadata.roles.keys(); + return false; + } + + // Verify that the type of the value is what we expect. + // If the value set is not of the expected type, we can try to convert it automatically. + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); + QVariant effectiveValue = value; + if (value.type() != roleData.type) { + if (!value.canConvert(int(roleData.type))) { + qmlWarning(this).nospace() << "setData(): the value " << value + << " set at row " << row << " column " << column << " with role " << roleName + << " cannot be converted to " << roleData.typeName; + return false; + } + + if (!effectiveValue.convert(int(roleData.type))) { + qmlWarning(this).nospace() << "setData(): failed converting value " << value + << " set at row " << row << " column " << column << " with role " << roleName + << " to " << roleData.typeName; + return false; + } + } + + if (roleData.isStringRole) { + // We know the data structure, so we can set it for the user. + QVariantMap modifiedRow = mRows.at(row).toMap(); + modifiedRow[roleData.name] = value; + + mRows[row] = modifiedRow; + } else { + // We don't know the data structure, so the user has to modify their data themselves. + auto engine = qmlEngine(this); + auto args = QJSValueList() + // arg 0: modelIndex. + << engine->toScriptValue(index) + // arg 1: cellData. + << engine->toScriptValue(value); + // Do the actual setting. + QJSValue setter = mColumns.at(column)->setterAtRole(roleName); + setter.call(args); + + /* + The chain of events so far: + + - User did e.g.: model.edit = textInput.text + - setData() is called + - setData() calls the setter + (remember that we need to emit the dataChanged() signal, + which is why the user can't just set the data directly in the delegate) + + Now the user's setter function has modified *their* copy of the + data, but *our* copy of the data is old. Imagine the getters and setters looked like this: + + display: function(modelIndex) { return rows[modelIndex.row][1].amount } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } + + We don't know the structure of the user's data, so we can't just do + what we do above for the isStringRole case: + + modifiedRow[column][roleName] = value + + This means that, besides getting the implicit row count when rows is initially set, + our copy of the data is unused when it comes to complex columns. + + Another point to note is that we can't pass rowData in to the getter as a convenience, + because we would be passing in *our* copy of the row, which is not up-to-date. + Since the user already has access to the data, it's not a big deal for them to do: + + display: function(modelIndex) { return rows[modelIndex.row][1].amount } + + instead of: + + display: function(modelIndex, rowData) { return rowData[1].amount } + */ + } + + QVector rolesChanged; + rolesChanged.append(role); + emit dataChanged(index, index, rolesChanged); + + return true; +} + +QHash QQmlTableModel::roleNames() const +{ + return mRoleNames; +} + +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() +{ +} + +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( + bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) : + isStringRole(isStringRole), + name(name), + type(type), + typeName(typeName) +{ +} + +bool QQmlTableModel::ColumnRoleMetadata::isValid() const +{ + return !name.isEmpty(); +} + +bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const +{ + if (!row.canConvert()) { + qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," + << " but got " << row.typeName() << " instead:\n" << row; + return false; + } + + const QJSValue rowAsJSValue = row.value(); + if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { + qmlWarning(this) << functionName << ": expected \"row\" argument " + << "to be an object or array, but got:\n" << rowAsJSValue.toString(); + return false; + } + + return true; +} + +bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, + int rowIndex, NewRowOperationFlag operation) const +{ + if (mColumnMetadata.isEmpty()) { + // There is no column metadata, so we have nothing to validate the row against. + // Rows have to be added before we can gather metadata from them, so just this + // once we'll return true to allow the rows to be added. + return true; + } + + // Don't require each row to be a QJSValue when setting all rows, + // as they won't be; they'll be QVariantMap. + if (operation != SetRowsOperation && !validateRowType(functionName, row)) + return false; + + if (operation == OtherOperation) { + // Inserting/setting. + if (rowIndex < 0) { + qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative"; + return false; + } + + if (rowIndex > mRowCount) { + qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex + << " is greater than rowCount() of " << mRowCount; + return false; + } + } + + const QVariant rowAsVariant = operation == SetRowsOperation + ? row : row.value().toVariant(); + if (rowAsVariant.type() != QVariant::Map) { + qmlWarning(this) << functionName << ": row manipulation functions " + << "do not support complex rows (row index: " << rowIndex << ")"; + return false; + } + + const QVariantMap rowAsMap = rowAsVariant.toMap(); + const int columnCount = rowAsMap.size(); + if (columnCount < mColumnCount) { + qmlWarning(this) << functionName << ": expected " << mColumnCount + << " columns, but only got " << columnCount; + return false; + } + + // We can't validate complex structures, but we can make sure that + // each simple string-based role in each column is correct. + for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { + QQmlTableModelColumn *column = mColumns.at(columnIndex); + const QHash getters = column->getters(); + const auto roleNames = getters.keys(); + const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex); + for (const QString &roleName : roleNames) { + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); + if (!roleData.isStringRole) + continue; + + if (!rowAsMap.contains(roleData.name)) { + qmlWarning(this).quote() << functionName << ": expected a property named " + << roleData.name << " in row at index " << rowIndex << ", but couldn't find one"; + return false; + } + + const QVariant rolePropertyValue = rowAsMap.value(roleData.name); + if (rolePropertyValue.type() != roleData.type) { + qmlWarning(this).quote() << functionName << ": expected the property named " + << roleData.name << " to be of type " << roleData.typeName + << ", but got " << QString::fromLatin1(rolePropertyValue.typeName()) << " instead"; + return false; + } + } + } + + return true; +} + +bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const +{ + if (rowIndex < 0) { + qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative"; + return false; + } + + if (rowIndex >= mRowCount) { + qmlWarning(this) << functionName << ": \"" << argumentName + << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount; + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmltablemodel_p.h b/src/qmlmodels/qqmltablemodel_p.h new file mode 100644 index 0000000000..114b162e5c --- /dev/null +++ b/src/qmlmodels/qqmltablemodel_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QQMLTABLEMODEL_P_H +#define QQMLTABLEMODEL_P_H + +// +// 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. +// + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL) + Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL) + Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL) + Q_PROPERTY(QQmlListProperty columns READ columns CONSTANT FINAL) + Q_INTERFACES(QQmlParserStatus) + Q_CLASSINFO("DefaultProperty", "columns") + +public: + QQmlTableModel(QObject *parent = nullptr); + ~QQmlTableModel() override; + + QVariant rows() const; + void setRows(const QVariant &rows); + + Q_INVOKABLE void appendRow(const QVariant &row); + Q_INVOKABLE void clear(); + Q_INVOKABLE QVariant getRow(int rowIndex); + Q_INVOKABLE void insertRow(int rowIndex, const QVariant &row); + Q_INVOKABLE void moveRow(int fromRowIndex, int toRowIndex, int rows = 1); + Q_INVOKABLE void removeRow(int rowIndex, int rows = 1); + Q_INVOKABLE void setRow(int rowIndex, const QVariant &row); + + QQmlListProperty columns(); + + static void columns_append(QQmlListProperty *property, QQmlTableModelColumn *value); + static int columns_count(QQmlListProperty *property); + static QQmlTableModelColumn *columns_at(QQmlListProperty *property, int index); + static void columns_clear(QQmlListProperty *property); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_INVOKABLE QVariant data(const QModelIndex &index, const QString &role) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_INVOKABLE bool setData(const QModelIndex &index, const QString &role, const QVariant &value); + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; + QHash roleNames() const override; + +Q_SIGNALS: + void columnCountChanged(); + void rowCountChanged(); + void rowsChanged(); + +private: + class ColumnRoleMetadata + { + public: + ColumnRoleMetadata(); + ColumnRoleMetadata(bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName); + + bool isValid() const; + + // If this is false, it's a function role. + bool isStringRole = false; + QString name; + QVariant::Type type = QVariant::Invalid; + QString typeName; + }; + + struct ColumnMetadata + { + // Key = role name that will be made visible to the delegate + // Value = metadata about that role, including actual name in the model data, type, etc. + QHash roles; + }; + + enum NewRowOperationFlag { + OtherOperation, // insert(), set(), etc. + SetRowsOperation, + AppendOperation + }; + + void doSetRows(const QVariantList &rowsAsVariantList); + ColumnRoleMetadata fetchColumnRoleData(const QString &roleNameKey, + QQmlTableModelColumn *tableModelColumn, int columnIndex) const; + void fetchColumnMetadata(); + + bool validateRowType(const char *functionName, const QVariant &row) const; + bool validateNewRow(const char *functionName, const QVariant &row, + int rowIndex, NewRowOperationFlag operation = OtherOperation) const; + bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const; + + void doInsert(int rowIndex, const QVariant &row); + + void classBegin() override; + void componentComplete() override; + + bool componentCompleted = false; + QVariantList mRows; + QList mColumns; + int mRowCount = 0; + int mColumnCount = 0; + // Each entry contains information about the properties of the column at that index. + QVector mColumnMetadata; + // key = property index (0 to number of properties across all columns) + // value = role name + QHash mRoleNames; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTableModel) + +#endif // QQMLTABLEMODEL_P_H diff --git a/src/qmlmodels/qqmltablemodelcolumn.cpp b/src/qmlmodels/qqmltablemodelcolumn.cpp new file mode 100644 index 0000000000..93da0642de --- /dev/null +++ b/src/qmlmodels/qqmltablemodelcolumn.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qqmltablemodelcolumn_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TableModelColumn + \instantiates QQmlTableModelColumn + \inqmlmodule Qt.labs.qmlmodels + \brief Represents a column in a model. + \since 5.14 + + \section1 Supported Roles + + TableModelColumn supports all of \l {Qt::ItemDataRole}{Qt's roles}, + with the exception of \c Qt::InitialSortOrderRole. + + \sa TableModel, TableView +*/ + +static const QString displayRoleName = QStringLiteral("display"); +static const QString decorationRoleName = QStringLiteral("decoration"); +static const QString editRoleName = QStringLiteral("edit"); +static const QString toolTipRoleName = QStringLiteral("toolTip"); +static const QString statusTipRoleName = QStringLiteral("statusTip"); +static const QString whatsThisRoleName = QStringLiteral("whatsThis"); + +static const QString fontRoleName = QStringLiteral("font"); +static const QString textAlignmentRoleName = QStringLiteral("textAlignment"); +static const QString backgroundRoleName = QStringLiteral("background"); +static const QString foregroundRoleName = QStringLiteral("foreground"); +static const QString checkStateRoleName = QStringLiteral("checkState"); + +static const QString accessibleTextRoleName = QStringLiteral("accessibleText"); +static const QString accessibleDescriptionRoleName = QStringLiteral("accessibleDescription"); + +static const QString sizeHintRoleName = QStringLiteral("sizeHint"); + + +QQmlTableModelColumn::QQmlTableModelColumn(QObject *parent) + : QObject(parent) +{ +} + +QQmlTableModelColumn::~QQmlTableModelColumn() +{ +} + +#define DEFINE_ROLE_PROPERTIES(getterGetterName, getterSetterName, getterSignal, setterGetterName, setterSetterName, setterSignal, roleName) \ +QJSValue QQmlTableModelColumn::getterGetterName() const \ +{ \ + return mGetters.value(roleName); \ +} \ +\ +void QQmlTableModelColumn::getterSetterName(const QJSValue &stringOrFunction) \ +{ \ + if (!stringOrFunction.isString() && !stringOrFunction.isCallable()) { \ + qmlWarning(this).quote() << "getter for " << roleName << " must be a function"; \ + return; \ + } \ + if (stringOrFunction.strictlyEquals(decoration())) \ + return; \ +\ + mGetters[roleName] = stringOrFunction; \ + emit decorationChanged(); \ +} \ +\ +QJSValue QQmlTableModelColumn::setterGetterName() const \ +{ \ + return mSetters.value(roleName); \ +} \ +\ +void QQmlTableModelColumn::setterSetterName(const QJSValue &function) \ +{ \ + if (!function.isCallable()) { \ + qmlWarning(this).quote() << "setter for " << roleName << " must be a function"; \ + return; \ + } \ +\ + if (function.strictlyEquals(getSetDisplay())) \ + return; \ +\ + mSetters[roleName] = function; \ + emit setDisplayChanged(); \ +} + +DEFINE_ROLE_PROPERTIES(display, setDisplay, displayChanged, + getSetDisplay, setSetDisplay, setDisplayChanged, displayRoleName) +DEFINE_ROLE_PROPERTIES(decoration, setDecoration, decorationChanged, + getSetDecoration, setSetDecoration, setDecorationChanged, decorationRoleName) +DEFINE_ROLE_PROPERTIES(edit, setEdit, editChanged, + getSetEdit, setSetEdit, setEditChanged, editRoleName) +DEFINE_ROLE_PROPERTIES(toolTip, setToolTip, toolTipChanged, + getSetToolTip, setSetToolTip, setToolTipChanged, toolTipRoleName) +DEFINE_ROLE_PROPERTIES(statusTip, setStatusTip, statusTipChanged, + getSetStatusTip, setSetStatusTip, setStatusTipChanged, statusTipRoleName) +DEFINE_ROLE_PROPERTIES(whatsThis, setWhatsThis, whatsThisChanged, + getSetWhatsThis, setSetWhatsThis, setWhatsThisChanged, whatsThisRoleName) + +DEFINE_ROLE_PROPERTIES(font, setFont, fontChanged, + getSetFont, setSetFont, setFontChanged, fontRoleName) +DEFINE_ROLE_PROPERTIES(textAlignment, setTextAlignment, textAlignmentChanged, + getSetTextAlignment, setSetTextAlignment, setTextAlignmentChanged, textAlignmentRoleName) +DEFINE_ROLE_PROPERTIES(background, setBackground, backgroundChanged, + getSetBackground, setSetBackground, setBackgroundChanged, backgroundRoleName) +DEFINE_ROLE_PROPERTIES(foreground, setForeground, foregroundChanged, + getSetForeground, setSetForeground, setForegroundChanged, foregroundRoleName) +DEFINE_ROLE_PROPERTIES(checkState, setCheckState, checkStateChanged, + getSetCheckState, setSetCheckState, setCheckStateChanged, checkStateRoleName) + +DEFINE_ROLE_PROPERTIES(accessibleText, setAccessibleText, accessibleTextChanged, + getSetAccessibleText, setSetAccessibleText, setAccessibleTextChanged, accessibleTextRoleName) +DEFINE_ROLE_PROPERTIES(accessibleDescription, setAccessibleDescription, accessibleDescriptionChanged, + getSetAccessibleDescription, setSetAccessibleDescription, setAccessibleDescriptionChanged, accessibleDescriptionRoleName) + +DEFINE_ROLE_PROPERTIES(sizeHint, setSizeHint, sizeHintChanged, + getSetSizeHint, setSetSizeHint, setSizeHintChanged, sizeHintRoleName) + +QJSValue QQmlTableModelColumn::getterAtRole(const QString &roleName) +{ + auto it = mGetters.find(roleName); + if (it == mGetters.end()) + return QJSValue(); + return *it; +} + +QJSValue QQmlTableModelColumn::setterAtRole(const QString &roleName) +{ + auto it = mSetters.find(roleName); + if (it == mSetters.end()) + return QJSValue(); + return *it; +} + +const QHash QQmlTableModelColumn::getters() const +{ + return mGetters; +} + +const QHash QQmlTableModelColumn::supportedRoleNames() +{ + QHash names; + names[Qt::DisplayRole] = QLatin1String("display"); + names[Qt::DecorationRole] = QLatin1String("decoration"); + names[Qt::EditRole] = QLatin1String("edit"); + names[Qt::ToolTipRole] = QLatin1String("toolTip"); + names[Qt::StatusTipRole] = QLatin1String("statusTip"); + names[Qt::WhatsThisRole] = QLatin1String("whatsThis"); + names[Qt::FontRole] = QLatin1String("font"); + names[Qt::TextAlignmentRole] = QLatin1String("textAlignment"); + names[Qt::BackgroundRole] = QLatin1String("background"); + names[Qt::ForegroundRole] = QLatin1String("foreground"); + names[Qt::CheckStateRole] = QLatin1String("checkState"); + names[Qt::AccessibleTextRole] = QLatin1String("accessibleText"); + names[Qt::AccessibleDescriptionRole] = QLatin1String("accessibleDescription"); + names[Qt::SizeHintRole] = QLatin1String("sizeHint"); + return names; +} + +QT_END_NAMESPACE diff --git a/src/qmlmodels/qqmltablemodelcolumn_p.h b/src/qmlmodels/qqmltablemodelcolumn_p.h new file mode 100644 index 0000000000..d125f8bb16 --- /dev/null +++ b/src/qmlmodels/qqmltablemodelcolumn_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QQMLTABLEMODELCOLUMN_P_H +#define QQMLTABLEMODELCOLUMN_P_H + +// +// 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. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableModelColumn : public QObject +{ + Q_OBJECT + Q_PROPERTY(QJSValue display READ display WRITE setDisplay NOTIFY displayChanged FINAL) + Q_PROPERTY(QJSValue setDisplay READ getSetDisplay WRITE setSetDisplay NOTIFY setDisplayChanged) + Q_PROPERTY(QJSValue decoration READ decoration WRITE setDecoration NOTIFY decorationChanged FINAL) + Q_PROPERTY(QJSValue setDecoration READ getSetDecoration WRITE setSetDecoration NOTIFY setDecorationChanged FINAL) + Q_PROPERTY(QJSValue edit READ edit WRITE setEdit NOTIFY editChanged FINAL) + Q_PROPERTY(QJSValue setEdit READ getSetEdit WRITE setSetEdit NOTIFY setEditChanged FINAL) + Q_PROPERTY(QJSValue toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged FINAL) + Q_PROPERTY(QJSValue setToolTip READ getSetToolTip WRITE setSetToolTip NOTIFY setToolTipChanged FINAL) + Q_PROPERTY(QJSValue statusTip READ statusTip WRITE setStatusTip NOTIFY statusTipChanged FINAL) + Q_PROPERTY(QJSValue setStatusTip READ getSetStatusTip WRITE setSetStatusTip NOTIFY setStatusTipChanged FINAL) + Q_PROPERTY(QJSValue whatsThis READ whatsThis WRITE setWhatsThis NOTIFY whatsThisChanged FINAL) + Q_PROPERTY(QJSValue setWhatsThis READ getSetWhatsThis WRITE setSetWhatsThis NOTIFY setWhatsThisChanged FINAL) + + Q_PROPERTY(QJSValue font READ font WRITE setFont NOTIFY fontChanged FINAL) + Q_PROPERTY(QJSValue setFont READ getSetFont WRITE setSetFont NOTIFY setFontChanged FINAL) + Q_PROPERTY(QJSValue textAlignment READ textAlignment WRITE setTextAlignment NOTIFY textAlignmentChanged FINAL) + Q_PROPERTY(QJSValue setTextAlignment READ getSetTextAlignment WRITE setSetTextAlignment NOTIFY setTextAlignmentChanged FINAL) + Q_PROPERTY(QJSValue background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QJSValue setBackground READ getSetBackground WRITE setSetBackground NOTIFY setBackgroundChanged FINAL) + Q_PROPERTY(QJSValue foreground READ foreground WRITE setForeground NOTIFY foregroundChanged FINAL) + Q_PROPERTY(QJSValue setForeground READ getSetForeground WRITE setSetForeground NOTIFY setForegroundChanged FINAL) + Q_PROPERTY(QJSValue checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) + Q_PROPERTY(QJSValue setCheckState READ getSetCheckState WRITE setSetCheckState NOTIFY setCheckStateChanged FINAL) + + Q_PROPERTY(QJSValue accessibleText READ accessibleText WRITE setAccessibleText NOTIFY accessibleTextChanged FINAL) + Q_PROPERTY(QJSValue setAccessibleText READ getSetAccessibleText WRITE setSetAccessibleText NOTIFY setAccessibleTextChanged FINAL) + Q_PROPERTY(QJSValue accessibleDescription READ accessibleDescription + WRITE setAccessibleDescription NOTIFY accessibleDescriptionChanged FINAL) + Q_PROPERTY(QJSValue setAccessibleDescription READ getSetAccessibleDescription + WRITE setSetAccessibleDescription NOTIFY setAccessibleDescriptionChanged FINAL) + + Q_PROPERTY(QJSValue sizeHint READ sizeHint WRITE setSizeHint NOTIFY sizeHintChanged FINAL) + Q_PROPERTY(QJSValue setSizeHint READ getSetSizeHint WRITE setSetSizeHint NOTIFY setSizeHintChanged FINAL) + +public: + QQmlTableModelColumn(QObject *parent = nullptr); + ~QQmlTableModelColumn() override; + + QJSValue display() const; + void setDisplay(const QJSValue &stringOrFunction); + QJSValue getSetDisplay() const; + void setSetDisplay(const QJSValue &function); + + QJSValue decoration() const; + void setDecoration(const QJSValue &stringOrFunction); + QJSValue getSetDecoration() const; + void setSetDecoration(const QJSValue &function); + + QJSValue edit() const; + void setEdit(const QJSValue &stringOrFunction); + QJSValue getSetEdit() const; + void setSetEdit(const QJSValue &function); + + QJSValue toolTip() const; + void setToolTip(const QJSValue &stringOrFunction); + QJSValue getSetToolTip() const; + void setSetToolTip(const QJSValue &function); + + QJSValue statusTip() const; + void setStatusTip(const QJSValue &stringOrFunction); + QJSValue getSetStatusTip() const; + void setSetStatusTip(const QJSValue &function); + + QJSValue whatsThis() const; + void setWhatsThis(const QJSValue &stringOrFunction); + QJSValue getSetWhatsThis() const; + void setSetWhatsThis(const QJSValue &function); + + QJSValue font() const; + void setFont(const QJSValue &stringOrFunction); + QJSValue getSetFont() const; + void setSetFont(const QJSValue &function); + + QJSValue textAlignment() const; + void setTextAlignment(const QJSValue &stringOrFunction); + QJSValue getSetTextAlignment() const; + void setSetTextAlignment(const QJSValue &function); + + QJSValue background() const; + void setBackground(const QJSValue &stringOrFunction); + QJSValue getSetBackground() const; + void setSetBackground(const QJSValue &function); + + QJSValue foreground() const; + void setForeground(const QJSValue &stringOrFunction); + QJSValue getSetForeground() const; + void setSetForeground(const QJSValue &function); + + QJSValue checkState() const; + void setCheckState(const QJSValue &stringOrFunction); + QJSValue getSetCheckState() const; + void setSetCheckState(const QJSValue &function); + + QJSValue accessibleText() const; + void setAccessibleText(const QJSValue &stringOrFunction); + QJSValue getSetAccessibleText() const; + void setSetAccessibleText(const QJSValue &function); + + QJSValue accessibleDescription() const; + void setAccessibleDescription(const QJSValue &stringOrFunction); + QJSValue getSetAccessibleDescription() const; + void setSetAccessibleDescription(const QJSValue &function); + + QJSValue sizeHint() const; + void setSizeHint(const QJSValue &stringOrFunction); + QJSValue getSetSizeHint() const; + void setSetSizeHint(const QJSValue &function); + + QJSValue getterAtRole(const QString &roleName); + QJSValue setterAtRole(const QString &roleName); + + const QHash getters() const; + + static const QHash supportedRoleNames(); + +Q_SIGNALS: + void indexChanged(); + void displayChanged(); + void setDisplayChanged(); + void decorationChanged(); + void setDecorationChanged(); + void editChanged(); + void setEditChanged(); + void toolTipChanged(); + void setToolTipChanged(); + void statusTipChanged(); + void setStatusTipChanged(); + void whatsThisChanged(); + void setWhatsThisChanged(); + + void fontChanged(); + void setFontChanged(); + void textAlignmentChanged(); + void setTextAlignmentChanged(); + void backgroundChanged(); + void setBackgroundChanged(); + void foregroundChanged(); + void setForegroundChanged(); + void checkStateChanged(); + void setCheckStateChanged(); + + void accessibleTextChanged(); + void setAccessibleTextChanged(); + void accessibleDescriptionChanged(); + void setAccessibleDescriptionChanged(); + void sizeHintChanged(); + void setSizeHintChanged(); + +private: + int mIndex = -1; + + // We store these in hashes because QQuickTableModel needs string-based lookup in certain situations. + QHash mGetters; + QHash mSetters; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTableModelColumn) + +#endif // QQMLTABLEMODELCOLUMN_P_H diff --git a/src/qmlmodels/qquickpackage.cpp b/src/qmlmodels/qquickpackage.cpp new file mode 100644 index 0000000000..03539d8737 --- /dev/null +++ b/src/qmlmodels/qquickpackage.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qquickpackage_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Package + \instantiates QQuickPackage + \inqmlmodule QtQuick + \ingroup qtquick-views + \brief Specifies a collection of named items. + + The Package type is used in conjunction with + DelegateModel to enable delegates with a shared context + to be provided to multiple views. + + Any item within a Package may be assigned a name via the + \l{Package::name}{Package.name} attached property. + + The example below creates a Package containing two named items; + \e list and \e grid. The third item in the package (the \l Rectangle) is parented to whichever + delegate it should appear in. This allows an item to move + between views. + + \snippet package/Delegate.qml 0 + + These named items are used as the delegates by the two views who + reference the special \l{DelegateModel::parts} property to select + a model which provides the chosen delegate. + + \snippet package/view.qml 0 + + \sa {Qt Quick Examples - Views}, {Qt Quick Demo - Photo Viewer}, {Qt QML} +*/ + +/*! + \qmlattachedproperty string QtQuick::Package::name + This attached property holds the name of an item within a Package. +*/ + + +class QQuickPackagePrivate : public QObjectPrivate +{ +public: + QQuickPackagePrivate() {} + + struct DataGuard : public QQmlGuard + { + DataGuard(QObject *obj, QList *l) : list(l) { (QQmlGuard&)*this = obj; } + QList *list; + void objectDestroyed(QObject *) override { + // we assume priv will always be destroyed after objectDestroyed calls + list->removeOne(*this); + } + }; + + QList dataList; + static void data_append(QQmlListProperty *prop, QObject *o) { + QList *list = static_cast *>(prop->data); + list->append(DataGuard(o, list)); + } + static void data_clear(QQmlListProperty *prop) { + QList *list = static_cast *>(prop->data); + list->clear(); + } + static QObject *data_at(QQmlListProperty *prop, int index) { + QList *list = static_cast *>(prop->data); + return list->at(index); + } + static int data_count(QQmlListProperty *prop) { + QList *list = static_cast *>(prop->data); + return list->count(); + } +}; + +QHash QQuickPackageAttached::attached; + +QQuickPackageAttached::QQuickPackageAttached(QObject *parent) +: QObject(parent) +{ + attached.insert(parent, this); +} + +QQuickPackageAttached::~QQuickPackageAttached() +{ + attached.remove(parent()); +} + +QString QQuickPackageAttached::name() const +{ + return _name; +} + +void QQuickPackageAttached::setName(const QString &n) +{ + _name = n; +} + +QQuickPackage::QQuickPackage(QObject *parent) + : QObject(*(new QQuickPackagePrivate), parent) +{ +} + +QQuickPackage::~QQuickPackage() +{ +} + +QQmlListProperty QQuickPackage::data() +{ + Q_D(QQuickPackage); + return QQmlListProperty(this, &d->dataList, QQuickPackagePrivate::data_append, + QQuickPackagePrivate::data_count, + QQuickPackagePrivate::data_at, + QQuickPackagePrivate::data_clear); +} + +bool QQuickPackage::hasPart(const QString &name) +{ + Q_D(QQuickPackage); + for (int ii = 0; ii < d->dataList.count(); ++ii) { + QObject *obj = d->dataList.at(ii); + QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); + if (a && a->name() == name) + return true; + } + return false; +} + +QObject *QQuickPackage::part(const QString &name) +{ + Q_D(QQuickPackage); + if (name.isEmpty() && !d->dataList.isEmpty()) + return d->dataList.at(0); + + for (int ii = 0; ii < d->dataList.count(); ++ii) { + QObject *obj = d->dataList.at(ii); + QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); + if (a && a->name() == name) + return obj; + } + + if (name == QLatin1String("default") && !d->dataList.isEmpty()) + return d->dataList.at(0); + + return nullptr; +} + +QQuickPackageAttached *QQuickPackage::qmlAttachedProperties(QObject *o) +{ + return new QQuickPackageAttached(o); +} + + + +QT_END_NAMESPACE + +#include "moc_qquickpackage_p.cpp" diff --git a/src/qmlmodels/qquickpackage_p.h b/src/qmlmodels/qquickpackage_p.h new file mode 100644 index 0000000000..122c7fcb30 --- /dev/null +++ b/src/qmlmodels/qquickpackage_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQUICKPACKAGE_H +#define QQUICKPACKAGE_H + +// +// 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. +// + +#include + +QT_BEGIN_NAMESPACE + +class QQuickPackagePrivate; +class QQuickPackageAttached; +class Q_AUTOTEST_EXPORT QQuickPackage : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickPackage) + + Q_CLASSINFO("DefaultProperty", "data") + Q_PROPERTY(QQmlListProperty data READ data) + +public: + QQuickPackage(QObject *parent=nullptr); + virtual ~QQuickPackage(); + + QQmlListProperty data(); + + QObject *part(const QString & = QString()); + bool hasPart(const QString &); + + static QQuickPackageAttached *qmlAttachedProperties(QObject *); +}; + +class QQuickPackageAttached : public QObject +{ +Q_OBJECT +Q_PROPERTY(QString name READ name WRITE setName) +public: + QQuickPackageAttached(QObject *parent); + virtual ~QQuickPackageAttached(); + + QString name() const; + void setName(const QString &n); + + static QHash attached; +private: + QString _name; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPackage) +QML_DECLARE_TYPEINFO(QQuickPackage, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKPACKAGE_H diff --git a/src/qmlmodels/qtqmlmodelsglobal.h b/src/qmlmodels/qtqmlmodelsglobal.h new file mode 100644 index 0000000000..6e6cf299b2 --- /dev/null +++ b/src/qmlmodels/qtqmlmodelsglobal.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QTQMLMODELSGLOBAL_H +#define QTQMLMODELSGLOBAL_H + +#include +#include + +QT_BEGIN_NAMESPACE + +#if !defined(QT_STATIC) +# if defined(QT_BUILD_QMLMODELS_LIB) +# define Q_QMLMODELS_EXPORT Q_DECL_EXPORT +# else +# define Q_QMLMODELS_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QMLMODELS_EXPORT +#endif + +QT_END_NAMESPACE +#endif // QTQMLMODELSGLOBAL_H diff --git a/src/qmlmodels/qtqmlmodelsglobal_p.h b/src/qmlmodels/qtqmlmodelsglobal_p.h new file mode 100644 index 0000000000..145112c9c1 --- /dev/null +++ b/src/qmlmodels/qtqmlmodelsglobal_p.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QTQMLMODELSGLOBAL_P_H +#define QTQMLMODELSGLOBAL_P_H + +// +// 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. +// + +#include +#include +#include + +#define Q_QMLMODELS_PRIVATE_EXPORT Q_QMLMODELS_EXPORT +#define Q_QMLMODELS_AUTOTEST_EXPORT Q_AUTOTEST_EXPORT + +#endif // QTQMLMODELSGLOBAL_P_H diff --git a/src/quick/configure.json b/src/quick/configure.json index 9ec3531ef4..70fe6d2129 100644 --- a/src/quick/configure.json +++ b/src/quick/configure.json @@ -2,7 +2,8 @@ "module": "quick", "depends": [ "qml-private", - "gui-private" + "gui-private", + "qmlmodels-private" ], "testDir": "../../config.tests", diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index ea5b5df9c6..0f1594f904 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -59,9 +59,9 @@ QT_REQUIRE_CONFIG(quick_itemview); #include "qquickitemviewfxitem_p_p.h" #include "qquickitemviewtransition_p.h" #include "qquickflickable_p_p.h" -#include -#include -#include +#include +#include +#include QT_BEGIN_NAMESPACE diff --git a/src/quick/items/qquickrepeater.cpp b/src/quick/items/qquickrepeater.cpp index 805b6fe190..c8a03aff33 100644 --- a/src/quick/items/qquickrepeater.cpp +++ b/src/quick/items/qquickrepeater.cpp @@ -41,7 +41,6 @@ #include "qquickrepeater_p_p.h" #include -#include #include #include diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 5e7e0db154..8f5130fc17 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -42,10 +42,10 @@ #include #include -#include -#include +#include +#include #include -#include +#include #include #include diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index cbf6824278..7f2aee9105 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -54,9 +54,9 @@ #include "qquicktableview_p.h" #include -#include +#include #include -#include +#include #include #include diff --git a/src/quick/quick.pro b/src/quick/quick.pro index 37d2ad1172..700f794af4 100644 --- a/src/quick/quick.pro +++ b/src/quick/quick.pro @@ -1,6 +1,6 @@ TARGET = QtQuick -QT = core-private gui-private qml-private +QT = core-private gui-private qml-private qmlmodels-private qtConfig(qml-network): \ QT_PRIVATE += network diff --git a/src/src.pro b/src/src.pro index 1b2b4ef6f8..bd634247e7 100644 --- a/src/src.pro +++ b/src/src.pro @@ -4,7 +4,9 @@ include($$OUT_PWD/qml/qtqml-config.pri) include($$OUT_PWD/quick/qtquick-config.pri) QT_FOR_CONFIG += qml qml-private quick-private SUBDIRS += \ - qml + qml \ + qmlmodels + qtHaveModule(gui):qtConfig(qml-animation) { SUBDIRS += \ -- cgit v1.2.3