From 4ec340a403d82c304293b125a33b84f9c22863d6 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 29 Nov 2017 21:45:20 +0800 Subject: [PATCH] refactor preview logics Use block user data to store preview info. --- src/hgmarkdownhighlighter.cpp | 30 +- src/hgmarkdownhighlighter.h | 30 +- src/src.pro | 2 - src/vimagepreviewer.cpp | 660 --------------------------------- src/vimagepreviewer.h | 250 ------------- src/vimageresourcemanager2.cpp | 85 +---- src/vimageresourcemanager2.h | 20 +- src/vmdedit.cpp | 13 +- src/vmdedit.h | 3 +- src/vmdeditor.cpp | 6 +- src/vpreviewmanager.cpp | 149 +++++--- src/vpreviewmanager.h | 43 ++- src/vtextblockdata.cpp | 94 ++++- src/vtextblockdata.h | 163 ++++++-- src/vtextdocumentlayout.cpp | 79 ++-- src/vtextdocumentlayout.h | 11 +- src/vtextedit.cpp | 28 +- src/vtextedit.h | 104 +----- 18 files changed, 485 insertions(+), 1285 deletions(-) delete mode 100644 src/vimagepreviewer.cpp delete mode 100644 src/vimagepreviewer.h diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index a0266e64..3fb236df 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/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(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); } } diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index e8a83402..3415a656 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -5,8 +5,8 @@ #include #include #include +#include #include -#include extern "C" { #include @@ -124,6 +124,10 @@ public: const QVector &getHeaderRegions() const; + const QSet &getPossiblePreviewBlocks() const; + + void clearPossiblePreviewBlocks(const QVector &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 m_potentialPreviewBlocks; - // All HTML comment regions. QVector 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 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 &HGMarkdownHighlighter::getPotentialPreviewBlocks() const -{ - return m_potentialPreviewBlocks; -} - inline const QVector &HGMarkdownHighlighter::getHeaderRegions() const { return m_headerRegions; } +inline const QSet &HGMarkdownHighlighter::getPossiblePreviewBlocks() const +{ + return m_possiblePreviewBlocks; +} + +inline void HGMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector &p_blocksToClear) +{ + for (auto i : p_blocksToClear) { + m_possiblePreviewBlocks.remove(i); + } +} #endif diff --git a/src/src.pro b/src/src.pro index edd7cdd3..b3a7f787 100644 --- a/src/src.pro +++ b/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 \ diff --git a/src/vimagepreviewer.cpp b/src/vimagepreviewer.cpp deleted file mode 100644 index bae56f0d..00000000 --- a/src/vimagepreviewer.cpp +++ /dev/null @@ -1,660 +0,0 @@ -#include "vimagepreviewer.h" - -#include -#include -#include -#include -#include -#include -#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 &p_imageRegions) -{ - kickOffPreview(p_imageRegions); -} - -void VImagePreviewer::kickOffPreview(const QVector &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 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 &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 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 &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 ® = 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(); -} - -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 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); -} diff --git a/src/vimagepreviewer.h b/src/vimagepreviewer.h deleted file mode 100644 index 81326c17..00000000 --- a/src/vimagepreviewer.h +++ /dev/null @@ -1,250 +0,0 @@ -#ifndef VIMAGEPREVIEWER_H -#define VIMAGEPREVIEWER_H - -#include -#include -#include -#include -#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 &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 &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 &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 &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 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 m_previewImages; - - // Regions of all the image links. - QVector 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 diff --git a/src/vimageresourcemanager2.cpp b/src/vimageresourcemanager2.cpp index 53b12c01..45e97ff7 100644 --- a/src/vimageresourcemanager2.cpp +++ b/src/vimageresourcemanager2.cpp @@ -1,9 +1,5 @@ #include "vimageresourcemanager2.h" -#include - -#include "vtextedit.h" - VImageResourceManager2::VImageResourceManager2() { @@ -20,81 +16,6 @@ bool VImageResourceManager2::contains(const QString &p_name) const return m_images.contains(p_name); } -QSet VImageResourceManager2::updateBlockInfos(const QVector &p_blocksInfo) -{ - QSet usedImages; - QHash> newBlocksInfo; - - for (auto const & info : p_blocksInfo) { - VBlockImageInfo2 *newInfo = NULL; - auto blockIt = newBlocksInfo.find(info.m_blockNumber); - if (blockIt == newBlocksInfo.end()) { - // New block. - QVector vec(1, info); - auto it = newBlocksInfo.insert(info.m_blockNumber, vec); - newInfo = &it.value().last(); - } else { - // Multiple images for a block. - QVector &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 affectedBlocks; - if (m_blocksInfo != newBlocksInfo) { - affectedBlocks = QSet::fromList(m_blocksInfo.keys()); - affectedBlocks.unite(QSet::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 *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); +} diff --git a/src/vimageresourcemanager2.h b/src/vimageresourcemanager2.h index 22383bf6..61407a96 100644 --- a/src/vimageresourcemanager2.h +++ b/src/vimageresourcemanager2.h @@ -4,11 +4,6 @@ #include #include #include -#include -#include -#include - -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 updateBlockInfos(const QVector &p_blocksInfo); - - const QVector *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 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> m_blocksInfo; }; #endif // VIMAGERESOURCEMANAGER2_H diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 647fa68e..a3ea2457 100644 --- a/src/vmdedit.cpp +++ b/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); } diff --git a/src/vmdedit.h b/src/vmdedit.h index 231d58c0..36ab0974 100644 --- a/src/vmdedit.h +++ b/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 m_insertedImages; diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index ac58bf7a..f2e515f6 100644 --- a/src/vmdeditor.cpp +++ b/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() diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index 1be41db5..82389e3b 100644 --- a/src/vpreviewmanager.cpp +++ b/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 &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(); - updateEditorBlockImages(); + long long ts = ++m_timeStamp; + + for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) { + clearBlockObsoletePreviewInfo(ts, static_cast(i)); + + clearObsoleteImages(ts, static_cast(i)); + } } -void VPreviewManager::previewImages() +void VPreviewManager::previewImages(TS p_timeStamp) { QVector 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 &p_imageLinks) -{ - QVector &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 spaceWidthOfFonts; @@ -316,3 +293,85 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block) return spaceWidth * nrSpaces; } + +void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp, + const QVector &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(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 affectedBlocks; + QVector 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(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); +} diff --git a/src/vpreviewmanager.h b/src/vpreviewmanager.h index 8fe09019..53375b0c 100644 --- a/src/vpreviewmanager.h +++ b/src/vpreviewmanager.h @@ -8,15 +8,18 @@ #include #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 &p_imageLinks); + // Update the preview info of related blocks according to @p_imageLinks. + void updateBlockPreviewInfo(TS p_timeStamp, const QVector &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 &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 m_imageRegions; - // All preview images and information. - // Each preview source corresponds to one vector. - QVector> m_blockImageInfo; - // Map from URL to name in the resource manager. // Used for downloading images. QHash m_urlToName; + + TS m_timeStamp; + + // Used to discard obsolete images. One per each preview source. + QHash m_imageCaches[(int)PreviewSource::MaxNumberOfSources]; }; +inline QHash &VPreviewManager::imageCache(PreviewSource p_source) +{ + return m_imageCaches[(int)p_source]; +} #endif // VPREVIEWMANAGER_H diff --git a/src/vtextblockdata.cpp b/src/vtextblockdata.cpp index a0438bda..a5a0092d 100644 --- a/src/vtextblockdata.cpp +++ b/src/vtextblockdata.cpp @@ -1,11 +1,101 @@ #include "vtextblockdata.h" +#include + 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; } diff --git a/src/vtextblockdata.h b/src/vtextblockdata.h index 1be35d8e..62d45ac6 100644 --- a/src/vtextblockdata.h +++ b/src/vtextblockdata.h @@ -2,7 +2,133 @@ #define VTEXTBLOCKDATA_H #include +#include +// 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 &getPreviews() const; + + // Return true if there have obsolete preview being deleted. + bool clearObsoletePreview(long long p_timeStamp, PreviewSource p_source); private: - // Whether this block maybe contains one or more preview images. - bool m_containsPreviewImage; + // Check the order of elements. + bool checkOrder() const; + + // Sorted by m_imageInfo.m_startPos, with no two element's position intersected. + QVector m_previews; }; -inline bool VTextBlockData::containsPreviewImage() const +inline const QVector &VTextBlockData::getPreviews() const { - return m_containsPreviewImage; -} - -inline void VTextBlockData::setContainsPreviewImage(bool p_contains) -{ - m_containsPreviewImage = p_contains; -} - -inline bool VTextBlockData::containsPreviewImage(const QTextBlock &p_block) -{ - VTextBlockData *blockData = dynamic_cast(p_block.userData()); - if (!blockData) { - return false; - } - - return blockData->containsPreviewImage(); + return m_previews; } #endif // VTEXTBLOCKDATA_H diff --git a/src/vtextdocumentlayout.cpp b/src/vtextdocumentlayout.cpp index af1a38e8..8355123f 100644 --- a/src/vtextdocumentlayout.cpp +++ b/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 *info = NULL; + const QVector *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(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 images; + QVector images; QVector> 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 *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); + VTextBlockData *blockData = dynamic_cast(p_block.userData()); + if (blockData) { + const QVector &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()); + 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 &p_blocks) updateDocumentSize(); } -qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector &p_info, +qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector &p_info, const QTextLine *p_line, qreal p_margin, int &p_index, - QVector &p_images, + QVector &p_images, QVector> &p_imageRange) { qreal maxHeight = 0; @@ -964,13 +972,8 @@ qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVectortextLength() + 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. diff --git a/src/vtextdocumentlayout.h b/src/vtextdocumentlayout.h index 120484e2..b423312a 100644 --- a/src/vtextdocumentlayout.h +++ b/src/vtextdocumentlayout.h @@ -7,7 +7,8 @@ #include 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 &p_info, + qreal fetchInlineImagesForOneLine(const QVector &p_info, const QTextLine *p_line, qreal p_margin, int &p_index, - QVector &p_images, + QVector &p_images, QVector> &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; diff --git a/src/vtextedit.cpp b/src/vtextedit.cpp index 48517444..6c7a1e3e 100644 --- a/src/vtextedit.cpp +++ b/src/vtextedit.cpp @@ -269,14 +269,6 @@ int VTextEdit::contentOffsetY() const return -(sb->value()); } -void VTextEdit::updateBlockImages(const QVector &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 &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) { diff --git a/src/vtextedit.h b/src/vtextedit.h index eb8b7e7e..bda36798 100644 --- a/src/vtextedit.h +++ b/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 &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 &p_blocks); + protected: void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;