diff options
-rw-r--r-- | examples/qtconcurrent/imagescaling/CMakeLists.txt | 3 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png | bin | 23710 -> 21049 bytes | |||
-rw-r--r-- | examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc | 140 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/downloaddialog.cpp | 87 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/downloaddialog.h | 75 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/downloaddialog.ui | 119 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/imagescaling.cpp | 221 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/imagescaling.h | 29 | ||||
-rw-r--r-- | examples/qtconcurrent/imagescaling/imagescaling.pro | 12 | ||||
-rw-r--r-- | src/concurrent/doc/qtconcurrent.qdocconf | 3 |
10 files changed, 620 insertions, 69 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 Binary files differindex 7c6794132a..a3860e1974 100644 --- a/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png +++ b/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png 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 diff --git a/src/concurrent/doc/qtconcurrent.qdocconf b/src/concurrent/doc/qtconcurrent.qdocconf index b60b3c1cd7..d68ec5cc1d 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 |