forked from qt-creator/qt-creator
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:
@@ -13,6 +13,7 @@ Project {
|
|||||||
"shootout/shootout.qbs",
|
"shootout/shootout.qbs",
|
||||||
"spinner/spinner.qbs",
|
"spinner/spinner.qbs",
|
||||||
"subdirfilecontainer/subdirfilecontainer.qbs",
|
"subdirfilecontainer/subdirfilecontainer.qbs",
|
||||||
|
"tasking/dataexchange/dataexchange.qbs",
|
||||||
"tasking/demo/demo.qbs",
|
"tasking/demo/demo.qbs",
|
||||||
"tasking/imagescaling/imagescaling.qbs",
|
"tasking/imagescaling/imagescaling.qbs",
|
||||||
"terminal/terminal.qbs",
|
"terminal/terminal.qbs",
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
|
add_subdirectory(dataexchange)
|
||||||
add_subdirectory(demo)
|
add_subdirectory(demo)
|
||||||
add_subdirectory(imagescaling)
|
add_subdirectory(imagescaling)
|
||||||
|
10
tests/manual/tasking/dataexchange/CMakeLists.txt
Normal file
10
tests/manual/tasking/dataexchange/CMakeLists.txt
Normal 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
|
||||||
|
)
|
15
tests/manual/tasking/dataexchange/dataexchange.qbs
Normal file
15
tests/manual/tasking/dataexchange/dataexchange.qbs
Normal 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",
|
||||||
|
]
|
||||||
|
}
|
17
tests/manual/tasking/dataexchange/main.cpp
Normal file
17
tests/manual/tasking/dataexchange/main.cpp
Normal 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();
|
||||||
|
}
|
86
tests/manual/tasking/dataexchange/recipe.cpp
Normal file
86
tests/manual/tasking/dataexchange/recipe.cpp
Normal 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;
|
||||||
|
}
|
36
tests/manual/tasking/dataexchange/recipe.h
Normal file
36
tests/manual/tasking/dataexchange/recipe.h
Normal 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
|
94
tests/manual/tasking/dataexchange/viewer.cpp
Normal file
94
tests/manual/tasking/dataexchange/viewer.cpp
Normal 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();
|
||||||
|
});
|
||||||
|
}
|
33
tests/manual/tasking/dataexchange/viewer.h
Normal file
33
tests/manual/tasking/dataexchange/viewer.h
Normal 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
|
Reference in New Issue
Block a user