refactor preview logics

Use block user data to store preview info.
This commit is contained in:
Le Tan 2017-11-29 21:45:20 +08:00
parent c7cb95d18b
commit 4ec340a403
18 changed files with 485 additions and 1285 deletions

View File

@ -93,38 +93,18 @@ HGMarkdownHighlighter::~HGMarkdownHighlighter()
void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text) void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
{ {
Q_UNUSED(p_text);
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(currentBlockUserData()); VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(currentBlockUserData());
if (!blockData) { if (!blockData) {
blockData = new VTextBlockData(); blockData = new VTextBlockData();
setCurrentBlockUserData(blockData); setCurrentBlockUserData(blockData);
} }
bool contains = p_text.contains(QChar::ObjectReplacementCharacter); if (blockData->getPreviews().isEmpty()) {
blockData->setContainsPreviewImage(contains); m_possiblePreviewBlocks.remove(p_blockNum);
auto curIt = m_potentialPreviewBlocks.find(p_blockNum);
if (curIt == m_potentialPreviewBlocks.end()) {
if (contains) {
m_potentialPreviewBlocks.insert(p_blockNum, true);
}
} else { } else {
if (!contains) { m_possiblePreviewBlocks.insert(p_blockNum);
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());
} }
} }

View File

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

View File

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

View File

@ -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);
}

View File

@ -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

View File

@ -1,9 +1,5 @@
#include "vimageresourcemanager2.h" #include "vimageresourcemanager2.h"
#include <QDebug>
#include "vtextedit.h"
VImageResourceManager2::VImageResourceManager2() VImageResourceManager2::VImageResourceManager2()
{ {
@ -20,81 +16,6 @@ bool VImageResourceManager2::contains(const QString &p_name) const
return m_images.contains(p_name); 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 const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
{ {
auto it = m_images.find(p_name); auto it = m_images.find(p_name);
@ -107,6 +28,10 @@ const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
void VImageResourceManager2::clear() void VImageResourceManager2::clear()
{ {
m_blocksInfo.clear();
m_images.clear(); m_images.clear();
} }
void VImageResourceManager2::removeImage(const QString &p_name)
{
m_images.remove(p_name);
}

View File

@ -4,11 +4,6 @@
#include <QHash> #include <QHash>
#include <QString> #include <QString>
#include <QPixmap> #include <QPixmap>
#include <QTextBlock>
#include <QVector>
#include <QSet>
struct VBlockImageInfo2;
class VImageResourceManager2 class VImageResourceManager2
@ -20,16 +15,12 @@ public:
// If @p_name already exists in the resources, it will update it. // If @p_name already exists in the resources, it will update it.
void addImage(const QString &p_name, const QPixmap &p_image); 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. // Whether the resources contains image with name @p_name.
bool contains(const QString &p_name) const; 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; const QPixmap *findImage(const QString &p_name) const;
void clear(); void clear();
@ -37,11 +28,6 @@ public:
private: private:
// All the images resources. // All the images resources.
QHash<QString, QPixmap> m_images; 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 #endif // VIMAGERESOURCEMANAGER2_H

View File

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

View File

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

View File

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

View File

@ -9,17 +9,17 @@
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vdownloader.h" #include "vdownloader.h"
#include "hgmarkdownhighlighter.h" #include "hgmarkdownhighlighter.h"
#include "vtextblockdata.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
VPreviewManager::VPreviewManager(VMdEditor *p_editor) VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
: QObject(p_editor), : QObject(p_editor),
m_editor(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); m_downloader = new VDownloader(this);
connect(m_downloader, &VDownloader::downloadFinished, connect(m_downloader, &VDownloader::downloadFinished,
this, &VPreviewManager::imageDownloaded); this, &VPreviewManager::imageDownloaded);
@ -31,9 +31,10 @@ void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRe
return; return;
} }
TS ts = ++m_timeStamp;
m_imageRegions = p_imageRegions; m_imageRegions = p_imageRegions;
previewImages(); previewImages(ts);
} }
void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url) void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
@ -71,27 +72,35 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
if (!m_previewEnabled) { if (!m_previewEnabled) {
clearPreview(); clearPreview();
} else {
requestUpdateImageLinks();
} }
} }
} }
void VPreviewManager::clearPreview() void VPreviewManager::clearPreview()
{ {
for (int i = 0; i < m_blockImageInfo.size(); ++i) { m_imageRegions.clear();
m_blockImageInfo[i].clear();
}
updateEditorBlockImages(); long long ts = ++m_timeStamp;
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; QVector<ImageLinkInfo> imageLinks;
fetchImageLinksFromRegions(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. // 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; 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 VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
{ {
QString name = p_link.m_linkShortUrl; QString name = p_link.m_linkShortUrl;
@ -271,14 +256,6 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
return name; 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) int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
{ {
static QHash<QString, int> spaceWidthOfFonts; static QHash<QString, int> spaceWidthOfFonts;
@ -316,3 +293,85 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
return spaceWidth * nrSpaces; 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);
}

View File

@ -8,15 +8,18 @@
#include <QVector> #include <QVector>
#include "hgmarkdownhighlighter.h" #include "hgmarkdownhighlighter.h"
#include "vmdeditor.h" #include "vmdeditor.h"
#include "vtextblockdata.h"
class VDownloader; class VDownloader;
typedef long long TS;
class VPreviewManager : public QObject class VPreviewManager : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VPreviewManager(VMdEditor *p_editor); VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter);
void setPreviewEnabled(bool p_enabled); void setPreviewEnabled(bool p_enabled);
@ -36,13 +39,6 @@ private slots:
void imageDownloaded(const QByteArray &p_data, const QString &p_url); void imageDownloaded(const QByteArray &p_data, const QString &p_url);
private: private:
// Sources of the preview.
enum PreviewSource
{
ImageLink = 0,
Invalid
};
struct ImageLinkInfo struct ImageLinkInfo
{ {
ImageLinkInfo() ImageLinkInfo()
@ -93,7 +89,7 @@ private:
}; };
// Start to preview images according to image links. // Start to preview images according to image links.
void previewImages(); void previewImages(TS p_timeStamp);
// According to m_imageRegions, fetch the image link Url. // According to m_imageRegions, fetch the image link Url.
// @p_imageRegions: output. // @p_imageRegions: output.
@ -106,21 +102,29 @@ private:
// @p_url: contains the short URL in ![](). // @p_url: contains the short URL in ![]().
QString fetchImagePathToPreview(const QString &p_text, QString &p_url); 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. // Get the name of the image in the resource manager.
// Will add the image to the resource manager if not exists. // Will add the image to the resource manager if not exists.
// Returns empty if fail to add the image to the resource manager. // Returns empty if fail to add the image to the resource manager.
QString imageResourceName(const ImageLinkInfo &p_link); QString imageResourceName(const ImageLinkInfo &p_link);
// Ask the editor to preview images.
void updateEditorBlockImages();
// Calculate the block margin (prefix spaces) in pixels. // Calculate the block margin (prefix spaces) in pixels.
int calculateBlockMargin(const QTextBlock &p_block); 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; VMdEditor *m_editor;
QTextDocument *m_document;
HGMarkdownHighlighter *m_highlighter;
VDownloader *m_downloader; VDownloader *m_downloader;
// Whether preview is enabled. // Whether preview is enabled.
@ -129,13 +133,18 @@ private:
// Regions of all the image links. // Regions of all the image links.
QVector<VElementRegion> m_imageRegions; 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. // Map from URL to name in the resource manager.
// Used for downloading images. // Used for downloading images.
QHash<QString, QString> m_urlToName; 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 #endif // VPREVIEWMANAGER_H

View File

@ -1,11 +1,101 @@
#include "vtextblockdata.h" #include "vtextblockdata.h"
#include <QDebug>
VTextBlockData::VTextBlockData() VTextBlockData::VTextBlockData()
: QTextBlockUserData(), : QTextBlockUserData()
m_containsPreviewImage(false)
{ {
} }
VTextBlockData::~VTextBlockData() 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;
} }

View File

@ -2,7 +2,133 @@
#define VTEXTBLOCKDATA_H #define VTEXTBLOCKDATA_H
#include <QTextBlockUserData> #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 class VTextBlockData : public QTextBlockUserData
{ {
public: public:
@ -10,35 +136,28 @@ public:
~VTextBlockData(); ~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;
// Return true if there have obsolete preview being deleted.
bool clearObsoletePreview(long long p_timeStamp, PreviewSource p_source);
private: private:
// Whether this block maybe contains one or more preview images. // Check the order of elements.
bool m_containsPreviewImage; bool checkOrder() const;
// Sorted by m_imageInfo.m_startPos, with no two element's position intersected.
QVector<VPreviewInfo *> m_previews;
}; };
inline bool VTextBlockData::containsPreviewImage() const inline const QVector<VPreviewInfo *> &VTextBlockData::getPreviews() const
{ {
return m_containsPreviewImage; return m_previews;
}
inline void VTextBlockData::setContainsPreviewImage(bool p_contains)
{
m_containsPreviewImage = p_contains;
}
inline bool VTextBlockData::containsPreviewImage(const QTextBlock &p_block)
{
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
if (!blockData) {
return false;
}
return blockData->containsPreviewImage();
} }
#endif // VTEXTBLOCKDATA_H #endif // VTEXTBLOCKDATA_H

View File

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

View File

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

View File

@ -269,14 +269,6 @@ int VTextEdit::contentOffsetY() const
return -(sb->value()); 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() void VTextEdit::clearBlockImages()
{ {
m_imageMgr->clear(); m_imageMgr->clear();
@ -284,11 +276,26 @@ void VTextEdit::clearBlockImages()
getLayout()->relayout(); getLayout()->relayout();
} }
void VTextEdit::relayout(const QSet<int> &p_blocks)
{
getLayout()->relayout(p_blocks);
}
bool VTextEdit::containsImage(const QString &p_imageName) const bool VTextEdit::containsImage(const QString &p_imageName) const
{ {
return m_imageMgr->contains(p_imageName); 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) void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
{ {
if (m_blockImageEnabled) { 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) void VTextEdit::setBlockImageEnabled(bool p_enabled)
{ {
if (m_blockImageEnabled == p_enabled) { if (m_blockImageEnabled == p_enabled) {

View File

@ -12,98 +12,6 @@ class QResizeEvent;
class VImageResourceManager2; 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 class VTextEdit : public QTextEdit, public VTextEditWithLineNumber
{ {
Q_OBJECT Q_OBJECT
@ -126,24 +34,28 @@ public:
QTextBlock firstVisibleBlock() const; 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(); void clearBlockImages();
// Whether the resoruce manager contains image of name @p_imageName. // Whether the resoruce manager contains image of name @p_imageName.
bool containsImage(const QString &p_imageName) const; 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. // Add an image to the resources.
void addImage(const QString &p_imageName, const QPixmap &p_image); 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 setBlockImageEnabled(bool p_enabled);
void setImageWidthConstrainted(bool p_enabled); void setImageWidthConstrainted(bool p_enabled);
void setImageLineColor(const QColor &p_color); void setImageLineColor(const QColor &p_color);
void relayout(const QSet<int> &p_blocks);
protected: protected:
void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;