mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-02 23:35:28 +08:00
qt 6.5.1 original
This commit is contained in:
11
examples/qtconcurrent/CMakeLists.txt
Normal file
11
examples/qtconcurrent/CMakeLists.txt
Normal 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()
|
7
examples/qtconcurrent/README
Normal file
7
examples/qtconcurrent/README
Normal 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.
|
40
examples/qtconcurrent/imagescaling/CMakeLists.txt
Normal file
40
examples/qtconcurrent/imagescaling/CMakeLists.txt
Normal 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}"
|
||||
)
|
BIN
examples/qtconcurrent/imagescaling/doc/images/imagescaling.webp
Normal file
BIN
examples/qtconcurrent/imagescaling/doc/images/imagescaling.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -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
|
||||
*/
|
40
examples/qtconcurrent/imagescaling/downloaddialog.cpp
Normal file
40
examples/qtconcurrent/imagescaling/downloaddialog.cpp
Normal 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;
|
||||
}
|
28
examples/qtconcurrent/imagescaling/downloaddialog.h
Normal file
28
examples/qtconcurrent/imagescaling/downloaddialog.h
Normal 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
|
119
examples/qtconcurrent/imagescaling/downloaddialog.ui
Normal file
119
examples/qtconcurrent/imagescaling/downloaddialog.ui
Normal 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>
|
236
examples/qtconcurrent/imagescaling/imagescaling.cpp
Normal file
236
examples/qtconcurrent/imagescaling/imagescaling.cpp
Normal 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();
|
||||
}
|
53
examples/qtconcurrent/imagescaling/imagescaling.h
Normal file
53
examples/qtconcurrent/imagescaling/imagescaling.h
Normal 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
|
14
examples/qtconcurrent/imagescaling/imagescaling.pro
Normal file
14
examples/qtconcurrent/imagescaling/imagescaling.pro
Normal 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
|
17
examples/qtconcurrent/imagescaling/main.cpp
Normal file
17
examples/qtconcurrent/imagescaling/main.cpp
Normal 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();
|
||||
}
|
40
examples/qtconcurrent/primecounter/CMakeLists.txt
Normal file
40
examples/qtconcurrent/primecounter/CMakeLists.txt
Normal 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}"
|
||||
)
|
BIN
examples/qtconcurrent/primecounter/doc/images/primecounter.png
Normal file
BIN
examples/qtconcurrent/primecounter/doc/images/primecounter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
@ -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
|
||||
|
||||
*/
|
18
examples/qtconcurrent/primecounter/main.cpp
Normal file
18
examples/qtconcurrent/primecounter/main.cpp
Normal 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();
|
||||
}
|
139
examples/qtconcurrent/primecounter/primecounter.cpp
Normal file
139
examples/qtconcurrent/primecounter/primecounter.cpp
Normal 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;
|
||||
}
|
49
examples/qtconcurrent/primecounter/primecounter.h
Normal file
49
examples/qtconcurrent/primecounter/primecounter.h
Normal 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
|
9
examples/qtconcurrent/primecounter/primecounter.pro
Normal file
9
examples/qtconcurrent/primecounter/primecounter.pro
Normal 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
|
177
examples/qtconcurrent/primecounter/primecounter.ui
Normal file
177
examples/qtconcurrent/primecounter/primecounter.ui
Normal 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>
|
13
examples/qtconcurrent/qtconcurrent.pro
Normal file
13
examples/qtconcurrent/qtconcurrent.pro
Normal file
@ -0,0 +1,13 @@
|
||||
requires(qtHaveModule(concurrent))
|
||||
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = imagescaling \
|
||||
primecounter \
|
||||
wordcount
|
||||
|
||||
!qtHaveModule(widgets) {
|
||||
SUBDIRS -= \
|
||||
imagescaling \
|
||||
primecounter \
|
||||
wordcount
|
||||
}
|
32
examples/qtconcurrent/wordcount/CMakeLists.txt
Normal file
32
examples/qtconcurrent/wordcount/CMakeLists.txt
Normal 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}"
|
||||
)
|
@ -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.
|
||||
*/
|
152
examples/qtconcurrent/wordcount/main.cpp
Normal file
152
examples/qtconcurrent/wordcount/main.cpp
Normal 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;
|
||||
}
|
7
examples/qtconcurrent/wordcount/wordcount.pro
Normal file
7
examples/qtconcurrent/wordcount/wordcount.pro
Normal file
@ -0,0 +1,7 @@
|
||||
QT += concurrent widgets
|
||||
CONFIG += cmdline
|
||||
|
||||
SOURCES += main.cpp
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/wordcount
|
||||
INSTALLS += target
|
Reference in New Issue
Block a user