Improve QtConcurrent ImageScaling example to demo new features

In order to demonstrate the new functionality, changed the example to
download the images from the network, scale and show them by attaching
different continuations to QFuture. Because QtConcurrent::map is not
used anymore, removed the suspension functionality (supporting
suspension of download would complicate the logic).

Task-number: QTBUG-87205
Change-Id: I5a48b63195d28025ae8c5de28bc6d6178dad03db
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Sona Kurazyan 2020-11-09 16:44:26 +01:00
parent cc3f693029
commit 190b77463d
10 changed files with 621 additions and 70 deletions

View File

@ -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
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -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.
*/

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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();
}
void Images::open()
//! [3]
void Images::process()
{
// Cancel and wait if we are already loading images.
if (imageScaling->isRunning()) {
imageScaling->cancel();
imageScaling->waitForFinished();
}
// Clean previous state
replies.clear();
// Show a file open dialog at QStandardPaths::PicturesLocation.
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Images"),
QStandardPaths::writableLocation(QStandardPaths::PicturesLocation),
"*.jpg *.png");
if (downloadDialog->exec() == QDialog::Accepted) {
if (files.isEmpty())
const auto urls = downloadDialog->getUrls();
if (urls.empty())
return;
const int imageSize = 100;
cancelButton->setEnabled(true);
// Do a simple layout.
qDeleteAll(labels);
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]
//! [8]
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
//! [8]
{
//! [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]
//! [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.");
scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio));
}
return scaled;
}
//! [14]
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();
}

View File

@ -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

View File

@ -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

View File

@ -38,7 +38,8 @@ exampledirs += ../../../examples/qtconcurrent \
.. \
.
manifestmeta.highlighted.names = "QtConcurrent/QtConcurrent Progress Dialog Example"
manifestmeta.highlighted.names = "QtConcurrent/QtConcurrent Progress Dialog Example" \
"Image Scaling Example"
excludedirs += ../../../examples/widgets/doc