diff options
Diffstat (limited to 'src/qml/util')
-rw-r--r-- | src/qml/util/qqmladaptormodel.cpp | 977 | ||||
-rw-r--r-- | src/qml/util/qqmladaptormodel_p.h | 154 | ||||
-rw-r--r-- | src/qml/util/qqmlchangeset.cpp | 621 | ||||
-rw-r--r-- | src/qml/util/qqmlchangeset_p.h | 167 | ||||
-rw-r--r-- | src/qml/util/qqmllistaccessor.cpp | 138 | ||||
-rw-r--r-- | src/qml/util/qqmllistaccessor_p.h | 74 | ||||
-rw-r--r-- | src/qml/util/qqmllistcompositor.cpp | 1484 | ||||
-rw-r--r-- | src/qml/util/qqmllistcompositor_p.h | 375 | ||||
-rw-r--r-- | src/qml/util/qqmlpropertymap.h | 4 | ||||
-rw-r--r-- | src/qml/util/util.pri | 8 |
10 files changed, 3998 insertions, 4 deletions
diff --git a/src/qml/util/qqmladaptormodel.cpp b/src/qml/util/qqmladaptormodel.cpp new file mode 100644 index 0000000000..5b6ef79338 --- /dev/null +++ b/src/qml/util/qqmladaptormodel.cpp @@ -0,0 +1,977 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmladaptormodel_p.h" + +#include <private/qqmldelegatemodel_p_p.h> +#include <private/qmetaobjectbuilder_p.h> +#include <private/qqmlproperty_p.h> +#include <private/qv8engine_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlAdaptorModelEngineData : public QV8Engine::Deletable +{ +public: + enum + { + Index, + ModelData, + HasModelChildren, + StringCount + }; + + QQmlAdaptorModelEngineData(QV8Engine *engine); + ~QQmlAdaptorModelEngineData(); + + v8::Local<v8::String> index() { return strings->Get(Index)->ToString(); } + v8::Local<v8::String> modelData() { return strings->Get(ModelData)->ToString(); } + v8::Local<v8::String> hasModelChildren() { return strings->Get(HasModelChildren)->ToString(); } + + v8::Persistent<v8::Function> constructorListItem; + v8::Persistent<v8::Array> strings; +}; + +V8_DEFINE_EXTENSION(QQmlAdaptorModelEngineData, engineData) + +static v8::Handle<v8::Value> get_index(v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(data, "Not a valid VisualData object"); + + return v8::Int32::New(data->index); +} + +template <typename T, typename M> static void setModelDataType(QMetaObjectBuilder *builder, M *metaType) +{ + builder->setFlags(QMetaObjectBuilder::DynamicMetaObject); + builder->setClassName(T::staticMetaObject.className()); + builder->setSuperClass(&T::staticMetaObject); + metaType->propertyOffset = T::staticMetaObject.propertyCount(); + metaType->signalOffset = T::staticMetaObject.methodCount(); +} + +static void addProperty(QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName, const QByteArray &propertyType) +{ + builder->addSignal("__" + QByteArray::number(propertyId) + "()"); + QMetaPropertyBuilder property = builder->addProperty( + propertyName, propertyType, propertyId); + property.setWritable(true); +} + +class VDMModelDelegateDataType; + +class QQmlDMCachedModelData : public QQmlDelegateModelItem +{ +public: + QQmlDMCachedModelData( + QQmlDelegateModelItemMetaType *metaType, + VDMModelDelegateDataType *dataType, + int index); + + 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); + bool resolveIndex(const QQmlAdaptorModel &model, int idx); + + static v8::Handle<v8::Value> get_property(v8::Local<v8::String>, const v8::AccessorInfo &info); + static void set_property( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info); + + VDMModelDelegateDataType *type; + QVector<QVariant> cachedData; +}; + +class VDMModelDelegateDataType + : public QQmlRefCount + , public QQmlAdaptorModel::Accessors + , public QAbstractDynamicMetaObject +{ +public: + VDMModelDelegateDataType(QQmlAdaptorModel *model) + : model(model) + , metaObject(0) + , propertyCache(0) + , propertyOffset(0) + , signalOffset(0) + , hasModelData(false) + { + } + + ~VDMModelDelegateDataType() + { + if (propertyCache) + propertyCache->release(); + free(metaObject); + + qPersistentDispose(constructor); + } + + bool notify( + const QQmlAdaptorModel &, + const QList<QQmlDelegateModelItem *> &items, + int index, + int count, + const QVector<int> &roles) const + { + bool changed = roles.isEmpty() && !watchedRoles.isEmpty(); + if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) { + QList<int> roleIds; + foreach (const QByteArray &r, watchedRoles) { + QHash<QByteArray, int>::const_iterator it = roleNames.find(r); + if (it != roleNames.end()) + roleIds << it.value(); + } + const_cast<VDMModelDelegateDataType *>(this)->watchedRoleIds = roleIds; + } + + QVector<int> signalIndexes; + for (int i = 0; i < roles.count(); ++i) { + const int role = roles.at(i); + if (!changed && watchedRoleIds.contains(role)) + changed = true; + + int propertyId = propertyRoles.indexOf(role); + if (propertyId != -1) + signalIndexes.append(propertyId + signalOffset); + } + if (roles.isEmpty()) { + for (int propertyId = 0; propertyId < propertyRoles.count(); ++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), 0); + } + } + return changed; + } + + void replaceWatchedRoles( + QQmlAdaptorModel &, + const QList<QByteArray> &oldRoles, + const QList<QByteArray> &newRoles) const + { + VDMModelDelegateDataType *dataType = const_cast<VDMModelDelegateDataType *>(this); + + dataType->watchedRoleIds.clear(); + foreach (const QByteArray &oldRole, oldRoles) + dataType->watchedRoles.removeOne(oldRole); + dataType->watchedRoles += newRoles; + } + + void initializeConstructor(QQmlAdaptorModelEngineData *const data) + { + constructor = qPersistentNew(v8::ObjectTemplate::New()); + constructor->SetHasExternalResource(true); + constructor->SetAccessor(data->index(), get_index); + + typedef QHash<QByteArray, int>::const_iterator iterator; + for (iterator it = roleNames.constBegin(), end = roleNames.constEnd(); it != end; ++it) { + const int propertyId = propertyRoles.indexOf(it.value()); + const QByteArray &propertyName = it.key(); + + constructor->SetAccessor( + v8::String::New(propertyName.constData(), propertyName.length()), + QQmlDMCachedModelData::get_property, + QQmlDMCachedModelData::set_property, + v8::Int32::New(propertyId)); + } + } + + // QAbstractDynamicMetaObject + + void objectDestroyed(QObject *) + { + release(); + } + + int metaCall(QObject *object, QMetaObject::Call call, int id, void **arguments) + { + return static_cast<QQmlDMCachedModelData *>(object)->metaCall(call, id, arguments); + } + + v8::Persistent<v8::ObjectTemplate> constructor; + QList<int> propertyRoles; + QList<int> watchedRoleIds; + QList<QByteArray> watchedRoles; + QHash<QByteArray, int> roleNames; + QQmlAdaptorModel *model; + QMetaObject *metaObject; + QQmlPropertyCache *propertyCache; + int propertyOffset; + int signalOffset; + bool hasModelData; +}; + +QQmlDMCachedModelData::QQmlDMCachedModelData( + QQmlDelegateModelItemMetaType *metaType, VDMModelDelegateDataType *dataType, int index) + : QQmlDelegateModelItem(metaType, index) + , type(dataType) +{ + if (index == -1) + cachedData.resize(type->hasModelData ? 1 : type->propertyRoles.count()); + + QObjectPrivate::get(this)->metaObject = type; + + type->addref(); + + QQmlData *qmldata = QQmlData::get(this, true); + qmldata->propertyCache = dataType->propertyCache; + qmldata->propertyCache->addref(); +} + +int QQmlDMCachedModelData::metaCall(QMetaObject::Call call, int id, void **arguments) +{ + if (call == QMetaObject::ReadProperty && id >= type->propertyOffset) { + const int propertyIndex = id - type->propertyOffset; + if (index == -1) { + if (!cachedData.isEmpty()) { + *static_cast<QVariant *>(arguments[0]) = cachedData.at( + type->hasModelData ? 0 : propertyIndex); + } + } else if (*type->model) { + *static_cast<QVariant *>(arguments[0]) = value(type->propertyRoles.at(propertyIndex)); + } + return -1; + } else if (call == QMetaObject::WriteProperty && id >= type->propertyOffset) { + const int propertyIndex = id - type->propertyOffset; + if (index == -1) { + const QMetaObject *meta = metaObject(); + if (cachedData.count() > 1) { + cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]); + QMetaObject::activate(this, meta, propertyIndex, 0); + } else if (cachedData.count() == 1) { + cachedData[0] = *static_cast<QVariant *>(arguments[0]); + QMetaObject::activate(this, meta, 0, 0); + QMetaObject::activate(this, meta, 1, 0); + } + } else if (*type->model) { + setValue(type->propertyRoles.at(propertyIndex), *static_cast<QVariant *>(arguments[0])); + } + return -1; + } else { + return qt_metacall(call, id, arguments); + } +} + +void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value) +{ + QHash<QByteArray, int>::iterator it = type->roleNames.find(role.toUtf8()); + if (it != type->roleNames.end()) { + for (int i = 0; i < type->propertyRoles.count(); ++i) { + if (type->propertyRoles.at(i) == *it) { + cachedData[i] = value; + return; + } + } + } +} + +bool QQmlDMCachedModelData::resolveIndex(const QQmlAdaptorModel &, int idx) +{ + if (index == -1) { + Q_ASSERT(idx >= 0); + index = idx; + cachedData.clear(); + emit modelIndexChanged(); + const QMetaObject *meta = metaObject(); + const int propertyCount = type->propertyRoles.count(); + for (int i = 0; i < propertyCount; ++i) + QMetaObject::activate(this, meta, i, 0); + return true; + } else { + return false; + } +} + +v8::Handle<v8::Value> QQmlDMCachedModelData::get_property( + v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(data, "Not a valid VisualData object"); + + QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(data); + const int propertyId = info.Data()->Int32Value(); + if (data->index == -1) { + if (!modelData->cachedData.isEmpty()) { + return data->engine->fromVariant( + modelData->cachedData.at(modelData->type->hasModelData ? 0 : propertyId)); + } + } else if (*modelData->type->model) { + return data->engine->fromVariant( + modelData->value(modelData->type->propertyRoles.at(propertyId))); + } + return v8::Undefined(); +} + +void QQmlDMCachedModelData::set_property( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE_SETTER(data, "Not a valid VisualData object"); + + const int propertyId = info.Data()->Int32Value(); + if (data->index == -1) { + QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(data); + if (!modelData->cachedData.isEmpty()) { + if (modelData->cachedData.count() > 1) { + modelData->cachedData[propertyId] = data->engine->toVariant(value, QVariant::Invalid); + QMetaObject::activate(data, data->metaObject(), propertyId, 0); + } else if (modelData->cachedData.count() == 1) { + modelData->cachedData[0] = data->engine->toVariant(value, QVariant::Invalid); + QMetaObject::activate(data, data->metaObject(), 0, 0); + QMetaObject::activate(data, data->metaObject(), 1, 0); + } + } + } +} + +//----------------------------------------------------------------- +// QAbstractItemModel +//----------------------------------------------------------------- + +class QQmlDMAbstractItemModelData : public QQmlDMCachedModelData +{ + Q_OBJECT + Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT) +public: + QQmlDMAbstractItemModelData( + QQmlDelegateModelItemMetaType *metaType, + VDMModelDelegateDataType *dataType, + int index) + : QQmlDMCachedModelData(metaType, dataType, index) + { + } + + bool hasModelChildren() const + { + if (index >= 0 && *type->model) { + const QAbstractItemModel * const model = type->model->aim(); + return model->hasChildren(model->index(index, 0, type->model->rootIndex)); + } else { + return false; + } + } + + QVariant value(int role) const + { + return type->model->aim()->index(index, 0, type->model->rootIndex).data(role); + } + + void setValue(int role, const QVariant &value) + { + type->model->aim()->setData( + type->model->aim()->index(index, 0, type->model->rootIndex), value, role); + } + + v8::Handle<v8::Value> get() + { + if (type->constructor.IsEmpty()) { + QQmlAdaptorModelEngineData * const data = engineData(engine); + v8::HandleScope handleScope; + v8::Context::Scope contextScope(engine->context()); + type->initializeConstructor(data); + type->constructor->SetAccessor(data->hasModelChildren(), get_hasModelChildren); + } + v8::Local<v8::Object> data = type->constructor->NewInstance(); + data->SetExternalResource(this); + ++scriptRef; + return data; + } + + static v8::Handle<v8::Value> get_hasModelChildren(v8::Local<v8::String>, const v8::AccessorInfo &info) + { + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(data, "Not a valid VisualData object"); + + const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(data)->type->model; + if (data->index >= 0 && *model) { + const QAbstractItemModel * const aim = model->aim(); + return v8::Boolean::New(aim->hasChildren(aim->index(data->index, 0, model->rootIndex))); + } else { + return v8::Boolean::New(false); + } + } +}; + +class VDMAbstractItemModelDataType : public VDMModelDelegateDataType +{ +public: + VDMAbstractItemModelDataType(QQmlAdaptorModel *model) + : VDMModelDelegateDataType(model) + { + } + + int count(const QQmlAdaptorModel &model) const + { + return model.aim()->rowCount(model.rootIndex); + } + + void cleanup(QQmlAdaptorModel &model, QQmlDelegateModel *vdm) const + { + QAbstractItemModel * const aim = model.aim(); + if (aim && vdm) { + QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)), + vdm, SLOT(_q_rowsInserted(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + vdm, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)), + vdm, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), + vdm, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>))); + QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + vdm, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); + QObject::disconnect(aim, SIGNAL(modelReset()), + vdm, SLOT(_q_modelReset())); + QObject::disconnect(aim, SIGNAL(layoutChanged()), + vdm, SLOT(_q_layoutChanged())); + } + + const_cast<VDMAbstractItemModelDataType *>(this)->release(); + } + + QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const + { + QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8()); + if (it != roleNames.end()) { + return model.aim()->index(index, 0, model.rootIndex).data(*it); + } else if (role == QLatin1String("hasModelChildren")) { + return QVariant(model.aim()->hasChildren(model.aim()->index(index, 0, model.rootIndex))); + } else { + return QVariant(); + } + } + + QVariant parentModelIndex(const QQmlAdaptorModel &model) const + { + return model + ? QVariant::fromValue(model.aim()->parent(model.rootIndex)) + : QVariant(); + } + + QVariant modelIndex(const QQmlAdaptorModel &model, int index) const + { + return model + ? QVariant::fromValue(model.aim()->index(index, 0, model.rootIndex)) + : QVariant(); + } + + bool canFetchMore(const QQmlAdaptorModel &model) const + { + return model && model.aim()->canFetchMore(model.rootIndex); + } + + void fetchMore(QQmlAdaptorModel &model) const + { + if (model) + model.aim()->fetchMore(model.rootIndex); + } + + QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &model, + QQmlDelegateModelItemMetaType *metaType, + QQmlEngine *engine, + int index) const + { + VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this); + if (!metaObject) + dataType->initializeMetaType(model, engine); + return new QQmlDMAbstractItemModelData(metaType, dataType, index); + } + + void initializeMetaType(QQmlAdaptorModel &model, QQmlEngine *engine) + { + QMetaObjectBuilder builder; + setModelDataType<QQmlDMAbstractItemModelData>(&builder, this); + + const QByteArray propertyType = QByteArrayLiteral("QVariant"); + const QHash<int, QByteArray> names = model.aim()->roleNames(); + for (QHash<int, QByteArray>::const_iterator it = names.begin(); it != names.end(); ++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 = builder.toMetaObject(); + *static_cast<QMetaObject *>(this) = *metaObject; + propertyCache = new QQmlPropertyCache(engine, metaObject); + } +}; + +//----------------------------------------------------------------- +// QQmlListAccessor +//----------------------------------------------------------------- + +class QQmlDMListAccessorData : public QQmlDelegateModelItem +{ + Q_OBJECT + Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged) +public: + QQmlDMListAccessorData(QQmlDelegateModelItemMetaType *metaType, int index, const QVariant &value) + : QQmlDelegateModelItem(metaType, index) + , cachedData(value) + { + } + + QVariant modelData() const + { + return cachedData; + } + + void setModelData(const QVariant &data) + { + if (index == -1 && data != cachedData) { + cachedData = data; + emit modelDataChanged(); + } + } + + static v8::Handle<v8::Value> get_modelData(v8::Local<v8::String>, const v8::AccessorInfo &info) + { + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(data, "Not a valid VisualData object"); + + return data->engine->fromVariant(static_cast<QQmlDMListAccessorData *>(data)->cachedData); + } + + static void set_modelData(v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info) + { + QQmlDelegateModelItem *data = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE_SETTER(data, "Not a valid VisualData object"); + + static_cast<QQmlDMListAccessorData *>(data)->setModelData( + data->engine->toVariant(value, QVariant::Invalid)); + } + + v8::Handle<v8::Value> get() + { + v8::Local<v8::Object> data = engineData(engine)->constructorListItem->NewInstance(); + data->SetExternalResource(this); + ++scriptRef; + return data; + } + + void setValue(const QString &role, const QVariant &value) + { + if (role == QLatin1String("modelData")) + cachedData = value; + } + + bool resolveIndex(const QQmlAdaptorModel &model, int idx) + { + 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 QQmlAdaptorModel::Accessors +{ +public: + inline VDMListDelegateDataType() {} + + int count(const QQmlAdaptorModel &model) const + { + return model.list.count(); + } + + QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const + { + return role == QLatin1String("modelData") + ? model.list.at(index) + : QVariant(); + } + + QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &model, + QQmlDelegateModelItemMetaType *metaType, + QQmlEngine *, + int index) const + { + return new QQmlDMListAccessorData( + metaType, + index, + index >= 0 && index < model.list.count() ? model.list.at(index) : QVariant()); + } +}; + +//----------------------------------------------------------------- +// QObject +//----------------------------------------------------------------- + +class VDMObjectDelegateDataType; +class QQmlDMObjectData : public QQmlDelegateModelItem, public QQmlAdaptorModelProxyInterface +{ + Q_OBJECT + Q_PROPERTY(QObject *modelData READ modelData CONSTANT) + Q_INTERFACES(QQmlAdaptorModelProxyInterface) +public: + QQmlDMObjectData( + QQmlDelegateModelItemMetaType *metaType, + VDMObjectDelegateDataType *dataType, + int index, + QObject *object); + + QObject *modelData() const { return object; } + QObject *proxiedObject() { return object; } + + QQmlGuard<QObject> object; +}; + +class VDMObjectDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors +{ +public: + QMetaObject *metaObject; + int propertyOffset; + int signalOffset; + bool shared; + QMetaObjectBuilder builder; + + VDMObjectDelegateDataType() + : metaObject(0) + , propertyOffset(0) + , signalOffset(0) + , shared(true) + { + } + + VDMObjectDelegateDataType(const VDMObjectDelegateDataType &type) + : QQmlRefCount() + , QQmlAdaptorModel::Accessors() + , metaObject(0) + , propertyOffset(type.propertyOffset) + , signalOffset(type.signalOffset) + , shared(false) + , builder(type.metaObject, QMetaObjectBuilder::Properties + | QMetaObjectBuilder::Signals + | QMetaObjectBuilder::SuperClass + | QMetaObjectBuilder::ClassName) + { + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + } + + ~VDMObjectDelegateDataType() + { + free(metaObject); + } + + int count(const QQmlAdaptorModel &model) const + { + return model.list.count(); + } + + QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const + { + if (QObject *object = model.list.at(index).value<QObject *>()) + return object->property(role.toUtf8()); + return QVariant(); + } + + QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &model, + QQmlDelegateModelItemMetaType *metaType, + QQmlEngine *, + int index) const + { + VDMObjectDelegateDataType *dataType = const_cast<VDMObjectDelegateDataType *>(this); + if (!metaObject) + dataType->initializeMetaType(model); + return index >= 0 && index < model.list.count() + ? new QQmlDMObjectData(metaType, dataType, index, qvariant_cast<QObject *>(model.list.at(index))) + : 0; + } + + void initializeMetaType(QQmlAdaptorModel &) + { + setModelDataType<QQmlDMObjectData>(&builder, this); + + metaObject = builder.toMetaObject(); + } + + void cleanup(QQmlAdaptorModel &, QQmlDelegateModel *) const + { + const_cast<VDMObjectDelegateDataType *>(this)->release(); + } +}; + +class QQmlDMObjectDataMetaObject : public QAbstractDynamicMetaObject +{ +public: + QQmlDMObjectDataMetaObject(QQmlDMObjectData *data, VDMObjectDelegateDataType *type) + : m_data(data) + , m_type(type) + { + QObjectPrivate *op = QObjectPrivate::get(m_data); + *static_cast<QMetaObject *>(this) = *type->metaObject; + op->metaObject = this; + m_type->addref(); + } + + ~QQmlDMObjectDataMetaObject() + { + m_type->release(); + } + + int metaCall(QMetaObject::Call call, int id, void **arguments) + { + 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, 0); + return -1; + } else { + return m_data->qt_metacall(call, id, arguments); + } + } + + int createProperty(const char *name, const char *) + { + 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()); + } + + if (m_type->metaObject) + free(m_type->metaObject); + m_type->metaObject = m_type->builder.toMetaObject(); + *static_cast<QMetaObject *>(this) = *m_type->metaObject; + + notifierId = previousMethodCount; + for (int i = previousPropertyCount; i < metaObject->propertyCount() - objectPropertyOffset; ++i) { + QMetaProperty property = metaObject->property(i + objectPropertyOffset); + if (property.hasNotifySignal()) { + QQmlPropertyPrivate::connect( + m_data->object, property.notifySignalIndex(), m_data, notifierId); + ++notifierId; + } + } + return propertyIndex + m_type->propertyOffset - objectPropertyOffset; + } + + QQmlDMObjectData *m_data; + VDMObjectDelegateDataType *m_type; +}; + +QQmlDMObjectData::QQmlDMObjectData( + QQmlDelegateModelItemMetaType *metaType, + VDMObjectDelegateDataType *dataType, + int index, + QObject *object) + : QQmlDelegateModelItem(metaType, index) + , object(object) +{ + new QQmlDMObjectDataMetaObject(this, dataType); +} + +//----------------------------------------------------------------- +// QQmlAdaptorModel +//----------------------------------------------------------------- + +static const QQmlAdaptorModel::Accessors qt_vdm_null_accessors; +static const VDMListDelegateDataType qt_vdm_list_accessors; + +QQmlAdaptorModel::Accessors::~Accessors() +{ +} + +QQmlAdaptorModel::QQmlAdaptorModel() + : accessors(&qt_vdm_null_accessors) +{ +} + +QQmlAdaptorModel::~QQmlAdaptorModel() +{ + accessors->cleanup(*this); +} + +void QQmlAdaptorModel::setModel(const QVariant &variant, QQmlDelegateModel *vdm, QQmlEngine *engine) +{ + accessors->cleanup(*this, vdm); + + list.setList(variant, engine); + + if (QObject *object = qvariant_cast<QObject *>(variant)) { + setObject(object); + if (QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(object)) { + accessors = new VDMAbstractItemModelDataType(this); + + qmlobject_connect(model, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + vdm, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int))); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + vdm, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + vdm, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), + vdm, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>))); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + vdm, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(modelReset()), + vdm, QQmlDelegateModel, SLOT(_q_modelReset())); + qmlobject_connect(model, QAbstractItemModel, SIGNAL(layoutChanged()), + vdm, QQmlDelegateModel, SLOT(_q_layoutChanged())); + } else { + accessors = new VDMObjectDelegateDataType; + } + } else if (list.type() == QQmlListAccessor::ListProperty) { + setObject(static_cast<const QQmlListReference *>(variant.constData())->object()); + accessors = new VDMObjectDelegateDataType; + } else if (list.type() != QQmlListAccessor::Invalid) { + Q_ASSERT(list.type() != QQmlListAccessor::Instance); // Should have cast to QObject. + setObject(0); + accessors = &qt_vdm_list_accessors; + } else { + setObject(0); + accessors = &qt_vdm_null_accessors; + } +} + +void QQmlAdaptorModel::invalidateModel(QQmlDelegateModel *vdm) +{ + accessors->cleanup(*this, vdm); + 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; +} + +void QQmlAdaptorModel::objectDestroyed(QObject *) +{ + setModel(QVariant(), 0, 0); +} + +QQmlAdaptorModelEngineData::QQmlAdaptorModelEngineData(QV8Engine *) +{ + strings = qPersistentNew(v8::Array::New(StringCount)); + strings->Set(Index, v8::String::New("index")); + strings->Set(ModelData, v8::String::New("modelData")); + strings->Set(HasModelChildren, v8::String::New("hasModelChildren")); + + v8::Local<v8::FunctionTemplate> listItem = v8::FunctionTemplate::New(); + listItem->InstanceTemplate()->SetHasExternalResource(true); + listItem->InstanceTemplate()->SetAccessor(index(), get_index); + listItem->InstanceTemplate()->SetAccessor( + modelData(), + QQmlDMListAccessorData::get_modelData, + QQmlDMListAccessorData::set_modelData); + constructorListItem = qPersistentNew(listItem->GetFunction()); +} + +QQmlAdaptorModelEngineData::~QQmlAdaptorModelEngineData() +{ + qPersistentDispose(constructorListItem); + qPersistentDispose(strings); +} + +QT_END_NAMESPACE + +#include <qqmladaptormodel.moc> diff --git a/src/qml/util/qqmladaptormodel_p.h b/src/qml/util/qqmladaptormodel_p.h new file mode 100644 index 0000000000..399350d725 --- /dev/null +++ b/src/qml/util/qqmladaptormodel_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLADAPTORMODEL_P_H +#define QQMLADAPTORMODEL_P_H + +#include <QtCore/qabstractitemmodel.h> + +#include "private/qqmllistaccessor_p.h" + +#include <private/qqmlguard_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; + +class QQmlDelegateModel; +class QQmlDelegateModelItem; +class QQmlDelegateModelItemMetaType; + +class QQmlAdaptorModel : public QQmlGuard<QObject> +{ +public: + class Accessors + { + public: + inline Accessors() {} + virtual ~Accessors(); + virtual int count(const QQmlAdaptorModel &) const { return 0; } + virtual void cleanup(QQmlAdaptorModel &, QQmlDelegateModel * = 0) const {} + + virtual QVariant value(const QQmlAdaptorModel &, int, const QString &) const { + return QVariant(); } + + virtual QQmlDelegateModelItem *createItem( + QQmlAdaptorModel &, + QQmlDelegateModelItemMetaType *, + QQmlEngine *, + int) const { return 0; } + + virtual bool notify( + const QQmlAdaptorModel &, + const QList<QQmlDelegateModelItem *> &, + int, + int, + const QVector<int> &) const { return false; } + virtual void replaceWatchedRoles( + QQmlAdaptorModel &, + const QList<QByteArray> &, + const QList<QByteArray> &) 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 {} + }; + + const Accessors *accessors; + QPersistentModelIndex rootIndex; + QQmlListAccessor list; + + QQmlAdaptorModel(); + ~QQmlAdaptorModel(); + + inline QVariant model() const { return list.list(); } + void setModel(const QVariant &variant, QQmlDelegateModel *vdm, QQmlEngine *engine); + void invalidateModel(QQmlDelegateModel *vdm); + + bool isValid() const; + + inline QAbstractItemModel *aim() { return static_cast<QAbstractItemModel *>(object()); } + inline const QAbstractItemModel *aim() const { return static_cast<const QAbstractItemModel *>(object()); } + + inline int count() const { return qMax(0, accessors->count(*this)); } + inline QVariant value(int index, const QString &role) const { + return accessors->value(*this, index, role); } + inline QQmlDelegateModelItem *createItem(QQmlDelegateModelItemMetaType *metaType, QQmlEngine *engine, int index) { + return accessors->createItem(*this, metaType, engine, index); } + inline bool hasProxyObject() const { + return list.type() == QQmlListAccessor::Instance || list.type() == QQmlListAccessor::ListProperty; } + + inline bool notify( + const QList<QQmlDelegateModelItem *> &items, + int index, + int count, + const QVector<int> &roles) const { + return accessors->notify(*this, items, index, count, roles); } + inline void replaceWatchedRoles( + const QList<QByteArray> &oldRoles, const QList<QByteArray> &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 *); +}; + +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 new file mode 100644 index 0000000000..831cb063a5 --- /dev/null +++ b/src/qml/util/qqmlchangeset.cpp @@ -0,0 +1,621 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.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<Insert>() << Insert(index, count)); +} + +/*! + Appends a notification that \a count items were removed at \a index. +*/ + +void QQmlChangeSet::remove(int index, int count) +{ + QVector<Remove> removes; + removes.append(Remove(index, count)); + remove(&removes, 0); +} + +/*! + 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<Remove> removes; + removes.append(Remove(from, count, moveId)); + QVector<Insert> inserts; + inserts.append(Insert(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<Change> changes; + changes.append(Change(index, count)); + change(changes); +} + +/*! + Applies the changes in a \a changeSet to another. +*/ + +void QQmlChangeSet::apply(const QQmlChangeSet &changeSet) +{ + QVector<Remove> r = changeSet.m_removes; + QVector<Insert> i = changeSet.m_inserts; + QVector<Change> 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<Remove> &removes, QVector<Insert> *inserts) +{ + QVector<Remove> r = removes; + remove(&r, inserts); +} + +void QQmlChangeSet::remove(QVector<Remove> *removes, QVector<Insert> *inserts) +{ + int removeCount = 0; + int insertCount = 0; + QVector<Insert>::iterator insert = m_inserts.begin(); + QVector<Change>::iterator change = m_changes.begin(); + QVector<Remove>::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, Remove( + 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, Insert( + 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<Insert>::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, Insert( + 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, Insert( + 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<Remove>::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<Remove>::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<Remove>::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, Remove( + 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<Insert> &inserts) +{ + int insertCount = 0; + QVector<Insert>::iterator insert = m_inserts.begin(); + QVector<Change>::iterator change = m_changes.begin(); + for (QVector<Insert>::const_iterator iit = inserts.begin(); iit != inserts.end(); ++iit) { + if (iit->count == 0) + continue; + int index = iit->index - insertCount; + + Insert current = *iit; + // Accumulate consecutive inserts into a single delta before attempting to insert. + for (QVector<Insert>::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, Insert( + 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<Remove> &removes, const QVector<Insert> &inserts) +{ + QVector<Remove> r = removes; + QVector<Insert> i = inserts; + remove(&r, &i); + insert(i); +} + +/*! + Applies a list of \a changes to a change set. +*/ + +void QQmlChangeSet::change(const QVector<Change> &changes) +{ + QVector<Change> c = changes; + change(&c); +} + +void QQmlChangeSet::change(QVector<Change> *changes) +{ + QVector<Insert>::iterator insert = m_inserts.begin(); + QVector<Change>::iterator change = m_changes.begin(); + for (QVector<Change>::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<Change>::iterator cbegin = change; + QVector<Change>::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("; + foreach (const QQmlChangeSet::Remove &remove, set.removes()) debug << remove; + foreach (const QQmlChangeSet::Insert &insert, set.inserts()) debug << insert; + foreach (const QQmlChangeSet::Change &change, set.changes()) debug << change; + return debug.nospace() << ')'; +} + +/*! + Prints a \a remove to the \a debug stream. +*/ + +QDebug operator <<(QDebug debug, const QQmlChangeSet::Remove &remove) +{ + if (remove.moveId == -1) { + return (debug.nospace() + << "Remove(" << remove.index + << ',' << remove.count + << ')').space(); + } else { + return (debug.nospace() + << "Remove(" << remove.index + << ',' << remove.count + << ',' << remove.moveId + << ',' << remove.offset + << ')').space(); + } +} + +/*! + Prints an \a insert to the \a debug stream. +*/ + +QDebug operator <<(QDebug debug, const QQmlChangeSet::Insert &insert) +{ + if (insert.moveId == -1) { + return (debug.nospace() + << "Insert(" << insert.index + << ',' << insert.count + << ')').space(); + } else { + return (debug.nospace() + << "Insert(" << insert.index + << ',' << insert.count + << ',' << insert.moveId + << ',' << insert.offset + << ')').space(); + } +} + +/*! + 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 new file mode 100644 index 0000000000..acafbd4eec --- /dev/null +++ b/src/qml/util/qqmlchangeset_p.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.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 <QtCore/qdebug.h> +#include <QtCore/qvector.h> +#include <QtQml/private/qtqmlglobal_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QML_PRIVATE_EXPORT QQmlChangeSet +{ +public: + struct MoveKey + { + MoveKey() : moveId(-1), offset(0) {} + MoveKey(int moveId, int offset) : moveId(moveId), offset(offset) {} + int moveId; + int offset; + }; + + struct Change + { + Change() : index(0), count(0), moveId(-1) {} + Change(int index, int count, int moveId = -1, int offset = 0) + : index(index), count(count), moveId(moveId), offset(offset) {} + + int index; + int count; + int moveId; + int 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; } + }; + + + struct Insert : public Change + { + Insert() {} + Insert(int index, int count, int moveId = -1, int offset = 0) + : Change(index, count, moveId, offset) {} + }; + + struct Remove : public Change + { + Remove() {} + Remove(int index, int count, int moveId = -1, int offset = 0) + : Change(index, count, moveId, offset) {} + }; + + QQmlChangeSet(); + QQmlChangeSet(const QQmlChangeSet &changeSet); + ~QQmlChangeSet(); + + QQmlChangeSet &operator =(const QQmlChangeSet &changeSet); + + const QVector<Remove> &removes() const { return m_removes; } + const QVector<Insert> &inserts() const { return m_inserts; } + const QVector<Change> &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<Insert> &inserts); + void remove(const QVector<Remove> &removes, QVector<Insert> *inserts = 0); + void move(const QVector<Remove> &removes, const QVector<Insert> &inserts); + void change(const QVector<Change> &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<Remove> *removes, QVector<Insert> *inserts); + void change(QVector<Change> *changes); + + QVector<Remove> m_removes; + QVector<Insert> m_inserts; + QVector<Change> m_changes; + int m_difference; +}; + +Q_DECLARE_TYPEINFO(QQmlChangeSet::Change, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(QQmlChangeSet::Remove, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(QQmlChangeSet::Insert, 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::Remove &remove); +Q_QML_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet::Insert &insert); +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 new file mode 100644 index 0000000000..2a2bd74a54 --- /dev/null +++ b/src/qml/util/qqmllistaccessor.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmllistaccessor_p.h" + +#include <private/qqmlmetatype_p.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qdebug.h> + +// ### Remove me +#include <private/qqmlengine_p.h> + +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; + + QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):0; + + 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)) { + m_type = Integer; + } else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) || + (enginePrivate && enginePrivate->isQObject(d.userType()))) { + QObject *data = enginePrivate?enginePrivate->toQObject(v):QQmlMetaType::toQObject(v); + d = QVariant::fromValue(data); + m_type = Instance; + } else if (d.userType() == qMetaTypeId<QQmlListReference>()) { + m_type = ListProperty; + } else { + m_type = Instance; + } +} + +int QQmlListAccessor::count() const +{ + switch(m_type) { + case StringList: + return qvariant_cast<QStringList>(d).count(); + case VariantList: + return qvariant_cast<QVariantList>(d).count(); + case ListProperty: + return ((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<QStringList>(d).at(idx)); + case VariantList: + return qvariant_cast<QVariantList>(d).at(idx); + case ListProperty: + return QVariant::fromValue(((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 new file mode 100644 index 0000000000..8f3fb41186 --- /dev/null +++ b/src/qml/util/qqmllistaccessor_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLLISTACCESSOR_H +#define QQMLLISTACCESSOR_H + +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class Q_AUTOTEST_EXPORT QQmlListAccessor +{ +public: + QQmlListAccessor(); + ~QQmlListAccessor(); + + QVariant list() const; + void setList(const QVariant &, QQmlEngine * = 0); + + 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 new file mode 100644 index 0000000000..75d2f67b51 --- /dev/null +++ b/src/qml/util/qqmllistcompositor.cpp @@ -0,0 +1,1484 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmllistcompositor_p.h" + +#include <QtCore/qvarlengtharray.h> + +//#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 VisualDataModel +*/ + +#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<QQmlListCompositor *>(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<Insert> *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<Insert> *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<Insert> *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<Insert> *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<Insert> *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<Remove> *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<Remove> *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<Remove> *removes, + QVector<Insert> *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<Insert> *translatedInsertions, + void *list, + const QVector<QQmlChangeSet::Insert> &insertions, + const QVector<MovedFlags> *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; + } + foreach (const QQmlChangeSet::Insert &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<MovedFlags>::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<Insert> *translatedInsertions) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count > 0); + + QVector<QQmlChangeSet::Insert> insertions; + insertions.append(QQmlChangeSet::Insert(index, count)); + + listItemsInserted(translatedInsertions, list, insertions); +} + +void QQmlListCompositor::listItemsRemoved( + QVector<Remove> *translatedRemovals, + void *list, + QVector<QQmlChangeSet::Remove> *removals, + QVector<QQmlChangeSet::Insert> *insertions, + QVector<MovedFlags> *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<QQmlChangeSet::Remove>::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<QQmlChangeSet::Insert>::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::Remove( + removal->index, -relativeIndex, splitMoveId)); + ++removal; + removal->count -= -relativeIndex; + insertion = insertions->insert(insertion, QQmlChangeSet::Insert( + 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::Remove( + removal->index, removeCount, translatedRemoval.moveId)); + ++removal; + insertion = insertions->insert(insertion, QQmlChangeSet::Insert( + 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<Remove> *translatedRemovals) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count >= 0); + + QVector<QQmlChangeSet::Remove> removals; + removals.append(QQmlChangeSet::Remove(index, count)); + listItemsRemoved(translatedRemovals, list, &removals, 0, 0); +} + +/*! + 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<Remove> *translatedRemovals, + QVector<Insert> *translatedInsertions) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << from << to << count) + Q_ASSERT(count >= 0); + + QVector<QQmlChangeSet::Remove> removals; + QVector<QQmlChangeSet::Insert> insertions; + QVector<MovedFlags> movedFlags; + removals.append(QQmlChangeSet::Remove(from, count, 0)); + insertions.append(QQmlChangeSet::Insert(to, count, 0)); + + listItemsRemoved(translatedRemovals, list, &removals, &insertions, &movedFlags); + listItemsInserted(translatedInsertions, list, insertions, &movedFlags); +} + +void QQmlListCompositor::listItemsChanged( + QVector<Change> *translatedChanges, + void *list, + const QVector<QQmlChangeSet::Change> &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; + } + foreach (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<Change> *translatedChanges) +{ + QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count) + Q_ASSERT(count >= 0); + QVector<QQmlChangeSet::Change> changes; + changes.append(QQmlChangeSet::Change(index, count)); + listItemsChanged(translatedChanges, list, changes); +} + +void QQmlListCompositor::transition( + Group from, + Group to, + QVector<QQmlChangeSet::Remove> *removes, + QVector<QQmlChangeSet::Insert> *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::Remove(it.index[from]- removeCount, it->count)); + removeCount += it->count; + } else if (it != from && it == to) { + inserts->append(QQmlChangeSet::Insert(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 new file mode 100644 index 0000000000..5d87051582 --- /dev/null +++ b/src/qml/util/qqmllistcompositor_p.h @@ -0,0 +1,375 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.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 <QtCore/qglobal.h> +#include <QtCore/qvector.h> + +#include <private/qqmlchangeset_p.h> + +#include <QtCore/qdebug.h> + +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), list(0), index(0), count(0), flags(0) {} + 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; + int index; + int count; + uint flags; + + 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<typename T> T *list() const { return static_cast<T *>(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; + int offset; + Group group; + int groupFlag; + int groupCount; + 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(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(iterator it, int count, uint flags, int moveId = -1) + : Change(it, count, flags, moveId) {} + }; + + struct Remove : public Change + { + Remove() {} + Remove(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<Insert> *inserts = 0); + void insert(Group group, int before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = 0); + iterator insert(iterator before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = 0); + + void setFlags(Group fromGroup, int from, int count, Group group, int flags, QVector<Insert> *inserts = 0); + void setFlags(iterator from, int count, Group group, uint flags, QVector<Insert> *inserts = 0); + void setFlags(Group fromGroup, int from, int count, uint flags, QVector<Insert> *inserts = 0) { + setFlags(fromGroup, from, count, fromGroup, flags, inserts); } + void setFlags(iterator from, int count, uint flags, QVector<Insert> *inserts = 0) { + setFlags(from, count, from.group, flags, inserts); } + + void clearFlags(Group fromGroup, int from, int count, Group group, uint flags, QVector<Remove> *removals = 0); + void clearFlags(iterator from, int count, Group group, uint flags, QVector<Remove> *removals = 0); + void clearFlags(Group fromGroup, int from, int count, uint flags, QVector<Remove> *removals = 0) { + clearFlags(fromGroup, from, count, fromGroup, flags, removals); } + void clearFlags(iterator from, int count, uint flags, QVector<Remove> *removals = 0) { + 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<Remove> *removals = 0, + QVector<Insert> *inserts = 0); + void clear(); + + void listItemsInserted(void *list, int index, int count, QVector<Insert> *inserts); + void listItemsRemoved(void *list, int index, int count, QVector<Remove> *removals); + void listItemsMoved(void *list, int from, int to, int count, QVector<Remove> *removals, QVector<Insert> *inserts); + void listItemsChanged(void *list, int index, int count, QVector<Change> *changes); + + void transition( + Group from, + Group to, + QVector<QQmlChangeSet::Remove> *removes, + QVector<QQmlChangeSet::Insert> *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<Remove> *translatedRemovals, + void *list, + QVector<QQmlChangeSet::Remove> *removals, + QVector<QQmlChangeSet::Insert> *insertions = 0, + QVector<MovedFlags> *movedFlags = 0); + void listItemsInserted( + QVector<Insert> *translatedInsertions, + void *list, + const QVector<QQmlChangeSet::Insert> &insertions, + const QVector<MovedFlags> *movedFlags = 0); + void listItemsChanged( + QVector<Change> *translatedChanges, + void *list, + const QVector<QQmlChangeSet::Change> &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() + : range(0), offset(0), group(Default), groupCount(0) {} +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(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/qqmlpropertymap.h b/src/qml/util/qqmlpropertymap.h index 5c79f08926..f963e9074a 100644 --- a/src/qml/util/qqmlpropertymap.h +++ b/src/qml/util/qqmlpropertymap.h @@ -49,8 +49,6 @@ #include <QtCore/QStringList> #include <QtCore/QVariant> -QT_BEGIN_HEADER - QT_BEGIN_NAMESPACE @@ -100,6 +98,4 @@ private: QT_END_NAMESPACE -QT_END_HEADER - #endif diff --git a/src/qml/util/util.pri b/src/qml/util/util.pri index 3b121ba3cb..a9c5ffe9b7 100644 --- a/src/qml/util/util.pri +++ b/src/qml/util/util.pri @@ -1,5 +1,13 @@ SOURCES += \ + $$PWD/qqmlchangeset.cpp \ + $$PWD/qqmllistaccessor.cpp \ + $$PWD/qqmllistcompositor.cpp \ + $$PWD/qqmladaptormodel.cpp \ $$PWD/qqmlpropertymap.cpp HEADERS += \ + $$PWD/qqmlchangeset_p.h \ + $$PWD/qqmllistaccessor_p.h \ + $$PWD/qqmllistcompositor_p.h \ + $$PWD/qqmladaptormodel_p.h \ $$PWD/qqmlpropertymap.h |