summaryrefslogtreecommitdiffstats
path: root/examples/demos
diff options
context:
space:
mode:
authorDoris Verria <doris.verria@qt.io>2020-11-01 22:43:55 +0100
committerDoris Verria <doris.verria@qt.io>2020-11-05 12:20:36 +0100
commitaf4d9a658e2bc337856f71a2c9729204daa611a4 (patch)
treedfd359e2a09483ac15cfc58934cf1820c27f1b7b /examples/demos
parent2216c740976ea7a552229f0d001f81fc73e59167 (diff)
Port photoviewer's XmlListModel away from QtXmlPatterns
Remove the qtxmlpatterns dependendency of photoviewer demo and reimplement XmlListModel class to use QXmlStreamReader instead of XPath/XQuery to query Xml. Fixes: QTBUG-88058 Change-Id: If21f3f361c8154bc56d94f37263801b33af8906f Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'examples/demos')
-rw-r--r--examples/demos/photoviewer/CMakeLists.txt16
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml6
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/BusyIndicator.qml2
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/Button.qml2
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/EditableButton.qml2
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml10
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/ProgressBar.qml2
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/RssModel.qml13
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/Tag.qml2
-rw-r--r--examples/demos/photoviewer/PhotoViewerCore/script/script.mjs14
-rw-r--r--examples/demos/photoviewer/doc/src/photoviewer.qdoc69
-rw-r--r--examples/demos/photoviewer/main.qml5
-rw-r--r--examples/demos/photoviewer/photoviewer.pro13
-rw-r--r--examples/demos/shared/xmllistmodel.cpp645
-rw-r--r--examples/demos/shared/xmllistmodel.h264
15 files changed, 1010 insertions, 55 deletions
diff --git a/examples/demos/photoviewer/CMakeLists.txt b/examples/demos/photoviewer/CMakeLists.txt
index 993cb2cdc..15cdb5d0d 100644
--- a/examples/demos/photoviewer/CMakeLists.txt
+++ b/examples/demos/photoviewer/CMakeLists.txt
@@ -19,23 +19,35 @@ find_package(Qt6 COMPONENTS Core)
find_package(Qt6 COMPONENTS Gui)
find_package(Qt6 COMPONENTS Qml)
find_package(Qt6 COMPONENTS Quick)
-find_package(Qt6 COMPONENTS XmlPatterns)
+find_package(Qt6 COMPONENTS QuickControls2 REQUIRED)
+find_package(Qt6 COMPONENTS Network)
qt_add_executable(photoviewer
main.cpp
+ ../shared/xmllistmodel.cpp
+ ../shared/xmllistmodel.h
)
+
set_target_properties(photoviewer PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
+ QT_QML_MODULE_VERSION 1.0
+ QT_QML_MODULE_URI XmlListModel
)
+qt6_qml_type_registration(photoviewer)
+
target_link_libraries(photoviewer PUBLIC
Qt::Core
Qt::Gui
Qt::Qml
Qt::Quick
- Qt::XmlPatterns
+ Qt::Network
+ Qt::QuickControls2
)
+target_include_directories(photoviewer PUBLIC
+ ../shared
+)
# Resources:
set(qmake_immediate_resource_files
diff --git a/examples/demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml b/examples/demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml
index 6b654377a..0d9d52828 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml
@@ -48,9 +48,9 @@
**
****************************************************************************/
-import QtQuick 2.0
-import QtQuick.XmlListModel 2.0
-import QtQml.Models 2.1
+import QtQuick
+import XmlListModel
+import QtQml.Models
Component {
id: albumDelegate
diff --git a/examples/demos/photoviewer/PhotoViewerCore/BusyIndicator.qml b/examples/demos/photoviewer/PhotoViewerCore/BusyIndicator.qml
index 17a7b0d8d..f60cca8c8 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/BusyIndicator.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/BusyIndicator.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
Image {
id: container
diff --git a/examples/demos/photoviewer/PhotoViewerCore/Button.qml b/examples/demos/photoviewer/PhotoViewerCore/Button.qml
index 8744dc2e7..401c35c7d 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/Button.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/Button.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
Item {
id: container
diff --git a/examples/demos/photoviewer/PhotoViewerCore/EditableButton.qml b/examples/demos/photoviewer/PhotoViewerCore/EditableButton.qml
index 2df47c063..82f1679de 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/EditableButton.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/EditableButton.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
Item {
id: container
diff --git a/examples/demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml b/examples/demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml
index b3652701e..6d02e8708 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
import "script/script.mjs" as Script
Package {
@@ -79,8 +79,8 @@ Package {
Rectangle {
id: placeHolder
- property int w: Script.getWidth(content)
- property int h: Script.getHeight(content)
+ property int w: 400
+ property int h: 400
property double s: Script.calculateScale(w, h, photoWrapper.width)
color: 'white'; anchors.centerIn: parent; antialiasing: true
@@ -98,7 +98,7 @@ Package {
BusyIndicator { anchors.centerIn: parent; on: originalImage.status != Image.Ready }
Image {
id: originalImage; antialiasing: true;
- source: "http://" + Script.getImagePath(content); cache: false
+ source: link; cache: false
fillMode: Image.PreserveAspectFit; width: photoWrapper.width; height: photoWrapper.height
}
Image {
@@ -143,7 +143,7 @@ Package {
width: mainWindow.width; height: mainWindow.height
}
PropertyChanges { target: border; opacity: 0 }
- PropertyChanges { target: hqImage; source: listItem.ListView.isCurrentItem ? hq : ""; visible: true }
+ PropertyChanges { target: hqImage; source: listItem.ListView.isCurrentItem ? link : ""; visible: true }
}
]
diff --git a/examples/demos/photoviewer/PhotoViewerCore/ProgressBar.qml b/examples/demos/photoviewer/PhotoViewerCore/ProgressBar.qml
index ee21eb6fc..4b0a5dc73 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/ProgressBar.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/ProgressBar.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
Item {
id: container
diff --git a/examples/demos/photoviewer/PhotoViewerCore/RssModel.qml b/examples/demos/photoviewer/PhotoViewerCore/RssModel.qml
index c19888a40..5fb1cce4e 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/RssModel.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/RssModel.qml
@@ -48,8 +48,8 @@
**
****************************************************************************/
-import QtQuick 2.0
-import QtQuick.XmlListModel 2.0
+import QtQuick
+import XmlListModel
XmlListModel {
property string tags : ""
@@ -57,10 +57,11 @@ XmlListModel {
function encodeTags(x) { return encodeURIComponent(x.replace(' ',',')); }
source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+encodeTags(tags)+"&" : "")
+
query: "/feed/entry"
- namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';"
- XmlRole { name: "title"; query: "title/string()" }
- XmlRole { name: "content"; query: "content/string()" }
- XmlRole { name: "hq"; query: "link[@rel='enclosure']/@href/string()" }
+ roles: [
+ XmlListModelRole { elementName: "title"; attributeName: "" },
+ XmlListModelRole { elementName: "link"; attributeName: "href" }
+ ]
}
diff --git a/examples/demos/photoviewer/PhotoViewerCore/Tag.qml b/examples/demos/photoviewer/PhotoViewerCore/Tag.qml
index bc12d81a9..bdeb02135 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/Tag.qml
+++ b/examples/demos/photoviewer/PhotoViewerCore/Tag.qml
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick
Flipable {
id: flipable
diff --git a/examples/demos/photoviewer/PhotoViewerCore/script/script.mjs b/examples/demos/photoviewer/PhotoViewerCore/script/script.mjs
index a1822197b..0a33a725d 100644
--- a/examples/demos/photoviewer/PhotoViewerCore/script/script.mjs
+++ b/examples/demos/photoviewer/PhotoViewerCore/script/script.mjs
@@ -48,20 +48,6 @@
**
****************************************************************************/
-export function getWidth(string) {
- return (string.match(/width=\"([0-9]+)\"/))[1]
-}
-
-export function getHeight(string) {
- return (string.match(/height=\"([0-9]+)\"/))[1]
-}
-
-export function getImagePath(string) {
- var pattern = /src=\"https?:\/\/(\S+)\"/
- var match = string.match(pattern)
- return match ? match[1] : ""
-}
-
export function calculateScale(width, height, cellSize) {
var widthScale = (cellSize * 1.0) / width
var heightScale = (cellSize * 1.0) / height
diff --git a/examples/demos/photoviewer/doc/src/photoviewer.qdoc b/examples/demos/photoviewer/doc/src/photoviewer.qdoc
index bebeb9b52..8f4742460 100644
--- a/examples/demos/photoviewer/doc/src/photoviewer.qdoc
+++ b/examples/demos/photoviewer/doc/src/photoviewer.qdoc
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
@@ -29,19 +29,20 @@
\title Qt Quick Demo - Photo Viewer
\ingroup qtquickdemos
\example demos/photoviewer
- \brief A QML photo viewer that that uses XmlListModel and XmlRole to
- download Flickr feeds, and Package to display the photos in different views.
+ \brief A QML photo viewer that uses XmlListModel and XmlListModelRole
+ custom QML types to download Flickr feeds, and Package to display the photos
+ in different views.
\image qtquick-demo-photoviewer-small.png
\e{Photo Viewer} demonstrates the following \l{Qt Quick} features:
\list
- \li Using custom types to create screens and screen controls.
+ \li Using custom QML types.
\li Using Qt Quick Controls 1 to create an application window.
\li Using the \l Package type with a \l DelegateModel to provide
delegates with a shared context to multiple views.
- \li Using XML list models to download Flickr feeds.
+ \li Using custom types to download Flickr feeds.
\li Using the \l Flipable type to create labels with different text on
the front and back.
\li Using the PathView, \l Path, PathAttribute, and PathLine types to
@@ -76,11 +77,55 @@
\skipto PhotoViewerCore
\printuntil "
+ We also use the following custom types that are defined in C++:
+
+ \list
+ \li \c XmlListModel
+ \li \c XmlListModelRole
+ \endlist
+
+ To register QML types from C++ we add the QML_ELEMENT macro to the QObject
+ derived classes' definitions like this:
+
+ \quotefromfile demos/shared/xmllistmodel.h
+ \skipto XmlListModelRole
+ \printuntil QML_ELEMENT
+
+ In addition we need to add the following to the CMakeLists.txt file of the
+ example:
+
+ \quotefromfile demos/photoviewer/CMakeLists.txt
+ \skipto set_target_properties
+ \printuntil PROPERTIES
+ \skipto QT_QML_MODULE_VERSION 1.0
+ \printuntil qt6_qml_type_registration(photoviewer)
+
+ To build with qmake, we add \c CONFIG += qmltypes, \c QML_IMPORT_NAME, and
+ \c QML_IMPORT_MAJOR_VERSION to the project file:
+
+ \quotefromfile demos/photoviewer/photoviewer.pro
+ \skipto CONFIG
+ \printuntil qmltypes
+ \skipto QML_IMPORT_NAME
+ \printuntil QML_IMPORT_MAJOR_VERSION
+
+ For more information on defining QML types from C++ see the
+ \l {Defining QML Types from C++} documentation.
+
+ To use the new types, we add an import statement that imports them to the QML
+ files that use the types:
+
+ \quotefromfile demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml
+ \skipto XmlListModel
+ \printuntil import
+
\section1 Creating the Main Window
In main.qml, we use the ApplicationWindow Qt Quick Control to create the app
main window:
+ \quotefromfile demos/photoviewer/main.qml
+ \skipto ApplicationWindow
\printuntil visible
We use a ListModel type with \l ListElement types to display photo albums:
@@ -156,7 +201,7 @@
\printuntil RssModel
\printuntil }
- In RssModel.qml, we use an XmlListModel type as a data source for
+ In RssModel.qml, we use the XmlListModel custom type as a data source for
\l Package objects to download photos from the selected feeds:
\quotefromfile demos/photoviewer/PhotoViewerCore/RssModel.qml
@@ -170,20 +215,16 @@
We use the \c source property to fetch photos that have the specified tags
attached from public Flickr feeds:
- \printuntil namespaceDeclarations
+ \printuntil query
The \c query property specifies that the XmlListModel generates a model item
for each feed entry.
- The \c namespaceDeclarations property specifies that the requested document
- uses the namespace \c{http://www.w3.org/2005/Atom}, which is declared as the
- default namespace.
-
- We use the XmlRole type to specify the model item attributes. Each model
- item has the \c title, \c content, and \c hq attributes that match the
+ We use the XmlListModelRole custom type to specify the model item attributes.
+ Each model item has the \c title and \c link attributes that match the
values of the corresponding feed entry:
- \printuntil hq
+ \printuntil link
\section1 Creating Flipable Labels
diff --git a/examples/demos/photoviewer/main.qml b/examples/demos/photoviewer/main.qml
index 8ed77ad2a..2b9651402 100644
--- a/examples/demos/photoviewer/main.qml
+++ b/examples/demos/photoviewer/main.qml
@@ -48,9 +48,8 @@
**
****************************************************************************/
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-import QtQml.Models 2.1
+import QtQuick
+import QtQuick.Controls
import "PhotoViewerCore"
ApplicationWindow {
diff --git a/examples/demos/photoviewer/photoviewer.pro b/examples/demos/photoviewer/photoviewer.pro
index 234318057..2d3d34aff 100644
--- a/examples/demos/photoviewer/photoviewer.pro
+++ b/examples/demos/photoviewer/photoviewer.pro
@@ -1,9 +1,16 @@
TEMPLATE = app
-QT += qml quick xmlpatterns
-CONFIG += lrelease embed_translations
+QT += qml quick
+CONFIG += lrelease embed_translations qmltypes
-SOURCES += main.cpp
+INCLUDEPATH += ../shared
+
+HEADERS += ../shared/xmllistmodel.h
+SOURCES += main.cpp \
+ ../shared/xmllistmodel.cpp
+
+QML_IMPORT_NAME = XmlListModel
+QML_IMPORT_MAJOR_VERSION = 1
lupdate_only {
SOURCES = *.qml \
diff --git a/examples/demos/shared/xmllistmodel.cpp b/examples/demos/shared/xmllistmodel.cpp
new file mode 100644
index 000000000..8595b6d04
--- /dev/null
+++ b/examples/demos/shared/xmllistmodel.cpp
@@ -0,0 +1,645 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "xmllistmodel.h"
+
+#include <QQmlFile>
+#include <QFile>
+#include <QCoreApplication>
+#include <QMutexLocker>
+
+Q_DECLARE_METATYPE(XmlListModelQueryResult)
+
+QHash<QQmlEngine *, XmlListModelQueryEngine*> XmlListModelQueryEngine::queryEngines;
+QMutex XmlListModelQueryEngine::queryEnginesMutex;
+
+XmlListModelQueryThreadObject::XmlListModelQueryThreadObject(XmlListModelQueryEngine *e)
+ : m_queryEngine(e)
+{
+}
+
+void XmlListModelQueryThreadObject::processJobs()
+{
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+}
+
+bool XmlListModelQueryThreadObject::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ m_queryEngine->processJobs();
+ return true;
+ }
+ return QObject::event(e);
+}
+
+XmlListModelQueryEngine::XmlListModelQueryEngine(QQmlEngine *eng)
+: QThread(eng), m_threadObject(nullptr), m_queryIds(1), m_engine(eng), m_eventLoopQuitHack(nullptr)
+{
+ qRegisterMetaType<XmlListModelQueryResult>("XmlListModelQueryResult");
+
+ m_eventLoopQuitHack = new QObject;
+ m_eventLoopQuitHack->moveToThread(this);
+
+ connect(m_eventLoopQuitHack, &QObject::destroyed,
+ this, &QThread::quit, Qt::DirectConnection);
+ start(QThread::IdlePriority);
+}
+
+XmlListModelQueryEngine::~XmlListModelQueryEngine()
+{
+ queryEnginesMutex.lock();
+ queryEngines.remove(m_engine);
+ queryEnginesMutex.unlock();
+
+ m_eventLoopQuitHack->deleteLater();
+ wait();
+}
+
+void XmlListModelQueryEngine::abort(int id)
+{
+ QMutexLocker ml(&m_mutex);
+ if (id != -1)
+ m_cancelledJobs.insert(id);
+}
+
+void XmlListModelQueryEngine::run()
+{
+ m_mutex.lock();
+ m_threadObject = new XmlListModelQueryThreadObject(this);
+ m_mutex.unlock();
+
+ processJobs();
+ exec();
+
+ delete m_threadObject;
+ m_threadObject = nullptr;
+}
+
+XmlListModelQueryEngine *XmlListModelQueryEngine::instance(QQmlEngine *engine)
+{
+ QMutexLocker ml(&queryEnginesMutex);
+ XmlListModelQueryEngine *queryEng = queryEngines.value(engine);
+ if (!queryEng) {
+ queryEng = new XmlListModelQueryEngine(engine);
+ queryEngines.insert(engine, queryEng);
+ }
+
+ return queryEng;
+}
+
+int XmlListModelQueryEngine::doQuery(QString query, QByteArray data, QList<XmlListModelRole *>* roles)
+{
+ {
+ QMutexLocker m1(&m_mutex);
+ m_queryIds.ref();
+ if (m_queryIds.loadRelaxed() <= 0)
+ m_queryIds.storeRelaxed(1);
+ }
+
+ XmlListModelQueryJob job;
+ job.queryId = m_queryIds.loadRelaxed();
+ job.data = data;
+ job.query = query;
+
+ for (int i = 0; i < roles->count(); i++) {
+ if (!roles->at(i)->isValid()) {
+ job.roleNames << QString();
+ job.roleQueries << QString();
+ continue;
+ }
+ job.roleNames << roles->at(i)->elementName();
+ job.roleQueries << roles->at(i)->attributeName();
+ job.roleQueryErrorId << static_cast<void*>(roles->at(i));
+ }
+
+ {
+ QMutexLocker ml(&m_mutex);
+ m_jobs.append(job);
+ if (m_threadObject)
+ m_threadObject->processJobs();
+ }
+
+ return job.queryId;
+}
+
+void XmlListModelQueryEngine::processJobs()
+{
+ QMutexLocker locker(&m_mutex);
+
+ while (true) {
+ if (m_jobs.isEmpty())
+ return;
+
+ XmlListModelQueryJob currentJob = m_jobs.takeLast();
+ while (m_cancelledJobs.remove(currentJob.queryId)) {
+ if (m_jobs.isEmpty())
+ return;
+ currentJob = m_jobs.takeLast();
+ }
+
+ locker.unlock();
+ processQuery(&currentJob);
+ locker.relock();
+ }
+}
+
+void XmlListModelQueryEngine::processQuery(XmlListModelQueryJob *job)
+{
+ XmlListModelQueryResult result;
+ result.queryId = job->queryId;
+ doQueryJob(job, &result);
+
+ QMutexLocker ml(&m_mutex);
+ if (m_cancelledJobs.contains(job->queryId)) {
+ m_cancelledJobs.remove(job->queryId);
+ } else {
+ ml.unlock();
+ Q_EMIT queryCompleted(result);
+ }
+}
+
+void XmlListModelQueryEngine::doQueryJob(XmlListModelQueryJob *currentJob, XmlListModelQueryResult *currentResult)
+{
+ Q_ASSERT(currentJob->queryId != -1);
+
+ QByteArray data(currentJob->data);
+ QXmlStreamReader reader;
+ reader.addData(data);
+
+ QStringList items = currentJob->query.split(QLatin1Char('/'), Qt::SkipEmptyParts);
+
+ while (!reader.atEnd()) {
+ int i = 0;
+ while (i < items.count()) {
+ if (reader.readNextStartElement()) {
+ if (reader.name() == items.at(i)) {
+ if (i != items.count() - 1) {
+ i++;
+ continue;
+ } else {
+ processElement(currentJob, currentResult, items.at(i), reader);
+ }
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+ if (reader.tokenType() == QXmlStreamReader::Invalid) {
+ reader.readNext();
+ break;
+ }
+ else if (reader.hasError()) {
+ reader.raiseError();
+ break;
+ }
+ }
+ }
+}
+
+void XmlListModelQueryEngine::processElement(XmlListModelQueryJob *currentJob, XmlListModelQueryResult*& currentResult, QString element, QXmlStreamReader &reader)
+{
+ if (!reader.isStartElement() || reader.name() != element)
+ return;
+
+ const QStringList &roleQueries = currentJob->roleQueries;
+ const QStringList &roleNames = currentJob->roleNames;
+ QHash<int, QString> resultList;
+
+
+ while (reader.readNextStartElement()) {
+
+ if (roleNames.contains(reader.name()) && !reader.name().isEmpty()) {
+ bool hasLink = false;
+ bool hasJpegImageLink = false;
+
+ QString roleResult;
+ const int index = roleNames.indexOf(reader.name());
+
+ if (!roleQueries.at(index).isEmpty()) {
+ if (reader.attributes().hasAttribute(roleQueries.at(index))) {
+ if (roleQueries.at(index) == "href") {
+ hasLink = true;
+ if (reader.attributes().value("type").toString() == "image/jpeg")
+ hasJpegImageLink = true;
+ }
+ roleResult = reader.attributes().value(roleQueries.at(index)).toString();
+ } else {
+ Q_EMIT error(currentJob->roleQueryErrorId.at(index), roleQueries[index]);
+ }
+ } else if (!roleNames.at(index).isEmpty()) {
+ roleResult = reader.readElementText();
+ }
+
+ if (hasLink && !hasJpegImageLink)
+ reader.skipCurrentElement();
+ else
+ resultList[index] = roleResult;
+
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+
+ if (resultList.count() <= 0)
+ resultList = QHash<int, QString>();
+ currentResult->data << resultList;
+}
+
+QString XmlListModelRole::elementName() const { return m_elementName; }
+
+void XmlListModelRole::setElementName(const QString &name)
+{
+ if (name == m_elementName)
+ return;
+ m_elementName = name;
+ Q_EMIT elementNameChanged();
+}
+
+QString XmlListModelRole::attributeName() const { return m_attributeName; }
+
+void XmlListModelRole::setAttributeName(const QString &attributeName)
+{
+ if (m_attributeName == attributeName)
+ return;
+ m_attributeName = attributeName;
+ Q_EMIT attributeNameChanged();
+}
+
+bool XmlListModelRole::isValid() const
+{
+ return !m_elementName.isEmpty();
+}
+
+XmlListModel::XmlListModel(QObject *parent) : QAbstractListModel(parent)
+ , m_isComponentComplete(true), m_size(0), m_highestRole(Qt::UserRole)
+#if QT_CONFIG(qml_network)
+ , m_reply(nullptr)
+#endif
+ , m_status(XmlListModel::Null), m_progress(0.0)
+ , m_queryId(-1), m_roleObjects(), m_redirectCount(0)
+{
+}
+
+QModelIndex XmlListModel::index(int row, int column, const QModelIndex &parent) const
+{
+ return !parent.isValid() && column == 0 && row >= 0 && m_size
+ ? createIndex(row, column)
+ : QModelIndex();
+}
+
+int XmlListModel::rowCount(const QModelIndex &parent) const
+{
+ return !parent.isValid() ? m_size : 0;
+}
+
+QVariant XmlListModel::data(const QModelIndex &index, int role) const
+{
+ const int roleIndex = m_roles.indexOf(role);
+ return (roleIndex == -1 || !index.isValid())
+ ? QVariant()
+ : m_data.value(index.row()).value(roleIndex);
+}
+
+QHash<int, QByteArray> XmlListModel::roleNames() const
+{
+ QHash<int,QByteArray> roleNames;
+ for (int i = 0; i < m_roles.count(); ++i)
+ roleNames.insert(m_roles.at(i), m_roleNames.at(i).toUtf8());
+ return roleNames;
+}
+
+int XmlListModel::count() const
+{
+ return m_size;
+}
+
+QUrl XmlListModel::source() const
+{
+ return m_source;
+}
+
+void XmlListModel::setSource(const QUrl &src)
+{
+ if (m_source != src) {
+ m_source = src;
+ reload();
+ Q_EMIT sourceChanged();
+ }
+}
+
+QString XmlListModel::query() const
+{
+ return m_query;
+}
+
+void XmlListModel::setQuery(const QString &query)
+{
+ if (!query.startsWith(QLatin1Char('/'))) {
+ qmlWarning(this) << QCoreApplication::translate("XmlListModelRoleList", "An XmlListModel query must start with '/'");
+ return;
+ }
+
+ if (m_query != query) {
+ m_query = query;
+ reload();
+ Q_EMIT queryChanged();
+ }
+}
+
+QQmlListProperty<XmlListModelRole> XmlListModel::roleObjects()
+{
+ QQmlListProperty<XmlListModelRole> list(this, &m_roleObjects);
+ list.append = &XmlListModel::appendRole;
+ list.clear = &XmlListModel::clearRole;
+ return list;
+}
+
+void XmlListModel::appendRole(XmlListModelRole* role)
+{
+ int i = m_roleObjects.count();
+ m_roleObjects.append(role);
+ if (m_roleNames.contains(role->elementName())) {
+ qmlWarning(role) << XmlListModel::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->elementName());
+ return;
+ }
+ m_roles.insert(i, m_highestRole);
+ m_roleNames.insert(i, role->elementName());
+ ++m_highestRole;
+}
+
+void XmlListModel::clearRole()
+{
+ m_roles.clear();
+ m_roleNames.clear();
+ m_roleObjects.clear();
+}
+
+void XmlListModel::appendRole(QQmlListProperty<XmlListModelRole>* list, XmlListModelRole* role)
+{
+ reinterpret_cast<XmlListModel*>(list->object)->appendRole(role);
+}
+
+void XmlListModel::clearRole(QQmlListProperty<XmlListModelRole>* list)
+{
+ reinterpret_cast<XmlListModel*>(list->object)->clearRole();
+}
+
+XmlListModel::Status XmlListModel::status() const
+{
+ return m_status;
+}
+
+qreal XmlListModel::progress() const
+{
+ return m_progress;
+}
+
+QString XmlListModel::errorString() const
+{
+ return m_errorString;
+}
+
+void XmlListModel::classBegin()
+{
+ m_isComponentComplete = false;
+
+ XmlListModelQueryEngine *queryEngine = XmlListModelQueryEngine::instance(qmlEngine(this));
+ connect(queryEngine, &XmlListModelQueryEngine::queryCompleted,
+ this, &XmlListModel::queryCompleted);
+ connect(queryEngine, &XmlListModelQueryEngine::error,
+ this, &XmlListModel::queryError);
+}
+
+void XmlListModel::componentComplete()
+{
+ m_isComponentComplete = true;
+ reload();
+}
+
+void XmlListModel::reload()
+{
+ if (!m_isComponentComplete)
+ return;
+
+ XmlListModelQueryEngine::instance(qmlEngine(this))->abort(m_queryId);
+ m_queryId = -1;
+
+ if (m_size < 0)
+ m_size = 0;
+
+#if QT_CONFIG(qml_network)
+ if (m_reply) {
+ m_reply->abort();
+ deleteReply();
+ }
+#endif
+
+ if (m_source.isEmpty()) {
+ m_queryId = 0;
+ notifyQueryStarted(false);
+ QTimer::singleShot(0, this, &XmlListModel::dataCleared);
+ }
+ else if (QQmlFile::isLocalFile(m_source)) {
+ QFile file(QQmlFile::urlToLocalFileOrQrc(m_source));
+ QByteArray data = file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
+ notifyQueryStarted(false);
+ if (data.isEmpty()) {
+ m_queryId = 0;
+ QTimer::singleShot(0, this, &XmlListModel::dataCleared);
+ } else {
+ m_queryId = XmlListModelQueryEngine::instance(qmlEngine(this))->doQuery(
+ m_query, data, &m_roleObjects);
+ }
+ } else {
+#if QT_CONFIG(qml_network)
+ notifyQueryStarted(true);
+ QNetworkRequest req(m_source);
+ req.setRawHeader("Accept", "application/xml,*/*");
+ m_reply = qmlContext(this)->engine()->networkAccessManager()->get(req);
+
+ QObject::connect(m_reply, &QNetworkReply::finished,
+ this, &XmlListModel::requestFinished);
+ QObject::connect(m_reply, &QNetworkReply::downloadProgress,
+ this, &XmlListModel::requestProgress);
+#else
+ m_queryId = 0;
+ notifyQueryStarted(false);
+ QTimer::singleShot(0, this, &XmlListModel::dataCleared);
+#endif
+ }
+}
+
+#define XMLLISTMODEL_MAX_REDIRECT 16
+
+#if QT_CONFIG(qml_network)
+void XmlListModel::requestFinished()
+{
+ m_redirectCount++;
+ if (m_redirectCount < XMLLISTMODEL_MAX_REDIRECT) {
+ QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirect.isValid()) {
+ QUrl url = m_reply->url().resolved(redirect.toUrl());
+ deleteReply();
+ setSource(url);
+ return;
+ }
+ }
+ m_redirectCount = 0;
+
+ if (m_reply->error() != QNetworkReply::NoError) {
+ m_errorString = m_reply->errorString();
+ deleteReply();
+
+ if (m_size > 0) {
+ beginRemoveRows(QModelIndex(), 0, m_size - 1);
+ m_data.clear();
+ m_size = 0;
+ endRemoveRows();
+ Q_EMIT countChanged();
+ }
+
+ m_status = Error;
+ m_queryId = -1;
+ Q_EMIT statusChanged(m_status);
+ } else {
+ QByteArray data = m_reply->readAll();
+ if (data.isEmpty()) {
+ m_queryId = 0;
+ QTimer::singleShot(0, this, &XmlListModel::dataCleared);
+ } else {
+ m_queryId = XmlListModelQueryEngine::instance(qmlEngine(this))->doQuery(m_query, data, &m_roleObjects);
+ }
+ deleteReply();
+
+ m_progress = 1.0;
+ Q_EMIT progressChanged(m_progress);
+ }
+}
+
+void XmlListModel::deleteReply()
+{
+ if (m_reply) {
+ QObject::disconnect(m_reply, 0, this, 0);
+ m_reply->deleteLater();
+ m_reply = nullptr;
+ }
+}
+#endif
+
+void XmlListModel::requestProgress(qint64 received, qint64 total)
+{
+ if (m_status == Loading && total > 0) {
+ m_progress = qreal(received)/total;
+ Q_EMIT progressChanged(m_progress);
+ }
+}
+
+void XmlListModel::dataCleared()
+{
+ XmlListModelQueryResult r;
+ r.queryId = 0;
+ queryCompleted(r);
+}
+
+void XmlListModel::queryError(void* object, const QString& error)
+{
+ for (int i = 0; i < m_roleObjects.count(); i++) {
+ if (m_roleObjects.at(i) == static_cast<XmlListModelRole*>(object)) {
+ qmlWarning(m_roleObjects.at(i)) << XmlListModel::tr("invalid query: \"%1\"").arg(error);
+ return;
+ }
+ }
+ qmlWarning(this) << XmlListModel::tr("invalid query: \"%1\"").arg(error);
+}
+
+void XmlListModel::queryCompleted(const XmlListModelQueryResult &result)
+{
+ if (result.queryId != m_queryId)
+ return;
+
+ int origCount = m_size;
+ bool sizeChanged = result.data.count() != m_size;
+
+ if (m_source.isEmpty())
+ m_status = Null;
+ else
+ m_status = Ready;
+ m_errorString.clear();
+ m_queryId = -1;
+
+ if (origCount > 0) {
+ beginRemoveRows(QModelIndex(), 0, origCount - 1);
+ endRemoveRows();
+ }
+ m_size = result.data.count();
+ m_data = result.data;
+
+ if (m_size > 0) {
+ beginInsertRows(QModelIndex(), 0, m_size - 1);
+ endInsertRows();
+ }
+
+ if (sizeChanged)
+ Q_EMIT countChanged();
+
+ Q_EMIT statusChanged(m_status);
+}
+
+void XmlListModel::notifyQueryStarted(bool remoteSource)
+{
+ m_progress = remoteSource ? 0.0 : 1.0;
+ m_status = XmlListModel::Loading;
+ m_errorString.clear();
+ Q_EMIT progressChanged(m_progress);
+ Q_EMIT statusChanged(m_status);
+}
+
+
+
+
diff --git a/examples/demos/shared/xmllistmodel.h b/examples/demos/shared/xmllistmodel.h
new file mode 100644
index 000000000..2ec23e3df
--- /dev/null
+++ b/examples/demos/shared/xmllistmodel.h
@@ -0,0 +1,264 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef XMLLISTMODEL_H
+#define XMLLISTMODEL_H
+
+#include <QtQml>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QAbstractItemModel>
+#include <QThread>
+#include <QByteArray>
+#include <QMap>
+#include <QMutex>
+#include <QHash>
+#include <QStringList>
+#include <QTimer>
+#include <QUrl>
+#if QT_CONFIG(qml_network)
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QXmlStreamReader>
+#endif
+
+
+class QQmlContext;
+struct XmlListModelQueryJob
+{
+ int queryId;
+ QByteArray data;
+ QString query;
+ QStringList roleNames;
+ QStringList roleQueries;
+ QList<void*> roleQueryErrorId;
+};
+struct XmlListModelQueryResult {
+ int queryId;
+ QList<QHash<int, QString> > data;
+};
+
+class XmlListModelRole : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString elementName READ elementName WRITE setElementName NOTIFY elementNameChanged)
+ Q_PROPERTY(QString attributeName READ attributeName WRITE setAttributeName NOTIFY attributeNameChanged)
+ QML_ELEMENT
+
+public:
+ XmlListModelRole() = default;
+ ~XmlListModelRole() = default;
+
+ QString elementName() const;
+ void setElementName(const QString &name);
+ QString attributeName() const;
+ void setAttributeName(const QString &attributeName);
+ bool isValid() const;
+
+signals:
+ void elementNameChanged();
+ void attributeNameChanged();
+
+ private:
+ QString m_elementName;
+ QString m_attributeName;
+
+};
+
+class XmlListModel : public QAbstractListModel, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+
+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+ Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
+ Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
+ Q_PROPERTY(QQmlListProperty<XmlListModelRole> roles READ roleObjects)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ QML_ELEMENT
+
+public:
+ XmlListModel(QObject *parent = nullptr);
+ ~XmlListModel() = default;
+
+ QModelIndex index(int row, int column, const QModelIndex &parent) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ int count() const;
+
+ QUrl source() const;
+ void setSource(const QUrl&);
+
+ QString query() const;
+ void setQuery(const QString&);
+
+ QQmlListProperty<XmlListModelRole> roleObjects();
+
+ void appendRole(XmlListModelRole*);
+ void clearRole();
+
+ enum Status { Null, Ready, Loading, Error };
+ Q_ENUM(Status)
+ Status status() const;
+ qreal progress() const;
+
+ Q_INVOKABLE QString errorString() const;
+
+ void classBegin() override;
+ void componentComplete() override;
+
+Q_SIGNALS:
+ void statusChanged(XmlListModel::Status);
+ void progressChanged(qreal progress);
+ void countChanged();
+ void sourceChanged();
+ void queryChanged();
+
+public Q_SLOTS:
+ void reload();
+
+private Q_SLOTS:
+#if QT_CONFIG(qml_network)
+ void requestFinished();
+#endif
+ void requestProgress(qint64,qint64);
+ void dataCleared();
+ void queryCompleted(const XmlListModelQueryResult &);
+ void queryError(void* object, const QString& error);
+
+private:
+ Q_DISABLE_COPY(XmlListModel)
+
+ void notifyQueryStarted(bool remoteSource);
+
+ static void appendRole(QQmlListProperty<XmlListModelRole>*, XmlListModelRole*);
+ static void clearRole(QQmlListProperty<XmlListModelRole>*);
+
+#if QT_CONFIG(qml_network)
+ void deleteReply();
+
+ QNetworkReply *m_reply;
+#endif
+
+ int m_size;
+ QUrl m_source;
+ QString m_query;
+ QStringList m_roleNames;
+ QList<int> m_roles;
+ QList<XmlListModelRole *> m_roleObjects;
+ QList<QHash<int, QString> > m_data;
+ bool m_isComponentComplete;
+ Status m_status;
+ QString m_errorString;
+ qreal m_progress;
+ int m_queryId;
+ int m_redirectCount;
+ int m_highestRole;
+
+};
+
+class XmlListModelQueryThreadObject;
+
+class XmlListModelQueryEngine : public QThread
+{
+ Q_OBJECT
+public:
+ XmlListModelQueryEngine(QQmlEngine *eng);
+ ~XmlListModelQueryEngine();
+
+ int doQuery(QString query, QByteArray data, QList<XmlListModelRole *>* roles);
+ void abort(int id);
+
+ void processJobs();
+
+ static XmlListModelQueryEngine *instance(QQmlEngine *engine);
+
+signals:
+ void queryCompleted(const XmlListModelQueryResult &);
+ void error(void*, const QString&);
+
+protected:
+ void run() override;
+
+private:
+ void processQuery(XmlListModelQueryJob *job);
+ void doQueryJob(XmlListModelQueryJob *job, XmlListModelQueryResult *currentResult);
+ void processElement(XmlListModelQueryJob *currentJob, XmlListModelQueryResult*& currentResult, QString element, QXmlStreamReader &reader);
+
+ QMutex m_mutex;
+ XmlListModelQueryThreadObject *m_threadObject;
+ QList<XmlListModelQueryJob> m_jobs;
+ QSet<int> m_cancelledJobs;
+ QAtomicInt m_queryIds;
+
+ QQmlEngine *m_engine;
+ QObject *m_eventLoopQuitHack;
+
+ static QHash<QQmlEngine *, XmlListModelQueryEngine*> queryEngines;
+ static QMutex queryEnginesMutex;
+};
+
+class XmlListModelQueryThreadObject : public QObject
+{
+ Q_OBJECT
+public:
+ XmlListModelQueryThreadObject(XmlListModelQueryEngine *);
+
+ void processJobs();
+ bool event(QEvent *e) override;
+
+private:
+ XmlListModelQueryEngine *m_queryEngine;
+};
+
+#endif