diff options
Diffstat (limited to 'src/declarative/util/qmlxmllistmodel.cpp')
-rw-r--r-- | src/declarative/util/qmlxmllistmodel.cpp | 172 |
1 files changed, 142 insertions, 30 deletions
diff --git a/src/declarative/util/qmlxmllistmodel.cpp b/src/declarative/util/qmlxmllistmodel.cpp index 4c153e2..a068094 100644 --- a/src/declarative/util/qmlxmllistmodel.cpp +++ b/src/declarative/util/qmlxmllistmodel.cpp @@ -63,6 +63,8 @@ QT_BEGIN_NAMESPACE +typedef QPair<int, int> QmlXmlListRange; + /*! \qmlclass XmlRole QmlXmlListModelRole \brief The XmlRole element allows you to specify a role for an XmlListModel. @@ -94,6 +96,17 @@ QT_BEGIN_NAMESPACE \endqml */ +/*! + \qmlproperty bool XmlRole::isKey + Defines whether this is a key role. + + Key roles are used to to determine whether a set of values should + be updated or added to the XML list model when XmlListModel::reload() + is called. + + \sa XmlListModel +*/ + class QmlXmlQuery : public QThread { @@ -118,7 +131,6 @@ public: int doQuery(QString query, QString namespaces, QByteArray data, QList<QmlXmlListModelRole *> *roleObjects) { QMutexLocker locker(&m_mutex); - m_modelData.clear(); m_size = 0; m_data = data; m_query = QLatin1String("doc($src)") + query; @@ -140,6 +152,16 @@ public: return m_modelData; } + QList<QmlXmlListRange> insertedItemRanges() { + QMutexLocker locker(&m_mutex); + return m_insertedItemRanges; + } + + QList<QmlXmlListRange> removedItemRanges() { + QMutexLocker locker(&m_mutex); + return m_removedItemRanges; + } + Q_SIGNALS: void queryCompleted(int queryId, int size); @@ -149,13 +171,12 @@ protected: m_mutex.lock(); int queryId = m_queryId; doQueryJob(); - if (m_size > 0) - doSubQueryJob(); + doSubQueryJob(); m_data.clear(); // no longer needed m_mutex.unlock(); m_mutex.lock(); - if (!m_abort && m_size > 0) + if (!m_abort) emit queryCompleted(queryId, m_size); if (!m_restart) m_condition.wait(&m_mutex); @@ -168,6 +189,8 @@ protected: private: void doQueryJob(); void doSubQueryJob(); + void getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const; + void addIndexToRangeList(QList<QmlXmlListRange> *ranges, int index) const; private: QMutex m_mutex; @@ -183,6 +206,9 @@ private: int m_queryId; const QList<QmlXmlListModelRole *> *m_roleObjects; QList<QList<QVariant> > m_modelData; + QStringList m_keysValues; + QList<QmlXmlListRange> m_insertedItemRanges; + QList<QmlXmlListRange> m_removedItemRanges; }; void QmlXmlQuery::doQueryJob() @@ -227,6 +253,40 @@ void QmlXmlQuery::doQueryJob() m_size = count; } +void QmlXmlQuery::getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const +{ + QStringList keysQueries; + for (int i=0; i<m_roleObjects->count(); i++) { + if (m_roleObjects->at(i)->isKey()) + keysQueries << m_roleObjects->at(i)->query(); + } + QString keysQuery; + if (keysQueries.count() == 1) + keysQuery = m_prefix + keysQueries[0]; + else if (keysQueries.count() > 1) + keysQuery = m_prefix + QLatin1String("concat(") + keysQueries.join(QLatin1String(",")) + QLatin1String(")"); + + if (!keysQuery.isEmpty()) { + query->setQuery(keysQuery); + QXmlResultItems resultItems; + query->evaluateTo(&resultItems); + QXmlItem item(resultItems.next()); + while (!item.isNull()) { + values->append(item.toAtomicValue().toString()); + item = resultItems.next(); + } + } +} + +void QmlXmlQuery::addIndexToRangeList(QList<QmlXmlListRange> *ranges, int index) const { + if (ranges->isEmpty()) + ranges->append(qMakePair(index, 1)); + else if (ranges->last().first + ranges->last().second == index) + ranges->last().second += 1; + else + ranges->append(qMakePair(index, 1)); +} + void QmlXmlQuery::doSubQueryJob() { m_modelData.clear(); @@ -237,6 +297,35 @@ void QmlXmlQuery::doSubQueryJob() QXmlQuery subquery; subquery.bindVariable(QLatin1String("inputDocument"), &b); + QStringList keysValues; + getValuesOfKeyRoles(&keysValues, &subquery); + + // See if any values of key roles have been inserted or removed. + m_insertedItemRanges.clear(); + m_removedItemRanges.clear(); + if (m_keysValues.isEmpty()) { + m_insertedItemRanges << qMakePair(0, m_size); + } else { + if (keysValues != m_keysValues) { + QStringList temp; + for (int i=0; i<m_keysValues.count(); i++) { + if (!keysValues.contains(m_keysValues[i])) + addIndexToRangeList(&m_removedItemRanges, i); + else + temp << m_keysValues[i]; + } + + for (int i=0; i<keysValues.count(); i++) { + if (temp.count() == i || keysValues[i] != temp[i]) { + temp.insert(i, keysValues[i]); + addIndexToRangeList(&m_insertedItemRanges, i); + } + } + } + } + m_keysValues = keysValues; + + // Get the new values for each role. //### we might be able to condense even further (query for everything in one go) for (int i = 0; i < m_roleObjects->size(); ++i) { QmlXmlListModelRole *role = m_roleObjects->at(i); @@ -248,13 +337,13 @@ void QmlXmlQuery::doSubQueryJob() continue; } subquery.setQuery(m_prefix + QLatin1String("(let $v := ") + role->query() + QLatin1String(" return if ($v) then ") + role->query() + QLatin1String(" else \"\")")); - QXmlResultItems output3; - subquery.evaluateTo(&output3); - QXmlItem item(output3.next()); + QXmlResultItems resultItems; + subquery.evaluateTo(&resultItems); + QXmlItem item(resultItems.next()); QList<QVariant> resultList; while (!item.isNull()) { resultList << item.toAtomicValue(); //### we used to trim strings - item = output3.next(); + item = resultItems.next(); } //### should warn here if things have gone wrong. while (resultList.count() < m_size) @@ -357,25 +446,40 @@ void QmlXmlListModelPrivate::clear_role(QmlListProperty<QmlXmlListModelRole> *li /*! \qmlclass XmlListModel QmlXmlListModel - \brief The XmlListModel element allows you to specify a model using XPath expressions. + \brief The XmlListModel element is used to specify a model using XPath expressions. - XmlListModel allows you to construct a model from XML data that can then be used as a data source - for the view classes (ListView, PathView, GridView) and any other classes that interact with model - data (like Repeater). + XmlListModel is used to create a model from XML data that can be used as a data source + for the view classes (such as ListView, PathView, GridView) and other classes that interact with model + data (such as Repeater). - The following is an example of a model containing news from a Yahoo RSS feed: + Here is an example of a model containing news from a Yahoo RSS feed: \qml XmlListModel { id: feedModel source: "http://rss.news.yahoo.com/rss/oceania" query: "/rss/channel/item" XmlRole { name: "title"; query: "title/string()" } - XmlRole { name: "link"; query: "link/string()" } + XmlRole { name: "pubDate"; query: "pubDate/string()" } XmlRole { name: "description"; query: "description/string()" } } \endqml - \note The model is currently static, so the above is really just a snapshot of an RSS feed. To force a - reload of the entire model, you can call the reload function. + + You can also define certain roles as "keys" so that the model only adds data + that contains new values for these keys when reload() is called. + + For example, if the roles above were defined like this: + + \qml + XmlRole { name: "title"; query: "title/string()"; isKey: true } + XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true } + \endqml + + Then when reload() is called, the model will only add new items with a + "title" and "pubDate" value combination that is not already present in + the model. + + This is useful to provide incremental updates and avoid repainting an + entire model in a view. */ QmlXmlListModel::QmlXmlListModel(QObject *parent) @@ -491,9 +595,9 @@ void QmlXmlListModel::setXml(const QString &xml) } /*! - \qmlproperty url XmlListModel::query + \qmlproperty string XmlListModel::query An absolute XPath query representing the base query for the model items. The query should start with - a '/' or '//'. + '/' or '//'. */ QString QmlXmlListModel::query() const { @@ -584,8 +688,13 @@ void QmlXmlListModel::componentComplete() /*! \qmlmethod XmlListModel::reload() - Reloads the model. All the existing model data will be removed, and the model - will be rebuilt from scratch. + Reloads the model. + + If no key roles have been specified, all existing model + data is removed, and the model is rebuilt from scratch. + + Otherwise, items are only added if the model does not already + contain items with matching key role values. */ void QmlXmlListModel::reload() { @@ -597,12 +706,8 @@ void QmlXmlListModel::reload() d->qmlXmlQuery.abort(); d->queryId = -1; - //clear existing data - int count = d->size; - d->size = 0; - d->data.clear(); - if (count > 0) - emit itemsRemoved(0, count); + if (d->size < 0) + d->size = 0; if (d->src.isEmpty() && d->xml.isEmpty()) return; @@ -669,12 +774,19 @@ void QmlXmlListModel::queryCompleted(int id, int size) Q_D(QmlXmlListModel); if (id != d->queryId) return; + bool sizeChanged = size != d->size; d->size = size; - if (size > 0) { - d->data = d->qmlXmlQuery.modelData(); - emit itemsInserted(0, d->size); + d->data = d->qmlXmlQuery.modelData(); + + QList<QmlXmlListRange> removed = d->qmlXmlQuery.removedItemRanges(); + for (int i=0; i<removed.count(); i++) + emit itemsRemoved(removed[i].first, removed[i].second); + QList<QmlXmlListRange> inserted = d->qmlXmlQuery.insertedItemRanges(); + for (int i=0; i<inserted.count(); i++) + emit itemsInserted(inserted[i].first, inserted[i].second); + + if (sizeChanged) emit countChanged(); - } } QT_END_NAMESPACE |