diff options
15 files changed, 494 insertions, 86 deletions
diff --git a/doc/global/manifest-meta.qdocconf b/doc/global/manifest-meta.qdocconf index 633888c3d1..d3535e437a 100644 --- a/doc/global/manifest-meta.qdocconf +++ b/doc/global/manifest-meta.qdocconf @@ -40,7 +40,6 @@ manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \ "QtBluetooth/Bluetooth Scanner Example" \ "QtBluetooth/QML Bluetooth Scanner Example" \ "QtCharts/*" \ - "QtConcurrent/QtConcurrent Progress Dialog Example" \ "QtDataVisualization/Audiolevels Example" \ "QtDataVisualization/Qt Quick 2 Scatter Example" \ "QtDataVisualization/Qt Quick 2 Surface Multiseries Example" \ diff --git a/examples/qtconcurrent/CMakeLists.txt b/examples/qtconcurrent/CMakeLists.txt index 77244436e4..6a5ef9e8ce 100644 --- a/examples/qtconcurrent/CMakeLists.txt +++ b/examples/qtconcurrent/CMakeLists.txt @@ -6,6 +6,6 @@ if(NOT TARGET Qt6::Concurrent) endif() if(TARGET Qt6::Widgets) qt_internal_add_example(imagescaling) - qt_internal_add_example(progressdialog) + qt_internal_add_example(primecounter) qt_internal_add_example(wordcount) endif() diff --git a/examples/qtconcurrent/progressdialog/CMakeLists.txt b/examples/qtconcurrent/primecounter/CMakeLists.txt index 5609572d95..de098df324 100644 --- a/examples/qtconcurrent/progressdialog/CMakeLists.txt +++ b/examples/qtconcurrent/primecounter/CMakeLists.txt @@ -1,36 +1,39 @@ -# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) -project(progressdialog LANGUAGES CXX) +project(primecounter LANGUAGES CXX) if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/progressdialog") +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/primecounter") find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets) qt_standard_project_setup() -qt_add_executable(progressdialog +qt_add_executable(primecounter main.cpp + primecounter.ui + primecounter.cpp + primecounter.h ) -set_target_properties(progressdialog PROPERTIES - WIN32_EXECUTABLE FALSE +set_target_properties(primecounter PROPERTIES + WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE ) -target_link_libraries(progressdialog PRIVATE +target_link_libraries(primecounter PRIVATE Qt6::Concurrent Qt6::Core Qt6::Gui Qt6::Widgets ) -install(TARGETS progressdialog +install(TARGETS primecounter RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" diff --git a/examples/qtconcurrent/primecounter/doc/images/primecounter.png b/examples/qtconcurrent/primecounter/doc/images/primecounter.png Binary files differnew file mode 100644 index 0000000000..d611a507b5 --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/images/primecounter.png diff --git a/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc new file mode 100644 index 0000000000..75877c5a12 --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc @@ -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 + +*/ diff --git a/examples/qtconcurrent/primecounter/main.cpp b/examples/qtconcurrent/primecounter/main.cpp new file mode 100644 index 0000000000..acfd581341 --- /dev/null +++ b/examples/qtconcurrent/primecounter/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtWidgets/qapplication.h> +#include "primecounter.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("QtProject"); + app.setApplicationName(QApplication::translate("main", "Prime Counter")); + + PrimeCounter dialog; + dialog.setWindowTitle(QApplication::translate("main", "Prime Counter")); + dialog.show(); + + return app.exec(); +} diff --git a/examples/qtconcurrent/primecounter/primecounter.cpp b/examples/qtconcurrent/primecounter/primecounter.cpp new file mode 100644 index 0000000000..01bde5d87f --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "primecounter.h" +#include "ui_primecounter.h" + +PrimeCounter::PrimeCounter(QWidget *parent) + : QDialog(parent), stepSize(100000), ui(setupUi()) +{ + // Control the concurrent operation with the QFutureWatcher + //! [1] + connect(ui->pushButton, &QPushButton::clicked, + this, [this] { start(); }); + connect(&watcher, &QFutureWatcher<Element>::finished, + this, [this] { finish(); }); + connect(&watcher, &QFutureWatcher<Element>::progressRangeChanged, + ui->progressBar, &QProgressBar::setRange); + connect(&watcher, &QFutureWatcher<Element>::progressValueChanged, + ui->progressBar, &QProgressBar::setValue); + //! [1] +} + +PrimeCounter::~PrimeCounter() +{ + watcher.cancel(); + delete ui; +} + +//! [3] +bool PrimeCounter::filterFunction(const Element &element) +{ + // Filter for primes + if (element <= 1) + return false; + for (Element i = 2; i*i <= element; ++i) { + if (element % i == 0) + return false; + } + return true; +} +//! [3] + +//! [4] +void PrimeCounter::reduceFunction(Element &out, const Element &value) +{ + // Count the amount of primes. + Q_UNUSED(value); + ++out; +} +//! [4] + +//! [2] +void PrimeCounter::start() +{ + if (ui->pushButton->isChecked()) { + ui->comboBox->setEnabled(false); + ui->pushButton->setText(tr("Cancel")); + ui->labelResult->setText(tr("Calculating ...")); + ui->labelFilter->setText(tr("Selected Reduce Option: %1").arg(ui->comboBox->currentText())); + fillElementList(ui->horizontalSlider->value() * stepSize); + + timer.start(); + watcher.setFuture( + QtConcurrent::filteredReduced( + &pool, + elementList, + filterFunction, + reduceFunction, + currentReduceOpt | QtConcurrent::SequentialReduce)); +//! [2] + } else { + watcher.cancel(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->labelResult->setText(tr("")); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText(tr("Operation Canceled")); + } +} + +void PrimeCounter::finish() +{ + // The finished signal from the QFutureWatcher is also emitted when cancelling. + if (watcher.isCanceled()) + return; + + auto elapsedTime = timer.elapsed(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->pushButton->setChecked(false); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText( + tr("Filter '%1' took %2 ms to calculate").arg(ui->comboBox->currentText()) + .arg(elapsedTime)); + ui->labelResult->setText( + tr("Found %1 primes in the range of elements").arg(watcher.result())); +} + +void PrimeCounter::fillElementList(unsigned int count) +{ + // Fill elementList with values from [1, count] when starting the calculations. + auto prevSize = elementList.size(); + if (prevSize == count) + return; // Nothing to do here. + + auto startVal = elementList.empty() ? 1 : elementList.back() + 1; + elementList.resize(count); + if (elementList.begin() + prevSize < elementList.end()) + std::iota(elementList.begin() + prevSize, elementList.end(), startVal); +} + +Ui::PrimeCounter* PrimeCounter::setupUi() +{ + Ui::PrimeCounter *setupUI = new Ui::PrimeCounter; + setupUI->setupUi(this); + setModal(true); + + // Set up the slider + connect(setupUI->horizontalSlider, &QSlider::valueChanged, + this, [setupUI, this] (const int &pos) { + setupUI->labelResult->setText(""); + setupUI->labelSize->setText(tr("Elements in list: %1").arg(pos * stepSize)); + }); + setupUI->horizontalSlider->setValue(30); + + // Set up the combo box + setupUI->comboBox->insertItem(0, tr("Unordered Reduce"), QtConcurrent::UnorderedReduce); + setupUI->comboBox->insertItem(1, tr("Ordered Reduce"), QtConcurrent::OrderedReduce); + + auto comboBoxChange = [this, setupUI](int pos) { + currentReduceOpt = setupUI->comboBox->itemData(pos).value<QtConcurrent::ReduceOptions>(); + setupUI->labelFilter->setText(tr("Selected Reduce Option: %1") + .arg(setupUI->comboBox->currentText())); + }; + comboBoxChange(setupUI->comboBox->currentIndex()); + connect(setupUI->comboBox, &QComboBox::currentIndexChanged, this, comboBoxChange); + + return setupUI; +} diff --git a/examples/qtconcurrent/primecounter/primecounter.h b/examples/qtconcurrent/primecounter/primecounter.h new file mode 100644 index 0000000000..5b6d442cc8 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.h @@ -0,0 +1,49 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef PRIMECOUNTER_H +#define PRIMECOUNTER_H + +#include <QtWidgets/qdialog.h> +#include <QtCore/qfuturewatcher.h> +#include <QtConcurrent/qtconcurrentfilter.h> +#include <QtConcurrent/qtconcurrentreducekernel.h> + +QT_BEGIN_NAMESPACE +class QLabel; +class QProgressBar; +namespace Ui { +class PrimeCounter; +} + +QT_END_NAMESPACE + +class PrimeCounter : public QDialog +{ + Q_OBJECT + using Element = unsigned long long; +public: + explicit PrimeCounter(QWidget* parent = nullptr); + ~PrimeCounter() override; + +private: + static bool filterFunction(const Element &element); + static void reduceFunction(Element &out, const Element &value); + void fillElementList(unsigned int count); + Ui::PrimeCounter* setupUi(); + +private slots: + void start(); + void finish(); + +private: + QList<Element> elementList; + QFutureWatcher<Element> watcher; + QtConcurrent::ReduceOptions currentReduceOpt; + QElapsedTimer timer; + QThreadPool pool; + unsigned int stepSize; + Ui::PrimeCounter *ui; +}; + +#endif //PRIMECOUNTER_H diff --git a/examples/qtconcurrent/primecounter/primecounter.pro b/examples/qtconcurrent/primecounter/primecounter.pro new file mode 100644 index 0000000000..bb5a740688 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.pro @@ -0,0 +1,9 @@ +QT += concurrent widgets + +SOURCES += main.cpp primecounter.cpp +HEADERS += primecounter.h + +target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/primecounter +INSTALLS += target + +FORMS += primecounter.ui diff --git a/examples/qtconcurrent/primecounter/primecounter.ui b/examples/qtconcurrent/primecounter/primecounter.ui new file mode 100644 index 0000000000..625462e05e --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.ui @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PrimeCounter</class> + <widget class="QDialog" name="PrimeCounter"> + <property name="windowModality"> + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>454</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>20</number> + </property> + <property name="topMargin"> + <number>20</number> + </property> + <property name="rightMargin"> + <number>20</number> + </property> + <property name="bottomMargin"> + <number>20</number> + </property> + <item> + <widget class="QLabel" name="labelInfo"> + <property name="font"> + <font> + <pointsize>11</pointsize> + <bold>false</bold> + </font> + </property> + <property name="text"> + <string>Select a reducing option and measure the speed</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>30</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout2"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QComboBox" name="comboBox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout1"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Start</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelFilter"> + <property name="text"> + <string>Filter Label</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout3"> + <item> + <widget class="QSlider" name="horizontalSlider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelSize"> + <property name="text"> + <string>size</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + <property name="indent"> + <number>10</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelResult"> + <property name="text"> + <string>Result Label</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png b/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png Binary files differdeleted file mode 100644 index 2e8b7735ad..0000000000 --- a/examples/qtconcurrent/progressdialog/doc/images/qtconcurrent-progressdialog.png +++ /dev/null diff --git a/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc b/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc deleted file mode 100644 index 103dbda8ba..0000000000 --- a/examples/qtconcurrent/progressdialog/doc/src/qtconcurrent-progressdialog.qdoc +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example progressdialog - \title QtConcurrent Progress Dialog Example - \brief Demonstrates how to monitor the progress of the active processes. - \ingroup qtconcurrentexamples - - The QtConcurrent Progress Dialog example shows how to use the - QFutureWatcher class to monitor the progress of a long-running operation. - - \image qtconcurrent-progressdialog.png -*/ diff --git a/examples/qtconcurrent/progressdialog/main.cpp b/examples/qtconcurrent/progressdialog/main.cpp deleted file mode 100644 index 702f59e910..0000000000 --- a/examples/qtconcurrent/progressdialog/main.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QtWidgets> -#include <QtConcurrent> - -#include <functional> - -using namespace QtConcurrent; - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - - const int iterations = 20; - - // Prepare the list. - QList<int> list; - for (int i = 0; i < iterations; ++i) - list.append(i); - - // Create a progress dialog. - QProgressDialog dialog; - dialog.setLabelText(QString("Progressing using %1 thread(s)...").arg(QThread::idealThreadCount())); - - // Create a QFutureWatcher and connect signals and slots. - QFutureWatcher<void> futureWatcher; - QObject::connect(&futureWatcher, &QFutureWatcher<void>::finished, &dialog, &QProgressDialog::reset); - QObject::connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher<void>::cancel); - QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &dialog, &QProgressDialog::setRange); - QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, &QProgressDialog::setValue); - - // Our function to compute - std::function<void(int&)> spin = [](int &iteration) { - const int work = 1000 * 1000 * 40; - volatile int v = 0; - for (int j = 0; j < work; ++j) - ++v; - - qDebug() << "iteration" << iteration << "in thread" << QThread::currentThreadId(); - }; - - // Start the computation. - futureWatcher.setFuture(QtConcurrent::map(list, spin)); - - // Display the dialog and start the event loop. - dialog.exec(); - - futureWatcher.waitForFinished(); - - // Query the future to check if was canceled. - qDebug() << "Canceled?" << futureWatcher.future().isCanceled(); -} diff --git a/examples/qtconcurrent/progressdialog/progressdialog.pro b/examples/qtconcurrent/progressdialog/progressdialog.pro deleted file mode 100644 index 8a5b3aabb7..0000000000 --- a/examples/qtconcurrent/progressdialog/progressdialog.pro +++ /dev/null @@ -1,7 +0,0 @@ -QT += concurrent widgets -CONFIG += console - -SOURCES += main.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/progressdialog -INSTALLS += target diff --git a/examples/qtconcurrent/qtconcurrent.pro b/examples/qtconcurrent/qtconcurrent.pro index 1db6013c4a..ea7618fa00 100644 --- a/examples/qtconcurrent/qtconcurrent.pro +++ b/examples/qtconcurrent/qtconcurrent.pro @@ -2,12 +2,12 @@ requires(qtHaveModule(concurrent)) TEMPLATE = subdirs SUBDIRS = imagescaling \ - progressdialog \ + primecounter \ wordcount !qtHaveModule(widgets) { SUBDIRS -= \ imagescaling \ - progressdialog \ + primecounter \ wordcount } |