mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
refactor image preview logics by adding VImagePreviewer
1. Support previewing non-relative local images; 2. Support previewing network images;
This commit is contained in:
parent
27b0d99965
commit
a8614839d9
@ -151,41 +151,10 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateImageBlocks();
|
|
||||||
|
|
||||||
pmh_free_elements(result);
|
pmh_free_elements(result);
|
||||||
result = NULL;
|
result = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HGMarkdownHighlighter::updateImageBlocks()
|
|
||||||
{
|
|
||||||
imageBlocks.clear();
|
|
||||||
for (int i = 0; i < highlightingStyles.size(); i++)
|
|
||||||
{
|
|
||||||
const HighlightingStyle &style = highlightingStyles[i];
|
|
||||||
if (style.type != pmh_IMAGE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pmh_element *elem_cursor = result[style.type];
|
|
||||||
while (elem_cursor != NULL)
|
|
||||||
{
|
|
||||||
if (elem_cursor->end <= elem_cursor->pos) {
|
|
||||||
elem_cursor = elem_cursor->next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startBlock = document->findBlock(elem_cursor->pos).blockNumber();
|
|
||||||
int endBlock = document->findBlock(elem_cursor->end).blockNumber();
|
|
||||||
for (int i = startBlock; i <= endBlock; ++i) {
|
|
||||||
imageBlocks.insert(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
elem_cursor = elem_cursor->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit imageBlocksUpdated(imageBlocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex)
|
void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex)
|
||||||
{
|
{
|
||||||
int startBlockNum = document->findBlock(pos).blockNumber();
|
int startBlockNum = document->findBlock(pos).blockNumber();
|
||||||
|
@ -91,7 +91,6 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void highlightCompleted();
|
void highlightCompleted();
|
||||||
void imageBlocksUpdated(QSet<int> p_blocks);
|
|
||||||
void codeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks);
|
void codeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -124,8 +123,6 @@ private:
|
|||||||
|
|
||||||
int m_numOfCodeBlockHighlightsToRecv;
|
int m_numOfCodeBlockHighlightsToRecv;
|
||||||
|
|
||||||
// Block numbers containing image link(s).
|
|
||||||
QSet<int> imageBlocks;
|
|
||||||
QAtomicInt parsing;
|
QAtomicInt parsing;
|
||||||
QTimer *timer;
|
QTimer *timer;
|
||||||
int waitInterval;
|
int waitInterval;
|
||||||
@ -144,7 +141,7 @@ private:
|
|||||||
void initBlockHighlightFromResult(int nrBlocks);
|
void initBlockHighlightFromResult(int nrBlocks);
|
||||||
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
||||||
int styleIndex);
|
int styleIndex);
|
||||||
void updateImageBlocks();
|
|
||||||
// Return true if there are fenced code blocks and it will call rehighlight() later.
|
// Return true if there are fenced code blocks and it will call rehighlight() later.
|
||||||
// Return false if there is none.
|
// Return false if there is none.
|
||||||
bool updateCodeBlocks();
|
bool updateCodeBlocks();
|
||||||
|
@ -22,6 +22,7 @@ enable_mathjax=false
|
|||||||
web_zoom_factor=-1
|
web_zoom_factor=-1
|
||||||
; Syntax highlight within code blocks in edit mode
|
; Syntax highlight within code blocks in edit mode
|
||||||
enable_code_block_highlight=true
|
enable_code_block_highlight=true
|
||||||
|
enable_preview_images=true
|
||||||
|
|
||||||
[session]
|
[session]
|
||||||
tools_dock_checked=true
|
tools_dock_checked=true
|
||||||
|
@ -60,7 +60,8 @@ SOURCES += main.cpp\
|
|||||||
vopenedlistmenu.cpp \
|
vopenedlistmenu.cpp \
|
||||||
vorphanfile.cpp \
|
vorphanfile.cpp \
|
||||||
vcodeblockhighlighthelper.cpp \
|
vcodeblockhighlighthelper.cpp \
|
||||||
vwebview.cpp
|
vwebview.cpp \
|
||||||
|
vimagepreviewer.cpp
|
||||||
|
|
||||||
HEADERS += vmainwindow.h \
|
HEADERS += vmainwindow.h \
|
||||||
vdirectorytree.h \
|
vdirectorytree.h \
|
||||||
@ -107,7 +108,8 @@ HEADERS += vmainwindow.h \
|
|||||||
vnavigationmode.h \
|
vnavigationmode.h \
|
||||||
vorphanfile.h \
|
vorphanfile.h \
|
||||||
vcodeblockhighlighthelper.h \
|
vcodeblockhighlighthelper.h \
|
||||||
vwebview.h
|
vwebview.h \
|
||||||
|
vimagepreviewer.h
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
vnote.qrc \
|
vnote.qrc \
|
||||||
|
@ -123,6 +123,9 @@ void VConfigManager::initialize()
|
|||||||
|
|
||||||
m_enableCodeBlockHighlight = getConfigFromSettings("global",
|
m_enableCodeBlockHighlight = getConfigFromSettings("global",
|
||||||
"enable_code_block_highlight").toBool();
|
"enable_code_block_highlight").toBool();
|
||||||
|
|
||||||
|
m_enablePreviewImages = getConfigFromSettings("global",
|
||||||
|
"enable_preview_images").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VConfigManager::readPredefinedColorsFromSettings()
|
void VConfigManager::readPredefinedColorsFromSettings()
|
||||||
|
@ -157,6 +157,9 @@ public:
|
|||||||
inline bool getEnableCodeBlockHighlight() const;
|
inline bool getEnableCodeBlockHighlight() const;
|
||||||
inline void setEnableCodeBlockHighlight(bool p_enabled);
|
inline void setEnableCodeBlockHighlight(bool p_enabled);
|
||||||
|
|
||||||
|
inline bool getEnablePreviewImages() const;
|
||||||
|
inline void setEnablePreviewImages(bool p_enabled);
|
||||||
|
|
||||||
// Get the folder the ini file exists.
|
// Get the folder the ini file exists.
|
||||||
QString getConfigFolder() const;
|
QString getConfigFolder() const;
|
||||||
|
|
||||||
@ -264,6 +267,9 @@ private:
|
|||||||
// Enable colde block syntax highlight.
|
// Enable colde block syntax highlight.
|
||||||
bool m_enableCodeBlockHighlight;
|
bool m_enableCodeBlockHighlight;
|
||||||
|
|
||||||
|
// Preview images in edit mode.
|
||||||
|
bool m_enablePreviewImages;
|
||||||
|
|
||||||
// The name of the config file in each directory, obsolete.
|
// The name of the config file in each directory, obsolete.
|
||||||
// Use c_dirConfigFile instead.
|
// Use c_dirConfigFile instead.
|
||||||
static const QString c_obsoleteDirConfigFile;
|
static const QString c_obsoleteDirConfigFile;
|
||||||
@ -689,4 +695,20 @@ inline void VConfigManager::setEnableCodeBlockHighlight(bool p_enabled)
|
|||||||
m_enableCodeBlockHighlight);
|
m_enableCodeBlockHighlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool VConfigManager::getEnablePreviewImages() const
|
||||||
|
{
|
||||||
|
return m_enablePreviewImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void VConfigManager::setEnablePreviewImages(bool p_enabled)
|
||||||
|
{
|
||||||
|
if (m_enablePreviewImages == p_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_enablePreviewImages = p_enabled;
|
||||||
|
setConfigToSettings("global", "enable_preview_images",
|
||||||
|
m_enablePreviewImages);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // VCONFIGMANAGER_H
|
#endif // VCONFIGMANAGER_H
|
||||||
|
@ -11,13 +11,14 @@ void VDownloader::handleDownloadFinished(QNetworkReply *reply)
|
|||||||
{
|
{
|
||||||
data = reply->readAll();
|
data = reply->readAll();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
emit downloadFinished(data);
|
qDebug() << "VDownloader receive" << reply->url().toString();
|
||||||
|
emit downloadFinished(data, reply->url().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VDownloader::download(QUrl url)
|
void VDownloader::download(const QUrl &p_url)
|
||||||
{
|
{
|
||||||
Q_ASSERT(url.isValid());
|
Q_ASSERT(p_url.isValid());
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(p_url);
|
||||||
webCtrl.get(request);
|
webCtrl.get(request);
|
||||||
qDebug() << "VDownloader get" << url.toString();
|
qDebug() << "VDownloader get" << p_url.toString();
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ class VDownloader : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit VDownloader(QObject *parent = 0);
|
explicit VDownloader(QObject *parent = 0);
|
||||||
void download(QUrl url);
|
void download(const QUrl &p_url);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void downloadFinished(const QByteArray &data);
|
void downloadFinished(const QByteArray &data, const QString &url);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleDownloadFinished(QNetworkReply *reply);
|
void handleDownloadFinished(QNetworkReply *reply);
|
||||||
|
@ -563,3 +563,9 @@ void VEdit::handleEditAct()
|
|||||||
{
|
{
|
||||||
emit editNote();
|
emit editNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VFile *VEdit::getFile() const
|
||||||
|
{
|
||||||
|
return m_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ public:
|
|||||||
const QString &p_replaceText);
|
const QString &p_replaceText);
|
||||||
void setReadOnly(bool p_ro);
|
void setReadOnly(bool p_ro);
|
||||||
void clearSearchedWordHighlight();
|
void clearSearchedWordHighlight();
|
||||||
|
VFile *getFile() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void saveAndRead();
|
void saveAndRead();
|
||||||
|
491
src/vimagepreviewer.cpp
Normal file
491
src/vimagepreviewer.cpp
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
#include "vimagepreviewer.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QTextDocument>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QUrl>
|
||||||
|
#include "vmdedit.h"
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
#include "utils/vutils.h"
|
||||||
|
#include "vfile.h"
|
||||||
|
#include "vdownloader.h"
|
||||||
|
|
||||||
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
|
enum ImageProperty { ImagePath = 1 };
|
||||||
|
|
||||||
|
VImagePreviewer::VImagePreviewer(VMdEdit *p_edit, int p_timeToPreview)
|
||||||
|
: QObject(p_edit), m_edit(p_edit), m_document(p_edit->document()),
|
||||||
|
m_file(p_edit->getFile()), m_enablePreview(true), m_isPreviewing(false),
|
||||||
|
m_requestCearBlocks(false), m_requestRefreshBlocks(false)
|
||||||
|
{
|
||||||
|
m_timer = new QTimer(this);
|
||||||
|
m_timer->setSingleShot(true);
|
||||||
|
Q_ASSERT(p_timeToPreview > 0);
|
||||||
|
m_timer->setInterval(p_timeToPreview);
|
||||||
|
|
||||||
|
connect(m_timer, &QTimer::timeout,
|
||||||
|
this, &VImagePreviewer::timerTimeout);
|
||||||
|
|
||||||
|
m_downloader = new VDownloader(this);
|
||||||
|
connect(m_downloader, &VDownloader::downloadFinished,
|
||||||
|
this, &VImagePreviewer::imageDownloaded);
|
||||||
|
|
||||||
|
connect(m_edit->document(), &QTextDocument::contentsChange,
|
||||||
|
this, &VImagePreviewer::handleContentChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::timerTimeout()
|
||||||
|
{
|
||||||
|
if (!vconfig.getEnablePreviewImages()) {
|
||||||
|
if (m_enablePreview) {
|
||||||
|
disableImagePreview();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_enablePreview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::handleContentChange(int /* p_position */,
|
||||||
|
int p_charsRemoved,
|
||||||
|
int p_charsAdded)
|
||||||
|
{
|
||||||
|
if (p_charsRemoved == 0 && p_charsAdded == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer->stop();
|
||||||
|
m_timer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::previewImages()
|
||||||
|
{
|
||||||
|
if (m_isPreviewing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isPreviewing = true;
|
||||||
|
QTextBlock block = m_document->begin();
|
||||||
|
while (block.isValid() && m_enablePreview) {
|
||||||
|
if (isImagePreviewBlock(block)) {
|
||||||
|
// Image preview block. Check if it is parentless.
|
||||||
|
if (!isValidImagePreviewBlock(block)) {
|
||||||
|
QTextBlock nblock = block.next();
|
||||||
|
removeBlock(block);
|
||||||
|
block = nblock;
|
||||||
|
} else {
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearCorruptedImagePreviewBlock(block);
|
||||||
|
|
||||||
|
block = previewImageOfOneBlock(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isPreviewing = false;
|
||||||
|
|
||||||
|
if (m_requestCearBlocks) {
|
||||||
|
m_requestCearBlocks = false;
|
||||||
|
clearAllImagePreviewBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_requestRefreshBlocks) {
|
||||||
|
m_requestRefreshBlocks = false;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit m_edit->statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VImagePreviewer::isImagePreviewBlock(const QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
if (!p_block.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text = p_block.text().trimmed();
|
||||||
|
return text == QString(QChar::ObjectReplacementCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VImagePreviewer::isValidImagePreviewBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
if (!isImagePreviewBlock(p_block)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is a valid image preview block only if the previous block is a block
|
||||||
|
// need to preview (containing exactly one image) and the image paths are
|
||||||
|
// identical.
|
||||||
|
QTextBlock prevBlock = p_block.previous();
|
||||||
|
if (prevBlock.isValid()) {
|
||||||
|
QString imagePath = fetchImagePathToPreview(prevBlock.text());
|
||||||
|
if (imagePath.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image preview block's image path.
|
||||||
|
QString curPath = fetchImagePathFromPreviewBlock(p_block);
|
||||||
|
|
||||||
|
return curPath == imagePath;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VImagePreviewer::fetchImageUrlToPreview(const QString &p_text)
|
||||||
|
{
|
||||||
|
QRegExp regExp("\\!\\[[^\\]]*\\]\\(([^\\)]+)\\)");
|
||||||
|
int index = regExp.indexIn(p_text);
|
||||||
|
if (index == -1) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastIndex = regExp.lastIndexIn(p_text);
|
||||||
|
if (lastIndex != index) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return regExp.capturedTexts()[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VImagePreviewer::fetchImagePathToPreview(const QString &p_text)
|
||||||
|
{
|
||||||
|
QString imageUrl = fetchImageUrlToPreview(p_text);
|
||||||
|
if (imageUrl.isEmpty()) {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString imagePath;
|
||||||
|
QFileInfo info(m_file->retriveBasePath(), imageUrl);
|
||||||
|
if (info.exists()) {
|
||||||
|
if (info.isNativePath()) {
|
||||||
|
// Local file.
|
||||||
|
imagePath = info.absoluteFilePath();
|
||||||
|
} else {
|
||||||
|
imagePath = imageUrl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QUrl url(imageUrl);
|
||||||
|
imagePath = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return imagePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextBlock VImagePreviewer::previewImageOfOneBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
if (!p_block.isValid()) {
|
||||||
|
return p_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextBlock nblock = p_block.next();
|
||||||
|
|
||||||
|
QString imagePath = fetchImagePathToPreview(p_block.text());
|
||||||
|
if (imagePath.isEmpty()) {
|
||||||
|
return nblock;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "block" << p_block.blockNumber() << imagePath;
|
||||||
|
|
||||||
|
if (isImagePreviewBlock(nblock)) {
|
||||||
|
QTextBlock nextBlock = nblock.next();
|
||||||
|
updateImagePreviewBlock(nblock, imagePath);
|
||||||
|
|
||||||
|
return nextBlock;
|
||||||
|
} else {
|
||||||
|
QTextBlock imgBlock = insertImagePreviewBlock(p_block, imagePath);
|
||||||
|
|
||||||
|
return imgBlock.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextBlock VImagePreviewer::insertImagePreviewBlock(QTextBlock &p_block,
|
||||||
|
const QString &p_imagePath)
|
||||||
|
{
|
||||||
|
QString imageName = imageCacheResourceName(p_imagePath);
|
||||||
|
if (imageName.isEmpty()) {
|
||||||
|
return p_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool modified = m_edit->isModified();
|
||||||
|
|
||||||
|
QTextCursor cursor(p_block);
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
cursor.movePosition(QTextCursor::EndOfBlock);
|
||||||
|
cursor.insertBlock();
|
||||||
|
|
||||||
|
QTextImageFormat imgFormat;
|
||||||
|
imgFormat.setName(imageName);
|
||||||
|
imgFormat.setProperty(ImagePath, p_imagePath);
|
||||||
|
cursor.insertImage(imgFormat);
|
||||||
|
cursor.endEditBlock();
|
||||||
|
|
||||||
|
V_ASSERT(cursor.block().text().at(0) == QChar::ObjectReplacementCharacter);
|
||||||
|
|
||||||
|
m_edit->setModified(modified);
|
||||||
|
|
||||||
|
return cursor.block();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::updateImagePreviewBlock(QTextBlock &p_block,
|
||||||
|
const QString &p_imagePath)
|
||||||
|
{
|
||||||
|
QTextImageFormat format = fetchFormatFromPreviewBlock(p_block);
|
||||||
|
V_ASSERT(format.isValid());
|
||||||
|
QString curPath = format.property(ImagePath).toString();
|
||||||
|
|
||||||
|
if (curPath == p_imagePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update it with the new image.
|
||||||
|
QString imageName = imageCacheResourceName(p_imagePath);
|
||||||
|
if (imageName.isEmpty()) {
|
||||||
|
// Delete current preview block.
|
||||||
|
removeBlock(p_block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
format.setName(imageName);
|
||||||
|
format.setProperty(ImagePath, p_imagePath);
|
||||||
|
updateFormatInPreviewBlock(p_block, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::removeBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
bool modified = m_edit->isModified();
|
||||||
|
|
||||||
|
QTextCursor cursor(p_block);
|
||||||
|
cursor.select(QTextCursor::BlockUnderCursor);
|
||||||
|
cursor.removeSelectedText();
|
||||||
|
|
||||||
|
m_edit->setModified(modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::clearCorruptedImagePreviewBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
if (!p_block.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text = p_block.text();
|
||||||
|
QVector<int> replacementChars;
|
||||||
|
bool onlySpaces = true;
|
||||||
|
for (int i = 0; i < text.size(); ++i) {
|
||||||
|
if (text[i] == QChar::ObjectReplacementCharacter) {
|
||||||
|
replacementChars.append(i);
|
||||||
|
} else if (!text[i].isSpace()) {
|
||||||
|
onlySpaces = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!onlySpaces && !replacementChars.isEmpty()) {
|
||||||
|
// ObjectReplacementCharacter mixed with other non-space texts.
|
||||||
|
// Users corrupt the image preview block. Just remove the char.
|
||||||
|
bool modified = m_edit->isModified();
|
||||||
|
|
||||||
|
QTextCursor cursor(p_block);
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
int blockPos = p_block.position();
|
||||||
|
for (int i = replacementChars.size() - 1; i >= 0; --i) {
|
||||||
|
int pos = replacementChars[i];
|
||||||
|
cursor.setPosition(blockPos + pos);
|
||||||
|
cursor.deleteChar();
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
|
|
||||||
|
m_edit->setModified(modified);
|
||||||
|
|
||||||
|
V_ASSERT(text.remove(QChar::ObjectReplacementCharacter) == p_block.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VImagePreviewer::isPreviewEnabled()
|
||||||
|
{
|
||||||
|
return m_enablePreview;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::enableImagePreview()
|
||||||
|
{
|
||||||
|
m_enablePreview = true;
|
||||||
|
|
||||||
|
if (vconfig.getEnablePreviewImages()) {
|
||||||
|
m_timer->stop();
|
||||||
|
m_timer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::disableImagePreview()
|
||||||
|
{
|
||||||
|
m_enablePreview = false;
|
||||||
|
|
||||||
|
if (m_isPreviewing) {
|
||||||
|
// It is previewing, append the request and clear preview blocks after
|
||||||
|
// finished previewing.
|
||||||
|
// It is weird that when selection changed, it will interrupt the process
|
||||||
|
// of previewing.
|
||||||
|
m_requestCearBlocks = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllImagePreviewBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::clearAllImagePreviewBlocks()
|
||||||
|
{
|
||||||
|
V_ASSERT(!m_isPreviewing);
|
||||||
|
|
||||||
|
QTextBlock block = m_document->begin();
|
||||||
|
QTextCursor cursor = m_edit->textCursor();
|
||||||
|
bool modified = m_edit->isModified();
|
||||||
|
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
while (block.isValid()) {
|
||||||
|
if (isImagePreviewBlock(block)) {
|
||||||
|
QTextBlock nextBlock = block.next();
|
||||||
|
removeBlock(block);
|
||||||
|
block = nextBlock;
|
||||||
|
} else {
|
||||||
|
clearCorruptedImagePreviewBlock(block);
|
||||||
|
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
|
|
||||||
|
m_edit->setModified(modified);
|
||||||
|
|
||||||
|
emit m_edit->statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VImagePreviewer::fetchImagePathFromPreviewBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
QTextImageFormat format = fetchFormatFromPreviewBlock(p_block);
|
||||||
|
if (!format.isValid()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return format.property(ImagePath).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextImageFormat VImagePreviewer::fetchFormatFromPreviewBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::updateFormatInPreviewBlock(QTextBlock &p_block,
|
||||||
|
const QTextImageFormat &p_format)
|
||||||
|
{
|
||||||
|
QTextCursor cursor(p_block);
|
||||||
|
int shift = p_block.text().indexOf(QChar::ObjectReplacementCharacter);
|
||||||
|
if (shift > 0) {
|
||||||
|
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
V_ASSERT(shift >= 0);
|
||||||
|
|
||||||
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
|
||||||
|
V_ASSERT(cursor.charFormat().toImageFormat().isValid());
|
||||||
|
|
||||||
|
cursor.setCharFormat(p_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, name);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer->stop();
|
||||||
|
QString name(imagePathToCacheResourceName(p_url));
|
||||||
|
m_document->addResource(QTextDocument::ImageResource, name, image);
|
||||||
|
m_imageCache.insert(p_url, name);
|
||||||
|
|
||||||
|
qDebug() << "downloaded image cache insert" << p_url << name;
|
||||||
|
|
||||||
|
m_timer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VImagePreviewer::refresh()
|
||||||
|
{
|
||||||
|
if (m_isPreviewing) {
|
||||||
|
m_requestRefreshBlocks = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer->stop();
|
||||||
|
m_imageCache.clear();
|
||||||
|
clearAllImagePreviewBlocks();
|
||||||
|
m_timer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage VImagePreviewer::fetchCachedImageFromPreviewBlock(QTextBlock &p_block)
|
||||||
|
{
|
||||||
|
QString path = fetchImagePathFromPreviewBlock(p_block);
|
||||||
|
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()).value<QImage>();
|
||||||
|
}
|
95
src/vimagepreviewer.h
Normal file
95
src/vimagepreviewer.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#ifndef VIMAGEPREVIEWER_H
|
||||||
|
#define VIMAGEPREVIEWER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
class VMdEdit;
|
||||||
|
class QTimer;
|
||||||
|
class QTextDocument;
|
||||||
|
class VFile;
|
||||||
|
class VDownloader;
|
||||||
|
|
||||||
|
class VImagePreviewer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit VImagePreviewer(VMdEdit *p_edit, int p_timeToPreview);
|
||||||
|
|
||||||
|
void disableImagePreview();
|
||||||
|
void enableImagePreview();
|
||||||
|
bool isPreviewEnabled();
|
||||||
|
|
||||||
|
bool isImagePreviewBlock(const QTextBlock &p_block);
|
||||||
|
|
||||||
|
QImage fetchCachedImageFromPreviewBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
// Clear the m_imageCache and all the preview blocks.
|
||||||
|
// Then re-preview all the blocks.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void timerTimeout();
|
||||||
|
void handleContentChange(int p_position, int p_charsRemoved, int p_charsAdded);
|
||||||
|
void imageDownloaded(const QByteArray &p_data, const QString &p_url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void previewImages();
|
||||||
|
bool isValidImagePreviewBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Try to preview the image of @p_block.
|
||||||
|
// Return the next block to process.
|
||||||
|
QTextBlock previewImageOfOneBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
// Insert a new block to preview image.
|
||||||
|
QTextBlock insertImagePreviewBlock(QTextBlock &p_block, const QString &p_imagePath);
|
||||||
|
|
||||||
|
// @p_block is the image block. Update it to preview @p_imagePath.
|
||||||
|
void updateImagePreviewBlock(QTextBlock &p_block, const QString &p_imagePath);
|
||||||
|
|
||||||
|
void removeBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
// Corrupted image preview block: ObjectReplacementCharacter mixed with other
|
||||||
|
// non-space characters.
|
||||||
|
// Remove the ObjectReplacementCharacter chars.
|
||||||
|
void clearCorruptedImagePreviewBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
void clearAllImagePreviewBlocks();
|
||||||
|
|
||||||
|
QTextImageFormat fetchFormatFromPreviewBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
QString fetchImagePathFromPreviewBlock(QTextBlock &p_block);
|
||||||
|
|
||||||
|
void updateFormatInPreviewBlock(QTextBlock &p_block,
|
||||||
|
const QTextImageFormat &p_format);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
VMdEdit *m_edit;
|
||||||
|
QTextDocument *m_document;
|
||||||
|
VFile *m_file;
|
||||||
|
QTimer *m_timer;
|
||||||
|
bool m_enablePreview;
|
||||||
|
bool m_isPreviewing;
|
||||||
|
bool m_requestCearBlocks;
|
||||||
|
bool m_requestRefreshBlocks;
|
||||||
|
|
||||||
|
// Map from image full path to QUrl identifier in the QTextDocument's cache.
|
||||||
|
QHash<QString, QString> m_imageCache;;
|
||||||
|
|
||||||
|
VDownloader *m_downloader;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VIMAGEPREVIEWER_H
|
297
src/vmdedit.cpp
297
src/vmdedit.cpp
@ -8,30 +8,29 @@
|
|||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
#include "utils/vutils.h"
|
#include "utils/vutils.h"
|
||||||
#include "dialog/vselectdialog.h"
|
#include "dialog/vselectdialog.h"
|
||||||
|
#include "vimagepreviewer.h"
|
||||||
|
|
||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
extern VNote *g_vnote;
|
extern VNote *g_vnote;
|
||||||
|
|
||||||
enum ImageProperty { ImagePath = 1 };
|
|
||||||
|
|
||||||
VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||||
QWidget *p_parent)
|
QWidget *p_parent)
|
||||||
: VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_previewImage(true)
|
: VEdit(p_file, p_parent), m_mdHighlighter(NULL)
|
||||||
{
|
{
|
||||||
Q_ASSERT(p_file->getDocType() == DocType::Markdown);
|
Q_ASSERT(p_file->getDocType() == DocType::Markdown);
|
||||||
|
|
||||||
setAcceptRichText(false);
|
setAcceptRichText(false);
|
||||||
m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
|
m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
|
||||||
vconfig.getCodeBlockStyles(),
|
vconfig.getCodeBlockStyles(),
|
||||||
500, document());
|
700, document());
|
||||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||||
this, &VMdEdit::generateEditOutline);
|
this, &VMdEdit::generateEditOutline);
|
||||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated,
|
|
||||||
this, &VMdEdit::updateImageBlocks);
|
|
||||||
|
|
||||||
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, 500);
|
||||||
|
|
||||||
m_editOps = new VMdEditOperations(this, m_file);
|
m_editOps = new VMdEditOperations(this, m_file);
|
||||||
connect(m_editOps, &VEditOperations::keyStateChanged,
|
connect(m_editOps, &VEditOperations::keyStateChanged,
|
||||||
this, &VMdEdit::handleEditStateChanged);
|
this, &VMdEdit::handleEditStateChanged);
|
||||||
@ -64,6 +63,8 @@ void VMdEdit::beginEdit()
|
|||||||
|
|
||||||
initInitImages();
|
initInitImages();
|
||||||
|
|
||||||
|
m_imagePreviewer->refresh();
|
||||||
|
|
||||||
setReadOnly(false);
|
setReadOnly(false);
|
||||||
setModified(false);
|
setModified(false);
|
||||||
|
|
||||||
@ -282,253 +283,6 @@ void VMdEdit::scrollToHeader(int p_headerIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::updateImageBlocks(QSet<int> p_imageBlocks)
|
|
||||||
{
|
|
||||||
if (!m_previewImage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We need to handle blocks backward to avoid shifting all the following blocks.
|
|
||||||
// Inserting the preview image block may cause highlighter to emit signal again.
|
|
||||||
QList<int> blockList = p_imageBlocks.toList();
|
|
||||||
std::sort(blockList.begin(), blockList.end(), std::greater<int>());
|
|
||||||
auto it = blockList.begin();
|
|
||||||
while (it != blockList.end()) {
|
|
||||||
previewImageOfBlock(*it);
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up un-referenced QChar::ObjectReplacementCharacter.
|
|
||||||
clearOrphanImagePreviewBlock();
|
|
||||||
|
|
||||||
emit statusChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::clearOrphanImagePreviewBlock()
|
|
||||||
{
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
QTextBlock block = doc->begin();
|
|
||||||
while (block.isValid()) {
|
|
||||||
if (isOrphanImagePreviewBlock(block)) {
|
|
||||||
qDebug() << "remove orphan image preview block" << block.blockNumber();
|
|
||||||
QTextBlock nextBlock = block.next();
|
|
||||||
removeBlock(block);
|
|
||||||
block = nextBlock;
|
|
||||||
} else {
|
|
||||||
clearCorruptedImagePreviewBlock(block);
|
|
||||||
block = block.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VMdEdit::isOrphanImagePreviewBlock(QTextBlock p_block)
|
|
||||||
{
|
|
||||||
if (isImagePreviewBlock(p_block)) {
|
|
||||||
// It is an orphan image preview block if previous block is not
|
|
||||||
// a block need to preview (containing exactly one image) or the image
|
|
||||||
// paths are not equal to each other.
|
|
||||||
QTextBlock prevBlock = p_block.previous();
|
|
||||||
if (prevBlock.isValid()) {
|
|
||||||
QString imageLink = fetchImageToPreview(prevBlock.text());
|
|
||||||
if (imageLink.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
QString imagePath = QDir(m_file->retriveBasePath()).filePath(imageLink);
|
|
||||||
|
|
||||||
// Get image preview block's image path.
|
|
||||||
QTextCursor cursor(p_block);
|
|
||||||
int shift = p_block.text().indexOf(QChar::ObjectReplacementCharacter);
|
|
||||||
if (shift > 0) {
|
|
||||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
|
|
||||||
shift + 1);
|
|
||||||
}
|
|
||||||
QTextImageFormat format = cursor.charFormat().toImageFormat();
|
|
||||||
Q_ASSERT(format.isValid());
|
|
||||||
QString curPath = format.property(ImagePath).toString();
|
|
||||||
|
|
||||||
return curPath != imagePath;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::clearCorruptedImagePreviewBlock(QTextBlock p_block)
|
|
||||||
{
|
|
||||||
if (!p_block.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QString text = p_block.text();
|
|
||||||
QVector<int> replacementChars;
|
|
||||||
bool onlySpaces = true;
|
|
||||||
for (int i = 0; i < text.size(); ++i) {
|
|
||||||
if (text[i] == QChar::ObjectReplacementCharacter) {
|
|
||||||
replacementChars.append(i);
|
|
||||||
} else if (!text[i].isSpace()) {
|
|
||||||
onlySpaces = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!onlySpaces && !replacementChars.isEmpty()) {
|
|
||||||
// ObjectReplacementCharacter mixed with other non-space texts.
|
|
||||||
// Users corrupt the image preview block. Just remove the char.
|
|
||||||
QTextCursor cursor(p_block);
|
|
||||||
int blockPos = p_block.position();
|
|
||||||
for (int i = replacementChars.size() - 1; i >= 0; --i) {
|
|
||||||
int pos = replacementChars[i];
|
|
||||||
cursor.setPosition(blockPos + pos);
|
|
||||||
cursor.deleteChar();
|
|
||||||
}
|
|
||||||
Q_ASSERT(text.remove(QChar::ObjectReplacementCharacter) == p_block.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::clearAllImagePreviewBlocks()
|
|
||||||
{
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
QTextBlock block = doc->begin();
|
|
||||||
bool modified = isModified();
|
|
||||||
while (block.isValid()) {
|
|
||||||
if (isImagePreviewBlock(block)) {
|
|
||||||
QTextBlock nextBlock = block.next();
|
|
||||||
removeBlock(block);
|
|
||||||
block = nextBlock;
|
|
||||||
} else {
|
|
||||||
clearCorruptedImagePreviewBlock(block);
|
|
||||||
block = block.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setModified(modified);
|
|
||||||
emit statusChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString VMdEdit::fetchImageToPreview(const QString &p_text)
|
|
||||||
{
|
|
||||||
QRegExp regExp("\\!\\[[^\\]]*\\]\\((images/[^/\\)]+)\\)");
|
|
||||||
int index = regExp.indexIn(p_text);
|
|
||||||
if (index == -1) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
int lastIndex = regExp.lastIndexIn(p_text);
|
|
||||||
if (lastIndex != index) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
return regExp.capturedTexts()[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::previewImageOfBlock(int p_block)
|
|
||||||
{
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
QTextBlock block = doc->findBlockByNumber(p_block);
|
|
||||||
if (!block.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text = block.text();
|
|
||||||
QString imageLink = fetchImageToPreview(text);
|
|
||||||
if (imageLink.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QString imagePath = QDir(m_file->retriveBasePath()).filePath(imageLink);
|
|
||||||
qDebug() << "block" << p_block << "image" << imagePath;
|
|
||||||
|
|
||||||
if (isImagePreviewBlock(p_block + 1)) {
|
|
||||||
updateImagePreviewBlock(p_block + 1, imagePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
insertImagePreviewBlock(p_block, imagePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VMdEdit::isImagePreviewBlock(int p_block)
|
|
||||||
{
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
QTextBlock block = doc->findBlockByNumber(p_block);
|
|
||||||
if (!block.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString text = block.text().trimmed();
|
|
||||||
return text == QString(QChar::ObjectReplacementCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VMdEdit::isImagePreviewBlock(QTextBlock p_block)
|
|
||||||
{
|
|
||||||
if (!p_block.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString text = p_block.text().trimmed();
|
|
||||||
return text == QString(QChar::ObjectReplacementCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::insertImagePreviewBlock(int p_block, const QString &p_image)
|
|
||||||
{
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
|
|
||||||
QImage image(p_image);
|
|
||||||
if (image.isNull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store current status.
|
|
||||||
bool modified = isModified();
|
|
||||||
int pos = textCursor().position();
|
|
||||||
|
|
||||||
QTextCursor cursor(doc->findBlockByNumber(p_block));
|
|
||||||
cursor.beginEditBlock();
|
|
||||||
cursor.movePosition(QTextCursor::EndOfBlock);
|
|
||||||
cursor.insertBlock();
|
|
||||||
|
|
||||||
QTextImageFormat imgFormat;
|
|
||||||
imgFormat.setName(p_image);
|
|
||||||
imgFormat.setProperty(ImagePath, p_image);
|
|
||||||
cursor.insertImage(imgFormat);
|
|
||||||
Q_ASSERT(cursor.block().text().at(0) == QChar::ObjectReplacementCharacter);
|
|
||||||
cursor.endEditBlock();
|
|
||||||
|
|
||||||
QTextCursor tmp = textCursor();
|
|
||||||
tmp.setPosition(pos);
|
|
||||||
setTextCursor(tmp);
|
|
||||||
setModified(modified);
|
|
||||||
emit statusChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::updateImagePreviewBlock(int p_block, const QString &p_image)
|
|
||||||
{
|
|
||||||
Q_ASSERT(isImagePreviewBlock(p_block));
|
|
||||||
QTextDocument *doc = document();
|
|
||||||
QTextBlock block = doc->findBlockByNumber(p_block);
|
|
||||||
if (!block.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QTextCursor cursor(block);
|
|
||||||
int shift = block.text().indexOf(QChar::ObjectReplacementCharacter);
|
|
||||||
if (shift > 0) {
|
|
||||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, shift + 1);
|
|
||||||
}
|
|
||||||
QTextImageFormat format = cursor.charFormat().toImageFormat();
|
|
||||||
Q_ASSERT(format.isValid());
|
|
||||||
QString curPath = format.property(ImagePath).toString();
|
|
||||||
|
|
||||||
if (curPath == p_image) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update it with the new image.
|
|
||||||
QImage image(p_image);
|
|
||||||
if (image.isNull()) {
|
|
||||||
// Delete current preview block.
|
|
||||||
removeBlock(block);
|
|
||||||
qDebug() << "remove invalid image in block" << p_block;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
format.setName(p_image);
|
|
||||||
qDebug() << "update block" << p_block << "to image" << p_image;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VMdEdit::removeBlock(QTextBlock p_block)
|
|
||||||
{
|
|
||||||
QTextCursor cursor(p_block);
|
|
||||||
cursor.select(QTextCursor::BlockUnderCursor);
|
|
||||||
cursor.removeSelectedText();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString VMdEdit::toPlainTextWithoutImg() const
|
QString VMdEdit::toPlainTextWithoutImg() const
|
||||||
{
|
{
|
||||||
QString text = toPlainText();
|
QString text = toPlainText();
|
||||||
@ -568,18 +322,20 @@ void VMdEdit::handleEditStateChanged(KeyState p_state)
|
|||||||
|
|
||||||
void VMdEdit::handleSelectionChanged()
|
void VMdEdit::handleSelectionChanged()
|
||||||
{
|
{
|
||||||
|
if (!vconfig.getEnablePreviewImages()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QString text = textCursor().selectedText();
|
QString text = textCursor().selectedText();
|
||||||
if (text.isEmpty() && !m_previewImage) {
|
if (text.isEmpty() && !m_imagePreviewer->isPreviewEnabled()) {
|
||||||
m_previewImage = true;
|
m_imagePreviewer->enableImagePreview();
|
||||||
m_mdHighlighter->updateHighlight();
|
} else if (m_imagePreviewer->isPreviewEnabled()) {
|
||||||
} else if (m_previewImage) {
|
|
||||||
if (text.trimmed() == QString(QChar::ObjectReplacementCharacter)) {
|
if (text.trimmed() == QString(QChar::ObjectReplacementCharacter)) {
|
||||||
// Select the image and some whitespaces.
|
// Select the image and some whitespaces.
|
||||||
// We can let the user copy the image.
|
// We can let the user copy the image.
|
||||||
return;
|
return;
|
||||||
} else if (text.contains(QChar::ObjectReplacementCharacter)) {
|
} else if (text.contains(QChar::ObjectReplacementCharacter)) {
|
||||||
m_previewImage = false;
|
m_imagePreviewer->disableImagePreview();
|
||||||
clearAllImagePreviewBlocks();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -596,24 +352,22 @@ void VMdEdit::handleClipboardChanged(QClipboard::Mode p_mode)
|
|||||||
QString text = mimeData->text();
|
QString text = mimeData->text();
|
||||||
if (clipboard->ownsClipboard() &&
|
if (clipboard->ownsClipboard() &&
|
||||||
(text.trimmed() == QString(QChar::ObjectReplacementCharacter))) {
|
(text.trimmed() == QString(QChar::ObjectReplacementCharacter))) {
|
||||||
QString imagePath = selectedImage();
|
QImage image = selectedImage();
|
||||||
qDebug() << "clipboard" << imagePath;
|
|
||||||
Q_ASSERT(!imagePath.isEmpty());
|
|
||||||
QImage image(imagePath);
|
|
||||||
Q_ASSERT(!image.isNull());
|
|
||||||
clipboard->clear(QClipboard::Clipboard);
|
clipboard->clear(QClipboard::Clipboard);
|
||||||
clipboard->setImage(image, QClipboard::Clipboard);
|
if (!image.isNull()) {
|
||||||
|
clipboard->setImage(image, QClipboard::Clipboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VMdEdit::selectedImage()
|
QImage VMdEdit::selectedImage()
|
||||||
{
|
{
|
||||||
QString imagePath;
|
QImage image;
|
||||||
QTextCursor cursor = textCursor();
|
QTextCursor cursor = textCursor();
|
||||||
if (!cursor.hasSelection()) {
|
if (!cursor.hasSelection()) {
|
||||||
return imagePath;
|
return image;
|
||||||
}
|
}
|
||||||
int start = cursor.selectionStart();
|
int start = cursor.selectionStart();
|
||||||
int end = cursor.selectionEnd();
|
int end = cursor.selectionEnd();
|
||||||
@ -622,9 +376,8 @@ QString VMdEdit::selectedImage()
|
|||||||
QTextBlock endBlock = doc->findBlock(end);
|
QTextBlock endBlock = doc->findBlock(end);
|
||||||
QTextBlock block = startBlock;
|
QTextBlock block = startBlock;
|
||||||
while (block.isValid()) {
|
while (block.isValid()) {
|
||||||
if (isImagePreviewBlock(block)) {
|
if (m_imagePreviewer->isImagePreviewBlock(block)) {
|
||||||
QString image = fetchImageToPreview(block.previous().text());
|
image = m_imagePreviewer->fetchCachedImageFromPreviewBlock(block);
|
||||||
imagePath = QDir(m_file->retriveBasePath()).filePath(image);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (block == endBlock) {
|
if (block == endBlock) {
|
||||||
@ -632,5 +385,5 @@ QString VMdEdit::selectedImage()
|
|||||||
}
|
}
|
||||||
block = block.next();
|
block = block.next();
|
||||||
}
|
}
|
||||||
return imagePath;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QImage>
|
||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
#include "veditoperations.h"
|
#include "veditoperations.h"
|
||||||
#include "vconfigmanager.h"
|
#include "vconfigmanager.h"
|
||||||
@ -13,6 +14,7 @@
|
|||||||
class HGMarkdownHighlighter;
|
class HGMarkdownHighlighter;
|
||||||
class VCodeBlockHighlightHelper;
|
class VCodeBlockHighlightHelper;
|
||||||
class VDocument;
|
class VDocument;
|
||||||
|
class VImagePreviewer;
|
||||||
|
|
||||||
class VMdEdit : public VEdit
|
class VMdEdit : public VEdit
|
||||||
{
|
{
|
||||||
@ -41,8 +43,6 @@ signals:
|
|||||||
private slots:
|
private slots:
|
||||||
void generateEditOutline();
|
void generateEditOutline();
|
||||||
void updateCurHeader();
|
void updateCurHeader();
|
||||||
// Update block list containing image links.
|
|
||||||
void updateImageBlocks(QSet<int> p_imageBlocks);
|
|
||||||
void handleEditStateChanged(KeyState p_state);
|
void handleEditStateChanged(KeyState p_state);
|
||||||
void handleSelectionChanged();
|
void handleSelectionChanged();
|
||||||
void handleClipboardChanged(QClipboard::Mode p_mode);
|
void handleClipboardChanged(QClipboard::Mode p_mode);
|
||||||
@ -59,32 +59,16 @@ private:
|
|||||||
// p_text[p_index] is QChar::ObjectReplacementCharacter. Remove the line containing it.
|
// p_text[p_index] is QChar::ObjectReplacementCharacter. Remove the line containing it.
|
||||||
// Returns the index of previous line's '\n'.
|
// Returns the index of previous line's '\n'.
|
||||||
int removeObjectReplacementLine(QString &p_text, int p_index) const;
|
int removeObjectReplacementLine(QString &p_text, int p_index) const;
|
||||||
void previewImageOfBlock(int p_block);
|
// There is a QChar::ObjectReplacementCharacter in the selection.
|
||||||
bool isImagePreviewBlock(int p_block);
|
// Get the QImage.
|
||||||
bool isImagePreviewBlock(QTextBlock p_block);
|
QImage selectedImage();
|
||||||
// p_block is a image preview block. We need to update it with image.
|
|
||||||
void updateImagePreviewBlock(int p_block, const QString &p_image);
|
|
||||||
// Insert a block after @p_block to preview image @p_image.
|
|
||||||
void insertImagePreviewBlock(int p_block, const QString &p_image);
|
|
||||||
// Clean up un-referenced image preview block.
|
|
||||||
void clearOrphanImagePreviewBlock();
|
|
||||||
void removeBlock(QTextBlock p_block);
|
|
||||||
bool isOrphanImagePreviewBlock(QTextBlock p_block);
|
|
||||||
// Block that has the QChar::ObjectReplacementCharacter as well as some non-space characters.
|
|
||||||
void clearCorruptedImagePreviewBlock(QTextBlock p_block);
|
|
||||||
// Returns the image relative path (image/xxx.png) only when
|
|
||||||
// there is one and only one image link.
|
|
||||||
QString fetchImageToPreview(const QString &p_text);
|
|
||||||
void clearAllImagePreviewBlocks();
|
|
||||||
// There is a QChar::ObjectReplacementCharacter in the selection. Find out the image path.
|
|
||||||
QString selectedImage();
|
|
||||||
|
|
||||||
HGMarkdownHighlighter *m_mdHighlighter;
|
HGMarkdownHighlighter *m_mdHighlighter;
|
||||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||||
|
VImagePreviewer *m_imagePreviewer;
|
||||||
QVector<QString> m_insertedImages;
|
QVector<QString> m_insertedImages;
|
||||||
QVector<QString> m_initImages;
|
QVector<QString> m_initImages;
|
||||||
QVector<VHeader> m_headers;
|
QVector<VHeader> m_headers;
|
||||||
bool m_previewImage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VMDEDIT_H
|
#endif // VMDEDIT_H
|
||||||
|
@ -131,8 +131,8 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
|||||||
} else {
|
} else {
|
||||||
// Download it to a QImage
|
// Download it to a QImage
|
||||||
VDownloader *downloader = new VDownloader(&dialog);
|
VDownloader *downloader = new VDownloader(&dialog);
|
||||||
QObject::connect(downloader, &VDownloader::downloadFinished,
|
connect(downloader, &VDownloader::downloadFinished,
|
||||||
&dialog, &VInsertImageDialog::imageDownloaded);
|
&dialog, &VInsertImageDialog::imageDownloaded);
|
||||||
downloader->download(imageUrl.toString());
|
downloader->download(imageUrl.toString());
|
||||||
}
|
}
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user