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