diff -r 93b982ccede2 -r 5daf16870df6 src/declarative/util/qdeclarativexmllistmodel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/declarative/util/qdeclarativexmllistmodel.cpp Thu Jul 22 16:41:55 2010 +0100 @@ -0,0 +1,1025 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (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 Technology Preview License Agreement accompanying +** this package. +** +** 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.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qdeclarativexmllistmodel_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_DECLARE_METATYPE(QDeclarativeXmlQueryResult) + +QT_BEGIN_NAMESPACE + + +typedef QPair QDeclarativeXmlListRange; + +#define XMLLISTMODEL_CLEAR_ID 0 + +/*! + \qmlclass XmlRole QDeclarativeXmlListModelRole + \since 4.7 + \brief The XmlRole element allows you to specify a role for an XmlListModel. + + \sa {QtDeclarative} +*/ + +/*! + \qmlproperty string XmlRole::name + The name for the role. This name is used to access the model data for this role from Qml. + + \qml + XmlListModel { + id: xmlModel + ... + XmlRole { name: "title"; query: "title/string()" } + } + + ListView { + model: xmlModel + Text { text: title } + } + \endqml +*/ + +/*! + \qmlproperty string XmlRole::query + The relative XPath query for this role. The query should not start with a '/' (i.e. it must be + relative). + + \qml + XmlRole { name: "title"; query: "title/string()" } + \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 +*/ + +struct XmlQueryJob +{ + int queryId; + QByteArray data; + QString query; + QString namespaces; + QStringList roleQueries; + QList roleQueryErrorId; // the ptr to send back if there is an error + QStringList keyRoleQueries; + QStringList keyRoleResultsCache; +}; + +class QDeclarativeXmlQuery : public QThread +{ + Q_OBJECT +public: + QDeclarativeXmlQuery(QObject *parent=0) + : QThread(parent), m_quit(false), m_abortQueryId(-1), m_queryIds(XMLLISTMODEL_CLEAR_ID + 1) { + qRegisterMetaType("QDeclarativeXmlQueryResult"); + } + + ~QDeclarativeXmlQuery() { + m_mutex.lock(); + m_quit = true; + m_condition.wakeOne(); + m_mutex.unlock(); + + wait(); + } + + void abort(int id) { + QMutexLocker locker(&m_mutex); + m_abortQueryId = id; + } + + int doQuery(QString query, QString namespaces, QByteArray data, QList *roleObjects, QStringList keyRoleResultsCache) { + QMutexLocker locker(&m_mutex); + + XmlQueryJob job; + job.queryId = m_queryIds; + job.data = data; + job.query = QLatin1String("doc($src)") + query; + job.namespaces = namespaces; + job.keyRoleResultsCache = keyRoleResultsCache; + + for (int i=0; icount(); i++) { + if (!roleObjects->at(i)->isValid()) { + job.roleQueries << QString(); + continue; + } + job.roleQueries << roleObjects->at(i)->query(); + job.roleQueryErrorId << static_cast(roleObjects->at(i)); + if (roleObjects->at(i)->isKey()) + job.keyRoleQueries << job.roleQueries.last(); + } + m_jobs.enqueue(job); + m_queryIds++; + + if (!isRunning()) + start(); + else + m_condition.wakeOne(); + return job.queryId; + } + +Q_SIGNALS: + void queryCompleted(const QDeclarativeXmlQueryResult &); + void error(void*, const QString&); + +protected: + void run() { + while (!m_quit) { + m_mutex.lock(); + doQueryJob(); + doSubQueryJob(); + m_mutex.unlock(); + + m_mutex.lock(); + const XmlQueryJob &job = m_jobs.dequeue(); + if (m_abortQueryId != job.queryId) { + QDeclarativeXmlQueryResult r; + r.queryId = job.queryId; + r.size = m_size; + r.data = m_modelData; + r.inserted = m_insertedItemRanges; + r.removed = m_removedItemRanges; + r.keyRoleResultsCache = job.keyRoleResultsCache; + emit queryCompleted(r); + } + if (m_jobs.isEmpty()) + m_condition.wait(&m_mutex); + m_abortQueryId = -1; + m_mutex.unlock(); + } + } + +private: + void doQueryJob(); + void doSubQueryJob(); + void getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const; + void addIndexToRangeList(QList *ranges, int index) const; + +private: + QMutex m_mutex; + QWaitCondition m_condition; + QQueue m_jobs; + bool m_quit; + int m_abortQueryId; + QString m_prefix; + int m_size; + int m_queryIds; + QList > m_modelData; + QList m_insertedItemRanges; + QList m_removedItemRanges; +}; + +Q_GLOBAL_STATIC(QDeclarativeXmlQuery, globalXmlQuery) + +void QDeclarativeXmlQuery::doQueryJob() +{ + Q_ASSERT(!m_jobs.isEmpty()); + XmlQueryJob &job = m_jobs.head(); + + QString r; + QXmlQuery query; + QBuffer buffer(&job.data); + buffer.open(QIODevice::ReadOnly); + query.bindVariable(QLatin1String("src"), &buffer); + query.setQuery(job.namespaces + job.query); + query.evaluateTo(&r); + + //always need a single root element + QByteArray xml = "\n" + r.toUtf8() + ""; + QBuffer b(&xml); + b.open(QIODevice::ReadOnly); + + QString namespaces = QLatin1String("declare namespace dummy=\"http://qtsotware.com/dummy\";\n") + job.namespaces; + QString prefix = QLatin1String("doc($inputDocument)/dummy:items") + + job.query.mid(job.query.lastIndexOf(QLatin1Char('/'))); + + //figure out how many items we are dealing with + int count = -1; + { + QXmlResultItems result; + QXmlQuery countquery; + countquery.bindVariable(QLatin1String("inputDocument"), &b); + countquery.setQuery(namespaces + QLatin1String("count(") + prefix + QLatin1Char(')')); + countquery.evaluateTo(&result); + QXmlItem item(result.next()); + if (item.isAtomicValue()) + count = item.toAtomicValue().toInt(); + } + + job.data = xml; + m_prefix = namespaces + prefix + QLatin1Char('/'); + m_size = 0; + if (count > 0) + m_size = count; +} + +void QDeclarativeXmlQuery::getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const +{ + Q_ASSERT(!m_jobs.isEmpty()); + + const QStringList &keysQueries = m_jobs.head().keyRoleQueries; + 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 QDeclarativeXmlQuery::addIndexToRangeList(QList *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 QDeclarativeXmlQuery::doSubQueryJob() +{ + Q_ASSERT(!m_jobs.isEmpty()); + XmlQueryJob &job = m_jobs.head(); + m_modelData.clear(); + + QBuffer b(&job.data); + b.open(QIODevice::ReadOnly); + + QXmlQuery subquery; + subquery.bindVariable(QLatin1String("inputDocument"), &b); + + QStringList keyRoleResults; + getValuesOfKeyRoles(&keyRoleResults, &subquery); + + // See if any values of key roles have been inserted or removed. + + m_insertedItemRanges.clear(); + m_removedItemRanges.clear(); + if (job.keyRoleResultsCache.isEmpty()) { + m_insertedItemRanges << qMakePair(0, m_size); + } else { + if (keyRoleResults != job.keyRoleResultsCache) { + QStringList temp; + for (int i=0; i resultList; + if (!queries[i].isEmpty()) { + subquery.setQuery(m_prefix + QLatin1String("(let $v := ") + queries[i] + QLatin1String(" return if ($v) then ") + queries[i] + QLatin1String(" else \"\")")); + if (subquery.isValid()) { + QXmlResultItems resultItems; + subquery.evaluateTo(&resultItems); + QXmlItem item(resultItems.next()); + while (!item.isNull()) { + resultList << item.toAtomicValue(); //### we used to trim strings + item = resultItems.next(); + } + } else { + emit error(job.roleQueryErrorId.at(i), queries[i]); + } + } + //### should warn here if things have gone wrong. + while (resultList.count() < m_size) + resultList << QVariant(); + m_modelData << resultList; + b.seek(0); + } + + //this method is much slower, but works better for incremental loading + /*for (int j = 0; j < m_size; ++j) { + QList resultList; + for (int i = 0; i < m_roleObjects->size(); ++i) { + QDeclarativeXmlListModelRole *role = m_roleObjects->at(i); + subquery.setQuery(m_prefix.arg(j+1) + role->query()); + if (role->isStringList()) { + QStringList data; + subquery.evaluateTo(&data); + resultList << QVariant(data); + //qDebug() << data; + } else { + QString s; + subquery.evaluateTo(&s); + if (role->isCData()) { + //un-escape + s.replace(QLatin1String("<"), QLatin1String("<")); + s.replace(QLatin1String(">"), QLatin1String(">")); + s.replace(QLatin1String("&"), QLatin1String("&")); + } + resultList << s.trimmed(); + //qDebug() << s; + } + b.seek(0); + } + m_modelData << resultList; + }*/ +} + +class QDeclarativeXmlListModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QDeclarativeXmlListModel) +public: + QDeclarativeXmlListModelPrivate() + : isComponentComplete(true), size(-1), highestRole(Qt::UserRole) + , reply(0), status(QDeclarativeXmlListModel::Null), progress(0.0) + , queryId(-1), roleObjects(), redirectCount(0) {} + + + void notifyQueryStarted(bool remoteSource) { + Q_Q(QDeclarativeXmlListModel); + progress = remoteSource ? 0.0 : 1.0; + status = QDeclarativeXmlListModel::Loading; + errorString.clear(); + emit q->progressChanged(progress); + emit q->statusChanged(status); + } + + bool isComponentComplete; + QUrl src; + QString xml; + QString query; + QString namespaces; + int size; + QList roles; + QStringList roleNames; + int highestRole; + QNetworkReply *reply; + QDeclarativeXmlListModel::Status status; + QString errorString; + qreal progress; + int queryId; + QStringList keyRoleResultsCache; + QList roleObjects; + static void append_role(QDeclarativeListProperty *list, QDeclarativeXmlListModelRole *role); + static void clear_role(QDeclarativeListProperty *list); + static void removeAt_role(QDeclarativeListProperty *list, int i); + static void insert_role(QDeclarativeListProperty *list, int i, QDeclarativeXmlListModelRole *role); + QList > data; + int redirectCount; +}; + + +void QDeclarativeXmlListModelPrivate::append_role(QDeclarativeListProperty *list, QDeclarativeXmlListModelRole *role) +{ + QDeclarativeXmlListModel *_this = qobject_cast(list->object); + if (_this) { + int i = _this->d_func()->roleObjects.count(); + _this->d_func()->roleObjects.append(role); + if (_this->d_func()->roleNames.contains(role->name())) { + qmlInfo(role) << QObject::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->name()); + return; + } + _this->d_func()->roles.insert(i, _this->d_func()->highestRole); + _this->d_func()->roleNames.insert(i, role->name()); + ++_this->d_func()->highestRole; + } +} + +//### clear needs to invalidate any cached data (in data table) as well +// (and the model should emit the appropriate signals) +void QDeclarativeXmlListModelPrivate::clear_role(QDeclarativeListProperty *list) +{ + QDeclarativeXmlListModel *_this = static_cast(list->object); + _this->d_func()->roles.clear(); + _this->d_func()->roleNames.clear(); + _this->d_func()->roleObjects.clear(); +} + +/*! + \class QDeclarativeXmlListModel + \internal +*/ + +/*! + \qmlclass XmlListModel QDeclarativeXmlListModel + \since 4.7 + \brief The XmlListModel element is used to specify a model using XPath expressions. + + XmlListModel is used to create a model from XML data. XmlListModel 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 \l Repeater). + + For example, if there is a XML document at http://www.mysite.com/feed.xml like this: + + \code + + + ... + + + Item A + Sat, 07 Sep 2010 10:00:01 GMT + + + Item B + Sat, 07 Sep 2010 15:35:01 GMT + + + + \endcode + + A XmlListModel could create a model from this data, like this: + + \qml + XmlListModel { + id: xmlModel + source: "http://www.mysite.com/feed.xml" + query: "/rss/channel/item" + XmlRole { name: "title"; query: "title/string()" } + XmlRole { name: "pubDate"; query: "pubDate/string()" } + } + \endqml + + The \l {XmlListModel::query}{query} value of "/rss/channel/item" specifies that the XmlListModel should generate + a model item for each \c in the XML document. The XmlRole objects define the + model item attributes; here, each model item will have \c title and \c pubDate + attributes that match the \c title and \c pubDate values of its corresponding \c . + + The model could be used in a ListView, like this: + + \qml + ListView { + width: 180; height: 300 + model: xmlModel + delegate: Text { title + " (" + pubDate + ")" } + } + \endqml + + + \section2 Using key XML roles + + You can define certain roles as "keys" so that when reload() is called, + the model will only add and refresh data that contains new values for + these keys. + + For example, if above role for "pubDate" was defined like this instead: + + \qml + XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true } + \endqml + + Then when reload() is called, the model will only add and reload + items with a "pubDate" value that is not already + present in the model. + + This is useful when displaying the contents of XML documents that + are incrementally updated (such as RSS feeds) to avoid repainting the + entire contents of a model in a view. + + If multiple key roles are specified, the model only adds and reload items + with a combined value of all key roles that is not already present in + the model. + + \sa {declarative/xml/xmldata}{XML data example} +*/ + +QDeclarativeXmlListModel::QDeclarativeXmlListModel(QObject *parent) + : QListModelInterface(*(new QDeclarativeXmlListModelPrivate), parent) +{ + connect(globalXmlQuery(), SIGNAL(queryCompleted(QDeclarativeXmlQueryResult)), + this, SLOT(queryCompleted(QDeclarativeXmlQueryResult))); + connect(globalXmlQuery(), SIGNAL(error(void*,QString)), + this, SLOT(queryError(void*,QString))); +} + +QDeclarativeXmlListModel::~QDeclarativeXmlListModel() +{ +} + +/*! + \qmlproperty list XmlListModel::roles + + The roles to make available for this model. +*/ +QDeclarativeListProperty QDeclarativeXmlListModel::roleObjects() +{ + Q_D(QDeclarativeXmlListModel); + QDeclarativeListProperty list(this, d->roleObjects); + list.append = &QDeclarativeXmlListModelPrivate::append_role; + list.clear = &QDeclarativeXmlListModelPrivate::clear_role; + return list; +} + +QHash QDeclarativeXmlListModel::data(int index, const QList &roles) const +{ + Q_D(const QDeclarativeXmlListModel); + QHash rv; + for (int i = 0; i < roles.size(); ++i) { + int role = roles.at(i); + int roleIndex = d->roles.indexOf(role); + rv.insert(role, roleIndex == -1 ? QVariant() : d->data.value(roleIndex).value(index)); + } + return rv; +} + +QVariant QDeclarativeXmlListModel::data(int index, int role) const +{ + Q_D(const QDeclarativeXmlListModel); + int roleIndex = d->roles.indexOf(role); + return (roleIndex == -1) ? QVariant() : d->data.value(roleIndex).value(index); +} + +/*! + \qmlproperty int XmlListModel::count + The number of data entries in the model. +*/ +int QDeclarativeXmlListModel::count() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->size; +} + +QList QDeclarativeXmlListModel::roles() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->roles; +} + +QString QDeclarativeXmlListModel::toString(int role) const +{ + Q_D(const QDeclarativeXmlListModel); + int index = d->roles.indexOf(role); + if (index == -1) + return QString(); + return d->roleNames.at(index); +} + +/*! + \qmlproperty url XmlListModel::source + The location of the XML data source. + + If both source and xml are set, xml will be used. +*/ +QUrl QDeclarativeXmlListModel::source() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->src; +} + +void QDeclarativeXmlListModel::setSource(const QUrl &src) +{ + Q_D(QDeclarativeXmlListModel); + if (d->src != src) { + d->src = src; + if (d->xml.isEmpty()) // src is only used if d->xml is not set + reload(); + emit sourceChanged(); + } +} + +/*! + \qmlproperty string XmlListModel::xml + This property holds XML text set directly. + + The text is assumed to be UTF-8 encoded. + + If both source and xml are set, xml will be used. +*/ +QString QDeclarativeXmlListModel::xml() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->xml; +} + +void QDeclarativeXmlListModel::setXml(const QString &xml) +{ + Q_D(QDeclarativeXmlListModel); + if (d->xml != xml) { + d->xml = xml; + reload(); + emit xmlChanged(); + } +} + +/*! + \qmlproperty string XmlListModel::query + An absolute XPath query representing the base query for creating model items + from this model's XmlRole objects. The query should start with '/' or '//'. +*/ +QString QDeclarativeXmlListModel::query() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->query; +} + +void QDeclarativeXmlListModel::setQuery(const QString &query) +{ + Q_D(QDeclarativeXmlListModel); + if (!query.startsWith(QLatin1Char('/'))) { + qmlInfo(this) << QCoreApplication::translate("QDeclarativeXmlRoleList", "An XmlListModel query must start with '/' or \"//\""); + return; + } + + if (d->query != query) { + d->query = query; + reload(); + emit queryChanged(); + } +} + +/*! + \qmlproperty string XmlListModel::namespaceDeclarations + The namespace declarations to be used in the XPath queries. + + The namespaces should be declared as in XQuery. For example, if a requested document + at http://mysite.com/feed.xml uses the namespace "http://www.w3.org/2005/Atom", + this can be declared as the default namespace: + + \qml + XmlListModel { + source: "http://mysite.com/feed.xml" + query: "/feed/entry" + namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';" + XmlRole { name: "title"; query: "title/string()" } + } + \endqml +*/ +QString QDeclarativeXmlListModel::namespaceDeclarations() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->namespaces; +} + +void QDeclarativeXmlListModel::setNamespaceDeclarations(const QString &declarations) +{ + Q_D(QDeclarativeXmlListModel); + if (d->namespaces != declarations) { + d->namespaces = declarations; + reload(); + emit namespaceDeclarationsChanged(); + } +} + +/*! + \qmlmethod object XmlListModel::get(int index) + + Returns the item at \a index in the model. + + For example, for a model like this: + + \qml + XmlListModel { + id: model + source: "http://mysite.com/feed.xml" + query: "/feed/entry" + XmlRole { name: "title"; query: "title/string()" } + } + \endqml + + This will access the \c title value for the first item in the model: + + \qml + var title = model.get(0).title; + \endqml +*/ +QScriptValue QDeclarativeXmlListModel::get(int index) const +{ + Q_D(const QDeclarativeXmlListModel); + + QScriptEngine *sengine = QDeclarativeEnginePrivate::getScriptEngine(qmlContext(this)->engine()); + if (index < 0 || index >= count()) + return sengine->undefinedValue(); + + QScriptValue sv = sengine->newObject(); + for (int i=0; iroleObjects.count(); i++) + sv.setProperty(d->roleObjects[i]->name(), qScriptValueFromValue(sengine, d->data.value(i).value(index))); + return sv; +} + +/*! + \qmlproperty enumeration XmlListModel::status + Specifies the model loading status, which can be one of the following: + + \list + \o XmlListModel.Null - No XML data has been set for this model. + \o XmlListModel.Ready - The XML data has been loaded into the model. + \o XmlListModel.Loading - The model is in the process of reading and loading XML data. + \o XmlListModel.Error - An error occurred while the model was loading. See errorString() for details + about the error. + \endlist + + \sa progress + +*/ +QDeclarativeXmlListModel::Status QDeclarativeXmlListModel::status() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->status; +} + +/*! + \qmlproperty real XmlListModel::progress + + This indicates the current progress of the downloading of the XML data + source. This value ranges from 0.0 (no data downloaded) to + 1.0 (all data downloaded). If the XML data is not from a remote source, + the progress becomes 1.0 as soon as the data is read. + + Note that when the progress is 1.0, the XML data has been downloaded, but + it is yet to be loaded into the model at this point. Use the status + property to find out when the XML data has been read and loaded into + the model. + + \sa status, source +*/ +qreal QDeclarativeXmlListModel::progress() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->progress; +} + +QString QDeclarativeXmlListModel::errorString() const +{ + Q_D(const QDeclarativeXmlListModel); + return d->errorString; +} + +void QDeclarativeXmlListModel::classBegin() +{ + Q_D(QDeclarativeXmlListModel); + d->isComponentComplete = false; +} + +void QDeclarativeXmlListModel::componentComplete() +{ + Q_D(QDeclarativeXmlListModel); + d->isComponentComplete = true; + reload(); +} + +/*! + \qmlmethod XmlListModel::reload() + + 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. + + \sa {Using key XML roles}, XmlRole::isKey +*/ +void QDeclarativeXmlListModel::reload() +{ + Q_D(QDeclarativeXmlListModel); + + if (!d->isComponentComplete) + return; + + globalXmlQuery()->abort(d->queryId); + d->queryId = -1; + + if (d->size < 0) + d->size = 0; + + if (d->reply) { + d->reply->abort(); + if (d->reply) { + // abort will generally have already done this (and more) + d->reply->deleteLater(); + d->reply = 0; + } + } + + if (!d->xml.isEmpty()) { + d->queryId = globalXmlQuery()->doQuery(d->query, d->namespaces, d->xml.toUtf8(), &d->roleObjects, d->keyRoleResultsCache); + d->notifyQueryStarted(false); + + } else if (d->src.isEmpty()) { + d->queryId = XMLLISTMODEL_CLEAR_ID; + d->notifyQueryStarted(false); + QTimer::singleShot(0, this, SLOT(dataCleared())); + + } else { + d->notifyQueryStarted(true); + QNetworkRequest req(d->src); + d->reply = qmlContext(this)->engine()->networkAccessManager()->get(req); + QObject::connect(d->reply, SIGNAL(finished()), this, SLOT(requestFinished())); + QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), + this, SLOT(requestProgress(qint64,qint64))); + } +} + +#define XMLLISTMODEL_MAX_REDIRECT 16 + +void QDeclarativeXmlListModel::requestFinished() +{ + Q_D(QDeclarativeXmlListModel); + + d->redirectCount++; + if (d->redirectCount < XMLLISTMODEL_MAX_REDIRECT) { + QVariant redirect = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirect.isValid()) { + QUrl url = d->reply->url().resolved(redirect.toUrl()); + d->reply->deleteLater(); + d->reply = 0; + setSource(url); + return; + } + } + d->redirectCount = 0; + + if (d->reply->error() != QNetworkReply::NoError) { + d->errorString = d->reply->errorString(); + disconnect(d->reply, 0, this, 0); + d->reply->deleteLater(); + d->reply = 0; + + int count = this->count(); + d->data.clear(); + d->size = 0; + if (count > 0) { + emit itemsRemoved(0, count); + emit countChanged(); + } + + d->status = Error; + d->queryId = -1; + emit statusChanged(d->status); + } else { + QByteArray data = d->reply->readAll(); + if (data.isEmpty()) { + d->queryId = XMLLISTMODEL_CLEAR_ID; + QTimer::singleShot(0, this, SLOT(dataCleared())); + } else { + d->queryId = globalXmlQuery()->doQuery(d->query, d->namespaces, data, &d->roleObjects, d->keyRoleResultsCache); + } + disconnect(d->reply, 0, this, 0); + d->reply->deleteLater(); + d->reply = 0; + + d->progress = 1.0; + emit progressChanged(d->progress); + } +} + +void QDeclarativeXmlListModel::requestProgress(qint64 received, qint64 total) +{ + Q_D(QDeclarativeXmlListModel); + if (d->status == Loading && total > 0) { + d->progress = qreal(received)/total; + emit progressChanged(d->progress); + } +} + +void QDeclarativeXmlListModel::dataCleared() +{ + Q_D(QDeclarativeXmlListModel); + QDeclarativeXmlQueryResult r; + r.queryId = XMLLISTMODEL_CLEAR_ID; + r.size = 0; + r.removed << qMakePair(0, count()); + r.keyRoleResultsCache = d->keyRoleResultsCache; + queryCompleted(r); +} + +void QDeclarativeXmlListModel::queryError(void* object, const QString& error) +{ + // Be extra careful, object may no longer exist, it's just an ID. + Q_D(QDeclarativeXmlListModel); + for (int i=0; iroleObjects.count(); i++) { + if (d->roleObjects.at(i) == static_cast(object)) { + qmlInfo(d->roleObjects.at(i)) << QObject::tr("invalid query: \"%1\"").arg(error); + return; + } + } + qmlInfo(this) << QObject::tr("invalid query: \"%1\"").arg(error); +} + +void QDeclarativeXmlListModel::queryCompleted(const QDeclarativeXmlQueryResult &result) +{ + Q_D(QDeclarativeXmlListModel); + if (result.queryId != d->queryId) + return; + + int origCount = d->size; + bool sizeChanged = result.size != d->size; + + d->size = result.size; + d->data = result.data; + d->keyRoleResultsCache = result.keyRoleResultsCache; + d->status = Ready; + d->errorString.clear(); + d->queryId = -1; + + bool hasKeys = false; + for (int i=0; iroleObjects.count(); i++) { + if (d->roleObjects[i]->isKey()) { + hasKeys = true; + break; + } + } + if (!hasKeys) { + if (!(origCount == 0 && d->size == 0)) { + emit itemsRemoved(0, origCount); + emit itemsInserted(0, d->size); + emit countChanged(); + } + + } else { + for (int i=0; istatus); +} + +QT_END_NAMESPACE + +#include