diff options
Diffstat (limited to 'examples/qtconcurrent')
38 files changed, 1310 insertions, 962 deletions
diff --git a/examples/qtconcurrent/CMakeLists.txt b/examples/qtconcurrent/CMakeLists.txt index 13cfed8773..13e78678d8 100644 --- a/examples/qtconcurrent/CMakeLists.txt +++ b/examples/qtconcurrent/CMakeLists.txt @@ -1,9 +1,11 @@ -if(TARGET Qt::Gui) - add_subdirectory(map) +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +if(NOT TARGET Qt6::Concurrent) + return() endif() -if(TARGET Qt::Widgets) - add_subdirectory(imagescaling) - add_subdirectory(progressdialog) - add_subdirectory(runfunction) - add_subdirectory(wordcount) +if(TARGET Qt6::Widgets) + qt_internal_add_example(imagescaling) + qt_internal_add_example(primecounter) + qt_internal_add_example(wordcount) endif() diff --git a/examples/qtconcurrent/imagescaling/CMakeLists.txt b/examples/qtconcurrent/imagescaling/CMakeLists.txt index 8585ed89a0..d053d04fc1 100644 --- a/examples/qtconcurrent/imagescaling/CMakeLists.txt +++ b/examples/qtconcurrent/imagescaling/CMakeLists.txt @@ -1,34 +1,41 @@ -# Generated from imagescaling.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.16) project(imagescaling LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Network Widgets) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) +qt_standard_project_setup() -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/imagescaling") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) -find_package(Qt6 COMPONENTS Widgets) - -add_qt_gui_executable(imagescaling +qt_add_executable(imagescaling + downloaddialog.cpp downloaddialog.h downloaddialog.ui imagescaling.cpp imagescaling.h main.cpp ) -target_link_libraries(imagescaling PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui - Qt::Widgets + +set_target_properties(imagescaling PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(imagescaling PRIVATE + Qt6::Concurrent + Qt6::Core + Qt6::Gui + Qt6::Network + Qt6::Widgets ) install(TARGETS imagescaling - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET imagescaling + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/qtconcurrent/imagescaling/doc/images/imagescaling.webp b/examples/qtconcurrent/imagescaling/doc/images/imagescaling.webp Binary files differnew file mode 100644 index 0000000000..56999faaa7 --- /dev/null +++ b/examples/qtconcurrent/imagescaling/doc/images/imagescaling.webp diff --git a/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png b/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png Binary files differdeleted file mode 100644 index 7c6794132a..0000000000 --- a/examples/qtconcurrent/imagescaling/doc/images/imagescaling_example.png +++ /dev/null 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 */ diff --git a/examples/qtconcurrent/imagescaling/downloaddialog.cpp b/examples/qtconcurrent/imagescaling/downloaddialog.cpp new file mode 100644 index 0000000000..c3ec550038 --- /dev/null +++ b/examples/qtconcurrent/imagescaling/downloaddialog.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#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..e4a6482907 --- /dev/null +++ b/examples/qtconcurrent/imagescaling/downloaddialog.h @@ -0,0 +1,28 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#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..f380ae9d90 100644 --- a/examples/qtconcurrent/imagescaling/imagescaling.cpp +++ b/examples/qtconcurrent/imagescaling/imagescaling.cpp @@ -1,153 +1,236 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "imagescaling.h" +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <qmath.h> +#include "imagescaling.h" +#include "downloaddialog.h" -#include <functional> +#include <QNetworkReply> -Images::Images(QWidget *parent) - : QWidget(parent) +Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog(this)) { - setWindowTitle(tr("Image loading 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); + +//! [6] + connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished, + this, &Images::scaleFinished); +//! [6] } Images::~Images() { - imageScaling->cancel(); - imageScaling->waitForFinished(); + cancel(); +} + +//! [3] +void Images::process() +{ + // Clean previous state + replies.clear(); + addUrlsButton->setEnabled(false); + + 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); + updateStatus(tr("Scaling...")); + //! [16] + scalingWatcher.setFuture(QtConcurrent::run(Images::scaled, + downloadFuture.results())); + //! [16] + }) + //! [4] + //! [5] + .onCanceled([this] { + updateStatus(tr("Download has been canceled.")); + }) + .onFailed([this](QNetworkReply::NetworkError error) { + updateStatus(tr("Download finished with error: %1").arg(error)); + // Abort all pending requests + abortDownload(); + }) + .onFailed([this](const std::exception &ex) { + updateStatus(tr(ex.what())); + }) + //! [5] + .then([this]() { + cancelButton->setEnabled(false); + addUrlsButton->setEnabled(true); + }); + } +} + +//! [7] +void Images::cancel() +{ + statusBar->showMessage(tr("Canceling...")); + + downloadFuture.cancel(); + abortDownload(); +} +//! [7] + +//! [15] +void Images::scaleFinished() +{ + const OptionalImages result = scalingWatcher.result(); + if (result.has_value()) { + const auto scaled = result.value(); + showImages(scaled); + updateStatus(tr("Finished")); + } else { + updateStatus(tr("Failed to extract image data.")); + } + addUrlsButton->setEnabled(true); } +//! [15] -void Images::open() +//! [8] +QFuture<QByteArray> Images::download(const QList<QUrl> &urls) { - // Cancel and wait if we are already loading images. - if (imageScaling->isRunning()) { - imageScaling->cancel(); - imageScaling->waitForFinished(); +//! [8] +//! [9] + QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>()); + promise->start(); +//! [9] + + //! [10] + for (const 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([promise] (QNetworkReply::NetworkError error) { + promise->setException(std::make_exception_ptr(error)); + promise->finish(); + }).onFailed([promise] { + 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] +Images::OptionalImages Images::scaled(const QList<QByteArray> &data) +{ + QList<QImage> scaled; + for (const auto &imgData : data) { + QImage image; + image.loadFromData(imgData); + if (image.isNull()) + return std::nullopt; + + scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio)); + } - if (files.isEmpty()) - return; + return scaled; +} +//! [14] - const int imageSize = 100; +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])); + } +} - // 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->widget(); + 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))); + 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..bc25161a85 100644 --- a/examples/qtconcurrent/imagescaling/imagescaling.h +++ b/examples/qtconcurrent/imagescaling/imagescaling.h @@ -1,76 +1,53 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef IMAGESCALING_H #define IMAGESCALING_H #include <QtWidgets> #include <QtConcurrent> +#include <QNetworkAccessManager> +#include <optional> +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); + 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 slots: + void scaleFinished(); + private: - QPushButton *openButton; + //! [1] + using OptionalImages = std::optional<QList<QImage>>; + //! [1] + static OptionalImages scaled(const QList<QByteArray> &data); + + 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; + QFutureWatcher<OptionalImages> scalingWatcher; }; #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/examples/qtconcurrent/imagescaling/main.cpp b/examples/qtconcurrent/imagescaling/main.cpp index 01390499f5..67b543a961 100644 --- a/examples/qtconcurrent/imagescaling/main.cpp +++ b/examples/qtconcurrent/imagescaling/main.cpp @@ -1,62 +1,16 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QtWidgets> -#include <QtConcurrent> - +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "imagescaling.h" +#include <QApplication> int main(int argc, char *argv[]) { QApplication app(argc,argv); + app.setOrganizationName("QtProject"); + app.setApplicationName(QObject::tr("Image Downloading and Scaling")); Images imageView; + imageView.setWindowTitle(QObject::tr("Image Downloading and Scaling")); imageView.show(); return app.exec(); diff --git a/examples/qtconcurrent/map/.prev_CMakeLists.txt b/examples/qtconcurrent/map/.prev_CMakeLists.txt deleted file mode 100644 index 8e158b2397..0000000000 --- a/examples/qtconcurrent/map/.prev_CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# Generated from map.pro. - -cmake_minimum_required(VERSION 3.14) -project(mapdemo LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) - -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/map") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) - -add_executable(mapdemo - main.cpp -) -target_link_libraries(mapdemo PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui -) - -install(TARGETS mapdemo - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/qtconcurrent/map/CMakeLists.txt b/examples/qtconcurrent/map/CMakeLists.txt deleted file mode 100644 index e5a3ba1c66..0000000000 --- a/examples/qtconcurrent/map/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# Generated from map.pro. - -cmake_minimum_required(VERSION 3.14) -project(mapdemo LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) - -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/map") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) - -add_executable(mapdemo - main.cpp -) -target_link_libraries(mapdemo PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui -) -install(TARGETS mapdemo - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/qtconcurrent/map/doc/src/qtconcurrent-map.qdoc b/examples/qtconcurrent/map/doc/src/qtconcurrent-map.qdoc deleted file mode 100644 index 2cc66e932e..0000000000 --- a/examples/qtconcurrent/map/doc/src/qtconcurrent-map.qdoc +++ /dev/null @@ -1,38 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -/*! - \example map - \title Map Example - \brief Demonstrates how to scale images synchronously. - \ingroup qtconcurrentexamples - - The QtConcurrent Map example shows how to use the synchronous (blocking) - QtConcurrent API to scale a collection of images. - - This is a command-line application. -*/ diff --git a/examples/qtconcurrent/map/main.cpp b/examples/qtconcurrent/map/main.cpp deleted file mode 100644 index 8fc670b64b..0000000000 --- a/examples/qtconcurrent/map/main.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QImage> -#include <QList> -#include <QThread> -#include <QDebug> -#include <QGuiApplication> -#include <qtconcurrentmap.h> - -#include <functional> - -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - - const int imageCount = 20; - - // Create a list containing imageCount images. - QList<QImage> images; - for (int i = 0; i < imageCount; ++i) - images.append(QImage(1600, 1200, QImage::Format_ARGB32_Premultiplied)); - - std::function<QImage(const QImage&)> scale = [](const QImage &image) -> QImage - { - qDebug() << "Scaling image in thread" << QThread::currentThread(); - return image.scaled(QSize(100, 100), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - }; - - // Use QtConcurrentBlocking::mapped to apply the scale function to all the - // images in the list. - QList<QImage> thumbnails = QtConcurrent::blockingMapped(images, scale); - - return 0; -} diff --git a/examples/qtconcurrent/map/map.pro b/examples/qtconcurrent/map/map.pro deleted file mode 100644 index 7f267beb22..0000000000 --- a/examples/qtconcurrent/map/map.pro +++ /dev/null @@ -1,9 +0,0 @@ -TEMPLATE = app -TARGET = mapdemo -QT += concurrent -CONFIG += cmdline - -SOURCES += main.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/map -INSTALLS += target diff --git a/examples/qtconcurrent/primecounter/CMakeLists.txt b/examples/qtconcurrent/primecounter/CMakeLists.txt new file mode 100644 index 0000000000..c8ac5f20e8 --- /dev/null +++ b/examples/qtconcurrent/primecounter/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(primecounter LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets) + +qt_standard_project_setup() + +qt_add_executable(primecounter + main.cpp + primecounter.ui + primecounter.cpp + primecounter.h +) + +set_target_properties(primecounter PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(primecounter PRIVATE + Qt6::Concurrent + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +install(TARGETS primecounter + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET primecounter + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/qtconcurrent/primecounter/doc/images/primecounter.png b/examples/qtconcurrent/primecounter/doc/images/primecounter.png Binary files differnew file mode 100644 index 0000000000..d611a507b5 --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/images/primecounter.png diff --git a/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc new file mode 100644 index 0000000000..de6c94b0cf --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc @@ -0,0 +1,89 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example primecounter + \meta tags {widgets, threads} + \title Prime Counter + \ingroup qtconcurrentexamples + \examplecategory {Data Processing & I/O} + \brief Demonstrates how to monitor the progress of concurrent operations. + + The following example demonstrates how to create an interactive and + non-blocking QtWidgets application using the QFutureWatcher class and the + \l {Concurrent Filter-Reduce} {filteredReduced} functions from + \l {Qt Concurrent}. With this example, the user can create a QList of + integers that can be resized. The list will be automatically filled with + natural numbers starting from 1 up to n. The program will then check for + prime numbers within the list and display the total count of prime numbers + found. + + \image primecounter.png + + \include examples-run.qdocinc + + \section1 Setting up the connections + + The \l {Qt Concurrent} library provides the + \l {Concurrent Filter-Reduce} {filteredReduced} functions, which can operate + in two modes: + \l {QtConcurrent::ReduceOption} {OrderedReduce and UnorderedReduce}. In + \c OrderedReduce mode, the reducing function is called in the order of the + original sequence, whereas in \c UnorderedReduce mode, the elements are + accessed randomly. + + After configuring the UI with the desired elements, it is necessary to + connect them to the signals of the concurrent operations using the Qt + \l {Signals & Slots} mechanism. In this example, we use the QFutureWatcher + class to monitor the progress of the concurrent operations and provide the + signals required to implement the interactive GUI. + + \dots + \snippet primecounter/primecounter.cpp 1 + \dots + + The QFutureWatcher class plays a vital role in this example as it provides + the signals required to update the UI in response to changes in the + concurrent operations. + + \section1 Starting the concurrent operation + + After connecting all the \l {Signals & Slots}, and when the user presses + the QPushButton, the \c {start()} function is called. + + In the \c {start()} function, we call the + \l {Concurrent Filter-Reduce} {filteredReduced} function from Qt Concurrent + and set the future on the QFutureWatcher member. To ensure that this + operation runs truly concurrently, we specify a separate QThreadPool as the + first parameter. This approach also avoids any possible blocking in the + global thread pool. We pass the QList of integers as the container, a + static filter and reduce function, and finally the + \l {QtConcurrent::} {ReduceOption} flag. + + \dots + \snippet primecounter/primecounter.cpp 2 + \dots + + Let's examine the filter and reduce functions. These functions are declared + static in this example since they do not depend on any member variable. + However, they could easily be specified as lambdas or member functions. + + The filter function marks elements for subsequent reduction with the reduce + function. This implementation is a simple prime filter. As this function + takes a const reference as an argument, it allows thread-safe operation on + the container it operates on. + + \dots + \snippet primecounter/primecounter.cpp 3 + \dots + + The reduce function takes a modifiable reference of the same type as the + container it operates on as its first parameter. The second parameter is the + previously filtered element from the filter function. In this example, we + count the number of primes. + + \dots + \snippet primecounter/primecounter.cpp 4 + \dots + +*/ diff --git a/examples/qtconcurrent/primecounter/main.cpp b/examples/qtconcurrent/primecounter/main.cpp new file mode 100644 index 0000000000..acfd581341 --- /dev/null +++ b/examples/qtconcurrent/primecounter/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtWidgets/qapplication.h> +#include "primecounter.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("QtProject"); + app.setApplicationName(QApplication::translate("main", "Prime Counter")); + + PrimeCounter dialog; + dialog.setWindowTitle(QApplication::translate("main", "Prime Counter")); + dialog.show(); + + return app.exec(); +} diff --git a/examples/qtconcurrent/primecounter/primecounter.cpp b/examples/qtconcurrent/primecounter/primecounter.cpp new file mode 100644 index 0000000000..01bde5d87f --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "primecounter.h" +#include "ui_primecounter.h" + +PrimeCounter::PrimeCounter(QWidget *parent) + : QDialog(parent), stepSize(100000), ui(setupUi()) +{ + // Control the concurrent operation with the QFutureWatcher + //! [1] + connect(ui->pushButton, &QPushButton::clicked, + this, [this] { start(); }); + connect(&watcher, &QFutureWatcher<Element>::finished, + this, [this] { finish(); }); + connect(&watcher, &QFutureWatcher<Element>::progressRangeChanged, + ui->progressBar, &QProgressBar::setRange); + connect(&watcher, &QFutureWatcher<Element>::progressValueChanged, + ui->progressBar, &QProgressBar::setValue); + //! [1] +} + +PrimeCounter::~PrimeCounter() +{ + watcher.cancel(); + delete ui; +} + +//! [3] +bool PrimeCounter::filterFunction(const Element &element) +{ + // Filter for primes + if (element <= 1) + return false; + for (Element i = 2; i*i <= element; ++i) { + if (element % i == 0) + return false; + } + return true; +} +//! [3] + +//! [4] +void PrimeCounter::reduceFunction(Element &out, const Element &value) +{ + // Count the amount of primes. + Q_UNUSED(value); + ++out; +} +//! [4] + +//! [2] +void PrimeCounter::start() +{ + if (ui->pushButton->isChecked()) { + ui->comboBox->setEnabled(false); + ui->pushButton->setText(tr("Cancel")); + ui->labelResult->setText(tr("Calculating ...")); + ui->labelFilter->setText(tr("Selected Reduce Option: %1").arg(ui->comboBox->currentText())); + fillElementList(ui->horizontalSlider->value() * stepSize); + + timer.start(); + watcher.setFuture( + QtConcurrent::filteredReduced( + &pool, + elementList, + filterFunction, + reduceFunction, + currentReduceOpt | QtConcurrent::SequentialReduce)); +//! [2] + } else { + watcher.cancel(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->labelResult->setText(tr("")); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText(tr("Operation Canceled")); + } +} + +void PrimeCounter::finish() +{ + // The finished signal from the QFutureWatcher is also emitted when cancelling. + if (watcher.isCanceled()) + return; + + auto elapsedTime = timer.elapsed(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->pushButton->setChecked(false); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText( + tr("Filter '%1' took %2 ms to calculate").arg(ui->comboBox->currentText()) + .arg(elapsedTime)); + ui->labelResult->setText( + tr("Found %1 primes in the range of elements").arg(watcher.result())); +} + +void PrimeCounter::fillElementList(unsigned int count) +{ + // Fill elementList with values from [1, count] when starting the calculations. + auto prevSize = elementList.size(); + if (prevSize == count) + return; // Nothing to do here. + + auto startVal = elementList.empty() ? 1 : elementList.back() + 1; + elementList.resize(count); + if (elementList.begin() + prevSize < elementList.end()) + std::iota(elementList.begin() + prevSize, elementList.end(), startVal); +} + +Ui::PrimeCounter* PrimeCounter::setupUi() +{ + Ui::PrimeCounter *setupUI = new Ui::PrimeCounter; + setupUI->setupUi(this); + setModal(true); + + // Set up the slider + connect(setupUI->horizontalSlider, &QSlider::valueChanged, + this, [setupUI, this] (const int &pos) { + setupUI->labelResult->setText(""); + setupUI->labelSize->setText(tr("Elements in list: %1").arg(pos * stepSize)); + }); + setupUI->horizontalSlider->setValue(30); + + // Set up the combo box + setupUI->comboBox->insertItem(0, tr("Unordered Reduce"), QtConcurrent::UnorderedReduce); + setupUI->comboBox->insertItem(1, tr("Ordered Reduce"), QtConcurrent::OrderedReduce); + + auto comboBoxChange = [this, setupUI](int pos) { + currentReduceOpt = setupUI->comboBox->itemData(pos).value<QtConcurrent::ReduceOptions>(); + setupUI->labelFilter->setText(tr("Selected Reduce Option: %1") + .arg(setupUI->comboBox->currentText())); + }; + comboBoxChange(setupUI->comboBox->currentIndex()); + connect(setupUI->comboBox, &QComboBox::currentIndexChanged, this, comboBoxChange); + + return setupUI; +} diff --git a/examples/qtconcurrent/primecounter/primecounter.h b/examples/qtconcurrent/primecounter/primecounter.h new file mode 100644 index 0000000000..5b6d442cc8 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.h @@ -0,0 +1,49 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef PRIMECOUNTER_H +#define PRIMECOUNTER_H + +#include <QtWidgets/qdialog.h> +#include <QtCore/qfuturewatcher.h> +#include <QtConcurrent/qtconcurrentfilter.h> +#include <QtConcurrent/qtconcurrentreducekernel.h> + +QT_BEGIN_NAMESPACE +class QLabel; +class QProgressBar; +namespace Ui { +class PrimeCounter; +} + +QT_END_NAMESPACE + +class PrimeCounter : public QDialog +{ + Q_OBJECT + using Element = unsigned long long; +public: + explicit PrimeCounter(QWidget* parent = nullptr); + ~PrimeCounter() override; + +private: + static bool filterFunction(const Element &element); + static void reduceFunction(Element &out, const Element &value); + void fillElementList(unsigned int count); + Ui::PrimeCounter* setupUi(); + +private slots: + void start(); + void finish(); + +private: + QList<Element> elementList; + QFutureWatcher<Element> watcher; + QtConcurrent::ReduceOptions currentReduceOpt; + QElapsedTimer timer; + QThreadPool pool; + unsigned int stepSize; + Ui::PrimeCounter *ui; +}; + +#endif //PRIMECOUNTER_H diff --git a/examples/qtconcurrent/primecounter/primecounter.pro b/examples/qtconcurrent/primecounter/primecounter.pro new file mode 100644 index 0000000000..bb5a740688 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.pro @@ -0,0 +1,9 @@ +QT += concurrent widgets + +SOURCES += main.cpp primecounter.cpp +HEADERS += primecounter.h + +target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/primecounter +INSTALLS += target + +FORMS += primecounter.ui diff --git a/examples/qtconcurrent/primecounter/primecounter.ui b/examples/qtconcurrent/primecounter/primecounter.ui new file mode 100644 index 0000000000..625462e05e --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.ui @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PrimeCounter</class> + <widget class="QDialog" name="PrimeCounter"> + <property name="windowModality"> + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>454</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>20</number> + </property> + <property name="topMargin"> + <number>20</number> + </property> + <property name="rightMargin"> + <number>20</number> + </property> + <property name="bottomMargin"> + <number>20</number> + </property> + <item> + <widget class="QLabel" name="labelInfo"> + <property name="font"> + <font> + <pointsize>11</pointsize> + <bold>false</bold> + </font> + </property> + <property name="text"> + <string>Select a reducing option and measure the speed</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>30</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout2"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QComboBox" name="comboBox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout1"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Start</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelFilter"> + <property name="text"> + <string>Filter Label</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout3"> + <item> + <widget class="QSlider" name="horizontalSlider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelSize"> + <property name="text"> + <string>size</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + <property name="indent"> + <number>10</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelResult"> + <property name="text"> + <string>Result Label</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/qtconcurrent/progressdialog/CMakeLists.txt b/examples/qtconcurrent/progressdialog/CMakeLists.txt deleted file mode 100644 index 912490bd65..0000000000 --- a/examples/qtconcurrent/progressdialog/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Generated from progressdialog.pro. - -cmake_minimum_required(VERSION 3.14) -project(progressdialog LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) - -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/progressdialog") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) -find_package(Qt6 COMPONENTS Widgets) - -add_executable(progressdialog - main.cpp -) -target_link_libraries(progressdialog PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui - Qt::Widgets -) - -install(TARGETS progressdialog - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png b/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png Binary files differdeleted file mode 100644 index 2e8b7735ad..0000000000 --- a/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png +++ /dev/null diff --git a/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc b/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc deleted file mode 100644 index 816bbd5170..0000000000 --- a/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc +++ /dev/null @@ -1,38 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -/*! - \example progressdialog - \title QtConcurrent Progress Dialog Example - \brief Demonstrates how to monitor the progress of the active processes. - \ingroup qtconcurrentexamples - - The QtConcurrent Progress Dialog example shows how to use the - QFutureWatcher class to monitor the progress of a long-running operation. - - \image qtconcurrent-progressdialog.png -*/ diff --git a/examples/qtconcurrent/progressdialog/main.cpp b/examples/qtconcurrent/progressdialog/main.cpp deleted file mode 100644 index cf4fd7e456..0000000000 --- a/examples/qtconcurrent/progressdialog/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QtWidgets> -#include <QtConcurrent> - -#include <functional> - -using namespace QtConcurrent; - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - - const int iterations = 20; - - // Prepare the list. - QList<int> list; - for (int i = 0; i < iterations; ++i) - list.append(i); - - // Create a progress dialog. - QProgressDialog dialog; - dialog.setLabelText(QString("Progressing using %1 thread(s)...").arg(QThread::idealThreadCount())); - - // Create a QFutureWatcher and connect signals and slots. - QFutureWatcher<void> futureWatcher; - QObject::connect(&futureWatcher, &QFutureWatcher<void>::finished, &dialog, &QProgressDialog::reset); - QObject::connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher<void>::cancel); - QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &dialog, &QProgressDialog::setRange); - QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, &QProgressDialog::setValue); - - // Our function to compute - std::function<void(int&)> spin = [](int &iteration) { - const int work = 1000 * 1000 * 40; - volatile int v = 0; - for (int j = 0; j < work; ++j) - ++v; - - qDebug() << "iteration" << iteration << "in thread" << QThread::currentThreadId(); - }; - - // Start the computation. - futureWatcher.setFuture(QtConcurrent::map(list, spin)); - - // Display the dialog and start the event loop. - dialog.exec(); - - futureWatcher.waitForFinished(); - - // Query the future to check if was canceled. - qDebug() << "Canceled?" << futureWatcher.future().isCanceled(); -} diff --git a/examples/qtconcurrent/progressdialog/progressdialog.pro b/examples/qtconcurrent/progressdialog/progressdialog.pro deleted file mode 100644 index 8a5b3aabb7..0000000000 --- a/examples/qtconcurrent/progressdialog/progressdialog.pro +++ /dev/null @@ -1,7 +0,0 @@ -QT += concurrent widgets -CONFIG += console - -SOURCES += main.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/progressdialog -INSTALLS += target diff --git a/examples/qtconcurrent/qtconcurrent.pro b/examples/qtconcurrent/qtconcurrent.pro index bdf41b03ea..ea7618fa00 100644 --- a/examples/qtconcurrent/qtconcurrent.pro +++ b/examples/qtconcurrent/qtconcurrent.pro @@ -2,21 +2,12 @@ requires(qtHaveModule(concurrent)) TEMPLATE = subdirs SUBDIRS = imagescaling \ - map \ - progressdialog \ - runfunction \ + primecounter \ wordcount - -!qtHaveModule(gui) { - SUBDIRS -= \ - map -} - !qtHaveModule(widgets) { SUBDIRS -= \ imagescaling \ - progressdialog \ - runfunction \ + primecounter \ wordcount } diff --git a/examples/qtconcurrent/runfunction/CMakeLists.txt b/examples/qtconcurrent/runfunction/CMakeLists.txt deleted file mode 100644 index a8cb3de4d4..0000000000 --- a/examples/qtconcurrent/runfunction/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Generated from runfunction.pro. - -cmake_minimum_required(VERSION 3.14) -project(runfunction LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) - -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/runfunction") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) -find_package(Qt6 COMPONENTS Widgets) - -add_executable(runfunction - main.cpp -) -target_link_libraries(runfunction PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui - Qt::Widgets -) - -install(TARGETS runfunction - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/qtconcurrent/runfunction/doc/src/qtconcurrent-runfunction.qdoc b/examples/qtconcurrent/runfunction/doc/src/qtconcurrent-runfunction.qdoc deleted file mode 100644 index 4459976a2c..0000000000 --- a/examples/qtconcurrent/runfunction/doc/src/qtconcurrent-runfunction.qdoc +++ /dev/null @@ -1,39 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -/*! - \example runfunction - \title Run Function Example - \brief Demonstrates how to run standard functions concurrently. - \ingroup qtconcurrentexamples - - The QtConcurrent Run Function example shows how to apply concurrency to - a standard function, using QFuture instances to retrieve return values - at a later time. - - This is a command-line application. -*/ diff --git a/examples/qtconcurrent/runfunction/main.cpp b/examples/qtconcurrent/runfunction/main.cpp deleted file mode 100644 index b349f886ba..0000000000 --- a/examples/qtconcurrent/runfunction/main.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QDebug> -#include <QThread> -#include <QString> -#include <qtconcurrentrun.h> -#include <QApplication> - -using namespace QtConcurrent; - -void hello(QString name) -{ - qDebug() << "Hello" << name << "from" << QThread::currentThread(); -} - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - QFuture<void> f1 = run(hello, QString("Alice")); - QFuture<void> f2 = run(hello, QString("Bob")); - f1.waitForFinished(); - f2.waitForFinished(); -} diff --git a/examples/qtconcurrent/runfunction/runfunction.pro b/examples/qtconcurrent/runfunction/runfunction.pro deleted file mode 100644 index 42c05551ba..0000000000 --- a/examples/qtconcurrent/runfunction/runfunction.pro +++ /dev/null @@ -1,7 +0,0 @@ -QT += concurrent widgets -CONFIG += cmdline - -SOURCES += main.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/runfunction -INSTALLS += target diff --git a/examples/qtconcurrent/wordcount/CMakeLists.txt b/examples/qtconcurrent/wordcount/CMakeLists.txt index 22d78c3877..0be10c3f35 100644 --- a/examples/qtconcurrent/wordcount/CMakeLists.txt +++ b/examples/qtconcurrent/wordcount/CMakeLists.txt @@ -1,33 +1,33 @@ -# Generated from wordcount.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.16) project(wordcount LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) +qt_standard_project_setup() -set(INSTALL_EXAMPLEDIR "examples/qtconcurrent/wordcount") - -find_package(Qt6 COMPONENTS Core) -find_package(Qt6 COMPONENTS Gui) -find_package(Qt6 COMPONENTS Concurrent) -find_package(Qt6 COMPONENTS Widgets) - -add_executable(wordcount +qt_add_executable(wordcount main.cpp ) -target_link_libraries(wordcount PUBLIC - Qt::Concurrent - Qt::Core - Qt::Gui - Qt::Widgets + +target_link_libraries(wordcount PRIVATE + Qt6::Concurrent + Qt6::Core + Qt6::Gui + Qt6::Widgets ) install(TARGETS wordcount - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET wordcount + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/qtconcurrent/wordcount/doc/src/qtconcurrent-wordcount.qdoc b/examples/qtconcurrent/wordcount/doc/src/qtconcurrent-wordcount.qdoc index 7486340c7b..adefdfdc59 100644 --- a/examples/qtconcurrent/wordcount/doc/src/qtconcurrent-wordcount.qdoc +++ b/examples/qtconcurrent/wordcount/doc/src/qtconcurrent-wordcount.qdoc @@ -1,39 +1,45 @@ -/**************************************************************************** -** -** 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 wordcount - \title QtConcurrent Word Count Example - \brief Demonstrates how to use the map-reduce algorithm. + \meta tags {threads, console} + \title Word Count \ingroup qtconcurrentexamples + \examplecategory {Data Processing & I/O} + \brief Demonstrates how to use the map-reduce algorithm. + + The Qt Concurrent \e {Word Count} example demonstrates the use of the + map-reduce algorithm when applied to the problem of counting words in a + collection of files. + + First, the Application starts a QFileDialog to select a starting + path, and then prints the output to the console. + + \include examples-run.qdocinc + + \section1 Comparing the operations + + Compare a single-threaded, sequential approach to counting the words in + the text files to a multithreaded approach with mappedReduced(): + + \dots + \snippet wordcount/main.cpp 1 + \dots + \snippet wordcount/main.cpp 2 + \dots + + The first argument to the \l {QtConcurrent::}{mappedReduced} function is the + container to operate on. The second argument is the mapping function + \c {countWords()}. It is called in parallel by multiple threads. The + third argument is the reducing function \c {reduce()}. It is called + once for each result returned by the mapping function, and generates the + final computation result. - The QtConcurrent Word Count example demonstrates the use of the map-reduce - algorithm when applied to the problem of counting words in a collection - of files. + The function returns a QFuture object of type \c WordCount. Call the + \l {QFuture::}{result} function immediately on this QFuture to block further + execution until the result becomes available. - This is a command-line application. + \note The mapping function must be thread-safe since it is called from + multiple threads. */ diff --git a/examples/qtconcurrent/wordcount/main.cpp b/examples/qtconcurrent/wordcount/main.cpp index ae8542a761..4175641ce9 100644 --- a/examples/qtconcurrent/wordcount/main.cpp +++ b/examples/qtconcurrent/wordcount/main.cpp @@ -1,90 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QList> -#include <QMap> -#include <QTextStream> -#include <QString> -#include <QStringList> -#include <QDir> -#include <QElapsedTimer> -#include <QApplication> -#include <QDebug> - -#include <qtconcurrentmap.h> - -using namespace QtConcurrent; - -/* - Utility function that recursivily searches for files. -*/ -QStringList findFiles(const QString &startDir, const QStringList &filters) -{ - QStringList names; - QDir dir(startDir); +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - const auto files = dir.entryList(filters, QDir::Files); - for (const QString &file : files) - names += startDir + '/' + file; - - const auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - for (const QString &subdir : subdirs) - names += findFiles(startDir + '/' + subdir, filters); - return names; -} +#include <QtWidgets/qfiledialog.h> +#include <QtWidgets/qapplication.h> +#include <QtCore/qmimedatabase.h> +#include <QtCore/qelapsedtimer.h> +#include <QtConcurrent/qtconcurrentmap.h> typedef QMap<QString, int> WordCount; -/* - Single threaded word counter function. -*/ +void printHighestResult(const WordCount &, qsizetype); +QStringList findFiles(const QString &); + +// Single threaded word counter function. WordCount singleThreadedWordCount(const QStringList &files) { WordCount wordCount; @@ -93,7 +21,7 @@ WordCount singleThreadedWordCount(const QStringList &files) f.open(QIODevice::ReadOnly); QTextStream textStream(&f); while (!textStream.atEnd()) { - const auto words = textStream.readLine().split(' '); + const auto words = textStream.readLine().split(' ', Qt::SkipEmptyParts); for (const QString &word : words) wordCount[word] += 1; } @@ -101,7 +29,6 @@ WordCount singleThreadedWordCount(const QStringList &files) return wordCount; } - // countWords counts the words in a single file. This function is // called in parallel by several threads and must be thread // safe. @@ -113,7 +40,7 @@ WordCount countWords(const QString &file) WordCount wordCount; while (!textStream.atEnd()) { - const auto words = textStream.readLine().split(' '); + const auto words = textStream.readLine().split(' ', Qt::SkipEmptyParts); for (const QString &word : words) wordCount[word] += 1; } @@ -133,33 +60,93 @@ void reduce(WordCount &result, const WordCount &w) int main(int argc, char** argv) { QApplication app(argc, argv); - qDebug() << "finding files..."; - QStringList files = findFiles("../../", QStringList() << "*.cpp" << "*.h"); - qDebug() << files.count() << "files"; - - qDebug() << "warmup"; - { - WordCount total = singleThreadedWordCount(files); - } - - qDebug() << "warmup done"; - - int singleThreadTime = 0; + app.setOrganizationName("QtProject"); + app.setApplicationName(QCoreApplication::translate("main", "Word Count")); + + QFileDialog fileDialog; + fileDialog.setOption(QFileDialog::ReadOnly); + // Grab the directory path from the dialog + auto dirPath = QFileDialog::getExistingDirectory(nullptr, + QCoreApplication::translate("main","Select a Folder"), + QDir::currentPath()); + + QStringList files = findFiles(dirPath); + qDebug() << QCoreApplication::translate("main", "Indexing %1 files in %2") + .arg(files.size()).arg(dirPath); + + // Start the single threaded operation + qint64 singleThreadTime; { QElapsedTimer timer; timer.start(); + //! [1] WordCount total = singleThreadedWordCount(files); + //! [1] singleThreadTime = timer.elapsed(); - qDebug() << "single thread" << singleThreadTime; + qDebug() << QCoreApplication::translate("main", "Single threaded scanning took %1 ms") + .arg(singleThreadTime); } - - int mapReduceTime = 0; + // Start the multithreaded mappedReduced operation. + qint64 mapReduceTime; { QElapsedTimer timer; timer.start(); - WordCount total = mappedReduced(files, countWords, reduce); + //! [2] + WordCount total = QtConcurrent::mappedReduced(files, countWords, reduce).result(); + //! [2] mapReduceTime = timer.elapsed(); - qDebug() << "MapReduce" << mapReduceTime; + qDebug() << QCoreApplication::translate("main", "MapReduce scanning took %1 ms") + .arg(mapReduceTime); + qDebug() << QCoreApplication::translate("main", "MapReduce speedup: %1") + .arg(((double)singleThreadTime - (double)mapReduceTime) / (double)mapReduceTime + 1); + printHighestResult(total, 10); } - qDebug() << "MapReduce speedup x" << ((double)singleThreadTime - (double)mapReduceTime) / (double)mapReduceTime + 1; +} + +// Utility function that recursively searches for text files. +QStringList findFiles(const QString &startDir) +{ + QStringList names; + QDir dir(startDir); + static const QMimeDatabase db; + + const auto files = dir.entryList(QDir::Files); + for (const QString &file : files) { + const auto path = startDir + QDir::separator() + file; + const QMimeType mime = db.mimeTypeForFile(QFileInfo(path)); + const auto mimeTypesForFile = mime.parentMimeTypes(); + + for (const auto &i : mimeTypesForFile) { + if (i.contains("text", Qt::CaseInsensitive) + || mime.comment().contains("text", Qt::CaseInsensitive)) { + names += startDir + QDir::separator() + file; + } + } + } + + const auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QString &subdir : subdirs) { + if (names.length() >= 20000) { + qDebug() << QCoreApplication::translate("main", "Too many files! Aborting ..."); + exit(-1); + } + names += findFiles(startDir + QDir::separator() + subdir); + } + return names; +} + +// Utility function that prints the results of the map in decreasing order based on the value. +void printHighestResult(const WordCount &countedWords, qsizetype nResults) +{ + using pair = QPair<QString, int>; + QList<pair> vec; + + std::copy(countedWords.keyValueBegin(), countedWords.keyValueEnd(), + std::back_inserter<QList<pair>>(vec)); + std::sort(vec.begin(), vec.end(), + [](const pair &l, const pair &r) { return l.second > r.second; }); + + qDebug() << QCoreApplication::translate("main", "Most occurring words are:"); + for (qsizetype i = 0; i < qMin(vec.size(), nResults); ++i) + qDebug() << vec[i].first << " : " << vec[i].second; } |