mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
preview image links
Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
parent
e069305b23
commit
a22bf1059d
@ -112,10 +112,41 @@ 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();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <QTextCharFormat>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QAtomicInt>
|
||||
#include <QSet>
|
||||
|
||||
extern "C" {
|
||||
#include <pmh_parser.h>
|
||||
@ -58,6 +59,7 @@ public:
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
void imageBlocksUpdated(QSet<int> p_blocks);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
|
||||
@ -74,6 +76,8 @@ private:
|
||||
QTextDocument *document;
|
||||
QVector<HighlightingStyle> highlightingStyles;
|
||||
QVector<QVector<HLUnit> > blockHighlights;
|
||||
// Block numbers containing image link(s).
|
||||
QSet<int> imageBlocks;
|
||||
QAtomicInt parsing;
|
||||
QTimer *timer;
|
||||
int waitInterval;
|
||||
@ -81,15 +85,17 @@ private:
|
||||
char *content;
|
||||
int capacity;
|
||||
pmh_element **result;
|
||||
static const int initCapacity;
|
||||
void resizeBuffer(int newCap);
|
||||
|
||||
static const int initCapacity;
|
||||
|
||||
void resizeBuffer(int newCap);
|
||||
void highlightCodeBlock(const QString &text);
|
||||
void parse();
|
||||
void parseInternal();
|
||||
void initBlockHighlightFromResult(int nrBlocks);
|
||||
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
||||
int styleIndex);
|
||||
void updateImageBlocks();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -29,7 +29,7 @@ void VLogger(QtMsgType type, const QMessageLogContext &context, const QString &m
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qInstallMessageHandler(VLogger);
|
||||
//qInstallMessageHandler(VLogger);
|
||||
|
||||
VSingleInstanceGuard guard;
|
||||
if (!guard.tryRun()) {
|
||||
|
@ -50,6 +50,8 @@ void VEditTab::setupUI()
|
||||
m_textEditor = new VMdEdit(m_file, this);
|
||||
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
|
||||
this, &VEditTab::updateTocFromHeaders);
|
||||
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::statusChanged,
|
||||
this, &VEditTab::noticeStatusChanged);
|
||||
connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)),
|
||||
this, SLOT(updateCurHeader(int, int)));
|
||||
connect(m_textEditor, &VEdit::textChanged,
|
||||
|
@ -52,6 +52,7 @@ private slots:
|
||||
void updateCurHeader(int p_lineNumber, int p_outlineIndex);
|
||||
void updateTocFromHeaders(const QVector<VHeader> &headers);
|
||||
void handleTextChanged();
|
||||
void noticeStatusChanged();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
@ -62,7 +63,6 @@ private:
|
||||
inline bool isChild(QObject *obj);
|
||||
void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||
void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||
void noticeStatusChanged();
|
||||
void scrollPreviewToHeader(int p_outlineIndex);
|
||||
|
||||
QPointer<VFile> m_file;
|
||||
|
217
src/vmdedit.cpp
217
src/vmdedit.cpp
@ -9,6 +9,8 @@
|
||||
|
||||
extern VConfigManager vconfig;
|
||||
|
||||
enum ImageProperty { ImagePath = 1 };
|
||||
|
||||
VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
|
||||
: VEdit(p_file, p_parent), m_mdHighlighter(NULL)
|
||||
{
|
||||
@ -19,6 +21,8 @@ VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
|
||||
500, document());
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||
this, &VMdEdit::generateEditOutline);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated,
|
||||
this, &VMdEdit::updateImageBlocks);
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
|
||||
connect(this, &VMdEdit::cursorPositionChanged,
|
||||
@ -53,7 +57,7 @@ void VMdEdit::beginEdit()
|
||||
|
||||
setFont(vconfig.getMdEditFont());
|
||||
|
||||
Q_ASSERT(m_file->getContent() == toPlainText());
|
||||
Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg());
|
||||
|
||||
initInitImages();
|
||||
|
||||
@ -75,13 +79,15 @@ void VMdEdit::saveFile()
|
||||
if (!document()->isModified()) {
|
||||
return;
|
||||
}
|
||||
m_file->setContent(toPlainText());
|
||||
m_file->setContent(toPlainTextWithoutImg());
|
||||
document()->setModified(false);
|
||||
}
|
||||
|
||||
void VMdEdit::reloadFile()
|
||||
{
|
||||
setPlainText(m_file->getContent());
|
||||
QString &content = m_file->getContent();
|
||||
Q_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1);
|
||||
setPlainText(content);
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@ -223,3 +229,208 @@ void VMdEdit::scrollToHeader(int p_headerIndex)
|
||||
scrollToLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
void VMdEdit::updateImageBlocks(QSet<int> p_imageBlocks)
|
||||
{
|
||||
// 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 {
|
||||
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).
|
||||
QTextBlock prevBlock = p_block.previous();
|
||||
if (prevBlock.isValid()) {
|
||||
if (fetchImageToPreview(prevBlock.text()).isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
int start = 0;
|
||||
do {
|
||||
int index = text.indexOf(QChar::ObjectReplacementCharacter, start);
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
start = removeObjectReplacementLine(text, index);
|
||||
} while (start < text.size());
|
||||
qDebug() << text;
|
||||
return text;
|
||||
}
|
||||
|
||||
int VMdEdit::removeObjectReplacementLine(QString &p_text, int p_index) const
|
||||
{
|
||||
Q_ASSERT(p_text.size() > p_index && p_text.at(p_index) == QChar::ObjectReplacementCharacter);
|
||||
Q_ASSERT(p_text.at(p_index + 1) == '\n');
|
||||
int prevLineIdx = p_text.lastIndexOf('\n', p_index);
|
||||
if (prevLineIdx == -1) {
|
||||
prevLineIdx = 0;
|
||||
}
|
||||
// Remove \n[....?\n]
|
||||
p_text.remove(prevLineIdx + 1, p_index - prevLineIdx + 1);
|
||||
return prevLineIdx;
|
||||
}
|
||||
|
@ -23,14 +23,19 @@ public:
|
||||
|
||||
// Scroll to m_headers[p_headerIndex].
|
||||
void scrollToHeader(int p_headerIndex);
|
||||
// Like toPlainText(), but remove special blocks containing images.
|
||||
QString toPlainTextWithoutImg() const;
|
||||
|
||||
signals:
|
||||
void headersChanged(const QVector<VHeader> &headers);
|
||||
void curHeaderChanged(int p_lineNumber, int p_outlineIndex);
|
||||
void statusChanged();
|
||||
|
||||
private slots:
|
||||
void generateEditOutline();
|
||||
void updateCurHeader();
|
||||
// Update block list containing image links.
|
||||
void updateImageBlocks(QSet<int> p_imageBlocks);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
|
||||
@ -42,6 +47,23 @@ private:
|
||||
void updateTabSettings();
|
||||
void initInitImages();
|
||||
void clearUnusedImages();
|
||||
// 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);
|
||||
// Returns the image relative path (image/xxx.png) only when
|
||||
// there is one and only one image link.
|
||||
QString fetchImageToPreview(const QString &p_text);
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
QVector<QString> m_insertedImages;
|
||||
@ -49,7 +71,6 @@ private:
|
||||
bool m_expandTab;
|
||||
QString m_tabSpaces;
|
||||
QVector<VHeader> m_headers;
|
||||
|
||||
};
|
||||
|
||||
#endif // VMDEDIT_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user