diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 7dfc34bb..5c72cd2a 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -112,10 +112,41 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks) } } + updateImageBlocks(); + pmh_free_elements(result); result = NULL; } +void HGMarkdownHighlighter::updateImageBlocks() +{ + imageBlocks.clear(); + for (int i = 0; i < highlightingStyles.size(); i++) + { + const HighlightingStyle &style = highlightingStyles[i]; + if (style.type != pmh_IMAGE) { + continue; + } + pmh_element *elem_cursor = result[style.type]; + while (elem_cursor != NULL) + { + if (elem_cursor->end <= elem_cursor->pos) { + elem_cursor = elem_cursor->next; + continue; + } + + int startBlock = document->findBlock(elem_cursor->pos).blockNumber(); + int endBlock = document->findBlock(elem_cursor->end).blockNumber(); + for (int i = startBlock; i <= endBlock; ++i) { + imageBlocks.insert(i); + } + + elem_cursor = elem_cursor->next; + } + } + emit imageBlocksUpdated(imageBlocks); +} + void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex) { int startBlockNum = document->findBlock(pos).blockNumber(); diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index f9f4388e..6823b502 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -13,6 +13,7 @@ #include #include #include +#include extern "C" { #include @@ -58,6 +59,7 @@ public: signals: void highlightCompleted(); + void imageBlocksUpdated(QSet p_blocks); protected: void highlightBlock(const QString &text) Q_DECL_OVERRIDE; @@ -74,6 +76,8 @@ private: QTextDocument *document; QVector highlightingStyles; QVector > blockHighlights; + // Block numbers containing image link(s). + QSet imageBlocks; QAtomicInt parsing; QTimer *timer; int waitInterval; @@ -81,15 +85,17 @@ private: char *content; int capacity; pmh_element **result; - static const int initCapacity; - void resizeBuffer(int newCap); + static const int initCapacity; + + void resizeBuffer(int newCap); void highlightCodeBlock(const QString &text); void parse(); void parseInternal(); void initBlockHighlightFromResult(int nrBlocks); void initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex); + void updateImageBlocks(); }; #endif diff --git a/src/main.cpp b/src/main.cpp index fc8cc968..ae55733f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ void VLogger(QtMsgType type, const QMessageLogContext &context, const QString &m int main(int argc, char *argv[]) { - qInstallMessageHandler(VLogger); + //qInstallMessageHandler(VLogger); VSingleInstanceGuard guard; if (!guard.tryRun()) { diff --git a/src/vedittab.cpp b/src/vedittab.cpp index 34953b5f..e722950a 100644 --- a/src/vedittab.cpp +++ b/src/vedittab.cpp @@ -50,6 +50,8 @@ void VEditTab::setupUI() m_textEditor = new VMdEdit(m_file, this); connect(dynamic_cast(m_textEditor), &VMdEdit::headersChanged, this, &VEditTab::updateTocFromHeaders); + connect(dynamic_cast(m_textEditor), &VMdEdit::statusChanged, + this, &VEditTab::noticeStatusChanged); connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)), this, SLOT(updateCurHeader(int, int))); connect(m_textEditor, &VEdit::textChanged, diff --git a/src/vedittab.h b/src/vedittab.h index a1129a48..91d40858 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -52,6 +52,7 @@ private slots: void updateCurHeader(int p_lineNumber, int p_outlineIndex); void updateTocFromHeaders(const QVector &headers); void handleTextChanged(); + void noticeStatusChanged(); private: void setupUI(); @@ -62,7 +63,6 @@ private: inline bool isChild(QObject *obj); void parseTocUl(QXmlStreamReader &xml, QVector &headers, int level); void parseTocLi(QXmlStreamReader &xml, QVector &headers, int level); - void noticeStatusChanged(); void scrollPreviewToHeader(int p_outlineIndex); QPointer m_file; diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index d4e967b2..bdebf506 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -9,6 +9,8 @@ extern VConfigManager vconfig; +enum ImageProperty { ImagePath = 1 }; + VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent) : VEdit(p_file, p_parent), m_mdHighlighter(NULL) { @@ -19,6 +21,8 @@ VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent) 500, document()); connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted, this, &VMdEdit::generateEditOutline); + connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated, + this, &VMdEdit::updateImageBlocks); m_editOps = new VMdEditOperations(this, m_file); connect(this, &VMdEdit::cursorPositionChanged, @@ -53,7 +57,7 @@ void VMdEdit::beginEdit() setFont(vconfig.getMdEditFont()); - Q_ASSERT(m_file->getContent() == toPlainText()); + Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg()); initInitImages(); @@ -75,13 +79,15 @@ void VMdEdit::saveFile() if (!document()->isModified()) { return; } - m_file->setContent(toPlainText()); + m_file->setContent(toPlainTextWithoutImg()); document()->setModified(false); } void VMdEdit::reloadFile() { - setPlainText(m_file->getContent()); + QString &content = m_file->getContent(); + Q_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1); + setPlainText(content); setModified(false); } @@ -223,3 +229,208 @@ void VMdEdit::scrollToHeader(int p_headerIndex) scrollToLine(line); } } + +void VMdEdit::updateImageBlocks(QSet p_imageBlocks) +{ + // We need to handle blocks backward to avoid shifting all the following blocks. + // Inserting the preview image block may cause highlighter to emit signal again. + QList blockList = p_imageBlocks.toList(); + std::sort(blockList.begin(), blockList.end(), std::greater()); + auto it = blockList.begin(); + while (it != blockList.end()) { + previewImageOfBlock(*it); + ++it; + } + + // Clean up un-referenced QChar::ObjectReplacementCharacter. + clearOrphanImagePreviewBlock(); + + emit statusChanged(); +} + +void VMdEdit::clearOrphanImagePreviewBlock() +{ + QTextDocument *doc = document(); + QTextBlock block = doc->begin(); + while (block.isValid()) { + if (isOrphanImagePreviewBlock(block)) { + qDebug() << "remove orphan image preview block" << block.blockNumber(); + QTextBlock nextBlock = block.next(); + removeBlock(block); + block = nextBlock; + } else { + block = block.next(); + } + } +} + +bool VMdEdit::isOrphanImagePreviewBlock(QTextBlock p_block) +{ + if (isImagePreviewBlock(p_block)) { + // It is an orphan image preview block if previous block is not + // a block need to preview (containing exactly one image). + QTextBlock prevBlock = p_block.previous(); + if (prevBlock.isValid()) { + if (fetchImageToPreview(prevBlock.text()).isEmpty()) { + return true; + } else { + return false; + } + } else { + return true; + } + } + return false; +} + +QString VMdEdit::fetchImageToPreview(const QString &p_text) +{ + QRegExp regExp("\\!\\[[^\\]]*\\]\\((images/[^/\\)]+)\\)"); + int index = regExp.indexIn(p_text); + if (index == -1) { + return QString(); + } + int lastIndex = regExp.lastIndexIn(p_text); + if (lastIndex != index) { + return QString(); + } + return regExp.capturedTexts()[1]; +} + +void VMdEdit::previewImageOfBlock(int p_block) +{ + QTextDocument *doc = document(); + QTextBlock block = doc->findBlockByNumber(p_block); + if (!block.isValid()) { + return; + } + + QString text = block.text(); + QString imageLink = fetchImageToPreview(text); + if (imageLink.isEmpty()) { + return; + } + QString imagePath = QDir(m_file->retriveBasePath()).filePath(imageLink); + qDebug() << "block" << p_block << "image" << imagePath; + + if (isImagePreviewBlock(p_block + 1)) { + updateImagePreviewBlock(p_block + 1, imagePath); + return; + } + insertImagePreviewBlock(p_block, imagePath); +} + +bool VMdEdit::isImagePreviewBlock(int p_block) +{ + QTextDocument *doc = document(); + QTextBlock block = doc->findBlockByNumber(p_block); + if (!block.isValid()) { + return false; + } + QString text = block.text().trimmed(); + return text == QString(QChar::ObjectReplacementCharacter); +} + +bool VMdEdit::isImagePreviewBlock(QTextBlock p_block) +{ + if (!p_block.isValid()) { + return false; + } + QString text = p_block.text().trimmed(); + return text == QString(QChar::ObjectReplacementCharacter); +} + +void VMdEdit::insertImagePreviewBlock(int p_block, const QString &p_image) +{ + QTextDocument *doc = document(); + + QImage image(p_image); + if (image.isNull()) { + return; + } + + // Store current status. + bool modified = isModified(); + int pos = textCursor().position(); + + QTextCursor cursor(doc->findBlockByNumber(p_block)); + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.insertBlock(); + + QTextImageFormat imgFormat; + imgFormat.setName(p_image); + imgFormat.setProperty(ImagePath, p_image); + cursor.insertImage(imgFormat); + Q_ASSERT(cursor.block().text().at(0) == QChar::ObjectReplacementCharacter); + cursor.endEditBlock(); + + QTextCursor tmp = textCursor(); + tmp.setPosition(pos); + setTextCursor(tmp); + setModified(modified); + emit statusChanged(); +} + +void VMdEdit::updateImagePreviewBlock(int p_block, const QString &p_image) +{ + Q_ASSERT(isImagePreviewBlock(p_block)); + QTextDocument *doc = document(); + QTextBlock block = doc->findBlockByNumber(p_block); + if (!block.isValid()) { + return; + } + QTextCursor cursor(block); + QTextImageFormat format = cursor.charFormat().toImageFormat(); + Q_ASSERT(format.isValid()); + QString curPath = format.property(ImagePath).toString(); + + if (curPath == p_image) { + return; + } + // Update it with the new image. + QImage image(p_image); + if (image.isNull()) { + // Delete current preview block. + removeBlock(block); + qDebug() << "remove invalid image in block" << p_block; + return; + } + format.setName(p_image); + qDebug() << "update block" << p_block << "to image" << p_image; +} + +void VMdEdit::removeBlock(QTextBlock p_block) +{ + QTextCursor cursor(p_block); + cursor.select(QTextCursor::BlockUnderCursor); + cursor.removeSelectedText(); +} + +QString VMdEdit::toPlainTextWithoutImg() const +{ + QString text = toPlainText(); + int start = 0; + do { + int index = text.indexOf(QChar::ObjectReplacementCharacter, start); + if (index == -1) { + break; + } + start = removeObjectReplacementLine(text, index); + } while (start < text.size()); + qDebug() << text; + return text; +} + +int VMdEdit::removeObjectReplacementLine(QString &p_text, int p_index) const +{ + Q_ASSERT(p_text.size() > p_index && p_text.at(p_index) == QChar::ObjectReplacementCharacter); + Q_ASSERT(p_text.at(p_index + 1) == '\n'); + int prevLineIdx = p_text.lastIndexOf('\n', p_index); + if (prevLineIdx == -1) { + prevLineIdx = 0; + } + // Remove \n[....?\n] + p_text.remove(prevLineIdx + 1, p_index - prevLineIdx + 1); + return prevLineIdx; +} diff --git a/src/vmdedit.h b/src/vmdedit.h index 79ba6b1c..78968af6 100644 --- a/src/vmdedit.h +++ b/src/vmdedit.h @@ -23,14 +23,19 @@ public: // Scroll to m_headers[p_headerIndex]. void scrollToHeader(int p_headerIndex); + // Like toPlainText(), but remove special blocks containing images. + QString toPlainTextWithoutImg() const; signals: void headersChanged(const QVector &headers); void curHeaderChanged(int p_lineNumber, int p_outlineIndex); + void statusChanged(); private slots: void generateEditOutline(); void updateCurHeader(); + // Update block list containing image links. + void updateImageBlocks(QSet p_imageBlocks); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; @@ -42,6 +47,23 @@ private: void updateTabSettings(); void initInitImages(); void clearUnusedImages(); + // p_text[p_index] is QChar::ObjectReplacementCharacter. Remove the line containing it. + // Returns the index of previous line's '\n'. + int removeObjectReplacementLine(QString &p_text, int p_index) const; + void previewImageOfBlock(int p_block); + bool isImagePreviewBlock(int p_block); + bool isImagePreviewBlock(QTextBlock p_block); + // p_block is a image preview block. We need to update it with image. + void updateImagePreviewBlock(int p_block, const QString &p_image); + // Insert a block after @p_block to preview image @p_image. + void insertImagePreviewBlock(int p_block, const QString &p_image); + // Clean up un-referenced image preview block. + void clearOrphanImagePreviewBlock(); + void removeBlock(QTextBlock p_block); + bool isOrphanImagePreviewBlock(QTextBlock p_block); + // Returns the image relative path (image/xxx.png) only when + // there is one and only one image link. + QString fetchImageToPreview(const QString &p_text); HGMarkdownHighlighter *m_mdHighlighter; QVector m_insertedImages; @@ -49,7 +71,6 @@ private: bool m_expandTab; QString m_tabSpaces; QVector m_headers; - }; #endif // VMDEDIT_H