mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59: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);
|
||||
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)
|
||||
{
|
||||
int startBlockNum = document->findBlock(pos).blockNumber();
|
||||
|
@ -91,7 +91,6 @@ public:
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
void imageBlocksUpdated(QSet<int> p_blocks);
|
||||
void codeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
protected:
|
||||
@ -124,8 +123,6 @@ private:
|
||||
|
||||
int m_numOfCodeBlockHighlightsToRecv;
|
||||
|
||||
// Block numbers containing image link(s).
|
||||
QSet<int> imageBlocks;
|
||||
QAtomicInt parsing;
|
||||
QTimer *timer;
|
||||
int waitInterval;
|
||||
@ -144,7 +141,7 @@ private:
|
||||
void initBlockHighlightFromResult(int nrBlocks);
|
||||
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
||||
int styleIndex);
|
||||
void updateImageBlocks();
|
||||
|
||||
// Return true if there are fenced code blocks and it will call rehighlight() later.
|
||||
// Return false if there is none.
|
||||
bool updateCodeBlocks();
|
||||
|
@ -22,6 +22,7 @@ enable_mathjax=false
|
||||
web_zoom_factor=-1
|
||||
; Syntax highlight within code blocks in edit mode
|
||||
enable_code_block_highlight=true
|
||||
enable_preview_images=true
|
||||
|
||||
[session]
|
||||
tools_dock_checked=true
|
||||
|
@ -60,7 +60,8 @@ SOURCES += main.cpp\
|
||||
vopenedlistmenu.cpp \
|
||||
vorphanfile.cpp \
|
||||
vcodeblockhighlighthelper.cpp \
|
||||
vwebview.cpp
|
||||
vwebview.cpp \
|
||||
vimagepreviewer.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -107,7 +108,8 @@ HEADERS += vmainwindow.h \
|
||||
vnavigationmode.h \
|
||||
vorphanfile.h \
|
||||
vcodeblockhighlighthelper.h \
|
||||
vwebview.h
|
||||
vwebview.h \
|
||||
vimagepreviewer.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -123,6 +123,9 @@ void VConfigManager::initialize()
|
||||
|
||||
m_enableCodeBlockHighlight = getConfigFromSettings("global",
|
||||
"enable_code_block_highlight").toBool();
|
||||
|
||||
m_enablePreviewImages = getConfigFromSettings("global",
|
||||
"enable_preview_images").toBool();
|
||||
}
|
||||
|
||||
void VConfigManager::readPredefinedColorsFromSettings()
|
||||
|
@ -157,6 +157,9 @@ public:
|
||||
inline bool getEnableCodeBlockHighlight() const;
|
||||
inline void setEnableCodeBlockHighlight(bool p_enabled);
|
||||
|
||||
inline bool getEnablePreviewImages() const;
|
||||
inline void setEnablePreviewImages(bool p_enabled);
|
||||
|
||||
// Get the folder the ini file exists.
|
||||
QString getConfigFolder() const;
|
||||
|
||||
@ -264,6 +267,9 @@ private:
|
||||
// Enable colde block syntax highlight.
|
||||
bool m_enableCodeBlockHighlight;
|
||||
|
||||
// Preview images in edit mode.
|
||||
bool m_enablePreviewImages;
|
||||
|
||||
// The name of the config file in each directory, obsolete.
|
||||
// Use c_dirConfigFile instead.
|
||||
static const QString c_obsoleteDirConfigFile;
|
||||
@ -689,4 +695,20 @@ inline void VConfigManager::setEnableCodeBlockHighlight(bool p_enabled)
|
||||
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
|
||||
|
@ -11,13 +11,14 @@ void VDownloader::handleDownloadFinished(QNetworkReply *reply)
|
||||
{
|
||||
data = reply->readAll();
|
||||
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());
|
||||
QNetworkRequest request(url);
|
||||
Q_ASSERT(p_url.isValid());
|
||||
QNetworkRequest request(p_url);
|
||||
webCtrl.get(request);
|
||||
qDebug() << "VDownloader get" << url.toString();
|
||||
qDebug() << "VDownloader get" << p_url.toString();
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ class VDownloader : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VDownloader(QObject *parent = 0);
|
||||
void download(QUrl url);
|
||||
void download(const QUrl &p_url);
|
||||
|
||||
signals:
|
||||
void downloadFinished(const QByteArray &data);
|
||||
void downloadFinished(const QByteArray &data, const QString &url);
|
||||
|
||||
private slots:
|
||||
void handleDownloadFinished(QNetworkReply *reply);
|
||||
|
@ -563,3 +563,9 @@ void VEdit::handleEditAct()
|
||||
{
|
||||
emit editNote();
|
||||
}
|
||||
|
||||
VFile *VEdit::getFile() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
const QString &p_replaceText);
|
||||
void setReadOnly(bool p_ro);
|
||||
void clearSearchedWordHighlight();
|
||||
VFile *getFile() const;
|
||||
|
||||
signals:
|
||||
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
|
295
src/vmdedit.cpp
295
src/vmdedit.cpp
@ -8,30 +8,29 @@
|
||||
#include "vtoc.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "dialog/vselectdialog.h"
|
||||
#include "vimagepreviewer.h"
|
||||
|
||||
extern VConfigManager vconfig;
|
||||
extern VNote *g_vnote;
|
||||
|
||||
enum ImageProperty { ImagePath = 1 };
|
||||
|
||||
VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
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);
|
||||
|
||||
setAcceptRichText(false);
|
||||
m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
|
||||
vconfig.getCodeBlockStyles(),
|
||||
500, document());
|
||||
700, document());
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||
this, &VMdEdit::generateEditOutline);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated,
|
||||
this, &VMdEdit::updateImageBlocks);
|
||||
|
||||
m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc,
|
||||
p_type);
|
||||
|
||||
m_imagePreviewer = new VImagePreviewer(this, 500);
|
||||
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
connect(m_editOps, &VEditOperations::keyStateChanged,
|
||||
this, &VMdEdit::handleEditStateChanged);
|
||||
@ -64,6 +63,8 @@ void VMdEdit::beginEdit()
|
||||
|
||||
initInitImages();
|
||||
|
||||
m_imagePreviewer->refresh();
|
||||
|
||||
setReadOnly(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 text = toPlainText();
|
||||
@ -568,18 +322,20 @@ void VMdEdit::handleEditStateChanged(KeyState p_state)
|
||||
|
||||
void VMdEdit::handleSelectionChanged()
|
||||
{
|
||||
if (!vconfig.getEnablePreviewImages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = textCursor().selectedText();
|
||||
if (text.isEmpty() && !m_previewImage) {
|
||||
m_previewImage = true;
|
||||
m_mdHighlighter->updateHighlight();
|
||||
} else if (m_previewImage) {
|
||||
if (text.isEmpty() && !m_imagePreviewer->isPreviewEnabled()) {
|
||||
m_imagePreviewer->enableImagePreview();
|
||||
} else if (m_imagePreviewer->isPreviewEnabled()) {
|
||||
if (text.trimmed() == QString(QChar::ObjectReplacementCharacter)) {
|
||||
// Select the image and some whitespaces.
|
||||
// We can let the user copy the image.
|
||||
return;
|
||||
} else if (text.contains(QChar::ObjectReplacementCharacter)) {
|
||||
m_previewImage = false;
|
||||
clearAllImagePreviewBlocks();
|
||||
m_imagePreviewer->disableImagePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -596,24 +352,22 @@ void VMdEdit::handleClipboardChanged(QClipboard::Mode p_mode)
|
||||
QString text = mimeData->text();
|
||||
if (clipboard->ownsClipboard() &&
|
||||
(text.trimmed() == QString(QChar::ObjectReplacementCharacter))) {
|
||||
QString imagePath = selectedImage();
|
||||
qDebug() << "clipboard" << imagePath;
|
||||
Q_ASSERT(!imagePath.isEmpty());
|
||||
QImage image(imagePath);
|
||||
Q_ASSERT(!image.isNull());
|
||||
QImage image = selectedImage();
|
||||
clipboard->clear(QClipboard::Clipboard);
|
||||
if (!image.isNull()) {
|
||||
clipboard->setImage(image, QClipboard::Clipboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString VMdEdit::selectedImage()
|
||||
QImage VMdEdit::selectedImage()
|
||||
{
|
||||
QString imagePath;
|
||||
QImage image;
|
||||
QTextCursor cursor = textCursor();
|
||||
if (!cursor.hasSelection()) {
|
||||
return imagePath;
|
||||
return image;
|
||||
}
|
||||
int start = cursor.selectionStart();
|
||||
int end = cursor.selectionEnd();
|
||||
@ -622,9 +376,8 @@ QString VMdEdit::selectedImage()
|
||||
QTextBlock endBlock = doc->findBlock(end);
|
||||
QTextBlock block = startBlock;
|
||||
while (block.isValid()) {
|
||||
if (isImagePreviewBlock(block)) {
|
||||
QString image = fetchImageToPreview(block.previous().text());
|
||||
imagePath = QDir(m_file->retriveBasePath()).filePath(image);
|
||||
if (m_imagePreviewer->isImagePreviewBlock(block)) {
|
||||
image = m_imagePreviewer->fetchCachedImageFromPreviewBlock(block);
|
||||
break;
|
||||
}
|
||||
if (block == endBlock) {
|
||||
@ -632,5 +385,5 @@ QString VMdEdit::selectedImage()
|
||||
}
|
||||
block = block.next();
|
||||
}
|
||||
return imagePath;
|
||||
return image;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
#include <QClipboard>
|
||||
#include <QImage>
|
||||
#include "vtoc.h"
|
||||
#include "veditoperations.h"
|
||||
#include "vconfigmanager.h"
|
||||
@ -13,6 +14,7 @@
|
||||
class HGMarkdownHighlighter;
|
||||
class VCodeBlockHighlightHelper;
|
||||
class VDocument;
|
||||
class VImagePreviewer;
|
||||
|
||||
class VMdEdit : public VEdit
|
||||
{
|
||||
@ -41,8 +43,6 @@ signals:
|
||||
private slots:
|
||||
void generateEditOutline();
|
||||
void updateCurHeader();
|
||||
// Update block list containing image links.
|
||||
void updateImageBlocks(QSet<int> p_imageBlocks);
|
||||
void handleEditStateChanged(KeyState p_state);
|
||||
void handleSelectionChanged();
|
||||
void handleClipboardChanged(QClipboard::Mode p_mode);
|
||||
@ -59,32 +59,16 @@ private:
|
||||
// p_text[p_index] is QChar::ObjectReplacementCharacter. Remove the line containing it.
|
||||
// Returns the index of previous line's '\n'.
|
||||
int removeObjectReplacementLine(QString &p_text, int p_index) const;
|
||||
void previewImageOfBlock(int p_block);
|
||||
bool isImagePreviewBlock(int p_block);
|
||||
bool isImagePreviewBlock(QTextBlock p_block);
|
||||
// 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();
|
||||
// There is a QChar::ObjectReplacementCharacter in the selection.
|
||||
// Get the QImage.
|
||||
QImage selectedImage();
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
VImagePreviewer *m_imagePreviewer;
|
||||
QVector<QString> m_insertedImages;
|
||||
QVector<QString> m_initImages;
|
||||
QVector<VHeader> m_headers;
|
||||
bool m_previewImage;
|
||||
};
|
||||
|
||||
#endif // VMDEDIT_H
|
||||
|
@ -131,7 +131,7 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
||||
} else {
|
||||
// Download it to a QImage
|
||||
VDownloader *downloader = new VDownloader(&dialog);
|
||||
QObject::connect(downloader, &VDownloader::downloadFinished,
|
||||
connect(downloader, &VDownloader::downloadFinished,
|
||||
&dialog, &VInsertImageDialog::imageDownloaded);
|
||||
downloader->download(imageUrl.toString());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user