Browse Source

preview image links

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 years ago
parent
commit
a22bf1059d
7 changed files with 278 additions and 7 deletions
  1. 31 0
      src/hgmarkdownhighlighter.cpp
  2. 7 1
      src/hgmarkdownhighlighter.h
  3. 1 1
      src/main.cpp
  4. 2 0
      src/vedittab.cpp
  5. 1 1
      src/vedittab.h
  6. 214 3
      src/vmdedit.cpp
  7. 22 1
      src/vmdedit.h

+ 31 - 0
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();

+ 7 - 1
src/hgmarkdownhighlighter.h

@@ -13,6 +13,7 @@
 #include <QTextCharFormat>
 #include <QSyntaxHighlighter>
 #include <QAtomicInt>
+#include <QSet>
 
 extern "C" {
 #include <pmh_parser.h>
@@ -58,6 +59,7 @@ public:
 
 signals:
     void highlightCompleted();
+    void imageBlocksUpdated(QSet<int> p_blocks);
 
 protected:
     void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
@@ -74,6 +76,8 @@ private:
     QTextDocument *document;
     QVector<HighlightingStyle> highlightingStyles;
     QVector<QVector<HLUnit> > blockHighlights;
+    // Block numbers containing image link(s).
+    QSet<int> 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);
 
+    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

+ 1 - 1
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()) {

+ 2 - 0
src/vedittab.cpp

@@ -50,6 +50,8 @@ void VEditTab::setupUI()
         m_textEditor = new VMdEdit(m_file, this);
         connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
                 this, &VEditTab::updateTocFromHeaders);
+        connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::statusChanged,
+                this, &VEditTab::noticeStatusChanged);
         connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)),
                 this, SLOT(updateCurHeader(int, int)));
         connect(m_textEditor, &VEdit::textChanged,

+ 1 - 1
src/vedittab.h

@@ -52,6 +52,7 @@ private slots:
     void updateCurHeader(int p_lineNumber, int p_outlineIndex);
     void updateTocFromHeaders(const QVector<VHeader> &headers);
     void handleTextChanged();
+    void noticeStatusChanged();
 
 private:
     void setupUI();
@@ -62,7 +63,6 @@ private:
     inline bool isChild(QObject *obj);
     void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
     void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
-    void noticeStatusChanged();
     void scrollPreviewToHeader(int p_outlineIndex);
 
     QPointer<VFile> m_file;

+ 214 - 3
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<int> 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<int> blockList = p_imageBlocks.toList();
+    std::sort(blockList.begin(), blockList.end(), std::greater<int>());
+    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;
+}

+ 22 - 1
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<VHeader> &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<int> 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<QString> m_insertedImages;
@@ -49,7 +71,6 @@ private:
     bool m_expandTab;
     QString m_tabSpaces;
     QVector<VHeader> m_headers;
-
 };
 
 #endif // VMDEDIT_H