mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
refactor preview logics
Use block user data to store preview info.
This commit is contained in:
parent
c7cb95d18b
commit
4ec340a403
@ -93,38 +93,18 @@ HGMarkdownHighlighter::~HGMarkdownHighlighter()
|
||||
|
||||
void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
|
||||
{
|
||||
Q_UNUSED(p_text);
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(currentBlockUserData());
|
||||
if (!blockData) {
|
||||
blockData = new VTextBlockData();
|
||||
setCurrentBlockUserData(blockData);
|
||||
}
|
||||
|
||||
bool contains = p_text.contains(QChar::ObjectReplacementCharacter);
|
||||
blockData->setContainsPreviewImage(contains);
|
||||
|
||||
auto curIt = m_potentialPreviewBlocks.find(p_blockNum);
|
||||
if (curIt == m_potentialPreviewBlocks.end()) {
|
||||
if (contains) {
|
||||
m_potentialPreviewBlocks.insert(p_blockNum, true);
|
||||
}
|
||||
if (blockData->getPreviews().isEmpty()) {
|
||||
m_possiblePreviewBlocks.remove(p_blockNum);
|
||||
} else {
|
||||
if (!contains) {
|
||||
m_potentialPreviewBlocks.erase(curIt);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete elements beyond current block count.
|
||||
int blocks = document->blockCount();
|
||||
if (!m_potentialPreviewBlocks.isEmpty()) {
|
||||
auto it = m_potentialPreviewBlocks.end();
|
||||
do {
|
||||
--it;
|
||||
if (it.key() >= blocks) {
|
||||
it = m_potentialPreviewBlocks.erase(it);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (it != m_potentialPreviewBlocks.begin());
|
||||
m_possiblePreviewBlocks.insert(p_blockNum);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QAtomicInt>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
|
||||
extern "C" {
|
||||
#include <pmh_parser.h>
|
||||
@ -124,6 +124,10 @@ public:
|
||||
|
||||
const QVector<VElementRegion> &getHeaderRegions() const;
|
||||
|
||||
const QSet<int> &getPossiblePreviewBlocks() const;
|
||||
|
||||
void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
|
||||
@ -167,11 +171,6 @@ private:
|
||||
|
||||
int m_numOfCodeBlockHighlightsToRecv;
|
||||
|
||||
// If the ith block contains preview, then the set contains i.
|
||||
// If the set contains i, the ith block may contain preview.
|
||||
// We just use the key.
|
||||
QMap<int, bool> m_potentialPreviewBlocks;
|
||||
|
||||
// All HTML comment regions.
|
||||
QVector<VElementRegion> m_commentRegions;
|
||||
|
||||
@ -193,6 +192,9 @@ private:
|
||||
// Whether this is the first parse.
|
||||
bool m_firstParse;
|
||||
|
||||
// Block number of those blocks which possible contains previewed image.
|
||||
QSet<int> m_possiblePreviewBlocks;
|
||||
|
||||
char *content;
|
||||
int capacity;
|
||||
pmh_element **result;
|
||||
@ -247,14 +249,20 @@ private:
|
||||
bool isValidHeader(unsigned long p_pos, unsigned long p_end);
|
||||
};
|
||||
|
||||
inline const QMap<int, bool> &HGMarkdownHighlighter::getPotentialPreviewBlocks() const
|
||||
{
|
||||
return m_potentialPreviewBlocks;
|
||||
}
|
||||
|
||||
inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const
|
||||
{
|
||||
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
|
||||
|
@ -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 \
|
||||
|
@ -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 ® = 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);
|
||||
}
|
@ -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
|
@ -1,9 +1,5 @@
|
||||
#include "vimageresourcemanager2.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "vtextedit.h"
|
||||
|
||||
|
||||
VImageResourceManager2::VImageResourceManager2()
|
||||
{
|
||||
@ -20,81 +16,6 @@ bool VImageResourceManager2::contains(const QString &p_name) const
|
||||
return m_images.contains(p_name);
|
||||
}
|
||||
|
||||
QSet<int> VImageResourceManager2::updateBlockInfos(const QVector<VBlockImageInfo2> &p_blocksInfo)
|
||||
{
|
||||
QSet<QString> usedImages;
|
||||
QHash<int, QVector<VBlockImageInfo2>> newBlocksInfo;
|
||||
|
||||
for (auto const & info : p_blocksInfo) {
|
||||
VBlockImageInfo2 *newInfo = NULL;
|
||||
auto blockIt = newBlocksInfo.find(info.m_blockNumber);
|
||||
if (blockIt == newBlocksInfo.end()) {
|
||||
// New block.
|
||||
QVector<VBlockImageInfo2> vec(1, info);
|
||||
auto it = newBlocksInfo.insert(info.m_blockNumber, vec);
|
||||
newInfo = &it.value().last();
|
||||
} else {
|
||||
// Multiple images for a block.
|
||||
QVector<VBlockImageInfo2> &vec = blockIt.value();
|
||||
int i;
|
||||
for (i = 0; i < vec.size(); ++i) {
|
||||
Q_ASSERT(vec[i].m_blockNumber == info.m_blockNumber);
|
||||
if (info < vec[i]) {
|
||||
vec.insert(i, info);
|
||||
newInfo = &vec[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == vec.size()) {
|
||||
vec.append(info);
|
||||
newInfo = &vec.last();
|
||||
}
|
||||
}
|
||||
|
||||
if (newInfo->m_padding < 0) {
|
||||
newInfo->m_padding = 0;
|
||||
}
|
||||
|
||||
auto imageIt = m_images.find(newInfo->m_imageName);
|
||||
if (imageIt != m_images.end()) {
|
||||
// Fill the width and height.
|
||||
newInfo->m_imageSize = imageIt.value().size();
|
||||
usedImages.insert(newInfo->m_imageName);
|
||||
}
|
||||
}
|
||||
|
||||
QSet<int> affectedBlocks;
|
||||
if (m_blocksInfo != newBlocksInfo) {
|
||||
affectedBlocks = QSet<int>::fromList(m_blocksInfo.keys());
|
||||
affectedBlocks.unite(QSet<int>::fromList(newBlocksInfo.keys()));
|
||||
|
||||
m_blocksInfo = newBlocksInfo;
|
||||
|
||||
// Clear unused images.
|
||||
for (auto it = m_images.begin(); it != m_images.end();) {
|
||||
if (!usedImages.contains(it.key())) {
|
||||
// Remove the image.
|
||||
it = m_images.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return affectedBlocks;
|
||||
}
|
||||
|
||||
const QVector<VBlockImageInfo2> *VImageResourceManager2::findImageInfoByBlock(int p_blockNumber) const
|
||||
{
|
||||
auto it = m_blocksInfo.find(p_blockNumber);
|
||||
if (it != m_blocksInfo.end()) {
|
||||
return &it.value();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
|
||||
{
|
||||
auto it = m_images.find(p_name);
|
||||
@ -107,6 +28,10 @@ const QPixmap *VImageResourceManager2::findImage(const QString &p_name) const
|
||||
|
||||
void VImageResourceManager2::clear()
|
||||
{
|
||||
m_blocksInfo.clear();
|
||||
m_images.clear();
|
||||
}
|
||||
|
||||
void VImageResourceManager2::removeImage(const QString &p_name)
|
||||
{
|
||||
m_images.remove(p_name);
|
||||
}
|
||||
|
@ -4,11 +4,6 @@
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QTextBlock>
|
||||
#include <QVector>
|
||||
#include <QSet>
|
||||
|
||||
struct VBlockImageInfo2;
|
||||
|
||||
|
||||
class VImageResourceManager2
|
||||
@ -20,16 +15,12 @@ public:
|
||||
// If @p_name already exists in the resources, it will update it.
|
||||
void addImage(const QString &p_name, const QPixmap &p_image);
|
||||
|
||||
// Remove image @p_name.
|
||||
void removeImage(const QString &p_name);
|
||||
|
||||
// Whether the resources contains image with name @p_name.
|
||||
bool contains(const QString &p_name) const;
|
||||
|
||||
// Update the block-image info for all blocks.
|
||||
// @p_maximumWidth: maximum width of the images plus the margin.
|
||||
// Return changed blocks' block number.
|
||||
QSet<int> updateBlockInfos(const QVector<VBlockImageInfo2> &p_blocksInfo);
|
||||
|
||||
const QVector<VBlockImageInfo2> *findImageInfoByBlock(int p_blockNumber) const;
|
||||
|
||||
const QPixmap *findImage(const QString &p_name) const;
|
||||
|
||||
void clear();
|
||||
@ -37,11 +28,6 @@ public:
|
||||
private:
|
||||
// All the images resources.
|
||||
QHash<QString, QPixmap> m_images;
|
||||
|
||||
// Image info of all the blocks with image.
|
||||
// One block may contain multiple inline images or only one block image.
|
||||
// If there are multiple inline images, they are sorted by the start position.
|
||||
QHash<int, QVector<VBlockImageInfo2>> m_blocksInfo;
|
||||
};
|
||||
|
||||
#endif // VIMAGERESOURCEMANAGER2_H
|
||||
|
@ -11,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);
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
class HGMarkdownHighlighter;
|
||||
class VCodeBlockHighlightHelper;
|
||||
class VDocument;
|
||||
class VImagePreviewer;
|
||||
|
||||
class VMdEdit : public VEdit
|
||||
{
|
||||
@ -112,7 +111,7 @@ private:
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
VImagePreviewer *m_imagePreviewer;
|
||||
// VImagePreviewer *m_imagePreviewer;
|
||||
|
||||
// Image links inserted while editing.
|
||||
QVector<ImageLink> m_insertedImages;
|
||||
|
@ -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()
|
||||
|
@ -9,17 +9,17 @@
|
||||
#include "utils/vutils.h"
|
||||
#include "vdownloader.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VPreviewManager::VPreviewManager(VMdEditor *p_editor)
|
||||
VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
|
||||
: QObject(p_editor),
|
||||
m_editor(p_editor),
|
||||
m_previewEnabled(false)
|
||||
m_document(p_editor->document()),
|
||||
m_highlighter(p_highlighter),
|
||||
m_previewEnabled(false),
|
||||
m_timeStamp(0)
|
||||
{
|
||||
m_blockImageInfo.resize(PreviewSource::Invalid);
|
||||
|
||||
m_downloader = new VDownloader(this);
|
||||
connect(m_downloader, &VDownloader::downloadFinished,
|
||||
this, &VPreviewManager::imageDownloaded);
|
||||
@ -31,9 +31,10 @@ void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRe
|
||||
return;
|
||||
}
|
||||
|
||||
TS ts = ++m_timeStamp;
|
||||
m_imageRegions = p_imageRegions;
|
||||
|
||||
previewImages();
|
||||
previewImages(ts);
|
||||
}
|
||||
|
||||
void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
|
||||
@ -71,27 +72,35 @@ void VPreviewManager::setPreviewEnabled(bool p_enabled)
|
||||
|
||||
if (!m_previewEnabled) {
|
||||
clearPreview();
|
||||
} else {
|
||||
requestUpdateImageLinks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VPreviewManager::clearPreview()
|
||||
{
|
||||
for (int i = 0; i < m_blockImageInfo.size(); ++i) {
|
||||
m_blockImageInfo[i].clear();
|
||||
m_imageRegions.clear();
|
||||
|
||||
long long ts = ++m_timeStamp;
|
||||
|
||||
for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
|
||||
clearBlockObsoletePreviewInfo(ts, static_cast<PreviewSource>(i));
|
||||
|
||||
clearObsoleteImages(ts, static_cast<PreviewSource>(i));
|
||||
}
|
||||
}
|
||||
|
||||
updateEditorBlockImages();
|
||||
}
|
||||
|
||||
void VPreviewManager::previewImages()
|
||||
void VPreviewManager::previewImages(TS p_timeStamp)
|
||||
{
|
||||
QVector<ImageLinkInfo> imageLinks;
|
||||
fetchImageLinksFromRegions(imageLinks);
|
||||
|
||||
updateBlockImageInfo(imageLinks);
|
||||
updateBlockPreviewInfo(p_timeStamp, imageLinks);
|
||||
|
||||
updateEditorBlockImages();
|
||||
clearBlockObsoletePreviewInfo(p_timeStamp, PreviewSource::ImageLink);
|
||||
|
||||
clearObsoleteImages(p_timeStamp, PreviewSource::ImageLink);
|
||||
}
|
||||
|
||||
// Returns true if p_text[p_start, p_end) is all spaces.
|
||||
@ -218,30 +227,6 @@ QString VPreviewManager::fetchImagePathToPreview(const QString &p_text, QString
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
void VPreviewManager::updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks)
|
||||
{
|
||||
QVector<VBlockImageInfo2> &blockInfos = m_blockImageInfo[PreviewSource::ImageLink];
|
||||
blockInfos.clear();
|
||||
|
||||
for (int i = 0; i < p_imageLinks.size(); ++i) {
|
||||
const ImageLinkInfo &link = p_imageLinks[i];
|
||||
|
||||
QString name = imageResourceName(link);
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VBlockImageInfo2 info(link.m_blockNumber,
|
||||
name,
|
||||
link.m_startPos - link.m_blockPos,
|
||||
link.m_endPos - link.m_blockPos,
|
||||
link.m_padding,
|
||||
!link.m_isBlock);
|
||||
|
||||
blockInfos.push_back(info);
|
||||
}
|
||||
}
|
||||
|
||||
QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
|
||||
{
|
||||
QString name = p_link.m_linkShortUrl;
|
||||
@ -271,14 +256,6 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
|
||||
return name;
|
||||
}
|
||||
|
||||
void VPreviewManager::updateEditorBlockImages()
|
||||
{
|
||||
// TODO: need to combine all preview sources.
|
||||
Q_ASSERT(m_blockImageInfo.size() == 1);
|
||||
|
||||
m_editor->updateBlockImages(m_blockImageInfo[PreviewSource::ImageLink]);
|
||||
}
|
||||
|
||||
int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
|
||||
{
|
||||
static QHash<QString, int> spaceWidthOfFonts;
|
||||
@ -316,3 +293,85 @@ int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
|
||||
|
||||
return spaceWidth * nrSpaces;
|
||||
}
|
||||
|
||||
void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
|
||||
const QVector<ImageLinkInfo> &p_imageLinks)
|
||||
{
|
||||
for (auto const & link : p_imageLinks) {
|
||||
QTextBlock block = m_document->findBlockByNumber(link.m_blockNumber);
|
||||
if (!block.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString name = imageResourceName(link);
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
Q_ASSERT(blockData);
|
||||
|
||||
VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink,
|
||||
p_timeStamp,
|
||||
link.m_startPos - link.m_blockPos,
|
||||
link.m_endPos - link.m_blockPos,
|
||||
link.m_padding,
|
||||
!link.m_isBlock,
|
||||
name,
|
||||
m_editor->imageSize(name));
|
||||
blockData->insertPreviewInfo(info);
|
||||
|
||||
imageCache(PreviewSource::ImageLink).insert(name, p_timeStamp);
|
||||
|
||||
qDebug() << "block" << link.m_blockNumber
|
||||
<< imageCache(PreviewSource::ImageLink).size()
|
||||
<< blockData->toString();
|
||||
}
|
||||
}
|
||||
|
||||
void VPreviewManager::clearObsoleteImages(long long p_timeStamp, PreviewSource p_source)
|
||||
{
|
||||
auto cache = imageCache(p_source);
|
||||
|
||||
for (auto it = cache.begin(); it != cache.end();) {
|
||||
if (it.value() < p_timeStamp) {
|
||||
m_editor->removeImage(it.key());
|
||||
it = cache.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
|
||||
PreviewSource p_source)
|
||||
{
|
||||
QSet<int> affectedBlocks;
|
||||
QVector<int> obsoleteBlocks;
|
||||
auto blocks = m_highlighter->getPossiblePreviewBlocks();
|
||||
qDebug() << "possible preview blocks" << blocks;
|
||||
for (auto i : blocks) {
|
||||
QTextBlock block = m_document->findBlockByNumber(i);
|
||||
if (!block.isValid()) {
|
||||
obsoleteBlocks.append(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
if (!blockData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blockData->clearObsoletePreview(p_timeStamp, p_source)) {
|
||||
affectedBlocks.insert(i);
|
||||
}
|
||||
|
||||
if (blockData->getPreviews().isEmpty()) {
|
||||
obsoleteBlocks.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
m_highlighter->clearPossiblePreviewBlocks(obsoleteBlocks);
|
||||
|
||||
m_editor->relayout(affectedBlocks);
|
||||
}
|
||||
|
@ -8,15 +8,18 @@
|
||||
#include <QVector>
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
class VDownloader;
|
||||
|
||||
typedef long long TS;
|
||||
|
||||
|
||||
class VPreviewManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VPreviewManager(VMdEditor *p_editor);
|
||||
VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter);
|
||||
|
||||
void setPreviewEnabled(bool p_enabled);
|
||||
|
||||
@ -36,13 +39,6 @@ private slots:
|
||||
void imageDownloaded(const QByteArray &p_data, const QString &p_url);
|
||||
|
||||
private:
|
||||
// Sources of the preview.
|
||||
enum PreviewSource
|
||||
{
|
||||
ImageLink = 0,
|
||||
Invalid
|
||||
};
|
||||
|
||||
struct ImageLinkInfo
|
||||
{
|
||||
ImageLinkInfo()
|
||||
@ -93,7 +89,7 @@ private:
|
||||
};
|
||||
|
||||
// Start to preview images according to image links.
|
||||
void previewImages();
|
||||
void previewImages(TS p_timeStamp);
|
||||
|
||||
// According to m_imageRegions, fetch the image link Url.
|
||||
// @p_imageRegions: output.
|
||||
@ -106,21 +102,29 @@ private:
|
||||
// @p_url: contains the short URL in ![]().
|
||||
QString fetchImagePathToPreview(const QString &p_text, QString &p_url);
|
||||
|
||||
void updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks);
|
||||
// Update the preview info of related blocks according to @p_imageLinks.
|
||||
void updateBlockPreviewInfo(TS p_timeStamp, const QVector<ImageLinkInfo> &p_imageLinks);
|
||||
|
||||
// Get the name of the image in the resource manager.
|
||||
// Will add the image to the resource manager if not exists.
|
||||
// Returns empty if fail to add the image to the resource manager.
|
||||
QString imageResourceName(const ImageLinkInfo &p_link);
|
||||
|
||||
// Ask the editor to preview images.
|
||||
void updateEditorBlockImages();
|
||||
|
||||
// Calculate the block margin (prefix spaces) in pixels.
|
||||
int calculateBlockMargin(const QTextBlock &p_block);
|
||||
|
||||
QHash<QString, long long> &imageCache(PreviewSource p_source);
|
||||
|
||||
void clearObsoleteImages(long long p_timeStamp, PreviewSource p_source);
|
||||
|
||||
void clearBlockObsoletePreviewInfo(long long p_timeStamp, PreviewSource p_source);
|
||||
|
||||
VMdEditor *m_editor;
|
||||
|
||||
QTextDocument *m_document;
|
||||
|
||||
HGMarkdownHighlighter *m_highlighter;
|
||||
|
||||
VDownloader *m_downloader;
|
||||
|
||||
// Whether preview is enabled.
|
||||
@ -129,13 +133,18 @@ private:
|
||||
// Regions of all the image links.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// All preview images and information.
|
||||
// Each preview source corresponds to one vector.
|
||||
QVector<QVector<VBlockImageInfo2>> m_blockImageInfo;
|
||||
|
||||
// Map from URL to name in the resource manager.
|
||||
// Used for downloading images.
|
||||
QHash<QString, QString> m_urlToName;
|
||||
|
||||
TS m_timeStamp;
|
||||
|
||||
// Used to discard obsolete images. One per each preview source.
|
||||
QHash<QString, long long> m_imageCaches[(int)PreviewSource::MaxNumberOfSources];
|
||||
};
|
||||
|
||||
inline QHash<QString, long long> &VPreviewManager::imageCache(PreviewSource p_source)
|
||||
{
|
||||
return m_imageCaches[(int)p_source];
|
||||
}
|
||||
#endif // VPREVIEWMANAGER_H
|
||||
|
@ -1,11 +1,101 @@
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
VTextBlockData::VTextBlockData()
|
||||
: QTextBlockUserData(),
|
||||
m_containsPreviewImage(false)
|
||||
: QTextBlockUserData()
|
||||
{
|
||||
}
|
||||
|
||||
VTextBlockData::~VTextBlockData()
|
||||
{
|
||||
for (auto it : m_previews) {
|
||||
delete it;
|
||||
}
|
||||
|
||||
m_previews.clear();
|
||||
}
|
||||
|
||||
void VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
|
||||
{
|
||||
bool inserted = false;
|
||||
for (auto it = m_previews.begin(); it != m_previews.end();) {
|
||||
VPreviewInfo *ele = *it;
|
||||
|
||||
if (p_info->m_imageInfo < ele->m_imageInfo) {
|
||||
// Insert p_info here.
|
||||
m_previews.insert(it, p_info);
|
||||
inserted = true;
|
||||
break;
|
||||
} else if (p_info->m_imageInfo == ele->m_imageInfo) {
|
||||
// Update the timestamp.
|
||||
delete ele;
|
||||
*it = p_info;
|
||||
inserted = true;
|
||||
qDebug() << "update eixsting image's timestamp" << p_info->m_imageInfo.toString();
|
||||
break;
|
||||
} else if (p_info->m_imageInfo.intersect(ele->m_imageInfo)) {
|
||||
// The new one intersect with an old one.
|
||||
// Remove the old one.
|
||||
Q_ASSERT(ele->m_timeStamp < p_info->m_timeStamp);
|
||||
qDebug() << "remove intersecting old image" << ele->m_imageInfo.toString();
|
||||
delete ele;
|
||||
it = m_previews.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
// Append it.
|
||||
m_previews.append(p_info);
|
||||
}
|
||||
|
||||
Q_ASSERT(checkOrder());
|
||||
}
|
||||
|
||||
QString VTextBlockData::toString() const
|
||||
{
|
||||
QString ret;
|
||||
for (int i = 0; i < m_previews.size(); ++i) {
|
||||
ret += QString("preview %1: source %2 ts %3 image %4\n")
|
||||
.arg(i)
|
||||
.arg((int)m_previews[i]->m_source)
|
||||
.arg(m_previews[i]->m_timeStamp)
|
||||
.arg(m_previews[i]->m_imageInfo.toString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VTextBlockData::checkOrder() const
|
||||
{
|
||||
for (int i = 1; i < m_previews.size(); ++i) {
|
||||
if (!(m_previews[i - 1]->m_imageInfo < m_previews[i]->m_imageInfo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p_source)
|
||||
{
|
||||
bool deleted = false;
|
||||
for (auto it = m_previews.begin(); it != m_previews.end();) {
|
||||
VPreviewInfo *ele = *it;
|
||||
|
||||
if (ele->m_source == p_source
|
||||
&& ele->m_timeStamp < p_timeStamp) {
|
||||
// Remove it.
|
||||
qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString();
|
||||
delete ele;
|
||||
it = m_previews.erase(it);
|
||||
deleted = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
@ -2,7 +2,133 @@
|
||||
#define VTEXTBLOCKDATA_H
|
||||
|
||||
#include <QTextBlockUserData>
|
||||
#include <QVector>
|
||||
|
||||
// Sources of the preview.
|
||||
enum class PreviewSource
|
||||
{
|
||||
ImageLink = 0,
|
||||
MaxNumberOfSources
|
||||
};
|
||||
|
||||
|
||||
// Info about a previewed image.
|
||||
struct VPreviewedImageInfo
|
||||
{
|
||||
VPreviewedImageInfo()
|
||||
: m_startPos(-1),
|
||||
m_endPos(-1),
|
||||
m_padding(0),
|
||||
m_inline(false)
|
||||
{
|
||||
}
|
||||
|
||||
VPreviewedImageInfo(int p_startPos,
|
||||
int p_endPos,
|
||||
int p_padding,
|
||||
bool p_inline,
|
||||
const QString &p_imageName,
|
||||
const QSize &p_imageSize)
|
||||
: m_startPos(p_startPos),
|
||||
m_endPos(p_endPos),
|
||||
m_padding(p_padding),
|
||||
m_inline(p_inline),
|
||||
m_imageName(p_imageName),
|
||||
m_imageSize(p_imageSize)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const VPreviewedImageInfo &a) const
|
||||
{
|
||||
return m_endPos <= a.m_startPos;
|
||||
}
|
||||
|
||||
bool operator==(const VPreviewedImageInfo &a) const
|
||||
{
|
||||
return m_startPos == a.m_startPos
|
||||
&& m_endPos == a.m_endPos
|
||||
&& m_padding == a.m_padding
|
||||
&& m_inline == a.m_inline
|
||||
&& m_imageName == a.m_imageName
|
||||
&& m_imageSize == a.m_imageSize;
|
||||
}
|
||||
|
||||
bool intersect(const VPreviewedImageInfo &a) const
|
||||
{
|
||||
return !(m_endPos <= a.m_startPos || m_startPos >= a.m_endPos);
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("previewed image (%1): [%2, %3] padding %4 inline %5 (%6,%7)")
|
||||
.arg(m_imageName)
|
||||
.arg(m_startPos)
|
||||
.arg(m_endPos)
|
||||
.arg(m_padding)
|
||||
.arg(m_inline)
|
||||
.arg(m_imageSize.width())
|
||||
.arg(m_imageSize.height());
|
||||
}
|
||||
|
||||
// Start position of text corresponding to the image within block.
|
||||
int m_startPos;
|
||||
|
||||
// End position of text corresponding to the image within block.
|
||||
int m_endPos;
|
||||
|
||||
// Padding of the image. Only valid for block image.
|
||||
int m_padding;
|
||||
|
||||
// Whether it is inline image or block image.
|
||||
bool m_inline;
|
||||
|
||||
// Image name in the resource manager.
|
||||
QString m_imageName;
|
||||
|
||||
// Image size of the image. Cache for performance.
|
||||
QSize m_imageSize;
|
||||
};
|
||||
|
||||
|
||||
struct VPreviewInfo
|
||||
{
|
||||
VPreviewInfo()
|
||||
: m_source(PreviewSource::ImageLink),
|
||||
m_timeStamp(0)
|
||||
{
|
||||
}
|
||||
|
||||
VPreviewInfo(PreviewSource p_source,
|
||||
long long p_timeStamp,
|
||||
int p_startPos,
|
||||
int p_endPos,
|
||||
int p_padding,
|
||||
bool p_inline,
|
||||
const QString &p_imageName,
|
||||
const QSize &p_imageSize)
|
||||
: m_source(p_source),
|
||||
m_timeStamp(p_timeStamp),
|
||||
m_imageInfo(p_startPos,
|
||||
p_endPos,
|
||||
p_padding,
|
||||
p_inline,
|
||||
p_imageName,
|
||||
p_imageSize)
|
||||
{
|
||||
}
|
||||
|
||||
// Source of this preview.
|
||||
PreviewSource m_source;
|
||||
|
||||
// Timestamp for this preview.
|
||||
long long m_timeStamp;
|
||||
|
||||
// Image info of this preview.
|
||||
VPreviewedImageInfo m_imageInfo;
|
||||
};
|
||||
|
||||
|
||||
// User data for each block.
|
||||
class VTextBlockData : public QTextBlockUserData
|
||||
{
|
||||
public:
|
||||
@ -10,35 +136,28 @@ public:
|
||||
|
||||
~VTextBlockData();
|
||||
|
||||
bool containsPreviewImage() const;
|
||||
// Insert @p_info into m_previews, preserving the order.
|
||||
void insertPreviewInfo(VPreviewInfo *p_info);
|
||||
|
||||
static bool containsPreviewImage(const QTextBlock &p_block);
|
||||
// For degub only.
|
||||
QString toString() const;
|
||||
|
||||
void setContainsPreviewImage(bool p_contains);
|
||||
const QVector<VPreviewInfo *> &getPreviews() const;
|
||||
|
||||
// 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<VPreviewInfo *> m_previews;
|
||||
};
|
||||
|
||||
inline bool VTextBlockData::containsPreviewImage() const
|
||||
inline const QVector<VPreviewInfo *> &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<VTextBlockData *>(p_block.userData());
|
||||
if (!blockData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return blockData->containsPreviewImage();
|
||||
return m_previews;
|
||||
}
|
||||
|
||||
#endif // VTEXTBLOCKDATA_H
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "vimageresourcemanager2.h"
|
||||
#include "vtextedit.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
#define MARKER_THICKNESS 2
|
||||
#define MAX_INLINE_IMAGE_HEIGHT 400
|
||||
@ -545,16 +546,17 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
|
||||
{
|
||||
// Handle block inline image.
|
||||
bool hasInlineImages = false;
|
||||
const QVector<VBlockImageInfo2> *info = NULL;
|
||||
const QVector<VPreviewInfo *> *info = NULL;
|
||||
if (m_blockImageEnabled) {
|
||||
info = m_imageMgr->findImageInfoByBlock(p_block.blockNumber());
|
||||
|
||||
if (info
|
||||
&& !info->isEmpty()
|
||||
&& info->first().m_inlineImage) {
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
|
||||
if (blockData) {
|
||||
info = &(blockData->getPreviews());
|
||||
if (!info->isEmpty()
|
||||
&& info->first()->m_imageInfo.m_inline) {
|
||||
hasInlineImages = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_tl->beginLayout();
|
||||
|
||||
@ -571,7 +573,7 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
|
||||
p_height += m_lineLeading;
|
||||
|
||||
if (hasInlineImages) {
|
||||
QVector<const VBlockImageInfo2 *> images;
|
||||
QVector<const VPreviewedImageInfo *> images;
|
||||
QVector<QPair<qreal, qreal>> imageRange;
|
||||
qreal imgHeight = fetchInlineImagesForOneLine(*info,
|
||||
&line,
|
||||
@ -604,7 +606,7 @@ qreal VTextDocumentLayout::layoutLines(const QTextBlock &p_block,
|
||||
return p_height;
|
||||
}
|
||||
|
||||
void VTextDocumentLayout::layoutInlineImage(const VBlockImageInfo2 *p_info,
|
||||
void VTextDocumentLayout::layoutInlineImage(const VPreviewedImageInfo *p_info,
|
||||
qreal p_heightInBlock,
|
||||
qreal p_imageSpaceHeight,
|
||||
qreal p_xStart,
|
||||
@ -756,10 +758,12 @@ QRectF VTextDocumentLayout::blockRectFromTextLayout(const QTextBlock &p_block,
|
||||
|
||||
// Handle block non-inline image.
|
||||
if (m_blockImageEnabled) {
|
||||
const QVector<VBlockImageInfo2> *info = m_imageMgr->findImageInfoByBlock(p_block.blockNumber());
|
||||
if (info && info->size() == 1) {
|
||||
const VBlockImageInfo2& img = info->first();
|
||||
if (!img.m_inlineImage && !img.m_imageSize.isNull()) {
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(p_block.userData());
|
||||
if (blockData) {
|
||||
const QVector<VPreviewInfo *> &info = blockData->getPreviews();
|
||||
if (info.size() == 1) {
|
||||
const VPreviewedImageInfo& img = info.first()->m_imageInfo;
|
||||
if (!img.m_inline) {
|
||||
int maximumWidth = tlRect.width();
|
||||
int padding;
|
||||
QSize size;
|
||||
@ -779,6 +783,7 @@ QRectF VTextDocumentLayout::blockRectFromTextLayout(const QTextBlock &p_block,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
br.adjust(0, 0, m_margin + m_cursorWidth, 0);
|
||||
|
||||
@ -831,7 +836,7 @@ void VTextDocumentLayout::setBlockImageEnabled(bool p_enabled)
|
||||
relayout();
|
||||
}
|
||||
|
||||
void VTextDocumentLayout::adjustImagePaddingAndSize(const VBlockImageInfo2 *p_info,
|
||||
void VTextDocumentLayout::adjustImagePaddingAndSize(const VPreviewedImageInfo *p_info,
|
||||
int p_maximumWidth,
|
||||
int &p_padding,
|
||||
QSize &p_size) const
|
||||
@ -873,7 +878,10 @@ void VTextDocumentLayout::drawImages(QPainter *p_painter,
|
||||
|
||||
for (auto const & img : images) {
|
||||
const QPixmap *image = m_imageMgr->findImage(img.m_name);
|
||||
Q_ASSERT(image);
|
||||
if (!image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QRect targetRect = img.m_rect.adjusted(p_offset.x(),
|
||||
p_offset.y(),
|
||||
p_offset.x(),
|
||||
@ -952,11 +960,11 @@ void VTextDocumentLayout::relayout(const QSet<int> &p_blocks)
|
||||
updateDocumentSize();
|
||||
}
|
||||
|
||||
qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VBlockImageInfo2> &p_info,
|
||||
qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VPreviewInfo *> &p_info,
|
||||
const QTextLine *p_line,
|
||||
qreal p_margin,
|
||||
int &p_index,
|
||||
QVector<const VBlockImageInfo2 *> &p_images,
|
||||
QVector<const VPreviewedImageInfo *> &p_images,
|
||||
QVector<QPair<qreal, qreal>> &p_imageRange)
|
||||
{
|
||||
qreal maxHeight = 0;
|
||||
@ -964,13 +972,8 @@ qreal VTextDocumentLayout::fetchInlineImagesForOneLine(const QVector<VBlockImage
|
||||
int end = p_line->textLength() + start;
|
||||
|
||||
for (int i = 0; i < p_info.size(); ++i) {
|
||||
const VBlockImageInfo2 &img = p_info[i];
|
||||
Q_ASSERT(img.m_inlineImage);
|
||||
|
||||
if (img.m_imageSize.isNull()) {
|
||||
p_index = i + 1;
|
||||
continue;
|
||||
}
|
||||
const VPreviewedImageInfo &img = p_info[i]->m_imageInfo;
|
||||
Q_ASSERT(img.m_inline);
|
||||
|
||||
if (img.m_startPos >= start && img.m_startPos < end) {
|
||||
// Start of a new image.
|
||||
|
@ -7,7 +7,8 @@
|
||||
#include <QSet>
|
||||
|
||||
class VImageResourceManager2;
|
||||
struct VBlockImageInfo2;
|
||||
struct VPreviewedImageInfo;
|
||||
struct VPreviewInfo;
|
||||
|
||||
|
||||
class VTextDocumentLayout : public QAbstractTextDocumentLayout
|
||||
@ -141,7 +142,7 @@ private:
|
||||
// Layout inline image in a line.
|
||||
// @p_info: if NULL, means just layout a marker.
|
||||
// Returns the image height.
|
||||
void layoutInlineImage(const VBlockImageInfo2 *p_info,
|
||||
void layoutInlineImage(const VPreviewedImageInfo *p_info,
|
||||
qreal p_heightInBlock,
|
||||
qreal p_imageSpaceHeight,
|
||||
qreal p_xStart,
|
||||
@ -154,11 +155,11 @@ private:
|
||||
// @p_images: contains all images and markers (NULL element indicates it
|
||||
// is just a placeholder for the marker.
|
||||
// Returns the maximum height of the images.
|
||||
qreal fetchInlineImagesForOneLine(const QVector<VBlockImageInfo2> &p_info,
|
||||
qreal fetchInlineImagesForOneLine(const QVector<VPreviewInfo *> &p_info,
|
||||
const QTextLine *p_line,
|
||||
qreal p_margin,
|
||||
int &p_index,
|
||||
QVector<const VBlockImageInfo2 *> &p_images,
|
||||
QVector<const VPreviewedImageInfo *> &p_images,
|
||||
QVector<QPair<qreal, qreal>> &p_imageRange);
|
||||
|
||||
// Clear the layout of @p_block.
|
||||
@ -211,7 +212,7 @@ private:
|
||||
// remain the same.
|
||||
void updateDocumentSizeWithOneBlockChanged(int p_blockNumber);
|
||||
|
||||
void adjustImagePaddingAndSize(const VBlockImageInfo2 *p_info,
|
||||
void adjustImagePaddingAndSize(const VPreviewedImageInfo *p_info,
|
||||
int p_maximumWidth,
|
||||
int &p_padding,
|
||||
QSize &p_size) const;
|
||||
|
@ -269,14 +269,6 @@ int VTextEdit::contentOffsetY() const
|
||||
return -(sb->value());
|
||||
}
|
||||
|
||||
void VTextEdit::updateBlockImages(const QVector<VBlockImageInfo2> &p_blocksInfo)
|
||||
{
|
||||
if (m_blockImageEnabled) {
|
||||
auto blocks = m_imageMgr->updateBlockInfos(p_blocksInfo);
|
||||
getLayout()->relayout(blocks);
|
||||
}
|
||||
}
|
||||
|
||||
void VTextEdit::clearBlockImages()
|
||||
{
|
||||
m_imageMgr->clear();
|
||||
@ -284,11 +276,26 @@ void VTextEdit::clearBlockImages()
|
||||
getLayout()->relayout();
|
||||
}
|
||||
|
||||
void VTextEdit::relayout(const QSet<int> &p_blocks)
|
||||
{
|
||||
getLayout()->relayout(p_blocks);
|
||||
}
|
||||
|
||||
bool VTextEdit::containsImage(const QString &p_imageName) const
|
||||
{
|
||||
return m_imageMgr->contains(p_imageName);
|
||||
}
|
||||
|
||||
QSize VTextEdit::imageSize(const QString &p_imageName) const
|
||||
{
|
||||
const QPixmap *img = m_imageMgr->findImage(p_imageName);
|
||||
if (img) {
|
||||
return img->size();
|
||||
}
|
||||
|
||||
return QSize();
|
||||
}
|
||||
|
||||
void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
|
||||
{
|
||||
if (m_blockImageEnabled) {
|
||||
@ -296,6 +303,11 @@ void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
|
||||
}
|
||||
}
|
||||
|
||||
void VTextEdit::removeImage(const QString &p_imageName)
|
||||
{
|
||||
m_imageMgr->removeImage(p_imageName);
|
||||
}
|
||||
|
||||
void VTextEdit::setBlockImageEnabled(bool p_enabled)
|
||||
{
|
||||
if (m_blockImageEnabled == p_enabled) {
|
||||
|
104
src/vtextedit.h
104
src/vtextedit.h
@ -12,98 +12,6 @@ class QResizeEvent;
|
||||
class VImageResourceManager2;
|
||||
|
||||
|
||||
struct VBlockImageInfo2
|
||||
{
|
||||
public:
|
||||
VBlockImageInfo2()
|
||||
: m_blockNumber(-1),
|
||||
m_startPos(-1),
|
||||
m_endPos(-1),
|
||||
m_padding(0),
|
||||
m_inlineImage(false)
|
||||
{
|
||||
}
|
||||
|
||||
VBlockImageInfo2(int p_blockNumber,
|
||||
const QString &p_imageName,
|
||||
int p_startPos = -1,
|
||||
int p_endPos = -1,
|
||||
int p_padding = 0,
|
||||
bool p_inlineImage = false)
|
||||
: m_blockNumber(p_blockNumber),
|
||||
m_startPos(p_startPos),
|
||||
m_endPos(p_endPos),
|
||||
m_padding(p_padding),
|
||||
m_inlineImage(p_inlineImage),
|
||||
m_imageName(p_imageName)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const VBlockImageInfo2 &p_other) const
|
||||
{
|
||||
return m_blockNumber == p_other.m_blockNumber
|
||||
&& m_startPos == p_other.m_startPos
|
||||
&& m_endPos == p_other.m_endPos
|
||||
&& m_padding == p_other.m_padding
|
||||
&& m_inlineImage == p_other.m_inlineImage
|
||||
&& m_imageName == p_other.m_imageName
|
||||
&& m_imageSize == p_other.m_imageSize;
|
||||
}
|
||||
|
||||
bool operator<(const VBlockImageInfo2 &p_other) const
|
||||
{
|
||||
if (m_blockNumber < p_other.m_blockNumber) {
|
||||
return true;
|
||||
} else if (m_blockNumber > p_other.m_blockNumber) {
|
||||
return false;
|
||||
} else if (m_startPos < p_other.m_startPos) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("VBlockImageInfo2 block %1 start %2 end %3 padding %4 "
|
||||
"inline %5 image %6 size [%7,%8]")
|
||||
.arg(m_blockNumber)
|
||||
.arg(m_startPos)
|
||||
.arg(m_endPos)
|
||||
.arg(m_padding)
|
||||
.arg(m_inlineImage)
|
||||
.arg(m_imageName)
|
||||
.arg(m_imageSize.width())
|
||||
.arg(m_imageSize.height());
|
||||
}
|
||||
|
||||
// Block number.
|
||||
int m_blockNumber;
|
||||
|
||||
// Start position of the image link in block.
|
||||
int m_startPos;
|
||||
|
||||
// End position of the image link in block.
|
||||
int m_endPos;
|
||||
|
||||
// Padding of the image.
|
||||
int m_padding;
|
||||
|
||||
// Whether it is inline image or block image.
|
||||
bool m_inlineImage;
|
||||
|
||||
// The name of the image corresponding to this block.
|
||||
QString m_imageName;
|
||||
|
||||
private:
|
||||
// For cache only.
|
||||
QSize m_imageSize;
|
||||
|
||||
friend class VImageResourceManager2;
|
||||
friend class VTextDocumentLayout;
|
||||
};
|
||||
|
||||
|
||||
class VTextEdit : public QTextEdit, public VTextEditWithLineNumber
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -126,24 +34,28 @@ public:
|
||||
|
||||
QTextBlock firstVisibleBlock() const;
|
||||
|
||||
// Update images of these given blocks.
|
||||
// Images of blocks not given here will be clear.
|
||||
void updateBlockImages(const QVector<VBlockImageInfo2> &p_blocksInfo);
|
||||
|
||||
void clearBlockImages();
|
||||
|
||||
// Whether the resoruce manager contains image of name @p_imageName.
|
||||
bool containsImage(const QString &p_imageName) const;
|
||||
|
||||
// Get the image size from the resource manager.
|
||||
QSize imageSize(const QString &p_imageName) const;
|
||||
|
||||
// Add an image to the resources.
|
||||
void addImage(const QString &p_imageName, const QPixmap &p_image);
|
||||
|
||||
// Remove an image from the resources.
|
||||
void removeImage(const QString &p_imageName);
|
||||
|
||||
void setBlockImageEnabled(bool p_enabled);
|
||||
|
||||
void setImageWidthConstrainted(bool p_enabled);
|
||||
|
||||
void setImageLineColor(const QColor &p_color);
|
||||
|
||||
void relayout(const QSet<int> &p_blocks);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user