mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support in place preview and live preview of code blocks
This commit is contained in:
parent
0b9cc6e5b3
commit
cfcc7e5494
@ -146,6 +146,8 @@ public:
|
||||
|
||||
void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
|
||||
|
||||
void addPossiblePreviewBlock(int p_blockNumber);
|
||||
|
||||
// Parse and only update the highlight results for rehighlight().
|
||||
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
|
||||
{
|
||||
return static_cast<VTextBlockData *>(currentBlockUserData());
|
||||
|
@ -43,7 +43,7 @@ var mdit = window.markdownit({
|
||||
typographer: false,
|
||||
langPrefix: 'lang-',
|
||||
highlight: function(str, lang) {
|
||||
if (lang && !specialCodeBlock(lang)) {
|
||||
if (lang && (!specialCodeBlock(lang) || highlightSpecialBlocks)) {
|
||||
if (hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(lang, str, true).value;
|
||||
} else {
|
||||
@ -134,7 +134,9 @@ var updateText = function(text) {
|
||||
};
|
||||
|
||||
var highlightText = function(text, id, timeStamp) {
|
||||
highlightSpecialBlocks = true;
|
||||
var html = mdit.render(text);
|
||||
highlightSpecialBlocks = false;
|
||||
content.highlightTextCB(html, id, timeStamp);
|
||||
};
|
||||
|
||||
|
@ -34,6 +34,8 @@
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,6 +4,8 @@ var contentDiv = document.getElementById('content-div');
|
||||
|
||||
var previewDiv = document.getElementById('preview-div');
|
||||
|
||||
var inplacePreviewDiv = document.getElementById('inplace-preview-div');
|
||||
|
||||
var textHtmlDiv = document.getElementById('text-html-div');
|
||||
|
||||
var content;
|
||||
@ -85,6 +87,9 @@ if (typeof VAddTOC == 'undefined') {
|
||||
VAddTOC = false;
|
||||
}
|
||||
|
||||
// Whether highlight special blocks like puml, flowchart.
|
||||
var highlightSpecialBlocks = false;
|
||||
|
||||
var getUrlScheme = function(url) {
|
||||
var idx = url.indexOf(':');
|
||||
if (idx > -1) {
|
||||
@ -1204,6 +1209,7 @@ var initStylesToInline = function() {
|
||||
};
|
||||
|
||||
// Embed the CSS styles of @ele and all its children.
|
||||
// StylesToInline need to be init before.
|
||||
var embedInlineStyles = function(ele) {
|
||||
var tagName = ele.tagName.toLowerCase();
|
||||
var props = StylesToInline.get(tagName);
|
||||
@ -1373,7 +1379,7 @@ var setPreviewEnabled = function(enabled) {
|
||||
};
|
||||
|
||||
var previewCodeBlock = function(id, lang, text, isLivePreview) {
|
||||
var div = previewDiv;
|
||||
var div = isLivePreview ? previewDiv : inplacePreviewDiv;
|
||||
div.innerHTML = '';
|
||||
div.className = '';
|
||||
|
||||
@ -1381,7 +1387,7 @@ var previewCodeBlock = function(id, lang, text, isLivePreview) {
|
||||
|| (lang != 'flow'
|
||||
&& lang != 'flowchart'
|
||||
&& lang != 'mermaid'
|
||||
&& (lang != 'puml' || VPlantUMLMode != 1))) {
|
||||
&& (lang != 'puml' || VPlantUMLMode != 1 || !isLivePreview))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1399,6 +1405,16 @@ var previewCodeBlock = function(id, lang, text, isLivePreview) {
|
||||
} else if (lang == 'puml') {
|
||||
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) {
|
||||
|
@ -16,7 +16,7 @@ renderer.heading = function(text, level) {
|
||||
// Highlight.js to highlight code block
|
||||
marked.setOptions({
|
||||
highlight: function(code, lang) {
|
||||
if (lang && !specialCodeBlock(lang)) {
|
||||
if (lang && (!specialCodeBlock(lang) || highlightSpecialBlocks)) {
|
||||
if (hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(lang, code, true).value;
|
||||
} else {
|
||||
@ -78,7 +78,9 @@ var updateText = function(text) {
|
||||
};
|
||||
|
||||
var highlightText = function(text, id, timeStamp) {
|
||||
highlightSpecialBlocks = true;
|
||||
var html = marked(text);
|
||||
highlightSpecialBlocks = false;
|
||||
content.highlightTextCB(html, id, timeStamp);
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ var highlightText = function(text, id, timeStamp) {
|
||||
|
||||
var parser = new DOMParser();
|
||||
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;
|
||||
|
||||
@ -142,7 +142,7 @@ var textToHtml = function(text) {
|
||||
|
||||
var parser = new DOMParser();
|
||||
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;
|
||||
|
||||
|
@ -17,6 +17,13 @@ public:
|
||||
VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
|
||||
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:
|
||||
void handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
@ -47,13 +54,6 @@ private:
|
||||
const QString &p_text, int &p_index,
|
||||
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 addToHighlightCache(const QString &p_text,
|
||||
|
@ -178,3 +178,8 @@ void VDocument::setPreviewContent(const QString &p_lang, const QString &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);
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ public slots:
|
||||
// Web-side call this to process Graphviz locally.
|
||||
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:
|
||||
void textChanged(const QString &text);
|
||||
|
||||
@ -153,6 +155,8 @@ signals:
|
||||
|
||||
void requestSetPreviewContent(const QString &p_lang, const QString &p_html);
|
||||
|
||||
void codeBlockPreviewReady(int p_id, const QString &p_lang, const QString &p_html);
|
||||
|
||||
private:
|
||||
QString m_toc;
|
||||
QString m_header;
|
||||
|
@ -159,6 +159,8 @@ public:
|
||||
|
||||
virtual QTextDocument *documentW() const = 0;
|
||||
|
||||
virtual int tabStopWidthW() const = 0;
|
||||
|
||||
virtual void setTabStopWidthW(int p_width) = 0;
|
||||
|
||||
virtual QTextCursor textCursorW() const = 0;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "vgraphvizhelper.h"
|
||||
#include "vplantumlhelper.h"
|
||||
#include "vcodeblockhighlighthelper.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -22,24 +23,88 @@ extern VConfigManager *g_config;
|
||||
|
||||
#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,
|
||||
VDocument *p_document,
|
||||
QObject *p_parent)
|
||||
: QObject(p_parent),
|
||||
m_editor(p_editor),
|
||||
m_document(p_document),
|
||||
m_doc(p_editor->documentW()),
|
||||
m_cbIndex(-1),
|
||||
m_livePreviewEnabled(false),
|
||||
m_inplacePreviewEnabled(false),
|
||||
m_graphvizHelper(NULL),
|
||||
m_plantUMLHelper(NULL)
|
||||
{
|
||||
connect(m_editor->object(), &VEditorObject::cursorPositionChanged,
|
||||
this, &VLivePreviewHelper::handleCursorPositionChanged);
|
||||
connect(m_document, &VDocument::codeBlockPreviewReady,
|
||||
this, &VLivePreviewHelper::webAsyncResultReady);
|
||||
|
||||
m_flowchartEnabled = g_config->getEnableFlowchart();
|
||||
m_mermaidEnabled = g_config->getEnableMermaid();
|
||||
m_plantUMLMode = g_config->getPlantUMLMode();
|
||||
m_graphvizEnabled = g_config->getEnableGraphviz();
|
||||
m_mathjaxEnabled = g_config->getEnableMathjax();
|
||||
}
|
||||
|
||||
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"))
|
||||
|| (m_mermaidEnabled && p_lang == "mermaid")
|
||||
|| (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)
|
||||
{
|
||||
if (!m_livePreviewEnabled) {
|
||||
if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -61,29 +127,32 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
|
||||
int cursorBlock = m_editor->textCursorW().block().blockNumber();
|
||||
int idx = 0;
|
||||
bool needUpdate = true;
|
||||
int nrCached = 0;
|
||||
for (auto const & cb : p_codeBlocks) {
|
||||
if (!isPreviewLang(cb.m_lang)) {
|
||||
for (auto const & vcb : p_codeBlocks) {
|
||||
if (!isPreviewLang(vcb.m_lang)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool cached = false;
|
||||
if (idx < m_codeBlocks.size()) {
|
||||
CodeBlock &vcb = m_codeBlocks[idx];
|
||||
if (vcb.m_codeBlock.equalContent(cb)) {
|
||||
vcb.m_codeBlock.updateNonContent(cb);
|
||||
CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
|
||||
if (cb.codeBlock().equalContent(vcb)) {
|
||||
cb.updateNonContent(m_doc, vcb);
|
||||
cached = true;
|
||||
++nrCached;
|
||||
} else {
|
||||
vcb.m_codeBlock = cb;
|
||||
vcb.m_cachedResult.clear();
|
||||
cb.setCodeBlock(vcb);
|
||||
}
|
||||
} else {
|
||||
m_codeBlocks.append(CodeBlock());
|
||||
m_codeBlocks[idx].m_codeBlock = cb;
|
||||
m_codeBlocks.append(CodeBlockPreviewInfo(vcb));
|
||||
}
|
||||
|
||||
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) {
|
||||
needUpdate = false;
|
||||
}
|
||||
@ -96,9 +165,7 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
|
||||
|
||||
m_codeBlocks.resize(idx);
|
||||
|
||||
qDebug() << "VLivePreviewHelper cache" << nrCached << "code blocks of" << m_codeBlocks.size();
|
||||
|
||||
if (needUpdate) {
|
||||
if (m_livePreviewEnabled && needUpdate) {
|
||||
updateLivePreview();
|
||||
}
|
||||
}
|
||||
@ -115,11 +182,11 @@ void VLivePreviewHelper::handleCursorPositionChanged()
|
||||
int mid = left;
|
||||
while (left <= right) {
|
||||
mid = (left + right) / 2;
|
||||
const CodeBlock &cb = m_codeBlocks[mid];
|
||||
|
||||
if (cb.m_codeBlock.m_startBlock <= cursorBlock && cb.m_codeBlock.m_endBlock >= cursorBlock) {
|
||||
const CodeBlockPreviewInfo &cb = m_codeBlocks[mid];
|
||||
const VCodeBlock &vcb = cb.codeBlock();
|
||||
if (vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) {
|
||||
break;
|
||||
} else if (cb.m_codeBlock.m_startBlock > cursorBlock) {
|
||||
} else if (vcb.m_startBlock > cursorBlock) {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
@ -136,9 +203,10 @@ void VLivePreviewHelper::handleCursorPositionChanged()
|
||||
|
||||
static QString removeFence(const QString &p_text)
|
||||
{
|
||||
Q_ASSERT(p_text.startsWith("```") && p_text.endsWith("```"));
|
||||
int idx = p_text.indexOf('\n') + 1;
|
||||
return p_text.mid(idx, p_text.size() - idx - 3);
|
||||
QString text = VCodeBlockHighlightHelper::unindentCodeBlock(p_text);
|
||||
Q_ASSERT(text.startsWith("```") && text.endsWith("```"));
|
||||
int idx = text.indexOf('\n') + 1;
|
||||
return text.mid(idx, text.size() - idx - 3);
|
||||
}
|
||||
|
||||
void VLivePreviewHelper::updateLivePreview()
|
||||
@ -148,43 +216,41 @@ void VLivePreviewHelper::updateLivePreview()
|
||||
}
|
||||
|
||||
Q_ASSERT(!(m_cbIndex & ~INDEX_MASK));
|
||||
|
||||
const CodeBlock &cb = m_codeBlocks[m_cbIndex];
|
||||
QString text = removeFence(cb.m_codeBlock.m_text);
|
||||
qDebug() << "updateLivePreview" << m_cbIndex << cb.m_codeBlock.m_lang;
|
||||
|
||||
if (cb.m_codeBlock.m_lang == "dot") {
|
||||
const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex];
|
||||
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.m_cachedResult.isEmpty()) {
|
||||
if (!cb.hasImageData()) {
|
||||
m_graphvizHelper->processAsync(m_cbIndex | LANG_PREFIX_GRAPHVIZ | TYPE_LIVE_PREVIEW,
|
||||
"svg",
|
||||
text);
|
||||
removeFence(vcb.m_text));
|
||||
} else {
|
||||
qDebug() << "use cached preview result of code block" << m_cbIndex;
|
||||
m_document->setPreviewContent(cb.m_codeBlock.m_lang, cb.m_cachedResult);
|
||||
m_document->setPreviewContent(vcb.m_lang, cb.imageData());
|
||||
}
|
||||
} 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) {
|
||||
m_plantUMLHelper = new VPlantUMLHelper(this);
|
||||
connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
|
||||
this, &VLivePreviewHelper::localAsyncResultReady);
|
||||
}
|
||||
|
||||
if (cb.m_cachedResult.isEmpty()) {
|
||||
if (!cb.hasImageData()) {
|
||||
m_plantUMLHelper->processAsync(m_cbIndex | LANG_PREFIX_PLANTUML | TYPE_LIVE_PREVIEW,
|
||||
"svg",
|
||||
text);
|
||||
removeFence(vcb.m_text));
|
||||
} else {
|
||||
qDebug() << "use cached preview result of code block" << m_cbIndex;
|
||||
m_document->setPreviewContent(cb.m_codeBlock.m_lang, cb.m_cachedResult);
|
||||
m_document->setPreviewContent(vcb.m_lang, cb.imageData());
|
||||
}
|
||||
} else {
|
||||
m_document->previewCodeBlock(m_cbIndex, cb.m_codeBlock.m_lang, text, true);
|
||||
} else if (vcb.m_lang != "puml") {
|
||||
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,
|
||||
const QString &p_format,
|
||||
const QString &p_result)
|
||||
@ -211,6 +291,7 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
|
||||
int idx = p_id & INDEX_MASK;
|
||||
bool livePreview = (p_id & TYPE_MASK) == TYPE_LIVE_PREVIEW;
|
||||
QString lang;
|
||||
|
||||
switch (p_id & LANG_PREFIX_MASK) {
|
||||
case LANG_PREFIX_PLANTUML:
|
||||
lang = "puml";
|
||||
@ -224,12 +305,105 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id,
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx >= m_codeBlocks.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
|
||||
cb.setImageData(p_format, p_result);
|
||||
if (livePreview) {
|
||||
if (idx != m_cbIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_codeBlocks[idx].m_cachedResult = 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();
|
||||
}
|
||||
|
@ -2,14 +2,95 @@
|
||||
#define VLIVEPREVIEWHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vpreviewmanager.h"
|
||||
|
||||
class VEditor;
|
||||
class VDocument;
|
||||
class VGraphvizHelper;
|
||||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -22,30 +103,42 @@ public:
|
||||
|
||||
void setLivePreviewEnabled(bool p_enabled);
|
||||
|
||||
void setInplacePreviewEnabled(bool p_enabled);
|
||||
|
||||
bool isPreviewEnabled() const;
|
||||
|
||||
public slots:
|
||||
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:
|
||||
void handleCursorPositionChanged();
|
||||
|
||||
void localAsyncResultReady(int p_id, const QString &p_format, const QString &p_result);
|
||||
|
||||
private:
|
||||
|
||||
bool isPreviewLang(const QString &p_lang) const;
|
||||
|
||||
struct CodeBlock
|
||||
{
|
||||
VCodeBlock m_codeBlock;
|
||||
QString m_cachedResult;
|
||||
};
|
||||
// Get image data for this code block for inplace preview.
|
||||
void processForInplacePreview(int p_idx);
|
||||
|
||||
// Emit signal to update inplace preview.
|
||||
void updateInplacePreview();
|
||||
|
||||
// Sorted by m_startBlock in ascending order.
|
||||
QVector<CodeBlock> m_codeBlocks;
|
||||
QVector<CodeBlockPreviewInfo> m_codeBlocks;
|
||||
|
||||
VEditor *m_editor;
|
||||
|
||||
VDocument *m_document;
|
||||
|
||||
QTextDocument *m_doc;
|
||||
|
||||
// Current previewed code block index in m_codeBlocks.
|
||||
int m_cbIndex;
|
||||
|
||||
@ -53,11 +146,18 @@ private:
|
||||
bool m_mermaidEnabled;
|
||||
int m_plantUMLMode;
|
||||
bool m_graphvizEnabled;
|
||||
bool m_mathjaxEnabled;
|
||||
|
||||
bool m_livePreviewEnabled;
|
||||
|
||||
bool m_inplacePreviewEnabled;
|
||||
|
||||
VGraphvizHelper *m_graphvizHelper;
|
||||
VPlantUMLHelper *m_plantUMLHelper;
|
||||
};
|
||||
|
||||
inline bool VLivePreviewHelper::isPreviewEnabled() const
|
||||
{
|
||||
return m_inplacePreviewEnabled || m_livePreviewEnabled;
|
||||
}
|
||||
#endif // VLIVEPREVIEWHELPER_H
|
||||
|
@ -85,7 +85,7 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
|
||||
m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
|
||||
m_previewMgr, &VPreviewManager::imageLinksUpdated);
|
||||
m_previewMgr, &VPreviewManager::updateImageLinks);
|
||||
connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
|
||||
m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);
|
||||
|
||||
|
@ -74,6 +74,8 @@ public:
|
||||
|
||||
HGMarkdownHighlighter *getMarkdownHighlighter() const;
|
||||
|
||||
VPreviewManager *getPreviewManager() const;
|
||||
|
||||
public slots:
|
||||
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
|
||||
|
||||
@ -91,6 +93,11 @@ public:
|
||||
return document();
|
||||
}
|
||||
|
||||
int tabStopWidthW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return tabStopWidth();
|
||||
}
|
||||
|
||||
void setTabStopWidthW(int p_width) Q_DECL_OVERRIDE
|
||||
{
|
||||
setTabStopWidth(p_width);
|
||||
@ -273,4 +280,9 @@ inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
|
||||
{
|
||||
return m_mdHighlighter;
|
||||
}
|
||||
|
||||
inline VPreviewManager *VMdEditor::getPreviewManager() const
|
||||
{
|
||||
return m_previewMgr;
|
||||
}
|
||||
#endif // VMDEDITOR_H
|
||||
|
@ -515,6 +515,15 @@ void VMdTab::setupMarkdownEditor()
|
||||
enableHeadingSequence(m_enableHeadingSequence);
|
||||
m_editor->reloadFile();
|
||||
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)
|
||||
@ -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()
|
||||
@ -1375,11 +1391,6 @@ void VMdTab::setCurrentMode(Mode p_mode)
|
||||
newSizes.append(a);
|
||||
newSizes.append(b);
|
||||
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) {
|
||||
m_webViewer->setZoomFactor(m_previewWebViewState->m_zoomFactor);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include <QTextLayout>
|
||||
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vdownloader.h"
|
||||
@ -17,24 +19,25 @@ VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_h
|
||||
m_editor(p_editor),
|
||||
m_document(p_editor->document()),
|
||||
m_highlighter(p_highlighter),
|
||||
m_previewEnabled(false),
|
||||
m_timeStamp(0)
|
||||
m_previewEnabled(false)
|
||||
{
|
||||
for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
|
||||
m_timeStamps[i] = 0;
|
||||
}
|
||||
|
||||
m_downloader = new VDownloader(this);
|
||||
connect(m_downloader, &VDownloader::downloadFinished,
|
||||
this, &VPreviewManager::imageDownloaded);
|
||||
}
|
||||
|
||||
void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions)
|
||||
void VPreviewManager::updateImageLinks(const QVector<VElementRegion> &p_imageRegions)
|
||||
{
|
||||
if (!m_previewEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
TS ts = ++m_timeStamp;
|
||||
m_imageRegions = p_imageRegions;
|
||||
|
||||
previewImages(ts);
|
||||
TS ts = ++timeStamp(PreviewSource::ImageLink);
|
||||
previewImages(ts, p_imageRegions);
|
||||
}
|
||||
|
||||
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) {
|
||||
m_previewEnabled = p_enabled;
|
||||
|
||||
emit previewEnabledChanged(p_enabled);
|
||||
|
||||
if (!m_previewEnabled) {
|
||||
clearPreview();
|
||||
} else {
|
||||
@ -80,21 +85,17 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
|
||||
|
||||
void VPreviewManager::clearPreview()
|
||||
{
|
||||
m_imageRegions.clear();
|
||||
|
||||
long long ts = ++m_timeStamp;
|
||||
|
||||
for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
|
||||
TS ts = ++timeStamp(static_cast<PreviewSource>(i));
|
||||
clearBlockObsoletePreviewInfo(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;
|
||||
fetchImageLinksFromRegions(imageLinks);
|
||||
fetchImageLinksFromRegions(p_imageRegions, imageLinks);
|
||||
|
||||
updateBlockPreviewInfo(p_timeStamp, imageLinks);
|
||||
|
||||
@ -116,20 +117,21 @@ static bool isAllSpaces(const QString &p_text, int p_start, int p_end)
|
||||
return true;
|
||||
}
|
||||
|
||||
void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks)
|
||||
void VPreviewManager::fetchImageLinksFromRegions(QVector<VElementRegion> p_imageRegions,
|
||||
QVector<ImageLinkInfo> &p_imageLinks)
|
||||
{
|
||||
p_imageLinks.clear();
|
||||
|
||||
if (m_imageRegions.isEmpty()) {
|
||||
if (p_imageRegions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
p_imageLinks.reserve(m_imageRegions.size());
|
||||
p_imageLinks.reserve(p_imageRegions.size());
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
|
||||
for (int i = 0; i < m_imageRegions.size(); ++i) {
|
||||
VElementRegion ® = m_imageRegions[i];
|
||||
for (int i = 0; i < p_imageRegions.size(); ++i) {
|
||||
VElementRegion ® = p_imageRegions[i];
|
||||
QTextBlock block = doc->findBlock(reg.m_startPos);
|
||||
if (!block.isValid()) {
|
||||
continue;
|
||||
@ -143,7 +145,7 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_image
|
||||
reg.m_endPos,
|
||||
blockStart,
|
||||
block.blockNumber(),
|
||||
calculateBlockMargin(block));
|
||||
calculateBlockMargin(block, m_editor->tabStopWidthW()));
|
||||
if ((reg.m_startPos == blockStart
|
||||
|| isAllSpaces(text, 0, reg.m_startPos - blockStart))
|
||||
&& (reg.m_endPos == blockEnd
|
||||
@ -256,7 +258,23 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
|
||||
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;
|
||||
|
||||
@ -272,7 +290,7 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
|
||||
} else if (text[i] == ' ') {
|
||||
++nrSpaces;
|
||||
} 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;
|
||||
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();
|
||||
auto it = spaceWidthOfFonts.find(fontName);
|
||||
if (it != spaceWidthOfFonts.end()) {
|
||||
@ -327,11 +352,57 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
|
||||
<< imageCache(PreviewSource::ImageLink).size()
|
||||
<< 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)
|
||||
{
|
||||
auto cache = imageCache(p_source);
|
||||
QHash<QString, long long> &cache = imageCache(p_source);
|
||||
|
||||
for (auto it = cache.begin(); it != cache.end();) {
|
||||
if (it.value() < p_timeStamp) {
|
||||
@ -348,7 +419,7 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
|
||||
{
|
||||
QSet<int> affectedBlocks;
|
||||
QVector<int> obsoleteBlocks;
|
||||
auto blocks = m_highlighter->getPossiblePreviewBlocks();
|
||||
const QSet<int> &blocks = m_highlighter->getPossiblePreviewBlocks();
|
||||
qDebug() << "possible preview blocks" << blocks;
|
||||
for (auto i : blocks) {
|
||||
QTextBlock block = m_document->findBlockByNumber(i);
|
||||
@ -384,5 +455,21 @@ void VPreviewManager::refreshPreview()
|
||||
|
||||
clearPreview();
|
||||
|
||||
// No need to request updating code blocks since this will also update them.
|
||||
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);
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <QTextBlock>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vtextblockdata.h"
|
||||
@ -14,7 +16,41 @@ class VDownloader;
|
||||
|
||||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -29,14 +65,23 @@ public:
|
||||
// Refresh all the preview.
|
||||
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:
|
||||
// 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:
|
||||
// Request highlighter to update image links.
|
||||
void requestUpdateImageLinks();
|
||||
|
||||
void previewEnabledChanged(bool p_enabled);
|
||||
|
||||
private slots:
|
||||
// Non-local image downloaded for preview.
|
||||
void imageDownloaded(const QByteArray &p_data, const QString &p_url);
|
||||
@ -92,11 +137,12 @@ private:
|
||||
};
|
||||
|
||||
// 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.
|
||||
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.
|
||||
QString fetchImageUrlToPreview(const QString &p_text);
|
||||
@ -108,13 +154,17 @@ private:
|
||||
// Update the preview info of related blocks according to @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.
|
||||
// Will add the image to the resource manager if not exists.
|
||||
// Returns empty if fail to add the image to the resource manager.
|
||||
QString imageResourceName(const ImageLinkInfo &p_link);
|
||||
|
||||
// Calculate the block margin (prefix spaces) in pixels.
|
||||
int calculateBlockMargin(const QTextBlock &p_block);
|
||||
QString imageResourceNameFromCodeBlock(const QSharedPointer<VImageToPreview> &p_image);
|
||||
|
||||
QHash<QString, long long> &imageCache(PreviewSource p_source);
|
||||
|
||||
@ -122,6 +172,8 @@ private:
|
||||
|
||||
void clearBlockObsoletePreviewInfo(long long p_timeStamp, PreviewSource p_source);
|
||||
|
||||
TS &timeStamp(PreviewSource p_source);
|
||||
|
||||
VMdEditor *m_editor;
|
||||
|
||||
QTextDocument *m_document;
|
||||
@ -133,14 +185,12 @@ private:
|
||||
// Whether preview is enabled.
|
||||
bool m_previewEnabled;
|
||||
|
||||
// Regions of all the image links.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// Map from URL to name in the resource manager.
|
||||
// Used for downloading images.
|
||||
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.
|
||||
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];
|
||||
}
|
||||
|
||||
inline TS &VPreviewManager::timeStamp(PreviewSource p_source)
|
||||
{
|
||||
return m_timeStamps[(int)p_source];
|
||||
}
|
||||
|
||||
inline bool VPreviewManager::isPreviewEnabled() const
|
||||
{
|
||||
return m_previewEnabled;
|
||||
}
|
||||
#endif // VPREVIEWMANAGER_H
|
||||
|
@ -17,8 +17,9 @@ VTextBlockData::~VTextBlockData()
|
||||
m_previews.clear();
|
||||
}
|
||||
|
||||
void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
|
||||
bool VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
|
||||
{
|
||||
bool tsUpdated = false;
|
||||
bool inserted = false;
|
||||
for (auto it = m_previews.begin(); it != m_previews.end();) {
|
||||
VPreviewInfo *ele = *it;
|
||||
@ -33,6 +34,7 @@ void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
|
||||
delete ele;
|
||||
*it = p_info;
|
||||
inserted = true;
|
||||
tsUpdated = true;
|
||||
qDebug() << "update eixsting image's timestamp" << p_info->m_imageInfo.toString();
|
||||
break;
|
||||
} else if (p_info->m_imageInfo.intersect(ele->m_imageInfo)) {
|
||||
@ -53,6 +55,8 @@ void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
|
||||
}
|
||||
|
||||
Q_ASSERT(checkOrder());
|
||||
|
||||
return tsUpdated;
|
||||
}
|
||||
|
||||
QString VTextBlockData::toString() const
|
||||
|
@ -8,6 +8,7 @@
|
||||
enum class PreviewSource
|
||||
{
|
||||
ImageLink = 0,
|
||||
CodeBlock,
|
||||
MaxNumberOfSources
|
||||
};
|
||||
|
||||
@ -137,7 +138,8 @@ public:
|
||||
~VTextBlockData();
|
||||
|
||||
// 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.
|
||||
QString toString() const;
|
||||
|
Loading…
x
Reference in New Issue
Block a user