Utils: Let MarkdownBrowser preload first frame async

Change-Id: Ib732874c27247dbe11cf6a1eda5c92d0ff678a3a
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-11-21 09:20:40 +01:00
parent 23cf6a8382
commit a106c1ef99

View File

@@ -6,6 +6,7 @@
#include "algorithm.h"
#include "async.h"
#include "mimeutils.h"
#include "movie.h"
#include "networkaccessmanager.h"
#include "stylehelper.h"
#include "textutils.h"
@@ -19,7 +20,6 @@
#include <QBuffer>
#include <QCache>
#include <QGuiApplication>
#include <QMovie>
#include <QPainter>
#include <QTextBlock>
#include <QTextBrowser>
@@ -145,7 +145,7 @@ public:
class Entry
{
public:
using Pointer = std::unique_ptr<Entry>;
using Pointer = std::shared_ptr<Entry>;
Entry(const QByteArray &data)
{
@@ -154,10 +154,22 @@ public:
buffer.setData(data);
movie.setDevice(&buffer);
if (movie.isValid()) {
if (!movie.frameRect().isValid())
movie.jumpToFrame(0);
}
moveToThread(nullptr);
}
void moveToThread(QThread *thread)
{
buffer.moveToThread(thread);
movie.moveToThread(thread);
}
QBuffer buffer;
QMovie movie;
QtcMovie movie;
};
public:
@@ -176,19 +188,24 @@ public:
{
Q_UNUSED(doc);
Q_UNUSED(posInDocument);
QSize result = Utils::Icons::UNKNOWN_FILE.icon().actualSize(QSize(16, 16));
QString name = format.toImageFormat().name();
Entry *entry = m_entries.object(name);
if (entry && entry->movie.isValid()) {
if (!entry->movie.frameRect().isValid())
entry->movie.jumpToFrame(0);
return entry->movie.frameRect().size();
} else if (!entry) {
Entry::Pointer *entryPtr = m_entries.object(name);
if (!entryPtr) {
m_scheduleLoad(name);
return result;
}
return Utils::Icons::UNKNOWN_FILE.icon().actualSize(QSize(16, 16));
Entry::Pointer entry = *entryPtr;
if (entry->movie.isValid()) {
if (!entry->movie.frameRect().isValid())
entry->movie.jumpToFrame(0);
result = entry->movie.frameRect().size();
}
return result;
}
void drawObject(
@@ -201,18 +218,15 @@ public:
Q_UNUSED(document);
Q_UNUSED(posInDocument);
Entry *entry = m_entries.object(format.toImageFormat().name());
Entry::Pointer *entryPtr = m_entries.object(format.toImageFormat().name());
if (entry) {
if (entry->movie.isValid())
painter->drawImage(rect, entry->movie.currentImage());
else
painter->drawPixmap(rect.toRect(), m_brokenImage.pixmap(rect.size().toSize()));
return;
}
painter->drawPixmap(
rect.toRect(), Utils::Icons::UNKNOWN_FILE.icon().pixmap(rect.size().toSize()));
if (!entryPtr)
painter->drawPixmap(
rect.toRect(), Utils::Icons::UNKNOWN_FILE.icon().pixmap(rect.size().toSize()));
else if (!(*entryPtr)->movie.isValid())
painter->drawPixmap(rect.toRect(), m_brokenImage.pixmap(rect.size().toSize()));
else
painter->drawImage(rect, (*entryPtr)->movie.currentImage());
}
void set(const QString &name, QByteArray data)
@@ -220,17 +234,21 @@ public:
if (data.size() > m_entries.maxCost())
data.clear();
set(name, std::make_unique<Entry>(data));
set(name, std::make_shared<Entry>(data));
}
void set(const QString &name, std::unique_ptr<Entry> entry)
void set(const QString &name, Entry::Pointer entry)
{
entry->moveToThread(thread());
if (entry->movie.frameCount() > 1) {
connect(&entry->movie, &QMovie::frameChanged, this, [this]() { m_redraw(); });
connect(&entry->movie, &QtcMovie::frameChanged, this, [this]() { m_redraw(); });
entry->movie.start();
}
const qint64 size = qMax(1, entry->buffer.size());
if (m_entries.insert(name, entry.release(), size))
Entry::Pointer *entryPtr = new Entry::Pointer(entry);
if (m_entries.insert(name, entryPtr, size))
m_redraw();
}
@@ -239,7 +257,7 @@ public:
private:
std::function<void()> m_redraw;
std::function<void(const QString &)> m_scheduleLoad;
QCache<QString, Entry> m_entries;
QCache<QString, Entry::Pointer> m_entries;
const Icon ErrorCloseIcon = Utils::Icon({{":/utils/images/close.png", Theme::IconsErrorColor}});
@@ -292,57 +310,73 @@ public:
if (!m_loadRemoteImages)
remoteUrls.clear();
Storage<std::pair<QUrl, QByteArray>> remoteData;
const LoopList remoteIterator(Utils::toList(remoteUrls));
const LoopList localIterator(Utils::toList(localUrls));
auto onQuerySetup = [this, remoteIterator, base = m_basePath.toUrl()](NetworkQuery &query) {
QUrl url = *remoteIterator;
if (url.isRelative())
url = base.resolved(url);
auto onQuerySetup =
[remoteData, this, remoteIterator, base = m_basePath.toUrl()](NetworkQuery &query) {
QUrl url = *remoteIterator;
if (url.isRelative())
url = base.resolved(url);
QNetworkRequest request(url);
if (m_requestHook)
m_requestHook(&request);
QNetworkRequest request(url);
if (m_requestHook)
m_requestHook(&request);
query.setRequest(request);
query.setNetworkAccessManager(m_networkAccessManager);
query.setProperty("originalName", *remoteIterator);
};
query.setRequest(request);
query.setNetworkAccessManager(m_networkAccessManager);
remoteData->first = *remoteIterator;
};
auto onQueryDone = [this](const NetworkQuery &query, DoneWith result) {
auto onQueryDone = [this, remoteData](const NetworkQuery &query, DoneWith result) {
if (result == DoneWith::Cancel)
return;
m_urlsToLoad.remove(query.reply()->url());
if (result == DoneWith::Success)
m_imageHandler.set(query.property("originalName").toString(), query.reply()->readAll());
else
m_imageHandler.set(query.property("originalName").toString(), QByteArray{});
markContentsDirty(0, this->characterCount());
if (result == DoneWith::Success) {
remoteData->second = query.reply()->readAll();
} else {
m_imageHandler.set(remoteData->first.toString(), QByteArray{});
markContentsDirty(0, this->characterCount());
}
};
using EntryPointer = AnimatedImageHandler::Entry::Pointer;
auto onMakeEntrySetup = [remoteData](Async<EntryPointer> &async) {
async.setConcurrentCallData(
[](const QByteArray &data) {
return std::make_shared<AnimatedImageHandler::Entry>(data);
},
remoteData->second);
};
auto onMakeEntryDone =
[this, remoteIterator, remoteData](const Async<EntryPointer> &async) {
EntryPointer result = async.result();
if (result) {
m_imageHandler.set(remoteData->first.toString(), result);
markContentsDirty(0, this->characterCount());
}
};
auto onLocalSetup = [localIterator, basePath = m_basePath](Async<EntryPointer> &async) {
const FilePath u = basePath.resolvePath(localIterator->path());
async.setConcurrentCallData(
[](FilePath f) -> EntryPointer {
[](QPromise<EntryPointer> &promise, const FilePath &f) {
auto data = f.fileContents();
if (!data)
return nullptr;
if (!data || promise.isCanceled())
return;
return std::make_unique<AnimatedImageHandler::Entry>(*data);
promise.addResult(std::make_shared<AnimatedImageHandler::Entry>(*data));
},
u);
};
auto onLocalDone = [localIterator, this](const Async<EntryPointer> &async) {
#if QT_VERSION > QT_VERSION_CHECK(6, 5, 2)
EntryPointer result = async.takeResult();
#else
EntryPointer result = {};
#endif
EntryPointer result = async.result();
if (result)
m_imageHandler.set(localIterator->toString(), std::move(result));
};
@@ -351,7 +385,11 @@ public:
Group group {
parallelLimit(2),
For(remoteIterator) >> Do {
NetworkQueryTask{onQuerySetup, onQueryDone} || successItem,
remoteData,
Group {
NetworkQueryTask{onQuerySetup, onQueryDone},
AsyncTask<EntryPointer>(onMakeEntrySetup, onMakeEntryDone),
} || successItem,
},
For(localIterator) >> Do {
AsyncTask<EntryPointer>(onLocalSetup, onLocalDone) || successItem,
@@ -372,7 +410,7 @@ public:
void setBasePath(const FilePath &filePath) { m_basePath = filePath; }
void setAllowRemoteImages(bool allow) { m_loadRemoteImages = allow; }
void setNetworkAccessManager(QNetworkAccessManager *nam) { m_networkAccessManager = nam;}
void setNetworkAccessManager(QNetworkAccessManager *nam) { m_networkAccessManager = nam; }
void setRequestHook(const MarkdownBrowser::RequestHook &hook) { m_requestHook = hook; }
void setMaximumCacheSize(qsizetype maxSize) { m_imageHandler.setMaximumCacheSize(maxSize); }