Browse Source

refactor: add VMdEdit to inherit VEdit for markdown edit

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 years ago
parent
commit
a884991150
9 changed files with 354 additions and 322 deletions
  1. 4 2
      src/src.pro
  2. 17 231
      src/vedit.cpp
  3. 9 50
      src/vedit.h
  4. 48 33
      src/vedittab.cpp
  5. 6 4
      src/vedittab.h
  6. 1 0
      src/vmainwindow.cpp
  7. 211 0
      src/vmdedit.cpp
  8. 51 0
      src/vmdedit.h
  9. 7 2
      src/vmdeditoperations.cpp

+ 4 - 2
src/src.pro

@@ -47,7 +47,8 @@ SOURCES += main.cpp\
     vfile.cpp \
     vnotebookselector.cpp \
     vnofocusitemdelegate.cpp \
-    vavatar.cpp
+    vavatar.cpp \
+    vmdedit.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -83,7 +84,8 @@ HEADERS  += vmainwindow.h \
     vfile.h \
     vnotebookselector.h \
     vnofocusitemdelegate.h \
-    vavatar.h
+    vavatar.h \
+    vmdedit.h
 
 RESOURCES += \
     vnote.qrc

+ 17 - 231
src/vedit.cpp

@@ -3,107 +3,34 @@
 #include "vedit.h"
 #include "vnote.h"
 #include "vconfigmanager.h"
-#include "hgmarkdownhighlighter.h"
-#include "vmdeditoperations.h"
 #include "vtoc.h"
 #include "utils/vutils.h"
+#include "veditoperations.h"
 
 extern VConfigManager vconfig;
 
 VEdit::VEdit(VFile *p_file, QWidget *p_parent)
-    : QTextEdit(p_parent), m_file(p_file), mdHighlighter(NULL)
+    : QTextEdit(p_parent), m_file(p_file), m_editOps(NULL)
 {
     connect(document(), &QTextDocument::modificationChanged,
             (VFile *)m_file, &VFile::setModified);
-
-    if (m_file->getDocType() == DocType::Markdown) {
-        setAcceptRichText(false);
-        mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
-                                                  500, document());
-        connect(mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
-                this, &VEdit::generateEditOutline);
-        editOps = new VMdEditOperations(this, m_file);
-    } else {
-        editOps = NULL;
-    }
-
-    updateTabSettings();
-    updateFontAndPalette();
-    connect(this, &VEdit::cursorPositionChanged,
-            this, &VEdit::updateCurHeader);
 }
 
 VEdit::~VEdit()
 {
+    qDebug() << "VEdit destruction";
     if (m_file) {
         disconnect(document(), &QTextDocument::modificationChanged,
                    (VFile *)m_file, &VFile::setModified);
     }
-    if (editOps) {
-        delete editOps;
-        editOps = NULL;
-    }
-}
-
-void VEdit::updateFontAndPalette()
-{
-    switch (m_file->getDocType()) {
-    case DocType::Markdown:
-        setFont(vconfig.getMdEditFont());
-        setPalette(vconfig.getMdEditPalette());
-        break;
-    case DocType::Html:
-        setFont(vconfig.getBaseEditFont());
-        setPalette(vconfig.getBaseEditPalette());
-        break;
-    default:
-        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
-        return;
-    }
-}
-
-void VEdit::updateTabSettings()
-{
-    switch (m_file->getDocType()) {
-    case DocType::Markdown:
-        if (vconfig.getTabStopWidth() > 0) {
-            QFontMetrics metrics(vconfig.getMdEditFont());
-            setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' '));
-        }
-        break;
-    case DocType::Html:
-        if (vconfig.getTabStopWidth() > 0) {
-            QFontMetrics metrics(vconfig.getBaseEditFont());
-            setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' '));
-        }
-        break;
-    default:
-        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
-        return;
-    }
-
-    isExpandTab = vconfig.getIsExpandTab();
-    if (isExpandTab && (vconfig.getTabStopWidth() > 0)) {
-        tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
+    if (m_editOps) {
+        delete m_editOps;
+        m_editOps = NULL;
     }
 }
 
 void VEdit::beginEdit()
 {
-    updateTabSettings();
-    updateFontAndPalette();
-    switch (m_file->getDocType()) {
-    case DocType::Html:
-        setHtml(m_file->getContent());
-        break;
-    case DocType::Markdown:
-        setFont(vconfig.getMdEditFont());
-        setPlainText(m_file->getContent());
-        initInitImages();
-        break;
-    default:
-        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
-    }
     setReadOnly(false);
     setModified(false);
 }
@@ -111,9 +38,6 @@ void VEdit::beginEdit()
 void VEdit::endEdit()
 {
     setReadOnly(true);
-    if (m_file->getDocType() == DocType::Markdown) {
-        clearUnusedImages();
-    }
 }
 
 void VEdit::saveFile()
@@ -121,176 +45,38 @@ void VEdit::saveFile()
     if (!document()->isModified()) {
         return;
     }
-
-    switch (m_file->getDocType()) {
-    case DocType::Html:
-        m_file->setContent(toHtml());
-        break;
-    case DocType::Markdown:
-        m_file->setContent(toPlainText());
-        break;
-    default:
-        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
-    }
+    m_file->setContent(toHtml());
     document()->setModified(false);
 }
 
 void VEdit::reloadFile()
 {
-    switch (m_file->getDocType()) {
-    case DocType::Html:
-        setHtml(m_file->getContent());
-        break;
-    case DocType::Markdown:
-        setPlainText(m_file->getContent());
-        break;
-    default:
-        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
-    }
+    setHtml(m_file->getContent());
     setModified(false);
 }
 
-void VEdit::keyPressEvent(QKeyEvent *event)
+void VEdit::scrollToLine(int p_lineNumber)
 {
-    if ((event->key() == Qt::Key_Tab) && isExpandTab) {
-        QTextCursor cursor(document());
-        cursor.setPosition(textCursor().position());
-        cursor.insertText(tabSpaces);
-        return;
-    }
-    QTextEdit::keyPressEvent(event);
-}
-
-bool VEdit::canInsertFromMimeData(const QMimeData *source) const
-{
-    return source->hasImage() || source->hasUrls()
-           || QTextEdit::canInsertFromMimeData(source);
-}
-
-void VEdit::insertFromMimeData(const QMimeData *source)
-{
-    if (source->hasImage()) {
-        // Image data in the clipboard
-        if (editOps) {
-            bool ret = editOps->insertImageFromMimeData(source);
-            if (ret) {
-                return;
-            }
-        }
-    } else if (source->hasUrls()) {
-        // Paste an image file
-        if (editOps) {
-            bool ret = editOps->insertURLFromMimeData(source);
-            if (ret) {
-                return;
-            }
-        }
-    }
-    QTextEdit::insertFromMimeData(source);
-}
-
-void VEdit::generateEditOutline()
-{
-    QTextDocument *doc = document();
-    headers.clear();
-    // Assume that each block contains only one line
-    // Only support # syntax for now
-    QRegExp headerReg("(#{1,6})\\s*(\\S.*)");  // Need to trim the spaces
-    for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
-        Q_ASSERT(block.lineCount() == 1);
-        if ((block.userState() == HighlightBlockState::BlockNormal) &&
-            headerReg.exactMatch(block.text())) {
-            VHeader header(headerReg.cap(1).length(),
-                           headerReg.cap(2).trimmed(), "", block.firstLineNumber());
-            headers.append(header);
-        }
-    }
-
-    emit headersChanged(headers);
-    updateCurHeader();
-}
-
-void VEdit::scrollToLine(int lineNumber)
-{
-    Q_ASSERT(lineNumber >= 0);
+    Q_ASSERT(p_lineNumber >= 0);
 
     // Move the cursor to the end first
     moveCursor(QTextCursor::End);
-    QTextCursor cursor(document()->findBlockByLineNumber(lineNumber));
+    QTextCursor cursor(document()->findBlockByLineNumber(p_lineNumber));
     cursor.movePosition(QTextCursor::EndOfBlock);
     setTextCursor(cursor);
 
     setFocus();
 }
 
-void VEdit::updateCurHeader()
+bool VEdit::isModified() const
 {
-    int curHeader = 0;
-    QTextCursor cursor(this->textCursor());
-    int curLine = cursor.block().firstLineNumber();
-    for (int i = headers.size() - 1; i >= 0; --i) {
-        if (headers[i].lineNumber <= curLine) {
-            curHeader = headers[i].lineNumber;
-            break;
-        }
-    }
-    emit curHeaderChanged(curHeader);
+    return document()->isModified();
 }
 
-void VEdit::insertImage(const QString &name)
+void VEdit::setModified(bool p_modified)
 {
-    m_insertedImages.append(name);
-}
-
-void VEdit::initInitImages()
-{
-    m_initImages = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
-}
-
-void VEdit::clearUnusedImages()
-{
-    QVector<QString> images = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
-
-    if (!m_insertedImages.isEmpty()) {
-        QVector<QString> imageNames(images.size());
-        for (int i = 0; i < imageNames.size(); ++i) {
-            imageNames[i] = VUtils::fileNameFromPath(images[i]);
-        }
-
-        QDir dir = QDir(m_file->retriveImagePath());
-        for (int i = 0; i < m_insertedImages.size(); ++i) {
-            QString name = m_insertedImages[i];
-            int j;
-            for (j = 0; j < imageNames.size(); ++j) {
-                if (name == imageNames[j]) {
-                    break;
-                }
-            }
-
-            // Delete it
-            if (j == imageNames.size()) {
-                QString imagePath = dir.filePath(name);
-                QFile(imagePath).remove();
-                qDebug() << "delete inserted image" << imagePath;
-            }
-        }
-        m_insertedImages.clear();
-    }
-
-    for (int i = 0; i < m_initImages.size(); ++i) {
-        QString imagePath = m_initImages[i];
-        int j;
-        for (j = 0; j < images.size(); ++j) {
-            if (imagePath == images[j]) {
-                break;
-            }
-        }
-
-        // Delete it
-        if (j == images.size()) {
-            QFile(imagePath).remove();
-            qDebug() << "delete existing image" << imagePath;
-        }
+    document()->setModified(p_modified);
+    if (m_file) {
+        m_file->setModified(p_modified);
     }
-    m_initImages.clear();
 }

+ 9 - 50
src/vedit.h

@@ -8,7 +8,6 @@
 #include "vtoc.h"
 #include "vfile.h"
 
-class HGMarkdownHighlighter;
 class VEditOperations;
 
 class VEdit : public QTextEdit
@@ -16,61 +15,21 @@ class VEdit : public QTextEdit
     Q_OBJECT
 public:
     VEdit(VFile *p_file, QWidget *p_parent = 0);
-    ~VEdit();
-    void beginEdit();
-    void endEdit();
-
+    virtual ~VEdit();
+    virtual void beginEdit();
+    virtual void endEdit();
     // Save buffer content to VFile.
-    void saveFile();
-
-    inline void setModified(bool modified);
-    inline bool isModified() const;
-
-    void reloadFile();
-    void scrollToLine(int lineNumber);
-    void insertImage(const QString &name);
-
-signals:
-    void headersChanged(const QVector<VHeader> &headers);
-    void curHeaderChanged(int lineNumber);
+    virtual void saveFile();
+    virtual void setModified(bool p_modified);
+    bool isModified() const;
+    virtual void reloadFile();
+    virtual void scrollToLine(int p_lineNumber);
 
 protected:
-    void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
-    bool canInsertFromMimeData(const QMimeData *source) const Q_DECL_OVERRIDE;
-    void insertFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
-
-private slots:
-    void generateEditOutline();
-    void updateCurHeader();
-
-private:
-    void updateTabSettings();
-    void updateFontAndPalette();
-    void initInitImages();
-    void clearUnusedImages();
-
     QPointer<VFile> m_file;
-    bool isExpandTab;
-    QString tabSpaces;
-    HGMarkdownHighlighter *mdHighlighter;
-    VEditOperations *editOps;
-    QVector<VHeader> headers;
-    QVector<QString> m_insertedImages;
-    QVector<QString> m_initImages;
+    VEditOperations *m_editOps;
 };
 
 
-inline bool VEdit::isModified() const
-{
-    return document()->isModified();
-}
-
-inline void VEdit::setModified(bool modified)
-{
-    document()->setModified(modified);
-    if (m_file) {
-        m_file->setModified(modified);
-    }
-}
 
 #endif // VEDIT_H

+ 48 - 33
src/vedittab.cpp

@@ -1,5 +1,4 @@
 #include <QtWidgets>
-#include <QTextBrowser>
 #include <QWebChannel>
 #include <QWebEngineView>
 #include <QFileInfo>
@@ -15,12 +14,13 @@
 #include "vmarkdownconverter.h"
 #include "vnotebook.h"
 #include "vtoc.h"
+#include "vmdedit.h"
 
 extern VConfigManager vconfig;
 
 VEditTab::VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent)
     : QStackedWidget(p_parent), m_file(p_file), isEditMode(false),
-      mdConverterType(vconfig.getMdConverterType())
+      mdConverterType(vconfig.getMdConverterType()), m_fileModified(false)
 {
     tableOfContent.filePath = p_file->retrivePath();
     curHeader.filePath = p_file->retrivePath();
@@ -45,31 +45,46 @@ VEditTab::~VEditTab()
 
 void VEditTab::setupUI()
 {
-    textEditor = new VEdit(m_file);
-    connect(textEditor, &VEdit::headersChanged,
-            this, &VEditTab::updateTocFromHeaders);
-    connect(textEditor, SIGNAL(curHeaderChanged(int)),
-            this, SLOT(updateCurHeader(int)));
-    connect(textEditor, &VEdit::textChanged,
-            this, &VEditTab::statusChanged);
-    addWidget(textEditor);
-
     switch (m_file->getDocType()) {
     case DocType::Markdown:
+        m_textEditor = new VMdEdit(m_file, this);
+        connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
+                this, &VEditTab::updateTocFromHeaders);
+        connect(m_textEditor, SIGNAL(curHeaderChanged(int)),
+                this, SLOT(updateCurHeader(int)));
+        connect(m_textEditor, &VEdit::textChanged,
+                this, &VEditTab::handleTextChanged);
+        addWidget(m_textEditor);
+
         setupMarkdownPreview();
-        textBrowser = NULL;
         break;
 
     case DocType::Html:
-        textBrowser = new QTextBrowser();
-        addWidget(textBrowser);
-        textBrowser->setFont(vconfig.getBaseEditFont());
-        textBrowser->setPalette(vconfig.getBaseEditPalette());
+        m_textEditor = new VEdit(m_file, this);
+        connect(m_textEditor, &VEdit::textChanged,
+                this, &VEditTab::handleTextChanged);
+        m_textEditor->reloadFile();
+        addWidget(m_textEditor);
         webPreviewer = NULL;
         break;
     default:
         qWarning() << "error: unknown doc type" << int(m_file->getDocType());
+        Q_ASSERT(false);
+    }
+}
+
+void VEditTab::handleTextChanged()
+{
+    if (m_fileModified) {
+        return;
     }
+    noticeStatusChanged();
+}
+
+void VEditTab::noticeStatusChanged()
+{
+    m_fileModified = m_file->isModified();
+    emit statusChanged();
 }
 
 void VEditTab::showFileReadMode()
@@ -78,10 +93,7 @@ void VEditTab::showFileReadMode()
     isEditMode = false;
     switch (m_file->getDocType()) {
     case DocType::Html:
-        textBrowser->setHtml(m_file->getContent());
-        textBrowser->setFont(vconfig.getBaseEditFont());
-        textBrowser->setPalette(vconfig.getBaseEditPalette());
-        setCurrentWidget(textBrowser);
+        m_textEditor->setReadOnly(true);
         break;
     case DocType::Markdown:
         if (mdConverterType == MarkdownConverterType::Marked) {
@@ -93,7 +105,9 @@ void VEditTab::showFileReadMode()
         break;
     default:
         qWarning() << "error: unknown doc type" << int(m_file->getDocType());
+        Q_ASSERT(false);
     }
+    noticeStatusChanged();
 }
 
 void VEditTab::previewByConverter()
@@ -112,17 +126,18 @@ void VEditTab::previewByConverter()
 void VEditTab::showFileEditMode()
 {
     isEditMode = true;
-    textEditor->beginEdit();
-    setCurrentWidget(textEditor);
-    textEditor->setFocus();
+    m_textEditor->beginEdit();
+    setCurrentWidget(m_textEditor);
+    m_textEditor->setFocus();
+    noticeStatusChanged();
 }
 
 bool VEditTab::closeFile(bool p_forced)
 {
     if (p_forced && isEditMode) {
         // Discard buffer content
-        textEditor->reloadFile();
-        textEditor->endEdit();
+        m_textEditor->reloadFile();
+        m_textEditor->endEdit();
         showFileReadMode();
     } else {
         readFile();
@@ -145,7 +160,7 @@ void VEditTab::readFile()
         return;
     }
 
-    if (textEditor->isModified()) {
+    if (m_textEditor->isModified()) {
         // Prompt to save the changes
         int ret = VUtils::showMessage(QMessageBox::Information, tr("Information"),
                                       QString("Note %1 has been modified.").arg(m_file->getName()),
@@ -157,7 +172,7 @@ void VEditTab::readFile()
             saveFile();
             // Fall through
         case QMessageBox::Discard:
-            textEditor->reloadFile();
+            m_textEditor->reloadFile();
             break;
         case QMessageBox::Cancel:
             // Nothing to do if user cancel this action
@@ -167,14 +182,14 @@ void VEditTab::readFile()
             return;
         }
     }
-    textEditor->endEdit();
+    m_textEditor->endEdit();
     showFileReadMode();
 }
 
 bool VEditTab::saveFile()
 {
     bool ret;
-    if (!isEditMode || !textEditor->isModified()) {
+    if (!isEditMode || !m_textEditor->isModified()) {
         return true;
     }
     // Make sure the file already exists. Temporary deal with cases when user delete or move
@@ -187,15 +202,15 @@ bool VEditTab::saveFile()
                             QMessageBox::Ok, QMessageBox::Ok, this);
         return false;
     }
-    textEditor->saveFile();
+    m_textEditor->saveFile();
     ret = m_file->save();
     if (!ret) {
         VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note"),
                             QString("Fail to write to disk when saving a note. Please try it again."),
                             QMessageBox::Ok, QMessageBox::Ok, this);
-        textEditor->setModified(true);
+        m_textEditor->setModified(true);
     }
-    emit statusChanged();
+    noticeStatusChanged();
     return ret;
 }
 
@@ -353,7 +368,7 @@ void VEditTab::scrollToAnchor(const VAnchor &anchor)
     curHeader = anchor;
     if (isEditMode) {
         if (anchor.lineNumber > -1) {
-            textEditor->scrollToLine(anchor.lineNumber);
+            m_textEditor->scrollToLine(anchor.lineNumber);
         }
     } else {
         if (!anchor.anchor.isEmpty()) {

+ 6 - 4
src/vedittab.h

@@ -12,7 +12,6 @@
 #include "vtoc.h"
 #include "vfile.h"
 
-class QTextBrowser;
 class QWebEngineView;
 class VNote;
 class QXmlStreamReader;
@@ -38,6 +37,7 @@ public:
     void requestUpdateCurHeader();
     void scrollToAnchor(const VAnchor& anchor);
     inline VFile *getFile();
+
 signals:
     void getFocused();
     void outlineChanged(const VToc &toc);
@@ -50,6 +50,7 @@ private slots:
     void updateCurHeader(const QString &anchor);
     void updateCurHeader(int lineNumber);
     void updateTocFromHeaders(const QVector<VHeader> &headers);
+    void handleTextChanged();
 
 private:
     void setupUI();
@@ -60,16 +61,17 @@ 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();
 
     QPointer<VFile> m_file;
     bool isEditMode;
-    QTextBrowser *textBrowser;
-    VEdit *textEditor;
+    VEdit *m_textEditor;
     QWebEngineView *webPreviewer;
     VDocument document;
     MarkdownConverterType mdConverterType;
     VToc tableOfContent;
     VAnchor curHeader;
+    bool m_fileModified;
 };
 
 inline bool VEditTab::getIsEditMode() const
@@ -79,7 +81,7 @@ inline bool VEditTab::getIsEditMode() const
 
 inline bool VEditTab::isModified() const
 {
-    return textEditor->isModified();
+    return m_textEditor->isModified();
 }
 
 inline bool VEditTab::isChild(QObject *obj)

+ 1 - 0
src/vmainwindow.cpp

@@ -530,6 +530,7 @@ void VMainWindow::setRenderBackgroundColor(QAction *action)
 
 void VMainWindow::updateToolbarFromTabChage(const VFile *p_file, bool p_editMode)
 {
+    qDebug() << "update toolbar";
     if (!p_file) {
         editNoteAct->setVisible(false);
         saveExitAct->setVisible(false);

+ 211 - 0
src/vmdedit.cpp

@@ -0,0 +1,211 @@
+#include <QtWidgets>
+#include "vmdedit.h"
+#include "hgmarkdownhighlighter.h"
+#include "vmdeditoperations.h"
+#include "vnote.h"
+#include "vconfigmanager.h"
+#include "vtoc.h"
+#include "utils/vutils.h"
+
+extern VConfigManager vconfig;
+
+VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
+    : VEdit(p_file, p_parent), m_mdHighlighter(NULL)
+{
+    Q_ASSERT(p_file->getDocType() == DocType::Markdown);
+
+    setAcceptRichText(false);
+    m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
+                                                500, document());
+    connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
+            this, &VMdEdit::generateEditOutline);
+    m_editOps = new VMdEditOperations(this, m_file);
+
+    connect(this, &VMdEdit::cursorPositionChanged,
+            this, &VMdEdit::updateCurHeader);
+
+    updateTabSettings();
+    updateFontAndPalette();
+}
+
+void VMdEdit::updateFontAndPalette()
+{
+    setFont(vconfig.getMdEditFont());
+    setPalette(vconfig.getMdEditPalette());
+}
+
+void VMdEdit::updateTabSettings()
+{
+    if (vconfig.getTabStopWidth() > 0) {
+        QFontMetrics metrics(vconfig.getMdEditFont());
+        setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' '));
+    }
+    m_expandTab = vconfig.getIsExpandTab();
+    if (m_expandTab && (vconfig.getTabStopWidth() > 0)) {
+        m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
+    }
+}
+
+void VMdEdit::beginEdit()
+{
+    updateTabSettings();
+    updateFontAndPalette();
+
+    setFont(vconfig.getMdEditFont());
+
+    setPlainText(m_file->getContent());
+
+    initInitImages();
+
+    setReadOnly(false);
+    setModified(false);
+}
+
+void VMdEdit::endEdit()
+{
+    setReadOnly(true);
+    clearUnusedImages();
+}
+
+void VMdEdit::saveFile()
+{
+    if (!document()->isModified()) {
+        return;
+    }
+    m_file->setContent(toPlainText());
+    document()->setModified(false);
+}
+
+void VMdEdit::reloadFile()
+{
+    setPlainText(m_file->getContent());
+    setModified(false);
+}
+
+void VMdEdit::keyPressEvent(QKeyEvent *event)
+{
+    if ((event->key() == Qt::Key_Tab) && m_expandTab) {
+        QTextCursor cursor(document());
+        cursor.setPosition(textCursor().position());
+        cursor.insertText(m_tabSpaces);
+        return;
+    }
+    VEdit::keyPressEvent(event);
+}
+
+bool VMdEdit::canInsertFromMimeData(const QMimeData *source) const
+{
+    return source->hasImage() || source->hasUrls()
+           || VEdit::canInsertFromMimeData(source);
+}
+
+void VMdEdit::insertFromMimeData(const QMimeData *source)
+{
+    if (source->hasImage()) {
+        // Image data in the clipboard
+        bool ret = m_editOps->insertImageFromMimeData(source);
+        if (ret) {
+            return;
+        }
+    } else if (source->hasUrls()) {
+        // Paste an image file
+        bool ret = m_editOps->insertURLFromMimeData(source);
+        if (ret) {
+            return;
+        }
+    }
+    VEdit::insertFromMimeData(source);
+}
+
+void VMdEdit::insertImage(const QString &p_name)
+{
+    m_insertedImages.append(p_name);
+}
+
+void VMdEdit::initInitImages()
+{
+    m_initImages = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
+}
+
+void VMdEdit::clearUnusedImages()
+{
+    QVector<QString> images = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
+
+    if (!m_insertedImages.isEmpty()) {
+        QVector<QString> imageNames(images.size());
+        for (int i = 0; i < imageNames.size(); ++i) {
+            imageNames[i] = VUtils::fileNameFromPath(images[i]);
+        }
+
+        QDir dir = QDir(m_file->retriveImagePath());
+        for (int i = 0; i < m_insertedImages.size(); ++i) {
+            QString name = m_insertedImages[i];
+            int j;
+            for (j = 0; j < imageNames.size(); ++j) {
+                if (name == imageNames[j]) {
+                    break;
+                }
+            }
+
+            // Delete it
+            if (j == imageNames.size()) {
+                QString imagePath = dir.filePath(name);
+                QFile(imagePath).remove();
+                qDebug() << "delete inserted image" << imagePath;
+            }
+        }
+        m_insertedImages.clear();
+    }
+
+    for (int i = 0; i < m_initImages.size(); ++i) {
+        QString imagePath = m_initImages[i];
+        int j;
+        for (j = 0; j < images.size(); ++j) {
+            if (imagePath == images[j]) {
+                break;
+            }
+        }
+
+        // Delete it
+        if (j == images.size()) {
+            QFile(imagePath).remove();
+            qDebug() << "delete existing image" << imagePath;
+        }
+    }
+    m_initImages.clear();
+}
+
+void VMdEdit::updateCurHeader()
+{
+    int curHeader = 0;
+    QTextCursor cursor(this->textCursor());
+    int curLine = cursor.block().firstLineNumber();
+    for (int i = m_headers.size() - 1; i >= 0; --i) {
+        if (m_headers[i].lineNumber <= curLine) {
+            curHeader = m_headers[i].lineNumber;
+            break;
+        }
+    }
+    emit curHeaderChanged(curHeader);
+}
+
+void VMdEdit::generateEditOutline()
+{
+    QTextDocument *doc = document();
+    m_headers.clear();
+    // Assume that each block contains only one line
+    // Only support # syntax for now
+    QRegExp headerReg("(#{1,6})\\s*(\\S.*)");  // Need to trim the spaces
+    for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
+        Q_ASSERT(block.lineCount() == 1);
+        if ((block.userState() == HighlightBlockState::BlockNormal) &&
+            headerReg.exactMatch(block.text())) {
+            VHeader header(headerReg.cap(1).length(),
+                           headerReg.cap(2).trimmed(), "", block.firstLineNumber());
+            m_headers.append(header);
+        }
+    }
+
+    emit headersChanged(m_headers);
+    updateCurHeader();
+}

+ 51 - 0
src/vmdedit.h

@@ -0,0 +1,51 @@
+#ifndef VMDEDIT_H
+#define VMDEDIT_H
+
+#include "vedit.h"
+#include <QVector>
+#include <QString>
+#include "vtoc.h"
+
+class HGMarkdownHighlighter;
+
+class VMdEdit : public VEdit
+{
+    Q_OBJECT
+public:
+    VMdEdit(VFile *p_file, QWidget *p_parent = 0);
+    void beginEdit() Q_DECL_OVERRIDE;
+    void endEdit() Q_DECL_OVERRIDE;
+    void saveFile() Q_DECL_OVERRIDE;
+    void reloadFile() Q_DECL_OVERRIDE;
+
+    void insertImage(const QString &p_name);
+
+signals:
+    void headersChanged(const QVector<VHeader> &headers);
+    void curHeaderChanged(int lineNumber);
+
+private slots:
+    void generateEditOutline();
+    void updateCurHeader();
+
+protected:
+    void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
+    bool canInsertFromMimeData(const QMimeData *source) const Q_DECL_OVERRIDE;
+    void insertFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
+
+private:
+    void updateFontAndPalette();
+    void updateTabSettings();
+    void initInitImages();
+    void clearUnusedImages();
+
+    HGMarkdownHighlighter *m_mdHighlighter;
+    QVector<QString> m_insertedImages;
+    QVector<QString> m_initImages;
+    bool m_expandTab;
+    QString m_tabSpaces;
+    QVector<VHeader> m_headers;
+
+};
+
+#endif // VMDEDIT_H

+ 7 - 2
src/vmdeditoperations.cpp

@@ -13,6 +13,7 @@
 #include "vedit.h"
 #include "vdownloader.h"
 #include "vfile.h"
+#include "vmdedit.h"
 
 VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
     : VEditOperations(p_editor, p_file)
@@ -54,7 +55,9 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
     QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
     insertTextAtCurPos(md);
 
-    m_editor->insertImage(fileName);
+    VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
+    Q_ASSERT(mdEditor);
+    mdEditor->insertImage(fileName);
 }
 
 void VMdEditOperations::insertImageFromPath(const QString &title,
@@ -77,7 +80,9 @@ void VMdEditOperations::insertImageFromPath(const QString &title,
     QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
     insertTextAtCurPos(md);
 
-    m_editor->insertImage(fileName);
+    VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
+    Q_ASSERT(mdEditor);
+    mdEditor->insertImage(fileName);
 }
 
 bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)