support in place preview and live preview of code blocks

This commit is contained in:
Le Tan 2018-04-09 18:28:18 +08:00
parent 0b9cc6e5b3
commit cfcc7e5494
19 changed files with 594 additions and 104 deletions

View File

@ -146,6 +146,8 @@ public:
void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear); void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
void addPossiblePreviewBlock(int p_blockNumber);
// Parse and only update the highlight results for rehighlight(). // Parse and only update the highlight results for rehighlight().
void updateHighlightFast(); void updateHighlightFast();
@ -329,6 +331,11 @@ inline void HGMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int>
} }
} }
inline void HGMarkdownHighlighter::addPossiblePreviewBlock(int p_blockNumber)
{
m_possiblePreviewBlocks.insert(p_blockNumber);
}
inline VTextBlockData *HGMarkdownHighlighter::currentBlockData() const inline VTextBlockData *HGMarkdownHighlighter::currentBlockData() const
{ {
return static_cast<VTextBlockData *>(currentBlockUserData()); return static_cast<VTextBlockData *>(currentBlockUserData());

View File

@ -43,7 +43,7 @@ var mdit = window.markdownit({
typographer: false, typographer: false,
langPrefix: 'lang-', langPrefix: 'lang-',
highlight: function(str, lang) { highlight: function(str, lang) {
if (lang && !specialCodeBlock(lang)) { if (lang && (!specialCodeBlock(lang) || highlightSpecialBlocks)) {
if (hljs.getLanguage(lang)) { if (hljs.getLanguage(lang)) {
return hljs.highlight(lang, str, true).value; return hljs.highlight(lang, str, true).value;
} else { } else {
@ -134,7 +134,9 @@ var updateText = function(text) {
}; };
var highlightText = function(text, id, timeStamp) { var highlightText = function(text, id, timeStamp) {
highlightSpecialBlocks = true;
var html = mdit.render(text); var html = mdit.render(text);
highlightSpecialBlocks = false;
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
}; };

View File

@ -34,6 +34,8 @@
<div id="preview-div" style="display:none;"></div> <div id="preview-div" style="display:none;"></div>
<div id="inplace-preview-div" style="display:none;"></div>
<div id="text-html-div" style="display:none;"></div> <div id="text-html-div" style="display:none;"></div>
</body> </body>
</html> </html>

View File

@ -4,6 +4,8 @@ var contentDiv = document.getElementById('content-div');
var previewDiv = document.getElementById('preview-div'); var previewDiv = document.getElementById('preview-div');
var inplacePreviewDiv = document.getElementById('inplace-preview-div');
var textHtmlDiv = document.getElementById('text-html-div'); var textHtmlDiv = document.getElementById('text-html-div');
var content; var content;
@ -85,6 +87,9 @@ if (typeof VAddTOC == 'undefined') {
VAddTOC = false; VAddTOC = false;
} }
// Whether highlight special blocks like puml, flowchart.
var highlightSpecialBlocks = false;
var getUrlScheme = function(url) { var getUrlScheme = function(url) {
var idx = url.indexOf(':'); var idx = url.indexOf(':');
if (idx > -1) { if (idx > -1) {
@ -1204,6 +1209,7 @@ var initStylesToInline = function() {
}; };
// Embed the CSS styles of @ele and all its children. // Embed the CSS styles of @ele and all its children.
// StylesToInline need to be init before.
var embedInlineStyles = function(ele) { var embedInlineStyles = function(ele) {
var tagName = ele.tagName.toLowerCase(); var tagName = ele.tagName.toLowerCase();
var props = StylesToInline.get(tagName); var props = StylesToInline.get(tagName);
@ -1373,7 +1379,7 @@ var setPreviewEnabled = function(enabled) {
}; };
var previewCodeBlock = function(id, lang, text, isLivePreview) { var previewCodeBlock = function(id, lang, text, isLivePreview) {
var div = previewDiv; var div = isLivePreview ? previewDiv : inplacePreviewDiv;
div.innerHTML = ''; div.innerHTML = '';
div.className = ''; div.className = '';
@ -1381,7 +1387,7 @@ var previewCodeBlock = function(id, lang, text, isLivePreview) {
|| (lang != 'flow' || (lang != 'flow'
&& lang != 'flowchart' && lang != 'flowchart'
&& lang != 'mermaid' && lang != 'mermaid'
&& (lang != 'puml' || VPlantUMLMode != 1))) { && (lang != 'puml' || VPlantUMLMode != 1 || !isLivePreview))) {
return; return;
} }
@ -1399,6 +1405,16 @@ var previewCodeBlock = function(id, lang, text, isLivePreview) {
} else if (lang == 'puml') { } else if (lang == 'puml') {
renderPlantUMLOneOnline(code); renderPlantUMLOneOnline(code);
} }
if (!isLivePreview) {
var children = div.children;
if (children.length > 0) {
content.previewCodeBlockCB(id, lang, children[0].innerHTML);
}
div.innerHTML = '';
div.className = '';
}
}; };
var setPreviewContent = function(lang, html) { var setPreviewContent = function(lang, html) {

View File

@ -16,7 +16,7 @@ renderer.heading = function(text, level) {
// Highlight.js to highlight code block // Highlight.js to highlight code block
marked.setOptions({ marked.setOptions({
highlight: function(code, lang) { highlight: function(code, lang) {
if (lang && !specialCodeBlock(lang)) { if (lang && (!specialCodeBlock(lang) || highlightSpecialBlocks)) {
if (hljs.getLanguage(lang)) { if (hljs.getLanguage(lang)) {
return hljs.highlight(lang, code, true).value; return hljs.highlight(lang, code, true).value;
} else { } else {
@ -78,7 +78,9 @@ var updateText = function(text) {
}; };
var highlightText = function(text, id, timeStamp) { var highlightText = function(text, id, timeStamp) {
highlightSpecialBlocks = true;
var html = marked(text); var html = marked(text);
highlightSpecialBlocks = false;
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
} }

View File

@ -128,7 +128,7 @@ var highlightText = function(text, id, timeStamp) {
var parser = new DOMParser(); var parser = new DOMParser();
var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html'); var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html');
highlightCodeBlocks(htmlDoc, false, false, false); highlightCodeBlocks(htmlDoc, false, false, false, false, false);
html = htmlDoc.getElementById('showdown-container').innerHTML; html = htmlDoc.getElementById('showdown-container').innerHTML;
@ -142,7 +142,7 @@ var textToHtml = function(text) {
var parser = new DOMParser(); var parser = new DOMParser();
var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html'); var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html');
highlightCodeBlocks(htmlDoc, false, false, false); highlightCodeBlocks(htmlDoc, false, false, false, false, false);
html = htmlDoc.getElementById('showdown-container').innerHTML; html = htmlDoc.getElementById('showdown-container').innerHTML;

View File

@ -17,6 +17,13 @@ public:
VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter, VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
VDocument *p_vdoc, MarkdownConverterType p_type); VDocument *p_vdoc, MarkdownConverterType p_type);
// @p_text: text of fenced code block.
// Get the indent level of the first line (fence) and unindent the whole block
// to make the fence at the highest indent level.
// This operation is to make sure JS could handle the code block correctly
// without any context.
static QString unindentCodeBlock(const QString &p_text);
private slots: private slots:
void handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks); void handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
@ -47,13 +54,6 @@ private:
const QString &p_text, int &p_index, const QString &p_text, int &p_index,
QVector<HLUnitPos> &p_units); QVector<HLUnitPos> &p_units);
// @p_text: text of fenced code block.
// Get the indent level of the first line (fence) and unindent the whole block
// to make the fence at the highest indent level.
// This operation is to make sure JS could handle the code block correctly
// without any context.
QString unindentCodeBlock(const QString &p_text);
void updateHighlightResults(int p_startPos, QVector<HLUnitPos> p_units); void updateHighlightResults(int p_startPos, QVector<HLUnitPos> p_units);
void addToHighlightCache(const QString &p_text, void addToHighlightCache(const QString &p_text,

View File

@ -178,3 +178,8 @@ void VDocument::setPreviewContent(const QString &p_lang, const QString &p_html)
{ {
emit requestSetPreviewContent(p_lang, p_html); emit requestSetPreviewContent(p_lang, p_html);
} }
void VDocument::previewCodeBlockCB(int p_id, const QString &p_lang, const QString &p_html)
{
emit codeBlockPreviewReady(p_id, p_lang, p_html);
}

View File

@ -104,6 +104,8 @@ public slots:
// Web-side call this to process Graphviz locally. // Web-side call this to process Graphviz locally.
void processGraphviz(int p_id, const QString &p_format, const QString &p_text); void processGraphviz(int p_id, const QString &p_format, const QString &p_text);
void previewCodeBlockCB(int p_id, const QString &p_lang, const QString &p_html);
signals: signals:
void textChanged(const QString &text); void textChanged(const QString &text);
@ -153,6 +155,8 @@ signals:
void requestSetPreviewContent(const QString &p_lang, const QString &p_html); void requestSetPreviewContent(const QString &p_lang, const QString &p_html);
void codeBlockPreviewReady(int p_id, const QString &p_lang, const QString &p_html);
private: private:
QString m_toc; QString m_toc;
QString m_header; QString m_header;

View File

@ -159,6 +159,8 @@ public:
virtual QTextDocument *documentW() const = 0; virtual QTextDocument *documentW() const = 0;
virtual int tabStopWidthW() const = 0;
virtual void setTabStopWidthW(int p_width) = 0; virtual void setTabStopWidthW(int p_width) = 0;
virtual QTextCursor textCursorW() const = 0; virtual QTextCursor textCursorW() const = 0;

View File

@ -7,6 +7,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vgraphvizhelper.h" #include "vgraphvizhelper.h"
#include "vplantumlhelper.h" #include "vplantumlhelper.h"
#include "vcodeblockhighlighthelper.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -22,24 +23,88 @@ extern VConfigManager *g_config;
#define INDEX_MASK 0x00ffffffUL #define INDEX_MASK 0x00ffffffUL
CodeBlockPreviewInfo::CodeBlockPreviewInfo()
{
}
CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb)
: m_codeBlock(p_cb)
{
}
void CodeBlockPreviewInfo::clearImageData()
{
m_imgData.clear();
m_inplacePreview.clear();
}
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;
} else {
m_inplacePreview->clear();
}
}
// Update inplace preview according to m_imgData.
void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
const QTextDocument *p_doc)
{
QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock);
if (block.isValid()) {
if (m_inplacePreview.isNull()) {
m_inplacePreview.reset(new VImageToPreview());
}
// m_image will be generated when signaling out.
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;
m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block,
p_editor->tabStopWidthW());
m_inplacePreview->m_name = QString::number(getImageIndex());
m_inplacePreview->m_isBlock = true;
} else {
m_inplacePreview->clear();
}
}
VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor, VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
VDocument *p_document, VDocument *p_document,
QObject *p_parent) QObject *p_parent)
: QObject(p_parent), : QObject(p_parent),
m_editor(p_editor), m_editor(p_editor),
m_document(p_document), m_document(p_document),
m_doc(p_editor->documentW()),
m_cbIndex(-1), m_cbIndex(-1),
m_livePreviewEnabled(false), m_livePreviewEnabled(false),
m_inplacePreviewEnabled(false),
m_graphvizHelper(NULL), m_graphvizHelper(NULL),
m_plantUMLHelper(NULL) m_plantUMLHelper(NULL)
{ {
connect(m_editor->object(), &VEditorObject::cursorPositionChanged, connect(m_editor->object(), &VEditorObject::cursorPositionChanged,
this, &VLivePreviewHelper::handleCursorPositionChanged); this, &VLivePreviewHelper::handleCursorPositionChanged);
connect(m_document, &VDocument::codeBlockPreviewReady,
this, &VLivePreviewHelper::webAsyncResultReady);
m_flowchartEnabled = g_config->getEnableFlowchart(); m_flowchartEnabled = g_config->getEnableFlowchart();
m_mermaidEnabled = g_config->getEnableMermaid(); m_mermaidEnabled = g_config->getEnableMermaid();
m_plantUMLMode = g_config->getPlantUMLMode(); m_plantUMLMode = g_config->getPlantUMLMode();
m_graphvizEnabled = g_config->getEnableGraphviz(); m_graphvizEnabled = g_config->getEnableGraphviz();
m_mathjaxEnabled = g_config->getEnableMathjax();
} }
bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const
@ -47,12 +112,13 @@ bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const
return (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart")) return (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart"))
|| (m_mermaidEnabled && p_lang == "mermaid") || (m_mermaidEnabled && p_lang == "mermaid")
|| (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml") || (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml")
|| (m_graphvizEnabled && p_lang == "dot"); || (m_graphvizEnabled && p_lang == "dot")
|| (m_mathjaxEnabled && p_lang == "mathjax");
} }
void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks) void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks)
{ {
if (!m_livePreviewEnabled) { if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) {
return; return;
} }
@ -61,29 +127,32 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
int cursorBlock = m_editor->textCursorW().block().blockNumber(); int cursorBlock = m_editor->textCursorW().block().blockNumber();
int idx = 0; int idx = 0;
bool needUpdate = true; bool needUpdate = true;
int nrCached = 0; for (auto const & vcb : p_codeBlocks) {
for (auto const & cb : p_codeBlocks) { if (!isPreviewLang(vcb.m_lang)) {
if (!isPreviewLang(cb.m_lang)) {
continue; continue;
} }
bool cached = false; bool cached = false;
if (idx < m_codeBlocks.size()) { if (idx < m_codeBlocks.size()) {
CodeBlock &vcb = m_codeBlocks[idx]; CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
if (vcb.m_codeBlock.equalContent(cb)) { if (cb.codeBlock().equalContent(vcb)) {
vcb.m_codeBlock.updateNonContent(cb); cb.updateNonContent(m_doc, vcb);
cached = true; cached = true;
++nrCached;
} else { } else {
vcb.m_codeBlock = cb; cb.setCodeBlock(vcb);
vcb.m_cachedResult.clear();
} }
} else { } else {
m_codeBlocks.append(CodeBlock()); m_codeBlocks.append(CodeBlockPreviewInfo(vcb));
m_codeBlocks[idx].m_codeBlock = cb;
} }
if (cb.m_startBlock <= cursorBlock && cb.m_endBlock >= cursorBlock) { if (m_inplacePreviewEnabled
&& !m_codeBlocks[idx].inplacePreviewReady()) {
processForInplacePreview(idx);
}
if (m_livePreviewEnabled
&& vcb.m_startBlock <= cursorBlock
&& vcb.m_endBlock >= cursorBlock) {
if (lastIndex == idx && cached) { if (lastIndex == idx && cached) {
needUpdate = false; needUpdate = false;
} }
@ -96,9 +165,7 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
m_codeBlocks.resize(idx); m_codeBlocks.resize(idx);
qDebug() << "VLivePreviewHelper cache" << nrCached << "code blocks of" << m_codeBlocks.size(); if (m_livePreviewEnabled && needUpdate) {
if (needUpdate) {
updateLivePreview(); updateLivePreview();
} }
} }
@ -115,11 +182,11 @@ void VLivePreviewHelper::handleCursorPositionChanged()
int mid = left; int mid = left;
while (left <= right) { while (left <= right) {
mid = (left + right) / 2; mid = (left + right) / 2;
const CodeBlock &cb = m_codeBlocks[mid]; const CodeBlockPreviewInfo &cb = m_codeBlocks[mid];
const VCodeBlock &vcb = cb.codeBlock();
if (cb.m_codeBlock.m_startBlock <= cursorBlock && cb.m_codeBlock.m_endBlock >= cursorBlock) { if (vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) {
break; break;
} else if (cb.m_codeBlock.m_startBlock > cursorBlock) { } else if (vcb.m_startBlock > cursorBlock) {
right = mid - 1; right = mid - 1;
} else { } else {
left = mid + 1; left = mid + 1;
@ -136,9 +203,10 @@ void VLivePreviewHelper::handleCursorPositionChanged()
static QString removeFence(const QString &p_text) static QString removeFence(const QString &p_text)
{ {
Q_ASSERT(p_text.startsWith("```") && p_text.endsWith("```")); QString text = VCodeBlockHighlightHelper::unindentCodeBlock(p_text);
int idx = p_text.indexOf('\n') + 1; Q_ASSERT(text.startsWith("```") && text.endsWith("```"));
return p_text.mid(idx, p_text.size() - idx - 3); int idx = text.indexOf('\n') + 1;
return text.mid(idx, text.size() - idx - 3);
} }
void VLivePreviewHelper::updateLivePreview() void VLivePreviewHelper::updateLivePreview()
@ -148,43 +216,41 @@ void VLivePreviewHelper::updateLivePreview()
} }
Q_ASSERT(!(m_cbIndex & ~INDEX_MASK)); Q_ASSERT(!(m_cbIndex & ~INDEX_MASK));
const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex];
const CodeBlock &cb = m_codeBlocks[m_cbIndex]; const VCodeBlock &vcb = cb.codeBlock();
QString text = removeFence(cb.m_codeBlock.m_text); if (vcb.m_lang == "dot") {
qDebug() << "updateLivePreview" << m_cbIndex << cb.m_codeBlock.m_lang;
if (cb.m_codeBlock.m_lang == "dot") {
if (!m_graphvizHelper) { if (!m_graphvizHelper) {
m_graphvizHelper = new VGraphvizHelper(this); m_graphvizHelper = new VGraphvizHelper(this);
connect(m_graphvizHelper, &VGraphvizHelper::resultReady, connect(m_graphvizHelper, &VGraphvizHelper::resultReady,
this, &VLivePreviewHelper::localAsyncResultReady); this, &VLivePreviewHelper::localAsyncResultReady);
} }
if (cb.m_cachedResult.isEmpty()) { if (!cb.hasImageData()) {
m_graphvizHelper->processAsync(m_cbIndex | LANG_PREFIX_GRAPHVIZ | TYPE_LIVE_PREVIEW, m_graphvizHelper->processAsync(m_cbIndex | LANG_PREFIX_GRAPHVIZ | TYPE_LIVE_PREVIEW,
"svg", "svg",
text); removeFence(vcb.m_text));
} else { } else {
qDebug() << "use cached preview result of code block" << m_cbIndex; m_document->setPreviewContent(vcb.m_lang, cb.imageData());
m_document->setPreviewContent(cb.m_codeBlock.m_lang, cb.m_cachedResult);
} }
} else if (cb.m_codeBlock.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) { } else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
if (!m_plantUMLHelper) { if (!m_plantUMLHelper) {
m_plantUMLHelper = new VPlantUMLHelper(this); m_plantUMLHelper = new VPlantUMLHelper(this);
connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady, connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
this, &VLivePreviewHelper::localAsyncResultReady); this, &VLivePreviewHelper::localAsyncResultReady);
} }
if (cb.m_cachedResult.isEmpty()) { if (!cb.hasImageData()) {
m_plantUMLHelper->processAsync(m_cbIndex | LANG_PREFIX_PLANTUML | TYPE_LIVE_PREVIEW, m_plantUMLHelper->processAsync(m_cbIndex | LANG_PREFIX_PLANTUML | TYPE_LIVE_PREVIEW,
"svg", "svg",
text); removeFence(vcb.m_text));
} else { } else {
qDebug() << "use cached preview result of code block" << m_cbIndex; m_document->setPreviewContent(vcb.m_lang, cb.imageData());
m_document->setPreviewContent(cb.m_codeBlock.m_lang, cb.m_cachedResult);
} }
} else { } else if (vcb.m_lang != "puml") {
m_document->previewCodeBlock(m_cbIndex, cb.m_codeBlock.m_lang, text, true); m_document->previewCodeBlock(m_cbIndex,
vcb.m_lang,
removeFence(vcb.m_text),
true);
} }
} }
@ -202,6 +268,20 @@ void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled)
} }
} }
void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled)
{
if (m_inplacePreviewEnabled == p_enabled) {
return;
}
m_inplacePreviewEnabled = p_enabled;
if (!m_livePreviewEnabled) {
for (auto & cb : m_codeBlocks) {
cb.clearImageData();
}
}
}
void VLivePreviewHelper::localAsyncResultReady(int p_id, void VLivePreviewHelper::localAsyncResultReady(int p_id,
const QString &p_format, const QString &p_format,
const QString &p_result) const QString &p_result)
@ -211,6 +291,7 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
int idx = p_id & INDEX_MASK; int idx = p_id & INDEX_MASK;
bool livePreview = (p_id & TYPE_MASK) == TYPE_LIVE_PREVIEW; bool livePreview = (p_id & TYPE_MASK) == TYPE_LIVE_PREVIEW;
QString lang; QString lang;
switch (p_id & LANG_PREFIX_MASK) { switch (p_id & LANG_PREFIX_MASK) {
case LANG_PREFIX_PLANTUML: case LANG_PREFIX_PLANTUML:
lang = "puml"; lang = "puml";
@ -224,12 +305,105 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
return; return;
} }
if (idx >= m_codeBlocks.size()) {
return;
}
CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
cb.setImageData(p_format, p_result);
if (livePreview) { if (livePreview) {
if (idx != m_cbIndex) { if (idx != m_cbIndex) {
return; return;
} }
m_codeBlocks[idx].m_cachedResult = p_result;
m_document->setPreviewContent(lang, p_result); m_document->setPreviewContent(lang, p_result);
} else {
// Inplace preview.
cb.updateInplacePreview(m_editor, m_doc);
updateInplacePreview();
} }
} }
void VLivePreviewHelper::processForInplacePreview(int p_idx)
{
CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx];
const VCodeBlock &vcb = cb.codeBlock();
if (vcb.m_lang == "dot") {
if (!m_graphvizHelper) {
m_graphvizHelper = new VGraphvizHelper(this);
connect(m_graphvizHelper, &VGraphvizHelper::resultReady,
this, &VLivePreviewHelper::localAsyncResultReady);
}
if (cb.hasImageData()) {
cb.updateInplacePreview(m_editor, m_doc);
updateInplacePreview();
} else {
m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW,
"svg",
removeFence(vcb.m_text));
}
} else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
if (!m_plantUMLHelper) {
m_plantUMLHelper = new VPlantUMLHelper(this);
connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
this, &VLivePreviewHelper::localAsyncResultReady);
}
if (cb.hasImageData()) {
cb.updateInplacePreview(m_editor, m_doc);
updateInplacePreview();
} else {
m_plantUMLHelper->processAsync(p_idx | LANG_PREFIX_PLANTUML | TYPE_INPLACE_PREVIEW,
"svg",
removeFence(vcb.m_text));
}
} else if (vcb.m_lang == "flow"
|| vcb.m_lang == "flowchart") {
m_document->previewCodeBlock(p_idx,
vcb.m_lang,
removeFence(vcb.m_text),
false);
}
}
void VLivePreviewHelper::updateInplacePreview()
{
QVector<QSharedPointer<VImageToPreview> > images;
for (int i = 0; i < m_codeBlocks.size(); ++i) {
CodeBlockPreviewInfo &cb = m_codeBlocks[i];
if (cb.inplacePreviewReady() && cb.hasImageData()) {
Q_ASSERT(!cb.inplacePreview().isNull());
// Generate the image.
cb.inplacePreview()->m_image.loadFromData(cb.imageData().toUtf8(),
cb.imageFormat().toLocal8Bit().data());
images.append(cb.inplacePreview());
}
}
emit inplacePreviewCodeBlockUpdated(images);
// Clear image.
for (int i = 0; i < m_codeBlocks.size(); ++i) {
CodeBlockPreviewInfo &cb = m_codeBlocks[i];
if (cb.inplacePreviewReady() && cb.hasImageData()) {
cb.inplacePreview()->m_image = QPixmap();
}
}
}
void VLivePreviewHelper::webAsyncResultReady(int p_id,
const QString &p_lang,
const QString &p_result)
{
Q_UNUSED(p_lang);
if (p_id >= m_codeBlocks.size() || p_result.isEmpty()) {
return;
}
CodeBlockPreviewInfo &cb = m_codeBlocks[p_id];
cb.setImageData(QStringLiteral("svg"), p_result);
cb.updateInplacePreview(m_editor, m_doc);
updateInplacePreview();
}

View File

@ -2,14 +2,95 @@
#define VLIVEPREVIEWHELPER_H #define VLIVEPREVIEWHELPER_H
#include <QObject> #include <QObject>
#include <QTextDocument>
#include "hgmarkdownhighlighter.h" #include "hgmarkdownhighlighter.h"
#include "vpreviewmanager.h"
class VEditor; class VEditor;
class VDocument; class VDocument;
class VGraphvizHelper; class VGraphvizHelper;
class VPlantUMLHelper; class VPlantUMLHelper;
class CodeBlockPreviewInfo
{
public:
CodeBlockPreviewInfo();
explicit CodeBlockPreviewInfo(const VCodeBlock &p_cb);
void clearImageData();
void updateNonContent(const QTextDocument *p_doc, const VCodeBlock &p_cb);
void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc);
VCodeBlock &codeBlock()
{
return m_codeBlock;
}
const VCodeBlock &codeBlock() const
{
return m_codeBlock;
}
void setCodeBlock(const VCodeBlock &p_cb)
{
m_codeBlock = p_cb;
clearImageData();
}
bool inplacePreviewReady() const
{
return !m_inplacePreview.isNull();
}
bool hasImageData() const
{
return !m_imgData.isEmpty();
}
const QString &imageData() const
{
return m_imgData;
}
const QString &imageFormat() const
{
return m_imgFormat;
}
void setImageData(const QString &p_format, const QString &p_data)
{
m_imgFormat = p_format;
m_imgData = p_data;
}
const QSharedPointer<VImageToPreview> inplacePreview() const
{
return m_inplacePreview;
}
private:
static int getImageIndex()
{
static int index = 0;
return ++index;
}
VCodeBlock m_codeBlock;
QString m_imgData;
QString m_imgFormat;
QSharedPointer<VImageToPreview> m_inplacePreview;
};
// Manage live preview and inplace preview.
class VLivePreviewHelper : public QObject class VLivePreviewHelper : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -22,30 +103,42 @@ public:
void setLivePreviewEnabled(bool p_enabled); void setLivePreviewEnabled(bool p_enabled);
void setInplacePreviewEnabled(bool p_enabled);
bool isPreviewEnabled() const;
public slots: public slots:
void updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks); void updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks);
void webAsyncResultReady(int p_id, const QString &p_lang, const QString &p_result);
signals:
void inplacePreviewCodeBlockUpdated(const QVector<QSharedPointer<VImageToPreview> > &p_images);
private slots: private slots:
void handleCursorPositionChanged(); void handleCursorPositionChanged();
void localAsyncResultReady(int p_id, const QString &p_format, const QString &p_result); void localAsyncResultReady(int p_id, const QString &p_format, const QString &p_result);
private: private:
bool isPreviewLang(const QString &p_lang) const; bool isPreviewLang(const QString &p_lang) const;
struct CodeBlock // Get image data for this code block for inplace preview.
{ void processForInplacePreview(int p_idx);
VCodeBlock m_codeBlock;
QString m_cachedResult; // Emit signal to update inplace preview.
}; void updateInplacePreview();
// Sorted by m_startBlock in ascending order. // Sorted by m_startBlock in ascending order.
QVector<CodeBlock> m_codeBlocks; QVector<CodeBlockPreviewInfo> m_codeBlocks;
VEditor *m_editor; VEditor *m_editor;
VDocument *m_document; VDocument *m_document;
QTextDocument *m_doc;
// Current previewed code block index in m_codeBlocks. // Current previewed code block index in m_codeBlocks.
int m_cbIndex; int m_cbIndex;
@ -53,11 +146,18 @@ private:
bool m_mermaidEnabled; bool m_mermaidEnabled;
int m_plantUMLMode; int m_plantUMLMode;
bool m_graphvizEnabled; bool m_graphvizEnabled;
bool m_mathjaxEnabled;
bool m_livePreviewEnabled; bool m_livePreviewEnabled;
bool m_inplacePreviewEnabled;
VGraphvizHelper *m_graphvizHelper; VGraphvizHelper *m_graphvizHelper;
VPlantUMLHelper *m_plantUMLHelper; VPlantUMLHelper *m_plantUMLHelper;
}; };
inline bool VLivePreviewHelper::isPreviewEnabled() const
{
return m_inplacePreviewEnabled || m_livePreviewEnabled;
}
#endif // VLIVEPREVIEWHELPER_H #endif // VLIVEPREVIEWHELPER_H

View File

@ -85,7 +85,7 @@ VMdEditor::VMdEditor(VFile *p_file,
m_previewMgr = new VPreviewManager(this, m_mdHighlighter); m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated, connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
m_previewMgr, &VPreviewManager::imageLinksUpdated); m_previewMgr, &VPreviewManager::updateImageLinks);
connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks, connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight); m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);

View File

@ -74,6 +74,8 @@ public:
HGMarkdownHighlighter *getMarkdownHighlighter() const; HGMarkdownHighlighter *getMarkdownHighlighter() const;
VPreviewManager *getPreviewManager() const;
public slots: public slots:
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE; bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
@ -91,6 +93,11 @@ public:
return document(); return document();
} }
int tabStopWidthW() const Q_DECL_OVERRIDE
{
return tabStopWidth();
}
void setTabStopWidthW(int p_width) Q_DECL_OVERRIDE void setTabStopWidthW(int p_width) Q_DECL_OVERRIDE
{ {
setTabStopWidth(p_width); setTabStopWidth(p_width);
@ -273,4 +280,9 @@ inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
{ {
return m_mdHighlighter; return m_mdHighlighter;
} }
inline VPreviewManager *VMdEditor::getPreviewManager() const
{
return m_previewMgr;
}
#endif // VMDEDITOR_H #endif // VMDEDITOR_H

View File

@ -515,6 +515,15 @@ void VMdTab::setupMarkdownEditor()
enableHeadingSequence(m_enableHeadingSequence); enableHeadingSequence(m_enableHeadingSequence);
m_editor->reloadFile(); m_editor->reloadFile();
m_splitter->insertWidget(0, m_editor); m_splitter->insertWidget(0, m_editor);
m_livePreviewHelper = new VLivePreviewHelper(m_editor, m_document, this);
connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::codeBlocksUpdated,
m_livePreviewHelper, &VLivePreviewHelper::updateCodeBlocks);
connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
m_livePreviewHelper, &VLivePreviewHelper::setInplacePreviewEnabled);
connect(m_livePreviewHelper, &VLivePreviewHelper::inplacePreviewCodeBlockUpdated,
m_editor->getPreviewManager(), &VPreviewManager::updateCodeBlocks);
m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
} }
void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml) void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
@ -1019,6 +1028,13 @@ void VMdTab::tabIsReady(TabReady p_mode)
} }
}); });
} }
if (m_editor
&& p_mode == TabReady::ReadMode
&& m_livePreviewHelper->isPreviewEnabled()) {
// Need to re-preview.
m_editor->getMarkdownHighlighter()->updateHighlight();
}
} }
void VMdTab::writeBackupFile() void VMdTab::writeBackupFile()
@ -1375,11 +1391,6 @@ void VMdTab::setCurrentMode(Mode p_mode)
newSizes.append(a); newSizes.append(a);
newSizes.append(b); newSizes.append(b);
m_splitter->setSizes(newSizes); m_splitter->setSizes(newSizes);
Q_ASSERT(!m_livePreviewHelper);
m_livePreviewHelper = new VLivePreviewHelper(m_editor, m_document, this);
connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::codeBlocksUpdated,
m_livePreviewHelper, &VLivePreviewHelper::updateCodeBlocks);
} else if (factor != m_previewWebViewState->m_zoomFactor) { } else if (factor != m_previewWebViewState->m_zoomFactor) {
m_webViewer->setZoomFactor(m_previewWebViewState->m_zoomFactor); m_webViewer->setZoomFactor(m_previewWebViewState->m_zoomFactor);
} }

View File

@ -5,6 +5,8 @@
#include <QDir> #include <QDir>
#include <QUrl> #include <QUrl>
#include <QVector> #include <QVector>
#include <QTextLayout>
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vdownloader.h" #include "vdownloader.h"
@ -17,24 +19,25 @@ VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_h
m_editor(p_editor), m_editor(p_editor),
m_document(p_editor->document()), m_document(p_editor->document()),
m_highlighter(p_highlighter), m_highlighter(p_highlighter),
m_previewEnabled(false), m_previewEnabled(false)
m_timeStamp(0)
{ {
for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
m_timeStamps[i] = 0;
}
m_downloader = new VDownloader(this); m_downloader = new VDownloader(this);
connect(m_downloader, &VDownloader::downloadFinished, connect(m_downloader, &VDownloader::downloadFinished,
this, &VPreviewManager::imageDownloaded); this, &VPreviewManager::imageDownloaded);
} }
void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions) void VPreviewManager::updateImageLinks(const QVector<VElementRegion> &p_imageRegions)
{ {
if (!m_previewEnabled) { if (!m_previewEnabled) {
return; return;
} }
TS ts = ++m_timeStamp; TS ts = ++timeStamp(PreviewSource::ImageLink);
m_imageRegions = p_imageRegions; previewImages(ts, p_imageRegions);
previewImages(ts);
} }
void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url) void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
@ -70,6 +73,8 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
if (m_previewEnabled != p_enabled) { if (m_previewEnabled != p_enabled) {
m_previewEnabled = p_enabled; m_previewEnabled = p_enabled;
emit previewEnabledChanged(p_enabled);
if (!m_previewEnabled) { if (!m_previewEnabled) {
clearPreview(); clearPreview();
} else { } else {
@ -80,21 +85,17 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
void VPreviewManager::clearPreview() void VPreviewManager::clearPreview()
{ {
m_imageRegions.clear();
long long ts = ++m_timeStamp;
for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) { for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
TS ts = ++timeStamp(static_cast<PreviewSource>(i));
clearBlockObsoletePreviewInfo(ts, static_cast<PreviewSource>(i)); clearBlockObsoletePreviewInfo(ts, static_cast<PreviewSource>(i));
clearObsoleteImages(ts, static_cast<PreviewSource>(i)); clearObsoleteImages(ts, static_cast<PreviewSource>(i));
} }
} }
void VPreviewManager::previewImages(TS p_timeStamp) void VPreviewManager::previewImages(TS p_timeStamp, const QVector<VElementRegion> &p_imageRegions)
{ {
QVector<ImageLinkInfo> imageLinks; QVector<ImageLinkInfo> imageLinks;
fetchImageLinksFromRegions(imageLinks); fetchImageLinksFromRegions(p_imageRegions, imageLinks);
updateBlockPreviewInfo(p_timeStamp, imageLinks); updateBlockPreviewInfo(p_timeStamp, imageLinks);
@ -116,20 +117,21 @@ static bool isAllSpaces(const QString &p_text, int p_start, int p_end)
return true; return true;
} }
void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks) void VPreviewManager::fetchImageLinksFromRegions(QVector<VElementRegion> p_imageRegions,
QVector<ImageLinkInfo> &p_imageLinks)
{ {
p_imageLinks.clear(); p_imageLinks.clear();
if (m_imageRegions.isEmpty()) { if (p_imageRegions.isEmpty()) {
return; return;
} }
p_imageLinks.reserve(m_imageRegions.size()); p_imageLinks.reserve(p_imageRegions.size());
QTextDocument *doc = m_editor->document(); QTextDocument *doc = m_editor->document();
for (int i = 0; i < m_imageRegions.size(); ++i) { for (int i = 0; i < p_imageRegions.size(); ++i) {
VElementRegion &reg = m_imageRegions[i]; VElementRegion &reg = p_imageRegions[i];
QTextBlock block = doc->findBlock(reg.m_startPos); QTextBlock block = doc->findBlock(reg.m_startPos);
if (!block.isValid()) { if (!block.isValid()) {
continue; continue;
@ -143,7 +145,7 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_image
reg.m_endPos, reg.m_endPos,
blockStart, blockStart,
block.blockNumber(), block.blockNumber(),
calculateBlockMargin(block)); calculateBlockMargin(block, m_editor->tabStopWidthW()));
if ((reg.m_startPos == blockStart if ((reg.m_startPos == blockStart
|| isAllSpaces(text, 0, reg.m_startPos - blockStart)) || isAllSpaces(text, 0, reg.m_startPos - blockStart))
&& (reg.m_endPos == blockEnd && (reg.m_endPos == blockEnd
@ -256,7 +258,23 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
return name; return name;
} }
int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block) QString VPreviewManager::imageResourceNameFromCodeBlock(const QSharedPointer<VImageToPreview> &p_image)
{
QString name = "CODE_BLOCK_" + p_image->m_name;
if (m_editor->containsImage(name)) {
return name;
}
// Add it to the resource.
if (p_image->m_image.isNull()) {
return QString();
}
m_editor->addImage(name, p_image->m_image);
return name;
}
int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block, int p_tabStopWidth)
{ {
static QHash<QString, int> spaceWidthOfFonts; static QHash<QString, int> spaceWidthOfFonts;
@ -272,7 +290,7 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
} else if (text[i] == ' ') { } else if (text[i] == ' ') {
++nrSpaces; ++nrSpaces;
} else if (text[i] == '\t') { } else if (text[i] == '\t') {
nrSpaces += m_editor->tabStopWidth(); nrSpaces += p_tabStopWidth;
} }
} }
@ -281,7 +299,14 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
} }
int spaceWidth = 0; int spaceWidth = 0;
QFont font = p_block.charFormat().font(); QFont font;
QVector<QTextLayout::FormatRange> fmts = p_block.layout()->formats();
if (fmts.isEmpty()) {
font = p_block.charFormat().font();
} else {
font = fmts.first().format.font();
}
QString fontName = font.toString(); QString fontName = font.toString();
auto it = spaceWidthOfFonts.find(fontName); auto it = spaceWidthOfFonts.find(fontName);
if (it != spaceWidthOfFonts.end()) { if (it != spaceWidthOfFonts.end()) {
@ -327,11 +352,57 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
<< imageCache(PreviewSource::ImageLink).size() << imageCache(PreviewSource::ImageLink).size()
<< blockData->toString(); << blockData->toString();
} }
// TODO: may need to call m_editor->update()?
}
void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
PreviewSource p_source,
const QVector<QSharedPointer<VImageToPreview> > &p_images)
{
QSet<int> affectedBlocks;
for (auto const & img : p_images) {
if (img.isNull()) {
continue;
}
QTextBlock block = m_document->findBlockByNumber(img->m_blockNumber);
if (!block.isValid()) {
continue;
}
QString name = imageResourceNameFromCodeBlock(img);
if (name.isEmpty()) {
continue;
}
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
Q_ASSERT(blockData);
VPreviewInfo *info = new VPreviewInfo(p_source,
p_timeStamp,
img->m_startPos - img->m_blockPos,
img->m_endPos - img->m_blockPos,
img->m_padding,
!img->m_isBlock,
name,
m_editor->imageSize(name));
bool tsUpdated = blockData->insertPreviewInfo(info);
imageCache(p_source).insert(name, p_timeStamp);
if (!tsUpdated) {
// No need to relayout the block if only timestamp is updated.
affectedBlocks.insert(img->m_blockNumber);
m_highlighter->addPossiblePreviewBlock(img->m_blockNumber);
}
}
// Relayout these blocks since they may not have been changed.
m_editor->relayout(affectedBlocks);
m_editor->update();
} }
void VPreviewManager::clearObsoleteImages(long long p_timeStamp, PreviewSource p_source) void VPreviewManager::clearObsoleteImages(long long p_timeStamp, PreviewSource p_source)
{ {
auto cache = imageCache(p_source); QHash<QString, long long> &cache = imageCache(p_source);
for (auto it = cache.begin(); it != cache.end();) { for (auto it = cache.begin(); it != cache.end();) {
if (it.value() < p_timeStamp) { if (it.value() < p_timeStamp) {
@ -348,7 +419,7 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
{ {
QSet<int> affectedBlocks; QSet<int> affectedBlocks;
QVector<int> obsoleteBlocks; QVector<int> obsoleteBlocks;
auto blocks = m_highlighter->getPossiblePreviewBlocks(); const QSet<int> &blocks = m_highlighter->getPossiblePreviewBlocks();
qDebug() << "possible preview blocks" << blocks; qDebug() << "possible preview blocks" << blocks;
for (auto i : blocks) { for (auto i : blocks) {
QTextBlock block = m_document->findBlockByNumber(i); QTextBlock block = m_document->findBlockByNumber(i);
@ -384,5 +455,21 @@ void VPreviewManager::refreshPreview()
clearPreview(); clearPreview();
// No need to request updating code blocks since this will also update them.
requestUpdateImageLinks(); requestUpdateImageLinks();
} }
void VPreviewManager::updateCodeBlocks(const QVector<QSharedPointer<VImageToPreview> > &p_images)
{
if (!m_previewEnabled) {
return;
}
TS ts = ++timeStamp(PreviewSource::CodeBlock);
updateBlockPreviewInfo(ts, PreviewSource::CodeBlock, p_images);
clearBlockObsoletePreviewInfo(ts, PreviewSource::CodeBlock);
clearObsoleteImages(ts, PreviewSource::CodeBlock);
}

View File

@ -6,6 +6,8 @@
#include <QTextBlock> #include <QTextBlock>
#include <QHash> #include <QHash>
#include <QVector> #include <QVector>
#include <QSharedPointer>
#include "hgmarkdownhighlighter.h" #include "hgmarkdownhighlighter.h"
#include "vmdeditor.h" #include "vmdeditor.h"
#include "vtextblockdata.h" #include "vtextblockdata.h"
@ -14,7 +16,41 @@ class VDownloader;
typedef long long TS; typedef long long TS;
// Info about image to preview.
struct VImageToPreview
{
void clear()
{
m_startPos = m_endPos = m_blockPos = m_blockNumber = -1;
m_padding = 0;
m_image = QPixmap();
m_name.clear();
m_isBlock = true;
};
int m_startPos;
int m_endPos;
// Position of this block.
int m_blockPos;
int m_blockNumber;
// Left padding of this block in pixels.
int m_padding;
QPixmap m_image;
// If @m_name are the same, then they are the same imges.
QString m_name;
// Whether it is an image block.
bool m_isBlock;
};
// Manage inplace preview.
class VPreviewManager : public QObject class VPreviewManager : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -29,14 +65,23 @@ public:
// Refresh all the preview. // Refresh all the preview.
void refreshPreview(); void refreshPreview();
bool isPreviewEnabled() const;
// Calculate the block margin (prefix spaces) in pixels.
static int calculateBlockMargin(const QTextBlock &p_block, int p_tabStopWidth);
public slots: public slots:
// Image links were updated from the highlighter. // Image links were updated from the highlighter.
void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions); void updateImageLinks(const QVector<VElementRegion> &p_imageRegions);
void updateCodeBlocks(const QVector<QSharedPointer<VImageToPreview> > &p_images);
signals: signals:
// Request highlighter to update image links. // Request highlighter to update image links.
void requestUpdateImageLinks(); void requestUpdateImageLinks();
void previewEnabledChanged(bool p_enabled);
private slots: private slots:
// Non-local image downloaded for preview. // Non-local image downloaded for preview.
void imageDownloaded(const QByteArray &p_data, const QString &p_url); void imageDownloaded(const QByteArray &p_data, const QString &p_url);
@ -92,11 +137,12 @@ private:
}; };
// Start to preview images according to image links. // Start to preview images according to image links.
void previewImages(TS p_timeStamp); void previewImages(TS p_timeStamp, const QVector<VElementRegion> &p_imageRegions);
// According to m_imageRegions, fetch the image link Url. // According to p_imageRegions, fetch the image link Url.
// @p_imageRegions: output. // @p_imageRegions: output.
void fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks); void fetchImageLinksFromRegions(QVector<VElementRegion> p_imageRegions,
QVector<ImageLinkInfo> &p_imageLinks);
// Fetch the image link's URL if there is only one link. // Fetch the image link's URL if there is only one link.
QString fetchImageUrlToPreview(const QString &p_text); QString fetchImageUrlToPreview(const QString &p_text);
@ -108,13 +154,17 @@ private:
// Update the preview info of related blocks according to @p_imageLinks. // Update the preview info of related blocks according to @p_imageLinks.
void updateBlockPreviewInfo(TS p_timeStamp, const QVector<ImageLinkInfo> &p_imageLinks); void updateBlockPreviewInfo(TS p_timeStamp, const QVector<ImageLinkInfo> &p_imageLinks);
// Update the preview info of related blocks according to @p_images.
void updateBlockPreviewInfo(TS p_timeStamp,
PreviewSource p_source,
const QVector<QSharedPointer<VImageToPreview> > &p_images);
// Get the name of the image in the resource manager. // Get the name of the image in the resource manager.
// Will add the image to the resource manager if not exists. // Will add the image to the resource manager if not exists.
// Returns empty if fail to add the image to the resource manager. // Returns empty if fail to add the image to the resource manager.
QString imageResourceName(const ImageLinkInfo &p_link); QString imageResourceName(const ImageLinkInfo &p_link);
// Calculate the block margin (prefix spaces) in pixels. QString imageResourceNameFromCodeBlock(const QSharedPointer<VImageToPreview> &p_image);
int calculateBlockMargin(const QTextBlock &p_block);
QHash<QString, long long> &imageCache(PreviewSource p_source); QHash<QString, long long> &imageCache(PreviewSource p_source);
@ -122,6 +172,8 @@ private:
void clearBlockObsoletePreviewInfo(long long p_timeStamp, PreviewSource p_source); void clearBlockObsoletePreviewInfo(long long p_timeStamp, PreviewSource p_source);
TS &timeStamp(PreviewSource p_source);
VMdEditor *m_editor; VMdEditor *m_editor;
QTextDocument *m_document; QTextDocument *m_document;
@ -133,14 +185,12 @@ private:
// Whether preview is enabled. // Whether preview is enabled.
bool m_previewEnabled; bool m_previewEnabled;
// Regions of all the image links.
QVector<VElementRegion> m_imageRegions;
// Map from URL to name in the resource manager. // Map from URL to name in the resource manager.
// Used for downloading images. // Used for downloading images.
QHash<QString, QString> m_urlToName; QHash<QString, QString> m_urlToName;
TS m_timeStamp; // Timestamp per each preview source.
TS m_timeStamps[(int)PreviewSource::MaxNumberOfSources];
// Used to discard obsolete images. One per each preview source. // Used to discard obsolete images. One per each preview source.
QHash<QString, long long> m_imageCaches[(int)PreviewSource::MaxNumberOfSources]; QHash<QString, long long> m_imageCaches[(int)PreviewSource::MaxNumberOfSources];
@ -150,4 +200,14 @@ inline QHash<QString, long long> &VPreviewManager::imageCache(PreviewSource p_so
{ {
return m_imageCaches[(int)p_source]; return m_imageCaches[(int)p_source];
} }
inline TS &VPreviewManager::timeStamp(PreviewSource p_source)
{
return m_timeStamps[(int)p_source];
}
inline bool VPreviewManager::isPreviewEnabled() const
{
return m_previewEnabled;
}
#endif // VPREVIEWMANAGER_H #endif // VPREVIEWMANAGER_H

View File

@ -17,8 +17,9 @@ VTextBlockData::~VTextBlockData()
m_previews.clear(); m_previews.clear();
} }
void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info) bool VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
{ {
bool tsUpdated = false;
bool inserted = false; bool inserted = false;
for (auto it = m_previews.begin(); it != m_previews.end();) { for (auto it = m_previews.begin(); it != m_previews.end();) {
VPreviewInfo *ele = *it; VPreviewInfo *ele = *it;
@ -33,6 +34,7 @@ void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
delete ele; delete ele;
*it = p_info; *it = p_info;
inserted = true; inserted = true;
tsUpdated = true;
qDebug() << "update eixsting image's timestamp" << p_info->m_imageInfo.toString(); qDebug() << "update eixsting image's timestamp" << p_info->m_imageInfo.toString();
break; break;
} else if (p_info->m_imageInfo.intersect(ele->m_imageInfo)) { } else if (p_info->m_imageInfo.intersect(ele->m_imageInfo)) {
@ -53,6 +55,8 @@ void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
} }
Q_ASSERT(checkOrder()); Q_ASSERT(checkOrder());
return tsUpdated;
} }
QString VTextBlockData::toString() const QString VTextBlockData::toString() const

View File

@ -8,6 +8,7 @@
enum class PreviewSource enum class PreviewSource
{ {
ImageLink = 0, ImageLink = 0,
CodeBlock,
MaxNumberOfSources MaxNumberOfSources
}; };
@ -137,7 +138,8 @@ public:
~VTextBlockData(); ~VTextBlockData();
// Insert @p_info into m_previews, preserving the order. // Insert @p_info into m_previews, preserving the order.
void insertPreviewInfo(VPreviewInfo *p_info); // Returns true if only timestamp is updated.
bool insertPreviewInfo(VPreviewInfo *p_info);
// For degub only. // For degub only.
QString toString() const; QString toString() const;