support file change check

This commit is contained in:
Le Tan 2017-11-15 19:45:33 +08:00
parent 0a97b2480d
commit 141b404240
20 changed files with 267 additions and 67 deletions

View File

@ -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>

After

Width:  |  Height:  |  Size: 1018 B

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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();
}
}

View File

@ -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.

View File

@ -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()

View File

@ -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();
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;

View File

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

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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>