From 190b77463d40db0278919992912d48d1e96e4796 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Mon, 9 Nov 2020 16:44:26 +0100 Subject: [PATCH] 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 Reviewed-by: Jarek Kobus --- .../qtconcurrent/imagescaling/CMakeLists.txt | 3 + .../doc/images/imagescaling_example.png | Bin 23710 -> 21049 bytes .../doc/src/qtconcurrent-imagescaling.qdoc | 140 ++++++++++- .../imagescaling/downloaddialog.cpp | 87 +++++++ .../imagescaling/downloaddialog.h | 75 ++++++ .../imagescaling/downloaddialog.ui | 119 ++++++++++ .../imagescaling/imagescaling.cpp | 223 +++++++++++++----- .../qtconcurrent/imagescaling/imagescaling.h | 29 ++- .../imagescaling/imagescaling.pro | 12 +- src/concurrent/doc/qtconcurrent.qdocconf | 3 +- 10 files changed, 621 insertions(+), 70 deletions(-) create mode 100644 examples/qtconcurrent/imagescaling/downloaddialog.cpp create mode 100644 examples/qtconcurrent/imagescaling/downloaddialog.h create mode 100644 examples/qtconcurrent/imagescaling/downloaddialog.ui diff --git a/examples/qtconcurrent/imagescaling/CMakeLists.txt b/examples/qtconcurrent/imagescaling/CMakeLists.txt index b1065e877bb..caca16da127 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 7c6794132a9ad493a512d42cccb0b6d0146b3565..a3860e19745c5e8e6b4e238df5a2eb226616cf81 100644 GIT binary patch literal 21049 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 literal 23710 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 diff --git a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc index 0134f0f8e81..a919b7e2edf 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 00000000000..2f2f901b0ef --- /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 + +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 DownloadDialog::getUrls() const +{ + QList 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 00000000000..9e5d478d7d3 --- /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 + +QT_BEGIN_NAMESPACE +namespace Ui { +class DownloadDialog; +} +QT_END_NAMESPACE + +class DownloadDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DownloadDialog(QWidget *parent = nullptr); + ~DownloadDialog(); + + QList 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 00000000000..c85a0635681 --- /dev/null +++ b/examples/qtconcurrent/imagescaling/downloaddialog.ui @@ -0,0 +1,119 @@ + + + DownloadDialog + + + + 0 + 0 + 489 + 333 + + + + Dialog + + + + + + + + + + + + + + + + + + + + Add URL + + + + + + + false + + + Remove URL + + + + + + + Clear + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DownloadDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DownloadDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/examples/qtconcurrent/imagescaling/imagescaling.cpp b/examples/qtconcurrent/imagescaling/imagescaling.cpp index 65ec16e3839..59664f8a58d 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 #include #include -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(this); - connect(imageScaling, &QFutureWatcher::resultReadyAt, this, &Images::showImage); - connect(imageScaling, &QFutureWatcher::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::cancel); - - pauseButton = new QPushButton(tr("Pause/Resume")); - pauseButton->setEnabled(false); - connect(pauseButton, &QPushButton::clicked, imageScaling, &QFutureWatcher::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(); + + 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 &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 Images::download(const QList &urls) +//! [8] +{ +//! [9] + QSharedPointer> promise(new QPromise()); + promise->start(); +//! [9] + +//! [10] + for (auto url : urls) { + QSharedPointer 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 Images::scaled() const +{ + QList 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)); } - // Show a file open dialog at QStandardPaths::PicturesLocation. - QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Images"), - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation), - "*.jpg *.png"); + return scaled; +} +//! [14] - if (files.isEmpty()) - return; +void Images::showImages(const QList &images) +{ + for (int i = 0; i < images.size(); ++i) { + labels[i]->setAlignment(Qt::AlignCenter); + labels[i]->setPixmap(QPixmap::fromImage(images[i])); + } +} - const int imageSize = 100; - - // Do a simple layout. - qDeleteAll(labels); +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 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 fe9c8013874..0c0d90870e0 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 #include +#include +class DownloadDialog; class Images : public QWidget { Q_OBJECT public: Images(QWidget *parent = nullptr); ~Images(); + + void initLayout(qsizetype count); + + QFuture download(const QList &urls); + QList scaled() const; + void updateStatus(const QString &msg); + void showImages(const QList &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 labels; QGridLayout *imagesLayout; - QFutureWatcher *imageScaling; + QStatusBar *statusBar; + DownloadDialog *downloadDialog; + + QNetworkAccessManager qnam; + QList> replies; + QFuture downloadFuture; }; #endif // IMAGESCALING_H diff --git a/examples/qtconcurrent/imagescaling/imagescaling.pro b/examples/qtconcurrent/imagescaling/imagescaling.pro index 127fa532c9e..9b458adfea5 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 diff --git a/src/concurrent/doc/qtconcurrent.qdocconf b/src/concurrent/doc/qtconcurrent.qdocconf index b60b3c1cd7d..d68ec5cc1db 100644 --- a/src/concurrent/doc/qtconcurrent.qdocconf +++ b/src/concurrent/doc/qtconcurrent.qdocconf @@ -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