qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,11 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT TARGET Qt6::Concurrent)
return()
endif()
if(TARGET Qt6::Widgets)
qt_internal_add_example(imagescaling)
qt_internal_add_example(primecounter)
qt_internal_add_example(wordcount)
endif()

View File

@ -0,0 +1,7 @@
Qt 4 extends Qt's support for multithreaded applications with an API for
concurrent programming which includes implementations of the well-known
map-reduce and filter-reduce algorithms.
Documentation for these examples can be found via the Examples
link in the main Qt documentation.

View File

@ -0,0 +1,40 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(imagescaling LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/imagescaling")
find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Network Widgets)
qt_standard_project_setup()
qt_add_executable(imagescaling
downloaddialog.cpp downloaddialog.h downloaddialog.ui
imagescaling.cpp imagescaling.h
main.cpp
)
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}"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,180 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example imagescaling
\meta tags {widgets, threads, network}
\title Image Scaling
\ingroup qtconcurrentexamples
\brief Demonstrates how to asynchronously download and scale 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
*/

View File

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

View File

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

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DownloadDialog</class>
<widget class="QDialog" name="DownloadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>489</width>
<height>333</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLineEdit" name="urlLineEdit"/>
</item>
<item>
<widget class="QListWidget" name="urlListWidget"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="addUrlButton">
<property name="text">
<string>Add URL</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeUrlButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove URL</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearUrlsButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DownloadDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DownloadDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,236 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "imagescaling.h"
#include "downloaddialog.h"
#include <QNetworkReply>
Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog(this))
{
resize(800, 600);
addUrlsButton = new QPushButton(tr("Add URLs"));
//! [1]
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
//! [1]
cancelButton = new QPushButton(tr("Cancel"));
cancelButton->setEnabled(false);
//! [2]
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
//! [2]
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(addUrlsButton);
buttonLayout->addWidget(cancelButton);
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()
{
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]
//! [8]
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{
//! [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]
//! [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));
}
return scaled;
}
//! [14]
void Images::showImages(const QList<QImage> &images)
{
for (int i = 0; i < images.size(); ++i) {
labels[i]->setAlignment(Qt::AlignCenter);
labels[i]->setPixmap(QPixmap::fromImage(images[i]));
}
}
void Images::initLayout(qsizetype count)
{
// Clean old images
QLayoutItem *child;
while ((child = imagesLayout->takeAt(0)) != nullptr) {
child->widget()->setParent(nullptr);
delete child->widget();
delete child;
}
labels.clear();
// 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(100, 100);
imagesLayout->addWidget(imageLabel, i, j);
labels.append(imageLabel);
}
}
}
void Images::updateStatus(const QString &msg)
{
statusBar->showMessage(msg);
}
void Images::abortDownload()
{
for (auto reply : replies)
reply->abort();
}

View File

@ -0,0 +1,53 @@
// 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 process();
void cancel();
private slots:
void scaleFinished();
private:
//! [1]
using OptionalImages = std::optional<QList<QImage>>;
//! [1]
static OptionalImages scaled(const QList<QByteArray> &data);
QPushButton *addUrlsButton;
QPushButton *cancelButton;
QVBoxLayout *mainLayout;
QList<QLabel *> labels;
QGridLayout *imagesLayout;
QStatusBar *statusBar;
DownloadDialog *downloadDialog;
QNetworkAccessManager qnam;
QList<QSharedPointer<QNetworkReply>> replies;
QFuture<QByteArray> downloadFuture;
QFutureWatcher<OptionalImages> scalingWatcher;
};
#endif // IMAGESCALING_H

View File

@ -0,0 +1,14 @@
QT += concurrent widgets network
CONFIG += exceptions
requires(qtConfig(filedialog))
SOURCES += main.cpp imagescaling.cpp \
downloaddialog.cpp
HEADERS += imagescaling.h \
downloaddialog.h
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/imagescaling
INSTALLS += target
FORMS += \
downloaddialog.ui

View File

@ -0,0 +1,17 @@
// 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();
}

View File

@ -0,0 +1,40 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(primecounter LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/primecounter")
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
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,88 @@
// 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
\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
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,13 @@
requires(qtHaveModule(concurrent))
TEMPLATE = subdirs
SUBDIRS = imagescaling \
primecounter \
wordcount
!qtHaveModule(widgets) {
SUBDIRS -= \
imagescaling \
primecounter \
wordcount
}

View File

@ -0,0 +1,32 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(wordcount LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/wordcount")
find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(wordcount
main.cpp
)
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}"
)

View File

@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example wordcount
\meta tags {threads, console}
\title Word Count
\ingroup qtconcurrentexamples
\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 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.
\note The mapping function must be thread-safe since it is called from
multiple threads.
*/

View File

@ -0,0 +1,152 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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;
void printHighestResult(const WordCount &, qsizetype);
QStringList findFiles(const QString &);
// Single threaded word counter function.
WordCount singleThreadedWordCount(const QStringList &files)
{
WordCount wordCount;
for (const QString &file : files) {
QFile f(file);
f.open(QIODevice::ReadOnly);
QTextStream textStream(&f);
while (!textStream.atEnd()) {
const auto words = textStream.readLine().split(' ', Qt::SkipEmptyParts);
for (const QString &word : words)
wordCount[word] += 1;
}
}
return wordCount;
}
// countWords counts the words in a single file. This function is
// called in parallel by several threads and must be thread
// safe.
WordCount countWords(const QString &file)
{
QFile f(file);
f.open(QIODevice::ReadOnly);
QTextStream textStream(&f);
WordCount wordCount;
while (!textStream.atEnd()) {
const auto words = textStream.readLine().split(' ', Qt::SkipEmptyParts);
for (const QString &word : words)
wordCount[word] += 1;
}
return wordCount;
}
// reduce adds the results from map to the final
// result. This functor will only be called by one thread
// at a time.
void reduce(WordCount &result, const WordCount &w)
{
for (auto i = w.begin(), end = w.end(); i != end; ++i)
result[i.key()] += i.value();
}
int main(int argc, char** argv)
{
QApplication app(argc, argv);
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() << QCoreApplication::translate("main", "Single threaded scanning took %1 ms")
.arg(singleThreadTime);
}
// Start the multithreaded mappedReduced operation.
qint64 mapReduceTime;
{
QElapsedTimer timer;
timer.start();
//! [2]
WordCount total = QtConcurrent::mappedReduced(files, countWords, reduce).result();
//! [2]
mapReduceTime = timer.elapsed();
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);
}
}
// 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;
}

View File

@ -0,0 +1,7 @@
QT += concurrent widgets
CONFIG += cmdline
SOURCES += main.cpp
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/wordcount
INSTALLS += target