preview image links

Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
Le Tan 2016-12-14 19:57:51 +08:00
parent e069305b23
commit a22bf1059d
7 changed files with 279 additions and 8 deletions

View File

@ -112,10 +112,41 @@ 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();

View File

@ -13,6 +13,7 @@
#include <QTextCharFormat> #include <QTextCharFormat>
#include <QSyntaxHighlighter> #include <QSyntaxHighlighter>
#include <QAtomicInt> #include <QAtomicInt>
#include <QSet>
extern "C" { extern "C" {
#include <pmh_parser.h> #include <pmh_parser.h>
@ -58,6 +59,7 @@ public:
signals: signals:
void highlightCompleted(); void highlightCompleted();
void imageBlocksUpdated(QSet<int> p_blocks);
protected: protected:
void highlightBlock(const QString &text) Q_DECL_OVERRIDE; void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
@ -74,6 +76,8 @@ private:
QTextDocument *document; QTextDocument *document;
QVector<HighlightingStyle> highlightingStyles; QVector<HighlightingStyle> highlightingStyles;
QVector<QVector<HLUnit> > blockHighlights; QVector<QVector<HLUnit> > blockHighlights;
// Block numbers containing image link(s).
QSet<int> imageBlocks;
QAtomicInt parsing; QAtomicInt parsing;
QTimer *timer; QTimer *timer;
int waitInterval; int waitInterval;
@ -81,15 +85,17 @@ private:
char *content; char *content;
int capacity; int capacity;
pmh_element **result; 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 highlightCodeBlock(const QString &text);
void parse(); void parse();
void parseInternal(); void parseInternal();
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();
}; };
#endif #endif

View File

@ -29,7 +29,7 @@ void VLogger(QtMsgType type, const QMessageLogContext &context, const QString &m
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
qInstallMessageHandler(VLogger); //qInstallMessageHandler(VLogger);
VSingleInstanceGuard guard; VSingleInstanceGuard guard;
if (!guard.tryRun()) { if (!guard.tryRun()) {

View File

@ -50,6 +50,8 @@ void VEditTab::setupUI()
m_textEditor = new VMdEdit(m_file, this); m_textEditor = new VMdEdit(m_file, this);
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged, connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
this, &VEditTab::updateTocFromHeaders); this, &VEditTab::updateTocFromHeaders);
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::statusChanged,
this, &VEditTab::noticeStatusChanged);
connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)), connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)),
this, SLOT(updateCurHeader(int, int))); this, SLOT(updateCurHeader(int, int)));
connect(m_textEditor, &VEdit::textChanged, connect(m_textEditor, &VEdit::textChanged,

View File

@ -52,6 +52,7 @@ private slots:
void updateCurHeader(int p_lineNumber, int p_outlineIndex); void updateCurHeader(int p_lineNumber, int p_outlineIndex);
void updateTocFromHeaders(const QVector<VHeader> &headers); void updateTocFromHeaders(const QVector<VHeader> &headers);
void handleTextChanged(); void handleTextChanged();
void noticeStatusChanged();
private: private:
void setupUI(); void setupUI();
@ -62,7 +63,6 @@ private:
inline bool isChild(QObject *obj); inline bool isChild(QObject *obj);
void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level); void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level); void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
void noticeStatusChanged();
void scrollPreviewToHeader(int p_outlineIndex); void scrollPreviewToHeader(int p_outlineIndex);
QPointer<VFile> m_file; QPointer<VFile> m_file;

View File

@ -9,6 +9,8 @@
extern VConfigManager vconfig; extern VConfigManager vconfig;
enum ImageProperty { ImagePath = 1 };
VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent) VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
: VEdit(p_file, p_parent), m_mdHighlighter(NULL) : VEdit(p_file, p_parent), m_mdHighlighter(NULL)
{ {
@ -19,6 +21,8 @@ VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
500, document()); 500, 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_editOps = new VMdEditOperations(this, m_file); m_editOps = new VMdEditOperations(this, m_file);
connect(this, &VMdEdit::cursorPositionChanged, connect(this, &VMdEdit::cursorPositionChanged,
@ -53,7 +57,7 @@ void VMdEdit::beginEdit()
setFont(vconfig.getMdEditFont()); setFont(vconfig.getMdEditFont());
Q_ASSERT(m_file->getContent() == toPlainText()); Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg());
initInitImages(); initInitImages();
@ -75,13 +79,15 @@ void VMdEdit::saveFile()
if (!document()->isModified()) { if (!document()->isModified()) {
return; return;
} }
m_file->setContent(toPlainText()); m_file->setContent(toPlainTextWithoutImg());
document()->setModified(false); document()->setModified(false);
} }
void VMdEdit::reloadFile() void VMdEdit::reloadFile()
{ {
setPlainText(m_file->getContent()); QString &content = m_file->getContent();
Q_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1);
setPlainText(content);
setModified(false); setModified(false);
} }
@ -223,3 +229,208 @@ void VMdEdit::scrollToHeader(int p_headerIndex)
scrollToLine(line); 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;
}

View File

@ -23,14 +23,19 @@ public:
// Scroll to m_headers[p_headerIndex]. // Scroll to m_headers[p_headerIndex].
void scrollToHeader(int p_headerIndex); void scrollToHeader(int p_headerIndex);
// Like toPlainText(), but remove special blocks containing images.
QString toPlainTextWithoutImg() const;
signals: signals:
void headersChanged(const QVector<VHeader> &headers); void headersChanged(const QVector<VHeader> &headers);
void curHeaderChanged(int p_lineNumber, int p_outlineIndex); void curHeaderChanged(int p_lineNumber, int p_outlineIndex);
void statusChanged();
private slots: private slots:
void generateEditOutline(); void generateEditOutline();
void updateCurHeader(); void updateCurHeader();
// Update block list containing image links.
void updateImageBlocks(QSet<int> p_imageBlocks);
protected: protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
@ -42,6 +47,23 @@ private:
void updateTabSettings(); void updateTabSettings();
void initInitImages(); void initInitImages();
void clearUnusedImages(); 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; HGMarkdownHighlighter *m_mdHighlighter;
QVector<QString> m_insertedImages; QVector<QString> m_insertedImages;
@ -49,7 +71,6 @@ private:
bool m_expandTab; bool m_expandTab;
QString m_tabSpaces; QString m_tabSpaces;
QVector<VHeader> m_headers; QVector<VHeader> m_headers;
}; };
#endif // VMDEDIT_H #endif // VMDEDIT_H