Browse Source

support file change check

Le Tan 8 years ago
parent
commit
141b404240

+ 14 - 0
src/resources/icons/reading_modified.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<path style="fill:#C9302C" d="M112,64v16v320h16V80h304v337.143c0,8.205-6.652,14.857-14.857,14.857H94.857C86.652,432,80,425.348,80,417.143V128h16v-16
+		H64v305.143C64,434.157,77.843,448,94.857,448h322.285C434.157,448,448,434.157,448,417.143V64H112z"/>
+	<rect style="fill:#C9302C" x="160" y="112" width="128" height="16"/>
+	<rect style="fill:#C9302C" x="160" y="192" width="240" height="16"/>
+	<rect style="fill:#C9302C" x="160" y="272" width="192" height="16"/>
+	<rect style="fill:#C9302C" x="160" y="352" width="240" height="16"/>
+</g>
+</svg>

+ 3 - 0
src/resources/vnote.ini

@@ -152,6 +152,9 @@ startup_page_type=0
 ; C:\users\vnote\vnote.md -> C:\\users\\vnote\\vnote.md
 startup_pages=
 
+; Timer interval to check file modification or save file to tmp file in milliseconds
+file_timer_interval=2000
+
 [web]
 ; Location and configuration for Mathjax
 mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML

+ 6 - 0
src/vconfigmanager.cpp

@@ -262,6 +262,12 @@ void VConfigManager::initialize()
                                            "startup_pages").toStringList();
 
     initFromSessionSettings();
+
+    m_fileTimerInterval = getConfigFromSettings("global",
+                                                "file_timer_interval").toInt();
+    if (m_fileTimerInterval < 100) {
+        m_fileTimerInterval = 100;
+    }
 }
 
 void VConfigManager::initSettings()

+ 11 - 0
src/vconfigmanager.h

@@ -372,6 +372,9 @@ public:
     // Read all available mdhl files in c_styleConfigFolder.
     QVector<QString> getEditorStyles() const;
 
+    // Return the timer interval for checking file.
+    int getFileTimerInterval() const;
+
 private:
     // Look up a config from user and default settings.
     QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@@ -703,6 +706,9 @@ private:
     // File paths to open on startup.
     QStringList m_startupPages;
 
+    // Timer interval to check file in milliseconds.
+    int m_fileTimerInterval;
+
     // The name of the config file in each directory, obsolete.
     // Use c_dirConfigFile instead.
     static const QString c_obsoleteDirConfigFile;
@@ -1779,4 +1785,9 @@ inline void VConfigManager::setStartupPages(const QStringList &p_pages)
     setConfigToSettings("global", "startup_pages", m_startupPages);
 }
 
+inline int VConfigManager::getFileTimerInterval() const
+{
+    return m_fileTimerInterval;
+}
+
 #endif // VCONFIGMANAGER_H

+ 0 - 11
src/vedit.cpp

@@ -48,9 +48,6 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
     connect(m_highlightTimer, &QTimer::timeout,
             this, &VEdit::doHighlightExtraSelections);
 
-    connect(document(), &QTextDocument::modificationChanged,
-            (VFile *)m_file, &VFile::setModified);
-
     m_extraSelections.resize((int)SelectionId::MaxSelection);
 
     updateFontAndPalette();
@@ -80,10 +77,6 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
 
 VEdit::~VEdit()
 {
-    if (m_file) {
-        disconnect(document(), &QTextDocument::modificationChanged,
-                   (VFile *)m_file, &VFile::setModified);
-    }
 }
 
 void VEdit::updateConfig()
@@ -146,16 +139,12 @@ bool VEdit::scrollToBlock(int p_blockNumber)
 
 bool VEdit::isModified() const
 {
-    Q_ASSERT(m_file ? (m_file->isModified() == document()->isModified()) : true);
     return document()->isModified();
 }
 
 void VEdit::setModified(bool p_modified)
 {
     document()->setModified(p_modified);
-    if (m_file) {
-        m_file->setModified(p_modified);
-    }
 }
 
 void VEdit::insertImage()

+ 21 - 0
src/veditarea.cpp

@@ -53,6 +53,14 @@ VEditArea::VEditArea(QWidget *parent)
                     win->focusNextTab(false);
                 }
             });
+
+    QTimer *timer = new QTimer(this);
+    timer->setSingleShot(false);
+    timer->setInterval(g_config->getFileTimerInterval());
+    connect(timer, &QTimer::timeout,
+            this, &VEditArea::handleFileTimerTimeout);
+
+    timer->start();
 }
 
 void VEditArea::setupUI()
@@ -1016,3 +1024,16 @@ void VEditArea::recordClosedFile(const VFileSessionInfo &p_file)
     m_lastClosedFiles.push(p_file);
     qDebug() << "pushed closed file" << p_file.m_file;
 }
+
+void VEditArea::handleFileTimerTimeout()
+{
+    checkFileChangeOutside();
+}
+
+void VEditArea::checkFileChangeOutside()
+{
+    int nrWin = splitter->count();
+    for (int i = 0; i < nrWin; ++i) {
+        getWindow(i)->checkFileChangeOutside();
+    }
+}

+ 6 - 0
src/veditarea.h

@@ -149,6 +149,9 @@ private slots:
     // Handle the vimStatusUpdated signal of VEditWindow.
     void handleWindowVimStatusUpdated(const VVim *p_vim);
 
+    // Handle the timeout signal of file timer.
+    void handleFileTimerTimeout();
+
 private:
     void setupUI();
     QVector<QPair<int, int> > findTabsByFile(const VFile *p_file);
@@ -167,6 +170,9 @@ private:
     // Init targets for Captain mode.
     void registerCaptainTargets();
 
+    // Check whether opened files have been changed outside.
+    void checkFileChangeOutside();
+
     // Captain mode functions.
 
     // Activate tab @p_idx.

+ 0 - 12
src/veditor.cpp

@@ -27,10 +27,6 @@ VEditor::VEditor(VFile *p_file, QWidget *p_editor)
 
 VEditor::~VEditor()
 {
-    if (m_file && m_document) {
-        QObject::disconnect(m_document, &QTextDocument::modificationChanged,
-                            (VFile *)m_file, &VFile::setModified);
-    }
 }
 
 void VEditor::init()
@@ -65,9 +61,6 @@ void VEditor::init()
 
     m_extraSelections.resize((int)SelectionId::MaxSelection);
 
-    QObject::connect(m_document, &QTextDocument::modificationChanged,
-                     (VFile *)m_file, &VFile::setModified);
-
     updateFontAndPalette();
 
     m_config.init(QFontMetrics(m_editor->font()), false);
@@ -344,17 +337,12 @@ bool VEditor::wordInSearchedSelection(const QString &p_text)
 
 bool VEditor::isModified() const
 {
-    Q_ASSERT(m_file ? (m_file->isModified() == m_document->isModified())
-                    : true);
     return m_document->isModified();
 }
 
 void VEditor::setModified(bool p_modified)
 {
     m_document->setModified(p_modified);
-    if (m_file) {
-        m_file->setModified(p_modified);
-    }
 }
 
 void VEditor::insertImage()

+ 50 - 2
src/vedittab.cpp

@@ -2,13 +2,20 @@
 #include <QApplication>
 #include <QWheelEvent>
 
+#include "utils/vutils.h"
+#include "vconfigmanager.h"
+
+extern VConfigManager *g_config;
+
 VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
     : QWidget(p_parent),
       m_file(p_file),
       m_isEditMode(false),
       m_outline(p_file),
       m_currentHeader(p_file, -1),
-      m_editArea(p_editArea)
+      m_editArea(p_editArea),
+      m_checkFileChange(true),
+      m_fileDiverged(false)
 {
     connect(qApp, &QApplication::focusChanged,
             this, &VEditTab::handleFocusChanged);
@@ -35,7 +42,7 @@ bool VEditTab::isEditMode() const
 
 bool VEditTab::isModified() const
 {
-    return m_file->isModified();
+    return false;
 }
 
 VFile *VEditTab::getFile() const
@@ -133,3 +140,44 @@ void VEditTab::applySnippet(const VSnippet *p_snippet)
 void VEditTab::applySnippet()
 {
 }
+
+void VEditTab::checkFileChangeOutside()
+{
+    if (!m_checkFileChange) {
+        return;
+    }
+
+    if (m_file->isChangedOutside()) {
+        int ret = VUtils::showMessage(QMessageBox::Information,
+                                      tr("Information"),
+                                      tr("Note <span style=\"%1\">%2</span> has been modified by another program.")
+                                        .arg(g_config->c_dataTextStyle).arg(m_file->fetchPath()),
+                                      tr("Do you want to reload it?"),
+                                      QMessageBox::Yes | QMessageBox::No,
+                                      QMessageBox::Yes,
+                                      this);
+        switch (ret) {
+        case QMessageBox::Yes:
+            reloadFromDisk();
+            break;
+
+        case QMessageBox::No:
+            m_checkFileChange = false;
+            m_fileDiverged = true;
+            updateStatus();
+            break;
+
+        default:
+            Q_ASSERT(false);
+            break;
+        }
+    }
+}
+
+void VEditTab::reloadFromDisk()
+{
+    m_file->reload();
+    m_fileDiverged = false;
+    m_checkFileChange = true;
+    reload();
+}

+ 16 - 1
src/vedittab.h

@@ -34,7 +34,7 @@ public:
 
     bool isEditMode() const;
 
-    bool isModified() const;
+    virtual bool isModified() const;
 
     void focusTab();
 
@@ -98,6 +98,15 @@ public:
     // Prompt for user to apply a snippet.
     virtual void applySnippet();
 
+    // Check whether this file has been changed outside.
+    void checkFileChangeOutside();
+
+    // Reload the editor from file.
+    virtual void reload() = 0;
+
+    // Reload file from disk and reload the editor.
+    void reloadFromDisk();
+
 public slots:
     // Enter edit mode
     virtual void editFile() = 0;
@@ -131,6 +140,12 @@ protected:
     // Tab info to restore from once ready.
     VEditTabInfo m_infoToRestore;
 
+    // Whether check the file change outside.
+    bool m_checkFileChange;
+
+    // File has diverged from disk.
+    bool m_fileDiverged;
+
 signals:
     void getFocused();
 

+ 46 - 6
src/veditwindow.cpp

@@ -158,6 +158,17 @@ void VEditWindow::initTabActions()
                 QDesktopServices::openUrl(url);
             });
 
+    m_reloadAct = new QAction(tr("Reload From Disk"), this);
+    m_reloadAct->setToolTip(tr("Reload the content of this note from disk"));
+    connect(m_reloadAct, &QAction::triggered,
+            this, [this](){
+                int tab = this->m_closeTabAct->data().toInt();
+                Q_ASSERT(tab != -1);
+
+                VEditTab *editor = getTab(tab);
+                editor->reloadFromDisk();
+            });
+
     m_recycleBinAct = new QAction(QIcon(":/resources/icons/recycle_bin.svg"),
                                   tr("&Recycle Bin"), this);
     m_recycleBinAct->setToolTip(tr("Open the recycle bin of this note"));
@@ -508,13 +519,16 @@ void VEditWindow::updateTabInfo(int p_index)
     const VFile *file = editor->getFile();
     bool editMode = editor->isEditMode();
 
-    setTabText(p_index, generateTabText(p_index, file));
+    setTabText(p_index, generateTabText(p_index, editor));
     setTabToolTip(p_index, generateTooltip(file));
 
-    QString iconUrl(":/resources/icons/reading.svg");
+    QString iconUrl;
     if (editMode) {
-        iconUrl = file->isModified() ? ":/resources/icons/editing_modified.svg"
-                                     : ":/resources/icons/editing.svg";
+        iconUrl = editor->isModified() ? ":/resources/icons/editing_modified.svg"
+                                       : ":/resources/icons/editing.svg";
+    } else {
+        iconUrl = editor->isModified() ? ":/resources/icons/reading_modified.svg"
+                                       : ":/resources/icons/reading.svg";
     }
 
     setTabIcon(p_index, QIcon(iconUrl));
@@ -524,8 +538,7 @@ void VEditWindow::updateAllTabsSequence()
 {
     for (int i = 0; i < count(); ++i) {
         VEditTab *editor = getTab(i);
-        const VFile *file = editor->getFile();
-        setTabText(i, generateTabText(i, file));
+        setTabText(i, generateTabText(i, editor));
     }
 }
 
@@ -627,6 +640,9 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos)
         m_openLocationAct->setData(tab);
         menu.addAction(m_openLocationAct);
 
+        m_reloadAct->setData(tab);
+        menu.addAction(m_reloadAct);
+
         m_noteInfoAct->setData(tab);
         menu.addAction(m_noteInfoAct);
     } else if (file->getType() == FileType::Orphan
@@ -637,6 +653,9 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos)
         m_openLocationAct->setData(tab);
         menu.addAction(m_openLocationAct);
 
+        m_reloadAct->setData(tab);
+        menu.addAction(m_reloadAct);
+
         m_noteInfoAct->setData(tab);
         menu.addAction(m_noteInfoAct);
     }
@@ -1054,3 +1073,24 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
 
     QTabWidget::dropEvent(p_event);
 }
+
+QVector<VEditTab *> VEditWindow::getAllTabs() const
+{
+    int nrTab = count();
+
+    QVector<VEditTab *> tabs;
+    tabs.reserve(nrTab);
+    for (int i = 0; i < nrTab; ++i) {
+        tabs.push_back(getTab(i));
+    }
+
+    return tabs;
+}
+
+void VEditWindow::checkFileChangeOutside()
+{
+    int nrTab = count();
+    for (int i = 0; i < nrTab; ++i) {
+        getTab(i)->checkFileChangeOutside();
+    }
+}

+ 15 - 6
src/veditwindow.h

@@ -53,6 +53,8 @@ public:
 
     QVector<VEditTabInfo> getAllTabsInfo() const;
 
+    QVector<VEditTab *> getAllTabs() const;
+
     // Insert a tab with @p_widget. @p_widget is a fully initialized VEditTab.
     bool addEditTab(QWidget *p_widget);
     // Set whether it is the current window.
@@ -71,6 +73,9 @@ public:
     // If @p_index is -1, it is current tab.
     void updateTabStatus(int p_index = -1);
 
+    // Check whether opened files have been changed outside.
+    void checkFileChangeOutside();
+
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
 
@@ -143,7 +148,7 @@ private:
 
     QString generateTooltip(const VFile *p_file) const;
 
-    QString generateTabText(int p_index, const VFile *p_file) const;
+    QString generateTabText(int p_index, const VEditTab *p_tab) const;
 
     bool canRemoveSplit();
 
@@ -196,6 +201,9 @@ private:
     // Open the location (the folder containing this file) of this note.
     QAction *m_openLocationAct;
 
+    // Reload the note from disk.
+    QAction *m_reloadAct;
+
     // Open the recycle bin folder of this note.
     QAction *m_recycleBinAct;
 };
@@ -215,16 +223,17 @@ inline QString VEditWindow::generateTooltip(const VFile *p_file) const
     }
 }
 
-inline QString VEditWindow::generateTabText(int p_index, const VFile *p_file) const
+inline QString VEditWindow::generateTabText(int p_index, const VEditTab *p_tab) const
 {
-    if (!p_file) {
+    const VFile *file = p_tab->getFile();
+    if (!file) {
         return "";
     }
 
     return QString("%1.%2%3%4").arg(QString::number(p_index + c_tabSequenceBase, 10))
-                               .arg(p_file->getName())
-                               .arg(p_file->isModifiable() ? "" : "#")
-                               .arg(p_file->isModified() ? "*" : "");
+                               .arg(file->getName())
+                               .arg(file->isModifiable() ? "" : "#")
+                               .arg(p_tab->isModified() ? "*" : "");
 }
 
 #endif // VEDITWINDOW_H

+ 14 - 7
src/vfile.cpp

@@ -1,7 +1,6 @@
 #include "vfile.h"
 
 #include <QDir>
-#include <QDebug>
 #include <QTextEdit>
 #include <QFileInfo>
 #include "utils/vutils.h"
@@ -15,7 +14,6 @@ VFile::VFile(QObject *p_parent,
     : QObject(p_parent),
       m_name(p_name),
       m_opened(false),
-      m_modified(false),
       m_docType(VUtils::docTypeFromName(p_name)),
       m_type(p_type),
       m_modifiable(p_modifiable),
@@ -40,7 +38,7 @@ bool VFile::open()
     QString filePath = fetchPath();
     Q_ASSERT(QFileInfo::exists(filePath));
     m_content = VUtils::readFileFromDisk(filePath);
-    m_modified = false;
+    m_lastModified = QFileInfo(filePath).lastModified();
     m_opened = true;
     return true;
 }
@@ -52,7 +50,6 @@ void VFile::close()
     }
 
     m_content.clear();
-    m_modified = false;
     m_opened = false;
 }
 
@@ -62,8 +59,8 @@ bool VFile::save()
     Q_ASSERT(m_modifiable);
     bool ret = VUtils::writeFileToDisk(fetchPath(), m_content);
     if (ret) {
+        m_lastModified = QFileInfo(fetchPath()).lastModified();
         m_modifiedTimeUtc = QDateTime::currentDateTimeUtc();
-        m_modified = false;
     }
 
     return ret;
@@ -98,8 +95,18 @@ bool VFile::isInternalImageFolder(const QString &p_path) const
            || VUtils::equalPath(p_path, fetchImageFolderPath());
 }
 
-void VFile::setModified(bool p_modified)
+bool VFile::isChangedOutside() const
 {
-    m_modified = p_modified;
+    QDateTime lm = QFileInfo(fetchPath()).lastModified();
+    return  lm != m_lastModified;
 }
 
+void VFile::reload()
+{
+    Q_ASSERT(m_opened);
+
+    QString filePath = fetchPath();
+    Q_ASSERT(QFileInfo::exists(filePath));
+    m_content = VUtils::readFileFromDisk(filePath);
+    m_lastModified = QFileInfo(filePath).lastModified();
+}

+ 11 - 15
src/vfile.h

@@ -22,22 +22,23 @@ public:
     virtual ~VFile();
 
     // Open the file to load content into m_content.
-    // Init m_opened, m_modified, and m_content.
+    // Init m_opened, and m_content.
     virtual bool open();
 
     // Close the file.
-    // Clear m_modified, m_content, m_opened.
+    // Clear m_content, m_opened.
     virtual void close();
 
     // Save m_content to the file.
     virtual bool save();
 
+    // Reload content from disk.
+    virtual void reload();
+
     const QString &getName() const;
 
     DocType getDocType() const;
 
-    bool isModified() const;
-
     bool isModifiable() const;
 
     bool isOpened() const;
@@ -75,8 +76,8 @@ public:
 
     QDateTime getModifiedTimeUtc() const;
 
-public slots:
-    void setModified(bool p_modified);
+    // Whether this file was changed outside VNote.
+    bool isChangedOutside() const;
 
 protected:
     // Name of this file.
@@ -85,9 +86,6 @@ protected:
     // Whether this file has been opened (content loaded).
     bool m_opened;
 
-    // m_content is different from that in the disk.
-    bool m_modified;
-
     // DocType of this file: Html, Markdown.
     DocType m_docType;
 
@@ -105,6 +103,10 @@ protected:
 
     // UTC time of last modification to this file in VNote.
     QDateTime m_modifiedTimeUtc;
+
+    // Last modified date and local time when the file is last modified
+    // corresponding to m_content.
+    QDateTime m_lastModified;
 };
 
 inline const QString &VFile::getName() const
@@ -117,11 +119,6 @@ inline DocType VFile::getDocType() const
     return m_docType;
 }
 
-inline bool VFile::isModified() const
-{
-    return m_modified;
-}
-
 inline bool VFile::isModifiable() const
 {
     return m_modifiable;
@@ -146,7 +143,6 @@ inline const QString &VFile::getContent() const
 inline void VFile::setContent(const QString &p_content)
 {
     m_content = p_content;
-    m_modified = true;
 }
 
 inline QDateTime VFile::getCreatedTimeUtc() const

+ 17 - 3
src/vhtmltab.cpp

@@ -101,7 +101,7 @@ void VHtmlTab::readFile()
         return;
     }
 
-    if (m_editor && m_editor->isModified()) {
+    if (m_editor && isModified()) {
         // Prompt to save the changes.
         bool modifiable = m_file->isModifiable();
         int ret = VUtils::showMessage(QMessageBox::Information,
@@ -123,7 +123,7 @@ void VHtmlTab::readFile()
                 return;
             }
 
-            // Fall through
+            V_FALLTHROUGH;
 
         case QMessageBox::Discard:
             m_editor->reloadFile();
@@ -148,7 +148,7 @@ void VHtmlTab::readFile()
 
 bool VHtmlTab::saveFile()
 {
-    if (!m_isEditMode || !m_editor->isModified()) {
+    if (!m_isEditMode || !isModified()) {
         return true;
     }
 
@@ -184,6 +184,9 @@ bool VHtmlTab::saveFile()
                             tr("Fail to write to disk when saving a note. Please try it again."),
                             QMessageBox::Ok, QMessageBox::Ok, this);
         m_editor->setModified(true);
+    } else {
+        m_fileDiverged = false;
+        m_checkFileChange = true;
     }
 
     updateStatus();
@@ -191,6 +194,11 @@ bool VHtmlTab::saveFile()
     return ret;
 }
 
+bool VHtmlTab::isModified() const
+{
+    return m_editor->isModified() || m_fileDiverged;
+}
+
 void VHtmlTab::saveAndRead()
 {
     saveFile();
@@ -265,3 +273,9 @@ bool VHtmlTab::restoreFromTabInfo(const VEditTabInfo &p_info)
 
     return true;
 }
+
+void VHtmlTab::reload()
+{
+    m_editor->reloadFile();
+    updateStatus();
+}

+ 4 - 0
src/vhtmltab.h

@@ -26,6 +26,8 @@ public:
     // Save file.
     bool saveFile() Q_DECL_OVERRIDE;
 
+    bool isModified() const Q_DECL_OVERRIDE;
+
     void insertImage() Q_DECL_OVERRIDE;
 
     // Search @p_text in current note.
@@ -45,6 +47,8 @@ public:
 
     void requestUpdateVimStatus() Q_DECL_OVERRIDE;
 
+    void reload() Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;

+ 1 - 1
src/vmainwindow.cpp

@@ -1864,7 +1864,7 @@ void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
             title.append('#');
         }
 
-        if (m_curFile->isModified()) {
+        if (m_curTab->isModified()) {
             title.append('*');
         }
     }

+ 27 - 3
src/vmdtab.cpp

@@ -241,7 +241,7 @@ void VMdTab::readFile()
         return;
     }
 
-    if (m_editor && m_editor->isModified()) {
+    if (m_editor && isModified()) {
         // Prompt to save the changes.
         bool modifiable = m_file->isModifiable();
         int ret = VUtils::showMessage(QMessageBox::Information, tr("Information"),
@@ -262,7 +262,7 @@ void VMdTab::readFile()
                 return;
             }
 
-            // Fall through
+            V_FALLTHROUGH;
 
         case QMessageBox::Discard:
             m_editor->reloadFile();
@@ -293,7 +293,7 @@ bool VMdTab::saveFile()
 
     Q_ASSERT(m_editor);
 
-    if (!m_editor->isModified()) {
+    if (!isModified()) {
         return true;
     }
 
@@ -329,6 +329,9 @@ bool VMdTab::saveFile()
                                 tr("Fail to write to disk when saving a note. Please try it again."),
                                 QMessageBox::Ok, QMessageBox::Ok, this);
             m_editor->setModified(true);
+        } else {
+            m_fileDiverged = false;
+            m_checkFileChange = true;
         }
     }
 
@@ -337,6 +340,11 @@ bool VMdTab::saveFile()
     return ret;
 }
 
+bool VMdTab::isModified() const
+{
+    return (m_editor ? m_editor->isModified() : false) || m_fileDiverged;
+}
+
 void VMdTab::saveAndRead()
 {
     saveFile();
@@ -818,3 +826,19 @@ VInsertSelector *VMdTab::prepareSnippetSelector(QWidget *p_parent)
     VInsertSelector *sel = new VInsertSelector(7, items, p_parent);
     return sel;
 }
+
+void VMdTab::reload()
+{
+    if (m_isEditMode) {
+        m_editor->reloadFile();
+        m_editor->endEdit();
+        m_editor->beginEdit();
+        updateStatus();
+    } else {
+        if (m_editor) {
+            m_editor->reloadFile();
+        }
+
+        showFileReadMode();
+    }
+}

+ 4 - 0
src/vmdtab.h

@@ -32,6 +32,8 @@ public:
     // Save file.
     bool saveFile() Q_DECL_OVERRIDE;
 
+    bool isModified() const Q_DECL_OVERRIDE;
+
     // Scroll to @p_header.
     void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
 
@@ -80,6 +82,8 @@ public:
 
     void applySnippet() Q_DECL_OVERRIDE;
 
+    void reload() Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;

+ 1 - 0
src/vnote.qrc

@@ -141,5 +141,6 @@
         <file>resources/icons/delete_snippet.svg</file>
         <file>resources/icons/snippet_info.svg</file>
         <file>resources/icons/apply_snippet.svg</file>
+        <file>resources/icons/reading_modified.svg</file>
     </qresource>
 </RCC>