/**************************************************************************** ** ** 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 #include #include #include #include #include "qmlopenmetaobject.h" #include #include "qmllistmodel.h" #include Q_DECLARE_METATYPE(QListModelInterface *) QT_BEGIN_NAMESPACE #define DATA_ROLE_ID 1 #define DATA_ROLE_NAME "data" struct ListInstruction { enum { Push, Pop, Value, Set } type; int dataIdx; }; struct ListModelData { int dataOffset; int instrCount; ListInstruction *instructions() const { return (ListInstruction *)((char *)this + sizeof(ListModelData)); } }; static void dump(ModelNode *node, int ind); /*! \qmlclass ListModel \brief The ListModel element defines a free-form list data source. The ListModel is a simple hierarchy of elements containing data roles. The contents can be defined dynamically, or explicitly in QML: For example: \code ListModel { id: FruitModel ListElement { name: "Apple" cost: 2.45 } ListElement { name: "Orange" cost: 3.25 } ListElement { name: "Banana" cost: 1.95 } } \endcode Roles (properties) must begin with a lower-case letter. The above example defines a ListModel containing three elements, 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 } } } ListView { model: FruitModel delegate: FruitDelegate anchors.fill: parent } \endcode It is possible for roles to contain list data. In the example below we create a list of fruit attributes: \code ListModel { id: FruitModel ListElement { name: "Apple" cost: 2.45 attributes: [ ListElement { description: "Core" }, ListElement { description: "Deciduous" } ] } ListElement { name: "Orange" cost: 3.25 attributes: [ ListElement { description: "Citrus" } ] } ListElement { name: "Banana" cost: 1.95 attributes: [ ListElement { description: "Tropical" }, ListElement { description: "Seedless" } ] } } \endcode The delegate below will list all the fruit attributes: \code Component { id: FruitDelegate Item { width: 200; height: 50 Text { id: Name; text: name } Text { text: '$'+cost; anchors.right: parent.right } Row { anchors.top: Name.bottom spacing: 5 Text { text: "Attributes:" } Repeater { dataSource: attributes Component { Text { text: description } } } } } } \endcode The content of a ListModel may be created and modified using the clear(), append(), and set() methods. For example: \code Component { id: FruitDelegate Item { width: 200; height: 50 Text { text: name } Text { text: '$'+cost; anchors.right: parent.right } // Double the price when clicked. MouseRegion { anchors.fill: parent onClicked: FruitModel.set(index, "cost", cost*2) } } } \endcode When creating content dynamically, note that the set of available properties cannot be changed except by first clearing the model - whatever properties are first added are then the only permitted properties in the model. */ class ModelObject : public QObject { Q_OBJECT public: ModelObject(); void setValue(const QByteArray &name, const QVariant &val) { _mo->setValue(name, val); } private: QmlOpenMetaObject *_mo; }; struct ModelNode { ModelNode(); ~ModelNode(); QList values; QHash properties; QmlListModel *model() { if (!modelCache) { modelCache = new QmlListModel; modelCache->_root = this; } return modelCache; } ModelObject *object() { if (!objectCache) { objectCache = new ModelObject(); QHash::iterator it; for (it = properties.begin(); it != properties.end(); ++it) { if (!(*it)->values.isEmpty()) objectCache->setValue(it.key().toLatin1(), (*it)->values.first()); } } return objectCache; } void setObjectValue(const QScriptValue& valuemap); void setListValue(const QScriptValue& valuelist); void setProperty(const QString& prop, const QVariant& val) { QHash::const_iterator it = properties.find(prop); if (it != properties.end()) { (*it)->values[0] = val; } else { ModelNode *n = new ModelNode; n->values << val; properties.insert(prop,n); } if (objectCache) objectCache->setValue(prop.toLatin1(), val); } QmlListModel *modelCache; ModelObject *objectCache; }; QT_END_NAMESPACE Q_DECLARE_METATYPE(ModelNode *) QT_BEGIN_NAMESPACE void ModelNode::setObjectValue(const QScriptValue& valuemap) { QScriptValueIterator it(valuemap); while (it.hasNext()) { it.next(); ModelNode *value = new ModelNode; QScriptValue v = it.value(); if (v.isArray()) { value->setListValue(v); } else if (v.isObject()) { value->setObjectValue(v); } else { value->values << v.toVariant(); } properties.insert(it.name(),value); } } void ModelNode::setListValue(const QScriptValue& valuelist) { QScriptValueIterator it(valuelist); values.clear(); while (it.hasNext()) { it.next(); ModelNode *value = new ModelNode; QScriptValue v = it.value(); if (v.isArray()) { value->setListValue(v); } else if (v.isObject()) { value->setObjectValue(v); } else { value->values << v.toVariant(); } values.append(qVariantFromValue(value)); } } ModelObject::ModelObject() : _mo(new QmlOpenMetaObject(this)) { } QmlListModel::QmlListModel(QObject *parent) : QListModelInterface(parent), _rolesOk(false), _root(0) { } QmlListModel::~QmlListModel() { delete _root; } void QmlListModel::checkRoles() const { if (_rolesOk || !_root) return; for (int ii = 0; ii < _root->values.count(); ++ii) { ModelNode *node = qvariant_cast(_root->values.at(ii)); if (node) { foreach (const QString &role, node->properties.keys()) addRole(role); } } _rolesOk = true; } void QmlListModel::addRole(const QString &role) const { if (!roleStrings.contains(role)) roleStrings << role; } QList QmlListModel::roles() const { checkRoles(); QList rv; for (int ii = 0; ii < roleStrings.count(); ++ii) rv << ii; return rv; } QString QmlListModel::toString(int role) const { checkRoles(); if (role < roleStrings.count()) return roleStrings.at(role); else return QString(); } QVariant QmlListModel::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(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 QmlListModel::data(int index, const QList &roles) const { checkRoles(); QHash rv; if (index >= count()) return rv; ModelNode *node = qvariant_cast(_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::ConstIterator iter = node->properties.find(roleString); if (iter != node->properties.end()) { ModelNode *row = *iter; rv.insert(roles.at(ii), valueForNode(row)); } } return rv; } /*! \qmlproperty int ListModel::count The number of data entries in the model. */ int QmlListModel::count() const { if (!_root) return 0; return _root->values.count(); } /*! \qmlmethod ListModel::clear() Deletes all content from the model. The properties are cleared such that different properties may be set on subsequent additions. \sa append() remove() */ void QmlListModel::clear() { int cleared = count(); _rolesOk = false; delete _root; _root = 0; roleStrings.clear(); emit itemsRemoved(0,cleared); } /*! \qmlmethod ListModel::remove(int index) Deletes the content at \a index from the model. \sa clear() */ void QmlListModel::remove(int index) { if (_root) { ModelNode *node = qvariant_cast(_root->values.at(index)); _root->values.removeAt(index); if (node) delete node; emit itemsRemoved(index,1); } } /*! \qmlmethod ListModel::insert(index,dict) Adds a new item to the list model at position \a index, with the values in \a dict. \code FruitModel.insert(2, {"cost": 5.95, "name":"Pizza"}) \endcode The \a index must be to an existing item in the list, or one past the end of the list (equivalent to append). \sa set() append() */ void QmlListModel::insert(int index, const QScriptValue& valuemap) { if (!_root) _root = new ModelNode; if (index >= _root->values.count()) { if (index == _root->values.count()) append(valuemap); return; } ModelNode *mn = new ModelNode; mn->setObjectValue(valuemap); _root->values.insert(index,qVariantFromValue(mn)); emit itemsInserted(index,1); } /*! \qmlmethod ListModel::move(from,to,n) Moves \a n items \a from one position \a to another. The from and to ranges must exist; for example, to move the first 3 items to the end of the list: \code FruitModel.move(0,FruitModel.count-3,3) \endcode \sa append() */ void QmlListModel::move(int from, int to, int n) { if (from+n > count() || to+n > count() || n==0 || from==to || from < 0 || to < 0) return; int origfrom=from; // preserve actual move, so any animations are correct int origto=to; int orign=n; if (from > to) { // Only move forwards - flip if backwards moving int tfrom = from; int tto = to; from = tto; to = tto+n; n = tfrom-tto; } if (n==1) { _root->values.move(from,to); } else { QList replaced; int i=0; QVariantList::const_iterator it=_root->values.begin(); it += from+n; for (; ivalues.begin(); it += from; for (; ivalues.begin(); t += from; for (; f != replaced.end(); ++f, ++t) *t = *f; } emit itemsMoved(origfrom,origto,orign); } /*! \qmlmethod ListModel::append(dict) Adds a new item to the end of the list model, with the values in \a dict. \code FruitModel.append({"cost": 5.95, "name":"Pizza"}) \endcode \sa set() remove() */ void QmlListModel::append(const QScriptValue& valuemap) { if (!valuemap.isObject()) { qWarning("ListModel::append: value is not an object"); return; } if (!_root) _root = new ModelNode; ModelNode *mn = new ModelNode; mn->setObjectValue(valuemap); _root->values << qVariantFromValue(mn); emit itemsInserted(count()-1,1); } /*! \qmlmethod ListModel::set(index,dict) Changes the item at \a index in the list model to the values in \a dict. \code FruitModel.set(3, {"cost": 5.95, "name":"Pizza"}) \endcode The \a index must be an element in the list. \sa append() */ void QmlListModel::set(int index, const QScriptValue& valuemap) { if (!_root) _root = new ModelNode; if ( index >= _root->values.count()) { qWarning() << "ListModel::set index out of range:" << index; return; } if (index == _root->values.count()) append(valuemap); else { ModelNode *node = qvariant_cast(_root->values.at(index)); QList roles; node->setObjectValue(valuemap); QScriptValueIterator it(valuemap); while (it.hasNext()) { it.next(); int r = roleStrings.indexOf(it.name()); if (r<0) { r = roleStrings.count(); roleStrings << it.name(); } roles.append(r); } emit itemsChanged(index,1,roles); } } /*! \qmlmethod ListModel::set(index,property,value) Changes the \a property of the item at \a index in the list model to \a value. \code FruitModel.set(3, "cost", 5.95) \endcode The \a index must be an element in the list. \sa append() */ void QmlListModel::set(int index, const QString& property, const QVariant& value) { if (!_root) _root = new ModelNode; if ( index >= _root->values.count()) { qWarning() << "ListModel::set index out of range:" << index; return; } ModelNode *node = qvariant_cast(_root->values.at(index)); int r = roleStrings.indexOf(property); if (r<0) { r = roleStrings.count(); roleStrings << property; } QList roles; roles.append(r); if (node) node->setProperty(property,value); emit itemsChanged(index,1,roles); } class QmlListModelParser : public QmlCustomParser { public: QByteArray compile(const QList &); bool compileProperty(const QmlCustomParserProperty &prop, QList &instr, QByteArray &data); void setCustomData(QObject *, const QByteArray &); }; bool QmlListModelParser::compileProperty(const QmlCustomParserProperty &prop, QList &instr, QByteArray &data) { QList values = prop.assignedValues(); for(int ii = 0; ii < values.count(); ++ii) { const QVariant &value = values.at(ii); if(value.userType() == qMetaTypeId()) { QmlCustomParserNode node = qvariant_cast(value); { ListInstruction li; li.type = ListInstruction::Push; li.dataIdx = -1; instr << li; } QList props = node.properties(); for(int jj = 0; jj < props.count(); ++jj) { const QmlCustomParserProperty &nodeProp = props.at(jj); if (nodeProp.name() == "") { error(nodeProp, QLatin1String("Cannot use default property in ListModel")); return false; } if (nodeProp.name() == "id") { error(nodeProp, QLatin1String("Cannot use reserved \"id\" property in ListModel")); return false; } ListInstruction li; int ref = data.count(); data.append(nodeProp.name()); data.append('\0'); li.type = ListInstruction::Set; li.dataIdx = ref; instr << li; if(!compileProperty(nodeProp, instr, data)) return false; li.type = ListInstruction::Pop; li.dataIdx = -1; instr << li; } { ListInstruction li; li.type = ListInstruction::Pop; li.dataIdx = -1; instr << li; } } else { QmlParser::Variant variant = qvariant_cast(value); int ref = data.count(); QByteArray d = variant.asScript().toLatin1(); d.append('\0'); data.append(d); ListInstruction li; li.type = ListInstruction::Value; li.dataIdx = ref; instr << li; } } return true; } QByteArray QmlListModelParser::compile(const QList &customProps) { QList instr; QByteArray data; for(int ii = 0; ii < customProps.count(); ++ii) { const QmlCustomParserProperty &prop = customProps.at(ii); if(prop.name() != "") { // isn't default property error(prop, QLatin1String("Cannot use default property")); return QByteArray(); } if(!compileProperty(prop, instr, data)) { return QByteArray(); } } int size = sizeof(ListModelData) + instr.count() * sizeof(ListInstruction) + data.count(); QByteArray rv; rv.resize(size); ListModelData *lmd = (ListModelData *)rv.data(); 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; } void QmlListModelParser::setCustomData(QObject *obj, const QByteArray &d) { QmlListModel *rv = static_cast(obj); ModelNode *root = new ModelNode; rv->_root = root; QStack 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; } } } QML_DEFINE_CUSTOM_TYPE(Qt, 4,6, (QT_VERSION&0x00ff00)>>8, ListModel, QmlListModel, QmlListModelParser) // ### FIXME class QmlListElement : public QObject { Q_OBJECT }; QML_DEFINE_TYPE(Qt,4,6,(QT_VERSION&0x00ff00)>>8,ListElement,QmlListElement) 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(node->values.at(ii)); if (subNode) { qWarning().nospace() << indent << "Sub-node " << ii; dump(subNode, ind + 1); } else { qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString(); } } for (QHash::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(values.at(ii)); if (node) { delete node; node = 0; } } if (modelCache) { modelCache->_root = 0/* ==this */; delete modelCache; modelCache = 0; } if (objectCache) { delete objectCache; } } QT_END_NAMESPACE QML_DECLARE_TYPE(QmlListElement) #include "qmllistmodel.moc"