summaryrefslogtreecommitdiffstats
path: root/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
diff options
context:
space:
mode:
Diffstat (limited to 'examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc')
-rw-r--r--examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc206
1 files changed, 175 insertions, 31 deletions
diff --git a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
index 0134f0f8e8..499cb165c8 100644
--- a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
+++ b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc
@@ -1,37 +1,181 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the documentation of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:FDL$
-** 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.
-**
-** GNU Free Documentation License Usage
-** Alternatively, this file may be used under the terms of the GNU Free
-** Documentation License version 1.3 as published by the Free Software
-** Foundation and appearing in the file included in the packaging of
-** this file. Please review the following information to ensure
-** the GNU Free Documentation License version 1.3 requirements
-** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example imagescaling
- \title Image Scaling Example
- \brief Demonstrates how to asynchronously scale images.
+ \meta tags {widgets, threads, network}
+ \title Image Scaling
\ingroup qtconcurrentexamples
- \image imagescaling_example.png
+ \examplecategory {Networking}
+ \brief Demonstrates how to asynchronously download and scale images.
- 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, QPromise, and QFutureWatcher
+ classes to download a collection of images from the network and scale them,
+ without blocking the UI.
+
+ \image imagescaling.webp
+
+ The application consists of 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:
+
+ \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 \l{QFuture::then}{.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 \l 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
+ \l{QFuture::onFailed}{.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.
+
+ The \c download() method is called from the \c Images::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.
+ More on that later:
+
+ \snippet imagescaling/imagescaling.cpp 4
+ \dots
+
+ After that we attach \l {QFuture::}{onCanceled()} and \l {QFuture::}{onFailed()}
+ handlers:
+
+ \snippet imagescaling/imagescaling.cpp 5
+ \dots
+
+ The handler attached via the \l {QFuture::onCanceled}{.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 \l {QFuture::onFailed}{.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
+ \l QNetworkReply::NetworkError as argument.
+
+ If the \c downloadFuture is not canceled, and didn't report any error, the
+ scaling continuation is executed.
+
+ Since the scaling may be computationally heavy, and we don't want to block
+ the main thread, we use \l QtConcurrent::run(), to launch the scaling step
+ in a new thread.
+
+ \snippet imagescaling/imagescaling.cpp 16
+
+ Since the scaling is launched in a separate thread, the user can potentially
+ decide to close the application while the scaling operation is in progress.
+ To handle such situations gracefully, we pass the \l QFuture returned by
+ \l QtConcurrent::run() to the \l QFutureWatcher instance.
+
+ The watcher's \l QFutureWatcher::finished signal is connected to the
+ \c Images::scaleFinished slot:
+
+ \snippet imagescaling/imagescaling.cpp 6
+
+ This slot is responsible for showing the scaled images in the UI, and also
+ for handling the errors that could potentially happen during scaling:
+
+ \snippet imagescaling/imagescaling.cpp 15
+
+ The error reporting is implemented by returning an optional from the
+ \c Images::scaled() method:
+
+ \snippet imagescaling/imagescaling.cpp 14
+
+ The \c Images::OptionalImages type here is simply a typedef for \c std::optional:
+
+ \snippet imagescaling/imagescaling.h 1
+
+ \note We cannot handle the errors from the async scaling operation using
+ the \l {QFuture::onFailed}{.onFailed()} handler, because the handler needs
+ to be executed in the context of \c Images object in the UI thread.
+ If the user closes the application while the async computation is done,
+ the \c Images object will be destroyed, and accessing its members from the
+ continuation will lead to a crash. Using \l QFutureWatcher and its signals
+ allows us to avoid the problem, because the signals are disconnected when
+ the \l QFutureWatcher is destroyed, so the related slots will never be
+ executed in a destroyed context.
+
+ The rest of the code is straightforward, you can check the example project for
+ more details.
+
+ \include examples-run.qdocinc
*/