Browse Source

refactor preview logics

Use block user data to store preview info.
Le Tan 8 years ago
parent
commit
4ec340a403

+ 5 - 25
src/hgmarkdownhighlighter.cpp

@@ -93,38 +93,18 @@ HGMarkdownHighlighter::~HGMarkdownHighlighter()
 
 void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
 {
+    Q_UNUSED(p_text);
+
     VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(currentBlockUserData());
     if (!blockData) {
         blockData = new VTextBlockData();
         setCurrentBlockUserData(blockData);
     }
 
-    bool contains = p_text.contains(QChar::ObjectReplacementCharacter);
-    blockData->setContainsPreviewImage(contains);
-
-    auto curIt = m_potentialPreviewBlocks.find(p_blockNum);
-    if (curIt == m_potentialPreviewBlocks.end()) {
-        if (contains) {
-            m_potentialPreviewBlocks.insert(p_blockNum, true);
-        }
+    if (blockData->getPreviews().isEmpty()) {
+        m_possiblePreviewBlocks.remove(p_blockNum);
     } else {
-        if (!contains) {
-            m_potentialPreviewBlocks.erase(curIt);
-        }
-    }
-
-    // Delete elements beyond current block count.
-    int blocks = document->blockCount();
-    if (!m_potentialPreviewBlocks.isEmpty()) {
-        auto it = m_potentialPreviewBlocks.end();
-        do {
-            --it;
-            if (it.key() >= blocks) {
-                it = m_potentialPreviewBlocks.erase(it);
-            } else {
-                break;
-            }
-        } while (it != m_potentialPreviewBlocks.begin());
+        m_possiblePreviewBlocks.insert(p_blockNum);
     }
 }
 

+ 18 - 10
src/hgmarkdownhighlighter.h

@@ -5,8 +5,8 @@
 #include <QSyntaxHighlighter>
 #include <QAtomicInt>
 #include <QMap>
+#include <QSet>
 #include <QString>
-#include <QHash>
 
 extern "C" {
 #include <pmh_parser.h>
@@ -124,6 +124,10 @@ public:
 
     const QVector<VElementRegion> &getHeaderRegions() const;
 
+    const QSet<int> &getPossiblePreviewBlocks() const;
+
+    void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
+
 signals:
     void highlightCompleted();
 
@@ -167,11 +171,6 @@ private:
 
     int m_numOfCodeBlockHighlightsToRecv;
 
-    // If the ith block contains preview, then the set contains i.
-    // If the set contains i, the ith block may contain preview.
-    // We just use the key.
-    QMap<int, bool> m_potentialPreviewBlocks;
-
     // All HTML comment regions.
     QVector<VElementRegion> m_commentRegions;
 
@@ -193,6 +192,9 @@ private:
     // Whether this is the first parse.
     bool m_firstParse;
 
+    // Block number of those blocks which possible contains previewed image.
+    QSet<int> m_possiblePreviewBlocks;
+
     char *content;
     int capacity;
     pmh_element **result;
@@ -247,14 +249,20 @@ private:
     bool isValidHeader(unsigned long p_pos, unsigned long p_end);
 };
 
-inline const QMap<int, bool> &HGMarkdownHighlighter::getPotentialPreviewBlocks() const
+inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const
 {
-    return m_potentialPreviewBlocks;
+    return m_headerRegions;
 }
 
-inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const
+inline const QSet<int> &HGMarkdownHighlighter::getPossiblePreviewBlocks() const
 {
-    return m_headerRegions;
+    return m_possiblePreviewBlocks;
 }
 
+inline void HGMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear)
+{
+    for (auto i : p_blocksToClear) {
+        m_possiblePreviewBlocks.remove(i);
+    }
+}
 #endif

+ 0 - 2
src/src.pro

@@ -61,7 +61,6 @@ SOURCES += main.cpp\
     vorphanfile.cpp \
     vcodeblockhighlighthelper.cpp \
     vwebview.cpp \
-    vimagepreviewer.cpp \
     vexporter.cpp \
     vmdtab.cpp \
     vhtmltab.cpp \
@@ -146,7 +145,6 @@ HEADERS  += vmainwindow.h \
     vorphanfile.h \
     vcodeblockhighlighthelper.h \
     vwebview.h \
-    vimagepreviewer.h \
     vexporter.h \
     vmdtab.h \
     vhtmltab.h \

+ 0 - 660
src/vimagepreviewer.cpp

@@ -1,660 +0,0 @@
-#include "vimagepreviewer.h"
-
-#include <QTimer>
-#include <QTextDocument>
-#include <QDebug>
-#include <QDir>
-#include <QUrl>
-#include <QVector>
-#include "vmdedit.h"
-#include "vconfigmanager.h"
-#include "utils/vutils.h"
-#include "utils/veditutils.h"
-#include "utils/vpreviewutils.h"
-#include "vfile.h"
-#include "vdownloader.h"
-#include "hgmarkdownhighlighter.h"
-#include "vtextblockdata.h"
-
-extern VConfigManager *g_config;
-
-const int VImagePreviewer::c_minImageWidth = 100;
-
-VImagePreviewer::VImagePreviewer(VMdEdit *p_edit, const HGMarkdownHighlighter *p_highlighter)
-    : QObject(p_edit), m_edit(p_edit), m_document(p_edit->document()),
-      m_file(p_edit->getFile()), m_highlighter(p_highlighter),
-      m_imageWidth(c_minImageWidth), m_timeStamp(0), m_previewIndex(0),
-      m_previewEnabled(g_config->getEnablePreviewImages()), m_isPreviewing(false)
-{
-    m_updateTimer = new QTimer(this);
-    m_updateTimer->setSingleShot(true);
-    m_updateTimer->setInterval(400);
-    connect(m_updateTimer, &QTimer::timeout,
-            this, &VImagePreviewer::doUpdatePreviewImageWidth);
-
-    m_downloader = new VDownloader(this);
-    connect(m_downloader, &VDownloader::downloadFinished,
-            this, &VImagePreviewer::imageDownloaded);
-}
-
-void VImagePreviewer::imageLinksChanged(const QVector<VElementRegion> &p_imageRegions)
-{
-    kickOffPreview(p_imageRegions);
-}
-
-void VImagePreviewer::kickOffPreview(const QVector<VElementRegion> &p_imageRegions)
-{
-    if (!m_previewEnabled) {
-        Q_ASSERT(m_imageRegions.isEmpty());
-        Q_ASSERT(m_previewImages.isEmpty());
-        Q_ASSERT(m_imageCache.isEmpty());
-        emit previewFinished();
-        return;
-    }
-
-    m_isPreviewing = true;
-
-    m_imageRegions = p_imageRegions;
-    ++m_timeStamp;
-
-    previewImages();
-
-    shrinkImageCache();
-    m_isPreviewing = false;
-
-    emit previewFinished();
-}
-
-void VImagePreviewer::previewImages()
-{
-    // Get the width of the m_edit.
-    m_imageWidth = qMax(m_edit->size().width() - 50, c_minImageWidth);
-
-    QVector<ImageLinkInfo> imageLinks;
-    fetchImageLinksFromRegions(imageLinks);
-
-    QTextCursor cursor(m_document);
-    previewImageLinks(imageLinks, cursor);
-    clearObsoletePreviewImages(cursor);
-}
-
-void VImagePreviewer::initImageFormat(QTextImageFormat &p_imgFormat,
-                                      const QString &p_imageName,
-                                      const PreviewImageInfo &p_info) const
-{
-    p_imgFormat.setName(p_imageName);
-    p_imgFormat.setProperty((int)ImageProperty::ImageID, p_info.m_id);
-    p_imgFormat.setProperty((int)ImageProperty::ImageSource, (int)PreviewImageSource::Image);
-    p_imgFormat.setProperty((int)ImageProperty::ImageType,
-                            p_info.m_isBlock ? (int)PreviewImageType::Block
-                                             : (int)PreviewImageType::Inline);
-}
-
-void VImagePreviewer::previewImageLinks(QVector<ImageLinkInfo> &p_imageLinks,
-                                        QTextCursor &p_cursor)
-{
-    bool hasNewPreview = false;
-    EditStatus status;
-    for (int i = 0; i < p_imageLinks.size(); ++i) {
-        ImageLinkInfo &link = p_imageLinks[i];
-        if (link.m_previewImageID > -1) {
-            continue;
-        }
-
-        QString imageName = imageCacheResourceName(link.m_linkUrl);
-        if (imageName.isEmpty()) {
-            continue;
-        }
-
-        PreviewImageInfo info(m_previewIndex++, m_timeStamp,
-                              link.m_linkUrl, link.m_isBlock);
-        QTextImageFormat imgFormat;
-        initImageFormat(imgFormat, imageName, info);
-
-        updateImageWidth(imgFormat);
-
-        saveEditStatus(status);
-        p_cursor.joinPreviousEditBlock();
-        p_cursor.setPosition(link.m_endPos);
-        if (link.m_isBlock) {
-            p_cursor.movePosition(QTextCursor::EndOfBlock);
-            VEditUtils::insertBlockWithIndent(p_cursor);
-        }
-
-        p_cursor.insertImage(imgFormat);
-        p_cursor.endEditBlock();
-
-        restoreEditStatus(status);
-
-        Q_ASSERT(!m_previewImages.contains(info.m_id));
-        m_previewImages.insert(info.m_id, info);
-        link.m_previewImageID = info.m_id;
-
-        hasNewPreview = true;
-        qDebug() << "preview new image" << info.toString();
-    }
-
-    if (hasNewPreview) {
-        emit m_edit->statusChanged();
-    }
-}
-
-void VImagePreviewer::clearObsoletePreviewImages(QTextCursor &p_cursor)
-{
-    // Clean up the hash.
-    for (auto it = m_previewImages.begin(); it != m_previewImages.end();) {
-        PreviewImageInfo &info = it.value();
-        if (info.m_timeStamp != m_timeStamp) {
-            qDebug() << "obsolete preview image" << info.toString();
-            it = m_previewImages.erase(it);
-        } else {
-            ++it;
-        }
-    }
-
-    // Clean block data and delete obsolete preview.
-    bool hasObsolete = false;
-    int blockCount = m_document->blockCount();
-    // Must copy it.
-    QMap<int, bool> potentialBlocks = m_highlighter->getPotentialPreviewBlocks();
-    // From back to front.
-    if (!potentialBlocks.isEmpty()) {
-        auto it = potentialBlocks.end();
-        do {
-            --it;
-            int blockNum = it.key();
-            if (blockNum >= blockCount) {
-                continue;
-            }
-
-            QTextBlock block = m_document->findBlockByNumber(blockNum);
-            if (block.isValid()
-                && VTextBlockData::containsPreviewImage(block)) {
-                // Notice the short circuit.
-                hasObsolete = clearObsoletePreviewImagesOfBlock(block, p_cursor) || hasObsolete;
-            }
-        } while (it != potentialBlocks.begin());
-    }
-
-    if (hasObsolete) {
-        emit m_edit->statusChanged();
-    }
-}
-
-bool VImagePreviewer::isImageSourcePreviewImage(const QTextImageFormat &p_format) const
-{
-    if (!p_format.isValid()) {
-        return false;
-    }
-
-    bool ok = true;
-    int src = p_format.property((int)ImageProperty::ImageSource).toInt(&ok);
-    if (ok) {
-        return src == (int)PreviewImageSource::Image;
-    } else {
-        return false;
-    }
-}
-
-bool VImagePreviewer::clearObsoletePreviewImagesOfBlock(QTextBlock &p_block,
-                                                        QTextCursor &p_cursor)
-{
-    QString text = p_block.text();
-    bool hasObsolete = false;
-    bool hasOtherChars = false;
-    bool hasValidPreview = false;
-    EditStatus status;
-
-    // From back to front.
-    for (int i = text.size() - 1; i >= 0; --i) {
-        if (text[i].isSpace()) {
-            continue;
-        }
-
-        if (text[i] == QChar::ObjectReplacementCharacter) {
-            int pos = p_block.position() + i;
-            Q_ASSERT(m_document->characterAt(pos) == QChar::ObjectReplacementCharacter);
-
-            QTextImageFormat imageFormat = VPreviewUtils::fetchFormatFromPosition(m_document, pos);
-            if (!isImageSourcePreviewImage(imageFormat)) {
-                hasValidPreview = true;
-                continue;
-            }
-
-            long long imageID = VPreviewUtils::getPreviewImageID(imageFormat);
-            auto it = m_previewImages.find(imageID);
-            if (it == m_previewImages.end()) {
-                // It is obsolete since we can't find it in the cache.
-                qDebug() << "remove obsolete preview image" << imageID;
-                saveEditStatus(status);
-                p_cursor.joinPreviousEditBlock();
-                p_cursor.setPosition(pos);
-                p_cursor.deleteChar();
-                p_cursor.endEditBlock();
-                restoreEditStatus(status);
-                hasObsolete = true;
-            } else {
-                hasValidPreview = true;
-            }
-        } else {
-            hasOtherChars = true;
-        }
-    }
-
-    if (hasObsolete && !hasOtherChars && !hasValidPreview) {
-        // Delete the whole block.
-        qDebug() << "delete a preview block" << p_block.blockNumber();
-        saveEditStatus(status);
-        p_cursor.joinPreviousEditBlock();
-        p_cursor.setPosition(p_block.position());
-        VEditUtils::removeBlock(p_cursor);
-        p_cursor.endEditBlock();
-        restoreEditStatus(status);
-    }
-
-    return hasObsolete;
-}
-
-// Returns true if p_text[p_start, p_end) is all spaces or QChar::ObjectReplacementCharacter.
-static bool isAllSpacesOrObject(const QString &p_text, int p_start, int p_end)
-{
-    for (int i = p_start; i < p_end && i < p_text.size(); ++i) {
-        if (!p_text[i].isSpace() && p_text[i] != QChar::ObjectReplacementCharacter) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-void VImagePreviewer::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks)
-{
-    p_imageLinks.clear();
-
-    if (m_imageRegions.isEmpty()) {
-        return;
-    }
-
-    p_imageLinks.reserve(m_imageRegions.size());
-
-    for (int i = 0; i < m_imageRegions.size(); ++i) {
-        VElementRegion &reg = m_imageRegions[i];
-        QTextBlock block = m_document->findBlock(reg.m_startPos);
-        if (!block.isValid()) {
-            continue;
-        }
-
-        int blockStart = block.position();
-        int blockEnd = blockStart + block.length() - 1;
-        QString text = block.text();
-        Q_ASSERT(reg.m_endPos <= blockEnd);
-        ImageLinkInfo info(reg.m_startPos, reg.m_endPos);
-        if ((reg.m_startPos == blockStart
-             || isAllSpacesOrObject(text, 0, reg.m_startPos - blockStart))
-            && (reg.m_endPos == blockEnd
-                || isAllSpacesOrObject(text, reg.m_endPos - blockStart, blockEnd - blockStart))) {
-            // Image block.
-            info.m_isBlock = true;
-            info.m_linkUrl = fetchImagePathToPreview(text);
-        } else {
-            // Inline image.
-            info.m_isBlock = false;
-            info.m_linkUrl = fetchImagePathToPreview(text.mid(reg.m_startPos - blockStart,
-                                                              reg.m_endPos - reg.m_startPos));
-        }
-
-        if (info.m_linkUrl.isEmpty()) {
-            continue;
-        }
-
-        // Check if this image link has been previewed previously.
-        info.m_previewImageID = isImageLinkPreviewed(info);
-
-        // Sorted in descending order of m_startPos.
-        p_imageLinks.append(info);
-
-        qDebug() << "image region" << i << info.m_startPos << info.m_endPos
-                 << info.m_linkUrl << info.m_isBlock << info.m_previewImageID;
-    }
-}
-
-long long VImagePreviewer::isImageLinkPreviewed(const ImageLinkInfo &p_info)
-{
-    long long imageID = -1;
-    if (p_info.m_isBlock) {
-        QTextBlock block = m_document->findBlock(p_info.m_startPos);
-        QTextBlock nextBlock = block.next();
-        if (!nextBlock.isValid()) {
-            return imageID;
-        }
-
-        if (!isImagePreviewBlock(nextBlock)) {
-            return imageID;
-        }
-
-        // Make sure the indentation is the same as @block.
-        if (VEditUtils::hasSameIndent(block, nextBlock)) {
-            QTextImageFormat format = fetchFormatFromPreviewBlock(nextBlock);
-            if (isImageSourcePreviewImage(format)) {
-                imageID = VPreviewUtils::getPreviewImageID(format);
-            }
-        }
-    } else {
-        QTextImageFormat format = VPreviewUtils::fetchFormatFromPosition(m_document, p_info.m_endPos);
-        if (isImageSourcePreviewImage(format)) {
-            imageID = VPreviewUtils::getPreviewImageID(format);
-        }
-    }
-
-    if (imageID != -1) {
-        auto it = m_previewImages.find(imageID);
-        if (it != m_previewImages.end()) {
-            PreviewImageInfo &img = it.value();
-            if (img.m_path == p_info.m_linkUrl
-                && img.m_isBlock == p_info.m_isBlock) {
-                img.m_timeStamp = m_timeStamp;
-            } else {
-                imageID = -1;
-            }
-        } else {
-            // This preview image does not exist in the cache, which means it may
-            // be deleted before but added back by user's undo action.
-            // We treat it an obsolete preview image.
-            imageID = -1;
-        }
-    }
-
-    return imageID;
-}
-
-bool VImagePreviewer::isImagePreviewBlock(const QTextBlock &p_block)
-{
-    if (!p_block.isValid()) {
-        return false;
-    }
-
-    QString text = p_block.text().trimmed();
-    return text == QString(QChar::ObjectReplacementCharacter);
-}
-
-QString VImagePreviewer::fetchImageUrlToPreview(const QString &p_text)
-{
-    QRegExp regExp(VUtils::c_imageLinkRegExp);
-
-    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()[2].trimmed();
-}
-
-QString VImagePreviewer::fetchImagePathToPreview(const QString &p_text)
-{
-    QString imageUrl = fetchImageUrlToPreview(p_text);
-    if (imageUrl.isEmpty()) {
-        return imageUrl;
-    }
-
-    QString imagePath;
-    QFileInfo info(m_file->fetchBasePath(), imageUrl);
-
-    if (info.exists()) {
-        if (info.isNativePath()) {
-            // Local file.
-            imagePath = QDir::cleanPath(info.absoluteFilePath());
-        } else {
-            imagePath = imageUrl;
-        }
-    } else {
-        QString decodedUrl(imageUrl);
-        VUtils::decodeUrl(decodedUrl);
-        QFileInfo dinfo(m_file->fetchBasePath(), decodedUrl);
-        if (dinfo.exists()) {
-            if (dinfo.isNativePath()) {
-                // Local file.
-                imagePath = QDir::cleanPath(dinfo.absoluteFilePath());
-            } else {
-                imagePath = imageUrl;
-            }
-        } else {
-            QUrl url(imageUrl);
-            imagePath = url.toString();
-        }
-    }
-
-    return imagePath;
-}
-
-void VImagePreviewer::clearAllPreviewImages()
-{
-    m_imageRegions.clear();
-    ++m_timeStamp;
-
-    QTextCursor cursor(m_document);
-    clearObsoletePreviewImages(cursor);
-
-    m_imageCache.clear();
-}
-
-QTextImageFormat VImagePreviewer::fetchFormatFromPreviewBlock(const QTextBlock &p_block) const
-{
-    QTextCursor cursor(p_block);
-    int shift = p_block.text().indexOf(QChar::ObjectReplacementCharacter);
-    if (shift >= 0) {
-        cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, shift + 1);
-    } else {
-        return QTextImageFormat();
-    }
-
-    return cursor.charFormat().toImageFormat();
-}
-
-QString VImagePreviewer::imageCacheResourceName(const QString &p_imagePath)
-{
-    V_ASSERT(!p_imagePath.isEmpty());
-
-    auto it = m_imageCache.find(p_imagePath);
-    if (it != m_imageCache.end()) {
-        return it.value().m_name;
-    }
-
-    // Add it to the resource cache even if it may exist there.
-    QFileInfo info(p_imagePath);
-    QImage image;
-    if (info.exists()) {
-        // Local file.
-        image = QImage(p_imagePath);
-    } else {
-        // URL. Try to download it.
-        m_downloader->download(p_imagePath);
-    }
-
-    if (image.isNull()) {
-        return QString();
-    }
-
-    QString name(imagePathToCacheResourceName(p_imagePath));
-    m_document->addResource(QTextDocument::ImageResource, name, image);
-    m_imageCache.insert(p_imagePath, ImageInfo(name, image.width()));
-
-    return name;
-}
-
-QString VImagePreviewer::imagePathToCacheResourceName(const QString &p_imagePath)
-{
-    return p_imagePath;
-}
-
-void VImagePreviewer::imageDownloaded(const QByteArray &p_data, const QString &p_url)
-{
-    QImage image(QImage::fromData(p_data));
-
-    if (!image.isNull()) {
-        auto it = m_imageCache.find(p_url);
-        if (it != m_imageCache.end()) {
-            return;
-        }
-
-        QString name(imagePathToCacheResourceName(p_url));
-        m_document->addResource(QTextDocument::ImageResource, name, image);
-        m_imageCache.insert(p_url, ImageInfo(name, image.width()));
-
-        qDebug() << "downloaded image cache insert" << p_url << name;
-        emit requestUpdateImageLinks();
-    }
-}
-
-QImage VImagePreviewer::fetchCachedImageByID(long long p_id)
-{
-    auto imgIt = m_previewImages.find(p_id);
-    if (imgIt == m_previewImages.end()) {
-        return QImage();
-    }
-
-    QString path = imgIt->m_path;
-    if (path.isEmpty()) {
-        return QImage();
-    }
-
-    auto it = m_imageCache.find(path);
-    if (it == m_imageCache.end()) {
-        return QImage();
-    }
-
-    return m_document->resource(QTextDocument::ImageResource, it.value().m_name).value<QImage>();
-}
-
-bool VImagePreviewer::updateImageWidth(QTextImageFormat &p_format)
-{
-    long long imageID = VPreviewUtils::getPreviewImageID(p_format);
-    auto imgIt = m_previewImages.find(imageID);
-    if (imgIt == m_previewImages.end()) {
-        return false;
-    }
-
-    auto it = m_imageCache.find(imgIt->m_path);
-    if (it != m_imageCache.end()) {
-        int newWidth = it.value().m_width;
-        if (g_config->getEnablePreviewImageConstraint()) {
-            newWidth = qMin(m_imageWidth, newWidth);
-        }
-
-        if (newWidth != p_format.width()) {
-            p_format.setWidth(newWidth);
-            return true;
-        }
-    }
-
-    return false;
-}
-
-void VImagePreviewer::updatePreviewImageWidth()
-{
-    if (!m_previewEnabled) {
-        emit previewWidthUpdated();
-        return;
-    }
-
-    m_updateTimer->stop();
-    m_updateTimer->start();
-}
-
-void VImagePreviewer::doUpdatePreviewImageWidth()
-{
-    // Get the width of the m_edit.
-    m_imageWidth = qMax(m_edit->size().width() - 50, c_minImageWidth);
-
-    bool updated = false;
-    QTextBlock block = m_document->begin();
-    QTextCursor cursor(block);
-    while (block.isValid()) {
-        if (VTextBlockData::containsPreviewImage(block)) {
-            // Notice the short circuit.
-            updated = updatePreviewImageWidthOfBlock(block, cursor) || updated;
-        }
-
-        block = block.next();
-    }
-
-    if (updated) {
-        emit m_edit->statusChanged();
-    }
-
-    qDebug() << "update preview image width" << updated;
-
-    emit previewWidthUpdated();
-}
-
-bool VImagePreviewer::updatePreviewImageWidthOfBlock(const QTextBlock &p_block,
-                                                     QTextCursor &p_cursor)
-{
-    QString text = p_block.text();
-    bool updated = false;
-    EditStatus status;
-
-    // From back to front.
-    for (int i = text.size() - 1; i >= 0; --i) {
-        if (text[i].isSpace()) {
-            continue;
-        }
-
-        if (text[i] == QChar::ObjectReplacementCharacter) {
-            int pos = p_block.position() + i;
-            Q_ASSERT(m_document->characterAt(pos) == QChar::ObjectReplacementCharacter);
-
-            QTextImageFormat imageFormat = VPreviewUtils::fetchFormatFromPosition(m_document, pos);
-            if (imageFormat.isValid()
-                && isImageSourcePreviewImage(imageFormat)
-                && updateImageWidth(imageFormat)) {
-                saveEditStatus(status);
-                p_cursor.joinPreviousEditBlock();
-                p_cursor.setPosition(pos);
-                p_cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
-                Q_ASSERT(p_cursor.charFormat().toImageFormat().isValid());
-                p_cursor.setCharFormat(imageFormat);
-                p_cursor.endEditBlock();
-                restoreEditStatus(status);
-                updated = true;
-            }
-        }
-    }
-
-    return updated;
-}
-
-void VImagePreviewer::shrinkImageCache()
-{
-    const int MaxSize = 20;
-    if (m_imageCache.size() > m_previewImages.size()
-        && m_imageCache.size() > MaxSize) {
-        QHash<QString, bool> usedImagePath;
-        for (auto it = m_previewImages.begin(); it != m_previewImages.end(); ++it) {
-            usedImagePath.insert(it->m_path, true);
-        }
-
-        for (auto it = m_imageCache.begin(); it != m_imageCache.end();) {
-            if (!usedImagePath.contains(it.key())) {
-                qDebug() << "shrink one image" << it.key();
-                it = m_imageCache.erase(it);
-            } else {
-                ++it;
-            }
-        }
-    }
-}
-
-void VImagePreviewer::saveEditStatus(EditStatus &p_status) const
-{
-    p_status.m_modified = m_edit->isModified();
-}
-
-void VImagePreviewer::restoreEditStatus(const EditStatus &p_status)
-{
-    m_edit->setModified(p_status.m_modified);
-}

+ 0 - 250
src/vimagepreviewer.h

@@ -1,250 +0,0 @@
-#ifndef VIMAGEPREVIEWER_H
-#define VIMAGEPREVIEWER_H
-
-#include <QObject>
-#include <QString>
-#include <QTextBlock>
-#include <QHash>
-#include "hgmarkdownhighlighter.h"
-
-class QTimer;
-class VMdEdit;
-class QTextDocument;
-class VFile;
-class VDownloader;
-
-class VImagePreviewer : public QObject
-{
-    Q_OBJECT
-public:
-    explicit VImagePreviewer(VMdEdit *p_edit, const HGMarkdownHighlighter *p_highlighter);
-
-    // Whether @p_block is an image previewed block.
-    // The image previewed block is a block containing only the special character
-    // and whitespaces.
-    bool isImagePreviewBlock(const QTextBlock &p_block);
-
-    QImage fetchCachedImageByID(long long p_id);
-
-    // Update preview image width.
-    void updatePreviewImageWidth();
-
-    bool isPreviewing() const;
-
-    bool isEnabled() const;
-
-public slots:
-    // Image links have changed.
-    void imageLinksChanged(const QVector<VElementRegion> &p_imageRegions);
-
-private slots:
-    // Non-local image downloaded for preview.
-    void imageDownloaded(const QByteArray &p_data, const QString &p_url);
-
-    // Update preview image width right now.
-    void doUpdatePreviewImageWidth();
-
-signals:
-    // Request highlighter to update image links.
-    void requestUpdateImageLinks();
-
-    // Emit after finishing previewing.
-    void previewFinished();
-
-    // Emit after updating preview width.
-    void previewWidthUpdated();
-
-private:
-    struct ImageInfo
-    {
-        ImageInfo(const QString &p_name, int p_width)
-            : m_name(p_name), m_width(p_width)
-        {
-        }
-
-        QString m_name;
-        int m_width;
-    };
-
-    struct ImageLinkInfo
-    {
-        ImageLinkInfo()
-            : m_startPos(-1), m_endPos(-1),
-              m_isBlock(false), m_previewImageID(-1)
-        {
-        }
-
-        ImageLinkInfo(int p_startPos, int p_endPos)
-            : m_startPos(p_startPos), m_endPos(p_endPos),
-              m_isBlock(false), m_previewImageID(-1)
-        {
-        }
-
-        int m_startPos;
-        int m_endPos;
-        QString m_linkUrl;
-
-        // Whether it is an image block.
-        bool m_isBlock;
-
-        // The previewed image ID if this link has been previewed.
-        // -1 if this link has not yet been previewed.
-        long long m_previewImageID;
-    };
-
-    // Info about a previewed image.
-    struct PreviewImageInfo
-    {
-        PreviewImageInfo() : m_id(-1), m_timeStamp(-1)
-        {
-        }
-
-        PreviewImageInfo(long long p_id, long long p_timeStamp,
-                         const QString p_path, bool p_isBlock)
-            : m_id(p_id), m_timeStamp(p_timeStamp),
-              m_path(p_path), m_isBlock(p_isBlock)
-        {
-        }
-
-        QString toString()
-        {
-            return QString("PreviewImageInfo(ID %0 path %1 stamp %2 isBlock %3")
-                          .arg(m_id).arg(m_path).arg(m_timeStamp).arg(m_isBlock);
-        }
-
-        long long m_id;
-        long long m_timeStamp;
-        QString m_path;
-        bool m_isBlock;
-    };
-
-    // Status about the VMdEdit, used for restore.
-    struct EditStatus
-    {
-        EditStatus()
-            : m_modified(false)
-        {
-        }
-
-        bool m_modified;
-    };
-
-    // Kick off new preview of m_imageRegions.
-    void kickOffPreview(const QVector<VElementRegion> &p_imageRegions);
-
-    // Preview images according to m_timeStamp and m_imageRegions.
-    void previewImages();
-
-    // According to m_imageRegions, fetch the image link Url.
-    // Will check if this link has been previewed correctly and mark the previewed
-    // image with the newest timestamp.
-    // @p_imageLinks should be sorted in descending order of m_startPos.
-    void fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks);
-
-    // Preview not previewed image links in @p_imageLinks.
-    // Insert the preview block with same indentation with the link block.
-    // @p_imageLinks should be sorted in descending order of m_startPos.
-    void previewImageLinks(QVector<ImageLinkInfo> &p_imageLinks, QTextCursor &p_cursor);
-
-    // Clear obsolete preview images whose timeStamp does not match current one
-    // or does not exist in the cache.
-    void clearObsoletePreviewImages(QTextCursor &p_cursor);
-
-    // Clear obsolete preview image in @p_block.
-    // A preview image is obsolete if it is not in the cache.
-    // If it is a preview block, delete the whole block.
-    // @p_block: a block may contain multiple preview images;
-    // @p_cursor: cursor used to manipulate the text;
-    bool clearObsoletePreviewImagesOfBlock(QTextBlock &p_block, QTextCursor &p_cursor);
-
-    // Update the width of preview image in @p_block.
-    bool updatePreviewImageWidthOfBlock(const QTextBlock &p_block, QTextCursor &p_cursor);
-
-    // Check if there is a correct previewed image following the @p_info link.
-    // Returns the previewImageID if yes. Otherwise, returns -1.
-    long long isImageLinkPreviewed(const ImageLinkInfo &p_info);
-
-    // Fetch the image link's URL if there is only one link.
-    QString fetchImageUrlToPreview(const QString &p_text);
-
-    // Fetch teh image's full path if there is only one image link.
-    QString fetchImagePathToPreview(const QString &p_text);
-
-    // Clear all the previewed images.
-    void clearAllPreviewImages();
-
-    // Fetch the text image format from an image preview block.
-    QTextImageFormat fetchFormatFromPreviewBlock(const QTextBlock &p_block) const;
-
-    // Whether the preview image is Image source.
-    bool isImageSourcePreviewImage(const QTextImageFormat &p_format) const;
-
-    void initImageFormat(QTextImageFormat &p_imgFormat,
-                         const QString &p_imageName,
-                         const PreviewImageInfo &p_info) const;
-
-    // Look up m_imageCache to get the resource name in QTextDocument's cache.
-    // If there is none, insert it.
-    QString imageCacheResourceName(const QString &p_imagePath);
-
-    QString imagePathToCacheResourceName(const QString &p_imagePath);
-
-    // Return true if and only if there is update.
-    bool updateImageWidth(QTextImageFormat &p_format);
-
-    // Clean up image cache.
-    void shrinkImageCache();
-
-    void saveEditStatus(EditStatus &p_status) const;
-    void restoreEditStatus(const EditStatus &p_status);
-
-    VMdEdit *m_edit;
-    QTextDocument *m_document;
-    VFile *m_file;
-
-    const HGMarkdownHighlighter *m_highlighter;
-
-    // Map from image full path to QUrl identifier in the QTextDocument's cache.
-    QHash<QString, ImageInfo> m_imageCache;
-
-    VDownloader *m_downloader;
-
-    // The preview width.
-    int m_imageWidth;
-
-    // Used to denote the obsolete previewed images.
-    // Increased when a new preview is kicked off.
-    long long m_timeStamp;
-
-    // Incremental ID for previewed images.
-    long long m_previewIndex;
-
-    // Map from previewImageID to PreviewImageInfo.
-    QHash<long long, PreviewImageInfo> m_previewImages;
-
-    // Regions of all the image links.
-    QVector<VElementRegion> m_imageRegions;
-
-    // Timer for updatePreviewImageWidth().
-    QTimer *m_updateTimer;
-
-    // Whether preview is enabled.
-    bool m_previewEnabled;
-
-    // Whether preview is ongoing.
-    bool m_isPreviewing;
-
-    static const int c_minImageWidth;
-};
-
-inline bool VImagePreviewer::isPreviewing() const
-{
-    return m_isPreviewing;
-}
-
-inline bool VImagePreviewer::isEnabled() const
-{
-    return m_previewEnabled;
-}
-
-#endif // VIMAGEPREVIEWER_H

+ 5 - 80
src/vimageresourcemanager2.cpp

@@ -1,9 +1,5 @@
 #include "vimageresourcemanager2.h"
 
-#include <QDebug>
-
-#include "vtextedit.h"
-
 
 VImageResourceManager2::VImageResourceManager2()
 {
@@ -20,81 +16,6 @@ bool VImageResourceManager2::contains(const QString &p_name) const
     return m_images.contains(p_name);
 }
 
-QSet<int> VImageResourceManager2::updateBlockInfos(const QVector<VBlockImageInfo2> &p_blocksInfo)
-{
-    QSet<QString> usedImages;
-    QHash<int, QVector<VBlockImageInfo2>> newBlocksInfo;
-
-    for (auto const & info : p_blocksInfo) {
-        VBlockImageInfo2 *newInfo = NULL;
-        auto blockIt = newBlocksInfo.find(info.m_blockNumber);
-        if (blockIt == newBlocksInfo.end()) {
-            // New block.
-            QVector<VBlockImageInfo2> vec(1, info);
-            auto it = newBlocksInfo.insert(info.m_blockNumber, vec);
-            newInfo = &it.value().last();
-        } else {
-            // Multiple images for a block.
-            QVector<VBlockImageInfo2> &vec = blockIt.value();
-            int i;
-            for (i = 0; i < vec.size(); ++i) {
-                Q_ASSERT(vec[i].m_blockNumber == info.m_blockNumber);
-                if (info < vec[i]) {
-                    vec.insert(i, info);
-                    newInfo = &vec[i];
-                    break;
-                }
-            }
-
-            if (i == vec.size()) {
-                vec.append(info);
-                newInfo = &vec.last();
-            }
-        }
-
-        if (newInfo->m_padding < 0) {
-            newInfo->m_padding = 0;
-        }
-
-        auto imageIt = m_images.find(newInfo->m_imageName);
-        if (imageIt != m_images.end()) {
-            // Fill the width and height.
-            newInfo->m_imageSize = imageIt.value().size();
-            usedImages.insert(newInfo->m_imageName);
-        }
-    }
-
-    QSet<int> affectedBlocks;
-    if (m_blocksInfo != newBlocksInfo) {
-        affectedBlocks = QSet<int>::fromList(m_blocksInfo.keys());
-        affectedBlocks.unite(QSet<int>::fromList(newBlocksInfo.keys()));
-
-        m_blocksInfo = newBlocksInfo;
-
-        // Clear unused images.
-        for (auto it = m_images.begin(); it != m_images.end();) {
-            if (!usedImages.contains(it.key())) {
-                // Remove the image.
-                it = m_images.erase(it);
-            } else {
-                ++it;
-            }
-        }
-    }
-
-    return affectedBlocks;
-}
-
-const QVector<VBlockImageInfo2> *VImageResourceManager2::findImageInfoByBlock(int p_blockNumber) const
-{
-    auto it = m_blocksInfo.find(p_blockNumber);
-    if (it != m_blocksInfo.end()) {
-        return &it.value();
-    }
-
-    return NULL;
-}
-
 const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
 {
     auto it = m_images.find(p_name);
@@ -107,6 +28,10 @@ const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
 
 void VImageResourceManager2::clear()
 {
-    m_blocksInfo.clear();
     m_images.clear();
 }
+
+void VImageResourceManager2::removeImage(const QString &p_name)
+{
+    m_images.remove(p_name);
+}

+ 3 - 17
src/vimageresourcemanager2.h

@@ -4,11 +4,6 @@
 #include <QHash>
 #include <QString>
 #include <QPixmap>
-#include <QTextBlock>
-#include <QVector>
-#include <QSet>
-
-struct VBlockImageInfo2;
 
 
 class VImageResourceManager2
@@ -20,16 +15,12 @@ public:
     // If @p_name already exists in the resources, it will update it.
     void addImage(const QString &p_name, const QPixmap &p_image);
 
+    // Remove image @p_name.
+    void removeImage(const QString &p_name);
+
     // Whether the resources contains image with name @p_name.
     bool contains(const QString &p_name) const;
 
-    // Update the block-image info for all blocks.
-    // @p_maximumWidth: maximum width of the images plus the margin.
-    // Return changed blocks' block number.
-    QSet<int> updateBlockInfos(const QVector<VBlockImageInfo2> &p_blocksInfo);
-
-    const QVector<VBlockImageInfo2> *findImageInfoByBlock(int p_blockNumber) const;
-
     const QPixmap *findImage(const QString &p_name) const;
 
     void clear();
@@ -37,11 +28,6 @@ public:
 private:
     // All the images resources.
     QHash<QString, QPixmap> m_images;
-
-    // Image info of all the blocks with image.
-    // One block may contain multiple inline images or only one block image.
-    // If there are multiple inline images, they are sorted by the start position.
-    QHash<int, QVector<VBlockImageInfo2>> m_blocksInfo;
 };
 
 #endif // VIMAGERESOURCEMANAGER2_H

+ 11 - 2
src/vmdedit.cpp

@@ -11,7 +11,6 @@
 #include "utils/vpreviewutils.h"
 #include "dialog/vselectdialog.h"
 #include "dialog/vconfirmdeletiondialog.h"
-#include "vimagepreviewer.h"
 #include "vtextblockdata.h"
 #include "vorphanfile.h"
 
@@ -47,6 +46,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
     m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc,
                                                     p_type);
 
+    /*
     m_imagePreviewer = new VImagePreviewer(this, m_mdHighlighter);
     connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
             m_imagePreviewer, &VImagePreviewer::imageLinksChanged);
@@ -65,6 +65,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
                     finishOneAsyncJob(1);
                 }
             });
+    */
 
     // Comment out these lines since we use VMdEditor to replace VMdEdit.
     /*
@@ -544,13 +545,16 @@ QString VMdEdit::getPlainTextWithoutPreviewImage() const
     while (true) {
         deletions.clear();
 
+        /*
         while (m_imagePreviewer->isPreviewing()) {
             VUtils::sleepWait(100);
         }
+        */
 
         // Iterate all the block to get positions for deletion.
         QTextBlock block = document()->begin();
         bool tryAgain = false;
+        /*
         while (block.isValid()) {
             if (VTextBlockData::containsPreviewImage(block)) {
                 if (!getPreviewImageRegionOfBlock(block, deletions)) {
@@ -561,6 +565,7 @@ QString VMdEdit::getPlainTextWithoutPreviewImage() const
 
             block = block.next();
         }
+        */
 
         if (tryAgain) {
             continue;
@@ -680,10 +685,12 @@ QImage VMdEdit::tryGetSelectedImage()
 
     if (format.isValid()) {
         PreviewImageSource src = VPreviewUtils::getPreviewImageSource(format);
-        long long id = VPreviewUtils::getPreviewImageID(format);
+        // long long id = VPreviewUtils::getPreviewImageID(format);
         if (src == PreviewImageSource::Image) {
+            /*
             Q_ASSERT(m_imagePreviewer->isEnabled());
             image = m_imagePreviewer->fetchCachedImageByID(id);
+            */
         }
     }
 
@@ -692,7 +699,9 @@ QImage VMdEdit::tryGetSelectedImage()
 
 void VMdEdit::resizeEvent(QResizeEvent *p_event)
 {
+    /*
     m_imagePreviewer->updatePreviewImageWidth();
+    */
 
     VEdit::resizeEvent(p_event);
 }

+ 1 - 2
src/vmdedit.h

@@ -15,7 +15,6 @@
 class HGMarkdownHighlighter;
 class VCodeBlockHighlightHelper;
 class VDocument;
-class VImagePreviewer;
 
 class VMdEdit : public VEdit
 {
@@ -112,7 +111,7 @@ private:
 
     HGMarkdownHighlighter *m_mdHighlighter;
     VCodeBlockHighlightHelper *m_cbHighlighter;
-    VImagePreviewer *m_imagePreviewer;
+    // VImagePreviewer *m_imagePreviewer;
 
     // Image links inserted while editing.
     QVector<ImageLink> m_insertedImages;

+ 3 - 3
src/vmdeditor.cpp

@@ -70,7 +70,7 @@ VMdEditor::VMdEditor(VFile *p_file,
                                                     p_doc,
                                                     p_type);
 
-    m_previewMgr = new VPreviewManager(this);
+    m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
     connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
             m_previewMgr, &VPreviewManager::imageLinksUpdated);
     connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
@@ -215,7 +215,6 @@ void VMdEditor::makeBlockVisible(const QTextBlock &p_block)
     }
 
     while (y < 0 && vbar->value() > vbar->minimum()) {
-        qDebug() << y << vbar->value() << vbar->minimum() << rectHeight;
         moved = true;
         vbar->setValue(vbar->value() - vbar->singleStep());
         rect = layout->blockBoundingRect(p_block);
@@ -848,7 +847,6 @@ void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest)
 
 void VMdEditor::updateTextEditConfig()
 {
-    m_previewMgr->setPreviewEnabled(g_config->getEnablePreviewImages());
     setBlockImageEnabled(g_config->getEnablePreviewImages());
 
     setImageWidthConstrainted(g_config->getEnablePreviewImageConstraint());
@@ -865,6 +863,8 @@ void VMdEditor::updateTextEditConfig()
     setLineNumberType((LineNumberType)lineNumber);
     setLineNumberColor(g_config->getEditorLineNumberFg(),
                        g_config->getEditorLineNumberBg());
+
+    m_previewMgr->setPreviewEnabled(g_config->getEnablePreviewImages());
 }
 
 void VMdEditor::updateConfig()

+ 104 - 45
src/vpreviewmanager.cpp

@@ -9,17 +9,17 @@
 #include "utils/vutils.h"
 #include "vdownloader.h"
 #include "hgmarkdownhighlighter.h"
-#include "vtextblockdata.h"
 
 extern VConfigManager *g_config;
 
-VPreviewManager::VPreviewManager(VMdEditor *p_editor)
+VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
     : QObject(p_editor),
       m_editor(p_editor),
-      m_previewEnabled(false)
+      m_document(p_editor->document()),
+      m_highlighter(p_highlighter),
+      m_previewEnabled(false),
+      m_timeStamp(0)
 {
-    m_blockImageInfo.resize(PreviewSource::Invalid);
-
     m_downloader = new VDownloader(this);
     connect(m_downloader, &VDownloader::downloadFinished,
             this, &VPreviewManager::imageDownloaded);
@@ -31,9 +31,10 @@ void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRe
         return;
     }
 
+    TS ts = ++m_timeStamp;
     m_imageRegions = p_imageRegions;
 
-    previewImages();
+    previewImages(ts);
 }
 
 void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
@@ -71,27 +72,35 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
 
         if (!m_previewEnabled) {
             clearPreview();
+        } else {
+            requestUpdateImageLinks();
         }
     }
 }
 
 void VPreviewManager::clearPreview()
 {
-    for (int i = 0; i < m_blockImageInfo.size(); ++i) {
-        m_blockImageInfo[i].clear();
-    }
+    m_imageRegions.clear();
+
+    long long ts = ++m_timeStamp;
 
-    updateEditorBlockImages();
+    for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
+        clearBlockObsoletePreviewInfo(ts, static_cast<PreviewSource>(i));
+
+        clearObsoleteImages(ts, static_cast<PreviewSource>(i));
+    }
 }
 
-void VPreviewManager::previewImages()
+void VPreviewManager::previewImages(TS p_timeStamp)
 {
     QVector<ImageLinkInfo> imageLinks;
     fetchImageLinksFromRegions(imageLinks);
 
-    updateBlockImageInfo(imageLinks);
+    updateBlockPreviewInfo(p_timeStamp, imageLinks);
 
-    updateEditorBlockImages();
+    clearBlockObsoletePreviewInfo(p_timeStamp, PreviewSource::ImageLink);
+
+    clearObsoleteImages(p_timeStamp, PreviewSource::ImageLink);
 }
 
 // Returns true if p_text[p_start, p_end) is all spaces.
@@ -218,30 +227,6 @@ QString VPreviewManager::fetchImagePathToPreview(const QString &p_text, QString
     return imagePath;
 }
 
-void VPreviewManager::updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks)
-{
-    QVector<VBlockImageInfo2> &blockInfos = m_blockImageInfo[PreviewSource::ImageLink];
-    blockInfos.clear();
-
-    for (int i = 0; i < p_imageLinks.size(); ++i) {
-        const ImageLinkInfo &link = p_imageLinks[i];
-
-        QString name = imageResourceName(link);
-        if (name.isEmpty()) {
-            continue;
-        }
-
-        VBlockImageInfo2 info(link.m_blockNumber,
-                              name,
-                              link.m_startPos - link.m_blockPos,
-                              link.m_endPos - link.m_blockPos,
-                              link.m_padding,
-                              !link.m_isBlock);
-
-        blockInfos.push_back(info);
-    }
-}
-
 QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
 {
     QString name = p_link.m_linkShortUrl;
@@ -271,14 +256,6 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
     return name;
 }
 
-void VPreviewManager::updateEditorBlockImages()
-{
-    // TODO: need to combine all preview sources.
-    Q_ASSERT(m_blockImageInfo.size() == 1);
-
-    m_editor->updateBlockImages(m_blockImageInfo[PreviewSource::ImageLink]);
-}
-
 int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
 {
     static QHash<QString, int> spaceWidthOfFonts;
@@ -316,3 +293,85 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
 
     return spaceWidth * nrSpaces;
 }
+
+void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
+                                             const QVector<ImageLinkInfo> &p_imageLinks)
+{
+    for (auto const & link : p_imageLinks) {
+        QTextBlock block = m_document->findBlockByNumber(link.m_blockNumber);
+        if (!block.isValid()) {
+            continue;
+        }
+
+        QString name = imageResourceName(link);
+        if (name.isEmpty()) {
+            continue;
+        }
+
+        VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
+        Q_ASSERT(blockData);
+
+        VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink,
+                                              p_timeStamp,
+                                              link.m_startPos - link.m_blockPos,
+                                              link.m_endPos - link.m_blockPos,
+                                              link.m_padding,
+                                              !link.m_isBlock,
+                                              name,
+                                              m_editor->imageSize(name));
+        blockData->insertPreviewInfo(info);
+
+        imageCache(PreviewSource::ImageLink).insert(name, p_timeStamp);
+
+        qDebug() << "block" << link.m_blockNumber
+                 << imageCache(PreviewSource::ImageLink).size()
+                 << blockData->toString();
+    }
+}
+
+void VPreviewManager::clearObsoleteImages(long long p_timeStamp, PreviewSource p_source)
+{
+    auto cache = imageCache(p_source);
+
+    for (auto it = cache.begin(); it != cache.end();) {
+        if (it.value() < p_timeStamp) {
+            m_editor->removeImage(it.key());
+            it = cache.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
+void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
+                                                    PreviewSource p_source)
+{
+    QSet<int> affectedBlocks;
+    QVector<int> obsoleteBlocks;
+    auto blocks = m_highlighter->getPossiblePreviewBlocks();
+    qDebug() << "possible preview blocks" << blocks;
+    for (auto i : blocks) {
+        QTextBlock block = m_document->findBlockByNumber(i);
+        if (!block.isValid()) {
+            obsoleteBlocks.append(i);
+            continue;
+        }
+
+        VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
+        if (!blockData) {
+            continue;
+        }
+
+        if (blockData->clearObsoletePreview(p_timeStamp, p_source)) {
+            affectedBlocks.insert(i);
+        }
+
+        if (blockData->getPreviews().isEmpty()) {
+            obsoleteBlocks.append(i);
+        }
+    }
+
+    m_highlighter->clearPossiblePreviewBlocks(obsoleteBlocks);
+
+    m_editor->relayout(affectedBlocks);
+}

+ 26 - 17
src/vpreviewmanager.h

@@ -8,15 +8,18 @@
 #include <QVector>
 #include "hgmarkdownhighlighter.h"
 #include "vmdeditor.h"
+#include "vtextblockdata.h"
 
 class VDownloader;
 
+typedef long long TS;
+
 
 class VPreviewManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit VPreviewManager(VMdEditor *p_editor);
+    VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter);
 
     void setPreviewEnabled(bool p_enabled);
 
@@ -36,13 +39,6 @@ private slots:
     void imageDownloaded(const QByteArray &p_data, const QString &p_url);
 
 private:
-    // Sources of the preview.
-    enum PreviewSource
-    {
-        ImageLink = 0,
-        Invalid
-    };
-
     struct ImageLinkInfo
     {
         ImageLinkInfo()
@@ -93,7 +89,7 @@ private:
     };
 
     // Start to preview images according to image links.
-    void previewImages();
+    void previewImages(TS p_timeStamp);
 
     // According to m_imageRegions, fetch the image link Url.
     // @p_imageRegions: output.
@@ -106,21 +102,29 @@ private:
     // @p_url: contains the short URL in ![]().
     QString fetchImagePathToPreview(const QString &p_text, QString &p_url);
 
-    void updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks);
+    // Update the preview info of related blocks according to @p_imageLinks.
+    void updateBlockPreviewInfo(TS p_timeStamp, const QVector<ImageLinkInfo> &p_imageLinks);
 
     // 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);
 
-    // Ask the editor to preview images.
-    void updateEditorBlockImages();
-
     // Calculate the block margin (prefix spaces) in pixels.
     int calculateBlockMargin(const QTextBlock &p_block);
 
+    QHash<QString, long long> &imageCache(PreviewSource p_source);
+
+    void clearObsoleteImages(long long p_timeStamp, PreviewSource p_source);
+
+    void clearBlockObsoletePreviewInfo(long long p_timeStamp, PreviewSource p_source);
+
     VMdEditor *m_editor;
 
+    QTextDocument *m_document;
+
+    HGMarkdownHighlighter *m_highlighter;
+
     VDownloader *m_downloader;
 
     // Whether preview is enabled.
@@ -129,13 +133,18 @@ private:
     // Regions of all the image links.
     QVector<VElementRegion> m_imageRegions;
 
-    // All preview images and information.
-    // Each preview source corresponds to one vector.
-    QVector<QVector<VBlockImageInfo2>> m_blockImageInfo;
-
     // Map from URL to name in the resource manager.
     // Used for downloading images.
     QHash<QString, QString> m_urlToName;
+
+    TS m_timeStamp;
+
+    // Used to discard obsolete images. One per each preview source.
+    QHash<QString, long long> m_imageCaches[(int)PreviewSource::MaxNumberOfSources];
 };
 
+inline QHash<QString, long long> &VPreviewManager::imageCache(PreviewSource p_source)
+{
+    return m_imageCaches[(int)p_source];
+}
 #endif // VPREVIEWMANAGER_H

+ 92 - 2
src/vtextblockdata.cpp

@@ -1,11 +1,101 @@
 #include "vtextblockdata.h"
 
+#include <QDebug>
+
 VTextBlockData::VTextBlockData()
-    : QTextBlockUserData(),
-      m_containsPreviewImage(false)
+    : QTextBlockUserData()
 {
 }
 
 VTextBlockData::~VTextBlockData()
 {
+    for (auto it : m_previews) {
+        delete it;
+    }
+
+    m_previews.clear();
+}
+
+void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
+{
+    bool inserted = false;
+    for (auto it = m_previews.begin(); it != m_previews.end();) {
+        VPreviewInfo *ele = *it;
+
+        if (p_info->m_imageInfo < ele->m_imageInfo) {
+            // Insert p_info here.
+            m_previews.insert(it, p_info);
+            inserted = true;
+            break;
+        } else if (p_info->m_imageInfo == ele->m_imageInfo) {
+            // Update the timestamp.
+            delete ele;
+            *it = p_info;
+            inserted = 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 {
+            ++it;
+        }
+    }
+
+    if (!inserted) {
+        // Append it.
+        m_previews.append(p_info);
+    }
+
+    Q_ASSERT(checkOrder());
+}
+
+QString VTextBlockData::toString() const
+{
+    QString ret;
+    for (int i = 0; i < m_previews.size(); ++i) {
+        ret += QString("preview %1: source %2 ts %3 image %4\n")
+                      .arg(i)
+                      .arg((int)m_previews[i]->m_source)
+                      .arg(m_previews[i]->m_timeStamp)
+                      .arg(m_previews[i]->m_imageInfo.toString());
+    }
+
+    return ret;
+}
+
+bool VTextBlockData::checkOrder() const
+{
+    for (int i = 1; i < m_previews.size(); ++i) {
+        if (!(m_previews[i - 1]->m_imageInfo < m_previews[i]->m_imageInfo)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p_source)
+{
+    bool deleted = false;
+    for (auto it = m_previews.begin(); it != m_previews.end();) {
+        VPreviewInfo *ele = *it;
+
+        if (ele->m_source == p_source
+            && ele->m_timeStamp < p_timeStamp) {
+            // Remove it.
+            qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString();
+            delete ele;
+            it = m_previews.erase(it);
+            deleted = true;
+        } else {
+            ++it;
+        }
+    }
+
+    return deleted;
 }

+ 141 - 22
src/vtextblockdata.h

@@ -2,7 +2,133 @@
 #define VTEXTBLOCKDATA_H
 
 #include <QTextBlockUserData>
+#include <QVector>
 
+// Sources of the preview.
+enum class PreviewSource
+{
+    ImageLink = 0,
+    MaxNumberOfSources
+};
+
+
+// Info about a previewed image.
+struct VPreviewedImageInfo
+{
+    VPreviewedImageInfo()
+        : m_startPos(-1),
+          m_endPos(-1),
+          m_padding(0),
+          m_inline(false)
+    {
+    }
+
+    VPreviewedImageInfo(int p_startPos,
+                        int p_endPos,
+                        int p_padding,
+                        bool p_inline,
+                        const QString &p_imageName,
+                        const QSize &p_imageSize)
+        : m_startPos(p_startPos),
+          m_endPos(p_endPos),
+          m_padding(p_padding),
+          m_inline(p_inline),
+          m_imageName(p_imageName),
+          m_imageSize(p_imageSize)
+    {
+    }
+
+    bool operator<(const VPreviewedImageInfo &a) const
+    {
+        return m_endPos <= a.m_startPos;
+    }
+
+    bool operator==(const VPreviewedImageInfo &a) const
+    {
+        return m_startPos == a.m_startPos
+               && m_endPos == a.m_endPos
+               && m_padding == a.m_padding
+               && m_inline == a.m_inline
+               && m_imageName == a.m_imageName
+               && m_imageSize == a.m_imageSize;
+    }
+
+    bool intersect(const VPreviewedImageInfo &a) const
+    {
+        return !(m_endPos <= a.m_startPos || m_startPos >= a.m_endPos);
+    }
+
+    QString toString() const
+    {
+        return QString("previewed image (%1): [%2, %3] padding %4 inline %5 (%6,%7)")
+                      .arg(m_imageName)
+                      .arg(m_startPos)
+                      .arg(m_endPos)
+                      .arg(m_padding)
+                      .arg(m_inline)
+                      .arg(m_imageSize.width())
+                      .arg(m_imageSize.height());
+    }
+
+    // Start position of text corresponding to the image within block.
+    int m_startPos;
+
+    // End position of text corresponding to the image within block.
+    int m_endPos;
+
+    // Padding of the image. Only valid for block image.
+    int m_padding;
+
+    // Whether it is inline image or block image.
+    bool m_inline;
+
+    // Image name in the resource manager.
+    QString m_imageName;
+
+    // Image size of the image. Cache for performance.
+    QSize m_imageSize;
+};
+
+
+struct VPreviewInfo
+{
+    VPreviewInfo()
+        : m_source(PreviewSource::ImageLink),
+          m_timeStamp(0)
+    {
+    }
+
+    VPreviewInfo(PreviewSource p_source,
+                 long long p_timeStamp,
+                 int p_startPos,
+                 int p_endPos,
+                 int p_padding,
+                 bool p_inline,
+                 const QString &p_imageName,
+                 const QSize &p_imageSize)
+        : m_source(p_source),
+          m_timeStamp(p_timeStamp),
+          m_imageInfo(p_startPos,
+                      p_endPos,
+                      p_padding,
+                      p_inline,
+                      p_imageName,
+                      p_imageSize)
+    {
+    }
+
+    // Source of this preview.
+    PreviewSource m_source;
+
+    // Timestamp for this preview.
+    long long m_timeStamp;
+
+    // Image info of this preview.
+    VPreviewedImageInfo m_imageInfo;
+};
+
+
+// User data for each block.
 class VTextBlockData : public QTextBlockUserData
 {
 public:
@@ -10,35 +136,28 @@ public:
 
     ~VTextBlockData();
 
-    bool containsPreviewImage() const;
+    // Insert @p_info into m_previews, preserving the order.
+    void insertPreviewInfo(VPreviewInfo *p_info);
 
-    static bool containsPreviewImage(const QTextBlock &p_block);
+    // For degub only.
+    QString toString() const;
 
-    void setContainsPreviewImage(bool p_contains);
+    const QVector<VPreviewInfo *> &getPreviews() const;
 
-private:
-    // Whether this block maybe contains one or more preview images.
-    bool m_containsPreviewImage;
-};
+    // Return true if there have obsolete preview being deleted.
+    bool clearObsoletePreview(long long p_timeStamp, PreviewSource p_source);
 
-inline bool VTextBlockData::containsPreviewImage() const
-{
-    return m_containsPreviewImage;
-}
+private:
+    // Check the order of elements.
+    bool checkOrder() const;
 
-inline void VTextBlockData::setContainsPreviewImage(bool p_contains)
-{
-    m_containsPreviewImage = p_contains;
-}
+    // Sorted by m_imageInfo.m_startPos, with no two element's position intersected.
+    QVector<VPreviewInfo *> m_previews;
+};
 
-inline bool VTextBlockData::containsPreviewImage(const QTextBlock &p_block)
+inline const QVector<VPreviewInfo *> &VTextBlockData::getPreviews() const
 {
-    VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
-    if (!blockData) {
-        return false;
-    }
-
-    return blockData->containsPreviewImage();
+    return m_previews;
 }
 
 #endif // VTEXTBLOCKDATA_H

+ 42 - 39
src/vtextdocumentlayout.cpp

@@ -12,6 +12,7 @@
 
 #include "vimageresourcemanager2.h"
 #include "vtextedit.h"
+#include "vtextblockdata.h"
 
 #define MARKER_THICKNESS        2
 #define MAX_INLINE_IMAGE_HEIGHT 400
@@ -545,14 +546,15 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
 {
     // Handle block inline image.
     bool hasInlineImages = false;
-    const QVector<VBlockImageInfo2> *info = NULL;
+    const QVector<VPreviewInfo *> *info = NULL;
     if (m_blockImageEnabled) {
-        info = m_imageMgr->findImageInfoByBlock(p_block.blockNumber());
-
-        if (info
-            && !info->isEmpty()
-            && info->first().m_inlineImage) {
-            hasInlineImages = true;
+        VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
+        if (blockData) {
+            info = &(blockData->getPreviews());
+            if (!info->isEmpty()
+                && info->first()->m_imageInfo.m_inline) {
+                hasInlineImages = true;
+            }
         }
     }
 
@@ -571,7 +573,7 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
         p_height += m_lineLeading;
 
         if (hasInlineImages) {
-            QVector<const VBlockImageInfo2 *> images;
+            QVector<const VPreviewedImageInfo *> images;
             QVector<QPair<qreal, qreal>> imageRange;
             qreal imgHeight = fetchInlineImagesForOneLine(*info,
                                                           &line,
@@ -604,7 +606,7 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
     return p_height;
 }
 
-void VTextDocumentLayout::layoutInlineImage(const VBlockImageInfo2 *p_info,
+void VTextDocumentLayout::layoutInlineImage(const VPreviewedImageInfo *p_info,
                                             qreal p_heightInBlock,
                                             qreal p_imageSpaceHeight,
                                             qreal p_xStart,
@@ -756,26 +758,29 @@ QRectF VTextDocumentLayout::blockRectFromTextLayout(const QTextBlock &p_block,
 
     // Handle block non-inline image.
     if (m_blockImageEnabled) {
-        const QVector<VBlockImageInfo2> *info = m_imageMgr->findImageInfoByBlock(p_block.blockNumber());
-        if (info && info->size() == 1) {
-            const VBlockImageInfo2& img = info->first();
-            if (!img.m_inlineImage && !img.m_imageSize.isNull()) {
-                int maximumWidth = tlRect.width();
-                int padding;
-                QSize size;
-                adjustImagePaddingAndSize(&img, maximumWidth, padding, size);
-
-                if (p_image) {
-                    p_image->m_name = img.m_imageName;
-                    p_image->m_rect = QRectF(padding + m_margin,
-                                             br.height() + m_lineLeading,
-                                             size.width(),
-                                             size.height());
-                }
+        VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
+        if (blockData) {
+            const QVector<VPreviewInfo *> &info = blockData->getPreviews();
+            if (info.size() == 1) {
+                const VPreviewedImageInfo& img = info.first()->m_imageInfo;
+                if (!img.m_inline) {
+                    int maximumWidth = tlRect.width();
+                    int padding;
+                    QSize size;
+                    adjustImagePaddingAndSize(&img, maximumWidth, padding, size);
+
+                    if (p_image) {
+                        p_image->m_name = img.m_imageName;
+                        p_image->m_rect = QRectF(padding + m_margin,
+                                                 br.height() + m_lineLeading,
+                                                 size.width(),
+                                                 size.height());
+                    }
 
-                int dw = padding + size.width() + m_margin - br.width();
-                int dh = size.height() + m_lineLeading;
-                br.adjust(0, 0, dw > 0 ? dw : 0, dh);
+                    int dw = padding + size.width() + m_margin - br.width();
+                    int dh = size.height() + m_lineLeading;
+                    br.adjust(0, 0, dw > 0 ? dw : 0, dh);
+                }
             }
         }
     }
@@ -831,7 +836,7 @@ void VTextDocumentLayout::setBlockImageEnabled(bool p_enabled)
     relayout();
 }
 
-void VTextDocumentLayout::adjustImagePaddingAndSize(const VBlockImageInfo2 *p_info,
+void VTextDocumentLayout::adjustImagePaddingAndSize(const VPreviewedImageInfo *p_info,
                                                     int p_maximumWidth,
                                                     int &p_padding,
                                                     QSize &p_size) const
@@ -873,7 +878,10 @@ void VTextDocumentLayout::drawImages(QPainter *p_painter,
 
     for (auto const & img : images) {
         const QPixmap *image = m_imageMgr->findImage(img.m_name);
-        Q_ASSERT(image);
+        if (!image) {
+            continue;
+        }
+
         QRect targetRect = img.m_rect.adjusted(p_offset.x(),
                                                p_offset.y(),
                                                p_offset.x(),
@@ -952,11 +960,11 @@ void VTextDocumentLayout::relayout(const QSet<int> &p_blocks)
     updateDocumentSize();
 }
 
-qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VBlockImageInfo2> &p_info,
+qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VPreviewInfo *> &p_info,
                                                        const QTextLine *p_line,
                                                        qreal p_margin,
                                                        int &p_index,
-                                                       QVector<const VBlockImageInfo2 *> &p_images,
+                                                       QVector<const VPreviewedImageInfo *> &p_images,
                                                        QVector<QPair<qreal, qreal>> &p_imageRange)
 {
     qreal maxHeight = 0;
@@ -964,13 +972,8 @@ qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VBlockImage
     int end = p_line->textLength() + start;
 
     for (int i = 0; i < p_info.size(); ++i) {
-        const VBlockImageInfo2 &img = p_info[i];
-        Q_ASSERT(img.m_inlineImage);
-
-        if (img.m_imageSize.isNull()) {
-            p_index = i + 1;
-            continue;
-        }
+        const VPreviewedImageInfo &img = p_info[i]->m_imageInfo;
+        Q_ASSERT(img.m_inline);
 
         if (img.m_startPos >= start && img.m_startPos < end) {
             // Start of a new image.

+ 6 - 5
src/vtextdocumentlayout.h

@@ -7,7 +7,8 @@
 #include <QSet>
 
 class VImageResourceManager2;
-struct VBlockImageInfo2;
+struct VPreviewedImageInfo;
+struct VPreviewInfo;
 
 
 class VTextDocumentLayout : public QAbstractTextDocumentLayout
@@ -141,7 +142,7 @@ private:
     // Layout inline image in a line.
     // @p_info: if NULL, means just layout a marker.
     // Returns the image height.
-    void layoutInlineImage(const VBlockImageInfo2 *p_info,
+    void layoutInlineImage(const VPreviewedImageInfo *p_info,
                            qreal p_heightInBlock,
                            qreal p_imageSpaceHeight,
                            qreal p_xStart,
@@ -154,11 +155,11 @@ private:
     // @p_images: contains all images and markers (NULL element indicates it
     // is just a placeholder for the marker.
     // Returns the maximum height of the images.
-    qreal fetchInlineImagesForOneLine(const QVector<VBlockImageInfo2> &p_info,
+    qreal fetchInlineImagesForOneLine(const QVector<VPreviewInfo *> &p_info,
                                       const QTextLine *p_line,
                                       qreal p_margin,
                                       int &p_index,
-                                      QVector<const VBlockImageInfo2 *> &p_images,
+                                      QVector<const VPreviewedImageInfo *> &p_images,
                                       QVector<QPair<qreal, qreal>> &p_imageRange);
 
     // Clear the layout of @p_block.
@@ -211,7 +212,7 @@ private:
     // remain the same.
     void updateDocumentSizeWithOneBlockChanged(int p_blockNumber);
 
-    void adjustImagePaddingAndSize(const VBlockImageInfo2 *p_info,
+    void adjustImagePaddingAndSize(const VPreviewedImageInfo *p_info,
                                    int p_maximumWidth,
                                    int &p_padding,
                                    QSize &p_size) const;

+ 20 - 8
src/vtextedit.cpp

@@ -269,14 +269,6 @@ int VTextEdit::contentOffsetY() const
     return -(sb->value());
 }
 
-void VTextEdit::updateBlockImages(const QVector<VBlockImageInfo2> &p_blocksInfo)
-{
-    if (m_blockImageEnabled) {
-        auto blocks = m_imageMgr->updateBlockInfos(p_blocksInfo);
-        getLayout()->relayout(blocks);
-    }
-}
-
 void VTextEdit::clearBlockImages()
 {
     m_imageMgr->clear();
@@ -284,11 +276,26 @@ void VTextEdit::clearBlockImages()
     getLayout()->relayout();
 }
 
+void VTextEdit::relayout(const QSet<int> &p_blocks)
+{
+    getLayout()->relayout(p_blocks);
+}
+
 bool VTextEdit::containsImage(const QString &p_imageName) const
 {
     return m_imageMgr->contains(p_imageName);
 }
 
+QSize VTextEdit::imageSize(const QString &p_imageName) const
+{
+    const QPixmap *img = m_imageMgr->findImage(p_imageName);
+    if (img) {
+        return img->size();
+    }
+
+    return QSize();
+}
+
 void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
 {
     if (m_blockImageEnabled) {
@@ -296,6 +303,11 @@ void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
     }
 }
 
+void VTextEdit::removeImage(const QString &p_imageName)
+{
+    m_imageMgr->removeImage(p_imageName);
+}
+
 void VTextEdit::setBlockImageEnabled(bool p_enabled)
 {
     if (m_blockImageEnabled == p_enabled) {

+ 8 - 96
src/vtextedit.h

@@ -12,98 +12,6 @@ class QResizeEvent;
 class VImageResourceManager2;
 
 
-struct VBlockImageInfo2
-{
-public:
-    VBlockImageInfo2()
-        : m_blockNumber(-1),
-          m_startPos(-1),
-          m_endPos(-1),
-          m_padding(0),
-          m_inlineImage(false)
-    {
-    }
-
-    VBlockImageInfo2(int p_blockNumber,
-                     const QString &p_imageName,
-                     int p_startPos = -1,
-                     int p_endPos = -1,
-                     int p_padding = 0,
-                     bool p_inlineImage = false)
-        : m_blockNumber(p_blockNumber),
-          m_startPos(p_startPos),
-          m_endPos(p_endPos),
-          m_padding(p_padding),
-          m_inlineImage(p_inlineImage),
-          m_imageName(p_imageName)
-    {
-    }
-
-    bool operator==(const VBlockImageInfo2 &p_other) const
-    {
-        return m_blockNumber == p_other.m_blockNumber
-               && m_startPos == p_other.m_startPos
-               && m_endPos == p_other.m_endPos
-               && m_padding == p_other.m_padding
-               && m_inlineImage == p_other.m_inlineImage
-               && m_imageName == p_other.m_imageName
-               && m_imageSize == p_other.m_imageSize;
-    }
-
-    bool operator<(const VBlockImageInfo2 &p_other) const
-    {
-        if (m_blockNumber < p_other.m_blockNumber) {
-            return true;
-        } else if (m_blockNumber > p_other.m_blockNumber) {
-            return false;
-        } else if (m_startPos < p_other.m_startPos) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    QString toString() const
-    {
-        return QString("VBlockImageInfo2 block %1 start %2 end %3 padding %4 "
-                       "inline %5 image %6 size [%7,%8]")
-                      .arg(m_blockNumber)
-                      .arg(m_startPos)
-                      .arg(m_endPos)
-                      .arg(m_padding)
-                      .arg(m_inlineImage)
-                      .arg(m_imageName)
-                      .arg(m_imageSize.width())
-                      .arg(m_imageSize.height());
-    }
-
-    // Block number.
-    int m_blockNumber;
-
-    // Start position of the image link in block.
-    int m_startPos;
-
-    // End position of the image link in block.
-    int m_endPos;
-
-    // Padding of the image.
-    int m_padding;
-
-    // Whether it is inline image or block image.
-    bool m_inlineImage;
-
-    // The name of the image corresponding to this block.
-    QString m_imageName;
-
-private:
-    // For cache only.
-    QSize m_imageSize;
-
-    friend class VImageResourceManager2;
-    friend class VTextDocumentLayout;
-};
-
-
 class VTextEdit : public QTextEdit, public VTextEditWithLineNumber
 {
     Q_OBJECT
@@ -126,24 +34,28 @@ public:
 
     QTextBlock firstVisibleBlock() const;
 
-    // Update images of these given blocks.
-    // Images of blocks not given here will be clear.
-    void updateBlockImages(const QVector<VBlockImageInfo2> &p_blocksInfo);
-
     void clearBlockImages();
 
     // Whether the resoruce manager contains image of name @p_imageName.
     bool containsImage(const QString &p_imageName) const;
 
+    // Get the image size from the resource manager.
+    QSize imageSize(const QString &p_imageName) const;
+
     // Add an image to the resources.
     void addImage(const QString &p_imageName, const QPixmap &p_image);
 
+    // Remove an image from the resources.
+    void removeImage(const QString &p_imageName);
+
     void setBlockImageEnabled(bool p_enabled);
 
     void setImageWidthConstrainted(bool p_enabled);
 
     void setImageLineColor(const QColor &p_color);
 
+    void relayout(const QSet<int> &p_blocks);
+
 protected:
     void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;