diff options
Diffstat (limited to 'src/declarative/util/qmllistmodel.cpp')
-rw-r--r-- | src/declarative/util/qmllistmodel.cpp | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/src/declarative/util/qmllistmodel.cpp b/src/declarative/util/qmllistmodel.cpp new file mode 100644 index 0000000..992185a --- /dev/null +++ b/src/declarative/util/qmllistmodel.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qdebug.h> +#include <QtCore/qstack.h> +#include <QXmlStreamReader> +#include "qmlcustomparser.h" +#include "qmlopenmetaobject.h" +#include <qmlcontext.h> +#include <qmlbindablevalue.h> +#include "qmllistmodel.h" + +QT_BEGIN_NAMESPACE + +#define DATA_ROLE_ID 1 +#define DATA_ROLE_NAME "data" + +Q_DECLARE_METATYPE(QListModelInterface *); +class QmlListModelPrivate +{ +public: + QmlListModelPrivate(QmlListModel *m) + : q(m), + type(QmlListModel::Invalid), + listModelInterface(0), + singleObject(0), + roleCacheValid(false) + { + } + + void clear() + { + type = QmlListModel::Invalid; + model = QVariant(); + if(listModelInterface) + listModelInterface->disconnect(q); + listModelInterface = 0; + singleObject = 0; + roleCacheValid = false; + roleCache.clear(); + } + + void updateRoleCache() + { + if(roleCacheValid) + return; + + roleCacheValid = true; + if(type == QmlListModel::SingleObject) + roleCache = QmlMetaProperty::properties(singleObject); + } + + QmlListModel *q; + + QmlListModel::ModelType type; + + QVariant model; + QListModelInterface *listModelInterface; + QObject *singleObject; + + bool roleCacheValid; + QStringList roleCache; +}; + +/*! + \qmlclass ListModel QmlListModel + \brief The ListModel element defines a free-form list data source. + + The ListModel is a simple XML heirarchy of items containing data roles. + For example: + + \code + <ListModel id="FruitModel"> + <Fruit> + <name>Apple</name> + <cost>2.45</cost> + <Fruit> + <Fruit> + <name>Orange</name> + <cost>3.25</cost> + </Fruit> + <Fruit> + <name>Banana</name> + <cost>1.95</cost> + </Fruit> + </ListModel> + \endcode + + Elements beginning with a capital are items. Elements beginning + with lower-case are the data roles. The above example defines a + ListModel containing three items, with the roles "name" and "cost". + + The defined model can be used in views such as ListView: + \code + <Component id="FruitDelegate"> + <Item width="200" height="50"> + <Text text="{name}"/> + <Text text="{'$'+cost}" anchors.right="{parent.right}"/> + </Item> + </Component> + + <ListView model="{FruitModel}" delegate="{FruitDelegate}" anchors.fill="{parent}"/> + \endcode +*/ +/*! + \internal + \class QmlListModel +*/ +QmlListModel::QmlListModel(QObject *parent) +: QListModelInterface(parent), d(new QmlListModelPrivate(this)) +{ +} + +QmlListModel::~QmlListModel() +{ + delete d; d = 0; +} + +QmlListModel::ModelType QmlListModel::modelType() const +{ + return d->type; +} + +bool QmlListModel::setModel(const QVariant &model) +{ + d->clear(); + + QListModelInterface *iface = qvariant_cast<QListModelInterface *>(model); + if(iface) { + QObject::connect(iface, SIGNAL(itemsInserted(int,int)), + this, SIGNAL(itemsInserted(int,int))); + QObject::connect(iface, SIGNAL(itemsRemoved(int,int)), + this, SIGNAL(itemsRemoved(int,int))); + QObject::connect(iface, SIGNAL(itemsMoved(int,int,int)), + this, SIGNAL(itemsMoved(int,int,int))); + QObject::connect(iface, SIGNAL(itemsChanged(int,int,QList<int>)), + this, SIGNAL(itemsChanged(int,int,QList<int>))); + d->listModelInterface = iface; + d->type = ListInterface; + d->model = model; + return true; + } + + QObject *object = qvariant_cast<QObject *>(model); + if(object) { + d->singleObject = object; + d->type = SingleObject; + d->model = model; + return true; + } + + if(QmlMetaType::isList(model)) { + d->type = SimpleList; + d->model = model; + return true; + } + + return false; +} + +QVariant QmlListModel::model() const +{ + return d->model; +} + +QList<int> QmlListModel::roles() const +{ + d->updateRoleCache(); + switch(modelType()) { + case Invalid: + return QList<int>(); + case SimpleList: + return QList<int>() << DATA_ROLE_ID; + case ListInterface: + return d->listModelInterface->roles(); + case SingleObject: + { + QList<int> rv; + for(int ii = 0; ii < d->roleCache.count(); ++ii) + rv << ii; + return rv; + } + break; + }; + return QList<int>(); +} + +QString QmlListModel::toString(int role) const +{ + d->updateRoleCache(); + switch(modelType()) { + case Invalid: + return QString(); + case SimpleList: + if(role == DATA_ROLE_ID) + return QLatin1String(DATA_ROLE_NAME); + else + return QString(); + case ListInterface: + return d->listModelInterface->toString(role); + case SingleObject: + if(role >= d->roleCache.count()) + return QString(); + else + return d->roleCache.at(role); + }; + return QString(); +} + +/*! + \qmlproperty int ListModel::count + This property holds the number of items in the list. +*/ +int QmlListModel::count() const +{ + switch(modelType()) { + case Invalid: + return 0; + case SimpleList: + return QmlMetaType::listCount(model()); + case ListInterface: + return d->listModelInterface->count(); + case SingleObject: + return 1; + } + return 0; +} + +QHash<int,QVariant> QmlListModel::data(int index, const QList<int> &roles) const +{ + d->updateRoleCache(); + QHash<int, QVariant> rv; + switch(modelType()) { + case Invalid: + break; + case SimpleList: + if(roles.contains(DATA_ROLE_ID)) + rv.insert(DATA_ROLE_ID, QmlMetaType::listAt(d->model, index)); + break; + case ListInterface: + return d->listModelInterface->data(index, roles); + case SingleObject: + { + for(int ii = 0; ii < roles.count(); ++ii) { + QmlMetaProperty prop(d->singleObject, toString(roles.at(ii))); + rv.insert(roles.at(ii), prop.read()); + } + } + break; + }; + + return rv; +} + + + +struct ModelNode; +class ListModel : public QListModelInterface +{ + Q_OBJECT +public: + ListModel(QObject *parent=0); + + virtual QList<int> roles() const; + virtual QString toString(int role) const; + Q_PROPERTY(int count READ count); + virtual int count() const; + virtual QHash<int,QVariant> data(int index, const QList<int> &roles = (QList<int>())) const; + +private: + QVariant valueForNode(ModelNode *) const; + mutable QStringList roleStrings; + friend class ListModelParser; + friend struct ModelNode; + + void checkRoles() const; + void addRole(const QString &) const; + mutable bool _rolesOk; + ModelNode *_root; +}; + +class ModelObject : public QObject +{ + Q_OBJECT +public: + ModelObject(ModelNode *); + + void setValue(const QByteArray &name, const QVariant &val) + { + _mo->setValue(name, val); + } + +private: + ModelNode *_node; + bool _haveProperties; + QmlOpenMetaObject *_mo; +}; + +struct ModelNode +{ + ModelNode(); + ~ModelNode(); + QString className; + + QList<QVariant> values; + QHash<QString, ModelNode *> properties; + + ListModel *model() { + if(!modelCache) { + modelCache = new ListModel; + modelCache->_root = this; + } + return modelCache; + } + + ModelObject *object() { + if(!objectCache) { + objectCache = new ModelObject(this); + QHash<QString, ModelNode *>::iterator it; + for (it = properties.begin(); it != properties.end(); ++it) { + if (!(*it)->values.isEmpty()) + objectCache->setValue(it.key().toLatin1(), (*it)->values.first()); + } + } + return objectCache; + } + + ListModel *modelCache; + ModelObject *objectCache; +}; +Q_DECLARE_METATYPE(ModelNode *); + +ModelObject::ModelObject(ModelNode *node) +: _node(node), _haveProperties(false), _mo(new QmlOpenMetaObject(this)) +{ +} + +QML_DECLARE_TYPE(ListModel); +QML_DEFINE_TYPE(ListModel,ListModel); +ListModel::ListModel(QObject *parent) +: QListModelInterface(parent), _rolesOk(false), _root(0) +{ +} + +void ListModel::checkRoles() const +{ + if(_rolesOk) + return; + + for(int ii = 0; ii < _root->values.count(); ++ii) { + ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(ii)); + if(node) { + foreach(QString role, node->properties.keys()) + addRole(role); + } + } + + _rolesOk = true; +} + +void ListModel::addRole(const QString &role) const +{ + if(!roleStrings.contains(role)) + roleStrings << role; +} + +QList<int> ListModel::roles() const +{ + checkRoles(); + QList<int> rv; + for(int ii = 0; ii < roleStrings.count(); ++ii) + rv << ii; + return rv; +} + +QString ListModel::toString(int role) const +{ + checkRoles(); + if(role < roleStrings.count()) + return roleStrings.at(role); + else + return QString(); +} + +QVariant ListModel::valueForNode(ModelNode *node) const +{ + QObject *rv = 0; + + if(!node->properties.isEmpty()) { + // Object + rv = node->object(); + } else if(node->values.count() == 0) { + // Invalid + return QVariant(); + } else if(node->values.count() == 1) { + // Value + QVariant &var = node->values[0]; + ModelNode *valueNode = qvariant_cast<ModelNode *>(var); + if(valueNode) { + if(!valueNode->properties.isEmpty()) + rv = valueNode->object(); + else + rv = valueNode->model(); + } else { + return var; + } + } else if(node->values.count() > 1) { + // List + rv = node->model(); + } + + if(rv) + return QVariant::fromValue(rv); + else + return QVariant(); +} + +QHash<int,QVariant> ListModel::data(int index, const QList<int> &roles) const +{ + checkRoles(); + QHash<int, QVariant> rv; + if(index >= count()) + return rv; + + ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index)); + if(!node) + return rv; + + for(int ii = 0; ii < roles.count(); ++ii) { + const QString &roleString = roleStrings.at(roles.at(ii)); + + QHash<QString, ModelNode *>::ConstIterator iter = + node->properties.find(roleString); + if(iter != node->properties.end()) { + ModelNode *row = *iter; + rv.insert(roles.at(ii), valueForNode(row)); + } + } + + return rv; +} + +int ListModel::count() const +{ + if(!_root) return 0; + return _root->values.count(); +} + +struct ListInstruction +{ + enum { Push, Pop, Value, Set } type; + int dataIdx; +}; + +class ListModelParser : public QmlCustomParser +{ +public: + virtual QByteArray compile(QXmlStreamReader& reader, bool *); + virtual QVariant create(const QByteArray &); +}; +QML_DEFINE_CUSTOM_PARSER(ListModel, ListModelParser); + +static void dump(ModelNode *node, int ind) +{ + QByteArray indentBa(ind * 4, ' '); + const char *indent = indentBa.constData(); + + for(int ii = 0; ii < node->values.count(); ++ii) { + ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii)); + if(subNode) { + qWarning().nospace() << indent << "Sub-node " << ii << ": class " << subNode->className; + dump(subNode, ind + 1); + } else { + qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString(); + } + } + + for(QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) { + qWarning().nospace() << indent << "Property " << iter.key() << ":"; + dump(iter.value(), ind + 1); + } +} + +ModelNode::ModelNode() +: modelCache(0), objectCache(0) +{ +} + +ModelNode::~ModelNode() +{ + qDeleteAll(properties); + for(int ii = 0; ii < values.count(); ++ii) { + ModelNode *node = qvariant_cast<ModelNode *>(values.at(ii)); + if(node) { delete node; node = 0; } + } + if(modelCache) { delete modelCache; modelCache = 0; } +} + +struct ListModelData +{ + int dataOffset; + int id; + int instrCount; + ListInstruction *instructions() const { return (ListInstruction *)((char *)this + sizeof(ListModelData)); } +}; + +QByteArray ListModelParser::compile(QXmlStreamReader& reader, bool *ok) +{ + *ok = true; + + QByteArray id; + QByteArray data; + QList<ListInstruction> instr; + int depth=0; + + while(!reader.atEnd() && depth >= 0) { + switch(reader.readNext()) { + case QXmlStreamReader::StartElement: + { + QStringRef name = reader.name(); + bool isType = name.at(0).isUpper(); + + if (isType) { + ListInstruction li; + li.type = ListInstruction::Push; + li.dataIdx = -1; + instr << li; + + for (int i = 0; i < reader.attributes().count(); ++i) { + const QXmlStreamAttribute &attr = reader.attributes().at(i); + QStringRef attrName = attr.name(); + QStringRef attrValue = attr.value(); + + ListInstruction li; + int ref = data.count(); + data.append(attrName.toString().toLatin1()); + data.append('\0'); + li.type = ListInstruction::Set; + li.dataIdx = ref; + instr << li; + + ref = data.count(); + data.append(attrValue.toString().toLatin1()); + data.append('\0'); + li.type = ListInstruction::Value; + li.dataIdx = ref; + instr << li; + + li.type = ListInstruction::Pop; + li.dataIdx = -1; + instr << li; + } + } else { + ListInstruction li; + int ref = data.count(); + data.append(name.toString().toLatin1()); + data.append('\0'); + li.type = ListInstruction::Set; + li.dataIdx = ref; + instr << li; + } + } + ++depth; + break; + case QXmlStreamReader::EndElement: + { + ListInstruction li; + li.type = ListInstruction::Pop; + li.dataIdx = -1; + instr << li; + --depth; + } + break; + case QXmlStreamReader::Characters: + if (!reader.isWhitespace()) { + int ref = data.count(); + QByteArray d = reader.text().toString().toLatin1(); + d.append('\0'); + data.append(d); + + ListInstruction li; + li.type = ListInstruction::Value; + li.dataIdx = ref; + instr << li; + } + break; + + case QXmlStreamReader::Invalid: + case QXmlStreamReader::NoToken: + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::EndDocument: + case QXmlStreamReader::Comment: + case QXmlStreamReader::DTD: + case QXmlStreamReader::EntityReference: + case QXmlStreamReader::ProcessingInstruction: + break; + } + } + + if (reader.hasError()) + *ok = true; + + if (!*ok) + return QByteArray(); + + int size = sizeof(ListModelData) + + instr.count() * sizeof(ListInstruction) + + data.count(); + + QByteArray rv; + rv.resize(size); + + ListModelData *lmd = (ListModelData *)rv.data(); + if(id.count()) + lmd->id = 0; + else + lmd->id = -1; + lmd->dataOffset = sizeof(ListModelData) + + instr.count() * sizeof(ListInstruction); + lmd->instrCount = instr.count(); + for(int ii = 0; ii < instr.count(); ++ii) + lmd->instructions()[ii] = instr.at(ii); + ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count()); + + return rv; +} + +QVariant ListModelParser::create(const QByteArray &d) +{ + ListModel *rv = new ListModel; + ModelNode *root = new ModelNode; + rv->_root = root; + QStack<ModelNode *> nodes; + nodes << root; + + const ListModelData *lmd = (const ListModelData *)d.constData(); + const char *data = ((const char *)lmd) + lmd->dataOffset; + + for(int ii = 0; ii < lmd->instrCount; ++ii) { + const ListInstruction &instr = lmd->instructions()[ii]; + + switch(instr.type) { + case ListInstruction::Push: + { + ModelNode *n = nodes.top(); + ModelNode *n2 = new ModelNode; + n->values << qVariantFromValue(n2); + nodes.push(n2); + } + break; + + case ListInstruction::Pop: + nodes.pop(); + break; + + case ListInstruction::Value: + { + ModelNode *n = nodes.top(); + n->values.append(QByteArray(data + instr.dataIdx)); + } + break; + + case ListInstruction::Set: + { + ModelNode *n = nodes.top(); + ModelNode *n2 = new ModelNode; + n->properties.insert(QLatin1String(data + instr.dataIdx), n2); + nodes.push(n2); + } + break; + } + } + + if(lmd->id != -1) { + QmlContext *ctxt = QmlContext::activeContext(); + ctxt->setContextProperty(QLatin1String(data + lmd->id), rv); + } + + return QVariant::fromValue(rv); +} + +QT_END_NAMESPACE +#include "qmllistmodel.moc" |