summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/qtconcurrent/imagescaling/CMakeLists.txt3
-rw-r--r--examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.pngbin23710 -> 21049 bytes
-rw-r--r--examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc140
-rw-r--r--examples/qtconcurrent/imagescaling/downloaddialog.cpp87
-rw-r--r--examples/qtconcurrent/imagescaling/downloaddialog.h75
-rw-r--r--examples/qtconcurrent/imagescaling/downloaddialog.ui119
-rw-r--r--examples/qtconcurrent/imagescaling/imagescaling.cpp221
-rw-r--r--examples/qtconcurrent/imagescaling/imagescaling.h29
-rw-r--r--examples/qtconcurrent/imagescaling/imagescaling.pro12
9 files changed, 618 insertions, 68 deletions
diff --git a/examples/qtconcurrent/imagescaling/CMakeLists.txt b/examples/qtconcurrent/imagescaling/CMakeLists.txt
index b1065e877b..caca16da12 100644
--- a/examples/qtconcurrent/imagescaling/CMakeLists.txt
+++ b/examples/qtconcurrent/imagescaling/CMakeLists.txt
@@ -19,8 +19,10 @@ find_package(Qt6 COMPONENTS Core)
find_package(Qt6 COMPONENTS Gui)
find_package(Qt6 COMPONENTS Concurrent)
find_package(Qt6 COMPONENTS Widgets)
+find_package(Qt6 COMPONENTS Network)
qt_add_executable(imagescaling
+ downloaddialog.cpp downloaddialog.h downloaddialog.ui
imagescaling.cpp imagescaling.h
main.cpp
)
@@ -32,6 +34,7 @@ target_link_libraries(imagescaling PUBLIC
Qt::Concurrent
Qt::Core
Qt::Gui
+ Qt::Network
Qt::Widgets
)
diff --git a/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png b/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png
index 7c6794132a..a3860e1974 100644
--- a/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png
+++ b/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png
Binary files differ
diff --git a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
index 0134f0f8e8..a919b7e2ed 100644
--- a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
+++ b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 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.
@@ -28,10 +28,142 @@
/*!
\example imagescaling
\title Image Scaling Example
- \brief Demonstrates how to asynchronously scale images.
+ \brief Demonstrates how to asynchronously download and scale images.
\ingroup qtconcurrentexamples
\image imagescaling_example.png
- The QtConcurrent Map example shows how to use the asynchronous
- QtConcurrent API to load and scale a collection of images.
+ This example shows how to use the QFuture and QPromise classes to download a
+ collection of images from the network and scale them, without blocking the UI.
+
+ The application consists of the the following steps:
+
+ \list 1
+ \li Download images form the list of URLs specified by the user.
+ \li Scale the images.
+ \li Show the scaled images in a grid layout.
+ \endlist
+
+ Let's start with the download:
+
+ \snippet imagescaling/imagescaling.cpp 8
+
+ The \c download() method takes a list of URLs and returns a QFuture. The QFuture
+ stores the byte array data received for each downloaded image. To store the data
+ inside the QFuture, we create a QPromise object and report that it has started to
+ indicate the start of the download:
+
+ \snippet imagescaling/imagescaling.cpp 9
+ \dots
+ \snippet imagescaling/imagescaling.cpp 13
+
+ The future associated with the promise is returned to the caller.
+
+ Without going into details yet, let's note that the promise object is wrapped
+ inside a QSharedPointer. This will be explained later.
+
+ We use QNetworkAccessManager to send network requests and download data for each
+ url:
+
+ \snippet imagescaling/imagescaling.cpp 10
+
+ And here starts the interesting part:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 11
+ \dots
+
+ Instead of connecting to QNetworkReply's signals using the QObject::connect()
+ method, we use QtFuture::connect(). It works similar to QObject::connect(), but
+ returns a QFuture object, that becomes available as soon as the
+ QNetworkReply::finished() signal is emitted. This allows us to attach continuations
+ and failure handlers, as it is done in the example.
+
+ In the continuation attached via \b{.then()}, we check if the user has requested to
+ cancel the download. If that's the case, we stop processing the request. By calling
+ the \c QPromise::finish() method, we notify the user that processing has been finished.
+ In case the network request has ended with an error, we throw an exception. The
+ exception will be handled in the failure handler attached using the \b{.onFailed()}
+ method. Note that we have two failure handlers: the first one captures the network
+ errors, the second one all other exceptions thrown during the execution. Both handlers
+ save the exception inside the promise object (to be handled by the caller of the
+ \c download() method) and report that the computation has finished. Also note that,
+ for simplicity, in case of an error we interrupt all pending downloads.
+
+ If the request has not been canceled and no error occurred, we read the data from
+ the network reply and add it to the list of results of the promise object:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 12
+ \dots
+
+ If the number of results stored inside the promise object is equal to the number
+ of the \c {url}s to be downloaded, there are no more requests to process, so we also
+ report that the promise has finished.
+
+ As mentioned earlier, we've wrapped the promise inside a QSharedPointer.
+ Since the promise object is shared between handlers connected to each network reply,
+ we need to copy and use the promise object in multiple places simultaneously. Hence,
+ a QSharedPointer is used.
+
+ \c download() method is called from the \c QImage::process method. It is invoked
+ when the user presses the \e {"Add URLs"} button:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 1
+ \dots
+
+ After clearing the possible leftovers from previous download, we create a dialog
+ so that the user can specify the URLs for the images to download. Based on the
+ specified URL count, we initialize the layout where the images will be shown and
+ start the download. The future returned by the \c download() method is saved, so that
+ the user can cancel the download if needed:
+
+ \snippet imagescaling/imagescaling.cpp 3
+ \dots
+
+ Next, we attach a continuation to handle the scaling step:
+
+ \snippet imagescaling/imagescaling.cpp 4
+ \dots
+
+ Since the scaling may be computationally heavy, and we don't want to block the main
+ thread, we pass the \c QtFuture::Launch::Async option, to launch the scaling step in
+ a new thread.
+
+ The \c scaled() method returns a list of the scaled images to the next step, which
+ takes care of showing images in the layout:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 5
+ \dots
+
+ Note that showImages() needs to be invoked from the main thread, so we call it through
+ QMetaObject::invokeMethod().
+
+ Then we add cancellation and failure handlers:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 6
+
+ The handler attached via the \c .onCanceled() method will be called if the user has
+ pressed the \e "Cancel" button:
+
+ \dots
+ \snippet imagescaling/imagescaling.cpp 2
+ \dots
+
+ The \c cancel() method simply aborts all the pending requests:
+
+ \snippet imagescaling/imagescaling.cpp 7
+
+ The handlers attached via \c .onFailed() method will be called in case an
+ error occurred during one of the previous steps. For example, if a network error
+ has been saved inside the promise during the download step, it will be propagated to
+ the handler that takes \c QNetworkReply::NetworkError as argument. A failure can
+ happen also during the scaling step:
+
+ \snippet imagescaling/imagescaling.cpp 14
+
+ The rest of the code is straightforward, you can check the example project for
+ more details.
*/
diff --git a/examples/qtconcurrent/imagescaling/downloaddialog.cpp b/examples/qtconcurrent/imagescaling/downloaddialog.cpp
new file mode 100644
index 0000000000..2f2f901b0e
--- /dev/null
+++ b/examples/qtconcurrent/imagescaling/downloaddialog.cpp
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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 "downloaddialog.h"
+#include "ui_downloaddialog.h"
+
+#include <QUrl>
+
+DownloadDialog::DownloadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::DownloadDialog)
+{
+ ui->setupUi(this);
+
+ ui->urlLineEdit->setPlaceholderText(tr("Enter the URL of an image to download"));
+
+ connect(ui->addUrlButton, &QPushButton::clicked, this, [this] {
+ const auto text = ui->urlLineEdit->text();
+ if (!text.isEmpty()) {
+ ui->urlListWidget->addItem(text);
+ ui->urlLineEdit->clear();
+ }
+ });
+ connect(ui->urlListWidget, &QListWidget::itemSelectionChanged, this, [this] {
+ ui->removeUrlButton->setEnabled(!ui->urlListWidget->selectedItems().empty());
+ });
+ connect(ui->clearUrlsButton, &QPushButton::clicked, ui->urlListWidget, &QListWidget::clear);
+ connect(ui->removeUrlButton, &QPushButton::clicked, this,
+ [this] { qDeleteAll(ui->urlListWidget->selectedItems()); });
+}
+
+DownloadDialog::~DownloadDialog()
+{
+ delete ui;
+}
+
+QList<QUrl> DownloadDialog::getUrls() const
+{
+ QList<QUrl> urls;
+ for (auto row = 0; row < ui->urlListWidget->count(); ++row)
+ urls.push_back(QUrl(ui->urlListWidget->item(row)->text()));
+ return urls;
+}
diff --git a/examples/qtconcurrent/imagescaling/downloaddialog.h b/examples/qtconcurrent/imagescaling/downloaddialog.h
new file mode 100644
index 0000000000..9e5d478d7d
--- /dev/null
+++ b/examples/qtconcurrent/imagescaling/downloaddialog.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** 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 DOWNLOADDIALOG_H
+#define DOWNLOADDIALOG_H
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class DownloadDialog;
+}
+QT_END_NAMESPACE
+
+class DownloadDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit DownloadDialog(QWidget *parent = nullptr);
+ ~DownloadDialog();
+
+ QList<QUrl> getUrls() const;
+
+private:
+ Ui::DownloadDialog *ui;
+};
+
+#endif // DOWNLOADDIALOG_H
diff --git a/examples/qtconcurrent/imagescaling/downloaddialog.ui b/examples/qtconcurrent/imagescaling/downloaddialog.ui
new file mode 100644
index 0000000000..c85a063568
--- /dev/null
+++ b/examples/qtconcurrent/imagescaling/downloaddialog.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadDialog</class>
+ <widget class="QDialog" name="DownloadDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>489</width>
+ <height>333</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLineEdit" name="urlLineEdit"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="urlListWidget"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPushButton" name="addUrlButton">
+ <property name="text">
+ <string>Add URL</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeUrlButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove URL</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clearUrlsButton">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DownloadDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DownloadDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/qtconcurrent/imagescaling/imagescaling.cpp b/examples/qtconcurrent/imagescaling/imagescaling.cpp
index 65ec16e383..59664f8a58 100644
--- a/examples/qtconcurrent/imagescaling/imagescaling.cpp
+++ b/examples/qtconcurrent/imagescaling/imagescaling.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
@@ -48,106 +48,219 @@
**
****************************************************************************/
#include "imagescaling.h"
+#include "downloaddialog.h"
+
+#include <QNetworkReply>
#include <qmath.h>
#include <functional>
-Images::Images(QWidget *parent)
- : QWidget(parent)
+Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog())
{
- setWindowTitle(tr("Image loading and scaling example"));
+ setWindowTitle(tr("Image downloading and scaling example"));
resize(800, 600);
- imageScaling = new QFutureWatcher<QImage>(this);
- connect(imageScaling, &QFutureWatcher<QImage>::resultReadyAt, this, &Images::showImage);
- connect(imageScaling, &QFutureWatcher<QImage>::finished, this, &Images::finished);
-
- openButton = new QPushButton(tr("Open Images"));
- connect(openButton, &QPushButton::clicked, this, &Images::open);
+ addUrlsButton = new QPushButton(tr("Add URLs"));
+//! [1]
+ connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
+//! [1]
cancelButton = new QPushButton(tr("Cancel"));
cancelButton->setEnabled(false);
- connect(cancelButton, &QPushButton::clicked, imageScaling, &QFutureWatcher<QImage>::cancel);
-
- pauseButton = new QPushButton(tr("Pause/Resume"));
- pauseButton->setEnabled(false);
- connect(pauseButton, &QPushButton::clicked, imageScaling, &QFutureWatcher<QImage>::toggleSuspended);
+//! [2]
+ connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
+//! [2]
QHBoxLayout *buttonLayout = new QHBoxLayout();
- buttonLayout->addWidget(openButton);
+ buttonLayout->addWidget(addUrlsButton);
buttonLayout->addWidget(cancelButton);
- buttonLayout->addWidget(pauseButton);
buttonLayout->addStretch();
+ statusBar = new QStatusBar();
+
imagesLayout = new QGridLayout();
mainLayout = new QVBoxLayout();
mainLayout->addLayout(buttonLayout);
mainLayout->addLayout(imagesLayout);
mainLayout->addStretch();
+ mainLayout->addWidget(statusBar);
setLayout(mainLayout);
}
Images::~Images()
{
- imageScaling->cancel();
- imageScaling->waitForFinished();
+ cancel();
+}
+
+//! [3]
+void Images::process()
+{
+ // Clean previous state
+ replies.clear();
+
+ if (downloadDialog->exec() == QDialog::Accepted) {
+
+ const auto urls = downloadDialog->getUrls();
+ if (urls.empty())
+ return;
+
+ cancelButton->setEnabled(true);
+
+ initLayout(urls.size());
+
+ downloadFuture = download(urls);
+ statusBar->showMessage(tr("Downloading..."));
+//! [3]
+
+//! [4]
+ downloadFuture.then([this](auto) { cancelButton->setEnabled(false); })
+ .then(QtFuture::Launch::Async,
+ [this] {
+ updateStatus(tr("Scaling..."));
+ return scaled();
+ })
+//! [4]
+//! [5]
+ .then([this](const QList<QImage> &scaled) {
+ QMetaObject::invokeMethod(this, [this, scaled] { showImages(scaled); });
+ updateStatus(tr("Finished"));
+ })
+//! [5]
+//! [6]
+ .onCanceled([this] { updateStatus(tr("Download has been canceled.")); })
+ .onFailed([this](QNetworkReply::NetworkError error) {
+ const auto msg = QString("Download finished with error: %1").arg(error);
+ updateStatus(tr(msg.toStdString().c_str()));
+
+ // Abort all pending requests
+ QMetaObject::invokeMethod(this, &Images::abortDownload);
+ })
+ .onFailed([this](const std::exception& ex) {
+ updateStatus(tr(ex.what()));
+ });
+//! [6]
+ }
+}
+
+//! [7]
+void Images::cancel()
+{
+ statusBar->showMessage(tr("Canceling..."));
+
+ downloadFuture.cancel();
+ abortDownload();
}
+//! [7]
-void Images::open()
+//! [8]
+QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
+//! [8]
{
- // Cancel and wait if we are already loading images.
- if (imageScaling->isRunning()) {
- imageScaling->cancel();
- imageScaling->waitForFinished();
+//! [9]
+ QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
+ promise->start();
+//! [9]
+
+//! [10]
+ for (auto url : urls) {
+ QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
+ replies.push_back(reply);
+//! [10]
+
+//! [11]
+ QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] {
+ if (promise->isCanceled()) {
+ if (!promise->future().isFinished())
+ promise->finish();
+ return;
+ }
+
+ if (reply->error() != QNetworkReply::NoError) {
+ if (!promise->future().isFinished())
+ throw reply->error();
+ }
+//! [12]
+ promise->addResult(reply->readAll());
+
+ // Report finished on the last download
+ if (promise->future().resultCount() == urls.size()) {
+ promise->finish();
+ }
+//! [12]
+ }).onFailed([=] (QNetworkReply::NetworkError error) {
+ promise->setException(std::make_exception_ptr(error));
+ promise->finish();
+ }).onFailed([=] {
+ const auto ex = std::make_exception_ptr(
+ std::runtime_error("Unknown error occurred while downloading."));
+ promise->setException(ex);
+ promise->finish();
+ });
}
+//! [11]
+
+//! [13]
+ return promise->future();
+}
+//! [13]
- // Show a file open dialog at QStandardPaths::PicturesLocation.
- QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Images"),
- QStandardPaths::writableLocation(QStandardPaths::PicturesLocation),
- "*.jpg *.png");
+//! [14]
+QList<QImage> Images::scaled() const
+{
+ QList<QImage> scaled;
+ const auto data = downloadFuture.results();
+ for (auto imgData : data) {
+ QImage image;
+ image.loadFromData(imgData);
+ if (image.isNull())
+ throw std::runtime_error("Failed to load image.");
- if (files.isEmpty())
- return;
+ scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio));
+ }
- const int imageSize = 100;
+ return scaled;
+}
+//! [14]
- // Do a simple layout.
- qDeleteAll(labels);
+void Images::showImages(const QList<QImage> &images)
+{
+ for (int i = 0; i < images.size(); ++i) {
+ labels[i]->setAlignment(Qt::AlignCenter);
+ labels[i]->setPixmap(QPixmap::fromImage(images[i]));
+ }
+}
+
+void Images::initLayout(qsizetype count)
+{
+ // Clean old images
+ QLayoutItem *child;
+ while ((child = imagesLayout->takeAt(0)) != nullptr) {
+ child->widget()->setParent(nullptr);
+ delete child;
+ }
labels.clear();
- int dim = qSqrt(qreal(files.count())) + 1;
+ // Init the images layout for the new images
+ const auto dim = int(qSqrt(qreal(count))) + 1;
for (int i = 0; i < dim; ++i) {
for (int j = 0; j < dim; ++j) {
QLabel *imageLabel = new QLabel;
- imageLabel->setFixedSize(imageSize,imageSize);
- imagesLayout->addWidget(imageLabel,i,j);
+ imageLabel->setFixedSize(100, 100);
+ imagesLayout->addWidget(imageLabel, i, j);
labels.append(imageLabel);
}
}
-
- std::function<QImage(const QString&)> scale = [&](const QString &imageFileName) {
- QImage image(imageFileName);
- return image.scaled(QSize(imageSize, imageSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
- };
-
- // Use mapped to run the thread safe scale function on the files.
- imageScaling->setFuture(QtConcurrent::mapped(files, scale));
-
- openButton->setEnabled(false);
- cancelButton->setEnabled(true);
- pauseButton->setEnabled(true);
}
-void Images::showImage(int num)
+void Images::updateStatus(const QString &msg)
{
- labels[num]->setPixmap(QPixmap::fromImage(imageScaling->resultAt(num)));
+ QMetaObject::invokeMethod(this, [this, msg] { statusBar->showMessage(msg); });
}
-void Images::finished()
+void Images::abortDownload()
{
- openButton->setEnabled(true);
- cancelButton->setEnabled(false);
- pauseButton->setEnabled(false);
+ for (auto reply : replies)
+ reply->abort();
}
diff --git a/examples/qtconcurrent/imagescaling/imagescaling.h b/examples/qtconcurrent/imagescaling/imagescaling.h
index fe9c801387..0c0d90870e 100644
--- a/examples/qtconcurrent/imagescaling/imagescaling.h
+++ b/examples/qtconcurrent/imagescaling/imagescaling.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
@@ -52,25 +52,40 @@
#include <QtWidgets>
#include <QtConcurrent>
+#include <QNetworkAccessManager>
+class DownloadDialog;
class Images : public QWidget
{
Q_OBJECT
public:
Images(QWidget *parent = nullptr);
~Images();
+
+ void initLayout(qsizetype count);
+
+ QFuture<QByteArray> download(const QList<QUrl> &urls);
+ QList<QImage> scaled() const;
+ void updateStatus(const QString &msg);
+ void showImages(const QList<QImage> &images);
+ void abortDownload();
+
public slots:
- void open();
- void showImage(int num);
- void finished();
+ void process();
+ void cancel();
+
private:
- QPushButton *openButton;
+ QPushButton *addUrlsButton;
QPushButton *cancelButton;
- QPushButton *pauseButton;
QVBoxLayout *mainLayout;
QList<QLabel *> labels;
QGridLayout *imagesLayout;
- QFutureWatcher<QImage> *imageScaling;
+ QStatusBar *statusBar;
+ DownloadDialog *downloadDialog;
+
+ QNetworkAccessManager qnam;
+ QList<QSharedPointer<QNetworkReply>> replies;
+ QFuture<QByteArray> downloadFuture;
};
#endif // IMAGESCALING_H
diff --git a/examples/qtconcurrent/imagescaling/imagescaling.pro b/examples/qtconcurrent/imagescaling/imagescaling.pro
index 127fa532c9..9b458adfea 100644
--- a/examples/qtconcurrent/imagescaling/imagescaling.pro
+++ b/examples/qtconcurrent/imagescaling/imagescaling.pro
@@ -1,8 +1,14 @@
-QT += concurrent widgets
+QT += concurrent widgets network
+CONFIG += exceptions
requires(qtConfig(filedialog))
-SOURCES += main.cpp imagescaling.cpp
-HEADERS += imagescaling.h
+SOURCES += main.cpp imagescaling.cpp \
+ downloaddialog.cpp
+HEADERS += imagescaling.h \
+ downloaddialog.h
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/imagescaling
INSTALLS += target
+
+FORMS += \
+ downloaddialog.ui