TaskTree: Add a new example showing input / output data exchange

This example shows how to separate the business logic of the recipe
from the GUI and how to write a reusable recipe.

It shows how to feed the recipe with the initial data and how to
retrieve the final result from the recipe using onStorageSetup()
and onStorageDone() handlers.

Change-Id: I04c0c0c9bd6cf25ac4e91317e527ad12832e9143
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-10-26 12:53:25 +02:00
parent a53dfaf623
commit 5a2df08ae0
9 changed files with 293 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ Project {
"shootout/shootout.qbs",
"spinner/spinner.qbs",
"subdirfilecontainer/subdirfilecontainer.qbs",
"tasking/dataexchange/dataexchange.qbs",
"tasking/demo/demo.qbs",
"tasking/imagescaling/imagescaling.qbs",
"terminal/terminal.qbs",

View File

@@ -1,2 +1,3 @@
add_subdirectory(dataexchange)
add_subdirectory(demo)
add_subdirectory(imagescaling)

View File

@@ -0,0 +1,10 @@
add_qtc_test(tst_tasking_dataexchange
MANUALTEST
DEPENDS Tasking Qt::Concurrent Qt::Network Qt::Widgets
SOURCES
main.cpp
recipe.cpp
recipe.h
viewer.cpp
viewer.h
)

View File

@@ -0,0 +1,15 @@
QtcManualTest {
name: "Tasking dataexchange"
type: ["application"]
Depends { name: "Qt"; submodules: ["concurrent", "network", "widgets"] }
Depends { name: "Tasking" }
files: [
"main.cpp",
"recipe.cpp",
"recipe.h",
"viewer.cpp",
"viewer.h",
]
}

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "viewer.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setOrganizationName("QtProject");
app.setApplicationName("Data Exchange");
Viewer viewer;
viewer.show();
return app.exec();
}

View File

@@ -0,0 +1,86 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "recipe.h"
#include <tasking/concurrentcall.h>
#include <tasking/networkquery.h>
using namespace Tasking;
static void readImage(QPromise<QImage> &promise, const QByteArray &data)
{
const auto image = QImage::fromData(data);
if (image.isNull())
promise.future().cancel();
else
promise.addResult(image);
}
static void scaleImage(QPromise<QImage> &promise, const QImage &inputImage, const QSize &size)
{
promise.addResult(inputImage.scaled(size, Qt::KeepAspectRatio));
}
class InternalData
{
public:
QByteArray dataSource;
QImage imageSource;
};
static int sizeForIndex(int index) { return (index + 1) * s_sizeInterval; }
Group recipe(const Tasking::TreeStorage<ExternalData> &externalStorage)
{
TreeStorage<InternalData> internalStorage;
const auto onDownloadSetup = [externalStorage](NetworkQuery &query) {
query.setNetworkAccessManager(externalStorage->inputNam);
query.setRequest(QNetworkRequest(externalStorage->inputUrl));
};
const auto onDownloadDone = [internalStorage, externalStorage](const NetworkQuery &query,
DoneWith doneWith) {
if (doneWith == DoneWith::Success) {
internalStorage->dataSource = query.reply()->readAll();
} else {
externalStorage->outputError
= QString("Download Error. Code: %1.").arg(query.reply()->error());
}
};
const auto onReadSetup = [internalStorage](ConcurrentCall<QImage> &data) {
data.setConcurrentCallData(&readImage, internalStorage->dataSource);
};
const auto onReadDone = [internalStorage, externalStorage](const ConcurrentCall<QImage> &data,
DoneWith doneWith) {
if (doneWith == DoneWith::Success)
internalStorage->imageSource = data.result();
else
externalStorage->outputError = "Image Data Error.";
};
QList<GroupItem> parallelTasks;
parallelTasks.reserve(s_imageCount + 1); // +1 for parallelLimit
parallelTasks.append(parallelLimit(QThread::idealThreadCount() - 1));
for (int i = 0; i < s_imageCount; ++i) {
const int s = sizeForIndex(i);
const auto onScaleSetup = [internalStorage, s](ConcurrentCall<QImage> &data) {
data.setConcurrentCallData(&scaleImage, internalStorage->imageSource, QSize(s, s));
};
const auto onScaleDone = [externalStorage, s](const ConcurrentCall<QImage> &data) {
externalStorage->outputImages.insert(s, data.result());
};
parallelTasks.append(ConcurrentCallTask<QImage>(onScaleSetup, onScaleDone));
}
const QList<GroupItem> recipe {
Storage(externalStorage),
Storage(internalStorage),
NetworkQueryTask(onDownloadSetup, onDownloadDone),
ConcurrentCallTask<QImage>(onReadSetup, onReadDone),
Group { parallelTasks }
};
return recipe;
}

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef RECIPE_H
#define RECIPE_H
#include <QImage>
#include <QMap>
#include <QUrl>
#include <optional>
namespace Tasking {
class Group;
template <typename T>
class TreeStorage;
}
static const int s_sizeInterval = 10;
static const int s_imageCount = 100;
static const int s_maxSize = s_sizeInterval * s_imageCount;
class QNetworkAccessManager;
class ExternalData
{
public:
QNetworkAccessManager *inputNam = nullptr;
QUrl inputUrl;
QMap<int, QImage> outputImages;
std::optional<QString> outputError;
};
Tasking::Group recipe(const Tasking::TreeStorage<ExternalData> &externalStorage);
#endif // RECIPE_H

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "viewer.h"
#include "recipe.h"
#include <tasking/concurrentcall.h>
#include <tasking/networkquery.h>
using namespace Tasking;
Viewer::Viewer(QWidget *parent)
: QWidget(parent)
, m_recipe(recipe(m_storage))
{
setWindowTitle(tr("Data Exchange"));
QLabel *urlLabel = new QLabel(tr("Url:"));
m_lineEdit = new QLineEdit("https://media.licdn.com/dms/image/D4D22AQFj3ksh5rmnrg/"
"feedshare-shrink_800/0/1697023188446?e=1701302400&v=beta"
"&t=6dy5dmhzgONaLu139A6XmFSGqDohiezq1fH-q2mmu3w");
QPushButton *startButton = new QPushButton(tr("Start"));
QPushButton *stopButton = new QPushButton(tr("Stop"));
QPushButton *resetButton = new QPushButton(tr("Reset"));
m_progressBar = new QProgressBar;
m_listWidget = new QListWidget;
m_statusBar = new QStatusBar;
QBoxLayout *mainLayout = new QVBoxLayout(this);
QBoxLayout *subLayout1 = new QHBoxLayout;
QBoxLayout *subLayout2 = new QHBoxLayout;
subLayout1->addWidget(urlLabel);
subLayout1->addWidget(m_lineEdit);
subLayout2->addWidget(startButton);
subLayout2->addWidget(stopButton);
subLayout2->addWidget(resetButton);
subLayout2->addWidget(m_progressBar);
mainLayout->addLayout(subLayout1);
mainLayout->addLayout(subLayout2);
mainLayout->addWidget(m_listWidget);
mainLayout->addWidget(m_statusBar);
m_listWidget->setIconSize(QSize(s_maxSize, s_maxSize));
const auto reset = [this] {
m_listWidget->clear();
m_statusBar->clearMessage();
m_progressBar->setValue(0);
};
connect(startButton, &QAbstractButton::clicked, this, [this, reset] {
reset();
const auto setInput = [this](ExternalData &data) {
data.inputNam = &m_nam;
data.inputUrl = m_lineEdit->text();
m_statusBar->showMessage(tr("Executing recipe..."));
};
const auto getOutput = [this](const ExternalData &data) {
if (data.outputError) {
m_statusBar->showMessage(*data.outputError);
return;
}
m_statusBar->showMessage(tr("Recipe executed successfully."));
for (auto it = data.outputImages.begin(); it != data.outputImages.end(); ++it) {
m_listWidget->addItem(new QListWidgetItem(QPixmap::fromImage(it.value()),
QString("%1x%1").arg(it.key())));
}
};
const auto onDone = [this] { m_taskTree.release()->deleteLater(); };
m_taskTree.reset(new TaskTree(m_recipe));
m_taskTree->onStorageSetup(m_storage, setInput);
m_taskTree->onStorageDone(m_storage, getOutput);
m_progressBar->setMaximum(m_taskTree->progressMaximum());
QObject::connect(m_taskTree.get(), &TaskTree::progressValueChanged,
m_progressBar, &QProgressBar::setValue);
QObject::connect(m_taskTree.get(), &TaskTree::done, this, onDone);
m_taskTree->start();
});
connect(stopButton, &QAbstractButton::clicked, this, [this] {
if (m_taskTree) {
m_statusBar->showMessage(tr("Recipe stopped by user."));
m_taskTree.reset();
}
});
connect(resetButton, &QAbstractButton::clicked, this, [this, reset] {
m_taskTree.reset();
reset();
});
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef VIEWER_H
#define VIEWER_H
#include "recipe.h"
#include <tasking/tasktree.h>
#include <QNetworkAccessManager>
#include <QtWidgets>
class Viewer : public QWidget
{
Q_OBJECT
public:
Viewer(QWidget *parent = nullptr);
private:
QLineEdit *m_lineEdit = nullptr;
QProgressBar *m_progressBar = nullptr;
QListWidget *m_listWidget = nullptr;
QStatusBar *m_statusBar = nullptr;
QNetworkAccessManager m_nam;
const Tasking::TreeStorage<ExternalData> m_storage;
const Tasking::Group m_recipe;
std::unique_ptr<Tasking::TaskTree> m_taskTree;
};
#endif // VIEWER_H