forked from qt-creator/qt-creator
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:
@@ -3,19 +3,24 @@
|
|||||||
|
|
||||||
#include "imagescaling.h"
|
#include "imagescaling.h"
|
||||||
#include "downloaddialog.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))
|
Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog(this))
|
||||||
{
|
{
|
||||||
resize(800, 600);
|
resize(800, 600);
|
||||||
|
|
||||||
addUrlsButton = new QPushButton(tr("Add URLs"));
|
QPushButton *addUrlsButton = new QPushButton(tr("Add URLs"));
|
||||||
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
|
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
|
||||||
|
|
||||||
cancelButton = new QPushButton(tr("Cancel"));
|
cancelButton = new QPushButton(tr("Cancel"));
|
||||||
cancelButton->setEnabled(false);
|
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();
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||||
buttonLayout->addWidget(addUrlsButton);
|
buttonLayout->addWidget(addUrlsButton);
|
||||||
@@ -32,141 +37,77 @@ Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDi
|
|||||||
mainLayout->addStretch();
|
mainLayout->addStretch();
|
||||||
mainLayout->addWidget(statusBar);
|
mainLayout->addWidget(statusBar);
|
||||||
setLayout(mainLayout);
|
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()
|
void Images::process()
|
||||||
{
|
{
|
||||||
// Clean previous state
|
if (downloadDialog->exec() != QDialog::Accepted)
|
||||||
replies.clear();
|
return;
|
||||||
addUrlsButton->setEnabled(false);
|
|
||||||
|
|
||||||
if (downloadDialog->exec() == QDialog::Accepted) {
|
const auto urls = downloadDialog->getUrls();
|
||||||
|
initLayout(urls.size());
|
||||||
const auto urls = downloadDialog->getUrls();
|
|
||||||
if (urls.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
const auto onRootSetup = [this] {
|
||||||
|
statusBar->showMessage(tr("Downloading and Scaling..."));
|
||||||
cancelButton->setEnabled(true);
|
cancelButton->setEnabled(true);
|
||||||
|
};
|
||||||
|
const auto onRootDone = [this] {
|
||||||
|
statusBar->showMessage(tr("Finished."));
|
||||||
|
cancelButton->setEnabled(false);
|
||||||
|
};
|
||||||
|
QList<GroupItem> tasks {
|
||||||
|
finishAllAndDone,
|
||||||
|
parallel,
|
||||||
|
onGroupSetup(onRootSetup),
|
||||||
|
onGroupDone(onRootDone)
|
||||||
|
};
|
||||||
|
|
||||||
initLayout(urls.size());
|
int i = 0;
|
||||||
|
for (const QUrl &url : urls) {
|
||||||
|
TreeStorage<QByteArray> storage;
|
||||||
|
|
||||||
downloadFuture = download(urls);
|
const auto onDownloadSetup = [this, url](NetworkQuery &query) {
|
||||||
statusBar->showMessage(tr("Downloading..."));
|
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()));
|
||||||
|
};
|
||||||
|
|
||||||
downloadFuture
|
const auto onScalingSetup = [storage](ConcurrentCall<QImage> &data) {
|
||||||
.then([this](auto) {
|
data.setConcurrentCallData(&scale, *storage);
|
||||||
cancelButton->setEnabled(false);
|
};
|
||||||
updateStatus(tr("Scaling..."));
|
const auto onScalingDone = [this, i](const ConcurrentCall<QImage> &data) {
|
||||||
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
|
labels[i]->setPixmap(QPixmap::fromImage(data.result()));
|
||||||
downloadFuture.results()));
|
};
|
||||||
})
|
const auto onScalingError = [this, i](const ConcurrentCall<QImage> &) {
|
||||||
.onCanceled([this] {
|
labels[i]->setText(tr("Image\nData\nError."));
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Images::cancel()
|
const Group group {
|
||||||
{
|
Storage(storage),
|
||||||
statusBar->showMessage(tr("Canceling..."));
|
NetworkQueryTask(onDownloadSetup, onDownloadDone, onDownloadError),
|
||||||
|
ConcurrentCallTask<QImage>(onScalingSetup, onScalingDone, onScalingError)
|
||||||
downloadFuture.cancel();
|
};
|
||||||
abortDownload();
|
tasks.append(group);
|
||||||
}
|
++i;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
taskTree.reset(new TaskTree(tasks));
|
||||||
}
|
connect(taskTree.get(), &TaskTree::done, this, [this] { taskTree.release()->deleteLater(); });
|
||||||
|
taskTree->start();
|
||||||
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]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Images::initLayout(qsizetype count)
|
void Images::initLayout(qsizetype count)
|
||||||
@@ -186,19 +127,9 @@ void Images::initLayout(qsizetype count)
|
|||||||
for (int j = 0; j < dim; ++j) {
|
for (int j = 0; j < dim; ++j) {
|
||||||
QLabel *imageLabel = new QLabel;
|
QLabel *imageLabel = new QLabel;
|
||||||
imageLabel->setFixedSize(100, 100);
|
imageLabel->setFixedSize(100, 100);
|
||||||
|
imageLabel->setAlignment(Qt::AlignCenter);
|
||||||
imagesLayout->addWidget(imageLabel, i, j);
|
imagesLayout->addWidget(imageLabel, i, j);
|
||||||
labels.append(imageLabel);
|
labels.append(imageLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Images::updateStatus(const QString &msg)
|
|
||||||
{
|
|
||||||
statusBar->showMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Images::abortDownload()
|
|
||||||
{
|
|
||||||
for (auto reply : replies)
|
|
||||||
reply->abort();
|
|
||||||
}
|
|
||||||
|
@@ -4,10 +4,9 @@
|
|||||||
#ifndef IMAGESCALING_H
|
#ifndef IMAGESCALING_H
|
||||||
#define IMAGESCALING_H
|
#define IMAGESCALING_H
|
||||||
|
|
||||||
#include <QtWidgets>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <optional>
|
#include <QtWidgets>
|
||||||
|
#include <tasking/tasktree.h>
|
||||||
|
|
||||||
class DownloadDialog;
|
class DownloadDialog;
|
||||||
class Images : public QWidget
|
class Images : public QWidget
|
||||||
@@ -15,27 +14,11 @@ class Images : public QWidget
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Images(QWidget *parent = nullptr);
|
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:
|
private:
|
||||||
using OptionalImages = std::optional<QList<QImage>>;
|
void process();
|
||||||
static OptionalImages scaled(const QList<QByteArray> &data);
|
void initLayout(qsizetype count);
|
||||||
|
|
||||||
QPushButton *addUrlsButton;
|
|
||||||
QPushButton *cancelButton;
|
QPushButton *cancelButton;
|
||||||
QVBoxLayout *mainLayout;
|
QVBoxLayout *mainLayout;
|
||||||
QList<QLabel *> labels;
|
QList<QLabel *> labels;
|
||||||
@@ -44,9 +27,7 @@ private:
|
|||||||
DownloadDialog *downloadDialog;
|
DownloadDialog *downloadDialog;
|
||||||
|
|
||||||
QNetworkAccessManager qnam;
|
QNetworkAccessManager qnam;
|
||||||
QList<QSharedPointer<QNetworkReply>> replies;
|
std::unique_ptr<Tasking::TaskTree> taskTree;
|
||||||
QFuture<QByteArray> downloadFuture;
|
|
||||||
QFutureWatcher<OptionalImages> scalingWatcher;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IMAGESCALING_H
|
#endif // IMAGESCALING_H
|
||||||
|
Reference in New Issue
Block a user