ImageScaling: Implement the example using TaskTree

Change-Id: Iac54157955d5dffe12a7fdeed904fbcf62a2b667
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Jarek Kobus
2023-06-03 01:34:32 +02:00
parent 2d128e9c70
commit 04b86eb3db
2 changed files with 69 additions and 157 deletions

View File

@@ -3,19 +3,24 @@
#include "imagescaling.h"
#include "downloaddialog.h"
#include <tasking/concurrentcall.h>
#include <tasking/networkquery.h>
#include <QNetworkReply>
using namespace Tasking;
Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog(this))
{
resize(800, 600);
addUrlsButton = new QPushButton(tr("Add URLs"));
QPushButton *addUrlsButton = new QPushButton(tr("Add URLs"));
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
cancelButton = new QPushButton(tr("Cancel"));
cancelButton->setEnabled(false);
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
connect(cancelButton, &QPushButton::clicked, this, [this] {
statusBar->showMessage(tr("Canceled."));
taskTree.reset();
});
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(addUrlsButton);
@@ -32,141 +37,77 @@ Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDi
mainLayout->addStretch();
mainLayout->addWidget(statusBar);
setLayout(mainLayout);
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished,
this, &Images::scaleFinished);
}
Images::~Images()
static void scale(QPromise<QImage> &promise, const QByteArray &data)
{
cancel();
const auto image = QImage::fromData(data);
if (image.isNull())
promise.future().cancel();
else
promise.addResult(image.scaled(100, 100, Qt::KeepAspectRatio));
}
void Images::process()
{
// Clean previous state
replies.clear();
addUrlsButton->setEnabled(false);
if (downloadDialog->exec() == QDialog::Accepted) {
if (downloadDialog->exec() != QDialog::Accepted)
return;
const auto urls = downloadDialog->getUrls();
if (urls.empty())
return;
cancelButton->setEnabled(true);
initLayout(urls.size());
downloadFuture = download(urls);
statusBar->showMessage(tr("Downloading..."));
downloadFuture
.then([this](auto) {
const auto onRootSetup = [this] {
statusBar->showMessage(tr("Downloading and Scaling..."));
cancelButton->setEnabled(true);
};
const auto onRootDone = [this] {
statusBar->showMessage(tr("Finished."));
cancelButton->setEnabled(false);
updateStatus(tr("Scaling..."));
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));
})
.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()));
})
.then([this]() {
cancelButton->setEnabled(false);
addUrlsButton->setEnabled(true);
});
}
}
};
QList<GroupItem> tasks {
finishAllAndDone,
parallel,
onGroupSetup(onRootSetup),
onGroupDone(onRootDone)
};
void Images::cancel()
{
statusBar->showMessage(tr("Canceling..."));
int i = 0;
for (const QUrl &url : urls) {
TreeStorage<QByteArray> storage;
downloadFuture.cancel();
abortDownload();
}
const auto onDownloadSetup = [this, url](NetworkQuery &query) {
query.setNetworkAccessManager(&qnam);
query.setRequest(QNetworkRequest(url));
};
const auto onDownloadDone = [storage](const NetworkQuery &query) {
*storage = query.reply()->readAll();
};
const auto onDownloadError = [this, i](const NetworkQuery &query) {
labels[i]->setText(tr("Download\nError.\nCode: %1.").arg(query.reply()->error()));
};
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);
}
const auto onScalingSetup = [storage](ConcurrentCall<QImage> &data) {
data.setConcurrentCallData(&scale, *storage);
};
const auto onScalingDone = [this, i](const ConcurrentCall<QImage> &data) {
labels[i]->setPixmap(QPixmap::fromImage(data.result()));
};
const auto onScalingError = [this, i](const ConcurrentCall<QImage> &) {
labels[i]->setText(tr("Image\nData\nError."));
};
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
promise->start();
for (const auto &url : urls) {
QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
replies.push_back(reply);
QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] {
if (promise->isCanceled()) {
if (!promise->future().isFinished())
promise->finish();
return;
const Group group {
Storage(storage),
NetworkQueryTask(onDownloadSetup, onDownloadDone, onDownloadError),
ConcurrentCallTask<QImage>(onScalingSetup, onScalingDone, onScalingError)
};
tasks.append(group);
++i;
}
if (reply->error() != QNetworkReply::NoError) {
if (!promise->future().isFinished())
throw reply->error();
}
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
}).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();
});
}
return promise->future();
}
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;
}
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]));
}
taskTree.reset(new TaskTree(tasks));
connect(taskTree.get(), &TaskTree::done, this, [this] { taskTree.release()->deleteLater(); });
taskTree->start();
}
void Images::initLayout(qsizetype count)
@@ -186,19 +127,9 @@ void Images::initLayout(qsizetype count)
for (int j = 0; j < dim; ++j) {
QLabel *imageLabel = new QLabel;
imageLabel->setFixedSize(100, 100);
imageLabel->setAlignment(Qt::AlignCenter);
imagesLayout->addWidget(imageLabel, i, j);
labels.append(imageLabel);
}
}
}
void Images::updateStatus(const QString &msg)
{
statusBar->showMessage(msg);
}
void Images::abortDownload()
{
for (auto reply : replies)
reply->abort();
}

View File

@@ -4,10 +4,9 @@
#ifndef IMAGESCALING_H
#define IMAGESCALING_H
#include <QtWidgets>
#include <QtConcurrent>
#include <QNetworkAccessManager>
#include <optional>
#include <QtWidgets>
#include <tasking/tasktree.h>
class DownloadDialog;
class Images : public QWidget
@@ -15,27 +14,11 @@ 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:
using OptionalImages = std::optional<QList<QImage>>;
static OptionalImages scaled(const QList<QByteArray> &data);
void process();
void initLayout(qsizetype count);
QPushButton *addUrlsButton;
QPushButton *cancelButton;
QVBoxLayout *mainLayout;
QList<QLabel *> labels;
@@ -44,9 +27,7 @@ private:
DownloadDialog *downloadDialog;
QNetworkAccessManager qnam;
QList<QSharedPointer<QNetworkReply>> replies;
QFuture<QByteArray> downloadFuture;
QFutureWatcher<OptionalImages> scalingWatcher;
std::unique_ptr<Tasking::TaskTree> taskTree;
};
#endif // IMAGESCALING_H