From aa5960f97447bdd527727067c96cbba4f66a335e Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 17 Apr 2018 20:17:23 +0800 Subject: [PATCH] preview non-codeblock MathJax --- src/hgmarkdownhighlighter.cpp | 135 +++++++++- src/hgmarkdownhighlighter.h | 53 +++- src/resources/hoedown.js | 16 +- src/resources/markdown-it.js | 16 +- src/resources/marked.js | 16 +- src/resources/mathjax_preview.js | 58 ++++- src/resources/showdown.js | 16 +- .../themes/v_moonlight/v_moonlight.css | 10 +- .../themes/v_moonlight/v_moonlight.palette | 2 +- src/resources/themes/v_native/v_native.css | 10 +- .../themes/v_native/v_native.palette | 2 +- src/resources/themes/v_pure/v_pure.css | 10 +- src/resources/themes/v_pure/v_pure.palette | 2 +- src/src.pro | 6 +- src/utils/vutils.cpp | 3 + src/vdocument.cpp | 15 +- src/vdocument.h | 24 +- src/vlivepreviewhelper.cpp | 67 +++-- src/vlivepreviewhelper.h | 11 +- src/vmathjaxinplacepreviewhelper.cpp | 242 ++++++++++++++++++ src/vmathjaxinplacepreviewhelper.h | 147 +++++++++++ src/vmathjaxpreviewhelper.cpp | 69 ++++- src/vmathjaxpreviewhelper.h | 8 + src/vmathjaxwebdocument.cpp | 7 +- src/vmathjaxwebdocument.h | 9 +- src/vmdeditor.cpp | 13 +- src/vmdeditor.h | 6 +- src/vmdtab.cpp | 28 +- src/vmdtab.h | 6 +- src/vpreviewmanager.cpp | 23 +- src/vpreviewmanager.h | 4 +- src/vtextblockdata.cpp | 2 - src/vtextblockdata.h | 32 ++- 33 files changed, 910 insertions(+), 158 deletions(-) create mode 100644 src/vmathjaxinplacepreviewhelper.cpp create mode 100644 src/vmathjaxinplacepreviewhelper.h diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 6b413ae9..e9ba5dca 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -87,7 +87,10 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector &s m_completeTimer->setSingleShot(true); m_completeTimer->setInterval(completeWaitTime); connect(m_completeTimer, &QTimer::timeout, - this, &HGMarkdownHighlighter::highlightCompleted); + this, [this]() { + updateMathjaxBlocks(); + emit highlightCompleted(); + }); connect(document, &QTextDocument::contentsChange, this, &HGMarkdownHighlighter::handleContentChange); @@ -183,7 +186,7 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text) setCurrentBlockState(HighlightBlockState::Verbatim); goto exit; } else if (m_enableMathjax) { - highlightMathJax(curBlock, text); + highlightMathJax(text); } } @@ -520,11 +523,14 @@ static bool intersect(const QList> &p_indices, int &p_start, int return false; } -void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QString &p_text) +void HGMarkdownHighlighter::highlightMathJax(const QString &p_text) { const int blockMarkLength = 2; const int inlineMarkLength = 1; + VTextBlockData *blockData = currentBlockData(); + Q_ASSERT(blockData); + int startIdx = 0; // Next position to search. int pos = 0; @@ -532,8 +538,10 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS QList> blockIdices; + bool fromPreBlock = true; // Mathjax block formula. if (state != HighlightBlockState::MathjaxBlock) { + fromPreBlock = false; startIdx = m_mathjaxBlockExp.indexIn(p_text); pos = startIdx + m_mathjaxBlockExp.matchedLength(); startIdx = pos - blockMarkLength; @@ -542,14 +550,56 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS while (startIdx >= 0) { int endIdx = m_mathjaxBlockExp.indexIn(p_text, pos); int mathLength = 0; + MathjaxInfo info; if (endIdx == -1) { setCurrentBlockState(HighlightBlockState::MathjaxBlock); mathLength = p_text.length() - startIdx; + pos = startIdx + mathLength; + + info.m_previewedAsBlock = false; + info.m_index = startIdx, + info.m_length = mathLength; + if (fromPreBlock) { + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + info.m_text = p_text.mid(startIdx, mathLength); + } + + blockData->setPendingMathjax(info); } else { + // Found end marker of a formula. mathLength = endIdx - startIdx + m_mathjaxBlockExp.matchedLength(); + pos = startIdx + mathLength; + + info.m_previewedAsBlock = false; + info.m_index = startIdx; + info.m_length = mathLength; + if (fromPreBlock) { + // A cross-block formula. + if (pos >= p_text.length()) { + info.m_previewedAsBlock = true; + } + + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + // A formula within one block. + if (pos >= p_text.length() && startIdx == 0) { + info.m_previewedAsBlock = true; + } + + info.m_text = p_text.mid(startIdx, mathLength); + } + + blockData->addMathjax(info); } - pos = startIdx + mathLength; + fromPreBlock = false; blockIdices.append(QPair(startIdx, pos)); @@ -562,7 +612,9 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS // Mathjax inline formula. startIdx = 0; pos = 0; + fromPreBlock = true; if (state != HighlightBlockState::MathjaxInline) { + fromPreBlock = false; startIdx = m_mathjaxInlineExp.indexIn(p_text); pos = startIdx + m_mathjaxInlineExp.matchedLength(); startIdx = pos - inlineMarkLength; @@ -582,8 +634,47 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS // Check if it intersect with blocks. if (!intersect(blockIdices, startIdx, pos)) { // A valid inline mathjax. + MathjaxInfo info; if (endIdx == -1) { setCurrentBlockState(HighlightBlockState::MathjaxInline); + + info.m_previewedAsBlock = false; + info.m_index = startIdx, + info.m_length = mathLength; + if (fromPreBlock) { + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + info.m_text = p_text.mid(startIdx, mathLength); + } + + blockData->setPendingMathjax(info); + } else { + info.m_previewedAsBlock = false; + info.m_index = startIdx; + info.m_length = mathLength; + if (fromPreBlock) { + // A cross-block formula. + if (pos >= p_text.length()) { + info.m_previewedAsBlock = true; + } + + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + // A formula within one block. + if (pos >= p_text.length() && startIdx == 0) { + info.m_previewedAsBlock = true; + } + + info.m_text = p_text.mid(startIdx, mathLength); + } + + blockData->addMathjax(info); } setFormat(startIdx, mathLength, m_mathjaxFormat); @@ -599,6 +690,8 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS startIdx = pos - inlineMarkLength; } + + fromPreBlock = false; } } @@ -813,12 +906,8 @@ bool HGMarkdownHighlighter::updateCodeBlocks() } m_numOfCodeBlockHighlightsToRecv = codeBlocks.size(); - if (m_numOfCodeBlockHighlightsToRecv > 0) { - emit codeBlocksUpdated(codeBlocks); - return true; - } else { - return false; - } + emit codeBlocksUpdated(codeBlocks); + return m_numOfCodeBlockHighlightsToRecv > 0; } static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b) @@ -977,3 +1066,29 @@ void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString } } } + +void HGMarkdownHighlighter::updateMathjaxBlocks() +{ + if (!m_enableMathjax) { + return; + } + + QVector blocks; + QTextBlock bl = document->firstBlock(); + while (bl.isValid()) { + VTextBlockData *data = static_cast(bl.userData()); + if (!data) { + bl = bl.next(); + continue; + } + + const QVector &info = data->getMathjax(); + for (auto const & it : info) { + blocks.append(VMathjaxBlock(bl.blockNumber(), it)); + } + + bl = bl.next(); + } + + emit mathjaxBlocksUpdated(blocks); +} diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index abe36502..1283f73e 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -69,6 +69,52 @@ struct VCodeBlock } }; + +struct VMathjaxBlock +{ + VMathjaxBlock() + : m_blockNumber(-1), + m_previewedAsBlock(false), + m_index(-1), + m_length(-1) + { + } + + VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info) + : m_blockNumber(p_blockNumber), + m_previewedAsBlock(p_info.m_previewedAsBlock), + m_index(p_info.m_index), + m_length(p_info.m_length), + m_text(p_info.m_text) + { + } + + bool equalContent(const VMathjaxBlock &p_block) const + { + return m_text == p_block.m_text; + } + + void updateNonContent(const VMathjaxBlock &p_block) + { + m_blockNumber = p_block.m_blockNumber; + m_previewedAsBlock = p_block.m_previewedAsBlock; + m_index = p_block.m_index; + m_length = p_block.m_length; + } + + int m_blockNumber; + + bool m_previewedAsBlock; + + // Start index within the block. + int m_index; + + int m_length; + + QString m_text; +}; + + // Highlight unit with global position and string style name. struct HLUnitPos { @@ -169,6 +215,9 @@ signals: // Emitted when header regions have been fetched from a new parsing result. void headersUpdated(const QVector &p_headerRegions); + // Emitted when Mathjax blocks updated. + void mathjaxBlocksUpdated(const QVector &p_mathjaxBlocks); + protected: void highlightBlock(const QString &text) Q_DECL_OVERRIDE; @@ -272,7 +321,7 @@ private: void highlightCodeBlock(const QTextBlock &p_block, const QString &p_text); - void highlightMathJax(const QTextBlock &p_block, const QString &p_text); + void highlightMathJax(const QString &p_text); // Highlight links using regular expression. // PEG Markdown Highlight treat URLs with spaces illegal. This function is @@ -295,6 +344,8 @@ private: // Return false if there is none. bool updateCodeBlocks(); + void updateMathjaxBlocks(); + // Fetch all the HTML comment regions from parsing result. void initHtmlCommentRegionsFromResult(); diff --git a/src/resources/hoedown.js b/src/resources/hoedown.js index b72180cc..8e324aed 100644 --- a/src/resources/hoedown.js +++ b/src/resources/hoedown.js @@ -96,14 +96,14 @@ var highlightText = function(text, id, timeStamp) { content.highlightTextCB(html, id, timeStamp); } -var textToHtml = function(text) { +var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) { var html = marked(text); - var container = textHtmlDiv; - container.innerHTML = html; + if (inlineStyle) { + var container = textHtmlDiv; + container.innerHTML = html; + html = getHtmlWithInlineStyles(container); + container.innerHTML = ""; + } - html = getHtmlWithInlineStyles(container); - - container.innerHTML = ""; - - content.textToHtmlCB(text, html); + content.textToHtmlCB(identifier, id, timeStamp, html); } diff --git a/src/resources/markdown-it.js b/src/resources/markdown-it.js index 7e08cf44..e2a27371 100644 --- a/src/resources/markdown-it.js +++ b/src/resources/markdown-it.js @@ -140,14 +140,14 @@ var highlightText = function(text, id, timeStamp) { content.highlightTextCB(html, id, timeStamp); }; -var textToHtml = function(text) { +var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) { var html = mdit.render(text); - var container = textHtmlDiv; - container.innerHTML = html; + if (inlineStyle) { + var container = textHtmlDiv; + container.innerHTML = html; + html = getHtmlWithInlineStyles(container); + container.innerHTML = ""; + } - html = getHtmlWithInlineStyles(container); - - container.innerHTML = ""; - - content.textToHtmlCB(text, html); + content.textToHtmlCB(identifier, id, timeStamp, html); }; diff --git a/src/resources/marked.js b/src/resources/marked.js index 0027f16e..d03c49fb 100644 --- a/src/resources/marked.js +++ b/src/resources/marked.js @@ -84,14 +84,14 @@ var highlightText = function(text, id, timeStamp) { content.highlightTextCB(html, id, timeStamp); } -var textToHtml = function(text) { +var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) { var html = marked(text); - var container = textHtmlDiv; - container.innerHTML = html; + if (inlineStyle) { + var container = textHtmlDiv; + container.innerHTML = html; + html = getHtmlWithInlineStyles(container); + container.innerHTML = ""; + } - html = getHtmlWithInlineStyles(container); - - container.innerHTML = ""; - - content.textToHtmlCB(text, html); + content.textToHtmlCB(identifier, id, timeStamp, html); } diff --git a/src/resources/mathjax_preview.js b/src/resources/mathjax_preview.js index 7b5eb72c..3c0c2a61 100644 --- a/src/resources/mathjax_preview.js +++ b/src/resources/mathjax_preview.js @@ -17,29 +17,72 @@ new QWebChannel(qt.webChannelTransport, channelInitialized = true; }); -var previewMathJax = function(identifier, id, timeStamp, text) { - if (text.length == 0) { +var timeStamps = new Map(); + +var htmlToElement = function(html) { + var template = document.createElement('template'); + html = html.trim(); + template.innerHTML = html; + return template.content.firstChild; +}; + +var isEmptyMathJax = function(text) { + return text.replace(/\$/g, '').trim().length == 0; +}; + +var previewMathJax = function(identifier, id, timeStamp, text, isHtml) { + timeStamps.set(identifier, timeStamp); + + if (isEmptyMathJax(text)) { + content.mathjaxResultReady(identifier, id, timeStamp, 'png', ''); + return; + } + + var p = null; + if (isHtml) { + p = htmlToElement(text); + if (isEmptyMathJax(p.textContent)) { + p = null; + } + } else { + p = document.createElement('p'); + p.textContent = text; + } + + if (!p) { + content.mathjaxResultReady(identifier, id, timeStamp, 'png', ''); return; } - var p = document.createElement('p'); - p.textContent = text; contentDiv.appendChild(p); + var isBlock = false; + if (text.indexOf('$$') !== -1) { + isBlock = true; + } + try { MathJax.Hub.Queue(["Typeset", MathJax.Hub, p, - postProcessMathJax.bind(undefined, identifier, id, timeStamp, p)]); + [postProcessMathJax, identifier, id, timeStamp, p, isBlock]]); } catch (err) { console.log("err: " + err); + content.mathjaxResultReady(identifier, id, timeStamp, 'png', ''); contentDiv.removeChild(p); delete p; } }; -var postProcessMathJax = function(identifier, id, timeStamp, container) { - domtoimage.toPng(container, { height: container.clientHeight * 1.5 }).then(function (dataUrl) { +var postProcessMathJax = function(identifier, id, timeStamp, container, isBlock) { + if (timeStamps.get(identifier) != timeStamp) { + contentDiv.removeChild(container); + delete container; + return; + } + + var hei = (isBlock ? container.clientHeight * 1.5 : container.clientHeight * 1.8) + 5; + domtoimage.toPng(container, { height: hei }).then(function (dataUrl) { var png = dataUrl.substring(dataUrl.indexOf(',') + 1); content.mathjaxResultReady(identifier, id, timeStamp, 'png', png); @@ -47,6 +90,7 @@ var postProcessMathJax = function(identifier, id, timeStamp, container) { delete container; }).catch(function (err) { console.log("err: " + err); + content.mathjaxResultReady(identifier, id, timeStamp, 'png', ''); contentDiv.removeChild(container); delete container; }); diff --git a/src/resources/showdown.js b/src/resources/showdown.js index c711ea68..e95ae9f1 100644 --- a/src/resources/showdown.js +++ b/src/resources/showdown.js @@ -137,7 +137,7 @@ var highlightText = function(text, id, timeStamp) { content.highlightTextCB(html, id, timeStamp); } -var textToHtml = function(text) { +var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) { var html = renderer.makeHtml(text); var parser = new DOMParser(); @@ -148,12 +148,12 @@ var textToHtml = function(text) { delete parser; - var container = textHtmlDiv; - container.innerHTML = html; + if (inlineStyle) { + var container = textHtmlDiv; + container.innerHTML = html; + html = getHtmlWithInlineStyles(container); + container.innerHTML = ""; + } - html = getHtmlWithInlineStyles(container); - - container.innerHTML = ""; - - content.textToHtmlCB(text, html); + content.textToHtmlCB(identifier, id, timeStamp, html); } diff --git a/src/resources/themes/v_moonlight/v_moonlight.css b/src/resources/themes/v_moonlight/v_moonlight.css index b2ae00c6..34e8c370 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.css +++ b/src/resources/themes/v_moonlight/v_moonlight.css @@ -23,17 +23,17 @@ p { } h1 { - font-size: 36px; -} - -h2 { font-size: 30px; } -h3 { +h2 { font-size: 26px; } +h3 { + font-size: 24px; +} + h4 { font-size: 22px; } diff --git a/src/resources/themes/v_moonlight/v_moonlight.palette b/src/resources/themes/v_moonlight/v_moonlight.palette index 3bfa7620..e599f9b6 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.palette +++ b/src/resources/themes/v_moonlight/v_moonlight.palette @@ -7,7 +7,7 @@ mdhl_file=v_moonlight.mdhl css_file=v_moonlight.css codeblock_css_file=v_moonlight_codeblock.css mermaid_css_file=v_moonlight_mermaid.css -version=8 +version=9 ; This mapping will be used to translate colors when the content of HTML is copied ; without background. You could just specify the foreground colors mapping here. diff --git a/src/resources/themes/v_native/v_native.css b/src/resources/themes/v_native/v_native.css index 2d53807d..f1eaefc0 100644 --- a/src/resources/themes/v_native/v_native.css +++ b/src/resources/themes/v_native/v_native.css @@ -22,17 +22,17 @@ p { } h1 { - font-size: 36px; -} - -h2 { font-size: 30px; } -h3 { +h2 { font-size: 26px; } +h3 { + font-size: 24px; +} + h4 { font-size: 22px; } diff --git a/src/resources/themes/v_native/v_native.palette b/src/resources/themes/v_native/v_native.palette index d87dee19..744f7ba8 100644 --- a/src/resources/themes/v_native/v_native.palette +++ b/src/resources/themes/v_native/v_native.palette @@ -7,7 +7,7 @@ mdhl_file=v_native.mdhl css_file=v_native.css codeblock_css_file=v_native_codeblock.css mermaid_css_file=v_native_mermaid.css -version=8 +version=9 [phony] ; Abstract color attributes. diff --git a/src/resources/themes/v_pure/v_pure.css b/src/resources/themes/v_pure/v_pure.css index 1e4574d6..f2a696bb 100644 --- a/src/resources/themes/v_pure/v_pure.css +++ b/src/resources/themes/v_pure/v_pure.css @@ -23,17 +23,17 @@ p { } h1 { - font-size: 36px; -} - -h2 { font-size: 30px; } -h3 { +h2 { font-size: 26px; } +h3 { + font-size: 24px; +} + h4 { font-size: 22px; } diff --git a/src/resources/themes/v_pure/v_pure.palette b/src/resources/themes/v_pure/v_pure.palette index 4edfb18f..eca267ce 100644 --- a/src/resources/themes/v_pure/v_pure.palette +++ b/src/resources/themes/v_pure/v_pure.palette @@ -7,7 +7,7 @@ mdhl_file=v_pure.mdhl css_file=v_pure.css codeblock_css_file=v_pure_codeblock.css mermaid_css_file=v_pure_mermaid.css -version=8 +version=9 [phony] ; Abstract color attributes. diff --git a/src/src.pro b/src/src.pro index 38ff2e02..1b7aa04d 100644 --- a/src/src.pro +++ b/src/src.pro @@ -130,7 +130,8 @@ SOURCES += main.cpp\ vgraphvizhelper.cpp \ vlivepreviewhelper.cpp \ vmathjaxpreviewhelper.cpp \ - vmathjaxwebdocument.cpp + vmathjaxwebdocument.cpp \ + vmathjaxinplacepreviewhelper.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -251,7 +252,8 @@ HEADERS += vmainwindow.h \ vgraphvizhelper.h \ vlivepreviewhelper.h \ vmathjaxpreviewhelper.h \ - vmathjaxwebdocument.h + vmathjaxwebdocument.h \ + vmathjaxinplacepreviewhelper.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index efa70cc6..0399bc2f 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -535,6 +535,9 @@ qreal VUtils::calculateScaleFactor() factor = dpi / refDpi; if (factor < 1) { factor = 1; + } else { + // Keep only two digits after the dot. + factor = (int)(factor * 100) / 100.0; } } diff --git a/src/vdocument.cpp b/src/vdocument.cpp index af5df365..78e812ae 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -11,7 +11,8 @@ VDocument::VDocument(const VFile *v_file, QObject *p_parent) m_file(v_file), m_readyToHighlight(false), m_plantUMLHelper(NULL), - m_graphvizHelper(NULL) + m_graphvizHelper(NULL), + m_nextID(0) { } @@ -83,9 +84,13 @@ void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp emit textHighlighted(p_html, p_id, p_timeStamp); } -void VDocument::textToHtmlAsync(const QString &p_text) +void VDocument::textToHtmlAsync(int p_identitifer, + int p_id, + int p_timeStamp, + const QString &p_text, + bool p_inlineStyle) { - emit requestTextToHtml(p_text); + emit requestTextToHtml(p_identitifer, p_id, p_timeStamp, p_text, p_inlineStyle); } void VDocument::getHtmlContentAsync() @@ -93,9 +98,9 @@ void VDocument::getHtmlContentAsync() emit requestHtmlContent(); } -void VDocument::textToHtmlCB(const QString &p_text, const QString &p_html) +void VDocument::textToHtmlCB(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html) { - emit textToHtmlFinished(p_text, p_html); + emit textToHtmlFinished(p_identitifer, p_id, p_timeStamp, p_html); } void VDocument::noticeReadyToHighlightText() diff --git a/src/vdocument.h b/src/vdocument.h index e60a273e..fc2eecd1 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -34,7 +34,11 @@ public: void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp); // Request to convert @p_text to HTML. - void textToHtmlAsync(const QString &p_text); + void textToHtmlAsync(int p_identitifer, + int p_id, + int p_timeStamp, + const QString &p_text, + bool p_inlineStyle); void setFile(const VFile *p_file); @@ -61,6 +65,8 @@ public: // Set the content of the preview. void setPreviewContent(const QString &p_lang, const QString &p_html); + int registerIdentifier(); + public slots: // Will be called in the HTML side @@ -82,7 +88,7 @@ public slots: void noticeReadyToHighlightText(); - void textToHtmlCB(const QString &p_text, const QString &p_html); + void textToHtmlCB(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html); void noticeReadyToTextToHtml(); @@ -130,9 +136,13 @@ signals: void logicsFinished(); - void requestTextToHtml(const QString &p_text); + void requestTextToHtml(int p_identitifer, + int p_id, + int p_timeStamp, + const QString &p_text, + bool p_inlineStyle); - void textToHtmlFinished(const QString &p_text, const QString &p_html); + void textToHtmlFinished(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html); void requestHtmlContent(); @@ -186,6 +196,8 @@ private: VPlantUMLHelper *m_plantUMLHelper; VGraphvizHelper *m_graphvizHelper; + + int m_nextID; }; inline bool VDocument::isReadyToHighlight() const @@ -203,4 +215,8 @@ inline const VWordCountInfo &VDocument::getWordCountInfo() const return m_wordCountInfo; } +inline int VDocument::registerIdentifier() +{ + return ++m_nextID; +} #endif // VDOCUMENT_H diff --git a/src/vlivepreviewhelper.cpp b/src/vlivepreviewhelper.cpp index b71c4a6b..e67e9263 100644 --- a/src/vlivepreviewhelper.cpp +++ b/src/vlivepreviewhelper.cpp @@ -39,13 +39,6 @@ CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb) { } -void CodeBlockPreviewInfo::clearImageData() -{ - m_imgData.clear(); - m_imgDataBa.clear(); - m_inplacePreview.clear(); -} - void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc, const VCodeBlock &p_cb) { @@ -60,6 +53,7 @@ void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc, m_inplacePreview->m_endPos = block.position() + block.length(); m_inplacePreview->m_blockPos = block.position(); m_inplacePreview->m_blockNumber = m_codeBlock.m_endBlock; + // Padding is not changed since content is not changed. } else { m_inplacePreview->clear(); } @@ -143,7 +137,8 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock m_cbIndex = -1; int cursorBlock = m_editor->textCursorW().block().blockNumber(); int idx = 0; - bool needUpdate = true; + bool needUpdate = m_livePreviewEnabled; + bool manualInplacePreview = m_inplacePreviewEnabled; for (auto const & vcb : p_codeBlocks) { if (!isPreviewLang(vcb.m_lang)) { continue; @@ -165,6 +160,7 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock if (m_inplacePreviewEnabled && !m_codeBlocks[idx].inplacePreviewReady()) { processForInplacePreview(idx); + manualInplacePreview = false; } if (m_livePreviewEnabled @@ -180,9 +176,17 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock ++idx; } - m_codeBlocks.resize(idx); + if (idx == m_codeBlocks.size()) { + manualInplacePreview = false; + } else { + m_codeBlocks.resize(idx); + } - if (m_livePreviewEnabled && needUpdate) { + if (manualInplacePreview) { + updateInplacePreview(); + } + + if (needUpdate) { updateLivePreview(); } } @@ -283,8 +287,12 @@ void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled) m_livePreviewEnabled = p_enabled; if (!m_livePreviewEnabled) { m_cbIndex = -1; - m_codeBlocks.clear(); m_document->previewCodeBlock(-1, "", "", true); + + if (!m_inplacePreviewEnabled) { + m_codeBlocks.clear(); + updateInplacePreview(); + } } } @@ -295,13 +303,11 @@ void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled) } m_inplacePreviewEnabled = p_enabled; - if (!m_livePreviewEnabled) { - for (auto & cb : m_codeBlocks) { - cb.clearImageData(); - } - - updateInplacePreview(); + if (!m_inplacePreviewEnabled && !m_livePreviewEnabled) { + m_codeBlocks.clear(); } + + updateInplacePreview(); } void VLivePreviewHelper::localAsyncResultReady(int p_id, @@ -356,6 +362,7 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx) { CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx]; const VCodeBlock &vcb = cb.codeBlock(); + Q_ASSERT(!(cb.hasImageData() || cb.hasImageDataBa())); if (vcb.m_lang == "dot") { if (!m_graphvizHelper) { m_graphvizHelper = new VGraphvizHelper(this); @@ -363,15 +370,10 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx) 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, - m_timeStamp, - "svg", - removeFence(vcb.m_text)); - } + m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW, + m_timeStamp, + "svg", + removeFence(vcb.m_text)); } else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) { if (!m_plantUMLHelper) { m_plantUMLHelper = new VPlantUMLHelper(this); @@ -379,15 +381,10 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx) 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, - m_timeStamp, - "svg", - removeFence(vcb.m_text)); - } + m_plantUMLHelper->processAsync(p_idx | LANG_PREFIX_PLANTUML | TYPE_INPLACE_PREVIEW, + m_timeStamp, + "svg", + removeFence(vcb.m_text)); } else if (vcb.m_lang == "flow" || vcb.m_lang == "flowchart") { m_mathJaxHelper->previewDiagram(m_mathJaxID, diff --git a/src/vlivepreviewhelper.h b/src/vlivepreviewhelper.h index f26b8445..f88b2c94 100644 --- a/src/vlivepreviewhelper.h +++ b/src/vlivepreviewhelper.h @@ -21,9 +21,15 @@ public: explicit CodeBlockPreviewInfo(const VCodeBlock &p_cb); - void clearImageData(); + void clearImageData() + { + m_imgData.clear(); + m_imgDataBa.clear(); + m_inplacePreview.clear(); + } - void updateNonContent(const QTextDocument *p_doc, const VCodeBlock &p_cb); + void updateNonContent(const QTextDocument *p_doc, + const VCodeBlock &p_cb); void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc); @@ -151,7 +157,6 @@ private slots: const QByteArray &p_data); private: - bool isPreviewLang(const QString &p_lang) const; // Get image data for this code block for inplace preview. diff --git a/src/vmathjaxinplacepreviewhelper.cpp b/src/vmathjaxinplacepreviewhelper.cpp new file mode 100644 index 00000000..299aa7a8 --- /dev/null +++ b/src/vmathjaxinplacepreviewhelper.cpp @@ -0,0 +1,242 @@ +#include "vmathjaxinplacepreviewhelper.h" + +#include + +#include "veditor.h" +#include "vdocument.h" +#include "vmainwindow.h" +#include "veditarea.h" +#include "vmathjaxpreviewhelper.h" + +extern VMainWindow *g_mainWin; + +MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo() +{ +} + +MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb) + : m_mathjaxBlock(p_mb) +{ +} + +void MathjaxBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc, + const VEditor *p_editor, + const VMathjaxBlock &p_mb) +{ + m_mathjaxBlock.updateNonContent(p_mb); + if (m_inplacePreview.isNull()) { + return; + } + + QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber); + if (block.isValid()) { + m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index; + m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length; + m_inplacePreview->m_blockPos = block.position(); + m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber; + // Padding may changed. + m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block, + p_editor->tabStopWidthW()); + m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock; + } else { + m_inplacePreview->clear(); + } +} + +void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, + const QTextDocument *p_doc) +{ + QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber); + if (block.isValid()) { + if (m_inplacePreview.isNull()) { + m_inplacePreview.reset(new VImageToPreview()); + } + + m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index; + m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length; + m_inplacePreview->m_blockPos = block.position(); + m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber; + m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block, + p_editor->tabStopWidthW()); + m_inplacePreview->m_name = QString::number(getImageIndex()); + m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock; + + if (hasImageDataBa()) { + m_inplacePreview->m_image.loadFromData(m_imgDataBa, + m_imgFormat.toLocal8Bit().data()); + } else { + m_inplacePreview->m_image = QPixmap(); + } + } else { + m_inplacePreview->clear(); + } +} + +VMathJaxInplacePreviewHelper::VMathJaxInplacePreviewHelper(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_enabled(false), + m_lastInplacePreviewSize(0), + m_timeStamp(0) +{ + m_mathJaxHelper = g_mainWin->getEditArea()->getMathJaxPreviewHelper(); + m_mathJaxID = m_mathJaxHelper->registerIdentifier(); + connect(m_mathJaxHelper, &VMathJaxPreviewHelper::mathjaxPreviewResultReady, + this, &VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady); + + m_documentID = m_document->registerIdentifier(); + connect(m_document, &VDocument::textToHtmlFinished, + this, &VMathJaxInplacePreviewHelper::textToHtmlFinished); +} + +void VMathJaxInplacePreviewHelper::setEnabled(bool p_enabled) +{ + if (m_enabled != p_enabled) { + m_enabled = p_enabled; + + if (!m_enabled) { + m_mathjaxBlocks.clear(); + } + + updateInplacePreview(); + } +} + +void VMathJaxInplacePreviewHelper::updateMathjaxBlocks(const QVector &p_blocks) +{ + if (!m_enabled) { + return; + } + + ++m_timeStamp; + + int idx = 0; + bool manualUpdate = true; + for (auto const & vmb : p_blocks) { + if (idx < m_mathjaxBlocks.size()) { + MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[idx]; + if (mb.mathjaxBlock().equalContent(vmb)) { + mb.updateNonContent(m_doc, m_editor, vmb); + } else { + mb.setMathjaxBlock(vmb); + } + } else { + m_mathjaxBlocks.append(MathjaxBlockPreviewInfo(vmb)); + } + + if (m_enabled + && !m_mathjaxBlocks[idx].inplacePreviewReady()) { + manualUpdate = false; + processForInplacePreview(idx); + } + + ++idx; + } + + m_mathjaxBlocks.resize(idx); + + if (manualUpdate) { + updateInplacePreview(); + } +} + +void VMathJaxInplacePreviewHelper::processForInplacePreview(int p_idx) +{ + MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_idx]; + const VMathjaxBlock &vmb = mb.mathjaxBlock(); + if (vmb.m_text.isEmpty()) { + updateInplacePreview(); + } else { + textToHtmlViaWebView(vmb.m_text, p_idx, m_timeStamp); + } +} + +void VMathJaxInplacePreviewHelper::textToHtmlViaWebView(const QString &p_text, + int p_id, + int p_timeStamp) +{ + int maxRetry = 50; + while (!m_document->isReadyToTextToHtml() && maxRetry > 0) { + qDebug() << "wait for web side ready to convert text to HTML"; + VUtils::sleepWait(100); + --maxRetry; + } + + if (maxRetry == 0) { + qWarning() << "web side is not ready to convert text to HTML"; + return; + } + + m_document->textToHtmlAsync(m_documentID, p_id, p_timeStamp, p_text, false); +} + +void VMathJaxInplacePreviewHelper::updateInplacePreview() +{ + QSet blocks; + QVector > images; + for (int i = 0; i < m_mathjaxBlocks.size(); ++i) { + MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[i]; + if (mb.inplacePreviewReady()) { + if (!mb.inplacePreview()->m_image.isNull()) { + images.append(mb.inplacePreview()); + } else { + blocks.insert(mb.inplacePreview()->m_blockNumber); + } + } else { + blocks.insert(mb.mathjaxBlock().m_blockNumber); + } + } + + if (images.isEmpty() && m_lastInplacePreviewSize == 0) { + return; + } + + emit inplacePreviewMathjaxBlockUpdated(images); + + m_lastInplacePreviewSize = images.size(); + + if (!blocks.isEmpty()) { + emit checkBlocksForObsoletePreview(blocks.toList()); + } +} + +void VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady(int p_identitifer, + int p_id, + TimeStamp p_timeStamp, + const QString &p_format, + const QByteArray &p_data) +{ + if (p_identitifer != m_mathJaxID || p_timeStamp != m_timeStamp) { + return; + } + + if (p_id >= m_mathjaxBlocks.size() || p_data.isEmpty()) { + updateInplacePreview(); + return; + } + + MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_id]; + mb.setImageDataBa(p_format, p_data); + mb.updateInplacePreview(m_editor, m_doc); + updateInplacePreview(); +} + +void VMathJaxInplacePreviewHelper::textToHtmlFinished(int p_identitifer, + int p_id, + int p_timeStamp, + const QString &p_html) +{ + if (m_documentID != p_identitifer || m_timeStamp != p_timeStamp) { + return; + } + + Q_ASSERT(p_html.startsWith("<")); + m_mathJaxHelper->previewMathJaxFromHtml(m_mathJaxID, + p_id, + p_timeStamp, + p_html); +} diff --git a/src/vmathjaxinplacepreviewhelper.h b/src/vmathjaxinplacepreviewhelper.h new file mode 100644 index 00000000..6c2265f5 --- /dev/null +++ b/src/vmathjaxinplacepreviewhelper.h @@ -0,0 +1,147 @@ +#ifndef VMATHJAXINPLACEPREVIEWHELPER_H +#define VMATHJAXINPLACEPREVIEWHELPER_H + +#include + +#include "hgmarkdownhighlighter.h" +#include "vpreviewmanager.h" +#include "vconstants.h" + +class VEditor; +class VDocument; +class QTextDocument; +class VMathJaxPreviewHelper; + +class MathjaxBlockPreviewInfo +{ +public: + MathjaxBlockPreviewInfo(); + + explicit MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb); + + void clearImageData() + { + m_imgDataBa.clear(); + m_inplacePreview.clear(); + } + + void updateNonContent(const QTextDocument *p_doc, + const VEditor *p_editor, + const VMathjaxBlock &p_mb); + + void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc); + + VMathjaxBlock &mathjaxBlock() + { + return m_mathjaxBlock; + } + + const VMathjaxBlock &mathjaxBlock() const + { + return m_mathjaxBlock; + } + + void setMathjaxBlock(const VMathjaxBlock &p_mb) + { + m_mathjaxBlock = p_mb; + clearImageData(); + } + + bool inplacePreviewReady() const + { + return !m_inplacePreview.isNull(); + } + + void setImageDataBa(const QString &p_format, const QByteArray &p_data) + { + m_imgFormat = p_format; + m_imgDataBa = p_data; + } + + bool hasImageDataBa() const + { + return !m_imgDataBa.isEmpty(); + } + + const QSharedPointer inplacePreview() const + { + return m_inplacePreview; + } + +private: + static int getImageIndex() + { + static int index = 0; + return ++index; + } + + VMathjaxBlock m_mathjaxBlock; + + QByteArray m_imgDataBa; + + QString m_imgFormat; + + QSharedPointer m_inplacePreview; +}; + +class VMathJaxInplacePreviewHelper : public QObject +{ + Q_OBJECT +public: + VMathJaxInplacePreviewHelper(VEditor *p_editor, + VDocument *p_document, + QObject *p_parent = nullptr); + + void setEnabled(bool p_enabled); + +public slots: + void updateMathjaxBlocks(const QVector &p_blocks); + +signals: + void inplacePreviewMathjaxBlockUpdated(const QVector > &p_images); + + void checkBlocksForObsoletePreview(const QList &p_blocks); + +private slots: + void mathjaxPreviewResultReady(int p_identitifer, + int p_id, + TimeStamp p_timeStamp, + const QString &p_format, + const QByteArray &p_data); + + void textToHtmlFinished(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html); + +private: + void processForInplacePreview(int p_idx); + + // Emit signal to update inplace preview. + void updateInplacePreview(); + + void textToHtmlViaWebView(const QString &p_text, + int p_id, + int p_timeStamp); + + VEditor *m_editor; + + VDocument *m_document; + + QTextDocument *m_doc; + + bool m_enabled; + + VMathJaxPreviewHelper *m_mathJaxHelper; + + // Identification for VMathJaxPreviewHelper. + int m_mathJaxID; + + int m_lastInplacePreviewSize; + + TimeStamp m_timeStamp; + + // Sorted by m_blockNumber in ascending order. + QVector m_mathjaxBlocks; + + int m_documentID; +}; + +#endif // VMATHJAXINPLACEPREVIEWHELPER_H diff --git a/src/vmathjaxpreviewhelper.cpp b/src/vmathjaxpreviewhelper.cpp index 6fd9ead9..c0f84b16 100644 --- a/src/vmathjaxpreviewhelper.cpp +++ b/src/vmathjaxpreviewhelper.cpp @@ -1,8 +1,8 @@ #include "vmathjaxpreviewhelper.h" -#include #include #include +#include #include "utils/vutils.h" #include "vmathjaxwebdocument.h" @@ -24,17 +24,31 @@ VMathJaxPreviewHelper::~VMathJaxPreviewHelper() void VMathJaxPreviewHelper::doInit() { Q_ASSERT(!m_initialized); + Q_ASSERT(m_parentWidget); + m_initialized = true; + QWidget *focusWid = QApplication::focusWidget(); + m_webView = new QWebEngineView(m_parentWidget); connect(m_webView, &QWebEngineView::loadFinished, this, [this]() { m_webReady = true; - }); + for (auto const & it : m_pendingFunc) { + it(); + } + m_pendingFunc.clear(); + }); m_webView->hide(); m_webView->setFocusPolicy(Qt::NoFocus); + if (focusWid) { + focusWid->setFocus(); + } else { + m_parentWidget->setFocus(); + } + m_webDoc = new VMathJaxWebDocument(m_webView); connect(m_webDoc, &VMathJaxWebDocument::mathjaxPreviewResultReady, this, [this](int p_identifier, @@ -61,10 +75,6 @@ void VMathJaxPreviewHelper::doInit() m_webView->page()->setWebChannel(channel); m_webView->setHtml(VUtils::generateMathJaxPreviewTemplate(), QUrl("qrc:/resources")); - - while (!m_webReady) { - VUtils::sleepWait(100); - } } void VMathJaxPreviewHelper::previewMathJax(int p_identifier, @@ -74,7 +84,39 @@ void VMathJaxPreviewHelper::previewMathJax(int p_identifier, { init(); - m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_text); + if (!m_webReady) { + auto func = std::bind(&VMathJaxWebDocument::previewMathJax, + m_webDoc, + p_identifier, + p_id, + p_timeStamp, + p_text, + false); + m_pendingFunc.append(func); + } else { + m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_text, false); + } +} + +void VMathJaxPreviewHelper::previewMathJaxFromHtml(int p_identifier, + int p_id, + TimeStamp p_timeStamp, + const QString &p_html) +{ + init(); + + if (!m_webReady) { + auto func = std::bind(&VMathJaxWebDocument::previewMathJax, + m_webDoc, + p_identifier, + p_id, + p_timeStamp, + p_html, + true); + m_pendingFunc.append(func); + } else { + m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_html, true); + } } void VMathJaxPreviewHelper::previewDiagram(int p_identifier, @@ -85,5 +127,16 @@ void VMathJaxPreviewHelper::previewDiagram(int p_identifier, { init(); - m_webDoc->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text); + if (!m_webReady) { + auto func = std::bind(&VMathJaxWebDocument::previewDiagram, + m_webDoc, + p_identifier, + p_id, + p_timeStamp, + p_lang, + p_text); + m_pendingFunc.append(func); + } else { + m_webDoc->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text); + } } diff --git a/src/vmathjaxpreviewhelper.h b/src/vmathjaxpreviewhelper.h index 0d798906..04e0b1ca 100644 --- a/src/vmathjaxpreviewhelper.h +++ b/src/vmathjaxpreviewhelper.h @@ -2,6 +2,8 @@ #define VMATHJAXPREVIEWHELPER_H #include +#include +#include #include "vconstants.h" @@ -9,6 +11,8 @@ class QWebEngineView; class VMathJaxWebDocument; class QWidget; +typedef std::function PendingFunc; + class VMathJaxPreviewHelper : public QObject { Q_OBJECT @@ -26,6 +30,8 @@ public: // @p_text: raw text of the MathJax script. void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text); + void previewMathJaxFromHtml(int p_identitifer, int p_id, TimeStamp p_timeStamp, const QString &p_html); + // Preview @p_text and return PNG data asynchronously. // @p_identifier: identifier the caller registered; // @p_id: internal id for each caller; @@ -66,6 +72,8 @@ private: VMathJaxWebDocument *m_webDoc; bool m_webReady; + + QVector m_pendingFunc; }; inline int VMathJaxPreviewHelper::registerIdentifier() diff --git a/src/vmathjaxwebdocument.cpp b/src/vmathjaxwebdocument.cpp index 63b4314b..5584edcd 100644 --- a/src/vmathjaxwebdocument.cpp +++ b/src/vmathjaxwebdocument.cpp @@ -1,7 +1,5 @@ #include "vmathjaxwebdocument.h" -#include - VMathJaxWebDocument::VMathJaxWebDocument(QObject *p_parent) : QObject(p_parent) { @@ -10,9 +8,10 @@ VMathJaxWebDocument::VMathJaxWebDocument(QObject *p_parent) void VMathJaxWebDocument::previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, - const QString &p_text) + const QString &p_text, + bool p_isHtml) { - emit requestPreviewMathJax(p_identifier, p_id, p_timeStamp, p_text); + emit requestPreviewMathJax(p_identifier, p_id, p_timeStamp, p_text, p_isHtml); } void VMathJaxWebDocument::mathjaxResultReady(int p_identifier, diff --git a/src/vmathjaxwebdocument.h b/src/vmathjaxwebdocument.h index 66995dd6..43768ba0 100644 --- a/src/vmathjaxwebdocument.h +++ b/src/vmathjaxwebdocument.h @@ -11,7 +11,11 @@ class VMathJaxWebDocument : public QObject public: explicit VMathJaxWebDocument(QObject *p_parent = nullptr); - void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text); + void previewMathJax(int p_identifier, + int p_id, + TimeStamp p_timeStamp, + const QString &p_text, + bool p_isHtml); void previewDiagram(int p_identifier, int p_id, @@ -38,7 +42,8 @@ signals: void requestPreviewMathJax(int p_identifier, int p_id, unsigned long long p_timeStamp, - const QString &p_text); + const QString &p_text, + bool p_isHtml); void requestPreviewDiagram(int p_identifier, int p_id, diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index ff76ebc2..229c48b8 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -39,7 +39,8 @@ VMdEditor::VMdEditor(VFile *p_file, m_freshEdit(true), m_textToHtmlDialog(NULL), m_zoomDelta(0), - m_editTab(NULL) + m_editTab(NULL), + m_copyTimeStamp(0) { Q_ASSERT(p_file->getDocType() == DocType::Markdown); @@ -1124,6 +1125,8 @@ void VMdEditor::updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_a void VMdEditor::handleCopyAsAction(QAction *p_act) { + ++m_copyTimeStamp; + QTextCursor cursor = textCursor(); Q_ASSERT(cursor.hasSelection()); @@ -1134,7 +1137,7 @@ void VMdEditor::handleCopyAsAction(QAction *p_act) m_textToHtmlDialog = new VCopyTextAsHtmlDialog(text, p_act->data().toString(), this); // For Hoedown, we use marked.js to convert the text to have a general interface. - emit requestTextToHtml(text); + emit requestTextToHtml(text, 0, m_copyTimeStamp); m_textToHtmlDialog->exec(); @@ -1142,11 +1145,13 @@ void VMdEditor::handleCopyAsAction(QAction *p_act) m_textToHtmlDialog = NULL; } -void VMdEditor::textToHtmlFinished(const QString &p_text, +void VMdEditor::textToHtmlFinished(int p_id, + int p_timeStamp, const QUrl &p_baseUrl, const QString &p_html) { - if (m_textToHtmlDialog && m_textToHtmlDialog->getText() == p_text) { + Q_UNUSED(p_id); + if (m_textToHtmlDialog && p_timeStamp == m_copyTimeStamp) { m_textToHtmlDialog->setConvertedHtml(p_baseUrl, p_html); } } diff --git a/src/vmdeditor.h b/src/vmdeditor.h index 09ff78d3..505f0712 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -79,7 +79,7 @@ public: public slots: bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE; - void textToHtmlFinished(const QString &p_text, const QUrl &p_baseUrl, const QString &p_html); + void textToHtmlFinished(int p_id, int p_timeStamp, const QUrl &p_baseUrl, const QString &p_html); // Wrapper functions for QPlainTextEdit/QTextEdit. public: @@ -194,7 +194,7 @@ signals: void statusChanged(); // Request to convert @p_text to Html. - void requestTextToHtml(const QString &p_text); + void requestTextToHtml(const QString &p_text, int p_id, int p_timeStamp); protected: void updateFontAndPalette() Q_DECL_OVERRIDE; @@ -274,6 +274,8 @@ private: int m_zoomDelta; VEditTab *m_editTab; + + int m_copyTimeStamp; }; inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index 330bf1fc..62ffbec0 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -23,6 +23,7 @@ #include "vinsertselector.h" #include "vsnippetlist.h" #include "vlivepreviewhelper.h" +#include "vmathjaxinplacepreviewhelper.h" extern VMainWindow *g_mainWin; @@ -39,7 +40,8 @@ VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea, m_enableHeadingSequence(false), m_backupFileChecked(false), m_mode(Mode::InvalidMode), - m_livePreviewHelper(NULL) + m_livePreviewHelper(NULL), + m_mathjaxPreviewHelper(NULL) { V_ASSERT(m_file->getDocType() == DocType::Markdown); @@ -412,6 +414,7 @@ void VMdTab::setupMarkdownViewer() page->setBackgroundColor(Qt::transparent); m_document = new VDocument(m_file, m_webViewer); + m_documentID = m_document->registerIdentifier(); QWebChannel *channel = new QWebChannel(m_webViewer); channel->registerObject(QStringLiteral("content"), m_document); @@ -435,9 +438,13 @@ void VMdTab::setupMarkdownViewer() tabIsReady(TabReady::ReadMode); }); connect(m_document, &VDocument::textToHtmlFinished, - this, [this](const QString &p_text, const QString &p_html) { + this, [this](int p_identitifer, int p_id, int p_timeStamp, const QString &p_html) { Q_ASSERT(m_editor); - m_editor->textToHtmlFinished(p_text, m_webViewer->url(), p_html); + if (m_documentID != p_identitifer) { + return; + } + + m_editor->textToHtmlFinished(p_id, p_timeStamp, m_webViewer->url(), p_html); }); connect(m_document, &VDocument::wordCountInfoUpdated, this, [this]() { @@ -526,6 +533,17 @@ void VMdTab::setupMarkdownEditor() connect(m_livePreviewHelper, &VLivePreviewHelper::checkBlocksForObsoletePreview, m_editor->getPreviewManager(), &VPreviewManager::checkBlocksForObsoletePreview); m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled()); + + m_mathjaxPreviewHelper = new VMathJaxInplacePreviewHelper(m_editor, m_document, this); + connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::mathjaxBlocksUpdated, + m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::updateMathjaxBlocks); + connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged, + m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::setEnabled); + connect(m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::inplacePreviewMathjaxBlockUpdated, + m_editor->getPreviewManager(), &VPreviewManager::updateMathjaxBlocks); + connect(m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::checkBlocksForObsoletePreview, + m_editor->getPreviewManager(), &VPreviewManager::checkBlocksForObsoletePreview); + m_mathjaxPreviewHelper->setEnabled(m_editor->getPreviewManager()->isPreviewEnabled()); } void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml) @@ -1129,7 +1147,7 @@ void VMdTab::handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act) } } -void VMdTab::textToHtmlViaWebView(const QString &p_text) +void VMdTab::textToHtmlViaWebView(const QString &p_text, int p_id, int p_timeStamp) { int maxRetry = 50; while (!m_document->isReadyToTextToHtml() && maxRetry > 0) { @@ -1143,7 +1161,7 @@ void VMdTab::textToHtmlViaWebView(const QString &p_text) return; } - m_document->textToHtmlAsync(p_text); + m_document->textToHtmlAsync(m_documentID, p_id, p_timeStamp, p_text, true); } void VMdTab::handleVimCmdCommandCancelled() diff --git a/src/vmdtab.h b/src/vmdtab.h index cb59f70a..f050721a 100644 --- a/src/vmdtab.h +++ b/src/vmdtab.h @@ -17,6 +17,7 @@ class QTimer; class QWebEngineDownloadItem; class QSplitter; class VLivePreviewHelper; +class VMathJaxInplacePreviewHelper; class VMdTab : public VEditTab { @@ -218,7 +219,7 @@ private: // updateStatus() with only cursor position information. void updateCursorStatus(); - void textToHtmlViaWebView(const QString &p_text); + void textToHtmlViaWebView(const QString &p_text, int p_id, int p_timeStamp); bool executeVimCommandInWebView(const QString &p_cmd); @@ -253,6 +254,9 @@ private: QSharedPointer m_previewWebViewState; VLivePreviewHelper *m_livePreviewHelper; + VMathJaxInplacePreviewHelper *m_mathjaxPreviewHelper; + + int m_documentID; }; inline VMdEditor *VMdTab::getEditor() diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index a8b300da..8dde8ba9 100644 --- a/src/vpreviewmanager.cpp +++ b/src/vpreviewmanager.cpp @@ -258,9 +258,10 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link) return name; } -QString VPreviewManager::imageResourceNameFromCodeBlock(const QSharedPointer &p_image) +QString VPreviewManager::imageResourceNameForSource(PreviewSource p_source, + const QSharedPointer &p_image) { - QString name = "CODE_BLOCK_" + p_image->m_name; + QString name = QString::number((int)p_source) + "_" + p_image->m_name; if (m_editor->containsImage(name)) { return name; } @@ -371,7 +372,7 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp, continue; } - QString name = imageResourceNameFromCodeBlock(img); + QString name = imageResourceNameForSource(p_source, img); if (name.isEmpty()) { continue; } @@ -420,7 +421,6 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp, QSet affectedBlocks; QVector obsoleteBlocks; const QSet &blocks = m_highlighter->getPossiblePreviewBlocks(); - qDebug() << "possible preview blocks" << blocks; for (auto i : blocks) { QTextBlock block = m_document->findBlockByNumber(i); if (!block.isValid()) { @@ -474,6 +474,21 @@ void VPreviewManager::updateCodeBlocks(const QVector > &p_images) +{ + if (!m_previewEnabled) { + return; + } + + TS ts = ++timeStamp(PreviewSource::MathjaxBlock); + + updateBlockPreviewInfo(ts, PreviewSource::MathjaxBlock, p_images); + + clearBlockObsoletePreviewInfo(ts, PreviewSource::MathjaxBlock); + + clearObsoleteImages(ts, PreviewSource::MathjaxBlock); +} + void VPreviewManager::checkBlocksForObsoletePreview(const QList &p_blocks) { if (p_blocks.isEmpty()) { diff --git a/src/vpreviewmanager.h b/src/vpreviewmanager.h index d7a5e959..0445a95b 100644 --- a/src/vpreviewmanager.h +++ b/src/vpreviewmanager.h @@ -80,6 +80,8 @@ public slots: void updateCodeBlocks(const QVector > &p_images); + void updateMathjaxBlocks(const QVector > &p_images); + signals: // Request highlighter to update image links. void requestUpdateImageLinks(); @@ -168,7 +170,7 @@ private: // Returns empty if fail to add the image to the resource manager. QString imageResourceName(const ImageLinkInfo &p_link); - QString imageResourceNameFromCodeBlock(const QSharedPointer &p_image); + QString imageResourceNameForSource(PreviewSource p_source, const QSharedPointer &p_image); QHash &imageCache(PreviewSource p_source); diff --git a/src/vtextblockdata.cpp b/src/vtextblockdata.cpp index 430834b2..13eb1510 100644 --- a/src/vtextblockdata.cpp +++ b/src/vtextblockdata.cpp @@ -35,13 +35,11 @@ bool VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info) *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)) { // The new one intersect with an old one. // Remove the old one. Q_ASSERT(ele->m_timeStamp < p_info->m_timeStamp); - qDebug() << "remove intersecting old image" << ele->m_imageInfo.toString(); delete ele; it = m_previews.erase(it); } else { diff --git a/src/vtextblockdata.h b/src/vtextblockdata.h index 067cd04a..c29d5fb5 100644 --- a/src/vtextblockdata.h +++ b/src/vtextblockdata.h @@ -9,6 +9,7 @@ enum class PreviewSource { ImageLink = 0, CodeBlock, + MathjaxBlock, MaxNumberOfSources }; @@ -133,7 +134,7 @@ struct MathjaxInfo { public: MathjaxInfo() - : m_isBlock(false), + : m_previewedAsBlock(false), m_index(-1), m_length(0) { @@ -145,26 +146,41 @@ public: return m_index >= 0 && m_length > 0; } - bool isBlock() const + bool previewedAsBlock() const { - return m_isBlock; + return m_previewedAsBlock; } void clear() { - m_isBlock = false; + m_previewedAsBlock = false; m_index = -1; m_length = 0; } - // Inline or block formula. - bool m_isBlock; + const QString &text() const + { + return m_text; + } + + QString toString() const + { + return QString("MathjaxInfo %1 (%2,%3) %4").arg(m_previewedAsBlock) + .arg(m_index) + .arg(m_length) + .arg(m_text); + } + + // Whether it should be previewed as block or not. + bool m_previewedAsBlock; // Start index wihtin block, including the start mark. int m_index; // Length of this mathjax, including the end mark. int m_length; + + QString m_text; }; @@ -198,7 +214,7 @@ public: void setPendingMathjax(const MathjaxInfo &p_info); - const QVector getMathjax() const; + const QVector &getMathjax() const; void addMathjax(const MathjaxInfo &p_info); @@ -250,7 +266,7 @@ inline void VTextBlockData::setPendingMathjax(const MathjaxInfo &p_info) m_pendingMathjax = p_info; } -inline const QVector VTextBlockData::getMathjax() const +inline const QVector &VTextBlockData::getMathjax() const { return m_mathjax; }