preview: add cache for code block in-place preview

This commit is contained in:
Le Tan 2018-05-15 20:12:29 +08:00
parent f2afe4b4e2
commit 4284d20dea
4 changed files with 181 additions and 200 deletions

View File

@ -1,6 +1,5 @@
#include "vlivepreviewhelper.h"
#include <QDebug>
#include <QByteArray>
#include "veditor.h"
@ -30,8 +29,6 @@ extern VMainWindow *g_mainWin;
#define INDEX_MASK 0x00ffffffUL
#define SCALE_FACTOR_THRESHOLD 1.1
CodeBlockPreviewInfo::CodeBlockPreviewInfo()
{
}
@ -41,33 +38,12 @@ CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb)
{
}
void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
const VCodeBlock &p_cb)
{
m_codeBlock.updateNonContent(p_cb);
if (m_inplacePreview.isNull()) {
return;
}
QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock);
if (block.isValid()) {
m_inplacePreview->m_startPos = block.position();
m_inplacePreview->m_endPos = block.position() + block.length();
m_inplacePreview->m_blockPos = block.position();
m_inplacePreview->m_blockNumber = m_codeBlock.m_endBlock;
// Padding is not changed since content is not changed.
} else {
m_inplacePreview.clear();
}
}
void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
const QTextDocument *p_doc,
qreal p_scaleFactor)
const QPixmap &p_image)
{
QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock);
if (block.isValid()) {
Qt::TransformationMode tMode = Qt::SmoothTransformation;
VImageToPreview *preview = new VImageToPreview();
preview->m_startPos = block.position();
@ -79,29 +55,7 @@ void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
preview->m_name = QString::number(getImageIndex());
preview->m_isBlock = true;
if (hasImageData()) {
if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) {
preview->m_image.loadFromData(m_imgData.toUtf8(),
m_imgFormat.toLocal8Bit().data());
} else {
QPixmap tmpImg;
tmpImg.loadFromData(m_imgData.toUtf8(),
m_imgFormat.toLocal8Bit().data());
preview->m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, tMode);
}
} else if (hasImageDataBa()) {
if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) {
preview->m_image.loadFromData(m_imgDataBa,
m_imgFormat.toLocal8Bit().data());
} else {
QPixmap tmpImg;
tmpImg.loadFromData(m_imgDataBa,
m_imgFormat.toLocal8Bit().data());
preview->m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, tMode);
}
} else {
preview->m_image = QPixmap();
}
preview->m_image = p_image;
m_inplacePreview.reset(preview);
} else {
@ -110,6 +64,9 @@ void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
}
#define CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF 10
#define CODE_BLOCK_IMAGE_CACHE_TIME_DIFF 5
VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
VDocument *p_document,
QObject *p_parent)
@ -144,13 +101,26 @@ VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
this, &VLivePreviewHelper::mathjaxPreviewResultReady);
}
bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const
void VLivePreviewHelper::checkLang(const QString &p_lang,
bool &p_livePreview,
bool &p_inplacePreview) const
{
return (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart"))
|| (m_mermaidEnabled && p_lang == "mermaid")
|| (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml")
|| (m_graphvizEnabled && p_lang == "dot")
|| (m_mathjaxEnabled && p_lang == "mathjax");
if (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart")) {
p_livePreview = p_inplacePreview = true;
} else if (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml") {
p_livePreview = true;
p_inplacePreview = m_plantUMLMode == PlantUMLMode::LocalPlantUML;
} else if (m_graphvizEnabled && p_lang == "dot") {
p_livePreview = p_inplacePreview = true;
} else if (m_mermaidEnabled && p_lang == "mermaid") {
p_livePreview = true;
p_inplacePreview = false;
} else if (m_mathjaxEnabled && p_lang == "mathjax") {
p_livePreview = false;
p_inplacePreview = true;
} else {
p_livePreview = p_inplacePreview = false;
}
}
void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks)
@ -164,34 +134,42 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
int lastIndex = m_cbIndex;
m_cbIndex = -1;
int cursorBlock = m_editor->textCursorW().block().blockNumber();
int idx = 0;
bool needUpdate = m_livePreviewEnabled;
bool manualInplacePreview = m_inplacePreviewEnabled;
for (auto const & vcb : p_codeBlocks) {
if (!isPreviewLang(vcb.m_lang)) {
m_codeBlocks.clear();
for (int i = 0; i < p_codeBlocks.size(); ++i) {
const VCodeBlock &vcb = p_codeBlocks[i];
bool livePreview = false, inplacePreview = false;
checkLang(vcb.m_lang, livePreview, inplacePreview);
if (!livePreview && !inplacePreview) {
continue;
}
const QString &text = vcb.m_text;
bool cached = false;
if (idx < m_codeBlocks.size()) {
CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
if (cb.codeBlock().equalContent(vcb)) {
cb.updateNonContent(m_doc, vcb);
cached = true;
} else {
cb.setCodeBlock(vcb);
}
} else {
m_codeBlocks.append(CodeBlockPreviewInfo(vcb));
int idx = m_codeBlocks.size() - 1;
auto it = m_cache.find(text);
if (it != m_cache.end()) {
QSharedPointer<CodeBlockImageCacheEntry> &entry = it.value();
entry->m_ts = m_timeStamp;
cached = true;
m_codeBlocks[idx].setImageData(entry->m_imgFormat, entry->m_imgData);
m_codeBlocks[idx].updateInplacePreview(m_editor, m_doc, entry->m_image);
}
if (m_inplacePreviewEnabled
&& inplacePreview
&& (!cached || !m_codeBlocks[idx].inplacePreviewReady())) {
processForInplacePreview(idx);
manualInplacePreview = false;
processForInplacePreview(idx);
}
if (m_livePreviewEnabled
&& livePreview
&& vcb.m_startBlock <= cursorBlock
&& vcb.m_endBlock >= cursorBlock) {
if (lastIndex == idx && cached) {
@ -200,14 +178,6 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
m_cbIndex = idx;
}
++idx;
}
if (idx == m_codeBlocks.size()) {
manualInplacePreview = false;
} else {
m_codeBlocks.resize(idx);
}
if (manualInplacePreview) {
@ -217,6 +187,8 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
if (needUpdate) {
updateLivePreview();
}
clearObsoleteCache();
}
void VLivePreviewHelper::handleCursorPositionChanged()
@ -319,6 +291,7 @@ void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled)
if (!m_inplacePreviewEnabled) {
m_codeBlocks.clear();
m_cache.clear();
updateInplacePreview();
}
}
@ -333,6 +306,7 @@ void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled)
m_inplacePreviewEnabled = p_enabled;
if (!m_inplacePreviewEnabled && !m_livePreviewEnabled) {
m_codeBlocks.clear();
m_cache.clear();
}
updateInplacePreview();
@ -371,7 +345,17 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
}
CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
const QString &text = cb.codeBlock().m_text;
QSharedPointer<CodeBlockImageCacheEntry> entry(new CodeBlockImageCacheEntry(p_timeStamp,
p_format,
p_result,
getScaleFactor(cb)));
m_cache.insert(text, entry);
cb.setImageData(p_format, p_result);
cb.updateInplacePreview(m_editor, m_doc, entry->m_image);
if (livePreview) {
if (idx != m_cbIndex) {
return;
@ -380,8 +364,6 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
m_document->setPreviewContent(lang, p_result);
} else {
// Inplace preview.
cb.updateInplacePreview(m_editor, m_doc, getScaleFactor(cb));
updateInplacePreview();
}
}
@ -390,7 +372,7 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
{
CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx];
const VCodeBlock &vcb = cb.codeBlock();
Q_ASSERT(!(cb.hasImageData() || cb.hasImageDataBa()));
Q_ASSERT(!cb.hasImageData());
if (vcb.m_lang == "dot") {
if (!m_graphvizHelper) {
m_graphvizHelper = new VGraphvizHelper(this);
@ -474,7 +456,31 @@ void VLivePreviewHelper::mathjaxPreviewResultReady(int p_identitifer,
}
CodeBlockPreviewInfo &cb = m_codeBlocks[p_id];
cb.setImageDataBa(p_format, p_data);
cb.updateInplacePreview(m_editor, m_doc, getScaleFactor(cb));
const QString &text = cb.codeBlock().m_text;
QSharedPointer<CodeBlockImageCacheEntry> entry(new CodeBlockImageCacheEntry(p_timeStamp,
p_format,
p_data,
getScaleFactor(cb)));
m_cache.insert(text, entry);
cb.updateInplacePreview(m_editor, m_doc, entry->m_image);
updateInplacePreview();
}
void VLivePreviewHelper::clearObsoleteCache()
{
if (m_cache.size() - m_codeBlocks.size() <= CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF) {
return;
}
for (auto it = m_cache.begin(); it != m_cache.end();) {
if (m_timeStamp - it.value()->m_ts > CODE_BLOCK_IMAGE_CACHE_TIME_DIFF) {
it.value().clear();
it = m_cache.erase(it);
} else {
++it;
}
}
}

View File

@ -21,19 +21,9 @@ public:
explicit CodeBlockPreviewInfo(const VCodeBlock &p_cb);
void clearImageData()
{
m_imgData.clear();
m_imgDataBa.clear();
m_inplacePreview.clear();
}
void updateNonContent(const QTextDocument *p_doc,
const VCodeBlock &p_cb);
void updateInplacePreview(const VEditor *p_editor,
const QTextDocument *p_doc,
qreal p_scaleFactor);
const QPixmap &p_image);
VCodeBlock &codeBlock()
{
@ -45,13 +35,6 @@ public:
return m_codeBlock;
}
void setCodeBlock(const VCodeBlock &p_cb)
{
m_codeBlock = p_cb;
clearImageData();
}
bool inplacePreviewReady() const
{
return !m_inplacePreview.isNull();
@ -67,16 +50,6 @@ public:
return m_imgData;
}
bool hasImageDataBa() const
{
return !m_imgDataBa.isEmpty();
}
const QByteArray &imageDataBa() const
{
return m_imgDataBa;
}
const QString &imageFormat() const
{
return m_imgFormat;
@ -84,22 +57,10 @@ public:
void setImageData(const QString &p_format, const QString &p_data)
{
m_imgDataBa.clear();
m_inplacePreview.clear();
m_imgFormat = p_format;
m_imgData = p_data;
}
void setImageDataBa(const QString &p_format, const QByteArray &p_data)
{
m_imgData.clear();
m_inplacePreview.clear();
m_imgFormat = p_format;
m_imgDataBa = p_data;
}
const QSharedPointer<VImageToPreview> inplacePreview() const
{
return m_inplacePreview;
@ -116,8 +77,6 @@ private:
QString m_imgData;
QByteArray m_imgDataBa;
QString m_imgFormat;
QSharedPointer<VImageToPreview> m_inplacePreview;
@ -161,7 +120,79 @@ private slots:
const QByteArray &p_data);
private:
bool isPreviewLang(const QString &p_lang) const;
struct CodeBlockImageCacheEntry
{
#define SCALE_FACTOR_THRESHOLD 1.1
CodeBlockImageCacheEntry()
: m_ts(0)
{
}
CodeBlockImageCacheEntry(TimeStamp p_ts,
const QString &p_format,
const QByteArray &p_data,
qreal p_scaleFactor)
: m_ts(p_ts)
{
if (!p_data.isEmpty()) {
if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) {
m_image.loadFromData(p_data,
p_format.toLocal8Bit().data());
} else {
QPixmap tmpImg;
tmpImg.loadFromData(p_data,
p_format.toLocal8Bit().data());
m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor,
Qt::SmoothTransformation);
}
}
}
CodeBlockImageCacheEntry(TimeStamp p_ts,
const QString &p_format,
const QString &p_data,
qreal p_scaleFactor)
: m_ts(p_ts),
m_imgData(p_data),
m_imgFormat(p_format)
{
if (!p_data.isEmpty()) {
if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) {
m_image.loadFromData(p_data.toUtf8(),
p_format.toLocal8Bit().data());
} else {
QPixmap tmpImg;
tmpImg.loadFromData(p_data.toUtf8(),
p_format.toLocal8Bit().data());
m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor,
Qt::SmoothTransformation);
}
}
}
bool hasImageData() const
{
return !m_imgData.isEmpty();
}
bool hasImage() const
{
return !m_image.isNull();
}
TimeStamp m_ts;
// For live preview.
QString m_imgData;
QString m_imgFormat;
// For in-place preview.
QPixmap m_image;
};
void checkLang(const QString &p_lang, bool &p_livePreview, bool &p_inplacePreview) const;
// Get image data for this code block for inplace preview.
void processForInplacePreview(int p_idx);
@ -171,6 +202,8 @@ private:
qreal getScaleFactor(const CodeBlockPreviewInfo &p_cb);
void clearObsoleteCache();
// Sorted by m_startBlock in ascending order.
QVector<CodeBlockPreviewInfo> m_codeBlocks;
@ -206,6 +239,9 @@ private:
TimeStamp m_timeStamp;
const qreal m_scaleFactor;
// Indexed by content.
QHash<QString, QSharedPointer<CodeBlockImageCacheEntry>> m_cache;
};
inline bool VLivePreviewHelper::isPreviewEnabled() const

View File

@ -19,32 +19,9 @@ MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb)
{
}
void MathjaxBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
const VEditor *p_editor,
const VMathjaxBlock &p_mb)
{
m_mathjaxBlock.updateNonContent(p_mb);
if (m_inplacePreview.isNull()) {
return;
}
QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber);
if (block.isValid()) {
m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index;
m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length;
m_inplacePreview->m_blockPos = block.position();
m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber;
// Padding may changed.
m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block,
p_editor->tabStopWidthW());
m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock;
} else {
m_inplacePreview.clear();
}
}
void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
const QTextDocument *p_doc)
const QTextDocument *p_doc,
const QPixmap &p_image)
{
QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber);
if (block.isValid()) {
@ -59,12 +36,7 @@ void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
preview->m_name = QString::number(getImageIndex());
preview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock;
if (hasImageDataBa()) {
preview->m_image.loadFromData(m_imgDataBa,
m_imgFormat.toLocal8Bit().data());
} else {
preview->m_image = QPixmap();
}
preview->m_image = p_image;
m_inplacePreview.reset(preview);
} else {
@ -133,13 +105,12 @@ void VMathJaxInplacePreviewHelper::updateMathjaxBlocks(const QVector<VMathjaxBlo
QSharedPointer<MathjaxImageCacheEntry> &entry = it.value();
entry->m_ts = m_timeStamp;
cached = true;
m_mathjaxBlocks[i].setImageDataBa(entry->m_imgFormat, entry->m_imgDataBa);
m_mathjaxBlocks[i].updateInplacePreview(m_editor, m_doc);
m_mathjaxBlocks.last().updateInplacePreview(m_editor, m_doc, entry->m_image);
}
if (!cached || !m_mathjaxBlocks[i].inplacePreviewReady()) {
if (!cached || !m_mathjaxBlocks.last().inplacePreviewReady()) {
manualUpdate = false;
processForInplacePreview(i);
processForInplacePreview(m_mathjaxBlocks.size() - 1);
}
}
@ -222,14 +193,12 @@ void VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady(int p_identitifer,
}
MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_id];
mb.setImageDataBa(p_format, p_data);
mb.updateInplacePreview(m_editor, m_doc);
// Update the cache.
QSharedPointer<MathjaxImageCacheEntry> entry(new MathjaxImageCacheEntry(p_timeStamp,
p_data,
p_format));
m_cache.insert(mb.mathjaxBlock().m_text, entry);
mb.updateInplacePreview(m_editor, m_doc, entry->m_image);
updateInplacePreview();
}

View File

@ -19,17 +19,9 @@ public:
explicit MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb);
void clearImageData()
{
m_imgDataBa.clear();
m_inplacePreview.clear();
}
void updateNonContent(const QTextDocument *p_doc,
const VEditor *p_editor,
const VMathjaxBlock &p_mb);
void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc);
void updateInplacePreview(const VEditor *p_editor,
const QTextDocument *p_doc,
const QPixmap &p_image);
VMathjaxBlock &mathjaxBlock()
{
@ -41,29 +33,11 @@ public:
return m_mathjaxBlock;
}
void setMathjaxBlock(const VMathjaxBlock &p_mb)
{
m_mathjaxBlock = p_mb;
clearImageData();
}
bool inplacePreviewReady() const
{
return !m_inplacePreview.isNull();
}
void setImageDataBa(const QString &p_format, const QByteArray &p_data)
{
m_imgFormat = p_format;
m_imgDataBa = p_data;
m_inplacePreview.clear();
}
bool hasImageDataBa() const
{
return !m_imgDataBa.isEmpty();
}
const QSharedPointer<VImageToPreview> inplacePreview() const
{
return m_inplacePreview;
@ -78,10 +52,6 @@ private:
VMathjaxBlock m_mathjaxBlock;
QByteArray m_imgDataBa;
QString m_imgFormat;
QSharedPointer<VImageToPreview> m_inplacePreview;
};
@ -124,15 +94,15 @@ private:
MathjaxImageCacheEntry(TimeStamp p_ts,
const QByteArray &p_dataBa,
const QString &p_format)
: m_ts(p_ts),
m_imgDataBa(p_dataBa),
m_imgFormat(p_format)
: m_ts(p_ts)
{
if (!p_dataBa.isEmpty()) {
m_image.loadFromData(p_dataBa, p_format.toLocal8Bit().data());
}
}
TimeStamp m_ts;
QByteArray m_imgDataBa;
QString m_imgFormat;
QPixmap m_image;
};