1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qqmlxmllistmodel_p.h" |
41 | |
42 | #include <qqmlcontext.h> |
43 | #include <qqmlfile.h> |
44 | #include <private/qqmlengine_p.h> |
45 | #include <private/qv4value_p.h> |
46 | #include <private/qv4engine_p.h> |
47 | #include <private/qv4object_p.h> |
48 | |
49 | #include <QDebug> |
50 | #include <QStringList> |
51 | #include <QMap> |
52 | #include <QThread> |
53 | #include <QXmlQuery> |
54 | #include <QXmlResultItems> |
55 | #include <QXmlNodeModelIndex> |
56 | #include <QBuffer> |
57 | #if QT_CONFIG(qml_network) |
58 | #include <QNetworkRequest> |
59 | #include <QNetworkReply> |
60 | #endif |
61 | #include <QTimer> |
62 | #include <QMutex> |
63 | |
64 | #include <private/qabstractitemmodel_p.h> |
65 | |
66 | Q_DECLARE_METATYPE(QQuickXmlQueryResult) |
67 | |
68 | QT_BEGIN_NAMESPACE |
69 | |
70 | using namespace QV4; |
71 | |
72 | typedef QPair<int, int> QQuickXmlListRange; |
73 | |
74 | #define XMLLISTMODEL_CLEAR_ID 0 |
75 | |
76 | /*! |
77 | \qmlmodule QtQuick.XmlListModel 2.\QtMinorVersion |
78 | \title Qt XML Patterns QML Types |
79 | \keyword Qt Quick XmlListModel QML Types |
80 | \ingroup qmlmodules |
81 | \brief Provides QML types for creating models from XML data |
82 | |
83 | This QML module contains types for creating models from XML data. |
84 | |
85 | To use the types in this module, import the module with the following line: |
86 | |
87 | \qml \QtMinorVersion |
88 | import QtQuick.XmlListModel 2.\1 |
89 | \endqml |
90 | */ |
91 | |
92 | /*! |
93 | \qmltype XmlRole |
94 | \inqmlmodule QtQuick.XmlListModel |
95 | \brief For specifying a role to an XmlListModel. |
96 | \ingroup qtquick-models |
97 | |
98 | \sa {All QML Types}{Qt QML} |
99 | */ |
100 | |
101 | /*! |
102 | \qmlproperty string QtQuick.XmlListModel::XmlRole::name |
103 | |
104 | The name for the role. This name is used to access the model data for this role. |
105 | |
106 | For example, the following model has a role named "title", which can be accessed |
107 | from the view's delegate: |
108 | |
109 | \qml |
110 | XmlListModel { |
111 | id: xmlModel |
112 | // ... |
113 | XmlRole { |
114 | name: "title" |
115 | query: "title/string()" |
116 | } |
117 | } |
118 | \endqml |
119 | |
120 | \qml |
121 | ListView { |
122 | model: xmlModel |
123 | delegate: Text { text: title } |
124 | } |
125 | \endqml |
126 | */ |
127 | |
128 | /*! |
129 | \qmlproperty string QtQuick.XmlListModel::XmlRole::query |
130 | The relative XPath expression query for this role. The query must be relative; it cannot start |
131 | with a '/'. |
132 | |
133 | For example, if there is an XML document like this: |
134 | |
135 | \quotefile qml/xmlrole.xml |
136 | Here are some valid XPath expressions for XmlRole queries on this document: |
137 | |
138 | \snippet qml/xmlrole.qml 0 |
139 | \dots 4 |
140 | \snippet qml/xmlrole.qml 1 |
141 | |
142 | Accessing the model data for the above roles from a delegate: |
143 | |
144 | \snippet qml/xmlrole.qml 2 |
145 | |
146 | See the \l{http://www.w3.org/TR/xpath20/}{W3C XPath 2.0 specification} for more information. |
147 | */ |
148 | |
149 | /*! |
150 | \qmlproperty bool QtQuick.XmlListModel::XmlRole::isKey |
151 | Defines whether this is a key role. |
152 | Key roles are used to determine whether a set of values should |
153 | be updated or added to the XML list model when XmlListModel::reload() |
154 | is called. |
155 | |
156 | \sa XmlListModel |
157 | */ |
158 | |
159 | struct XmlQueryJob |
160 | { |
161 | int queryId; |
162 | QByteArray data; |
163 | QString query; |
164 | QString namespaces; |
165 | QStringList roleQueries; |
166 | QList<void*> roleQueryErrorId; // the ptr to send back if there is an error |
167 | QStringList keyRoleQueries; |
168 | QStringList keyRoleResultsCache; |
169 | QString prefix; |
170 | }; |
171 | |
172 | |
173 | class QQuickXmlQueryEngine; |
174 | class QQuickXmlQueryThreadObject : public QObject |
175 | { |
176 | Q_OBJECT |
177 | public: |
178 | QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *); |
179 | |
180 | void processJobs(); |
181 | bool event(QEvent *e) override; |
182 | |
183 | private: |
184 | QQuickXmlQueryEngine *m_queryEngine; |
185 | }; |
186 | |
187 | |
188 | class QQuickXmlQueryEngine : public QThread |
189 | { |
190 | Q_OBJECT |
191 | public: |
192 | QQuickXmlQueryEngine(QQmlEngine *eng); |
193 | ~QQuickXmlQueryEngine(); |
194 | |
195 | int doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache); |
196 | void abort(int id); |
197 | |
198 | void processJobs(); |
199 | |
200 | static QQuickXmlQueryEngine *instance(QQmlEngine *engine); |
201 | |
202 | signals: |
203 | void queryCompleted(const QQuickXmlQueryResult &); |
204 | void error(void*, const QString&); |
205 | |
206 | protected: |
207 | void run() override; |
208 | |
209 | private: |
210 | void processQuery(XmlQueryJob *job); |
211 | void doQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult); |
212 | void doSubQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult); |
213 | void getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const; |
214 | void addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const; |
215 | |
216 | QMutex m_mutex; |
217 | QQuickXmlQueryThreadObject *m_threadObject; |
218 | QList<XmlQueryJob> m_jobs; |
219 | QSet<int> m_cancelledJobs; |
220 | QAtomicInt m_queryIds; |
221 | |
222 | QQmlEngine *m_engine; |
223 | QObject *m_eventLoopQuitHack; |
224 | |
225 | static QHash<QQmlEngine *,QQuickXmlQueryEngine*> queryEngines; |
226 | static QMutex queryEnginesMutex; |
227 | }; |
228 | QHash<QQmlEngine *,QQuickXmlQueryEngine*> QQuickXmlQueryEngine::queryEngines; |
229 | QMutex QQuickXmlQueryEngine::queryEnginesMutex; |
230 | |
231 | |
232 | QQuickXmlQueryThreadObject::QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *e) |
233 | : m_queryEngine(e) |
234 | { |
235 | } |
236 | |
237 | void QQuickXmlQueryThreadObject::processJobs() |
238 | { |
239 | QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User)); |
240 | } |
241 | |
242 | bool QQuickXmlQueryThreadObject::event(QEvent *e) |
243 | { |
244 | if (e->type() == QEvent::User) { |
245 | m_queryEngine->processJobs(); |
246 | return true; |
247 | } else { |
248 | return QObject::event(event: e); |
249 | } |
250 | } |
251 | |
252 | |
253 | |
254 | QQuickXmlQueryEngine::QQuickXmlQueryEngine(QQmlEngine *eng) |
255 | : QThread(eng), m_threadObject(0), m_queryIds(XMLLISTMODEL_CLEAR_ID + 1), m_engine(eng), m_eventLoopQuitHack(0) |
256 | { |
257 | qRegisterMetaType<QQuickXmlQueryResult>(typeName: "QQuickXmlQueryResult" ); |
258 | |
259 | m_eventLoopQuitHack = new QObject; |
260 | m_eventLoopQuitHack->moveToThread(thread: this); |
261 | connect(asender: m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), atype: Qt::DirectConnection); |
262 | start(QThread::IdlePriority); |
263 | } |
264 | |
265 | QQuickXmlQueryEngine::~QQuickXmlQueryEngine() |
266 | { |
267 | queryEnginesMutex.lock(); |
268 | queryEngines.remove(key: m_engine); |
269 | queryEnginesMutex.unlock(); |
270 | |
271 | m_eventLoopQuitHack->deleteLater(); |
272 | wait(); |
273 | } |
274 | |
275 | int QQuickXmlQueryEngine::doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache) { |
276 | { |
277 | QMutexLocker m1(&m_mutex); |
278 | m_queryIds.ref(); |
279 | if (m_queryIds.loadRelaxed() <= 0) |
280 | m_queryIds.storeRelaxed(newValue: 1); |
281 | } |
282 | |
283 | XmlQueryJob job; |
284 | job.queryId = m_queryIds.loadRelaxed(); |
285 | job.data = data; |
286 | job.query = QLatin1String("doc($src)" ) + query; |
287 | job.namespaces = namespaces; |
288 | job.keyRoleResultsCache = keyRoleResultsCache; |
289 | |
290 | for (int i=0; i<roleObjects->count(); i++) { |
291 | if (!roleObjects->at(i)->isValid()) { |
292 | job.roleQueries << QString(); |
293 | continue; |
294 | } |
295 | job.roleQueries << roleObjects->at(i)->query(); |
296 | job.roleQueryErrorId << static_cast<void*>(roleObjects->at(i)); |
297 | if (roleObjects->at(i)->isKey()) |
298 | job.keyRoleQueries << job.roleQueries.last(); |
299 | } |
300 | |
301 | { |
302 | QMutexLocker ml(&m_mutex); |
303 | m_jobs.append(t: job); |
304 | if (m_threadObject) |
305 | m_threadObject->processJobs(); |
306 | } |
307 | |
308 | return job.queryId; |
309 | } |
310 | |
311 | void QQuickXmlQueryEngine::abort(int id) |
312 | { |
313 | QMutexLocker ml(&m_mutex); |
314 | if (id != -1) |
315 | m_cancelledJobs.insert(value: id); |
316 | } |
317 | |
318 | void QQuickXmlQueryEngine::run() |
319 | { |
320 | m_mutex.lock(); |
321 | m_threadObject = new QQuickXmlQueryThreadObject(this); |
322 | m_mutex.unlock(); |
323 | |
324 | processJobs(); |
325 | exec(); |
326 | |
327 | delete m_threadObject; |
328 | m_threadObject = 0; |
329 | } |
330 | |
331 | void QQuickXmlQueryEngine::processJobs() |
332 | { |
333 | QMutexLocker locker(&m_mutex); |
334 | |
335 | while (true) { |
336 | if (m_jobs.isEmpty()) |
337 | return; |
338 | |
339 | XmlQueryJob currentJob = m_jobs.takeLast(); |
340 | while (m_cancelledJobs.remove(value: currentJob.queryId)) { |
341 | if (m_jobs.isEmpty()) |
342 | return; |
343 | currentJob = m_jobs.takeLast(); |
344 | } |
345 | |
346 | locker.unlock(); |
347 | processQuery(job: ¤tJob); |
348 | locker.relock(); |
349 | } |
350 | } |
351 | |
352 | QQuickXmlQueryEngine *QQuickXmlQueryEngine::instance(QQmlEngine *engine) |
353 | { |
354 | queryEnginesMutex.lock(); |
355 | QQuickXmlQueryEngine *queryEng = queryEngines.value(key: engine); |
356 | if (!queryEng) { |
357 | queryEng = new QQuickXmlQueryEngine(engine); |
358 | queryEngines.insert(key: engine, value: queryEng); |
359 | } |
360 | queryEnginesMutex.unlock(); |
361 | |
362 | return queryEng; |
363 | } |
364 | |
365 | void QQuickXmlQueryEngine::processQuery(XmlQueryJob *job) |
366 | { |
367 | QQuickXmlQueryResult result; |
368 | result.queryId = job->queryId; |
369 | doQueryJob(job, currentResult: &result); |
370 | doSubQueryJob(job, currentResult: &result); |
371 | |
372 | { |
373 | QMutexLocker ml(&m_mutex); |
374 | if (m_cancelledJobs.contains(value: job->queryId)) { |
375 | m_cancelledJobs.remove(value: job->queryId); |
376 | } else { |
377 | emit queryCompleted(result); |
378 | } |
379 | } |
380 | } |
381 | |
382 | void QQuickXmlQueryEngine::doQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult) |
383 | { |
384 | Q_ASSERT(currentJob->queryId != -1); |
385 | |
386 | QString r; |
387 | QXmlQuery query; |
388 | QBuffer buffer(¤tJob->data); |
389 | buffer.open(openMode: QIODevice::ReadOnly); |
390 | query.bindVariable(localName: QLatin1String("src" ), &buffer); |
391 | query.setQuery(sourceCode: currentJob->namespaces + currentJob->query); |
392 | query.evaluateTo(output: &r); |
393 | |
394 | //always need a single root element |
395 | QByteArray xml = "<dummy:items xmlns:dummy=\"http://qtsotware.com/dummy\">\n" + r.toUtf8() + "</dummy:items>" ; |
396 | QBuffer b(&xml); |
397 | b.open(openMode: QIODevice::ReadOnly); |
398 | |
399 | QString namespaces = QLatin1String("declare namespace dummy=\"http://qtsotware.com/dummy\";\n" ) + currentJob->namespaces; |
400 | QString prefix = QLatin1String("doc($inputDocument)/dummy:items/*" ); |
401 | |
402 | //figure out how many items we are dealing with |
403 | int count = -1; |
404 | { |
405 | QXmlResultItems result; |
406 | QXmlQuery countquery; |
407 | countquery.bindVariable(localName: QLatin1String("inputDocument" ), &b); |
408 | countquery.setQuery(sourceCode: namespaces + QLatin1String("count(" ) + prefix + QLatin1Char(')')); |
409 | countquery.evaluateTo(result: &result); |
410 | QXmlItem item(result.next()); |
411 | if (item.isAtomicValue()) |
412 | count = item.toAtomicValue().toInt(); |
413 | } |
414 | |
415 | currentJob->data = xml; |
416 | currentJob->prefix = namespaces + prefix + QLatin1Char('/'); |
417 | currentResult->size = (count > 0 ? count : 0); |
418 | } |
419 | |
420 | void QQuickXmlQueryEngine::getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const |
421 | { |
422 | const QStringList &keysQueries = currentJob.keyRoleQueries; |
423 | QString keysQuery; |
424 | if (keysQueries.count() == 1) |
425 | keysQuery = currentJob.prefix + keysQueries[0]; |
426 | else if (keysQueries.count() > 1) |
427 | keysQuery = currentJob.prefix + QLatin1String("concat(" ) + keysQueries.join(sep: QLatin1Char(',')) + QLatin1Char(')'); |
428 | |
429 | if (!keysQuery.isEmpty()) { |
430 | query->setQuery(sourceCode: keysQuery); |
431 | QXmlResultItems resultItems; |
432 | query->evaluateTo(result: &resultItems); |
433 | QXmlItem item(resultItems.next()); |
434 | while (!item.isNull()) { |
435 | values->append(t: item.toAtomicValue().toString()); |
436 | item = resultItems.next(); |
437 | } |
438 | } |
439 | } |
440 | |
441 | void QQuickXmlQueryEngine::addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const { |
442 | if (ranges->isEmpty()) |
443 | ranges->append(t: qMakePair(x: index, y: 1)); |
444 | else if (ranges->last().first + ranges->last().second == index) |
445 | ranges->last().second += 1; |
446 | else |
447 | ranges->append(t: qMakePair(x: index, y: 1)); |
448 | } |
449 | |
450 | void QQuickXmlQueryEngine::doSubQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult) |
451 | { |
452 | Q_ASSERT(currentJob->queryId != -1); |
453 | |
454 | QBuffer b(¤tJob->data); |
455 | b.open(openMode: QIODevice::ReadOnly); |
456 | |
457 | QXmlQuery subquery; |
458 | subquery.bindVariable(localName: QLatin1String("inputDocument" ), &b); |
459 | |
460 | QStringList keyRoleResults; |
461 | getValuesOfKeyRoles(currentJob: *currentJob, values: &keyRoleResults, query: &subquery); |
462 | |
463 | // See if any values of key roles have been inserted or removed. |
464 | |
465 | if (currentJob->keyRoleResultsCache.isEmpty()) { |
466 | currentResult->inserted << qMakePair(x: 0, y: currentResult->size); |
467 | } else { |
468 | if (keyRoleResults != currentJob->keyRoleResultsCache) { |
469 | QStringList temp; |
470 | for (int i=0; i<currentJob->keyRoleResultsCache.count(); i++) { |
471 | if (!keyRoleResults.contains(str: currentJob->keyRoleResultsCache[i])) |
472 | addIndexToRangeList(ranges: ¤tResult->removed, index: i); |
473 | else |
474 | temp << currentJob->keyRoleResultsCache[i]; |
475 | } |
476 | for (int i=0; i<keyRoleResults.count(); i++) { |
477 | if (temp.count() == i || keyRoleResults[i] != temp[i]) { |
478 | temp.insert(i, t: keyRoleResults[i]); |
479 | addIndexToRangeList(ranges: ¤tResult->inserted, index: i); |
480 | } |
481 | } |
482 | } |
483 | } |
484 | currentResult->keyRoleResultsCache = keyRoleResults; |
485 | |
486 | // Get the new values for each role. |
487 | //### we might be able to condense even further (query for everything in one go) |
488 | const QStringList &queries = currentJob->roleQueries; |
489 | for (int i = 0; i < queries.size(); ++i) { |
490 | QList<QVariant> resultList; |
491 | if (!queries[i].isEmpty()) { |
492 | subquery.setQuery(sourceCode: currentJob->prefix + QLatin1String("(let $v := string(" ) + queries[i] + QLatin1String(") return if ($v) then " ) + queries[i] + QLatin1String(" else \"\")" )); |
493 | if (subquery.isValid()) { |
494 | QXmlResultItems resultItems; |
495 | subquery.evaluateTo(result: &resultItems); |
496 | QXmlItem item(resultItems.next()); |
497 | while (!item.isNull()) { |
498 | resultList << item.toAtomicValue(); //### we used to trim strings |
499 | item = resultItems.next(); |
500 | } |
501 | } else { |
502 | emit error(currentJob->roleQueryErrorId.at(i), queries[i]); |
503 | } |
504 | } |
505 | //### should warn here if things have gone wrong. |
506 | while (resultList.count() < currentResult->size) |
507 | resultList << QVariant(); |
508 | currentResult->data << resultList; |
509 | b.seek(off: 0); |
510 | } |
511 | |
512 | //this method is much slower, but works better for incremental loading |
513 | /*for (int j = 0; j < m_size; ++j) { |
514 | QList<QVariant> resultList; |
515 | for (int i = 0; i < m_roleObjects->size(); ++i) { |
516 | QQuickXmlListModelRole *role = m_roleObjects->at(i); |
517 | subquery.setQuery(m_prefix.arg(j+1) + role->query()); |
518 | if (role->isStringList()) { |
519 | QStringList data; |
520 | subquery.evaluateTo(&data); |
521 | resultList << QVariant(data); |
522 | //qDebug() << data; |
523 | } else { |
524 | QString s; |
525 | subquery.evaluateTo(&s); |
526 | if (role->isCData()) { |
527 | //un-escape |
528 | s.replace(QLatin1String("<"), QLatin1String("<")); |
529 | s.replace(QLatin1String(">"), QLatin1String(">")); |
530 | s.replace(QLatin1String("&"), QLatin1String("&")); |
531 | } |
532 | resultList << s.trimmed(); |
533 | //qDebug() << s; |
534 | } |
535 | b.seek(0); |
536 | } |
537 | m_modelData << resultList; |
538 | }*/ |
539 | } |
540 | |
541 | class QQuickXmlListModelPrivate : public QAbstractItemModelPrivate |
542 | { |
543 | Q_DECLARE_PUBLIC(QQuickXmlListModel) |
544 | public: |
545 | QQuickXmlListModelPrivate() |
546 | : isComponentComplete(true), size(0), highestRole(Qt::UserRole) |
547 | #if QT_CONFIG(qml_network) |
548 | , reply(0) |
549 | #endif |
550 | , status(QQuickXmlListModel::Null), progress(0.0) |
551 | , queryId(-1), roleObjects(), redirectCount(0) {} |
552 | |
553 | |
554 | void notifyQueryStarted(bool remoteSource) { |
555 | Q_Q(QQuickXmlListModel); |
556 | progress = remoteSource ? 0.0 : 1.0; |
557 | status = QQuickXmlListModel::Loading; |
558 | errorString.clear(); |
559 | emit q->progressChanged(progress); |
560 | emit q->statusChanged(status); |
561 | } |
562 | |
563 | #if QT_CONFIG(qml_network) |
564 | void deleteReply() { |
565 | Q_Q(QQuickXmlListModel); |
566 | if (reply) { |
567 | QObject::disconnect(sender: reply, signal: 0, receiver: q, member: 0); |
568 | reply->deleteLater(); |
569 | reply = 0; |
570 | } |
571 | } |
572 | #endif |
573 | |
574 | bool isComponentComplete; |
575 | QUrl src; |
576 | QString xml; |
577 | QString query; |
578 | QString namespaces; |
579 | int size; |
580 | QList<int> roles; |
581 | QStringList roleNames; |
582 | int highestRole; |
583 | |
584 | #if QT_CONFIG(qml_network) |
585 | QNetworkReply *reply; |
586 | #endif |
587 | QQuickXmlListModel::Status status; |
588 | QString errorString; |
589 | qreal progress; |
590 | int queryId; |
591 | QStringList keyRoleResultsCache; |
592 | QList<QQuickXmlListModelRole *> roleObjects; |
593 | |
594 | static void append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role); |
595 | static void clear_role(QQmlListProperty<QQuickXmlListModelRole> *list); |
596 | QList<QList<QVariant> > data; |
597 | int redirectCount; |
598 | }; |
599 | |
600 | |
601 | void QQuickXmlListModelPrivate::append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role) |
602 | { |
603 | QQuickXmlListModel *_this = qobject_cast<QQuickXmlListModel *>(object: list->object); |
604 | if (_this && role) { |
605 | int i = _this->d_func()->roleObjects.count(); |
606 | _this->d_func()->roleObjects.append(t: role); |
607 | if (_this->d_func()->roleNames.contains(str: role->name())) { |
608 | qmlWarning(me: role) << QQuickXmlListModel::tr(s: "\"%1\" duplicates a previous role name and will be disabled." ).arg(a: role->name()); |
609 | return; |
610 | } |
611 | _this->d_func()->roles.insert(i, t: _this->d_func()->highestRole); |
612 | _this->d_func()->roleNames.insert(i, t: role->name()); |
613 | ++_this->d_func()->highestRole; |
614 | } |
615 | } |
616 | |
617 | //### clear needs to invalidate any cached data (in data table) as well |
618 | // (and the model should emit the appropriate signals) |
619 | void QQuickXmlListModelPrivate::clear_role(QQmlListProperty<QQuickXmlListModelRole> *list) |
620 | { |
621 | QQuickXmlListModel *_this = static_cast<QQuickXmlListModel *>(list->object); |
622 | _this->d_func()->roles.clear(); |
623 | _this->d_func()->roleNames.clear(); |
624 | _this->d_func()->roleObjects.clear(); |
625 | } |
626 | |
627 | /*! |
628 | \qmltype XmlListModel |
629 | \inqmlmodule QtQuick.XmlListModel |
630 | \brief For specifying a read-only model using XPath expressions. |
631 | \ingroup qtquick-models |
632 | |
633 | |
634 | To use this element, you will need to import the module with the following line: |
635 | \code |
636 | import QtQuick.XmlListModel 2.0 |
637 | \endcode |
638 | |
639 | XmlListModel is used to create a read-only model from XML data. It can be used as a data source |
640 | for view elements (such as ListView, PathView, GridView) and other elements that interact with model |
641 | data (such as \l [QML]{Repeater}). |
642 | |
643 | For example, if there is a XML document at http://www.mysite.com/feed.xml like this: |
644 | |
645 | \code |
646 | <?xml version="1.0" encoding="utf-8"?> |
647 | <rss version="2.0"> |
648 | ... |
649 | <channel> |
650 | <item> |
651 | <title>A blog post</title> |
652 | <pubDate>Sat, 07 Sep 2010 10:00:01 GMT</pubDate> |
653 | </item> |
654 | <item> |
655 | <title>Another blog post</title> |
656 | <pubDate>Sat, 07 Sep 2010 15:35:01 GMT</pubDate> |
657 | </item> |
658 | </channel> |
659 | </rss> |
660 | \endcode |
661 | |
662 | A XmlListModel could create a model from this data, like this: |
663 | |
664 | \qml |
665 | import QtQuick 2.0 |
666 | import QtQuick.XmlListModel 2.0 |
667 | |
668 | XmlListModel { |
669 | id: xmlModel |
670 | source: "http://www.mysite.com/feed.xml" |
671 | query: "/rss/channel/item" |
672 | |
673 | XmlRole { name: "title"; query: "title/string()" } |
674 | XmlRole { name: "pubDate"; query: "pubDate/string()" } |
675 | } |
676 | \endqml |
677 | |
678 | The \l {XmlListModel::query}{query} value of "/rss/channel/item" specifies that the XmlListModel should generate |
679 | a model item for each \c <item> in the XML document. |
680 | |
681 | The XmlRole objects define the |
682 | model item attributes. Here, each model item will have \c title and \c pubDate |
683 | attributes that match the \c title and \c pubDate values of its corresponding \c <item>. |
684 | (See \l XmlRole::query for more examples of valid XPath expressions for XmlRole.) |
685 | |
686 | The model could be used in a ListView, like this: |
687 | |
688 | \qml |
689 | ListView { |
690 | width: 180; height: 300 |
691 | model: xmlModel |
692 | delegate: Text { text: title + ": " + pubDate } |
693 | } |
694 | \endqml |
695 | |
696 | \image qml-xmllistmodel-example.png |
697 | |
698 | The XmlListModel data is loaded asynchronously, and \l status |
699 | is set to \c XmlListModel.Ready when loading is complete. |
700 | Note this means when XmlListModel is used for a view, the view is not |
701 | populated until the model is loaded. |
702 | |
703 | |
704 | \section2 Using Key XML Roles |
705 | |
706 | You can define certain roles as "keys" so that when reload() is called, |
707 | the model will only add and refresh data that contains new values for |
708 | these keys. |
709 | |
710 | For example, if above role for "pubDate" was defined like this instead: |
711 | |
712 | \qml |
713 | XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true } |
714 | \endqml |
715 | |
716 | Then when reload() is called, the model will only add and reload |
717 | items with a "pubDate" value that is not already |
718 | present in the model. |
719 | |
720 | This is useful when displaying the contents of XML documents that |
721 | are incrementally updated (such as RSS feeds) to avoid repainting the |
722 | entire contents of a model in a view. |
723 | |
724 | If multiple key roles are specified, the model only adds and reload items |
725 | with a combined value of all key roles that is not already present in |
726 | the model. |
727 | |
728 | \sa {Qt Quick Demo - RSS News} |
729 | */ |
730 | |
731 | QQuickXmlListModel::QQuickXmlListModel(QObject *parent) |
732 | : QAbstractListModel(*(new QQuickXmlListModelPrivate), parent) |
733 | { |
734 | } |
735 | |
736 | QQuickXmlListModel::~QQuickXmlListModel() |
737 | { |
738 | } |
739 | |
740 | /*! |
741 | \qmlproperty list<XmlRole> QtQuick.XmlListModel::XmlListModel::roles |
742 | |
743 | The roles to make available for this model. |
744 | */ |
745 | QQmlListProperty<QQuickXmlListModelRole> QQuickXmlListModel::roleObjects() |
746 | { |
747 | Q_D(QQuickXmlListModel); |
748 | QQmlListProperty<QQuickXmlListModelRole> list(this, &d->roleObjects); |
749 | list.append = &QQuickXmlListModelPrivate::append_role; |
750 | list.clear = &QQuickXmlListModelPrivate::clear_role; |
751 | return list; |
752 | } |
753 | |
754 | QModelIndex QQuickXmlListModel::index(int row, int column, const QModelIndex &parent) const |
755 | { |
756 | Q_D(const QQuickXmlListModel); |
757 | return !parent.isValid() && column == 0 && row >= 0 && row < d->size |
758 | ? createIndex(arow: row, acolumn: column) |
759 | : QModelIndex(); |
760 | } |
761 | |
762 | int QQuickXmlListModel::rowCount(const QModelIndex &parent) const |
763 | { |
764 | Q_D(const QQuickXmlListModel); |
765 | return !parent.isValid() ? d->size : 0; |
766 | } |
767 | |
768 | QVariant QQuickXmlListModel::data(const QModelIndex &index, int role) const |
769 | { |
770 | Q_D(const QQuickXmlListModel); |
771 | const int roleIndex = d->roles.indexOf(t: role); |
772 | return (roleIndex == -1 || !index.isValid()) |
773 | ? QVariant() |
774 | : d->data.value(i: roleIndex).value(i: index.row()); |
775 | } |
776 | |
777 | QHash<int, QByteArray> QQuickXmlListModel::roleNames() const |
778 | { |
779 | Q_D(const QQuickXmlListModel); |
780 | QHash<int,QByteArray> roleNames; |
781 | for (int i = 0; i < d->roles.count(); ++i) |
782 | roleNames.insert(key: d->roles.at(i), value: d->roleNames.at(i).toUtf8()); |
783 | return roleNames; |
784 | } |
785 | |
786 | /*! |
787 | \qmlproperty int QtQuick.XmlListModel::XmlListModel::count |
788 | The number of data entries in the model. |
789 | */ |
790 | int QQuickXmlListModel::count() const |
791 | { |
792 | Q_D(const QQuickXmlListModel); |
793 | return d->size; |
794 | } |
795 | |
796 | /*! |
797 | \qmlproperty url QtQuick.XmlListModel::XmlListModel::source |
798 | The location of the XML data source. |
799 | |
800 | If both \c source and \l xml are set, \l xml is used. |
801 | */ |
802 | QUrl QQuickXmlListModel::source() const |
803 | { |
804 | Q_D(const QQuickXmlListModel); |
805 | return d->src; |
806 | } |
807 | |
808 | void QQuickXmlListModel::setSource(const QUrl &src) |
809 | { |
810 | Q_D(QQuickXmlListModel); |
811 | if (d->src != src) { |
812 | d->src = src; |
813 | if (d->xml.isEmpty()) // src is only used if d->xml is not set |
814 | reload(); |
815 | emit sourceChanged(); |
816 | } |
817 | } |
818 | |
819 | /*! |
820 | \qmlproperty string QtQuick.XmlListModel::XmlListModel::xml |
821 | This property holds the XML data for this model, if set. |
822 | |
823 | The text is assumed to be UTF-8 encoded. |
824 | |
825 | If both \l source and \c xml are set, \c xml is used. |
826 | */ |
827 | QString QQuickXmlListModel::xml() const |
828 | { |
829 | Q_D(const QQuickXmlListModel); |
830 | return d->xml; |
831 | } |
832 | |
833 | void QQuickXmlListModel::setXml(const QString &xml) |
834 | { |
835 | Q_D(QQuickXmlListModel); |
836 | if (d->xml != xml) { |
837 | d->xml = xml; |
838 | reload(); |
839 | emit xmlChanged(); |
840 | } |
841 | } |
842 | |
843 | /*! |
844 | \qmlproperty string QtQuick.XmlListModel::XmlListModel::query |
845 | An absolute XPath query representing the base query for creating model items |
846 | from this model's XmlRole objects. The query should start with '/' or '//'. |
847 | */ |
848 | QString QQuickXmlListModel::query() const |
849 | { |
850 | Q_D(const QQuickXmlListModel); |
851 | return d->query; |
852 | } |
853 | |
854 | void QQuickXmlListModel::setQuery(const QString &query) |
855 | { |
856 | Q_D(QQuickXmlListModel); |
857 | if (!query.startsWith(c: QLatin1Char('/'))) { |
858 | qmlWarning(me: this) << QCoreApplication::translate(context: "QQuickXmlRoleList" , key: "An XmlListModel query must start with '/' or \"//\"" ); |
859 | return; |
860 | } |
861 | |
862 | if (d->query != query) { |
863 | d->query = query; |
864 | reload(); |
865 | emit queryChanged(); |
866 | } |
867 | } |
868 | |
869 | /*! |
870 | \qmlproperty string QtQuick.XmlListModel::XmlListModel::namespaceDeclarations |
871 | The namespace declarations to be used in the XPath queries. |
872 | |
873 | The namespaces should be declared as in XQuery. For example, if a requested document |
874 | at http://mysite.com/feed.xml uses the namespace "http://www.w3.org/2005/Atom", |
875 | this can be declared as the default namespace: |
876 | |
877 | \qml |
878 | XmlListModel { |
879 | source: "http://mysite.com/feed.xml" |
880 | query: "/feed/entry" |
881 | namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';" |
882 | |
883 | XmlRole { name: "title"; query: "title/string()" } |
884 | } |
885 | \endqml |
886 | */ |
887 | QString QQuickXmlListModel::namespaceDeclarations() const |
888 | { |
889 | Q_D(const QQuickXmlListModel); |
890 | return d->namespaces; |
891 | } |
892 | |
893 | void QQuickXmlListModel::setNamespaceDeclarations(const QString &declarations) |
894 | { |
895 | Q_D(QQuickXmlListModel); |
896 | if (d->namespaces != declarations) { |
897 | d->namespaces = declarations; |
898 | reload(); |
899 | emit namespaceDeclarationsChanged(); |
900 | } |
901 | } |
902 | |
903 | /*! |
904 | \qmlmethod object QtQuick.XmlListModel::XmlListModel::get(int index) |
905 | |
906 | Returns the item at \a index in the model. |
907 | |
908 | For example, for a model like this: |
909 | |
910 | \qml |
911 | XmlListModel { |
912 | id: model |
913 | source: "http://mysite.com/feed.xml" |
914 | query: "/feed/entry" |
915 | XmlRole { name: "title"; query: "title/string()" } |
916 | } |
917 | \endqml |
918 | |
919 | This will access the \c title value for the first item in the model: |
920 | |
921 | \js |
922 | var title = model.get(0).title; |
923 | \endjs |
924 | */ |
925 | QJSValue QQuickXmlListModel::get(int index) const |
926 | { |
927 | // Must be called with a context and handle scope |
928 | Q_D(const QQuickXmlListModel); |
929 | |
930 | if (index < 0 || index >= count()) |
931 | return QJSValue(QJSValue::UndefinedValue); |
932 | |
933 | QQmlEngine *engine = qmlContext(this)->engine(); |
934 | ExecutionEngine *v4engine = engine->handle(); |
935 | Scope scope(v4engine); |
936 | Scoped<Object> o(scope, v4engine->newObject()); |
937 | ScopedString name(scope); |
938 | ScopedValue value(scope); |
939 | for (int ii = 0; ii < d->roleObjects.count(); ++ii) { |
940 | name = v4engine->newIdentifier(text: d->roleObjects[ii]->name()); |
941 | value = v4engine->fromVariant(d->data.value(i: ii).value(i: index)); |
942 | o->insertMember(s: name.getPointer(), v: value); |
943 | } |
944 | |
945 | return QJSValue(v4engine, o->asReturnedValue()); |
946 | } |
947 | |
948 | /*! |
949 | \qmlproperty enumeration QtQuick.XmlListModel::XmlListModel::status |
950 | Specifies the model loading status, which can be one of the following: |
951 | |
952 | \list |
953 | \li XmlListModel.Null - No XML data has been set for this model. |
954 | \li XmlListModel.Ready - The XML data has been loaded into the model. |
955 | \li XmlListModel.Loading - The model is in the process of reading and loading XML data. |
956 | \li XmlListModel.Error - An error occurred while the model was loading. See errorString() for details |
957 | about the error. |
958 | \endlist |
959 | |
960 | \sa progress |
961 | |
962 | */ |
963 | QQuickXmlListModel::Status QQuickXmlListModel::status() const |
964 | { |
965 | Q_D(const QQuickXmlListModel); |
966 | return d->status; |
967 | } |
968 | |
969 | /*! |
970 | \qmlproperty real QtQuick.XmlListModel::XmlListModel::progress |
971 | |
972 | This indicates the current progress of the downloading of the XML data |
973 | source. This value ranges from 0.0 (no data downloaded) to |
974 | 1.0 (all data downloaded). If the XML data is not from a remote source, |
975 | the progress becomes 1.0 as soon as the data is read. |
976 | |
977 | Note that when the progress is 1.0, the XML data has been downloaded, but |
978 | it is yet to be loaded into the model at this point. Use the status |
979 | property to find out when the XML data has been read and loaded into |
980 | the model. |
981 | |
982 | \sa status, source |
983 | */ |
984 | qreal QQuickXmlListModel::progress() const |
985 | { |
986 | Q_D(const QQuickXmlListModel); |
987 | return d->progress; |
988 | } |
989 | |
990 | /*! |
991 | \qmlmethod QtQuick.XmlListModel::XmlListModel::errorString() |
992 | |
993 | Returns a string description of the last error that occurred |
994 | if \l status is XmlListModel::Error. |
995 | */ |
996 | QString QQuickXmlListModel::errorString() const |
997 | { |
998 | Q_D(const QQuickXmlListModel); |
999 | return d->errorString; |
1000 | } |
1001 | |
1002 | void QQuickXmlListModel::classBegin() |
1003 | { |
1004 | Q_D(QQuickXmlListModel); |
1005 | d->isComponentComplete = false; |
1006 | |
1007 | QQuickXmlQueryEngine *queryEngine = QQuickXmlQueryEngine::instance(engine: qmlEngine(this)); |
1008 | connect(asender: queryEngine, SIGNAL(queryCompleted(QQuickXmlQueryResult)), |
1009 | SLOT(queryCompleted(QQuickXmlQueryResult))); |
1010 | connect(asender: queryEngine, SIGNAL(error(void*,QString)), |
1011 | SLOT(queryError(void*,QString))); |
1012 | } |
1013 | |
1014 | void QQuickXmlListModel::componentComplete() |
1015 | { |
1016 | Q_D(QQuickXmlListModel); |
1017 | d->isComponentComplete = true; |
1018 | reload(); |
1019 | } |
1020 | |
1021 | /*! |
1022 | \qmlmethod QtQuick.XmlListModel::XmlListModel::reload() |
1023 | |
1024 | Reloads the model. |
1025 | |
1026 | If no key roles have been specified, all existing model |
1027 | data is removed, and the model is rebuilt from scratch. |
1028 | |
1029 | Otherwise, items are only added if the model does not already |
1030 | contain items with matching key role values. |
1031 | |
1032 | \sa {Using key XML roles}, XmlRole::isKey |
1033 | */ |
1034 | void QQuickXmlListModel::reload() |
1035 | { |
1036 | Q_D(QQuickXmlListModel); |
1037 | |
1038 | if (!d->isComponentComplete) |
1039 | return; |
1040 | |
1041 | QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->abort(id: d->queryId); |
1042 | d->queryId = -1; |
1043 | |
1044 | if (d->size < 0) |
1045 | d->size = 0; |
1046 | |
1047 | #if QT_CONFIG(qml_network) |
1048 | if (d->reply) { |
1049 | d->reply->abort(); |
1050 | d->deleteReply(); |
1051 | } |
1052 | #endif |
1053 | |
1054 | if (!d->xml.isEmpty()) { |
1055 | d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery(query: d->query, namespaces: d->namespaces, data: d->xml.toUtf8(), roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); |
1056 | d->notifyQueryStarted(remoteSource: false); |
1057 | |
1058 | } else if (d->src.isEmpty()) { |
1059 | d->queryId = XMLLISTMODEL_CLEAR_ID; |
1060 | d->notifyQueryStarted(remoteSource: false); |
1061 | QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); |
1062 | |
1063 | } else if (QQmlFile::isLocalFile(url: d->src)) { |
1064 | QFile file(QQmlFile::urlToLocalFileOrQrc(d->src)); |
1065 | QByteArray data = file.open(flags: QIODevice::ReadOnly) ? file.readAll() : QByteArray(); |
1066 | d->notifyQueryStarted(remoteSource: false); |
1067 | if (data.isEmpty()) { |
1068 | d->queryId = XMLLISTMODEL_CLEAR_ID; |
1069 | QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); |
1070 | } else { |
1071 | d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery( |
1072 | query: d->query, namespaces: d->namespaces, data, roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); |
1073 | } |
1074 | } else { |
1075 | #if QT_CONFIG(qml_network) |
1076 | d->notifyQueryStarted(remoteSource: true); |
1077 | QNetworkRequest req(d->src); |
1078 | req.setRawHeader(headerName: "Accept" , value: "application/xml,*/*" ); |
1079 | d->reply = qmlContext(this)->engine()->networkAccessManager()->get(request: req); |
1080 | QObject::connect(sender: d->reply, SIGNAL(finished()), receiver: this, SLOT(requestFinished())); |
1081 | QObject::connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), |
1082 | receiver: this, SLOT(requestProgress(qint64,qint64))); |
1083 | #else |
1084 | d->queryId = XMLLISTMODEL_CLEAR_ID; |
1085 | d->notifyQueryStarted(false); |
1086 | QTimer::singleShot(0, this, SLOT(dataCleared())); |
1087 | #endif |
1088 | } |
1089 | } |
1090 | |
1091 | #define XMLLISTMODEL_MAX_REDIRECT 16 |
1092 | |
1093 | #if QT_CONFIG(qml_network) |
1094 | void QQuickXmlListModel::requestFinished() |
1095 | { |
1096 | Q_D(QQuickXmlListModel); |
1097 | |
1098 | d->redirectCount++; |
1099 | if (d->redirectCount < XMLLISTMODEL_MAX_REDIRECT) { |
1100 | QVariant redirect = d->reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute); |
1101 | if (redirect.isValid()) { |
1102 | QUrl url = d->reply->url().resolved(relative: redirect.toUrl()); |
1103 | d->deleteReply(); |
1104 | setSource(url); |
1105 | return; |
1106 | } |
1107 | } |
1108 | d->redirectCount = 0; |
1109 | |
1110 | if (d->reply->error() != QNetworkReply::NoError) { |
1111 | d->errorString = d->reply->errorString(); |
1112 | d->deleteReply(); |
1113 | |
1114 | if (d->size > 0) { |
1115 | beginRemoveRows(parent: QModelIndex(), first: 0, last: d->size - 1); |
1116 | d->data.clear(); |
1117 | d->size = 0; |
1118 | endRemoveRows(); |
1119 | emit countChanged(); |
1120 | } |
1121 | |
1122 | d->status = Error; |
1123 | d->queryId = -1; |
1124 | emit statusChanged(d->status); |
1125 | } else { |
1126 | QByteArray data = d->reply->readAll(); |
1127 | if (data.isEmpty()) { |
1128 | d->queryId = XMLLISTMODEL_CLEAR_ID; |
1129 | QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); |
1130 | } else { |
1131 | d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery(query: d->query, namespaces: d->namespaces, data, roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); |
1132 | } |
1133 | d->deleteReply(); |
1134 | |
1135 | d->progress = 1.0; |
1136 | emit progressChanged(progress: d->progress); |
1137 | } |
1138 | } |
1139 | #endif |
1140 | |
1141 | void QQuickXmlListModel::requestProgress(qint64 received, qint64 total) |
1142 | { |
1143 | Q_D(QQuickXmlListModel); |
1144 | if (d->status == Loading && total > 0) { |
1145 | d->progress = qreal(received)/total; |
1146 | emit progressChanged(progress: d->progress); |
1147 | } |
1148 | } |
1149 | |
1150 | void QQuickXmlListModel::dataCleared() |
1151 | { |
1152 | Q_D(QQuickXmlListModel); |
1153 | QQuickXmlQueryResult r; |
1154 | r.queryId = XMLLISTMODEL_CLEAR_ID; |
1155 | r.size = 0; |
1156 | r.removed << qMakePair(x: 0, y: count()); |
1157 | r.keyRoleResultsCache = d->keyRoleResultsCache; |
1158 | queryCompleted(r); |
1159 | } |
1160 | |
1161 | void QQuickXmlListModel::queryError(void* object, const QString& error) |
1162 | { |
1163 | // Be extra careful, object may no longer exist, it's just an ID. |
1164 | Q_D(QQuickXmlListModel); |
1165 | for (int i=0; i<d->roleObjects.count(); i++) { |
1166 | if (d->roleObjects.at(i) == static_cast<QQuickXmlListModelRole*>(object)) { |
1167 | qmlWarning(me: d->roleObjects.at(i)) << QQuickXmlListModel::tr(s: "invalid query: \"%1\"" ).arg(a: error); |
1168 | return; |
1169 | } |
1170 | } |
1171 | qmlWarning(me: this) << QQuickXmlListModel::tr(s: "invalid query: \"%1\"" ).arg(a: error); |
1172 | } |
1173 | |
1174 | void QQuickXmlListModel::queryCompleted(const QQuickXmlQueryResult &result) |
1175 | { |
1176 | Q_D(QQuickXmlListModel); |
1177 | if (result.queryId != d->queryId) |
1178 | return; |
1179 | |
1180 | int origCount = d->size; |
1181 | bool sizeChanged = result.size != d->size; |
1182 | |
1183 | d->keyRoleResultsCache = result.keyRoleResultsCache; |
1184 | if (d->src.isEmpty() && d->xml.isEmpty()) |
1185 | d->status = Null; |
1186 | else |
1187 | d->status = Ready; |
1188 | d->errorString.clear(); |
1189 | d->queryId = -1; |
1190 | |
1191 | bool hasKeys = false; |
1192 | for (int i=0; i<d->roleObjects.count(); i++) { |
1193 | if (d->roleObjects[i]->isKey()) { |
1194 | hasKeys = true; |
1195 | break; |
1196 | } |
1197 | } |
1198 | if (!hasKeys) { |
1199 | if (origCount > 0) { |
1200 | beginRemoveRows(parent: QModelIndex(), first: 0, last: origCount - 1); |
1201 | endRemoveRows(); |
1202 | } |
1203 | d->size = result.size; |
1204 | d->data = result.data; |
1205 | if (d->size > 0) { |
1206 | beginInsertRows(parent: QModelIndex(), first: 0, last: d->size - 1); |
1207 | endInsertRows(); |
1208 | } |
1209 | } else { |
1210 | for (int i=0; i<result.removed.count(); i++) { |
1211 | const int index = result.removed[i].first; |
1212 | const int count = result.removed[i].second; |
1213 | if (count > 0) { |
1214 | beginRemoveRows(parent: QModelIndex(), first: index, last: index + count - 1); |
1215 | endRemoveRows(); |
1216 | } |
1217 | } |
1218 | d->size = result.size; |
1219 | d->data = result.data; |
1220 | for (int i=0; i<result.inserted.count(); i++) { |
1221 | const int index = result.inserted[i].first; |
1222 | const int count = result.inserted[i].second; |
1223 | if (count > 0) { |
1224 | beginInsertRows(parent: QModelIndex(), first: index, last: index + count - 1); |
1225 | endInsertRows(); |
1226 | } |
1227 | } |
1228 | } |
1229 | if (sizeChanged) |
1230 | emit countChanged(); |
1231 | |
1232 | emit statusChanged(d->status); |
1233 | } |
1234 | |
1235 | QT_END_NAMESPACE |
1236 | |
1237 | #include <qqmlxmllistmodel.moc> |
1238 | |