1
0
Эх сурвалжийг харах

refactor: add VFile and VDirectory

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 жил өмнө
parent
commit
e7c42ba5be

+ 5 - 5
src/dialog/vdirinfodialog.cpp

@@ -20,7 +20,7 @@ void VDirInfoDialog::setupUI()
     if (!info.isEmpty()) {
         infoLabel = new QLabel(info);
     }
-    nameLabel = new QLabel(tr("&Name"));
+    nameLabel = new QLabel(tr("&Name:"));
     nameEdit = new QLineEdit(defaultName);
     nameEdit->selectAll();
     nameLabel->setBuddy(nameEdit);
@@ -29,10 +29,7 @@ void VDirInfoDialog::setupUI()
     okBtn->setDefault(true);
     cancelBtn = new QPushButton(tr("&Cancel"));
 
-    QVBoxLayout *topLayout = new QVBoxLayout();
-    if (infoLabel) {
-        topLayout->addWidget(infoLabel);
-    }
+    QHBoxLayout *topLayout = new QHBoxLayout();
     topLayout->addWidget(nameLabel);
     topLayout->addWidget(nameEdit);
 
@@ -42,6 +39,9 @@ void VDirInfoDialog::setupUI()
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
     mainLayout->addLayout(topLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);

+ 4 - 4
src/dialog/vfileinfodialog.cpp

@@ -29,10 +29,7 @@ void VFileInfoDialog::setupUI()
     okBtn->setDefault(true);
     cancelBtn = new QPushButton(tr("&Cancel"));
 
-    QVBoxLayout *topLayout = new QVBoxLayout();
-    if (infoLabel) {
-        topLayout->addWidget(infoLabel);
-    }
+    QHBoxLayout *topLayout = new QHBoxLayout();
     topLayout->addWidget(nameLabel);
     topLayout->addWidget(nameEdit);
 
@@ -42,6 +39,9 @@ void VFileInfoDialog::setupUI()
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
     mainLayout->addLayout(topLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);

+ 11 - 3
src/dialog/vnewdirdialog.cpp

@@ -1,9 +1,9 @@
 #include <QtWidgets>
 #include "vnewdirdialog.h"
 
-VNewDirDialog::VNewDirDialog(const QString &title, const QString &name, const QString &defaultName,
+VNewDirDialog::VNewDirDialog(const QString &title, const QString &info, const QString &name, const QString &defaultName,
                              QWidget *parent)
-    : QDialog(parent), title(title), name(name), defaultName(defaultName)
+    : QDialog(parent), title(title), info(info), name(name), defaultName(defaultName)
 {
     setupUI();
 
@@ -14,6 +14,11 @@ VNewDirDialog::VNewDirDialog(const QString &title, const QString &name, const QS
 
 void VNewDirDialog::setupUI()
 {
+    QLabel *infoLabel = NULL;
+    if (!info.isEmpty()) {
+        infoLabel = new QLabel(info);
+    }
+
     nameLabel = new QLabel(name);
     nameEdit = new QLineEdit(defaultName);
     nameEdit->selectAll();
@@ -23,7 +28,7 @@ void VNewDirDialog::setupUI()
     okBtn->setDefault(true);
     cancelBtn = new QPushButton(tr("&Cancel"));
 
-    QVBoxLayout *topLayout = new QVBoxLayout();
+    QHBoxLayout *topLayout = new QHBoxLayout();
     topLayout->addWidget(nameLabel);
     topLayout->addWidget(nameEdit);
 
@@ -33,6 +38,9 @@ void VNewDirDialog::setupUI()
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
     mainLayout->addLayout(topLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);

+ 2 - 1
src/dialog/vnewdirdialog.h

@@ -12,7 +12,7 @@ class VNewDirDialog : public QDialog
 {
     Q_OBJECT
 public:
-    VNewDirDialog(const QString &title, const QString &name,
+    VNewDirDialog(const QString &title, const QString &info, const QString &name,
                   const QString &defaultName, QWidget *parent = 0);
     QString getNameInput() const;
 
@@ -28,6 +28,7 @@ private:
     QPushButton *cancelBtn;
 
     QString title;
+    QString info;
     QString name;
     QString defaultName;
 };

+ 12 - 4
src/dialog/vnewfiledialog.cpp

@@ -1,9 +1,9 @@
 #include <QtWidgets>
 #include "vnewfiledialog.h"
 
-VNewFileDialog::VNewFileDialog(const QString &title, const QString &name, const QString &defaultName,
-                             QWidget *parent)
-    : QDialog(parent), title(title), name(name), defaultName(defaultName)
+VNewFileDialog::VNewFileDialog(const QString &title, const QString &info, const QString &name,
+                               const QString &defaultName, QWidget *parent)
+    : QDialog(parent), title(title), info(info), name(name), defaultName(defaultName)
 {
     setupUI();
 
@@ -14,6 +14,11 @@ VNewFileDialog::VNewFileDialog(const QString &title, const QString &name, const
 
 void VNewFileDialog::setupUI()
 {
+    QLabel *infoLabel = NULL;
+    if (!info.isEmpty()) {
+        infoLabel = new QLabel(info);
+    }
+
     nameLabel = new QLabel(name);
     nameEdit = new QLineEdit(defaultName);
     nameEdit->selectAll();
@@ -23,7 +28,7 @@ void VNewFileDialog::setupUI()
     okBtn->setDefault(true);
     cancelBtn = new QPushButton(tr("&Cancel"));
 
-    QVBoxLayout *topLayout = new QVBoxLayout();
+    QHBoxLayout *topLayout = new QHBoxLayout();
     topLayout->addWidget(nameLabel);
     topLayout->addWidget(nameEdit);
 
@@ -33,6 +38,9 @@ void VNewFileDialog::setupUI()
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
     mainLayout->addLayout(topLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);

+ 3 - 2
src/dialog/vnewfiledialog.h

@@ -12,8 +12,8 @@ class VNewFileDialog : public QDialog
 {
     Q_OBJECT
 public:
-    VNewFileDialog(const QString &title, const QString &name, const QString &defaultName,
-                   QWidget *parent = 0);
+    VNewFileDialog(const QString &title, const QString &info, const QString &name,
+                   const QString &defaultName, QWidget *parent = 0);
     QString getNameInput() const;
 
 private slots:
@@ -28,6 +28,7 @@ private:
     QPushButton *cancelBtn;
 
     QString title;
+    QString info;
     QString name;
     QString defaultName;
 };

+ 6 - 6
src/src.pro

@@ -23,7 +23,6 @@ SOURCES += main.cpp\
     vfilelist.cpp \
     dialog/vnewfiledialog.cpp \
     vedit.cpp \
-    vnotefile.cpp \
     vdocument.cpp \
     utils/vutils.cpp \
     vpreviewpage.cpp \
@@ -43,8 +42,9 @@ SOURCES += main.cpp\
     vedittab.cpp \
     voutline.cpp \
     vtoc.cpp \
-    vfilelocation.cpp \
-    vsingleinstanceguard.cpp
+    vsingleinstanceguard.cpp \
+    vdirectory.cpp \
+    vfile.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -56,7 +56,6 @@ HEADERS  += vmainwindow.h \
     dialog/vnewfiledialog.h \
     vedit.h \
     vconstants.h \
-    vnotefile.h \
     vdocument.h \
     utils/vutils.h \
     vpreviewpage.h \
@@ -76,8 +75,9 @@ HEADERS  += vmainwindow.h \
     vedittab.h \
     voutline.h \
     vtoc.h \
-    vfilelocation.h \
-    vsingleinstanceguard.h
+    vsingleinstanceguard.h \
+    vdirectory.h \
+    vfile.h
 
 RESOURCES += \
     vnote.qrc

+ 35 - 1
src/utils/vutils.cpp

@@ -9,7 +9,6 @@
 #include <QJsonObject>
 #include <QJsonDocument>
 #include <QDateTime>
-
 VUtils::VUtils()
 {
 }
@@ -202,3 +201,38 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
     }
     return true;
 }
+
+int VUtils::showMessage(QMessageBox::Icon p_icon, const QString &p_title, const QString &p_text, const QString &p_infoText,
+                        QMessageBox::StandardButtons p_buttons, QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent)
+{
+    QMessageBox msgBox(p_icon, p_title, p_text, p_buttons, p_parent);
+    msgBox.setInformativeText(p_infoText);
+    msgBox.setDefaultButton(p_defaultBtn);
+    return msgBox.exec();
+}
+
+QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName)
+{
+    QString suffix;
+    QString base = p_fileName;
+    int dotIdx = p_fileName.lastIndexOf('.');
+    if (dotIdx != -1) {
+        // .md
+        suffix = p_fileName.right(p_fileName.size() - dotIdx);
+        base = p_fileName.left(dotIdx);
+    }
+    QDir dir(p_dirPath);
+    QString name = p_fileName;
+    QString filePath = dir.filePath(name);
+    int index = 0;
+    while (QFile(filePath).exists()) {
+        QString seq;
+        if (index > 0) {
+            seq = QString::number(index);
+        }
+        index++;
+        name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
+        filePath = dir.filePath(name);
+    }
+    return name;
+}

+ 5 - 0
src/utils/vutils.h

@@ -5,6 +5,7 @@
 #include <QColor>
 #include <QVector>
 #include <QPair>
+#include <QMessageBox>
 #include "vconfigmanager.h"
 #include "vconstants.h"
 
@@ -19,6 +20,7 @@ public:
     static QRgb QRgbFromString(const QString &str);
     static QString generateImageFileName(const QString &path, const QString &title,
                                          const QString &format = "png");
+    static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
     static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
     static bool isMarkdown(const QString &fileName);
     static inline QString directoryNameFromPath(const QString& path);
@@ -28,6 +30,9 @@ public:
     static void makeDirectory(const QString &path);
     static ClipboardOpType opTypeInClipboard();
     static bool copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut);
+    static int showMessage(QMessageBox::Icon p_icon, const QString &p_title, const QString &p_text,
+                           const QString &p_infoText, QMessageBox::StandardButtons p_buttons,
+                           QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent);
 };
 
 inline QString VUtils::directoryNameFromPath(const QString &path)

+ 1 - 0
src/vconstants.h

@@ -3,5 +3,6 @@
 
 enum class DocType { Html, Markdown };
 enum class ClipboardOpType { Invalid, CopyFile, CopyDir };
+enum class OpenFileMode {Read = 0, Edit};
 
 #endif

+ 468 - 0
src/vdirectory.cpp

@@ -0,0 +1,468 @@
+#include "vdirectory.h"
+#include <QDir>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QDebug>
+#include "vconfigmanager.h"
+#include "vfile.h"
+#include "utils/vutils.h"
+
+VDirectory::VDirectory(VNotebook *p_notebook,
+                       const QString &p_name, QObject *p_parent)
+    : QObject(p_parent), m_notebook(p_notebook), m_name(p_name), m_opened(false)
+{
+}
+
+bool VDirectory::open()
+{
+    if (m_opened) {
+        return true;
+    }
+    Q_ASSERT(m_subDirs.isEmpty() && m_files.isEmpty());
+    QString path = retrivePath();
+    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
+    if (configJson.isEmpty()) {
+        qWarning() << "invalid directory configuration in path" << path;
+        return false;
+    }
+
+    // [sub_directories] section
+    QJsonArray dirJson = configJson["sub_directories"].toArray();
+    for (int i = 0; i < dirJson.size(); ++i) {
+        QJsonObject dirItem = dirJson[i].toObject();
+        VDirectory *dir = new VDirectory(m_notebook, dirItem["name"].toString(), this);
+        m_subDirs.append(dir);
+    }
+
+    // [files] section
+    QJsonArray fileJson = configJson["files"].toArray();
+    for (int i = 0; i < fileJson.size(); ++i) {
+        QJsonObject fileItem = fileJson[i].toObject();
+        VFile *file = new VFile(fileItem["name"].toString(), this);
+        m_files.append(file);
+    }
+    m_opened = true;
+    qDebug() << "dir" << m_name << "open" << m_subDirs.size() << "sub directories" << m_files.size() << "files";
+    return true;
+}
+
+void VDirectory::close()
+{
+    if (!m_opened) {
+        return;
+    }
+
+    for (int i = 0; i < m_subDirs.size(); ++i) {
+        VDirectory *dir = m_subDirs[i];
+        dir->close();
+        delete dir;
+    }
+    m_subDirs.clear();
+
+    for (int i = 0; i < m_files.size(); ++i) {
+        VFile *file = m_files[i];
+        file->close();
+        delete file;
+    }
+    m_files.clear();
+
+    m_opened = false;
+}
+
+QString VDirectory::retrivePath(const VDirectory *p_dir) const
+{
+    if (!p_dir) {
+        return "";
+    }
+    VDirectory *parentDir = (VDirectory *)p_dir->parent();
+    if (parentDir) {
+        // Not the root directory
+        return QDir(retrivePath(parentDir)).filePath(p_dir->getName());
+    } else {
+        return m_notebook->getPath();
+    }
+}
+
+QString VDirectory::retriveRelativePath(const VDirectory *p_dir) const
+{
+    if (!p_dir) {
+        return "";
+    }
+    VDirectory *parentDir = (VDirectory *)p_dir->parent();
+    if (parentDir) {
+        // Not the root directory
+        return QDir(retriveRelativePath(parentDir)).filePath(p_dir->getName());
+    } else {
+        return "";
+    }
+}
+
+QJsonObject VDirectory::createDirectoryJson() const
+{
+    QJsonObject dirJson;
+    dirJson["version"] = "1";
+    dirJson["sub_directories"] = QJsonArray();
+    dirJson["files"] = QJsonArray();
+    return dirJson;
+}
+
+VDirectory *VDirectory::createSubDirectory(const QString &p_name)
+{
+    Q_ASSERT(!p_name.isEmpty());
+    // First open current directory
+    if (!open()) {
+        return NULL;
+    }
+    QString path = retrivePath();
+    QDir dir(path);
+    if (!dir.mkdir(p_name)) {
+        qWarning() << "failed to create directory" << p_name << "under" << path;
+        return NULL;
+    }
+
+    QJsonObject subJson = createDirectoryJson();
+    if (!VConfigManager::writeDirectoryConfig(QDir::cleanPath(QDir(path).filePath(p_name)), subJson)) {
+        dir.rmdir(p_name);
+        return NULL;
+    }
+
+    // Update parent's config file to include this new directory
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    Q_ASSERT(!dirJson.isEmpty());
+    QJsonObject itemJson;
+    itemJson["name"] = p_name;
+    QJsonArray subDirArray = dirJson["sub_directories"].toArray();
+    subDirArray.append(itemJson);
+    dirJson["sub_directories"] = subDirArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        VConfigManager::deleteDirectoryConfig(QDir(path).filePath(p_name));
+        dir.rmdir(p_name);
+        return NULL;
+    }
+
+    VDirectory *ret = new VDirectory(m_notebook, p_name, this);
+    m_subDirs.append(ret);
+    return ret;
+}
+
+VDirectory *VDirectory::findSubDirectory(const QString &p_name)
+{
+    if (!m_opened && !open()) {
+        return NULL;
+    }
+    for (int i = 0; i < m_subDirs.size(); ++i) {
+        if (p_name == m_subDirs[i]->getName()) {
+            return m_subDirs[i];
+        }
+    }
+    return NULL;
+}
+
+VFile *VDirectory::findFile(const QString &p_name)
+{
+    if (!m_opened && !open()) {
+        return NULL;
+    }
+    for (int i = 0; i < m_files.size(); ++i) {
+        if (p_name == m_files[i]->getName()) {
+            return m_files[i];
+        }
+    }
+    return NULL;
+}
+
+VFile *VDirectory::createFile(const QString &p_name)
+{
+    Q_ASSERT(!p_name.isEmpty());
+    if (!open()) {
+        return NULL;
+    }
+    QString path = retrivePath();
+    QString filePath = QDir(path).filePath(p_name);
+    QFile file(filePath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        qWarning() << "failed to create file" << p_name;
+        return NULL;
+    }
+    file.close();
+    qDebug() << "file created" << p_name;
+
+    if (!createFileInConfig(p_name)) {
+        file.remove();
+        return NULL;
+    }
+
+    VFile *ret = new VFile(p_name, this);
+    m_files.append(ret);
+    return ret;
+}
+
+bool VDirectory::createFileInConfig(const QString &p_name, int p_index)
+{
+    QString path = retrivePath();
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    Q_ASSERT(!dirJson.isEmpty());
+    QJsonObject itemJson;
+    itemJson["name"] = p_name;
+    QJsonArray fileArray = dirJson["files"].toArray();
+    if (p_index == -1) {
+        fileArray.append(itemJson);
+    } else {
+        fileArray.insert(p_index, itemJson);
+    }
+    dirJson["files"] = fileArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        return false;
+    }
+    return true;
+}
+
+VFile *VDirectory::addFile(VFile *p_file, int p_index)
+{
+    if (!open()) {
+        return NULL;
+    }
+    if (!createFileInConfig(p_file->getName(), p_index)) {
+        return NULL;
+    }
+    if (p_index == -1) {
+        m_files.append(p_file);
+    } else {
+        m_files.insert(p_index, p_file);
+    }
+    p_file->setParent(this);
+    return p_file;
+}
+
+VFile *VDirectory::addFile(const QString &p_name, int p_index)
+{
+    if (!open()) {
+        return NULL;
+    }
+    if (!createFileInConfig(p_name, p_index)) {
+        return NULL;
+    }
+    VFile *file = new VFile(p_name, this);
+    if (!file) {
+        return NULL;
+    }
+    if (p_index == -1) {
+        m_files.append(file);
+    } else {
+        m_files.insert(p_index, file);
+    }
+    return file;
+}
+
+void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
+{
+    if (!open()) {
+        return;
+    }
+    QString path = retrivePath();
+
+    int index;
+    for (index = 0; index < m_subDirs.size(); ++index) {
+        if (m_subDirs[index] == p_subDir) {
+            break;
+        }
+    }
+    if (index == m_subDirs.size()) {
+        return;
+    }
+    m_subDirs.remove(index);
+    QString name = p_subDir->getName();
+    // Update config to exclude this directory
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    QJsonArray subDirArray = dirJson["sub_directories"].toArray();
+    bool deleted = false;
+    for (int i = 0; i < subDirArray.size(); ++i) {
+        QJsonObject ele = subDirArray[i].toObject();
+        if (ele["name"].toString() == name) {
+            subDirArray.removeAt(i);
+            deleted = true;
+            break;
+        }
+    }
+    Q_ASSERT(deleted);
+    dirJson["sub_directories"] = subDirArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        qWarning() << "failed to update configuration in" << path;
+    }
+
+    // Delete the entire directory
+    p_subDir->close();
+    delete p_subDir;
+    QString dirName = QDir(path).filePath(name);
+    QDir dir(dirName);
+    if (!dir.removeRecursively()) {
+        qWarning() << "failed to delete" << dirName << "recursively";
+    } else {
+        qDebug() << "deleted" << dirName;
+    }
+}
+
+// After calling this, p_file->parent() remain the same.
+int VDirectory::removeFile(VFile *p_file)
+{
+    Q_ASSERT(m_opened);
+    Q_ASSERT(p_file);
+
+    QString path = retrivePath();
+
+    int index;
+    for (index = 0; index < m_files.size(); ++index) {
+        if (m_files[index] == p_file) {
+            break;
+        }
+    }
+    Q_ASSERT(index != m_files.size());
+    m_files.remove(index);
+    QString name = p_file->getName();
+
+    // Update config to exclude this file
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    QJsonArray subFileArray = dirJson["files"].toArray();
+    bool deleted = false;
+    for (int i = 0; i < subFileArray.size(); ++i) {
+        QJsonObject ele = subFileArray[i].toObject();
+        if (ele["name"].toString() == name) {
+            subFileArray.removeAt(i);
+            deleted = true;
+            index = i;
+            break;
+        }
+    }
+    Q_ASSERT(deleted);
+    dirJson["files"] = subFileArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        qWarning() << "failed to update configuration in" << path;
+    }
+    return index;
+}
+
+void VDirectory::deleteFile(VFile *p_file)
+{
+    removeFile(p_file);
+
+    // Delete the file
+    Q_ASSERT(!p_file->isOpened());
+    p_file->deleteDiskFile();
+    delete p_file;
+}
+
+bool VDirectory::rename(const QString &p_name)
+{
+    if (m_name == p_name) {
+        return true;
+    }
+    VDirectory *parentDir = getParentDirectory();
+    Q_ASSERT(parentDir);
+    QString parentPath = parentDir->retrivePath();
+    QDir dir(parentPath);
+    QString name = m_name;
+    if (!dir.rename(m_name, p_name)) {
+        qWarning() << "failed to rename directory" << m_name << "to" << p_name;
+        return false;
+    }
+    m_name = p_name;
+
+    // Update parent's config file
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(parentPath);
+    QJsonArray subDirArray = dirJson["sub_directories"].toArray();
+    int index = 0;
+    for (index = 0; index < subDirArray.size(); ++index) {
+        QJsonObject ele = subDirArray[index].toObject();
+        if (ele["name"].toString() == name) {
+            ele["name"] = p_name;
+            subDirArray[index] = ele;
+            break;
+        }
+    }
+    Q_ASSERT(index != subDirArray.size());
+    dirJson["sub_directories"] = subDirArray;
+    if (!VConfigManager::writeDirectoryConfig(parentPath, dirJson)) {
+        return false;
+    }
+    return true;
+}
+
+VFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
+                            VFile *p_srcFile, bool p_cut)
+{
+    QString srcPath = QDir::cleanPath(p_srcFile->retrivePath());
+    QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
+    if (srcPath == destPath) {
+        return p_srcFile;
+    }
+    VDirectory *srcDir = p_srcFile->getDirectory();
+    DocType docType = p_srcFile->getDocType();
+    DocType newDocType = VUtils::isMarkdown(destPath) ? DocType::Markdown : DocType::Html;
+
+    QVector<QString> images;
+    if (docType == DocType::Markdown) {
+        images = VUtils::imagesFromMarkdownFile(srcPath);
+    }
+
+    // Copy the file
+    if (!VUtils::copyFile(srcPath, destPath, p_cut)) {
+        return NULL;
+    }
+
+    // Handle VDirectory and VFile
+    int index = -1;
+    VFile *destFile = NULL;
+    if (p_cut) {
+        // Remove the file from config
+        index = srcDir->removeFile(p_srcFile);
+        p_srcFile->setName(p_destName);
+        if (srcDir != p_destDir) {
+            index = -1;
+        }
+        // Add the file to new dir's config
+        destFile = p_destDir->addFile(p_srcFile, index);
+    } else {
+        destFile = p_destDir->addFile(p_destName, -1);
+    }
+    if (!destFile) {
+        return NULL;
+    }
+
+    if (docType != newDocType) {
+        destFile->convert(docType, newDocType);
+    }
+
+    // We need to copy images when it is still markdown
+    if (!images.isEmpty()) {
+        if (newDocType == DocType::Markdown) {
+            QString dirPath = destFile->retriveImagePath();
+            VUtils::makeDirectory(dirPath);
+            int nrPasted = 0;
+            for (int i = 0; i < images.size(); ++i) {
+                if (!QFile(images[i]).exists()) {
+                    continue;
+                }
+
+                QString destImagePath = QDir(dirPath).filePath(VUtils::fileNameFromPath(images[i]));
+                // Copy or Cut the images accordingly.
+                if (VUtils::copyFile(images[i], destImagePath, p_cut)) {
+                    nrPasted++;
+                } else {
+                    VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                        QString("Failed to copy image %1.").arg(images[i]),
+                                        tr("Please check if there already exists a file with the same name and manually copy it"),
+                                        QMessageBox::Ok, QMessageBox::Ok, NULL);
+                }
+            }
+            qDebug() << "pasted" << nrPasted << "images sucessfully";
+        } else {
+            // Delete the images
+            for (int i = 0; i < images.size(); ++i) {
+                QFile file(images[i]);
+                file.remove();
+            }
+        }
+    }
+
+    return destFile;
+}

+ 109 - 0
src/vdirectory.h

@@ -0,0 +1,109 @@
+#ifndef VDIRECTORY_H
+#define VDIRECTORY_H
+
+#include <QObject>
+#include <QString>
+#include <QVector>
+#include <QPointer>
+#include <QJsonObject>
+#include "vnotebook.h"
+
+class VFile;
+
+class VDirectory : public QObject
+{
+    Q_OBJECT
+public:
+    VDirectory(VNotebook *p_notebook,
+               const QString &p_name, QObject *p_parent = 0);
+    bool open();
+    void close();
+    VDirectory *createSubDirectory(const QString &p_name);
+    VDirectory *findSubDirectory(const QString &p_name);
+    VFile *findFile(const QString &p_name);
+    VFile *createFile(const QString &p_name);
+    void deleteSubDirectory(VDirectory *p_subDir);
+    // Remove the file in the config and m_files without deleting it in the disk.
+    int removeFile(VFile *p_file);
+    // Add the file in the config and m_files. If @p_index is -1, add it at the end.
+    // Return the VFile if succeed.
+    VFile *addFile(VFile *p_file, int p_index);
+    VFile *addFile(const QString &p_name, int p_index);
+    void deleteFile(VFile *p_file);
+    bool rename(const QString &p_name);
+
+    inline const QVector<VDirectory *> &getSubDirs() const;
+    inline const QString &getName() const;
+    inline bool isOpened() const;
+    inline VDirectory *getParentDirectory();
+    inline const QVector<VFile *> &getFiles() const;
+    inline QString retrivePath() const;
+    inline QString retriveRelativePath() const;
+    inline QString retriveNotebook() const;
+
+    // Copy @p_srcFile to @p_destDir, setting new name to @p_destName.
+    // @p_cut: copy or cut. Returns the dest VFile.
+    static VFile *copyFile(VDirectory *p_destDir, const QString &p_destName,
+                           VFile *p_srcFile, bool p_cut);
+signals:
+
+public slots:
+
+private:
+    // Get the path of @p_dir recursively
+    QString retrivePath(const VDirectory *p_dir) const;
+    // Get teh relative path of @p_dir recursively related to the notebook path
+    QString retriveRelativePath(const VDirectory *p_dir) const;
+    QJsonObject createDirectoryJson() const;
+    bool createFileInConfig(const QString &p_name, int p_index = -1);
+
+    QPointer<VNotebook> m_notebook;
+    QString m_name;
+    // Owner of the sub-directories
+    QVector<VDirectory *> m_subDirs;
+    // Owner of the files
+    QVector<VFile *> m_files;
+    bool m_opened;
+};
+
+inline const QVector<VDirectory *> &VDirectory::getSubDirs() const
+{
+    return m_subDirs;
+}
+
+inline const QString &VDirectory::getName() const
+{
+    return m_name;
+}
+
+inline bool VDirectory::isOpened() const
+{
+    return m_opened;
+}
+
+inline VDirectory *VDirectory::getParentDirectory()
+{
+    return (VDirectory *)this->parent();
+}
+
+inline const QVector<VFile *> &VDirectory::getFiles() const
+{
+    return m_files;
+}
+
+inline QString VDirectory::retriveNotebook() const
+{
+    return m_notebook->getName();
+}
+
+inline QString VDirectory::retrivePath() const
+{
+    return retrivePath(this);
+}
+
+inline QString VDirectory::retriveRelativePath() const
+{
+    return retriveRelativePath(this);
+}
+
+#endif // VDIRECTORY_H

+ 170 - 395
src/vdirectorytree.cpp

@@ -4,6 +4,8 @@
 #include "vconfigmanager.h"
 #include "dialog/vdirinfodialog.h"
 #include "vnote.h"
+#include "vdirectory.h"
+#include "utils/vutils.h"
 
 VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
     : QTreeWidget(parent), vnote(vnote)
@@ -14,7 +16,7 @@ VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
     initActions();
 
     connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
-            this, SLOT(updateItemSubtree(QTreeWidgetItem*)));
+            this, SLOT(updateChildren(QTreeWidgetItem*)));
     connect(this, SIGNAL(customContextMenuRequested(QPoint)),
             this, SLOT(contextMenuRequested(QPoint)));
     connect(this, &VDirectoryTree::currentItemChanged,
@@ -29,11 +31,6 @@ void VDirectoryTree::initActions()
     connect(newRootDirAct, &QAction::triggered,
             this, &VDirectoryTree::newRootDirectory);
 
-    newSiblingDirAct = new QAction(tr("New &sibling directory"), this);
-    newSiblingDirAct->setStatusTip(tr("Create a new sibling directory at current level"));
-    connect(newSiblingDirAct, &QAction::triggered,
-            this, &VDirectoryTree::newSiblingDirectory);
-
     newSubDirAct = new QAction(tr("&New sub-directory"), this);
     newSubDirAct->setStatusTip(tr("Create a new sub-directory"));
     connect(newSubDirAct, &QAction::triggered,
@@ -52,208 +49,159 @@ void VDirectoryTree::initActions()
             this, &VDirectoryTree::editDirectoryInfo);
 }
 
-void VDirectoryTree::setNotebook(const QString& notebookName)
+void VDirectoryTree::setNotebook(VNotebook *p_notebook)
 {
-    if (notebook == notebookName) {
+    if (m_notebook == p_notebook) {
         return;
     }
 
-    notebook = notebookName;
-    treePath = "";
-    if (notebook.isEmpty()) {
+    if (m_notebook) {
+        // Disconnect
+        disconnect((VNotebook *)m_notebook, &VNotebook::contentChanged,
+                   this, &VDirectoryTree::updateDirectoryTree);
+    }
+    m_notebook = p_notebook;
+    if (m_notebook) {
+        connect((VNotebook *)m_notebook, &VNotebook::contentChanged,
+                this, &VDirectoryTree::updateDirectoryTree);
+    } else {
         clear();
         return;
     }
-    const QVector<VNotebook *> &notebooks = vnote->getNotebooks();
-    for (int i = 0; i < notebooks.size(); ++i) {
-        if (notebooks[i]->getName() == notebook) {
-            treePath = notebooks[i]->getPath();
-            break;
-        }
+    if (!m_notebook->open()) {
+        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                            QString("Failed to open notebook %1").arg(m_notebook->getName()), "",
+                            QMessageBox::Ok, QMessageBox::Ok, this);
+        clear();
+        return;
     }
-    Q_ASSERT(!treePath.isEmpty());
-
     updateDirectoryTree();
-    if (topLevelItemCount() > 0) {
-       setCurrentItem(topLevelItem(0));
-    }
 }
 
-bool VDirectoryTree::validatePath(const QString &path)
+void VDirectoryTree::fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name,
+                                  VDirectory *p_directory, const QIcon &p_icon)
 {
-    return QDir(path).exists();
+    p_item.setText(0, p_name);
+    p_item.setData(0, Qt::UserRole, QVariant::fromValue(p_directory));
+    p_item.setIcon(0, p_icon);
 }
 
 void VDirectoryTree::updateDirectoryTree()
 {
-    updateDirectoryTreeTopLevel();
-
-    int nrTopLevelItems = topLevelItemCount();
-    for (int i = 0; i < nrTopLevelItems; ++i) {
-        QTreeWidgetItem *item = topLevelItem(i);
-        Q_ASSERT(item);
-        QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
-        Q_ASSERT(!itemJson.isEmpty());
-        updateDirectoryTreeOne(*item, itemJson["name"].toString(), 1);
-    }
-}
-
-void VDirectoryTree::fillDirectoryTreeItem(QTreeWidgetItem &item, QJsonObject itemJson)
-{
-    item.setText(0, itemJson["name"].toString());
-    item.setData(0, Qt::UserRole, itemJson);
-    item.setIcon(0, QIcon(":/resources/icons/dir_item.svg"));
-}
-
-QTreeWidgetItem* VDirectoryTree::insertDirectoryTreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding,
-                                                         const QJsonObject &newItem)
-{
-    QTreeWidgetItem *item;
-    if (parent) {
-        if (preceding) {
-            item = new QTreeWidgetItem(parent, preceding);
-        } else {
-            item = new QTreeWidgetItem(parent);
-        }
-    } else {
-        if (preceding) {
-            item = new QTreeWidgetItem(this, preceding);
-        } else {
-            item = new QTreeWidgetItem(this);
-        }
-    }
-
-    fillDirectoryTreeItem(*item, newItem);
-    return item;
-}
-
-void VDirectoryTree::removeDirectoryTreeItem(QTreeWidgetItem *item)
-{
-    delete item;
-}
-
-void VDirectoryTree::updateDirectoryTreeTopLevel()
-{
-    const QString &path = treePath;
-
     clear();
+    VDirectory *rootDir = m_notebook->getRootDir();
+    const QVector<VDirectory *> &subDirs = rootDir->getSubDirs();
+    for (int i = 0; i < subDirs.size(); ++i) {
+        VDirectory *dir = subDirs[i];
+        QTreeWidgetItem *item = new QTreeWidgetItem(this);
 
-    if (!validatePath(path)) {
-        qDebug() << "invalid notebook path:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook path."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook path \"%1\" either does not exist or is not valid.")
-                                  .arg(path));
-        msgBox.exec();
-        return;
-    }
-
-    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
-    if (configJson.isEmpty()) {
-        qDebug() << "invalid notebook configuration for path:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook configuration."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook path \"%1\" does not contain a valid configuration file.")
-                                  .arg(path));
-        msgBox.exec();
-        return;
-    }
+        fillTreeItem(*item, dir->getName(), dir,
+                     QIcon(":/resources/icons/dir_item.svg"));
 
-    // Handle sub_directories section
-    QJsonArray dirJson = configJson["sub_directories"].toArray();
-    QTreeWidgetItem *preItem = NULL;
-    for (int i = 0; i < dirJson.size(); ++i) {
-        QJsonObject dirItem = dirJson[i].toObject();
-        QTreeWidgetItem *treeItem = insertDirectoryTreeItem(NULL, preItem, dirItem);
-        preItem = treeItem;
+        updateDirectoryTreeOne(item, 1);
     }
-
-    qDebug() << "updated" << dirJson.size() << "top-level items";
+    setCurrentItem(topLevelItem(0));
 }
 
-void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem &parent, const QString &relativePath,
-                                            int depth)
+void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem *p_parent, int depth)
 {
-    Q_ASSERT(parent.childCount() == 0);
-    // Going deep enough
+    Q_ASSERT(p_parent->childCount() == 0);
     if (depth <= 0) {
         return;
     }
-
-    qDebug() << "update directory" << relativePath;
-
-    QString path(QDir::cleanPath(treePath + QDir::separator() + relativePath));
-    if (!validatePath(path)) {
-        qDebug() << "invalide notebook directory:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook directory \"%1\" either does not exist or is not a valid notebook directory.")
-                                  .arg(path));
-        msgBox.exec();
+    VDirectory *dir = getVDirectory(p_parent);
+    if (!dir->open()) {
+        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                            QString("Failed to open directory %1").arg(dir->getName()), "",
+                            QMessageBox::Ok, QMessageBox::Ok, this);
         return;
     }
+    const QVector<VDirectory *> &subDirs = dir->getSubDirs();
+    for (int i = 0; i < subDirs.size(); ++i) {
+        VDirectory *subDir = subDirs[i];
+        QTreeWidgetItem *item = new QTreeWidgetItem(p_parent);
 
-    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
-    if (configJson.isEmpty()) {
-        qDebug() << "invalid notebook configuration for directory:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook directory \"%1\" does not contain a valid configuration file.")
-                                  .arg(path));
-        msgBox.exec();
-        return;
-    }
-
-    // Handle sub_directories section
-    QJsonArray dirJson = configJson["sub_directories"].toArray();
-    QTreeWidgetItem *preItem = NULL;
-    for (int i = 0; i < dirJson.size(); ++i) {
-        QJsonObject dirItem = dirJson[i].toObject();
-        QTreeWidgetItem *treeItem = insertDirectoryTreeItem(&parent, preItem, dirItem);
-        preItem = treeItem;
+        fillTreeItem(*item, subDir->getName(), subDir,
+                     QIcon(":/resources/icons/dir_item.svg"));
 
-        // Update its sub-directory recursively
-        updateDirectoryTreeOne(*treeItem, QDir::cleanPath(QDir(relativePath).filePath(dirItem["name"].toString())), depth - 1);
+        updateDirectoryTreeOne(item, depth - 1);
     }
 }
 
-QString VDirectoryTree::calculateItemRelativePath(QTreeWidgetItem *item)
+// Update @p_item's children items
+void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item)
 {
-    if (!item) {
-        return ".";
+    Q_ASSERT(p_item);
+    int nrChild = p_item->childCount();
+    if (nrChild == 0) {
+        return;
+    }
+    for (int i = 0; i < nrChild; ++i) {
+        QTreeWidgetItem *childItem = p_item->child(i);
+        if (childItem->childCount() > 0) {
+            continue;
+        }
+        updateDirectoryTreeOne(childItem, 1);
     }
-    QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
-    Q_ASSERT(!itemJson.isEmpty());
-    QString name = itemJson["name"].toString();
-    Q_ASSERT(!name.isEmpty());
-    return QDir::cleanPath(calculateItemRelativePath(item->parent()) +
-                           QDir::separator() + name);
 }
 
-void VDirectoryTree::updateItemSubtree(QTreeWidgetItem *item)
+QVector<QTreeWidgetItem *> VDirectoryTree::updateItemChildrenAdded(QTreeWidgetItem *p_item)
 {
-    QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
-    Q_ASSERT(!itemJson.isEmpty());
-    QString relativePath = calculateItemRelativePath(item);
-    int nrChild = item->childCount();
-    if (nrChild == 0) {
-        updateDirectoryTreeOne(*item, relativePath, 2);
+    QVector<QTreeWidgetItem *> ret;
+    QPointer<VDirectory> parentDir;
+    if (p_item) {
+        parentDir = getVDirectory(p_item);
     } else {
-        for (int i = 0; i < nrChild; ++i) {
-            QTreeWidgetItem *childItem = item->child(i);
-            if (childItem->childCount() > 0) {
-                continue;
+        parentDir = m_notebook->getRootDir();
+    }
+    const QVector<VDirectory *> &subDirs = parentDir->getSubDirs();
+    for (int i = 0; i < subDirs.size(); ++i) {
+        int nrChild;
+        if (p_item) {
+            nrChild = p_item->childCount();
+        } else {
+            nrChild = this->topLevelItemCount();
+        }
+        VDirectory *dir = subDirs[i];
+        QTreeWidgetItem *item;
+        if (i >= nrChild) {
+            if (p_item) {
+                item = new QTreeWidgetItem(p_item);
+            } else {
+                item = new QTreeWidgetItem(this);
+            }
+            fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg"));
+            updateDirectoryTreeOne(item, 1);
+            ret.append(item);
+        } else {
+            VDirectory *itemDir;
+            if (p_item) {
+                itemDir = getVDirectory(p_item->child(i));
+            } else {
+                itemDir = getVDirectory(topLevelItem(i));
+            }
+            if (itemDir != dir) {
+                item = new QTreeWidgetItem();
+                if (p_item) {
+                    p_item->insertChild(i, item);
+                } else {
+                    this->insertTopLevelItem(i, item);
+                }
+                fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg"));
+                updateDirectoryTreeOne(item, 1);
+                ret.append(item);
             }
-            QJsonObject childJson = childItem->data(0, Qt::UserRole).toJsonObject();
-            Q_ASSERT(!childJson.isEmpty());
-            updateDirectoryTreeOne(*childItem,
-                                   QDir::cleanPath(QDir(relativePath).filePath(childJson["name"].toString())), 1);
         }
     }
+    qDebug() << ret.size() << "items added";
+    return ret;
 }
 
 void VDirectoryTree::contextMenuRequested(QPoint pos)
 {
+    if (!m_notebook) {
+        return;
+    }
     QTreeWidgetItem *item = itemAt(pos);
     QMenu menu(this);
 
@@ -265,7 +213,6 @@ void VDirectoryTree::contextMenuRequested(QPoint pos)
         if (item->parent()) {
             // Low-level item
             menu.addAction(newSubDirAct);
-            menu.addAction(newSiblingDirAct);
         } else {
             // Top-level item
             menu.addAction(newRootDirAct);
@@ -277,60 +224,40 @@ void VDirectoryTree::contextMenuRequested(QPoint pos)
     menu.exec(mapToGlobal(pos));
 }
 
-void VDirectoryTree::newSiblingDirectory()
-{
-    QTreeWidgetItem *parentItem = currentItem()->parent();
-    Q_ASSERT(parentItem);
-    QJsonObject parentItemJson = parentItem->data(0, Qt::UserRole).toJsonObject();
-    QString parentItemName = parentItemJson["name"].toString();
-
-    QString text("&Directory name:");
-    QString defaultText("new_directory");
-    do {
-        VNewDirDialog dialog(QString("Create a new directory under %1").arg(parentItemName), text,
-                             defaultText, this);
-        if (dialog.exec() == QDialog::Accepted) {
-            QString name = dialog.getNameInput();
-            if (isConflictNameWithChildren(parentItem, name)) {
-                text = "Name already exists.\nPlease choose another name:";
-                defaultText = name;
-                continue;
-            }
-            QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(parentItem, name);
-            if (newItem) {
-                this->setCurrentItem(newItem);
-            }
-        }
-        break;
-    } while (true);
-}
-
 void VDirectoryTree::newSubDirectory()
 {
+    if (!m_notebook) {
+        return;
+    }
     QTreeWidgetItem *curItem = currentItem();
     if (!curItem) {
         return;
     }
-    QJsonObject curItemJson = curItem->data(0, Qt::UserRole).toJsonObject();
-    QString curItemName = curItemJson["name"].toString();
+    VDirectory *curDir = getVDirectory(curItem);
 
+    QString info = QString("Create sub-directory under %1.").arg(curDir->getName());
     QString text("&Directory name:");
     QString defaultText("new_directory");
 
     do {
-        VNewDirDialog dialog(QString("Create a new directory under %1").arg(curItemName), text,
-                             defaultText, this);
+        VNewDirDialog dialog(tr("Create directory"), info, text, defaultText, this);
         if (dialog.exec() == QDialog::Accepted) {
             QString name = dialog.getNameInput();
-            if (isConflictNameWithChildren(curItem, name)) {
-                text = "Name already exists.\nPlease choose another name:";
+            if (curDir->findSubDirectory(name)) {
+                info = QString("Name already exists under %1.\nPlease choose another name.").arg(curDir->getName());
                 defaultText = name;
                 continue;
             }
-            QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(curItem, name);
-            if (newItem) {
-                this->setCurrentItem(newItem);
+            VDirectory *subDir = curDir->createSubDirectory(name);
+            if (!subDir) {
+                VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                    QString("Failed to create directory %1.").arg(name), "",
+                                    QMessageBox::Ok, QMessageBox::Ok, this);
+                return;
             }
+            QVector<QTreeWidgetItem *> items = updateItemChildrenAdded(curItem);
+            Q_ASSERT(items.size() == 1);
+            setCurrentItem(items[0]);
         }
         break;
     } while (true);
@@ -338,23 +265,32 @@ void VDirectoryTree::newSubDirectory()
 
 void VDirectoryTree::newRootDirectory()
 {
+    if (!m_notebook) {
+        return;
+    }
+    QString info = QString("Create root directory in notebook %1.").arg(m_notebook->getName());
     QString text("&Directory name:");
     QString defaultText("new_directory");
-
+    VDirectory *rootDir = m_notebook->getRootDir();
     do {
-        VNewDirDialog dialog(tr("Create a new root directory"), text,
-                             defaultText, this);
+        VNewDirDialog dialog(tr("Create root directory"), info, text, defaultText, this);
         if (dialog.exec() == QDialog::Accepted) {
             QString name = dialog.getNameInput();
-            if (isConflictNameWithChildren(NULL, name)) {
-                text = "Name already exists.\nPlease choose another name:";
+            if (rootDir->findSubDirectory(name)) {
+                info = QString("Name already exists in notebook %1.\nPlease choose another name.").arg(m_notebook->getName());
                 defaultText = name;
                 continue;
             }
-            QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(NULL, name);
-            if (newItem) {
-                this->setCurrentItem(newItem);
+            VDirectory *subDir = rootDir->createSubDirectory(name);
+            if (!subDir) {
+                VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                    QString("Failed to create directory %1.").arg(name), "",
+                                    QMessageBox::Ok, QMessageBox::Ok, this);
+                return;
             }
+            QVector<QTreeWidgetItem *> items = updateItemChildrenAdded(NULL);
+            Q_ASSERT(items.size() == 1);
+            setCurrentItem(items[0]);
         }
         break;
     } while (true);
@@ -366,140 +302,26 @@ void VDirectoryTree::deleteDirectory()
     if (!curItem) {
         return;
     }
-    QJsonObject curItemJson = curItem->data(0, Qt::UserRole).toJsonObject();
-    QString curItemName = curItemJson["name"].toString();
-
-    QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Are you sure you want to delete directory \"%1\"?")
-                       .arg(curItemName), QMessageBox::Ok | QMessageBox::Cancel, this);
-    msgBox.setInformativeText(tr("This will delete any files under this directory."));
-    msgBox.setDefaultButton(QMessageBox::Cancel);
-    if (msgBox.exec() == QMessageBox::Ok) {
-        deleteDirectoryAndUpdateTree(curItem);
-    }
-}
-
-QTreeWidgetItem* VDirectoryTree::createDirectoryAndUpdateTree(QTreeWidgetItem *parent,
-                                                              const QString &name)
-{
-    QString relativePath = calculateItemRelativePath(parent);
-    QString path = QDir::cleanPath(QDir(treePath).filePath(relativePath));
-    QDir dir(path);
-    if (!dir.mkdir(name)) {
-        qWarning() << "error: fail to create directory" << name << "under" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create directory \"%1\" under \"%2\".")
-                           .arg(name).arg(path), QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Please check if there already exists a directory named \"%1\".").arg(name));
-        msgBox.exec();
-        return NULL;
-    }
-
-    QJsonObject configJson;
-    configJson["version"] = "1";
-    configJson["sub_directories"] = QJsonArray();
-    configJson["files"] = QJsonArray();
-
-    if (!VConfigManager::writeDirectoryConfig(QDir::cleanPath(QDir(path).filePath(name)), configJson)) {
-        return NULL;
-    }
-
-    // Update parent's config file to include this new directory
-    configJson = VConfigManager::readDirectoryConfig(path);
-    Q_ASSERT(!configJson.isEmpty());
-    QJsonObject itemJson;
-    itemJson["name"] = name;
-    QJsonArray subDirArray = configJson["sub_directories"].toArray();
-    subDirArray.append(itemJson);
-    configJson["sub_directories"] = subDirArray;
-    if (!VConfigManager::writeDirectoryConfig(path, configJson)) {
-        VConfigManager::deleteDirectoryConfig(QDir::cleanPath(QDir(path).filePath(name)));
-        dir.rmdir(name);
-        return NULL;
-    }
-
-    return insertDirectoryTreeItem(parent, NULL, itemJson);
-}
-
-void VDirectoryTree::deleteDirectoryAndUpdateTree(QTreeWidgetItem *item)
-{
-    if (!item) {
-        return;
-    }
-    QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
-    QString itemName = itemJson["name"].toString();
-    QString parentRelativePath = calculateItemRelativePath(item->parent());
-
-    // Update parent's config file to exclude this directory
-    QString path = QDir::cleanPath(QDir(treePath).filePath(parentRelativePath));
-    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
-    Q_ASSERT(!configJson.isEmpty());
-    QJsonArray subDirArray = configJson["sub_directories"].toArray();
-    bool deleted = false;
-    for (int i = 0; i < subDirArray.size(); ++i) {
-        QJsonObject ele = subDirArray[i].toObject();
-        if (ele["name"].toString() == itemName) {
-            subDirArray.removeAt(i);
-            deleted = true;
-            break;
-        }
-    }
-    if (!deleted) {
-        qWarning() << "error: fail to find" << itemName << "to delete in its parent's configuration file";
-        return;
-    }
-    configJson["sub_directories"] = subDirArray;
-    if (!VConfigManager::writeDirectoryConfig(path, configJson)) {
-        qWarning() << "error: fail to update parent's configuration file to delete" << itemName;
-        return;
+    VDirectory *curDir = getVDirectory(curItem);
+    int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                  QString("Are you sure to delete directory %1?").arg(curDir->getName()),
+                                  tr("This will delete any files under this directory."), QMessageBox::Ok | QMessageBox::Cancel,
+                                  QMessageBox::Ok, this);
+    if (ret == QMessageBox::Ok) {
+        VDirectory *parentDir = curDir->getParentDirectory();
+        Q_ASSERT(parentDir);
+        parentDir->deleteSubDirectory(curDir);
+        delete curItem;
     }
-
-    // Delete the entire directory
-    QString dirName = QDir::cleanPath(QDir(path).filePath(itemName));
-    QDir dir(dirName);
-    if (!dir.removeRecursively()) {
-        qWarning() << "error: fail to delete" << dirName << "recursively";
-    } else {
-        qDebug() << "delete" << dirName << "recursively";
-    }
-
-    // Update the tree
-    removeDirectoryTreeItem(item);
-}
-
-bool VDirectoryTree::isConflictNameWithChildren(const QTreeWidgetItem *parent, const QString &name)
-{
-    if (parent) {
-        int nrChild = parent->childCount();
-        for (int i = 0; i < nrChild; ++i) {
-            QJsonObject childItemJson = parent->child(i)->data(0, Qt::UserRole).toJsonObject();
-            Q_ASSERT(!childItemJson.isEmpty());
-            if (childItemJson["name"].toString() == name) {
-                return true;
-            }
-        }
-    } else {
-        int nrTopLevelItems = topLevelItemCount();
-        for (int i = 0; i < nrTopLevelItems; ++i) {
-            QJsonObject itemJson = topLevelItem(i)->data(0, Qt::UserRole).toJsonObject();
-            Q_ASSERT(!itemJson.isEmpty());
-            if (itemJson["name"].toString() == name) {
-                return true;
-            }
-        }
-    }
-    return false;
 }
 
 void VDirectoryTree::currentDirectoryItemChanged(QTreeWidgetItem *currentItem)
 {
     if (!currentItem) {
-        emit currentDirectoryChanged(QJsonObject());
+        emit currentDirectoryChanged(NULL);
         return;
     }
-    QJsonObject itemJson = currentItem->data(0, Qt::UserRole).toJsonObject();
-    Q_ASSERT(!itemJson.isEmpty());
-    itemJson["notebook"] = notebook;
-    itemJson["relative_path"] = calculateItemRelativePath(currentItem);
-    emit currentDirectoryChanged(itemJson);
+    emit currentDirectoryChanged(getVDirectory(currentItem));
 }
 
 void VDirectoryTree::editDirectoryInfo()
@@ -508,80 +330,33 @@ void VDirectoryTree::editDirectoryInfo()
     if (!curItem) {
         return;
     }
-    QJsonObject curItemJson = curItem->data(0, Qt::UserRole).toJsonObject();
-    QString curItemName = curItemJson["name"].toString();
 
+    VDirectory *curDir = getVDirectory(curItem);
+    VDirectory *parentDir = curDir->getParentDirectory();
+    QString curName = curDir->getName();
     QString info;
-    QString defaultName = curItemName;
+    QString defaultName = curName;
 
     do {
         VDirInfoDialog dialog(tr("Directory Information"), info, defaultName, this);
         if (dialog.exec() == QDialog::Accepted) {
             QString name = dialog.getNameInput();
-            if (name == curItemName) {
+            if (name == curName) {
                 return;
             }
-            if (isConflictNameWithChildren(curItem->parent(), name)) {
-                info = "Name already exists.\nPlease choose another name:";
+            if (parentDir->findSubDirectory(name)) {
+                info = "Name already exists.\nPlease choose another name.";
                 defaultName = name;
                 continue;
             }
-            renameDirectory(curItem, name);
+            if (!curDir->rename(name)) {
+                VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                    QString("Failed to rename directory %1.").arg(curName), "",
+                                    QMessageBox::Ok, QMessageBox::Ok, this);
+                return;
+            }
+            curItem->setText(0, name);
         }
         break;
     } while (true);
 }
-
-void VDirectoryTree::renameDirectory(QTreeWidgetItem *item, const QString &newName)
-{
-    if (!item) {
-        return;
-    }
-    QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
-    QString name = itemJson["name"].toString();
-
-    QTreeWidgetItem *parent = item->parent();
-    QString parentRelativePath = calculateItemRelativePath(parent);
-
-    QString path = QDir::cleanPath(QDir(treePath).filePath(parentRelativePath));
-    QDir dir(path);
-
-    if (!dir.rename(name, newName)) {
-        qWarning() << "error: fail to rename directory" << name << "under" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not rename directory \"%1\" under \"%2\".")
-                           .arg(name).arg(path), QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Please check if there already exists a directory named \"%1\".").arg(name));
-        msgBox.exec();
-        return;
-    }
-
-    // Update parent's config file
-    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
-    Q_ASSERT(!configJson.isEmpty());
-    QJsonArray subDirArray = configJson["sub_directories"].toArray();
-    int index = 0;
-    for (index = 0; index < subDirArray.size(); ++index) {
-        QJsonObject tmp = subDirArray[index].toObject();
-        if (tmp["name"].toString() == name) {
-            tmp["name"] = newName;
-            subDirArray[index] = tmp;
-            break;
-        }
-    }
-    Q_ASSERT(index != subDirArray.size());
-    configJson["sub_directories"] = subDirArray;
-    if (!VConfigManager::writeDirectoryConfig(path, configJson)) {
-        dir.rename(newName, name);
-        return;
-    }
-
-    // Update item
-    itemJson["name"] = newName;
-    item->setData(0, Qt::UserRole, itemJson);
-    item->setText(0, newName);
-
-    QString oldPath = QDir::cleanPath(QDir(parentRelativePath).filePath(name));
-    QString newPath = QDir::cleanPath(QDir(parentRelativePath).filePath(newName));
-    qDebug() << "directory renamed" << oldPath << "to" << newPath;
-    emit directoryRenamed(notebook, oldPath, newPath);
-}

+ 21 - 33
src/vdirectorytree.h

@@ -3,6 +3,9 @@
 
 #include <QTreeWidget>
 #include <QJsonObject>
+#include <QPointer>
+#include <QVector>
+#include "vdirectory.h"
 #include "vnotebook.h"
 
 class VNote;
@@ -14,56 +17,35 @@ public:
     explicit VDirectoryTree(VNote *vnote, QWidget *parent = 0);
 
 signals:
-    void currentDirectoryChanged(QJsonObject itemJson);
+    void currentDirectoryChanged(VDirectory *p_directory);
     void directoryRenamed(const QString &notebook, const QString &oldRelativePath,
                           const QString &newRelativePath);
 
 public slots:
-    void setNotebook(const QString &notebookName);
+    void setNotebook(VNotebook *p_notebook);
     void newRootDirectory();
     void deleteDirectory();
     void editDirectoryInfo();
+    void updateDirectoryTree();
 
 private slots:
-    // Read config file and pdate the subtree of @item in the directory tree.
-    // If @item has no child, we will call updateDirectoryTreeOne() to update it.
-    // Otherwise, we will loop all its direct-children and try to populate it if
-    // it has not been populated yet.
-    void updateItemSubtree(QTreeWidgetItem *item);
+    void updateChildren(QTreeWidgetItem *p_item);
     void contextMenuRequested(QPoint pos);
-    void newSiblingDirectory();
     void newSubDirectory();
     void currentDirectoryItemChanged(QTreeWidgetItem *currentItem);
 
 private:
-    QString calculateItemRelativePath(QTreeWidgetItem *item);
-    // Clean and pdate the TreeWidget according to treePath
-    void updateDirectoryTree();
-    // Update the top-level items of the directory tree. Will not clean the tree at first.
-    void updateDirectoryTreeTopLevel();
-
-    // @relativePath is the relative path of the direcotry we are updating
-    void updateDirectoryTreeOne(QTreeWidgetItem &parent, const QString &relativePath, int depth);
-    // Validate if a directory is valid
-    bool validatePath(const QString &path);
-
-    void fillDirectoryTreeItem(QTreeWidgetItem &item, QJsonObject itemJson);
+    void updateDirectoryTreeOne(QTreeWidgetItem *p_parent, int depth);
+    void fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name,
+                      VDirectory *p_directory, const QIcon &p_icon);
     void initActions();
-    QTreeWidgetItem* createDirectoryAndUpdateTree(QTreeWidgetItem *parent, const QString &name);
-    void deleteDirectoryAndUpdateTree(QTreeWidgetItem *item);
-    // If @name conflict with the children's names of @parent.
-    bool isConflictNameWithChildren(const QTreeWidgetItem *parent, const QString &name);
-
-    QTreeWidgetItem* insertDirectoryTreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding,
-                                             const QJsonObject &newItem);
-    void removeDirectoryTreeItem(QTreeWidgetItem *item);
-    void renameDirectory(QTreeWidgetItem *item, const QString &newName);
+    // New directories added to @p_item. Update @p_item's children items.
+    // If @p_item is NULL, then top level items added.
+    QVector<QTreeWidgetItem *> updateItemChildrenAdded(QTreeWidgetItem *p_item);
+    inline QPointer<VDirectory> getVDirectory(QTreeWidgetItem *p_item);
 
     VNote *vnote;
-
-    QString notebook;
-    // Used for cache
-    QString treePath;
+    QPointer<VNotebook> m_notebook;
 
     // Actions
     QAction *newRootDirAct;
@@ -73,4 +55,10 @@ private:
     QAction *dirInfoAct;
 };
 
+inline QPointer<VDirectory> VDirectoryTree::getVDirectory(QTreeWidgetItem *p_item)
+{
+    Q_ASSERT(p_item);
+    return p_item->data(0, Qt::UserRole).value<VDirectory *>();
+}
+
 #endif // VDIRECTORYTREE_H

+ 34 - 26
src/vedit.cpp

@@ -10,18 +10,20 @@
 
 extern VConfigManager vconfig;
 
-VEdit::VEdit(VNoteFile *noteFile, QWidget *parent)
-    : QTextEdit(parent), noteFile(noteFile), mdHighlighter(NULL)
+VEdit::VEdit(VFile *p_file, QWidget *p_parent)
+    : QTextEdit(p_parent), m_file(p_file), mdHighlighter(NULL)
 {
-    if (noteFile->docType == DocType::Markdown) {
+    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, noteFile);
+        editOps = new VMdEditOperations(this, m_file);
     } else {
-        setAutoFormatting(QTextEdit::AutoBulletList);
         editOps = NULL;
     }
 
@@ -33,6 +35,10 @@ VEdit::VEdit(VNoteFile *noteFile, QWidget *parent)
 
 VEdit::~VEdit()
 {
+    if (m_file) {
+        disconnect(document(), &QTextDocument::modificationChanged,
+                   (VFile *)m_file, &VFile::setModified);
+    }
     if (editOps) {
         delete editOps;
         editOps = NULL;
@@ -41,7 +47,7 @@ VEdit::~VEdit()
 
 void VEdit::updateFontAndPalette()
 {
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Markdown:
         setFont(vconfig.getMdEditFont());
         setPalette(vconfig.getMdEditPalette());
@@ -51,14 +57,14 @@ void VEdit::updateFontAndPalette()
         setPalette(vconfig.getBaseEditPalette());
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
         return;
     }
 }
 
 void VEdit::updateTabSettings()
 {
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Markdown:
         if (vconfig.getTabStopWidth() > 0) {
             QFontMetrics metrics(vconfig.getMdEditFont());
@@ -72,7 +78,7 @@ void VEdit::updateTabSettings()
         }
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
         return;
     }
 
@@ -84,27 +90,28 @@ void VEdit::updateTabSettings()
 
 void VEdit::beginEdit()
 {
-    setReadOnly(false);
     updateTabSettings();
     updateFontAndPalette();
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Html:
-        setHtml(noteFile->content);
+        setHtml(m_file->getContent());
         break;
     case DocType::Markdown:
         setFont(vconfig.getMdEditFont());
-        setPlainText(noteFile->content);
+        setPlainText(m_file->getContent());
         initInitImages();
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
     }
+    setReadOnly(false);
+    setModified(false);
 }
 
 void VEdit::endEdit()
 {
     setReadOnly(true);
-    if (noteFile->docType == DocType::Markdown) {
+    if (m_file->getDocType() == DocType::Markdown) {
         clearUnusedImages();
     }
 }
@@ -115,31 +122,32 @@ void VEdit::saveFile()
         return;
     }
 
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Html:
-        noteFile->content = toHtml();
+        m_file->setContent(toHtml());
         break;
     case DocType::Markdown:
-        noteFile->content = toPlainText();
+        m_file->setContent(toPlainText());
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
     }
     document()->setModified(false);
 }
 
 void VEdit::reloadFile()
 {
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Html:
-        setHtml(noteFile->content);
+        setHtml(m_file->getContent());
         break;
     case DocType::Markdown:
-        setPlainText(noteFile->content);
+        setPlainText(m_file->getContent());
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
     }
+    setModified(false);
 }
 
 void VEdit::keyPressEvent(QKeyEvent *event)
@@ -236,12 +244,12 @@ void VEdit::insertImage(const QString &name)
 
 void VEdit::initInitImages()
 {
-    m_initImages = VUtils::imagesFromMarkdownFile(QDir(noteFile->basePath).filePath(noteFile->fileName));
+    m_initImages = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
 }
 
 void VEdit::clearUnusedImages()
 {
-    QVector<QString> images = VUtils::imagesFromMarkdownFile(QDir(noteFile->basePath).filePath(noteFile->fileName));
+    QVector<QString> images = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
 
     if (!m_insertedImages.isEmpty()) {
         QVector<QString> imageNames(images.size());
@@ -249,7 +257,7 @@ void VEdit::clearUnusedImages()
             imageNames[i] = VUtils::fileNameFromPath(images[i]);
         }
 
-        QDir dir = QDir(QDir(noteFile->basePath).filePath("images"));
+        QDir dir = QDir(m_file->retriveImagePath());
         for (int i = 0; i < m_insertedImages.size(); ++i) {
             QString name = m_insertedImages[i];
             int j;

+ 8 - 4
src/vedit.h

@@ -3,9 +3,10 @@
 
 #include <QTextEdit>
 #include <QString>
+#include <QPointer>
 #include "vconstants.h"
-#include "vnotefile.h"
 #include "vtoc.h"
+#include "vfile.h"
 
 class HGMarkdownHighlighter;
 class VEditOperations;
@@ -14,12 +15,12 @@ class VEdit : public QTextEdit
 {
     Q_OBJECT
 public:
-    VEdit(VNoteFile *noteFile, QWidget *parent = 0);
+    VEdit(VFile *p_file, QWidget *p_parent = 0);
     ~VEdit();
     void beginEdit();
     void endEdit();
 
-    // Save buffer content to noteFile->content.
+    // Save buffer content to VFile.
     void saveFile();
 
     inline void setModified(bool modified);
@@ -48,9 +49,9 @@ private:
     void initInitImages();
     void clearUnusedImages();
 
+    QPointer<VFile> m_file;
     bool isExpandTab;
     QString tabSpaces;
-    VNoteFile *noteFile;
     HGMarkdownHighlighter *mdHighlighter;
     VEditOperations *editOps;
     QVector<VHeader> headers;
@@ -67,6 +68,9 @@ inline bool VEdit::isModified() const
 inline void VEdit::setModified(bool modified)
 {
     document()->setModified(modified);
+    if (m_file) {
+        m_file->setModified(modified);
+    }
 }
 
 #endif // VEDIT_H

+ 25 - 51
src/veditarea.cpp

@@ -4,6 +4,7 @@
 #include "vedittab.h"
 #include "vnote.h"
 #include "vconfigmanager.h"
+#include "vfile.h"
 
 VEditArea::VEditArea(VNote *vnote, QWidget *parent)
     : QWidget(parent), vnote(vnote), curWindowIndex(0)
@@ -79,25 +80,17 @@ void VEditArea::removeSplitWindow(VEditWindow *win)
 
 // A given file can be opened in multiple split windows. A given file could be
 // opened at most in one tab inside a window.
-void VEditArea::openFile(QJsonObject fileJson)
+void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode)
 {
-    if (fileJson.isEmpty()) {
+    if (!p_file) {
         return;
     }
-
-    QString notebook = fileJson["notebook"].toString();
-    QString relativePath = fileJson["relative_path"].toString();
-    int mode = OpenFileMode::Read;
-    if (fileJson.contains("mode")) {
-        mode = fileJson["mode"].toInt();
-    }
-
-    qDebug() << "open notebook" << notebook << "path" << relativePath << mode;
+    qDebug() << "VEditArea open" << p_file->getName() << (int)p_mode;
 
     // Find if it has been opened already
     int winIdx, tabIdx;
     bool setFocus = false;
-    auto tabs = findTabsByFile(notebook, relativePath);
+    auto tabs = findTabsByFile(p_file);
     if (!tabs.empty()) {
         // Current window first
         winIdx = tabs[0].first;
@@ -115,19 +108,19 @@ void VEditArea::openFile(QJsonObject fileJson)
 
     // Open it in current window
     winIdx = curWindowIndex;
-    tabIdx = openFileInWindow(winIdx, notebook, relativePath, mode);
+    tabIdx = openFileInWindow(winIdx, p_file, p_mode);
 
 out:
     setCurrentTab(winIdx, tabIdx, setFocus);
 }
 
-QVector<QPair<int, int> > VEditArea::findTabsByFile(const QString &notebook, const QString &relativePath)
+QVector<QPair<int, int> > VEditArea::findTabsByFile(const VFile *p_file)
 {
     QVector<QPair<int, int> > tabs;
     int nrWin = splitter->count();
     for (int winIdx = 0; winIdx < nrWin; ++winIdx) {
         VEditWindow *win = getWindow(winIdx);
-        int tabIdx = win->findTabByFile(notebook, relativePath);
+        int tabIdx = win->findTabByFile(p_file);
         if (tabIdx != -1) {
             QPair<int, int> match;
             match.first = winIdx;
@@ -138,12 +131,11 @@ QVector<QPair<int, int> > VEditArea::findTabsByFile(const QString &notebook, con
     return tabs;
 }
 
-int VEditArea::openFileInWindow(int windowIndex, const QString &notebook, const QString &relativePath,
-                                int mode)
+int VEditArea::openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode)
 {
     Q_ASSERT(windowIndex < splitter->count());
     VEditWindow *win = getWindow(windowIndex);
-    return win->openFile(notebook, relativePath, mode);
+    return win->openFile(p_file, p_mode);
 }
 
 void VEditArea::setCurrentTab(int windowIndex, int tabIndex, bool setFocus)
@@ -178,21 +170,18 @@ void VEditArea::updateWindowStatus()
     win->requestUpdateCurHeader();
 }
 
-bool VEditArea::closeFile(QJsonObject fileJson)
+bool VEditArea::closeFile(const VFile *p_file, bool p_forced)
 {
-    if (fileJson.isEmpty()) {
+    if (!p_file) {
         return true;
     }
-    QString notebook = fileJson["notebook"].toString();
-    QString relativePath = fileJson["relative_path"].toString();
-    bool isForced = fileJson["is_forced"].toBool();
-
     int nrWin = splitter->count();
     bool ret = false;
     for (int i = 0; i < nrWin; ++i) {
         VEditWindow *win = getWindow(i);
-        ret = ret || win->closeFile(notebook, relativePath, isForced);
+        ret = ret || win->closeFile(p_file, p_forced);
     }
+    updateWindowStatus();
     return ret;
 }
 
@@ -206,6 +195,7 @@ bool VEditArea::closeAllFiles(bool p_forced)
             return false;
         }
     }
+    updateWindowStatus();
     return true;
 }
 
@@ -233,29 +223,6 @@ void VEditArea::saveAndReadFile()
     win->saveAndReadFile();
 }
 
-void VEditArea::handleDirectoryRenamed(const QString &notebook, const QString &oldRelativePath,
-                                       const QString &newRelativePath)
-{
-    int nrWin = splitter->count();
-    for (int i = 0; i < nrWin; ++i) {
-        VEditWindow *win = getWindow(i);
-        win->handleDirectoryRenamed(notebook, oldRelativePath, newRelativePath);
-    }
-    updateWindowStatus();
-}
-
-void VEditArea::handleFileRenamed(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                                  const QString &p_destNotebook, const QString &p_destRelativePath)
-{
-    qDebug() << "fileRenamed" << p_srcNotebook << p_srcRelativePath << p_destNotebook << p_destRelativePath;
-    int nrWin = splitter->count();
-    for (int i = 0; i < nrWin; ++i) {
-        VEditWindow *win = getWindow(i);
-        win->handleFileRenamed(p_srcNotebook, p_srcRelativePath, p_destNotebook, p_destRelativePath);
-    }
-    updateWindowStatus();
-}
-
 void VEditArea::handleSplitWindowRequest(VEditWindow *curWindow)
 {
     if (!curWindow) {
@@ -287,7 +254,6 @@ void VEditArea::handleRemoveSplitRequest(VEditWindow *curWindow)
 
 void VEditArea::mousePressEvent(QMouseEvent *event)
 {
-    return;
     qDebug() << "VEditArea press event" << event;
     QPoint pos = event->pos();
     int nrWin = splitter->count();
@@ -335,7 +301,15 @@ void VEditArea::handleOutlineItemActivated(const VAnchor &anchor)
     getWindow(curWindowIndex)->scrollCurTab(anchor);
 }
 
-bool VEditArea::isFileOpened(const QString &notebook, const QString &relativePath)
+bool VEditArea::isFileOpened(const VFile *p_file)
+{
+    return !findTabsByFile(p_file).isEmpty();
+}
+
+void VEditArea::handleFileUpdated(const VFile *p_file)
 {
-    return !findTabsByFile(notebook, relativePath).isEmpty();
+    int nrWin = splitter->count();
+    for (int i = 0; i < nrWin; ++i) {
+        getWindow(i)->updateFileInfo(p_file);
+    }
 }

+ 8 - 12
src/veditarea.h

@@ -15,18 +15,18 @@
 #include "vtoc.h"
 
 class VNote;
+class VFile;
 
 class VEditArea : public QWidget
 {
     Q_OBJECT
 public:
     explicit VEditArea(VNote *vnote, QWidget *parent = 0);
-    bool isFileOpened(const QString &notebook, const QString &relativePath);
+    bool isFileOpened(const VFile *p_file);
     bool closeAllFiles(bool p_forced);
 
 signals:
-    void curTabStatusChanged(const QString &notebook, const QString &relativePath,
-                             bool editMode, bool modifiable, bool modified);
+    void curTabStatusChanged(const VFile *p_file, bool p_editMode);
     void outlineChanged(const VToc &toc);
     void curHeaderChanged(const VAnchor &anchor);
 
@@ -34,17 +34,14 @@ protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
 
 public slots:
-    void openFile(QJsonObject fileJson);
-    bool closeFile(QJsonObject fileJson);
+    void openFile(VFile *p_file, OpenFileMode p_mode);
+    bool closeFile(const VFile *p_file, bool p_forced);
     void editFile();
     void saveFile();
     void readFile();
     void saveAndReadFile();
     void handleOutlineItemActivated(const VAnchor &anchor);
-    void handleDirectoryRenamed(const QString &notebook,
-                                const QString &oldRelativePath, const QString &newRelativePath);
-    void handleFileRenamed(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                           const QString &p_destNotebook, const QString &p_destRelativePath);
+    void handleFileUpdated(const VFile *p_file);
 
 private slots:
     void handleSplitWindowRequest(VEditWindow *curWindow);
@@ -55,9 +52,8 @@ private slots:
 
 private:
     void setupUI();
-    QVector<QPair<int, int> > findTabsByFile(const QString &notebook, const QString &relativePath);
-    int openFileInWindow(int windowIndex, const QString &notebook, const QString &relativePath,
-                         int mode);
+    QVector<QPair<int, int> > findTabsByFile(const VFile *p_file);
+    int openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode);
     void setCurrentTab(int windowIndex, int tabIndex, bool setFocus);
     void setCurrentWindow(int windowIndex, bool setFocus);
     inline VEditWindow *getWindow(int windowIndex) const;

+ 4 - 4
src/veditoperations.cpp

@@ -3,15 +3,15 @@
 #include "vedit.h"
 #include "veditoperations.h"
 
-VEditOperations::VEditOperations(VEdit *editor, VNoteFile *noteFile)
-    : editor(editor), noteFile(noteFile)
+VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
+    : m_editor(p_editor), m_file(p_file)
 {
 }
 
 void VEditOperations::insertTextAtCurPos(const QString &text)
 {
-    QTextCursor cursor(editor->document());
-    cursor.setPosition(editor->textCursor().position());
+    QTextCursor cursor(m_editor->document());
+    cursor.setPosition(m_editor->textCursor().position());
     cursor.insertText(text);
 }
 

+ 6 - 4
src/veditoperations.h

@@ -1,22 +1,24 @@
 #ifndef VEDITOPERATIONS_H
 #define VEDITOPERATIONS_H
 
-class VNoteFile;
+#include <QPointer>
+#include "vfile.h"
+
 class VEdit;
 class QMimeData;
 
 class VEditOperations
 {
 public:
-    VEditOperations(VEdit *editor, VNoteFile *noteFile);
+    VEditOperations(VEdit *p_editor, VFile *p_file);
     virtual ~VEditOperations();
     virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
     virtual bool insertURLFromMimeData(const QMimeData *source) = 0;
 
 protected:
     void insertTextAtCurPos(const QString &text);
-    VEdit *editor;
-    VNoteFile *noteFile;
+    VEdit *m_editor;
+    QPointer<VFile> m_file;
 };
 
 #endif // VEDITOPERATIONS_H

+ 54 - 67
src/vedittab.cpp

@@ -18,37 +18,33 @@
 
 extern VConfigManager vconfig;
 
-VEditTab::VEditTab(const QString &path, bool modifiable, QWidget *parent)
-    : QStackedWidget(parent), mdConverterType(vconfig.getMdConverterType())
+VEditTab::VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent)
+    : QStackedWidget(p_parent), m_file(p_file), isEditMode(false),
+      mdConverterType(vconfig.getMdConverterType())
 {
-    DocType docType = VUtils::isMarkdown(path) ? DocType::Markdown : DocType::Html;
-    QString basePath = QFileInfo(path).path();
-    QString fileName = QFileInfo(path).fileName();
-    qDebug() << "VEditTab basePath" << basePath << "file" << fileName;
-    QString fileText = VUtils::readFileFromDisk(path);
-    noteFile = new VNoteFile(basePath, fileName, fileText,
-                             docType, modifiable);
-
-    isEditMode = false;
-
+    qDebug() << "ready to open" << p_file->getName();
+    Q_ASSERT(!m_file->isOpened());
+    m_file->open();
     setupUI();
-
-    showFileReadMode();
-
+    if (p_mode == OpenFileMode::Edit) {
+        showFileEditMode();
+    } else {
+        showFileReadMode();
+    }
     connect(qApp, &QApplication::focusChanged,
             this, &VEditTab::handleFocusChanged);
 }
 
 VEditTab::~VEditTab()
 {
-    if (noteFile) {
-        delete noteFile;
+    if (m_file) {
+        m_file->close();
     }
 }
 
 void VEditTab::setupUI()
 {
-    textEditor = new VEdit(noteFile);
+    textEditor = new VEdit(m_file);
     connect(textEditor, &VEdit::headersChanged,
             this, &VEditTab::updateTocFromHeaders);
     connect(textEditor, SIGNAL(curHeaderChanged(int)),
@@ -57,7 +53,7 @@ void VEditTab::setupUI()
             this, &VEditTab::statusChanged);
     addWidget(textEditor);
 
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Markdown:
         setupMarkdownPreview();
         textBrowser = NULL;
@@ -71,37 +67,38 @@ void VEditTab::setupUI()
         webPreviewer = NULL;
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
     }
 }
 
 void VEditTab::showFileReadMode()
 {
+    qDebug() << "read" << m_file->getName();
     isEditMode = false;
-    switch (noteFile->docType) {
+    switch (m_file->getDocType()) {
     case DocType::Html:
-        textBrowser->setHtml(noteFile->content);
+        textBrowser->setHtml(m_file->getContent());
         textBrowser->setFont(vconfig.getBaseEditFont());
         textBrowser->setPalette(vconfig.getBaseEditPalette());
         setCurrentWidget(textBrowser);
         break;
     case DocType::Markdown:
         if (mdConverterType == MarkdownConverterType::Marked) {
-            document.setText(noteFile->content);
+            document.setText(m_file->getContent());
         } else {
             previewByConverter();
         }
         setCurrentWidget(webPreviewer);
         break;
     default:
-        qWarning() << "error: unknown doc type" << int(noteFile->docType);
+        qWarning() << "error: unknown doc type" << int(m_file->getDocType());
     }
 }
 
 void VEditTab::previewByConverter()
 {
     VMarkdownConverter mdConverter;
-    QString content = noteFile->content;
+    QString &content = m_file->getContent();
     QString html = mdConverter.generateHtml(content, vconfig.getMarkdownExtensions());
     QRegularExpression tocExp("<p>\\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption);
     QString toc = mdConverter.generateToc(content, vconfig.getMarkdownExtensions());
@@ -119,15 +116,22 @@ void VEditTab::showFileEditMode()
     textEditor->setFocus();
 }
 
-bool VEditTab::requestClose()
+bool VEditTab::closeFile(bool p_forced)
 {
-    readFile();
+    if (p_forced && isEditMode) {
+        // Discard buffer content
+        textEditor->reloadFile();
+        textEditor->endEdit();
+        showFileReadMode();
+    } else {
+        readFile();
+    }
     return !isEditMode;
 }
 
 void VEditTab::editFile()
 {
-    if (isEditMode || !noteFile->modifiable) {
+    if (isEditMode) {
         return;
     }
 
@@ -141,14 +145,12 @@ void VEditTab::readFile()
     }
 
     if (textEditor->isModified()) {
-        // Need to save the changes
-        QMessageBox msgBox(this);
-        msgBox.setText(QString("The note \"%1\" has been modified.").arg(noteFile->fileName));
-        msgBox.setInformativeText("Do you want to save your changes?");
-        msgBox.setIcon(QMessageBox::Information);
-        msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
-        msgBox.setDefaultButton(QMessageBox::Save);
-        int ret = msgBox.exec();
+        // Prompt to save the changes
+        int ret = VUtils::showMessage(QMessageBox::Information, tr("Information"),
+                                      QString("Note %1 has been modified.").arg(m_file->getName()),
+                                      tr("Do you want to save your changes?"),
+                                      QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
+                                      QMessageBox::Save, this);
         switch (ret) {
         case QMessageBox::Save:
             saveFile();
@@ -171,33 +173,27 @@ void VEditTab::readFile()
 bool VEditTab::saveFile()
 {
     bool ret;
-    if (!isEditMode || !noteFile->modifiable || !textEditor->isModified()) {
+    if (!isEditMode || !textEditor->isModified()) {
         return true;
     }
     // Make sure the file already exists. Temporary deal with cases when user delete or move
     // a file.
-    QString filePath = QDir(noteFile->basePath).filePath(noteFile->fileName);
+    QString filePath = m_file->retrivePath();
     if (!QFile(filePath).exists()) {
-        qWarning() << "error:" << filePath << "being written has been removed";
-        QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"),
-                           QString("%1 being written has been removed.").arg(filePath),
-                           QMessageBox::Ok, this);
-        msgBox.setDefaultButton(QMessageBox::Ok);
-        msgBox.exec();
+        qWarning() << filePath << "being written has been removed";
+        VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note"),
+                            QString("%1 being written has been removed.").arg(filePath),
+                            QMessageBox::Ok, QMessageBox::Ok, this);
         return false;
     }
     textEditor->saveFile();
-    ret = VUtils::writeFileToDisk(filePath, noteFile->content);
+    ret = m_file->save();
     if (!ret) {
-        QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"),
-                           QString("Fail to write to disk when saving a note. Please try it again."),
-                           QMessageBox::Ok, this);
-        msgBox.setDefaultButton(QMessageBox::Ok);
-        msgBox.exec();
+        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);
-        ret = false;
     }
-    ret = true;
     emit statusChanged();
     return ret;
 }
@@ -218,10 +214,10 @@ void VEditTab::setupMarkdownPreview()
 
     if (mdConverterType == MarkdownConverterType::Marked) {
         webPreviewer->setHtml(VNote::templateHtml,
-                              QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
+                              QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator()));
     } else {
         webPreviewer->setHtml(VNote::preTemplateHtml + VNote::postTemplateHtml,
-                              QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
+                              QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator()));
     }
 
     addWidget(webPreviewer);
@@ -260,7 +256,7 @@ void VEditTab::updateTocFromHtml(const QString &tocHtml)
         }
     }
 
-    tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName));
+    tableOfContent.filePath = m_file->retrivePath();
     tableOfContent.valid = true;
 
     emit outlineChanged(tableOfContent);
@@ -270,7 +266,7 @@ void VEditTab::updateTocFromHeaders(const QVector<VHeader> &headers)
 {
     tableOfContent.type = VHeaderType::LineNumber;
     tableOfContent.headers = headers;
-    tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName));
+    tableOfContent.filePath = m_file->retrivePath();
     tableOfContent.valid = true;
 
     emit outlineChanged(tableOfContent);
@@ -367,8 +363,7 @@ void VEditTab::updateCurHeader(const QString &anchor)
     if (curHeader.anchor.mid(1) == anchor) {
         return;
     }
-    curHeader = VAnchor(QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)),
-                        "#" + anchor, -1);
+    curHeader = VAnchor(m_file->retrivePath(), "#" + anchor, -1);
     if (!anchor.isEmpty()) {
         emit curHeaderChanged(curHeader);
     }
@@ -379,16 +374,8 @@ void VEditTab::updateCurHeader(int lineNumber)
     if (curHeader.lineNumber == lineNumber) {
         return;
     }
-    curHeader = VAnchor(QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)),
-                        "", lineNumber);
+    curHeader = VAnchor(m_file->retrivePath(), "", lineNumber);
     if (lineNumber > -1) {
         emit curHeaderChanged(curHeader);
     }
 }
-
-void VEditTab::updatePath(const QString &newPath)
-{
-    QFileInfo info(newPath);
-    noteFile->basePath = info.path();
-    noteFile->fileName = info.fileName();
-}

+ 11 - 6
src/vedittab.h

@@ -3,13 +3,14 @@
 
 #include <QStackedWidget>
 #include <QString>
+#include <QPointer>
 #include "vconstants.h"
-#include "vnotefile.h"
 #include "vdocument.h"
 #include "vmarkdownconverter.h"
 #include "vconfigmanager.h"
 #include "vedit.h"
 #include "vtoc.h"
+#include "vfile.h"
 
 class QTextBrowser;
 class QWebEngineView;
@@ -20,9 +21,9 @@ class VEditTab : public QStackedWidget
 {
     Q_OBJECT
 public:
-    VEditTab(const QString &path, bool modifiable, QWidget *parent = 0);
+    VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent = 0);
     ~VEditTab();
-    bool requestClose();
+    bool closeFile(bool p_forced);
     // Enter edit mode
     void editFile();
     // Enter read mode
@@ -36,8 +37,7 @@ public:
     void requestUpdateOutline();
     void requestUpdateCurHeader();
     void scrollToAnchor(const VAnchor& anchor);
-    void updatePath(const QString &newPath);
-
+    inline VFile *getFile();
 signals:
     void getFocused();
     void outlineChanged(const VToc &toc);
@@ -61,7 +61,7 @@ private:
     void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
     void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
 
-    VNoteFile *noteFile;
+    QPointer<VFile> m_file;
     bool isEditMode;
     QTextBrowser *textBrowser;
     VEdit *textEditor;
@@ -93,4 +93,9 @@ inline bool VEditTab::isChild(QObject *obj)
     return false;
 }
 
+inline VFile *VEditTab::getFile()
+{
+    return m_file;
+}
+
 #endif // VEDITTAB_H

+ 96 - 144
src/veditwindow.cpp

@@ -5,6 +5,7 @@
 #include "vnote.h"
 #include "vconfigmanager.h"
 #include "utils/vutils.h"
+#include "vfile.h"
 
 extern VConfigManager vconfig;
 
@@ -70,11 +71,9 @@ void VEditWindow::splitWindow()
 void VEditWindow::removeSplit()
 {
     // Close all the files one by one
-    // If user do not want to close a file, just stop removing
-    if (closeAllFiles(false)) {
-        Q_ASSERT(count() == 0);
-        emit requestRemoveSplit(this);
-    }
+    // If user do not want to close a file, just stop removing.
+    // Otherwise, closeAllFiles() will emit requestRemoveSplit.
+    closeAllFiles(false);
 }
 
 void VEditWindow::setRemoveSplitEnable(bool enabled)
@@ -82,68 +81,67 @@ void VEditWindow::setRemoveSplitEnable(bool enabled)
     removeSplitAct->setVisible(enabled);
 }
 
-void VEditWindow::openWelcomePage()
+void VEditWindow::removeEditTab(int p_index)
 {
-    int idx = openFileInTab("", vconfig.getWelcomePagePath(), false);
-    setTabText(idx, generateTabText("Welcome to VNote", false));
-    setTabToolTip(idx, "VNote");
+    if (p_index > -1 && p_index < tabBar()->count()) {
+        VEditTab *editor = getTab(p_index);
+        removeTab(p_index);
+        delete editor;
+        if (tabBar()->count() == 0) {
+            emit requestRemoveSplit(this);
+        }
+    }
 }
 
-int VEditWindow::insertTabWithData(int index, QWidget *page,
-                                   const QJsonObject &tabData)
+int VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
 {
-    QString label = VUtils::fileNameFromPath(tabData["relative_path"].toString());
-    int idx = insertTab(index, page, label);
+    int idx = insertTab(p_index, p_page, p_file->getName());
     QTabBar *tabs = tabBar();
-    tabs->setTabData(idx, tabData);
-    tabs->setTabToolTip(idx, generateTooltip(tabData));
+    tabs->setTabToolTip(idx, generateTooltip(p_file));
     noticeStatus(currentIndex());
     return idx;
 }
 
-int VEditWindow::appendTabWithData(QWidget *page, const QJsonObject &tabData)
+int VEditWindow::appendEditTab(VFile *p_file, QWidget *p_page)
 {
-    return insertTabWithData(count(), page, tabData);
+    return insertEditTab(count(), p_file, p_page);
 }
 
-int VEditWindow::openFile(const QString &notebook, const QString &relativePath, int mode)
+int VEditWindow::openFile(VFile *p_file, OpenFileMode p_mode)
 {
+    qDebug() << "open" << p_file->getName();
     // Find if it has been opened already
-    int idx = findTabByFile(notebook, relativePath);
+    int idx = findTabByFile(p_file);
     if (idx > -1) {
         goto out;
     }
-    idx = openFileInTab(notebook, relativePath, true);
+    idx = openFileInTab(p_file, p_mode);
 out:
     setCurrentIndex(idx);
-    if (mode == OpenFileMode::Edit) {
-        editFile();
-    }
     focusWindow();
     noticeStatus(idx);
     return idx;
 }
 
-// Return true if we closed the file
-bool VEditWindow::closeFile(const QString &notebook, const QString &relativePath, bool isForced)
+// Return true if we closed the file actually
+bool VEditWindow::closeFile(const VFile *p_file, bool p_forced)
 {
     // Find if it has been opened already
-    int idx = findTabByFile(notebook, relativePath);
+    int idx = findTabByFile(p_file);
     if (idx == -1) {
         return false;
     }
 
     VEditTab *editor = getTab(idx);
     Q_ASSERT(editor);
-    bool ok = true;
-    if (!isForced) {
+    if (!p_forced) {
         setCurrentIndex(idx);
         noticeStatus(idx);
-        ok = editor->requestClose();
     }
+    // Even p_forced is true we need to delete unused images.
+    bool ok = editor->closeFile(p_forced);
     if (ok) {
-        removeTab(idx);
-        delete editor;
+        removeEditTab(idx);
     }
     updateTabListMenu();
     return ok;
@@ -155,15 +153,15 @@ bool VEditWindow::closeAllFiles(bool p_forced)
     bool ret = true;
     for (int i = 0; i < nrTab; ++i) {
         VEditTab *editor = getTab(0);
-        bool ok = true;
+
         if (!p_forced) {
             setCurrentIndex(0);
             noticeStatus(0);
-            ok = editor->requestClose();
         }
+        // Even p_forced is true we need to delete unused images.
+        bool ok = editor->closeFile(p_forced);
         if (ok) {
-            removeTab(0);
-            delete editor;
+            removeEditTab(0);
         } else {
             ret = false;
             break;
@@ -176,11 +174,9 @@ bool VEditWindow::closeAllFiles(bool p_forced)
     return ret;
 }
 
-int VEditWindow::openFileInTab(const QString &notebook, const QString &relativePath,
-                              bool modifiable)
+int VEditWindow::openFileInTab(VFile *p_file, OpenFileMode p_mode)
 {
-    QString path = QDir::cleanPath(QDir(vnote->getNotebookPath(notebook)).filePath(relativePath));
-    VEditTab *editor = new VEditTab(path, modifiable);
+    VEditTab *editor = new VEditTab(p_file, p_mode);
     connect(editor, &VEditTab::getFocused,
             this, &VEditWindow::getFocused);
     connect(editor, &VEditTab::outlineChanged,
@@ -190,23 +186,16 @@ int VEditWindow::openFileInTab(const QString &notebook, const QString &relativeP
     connect(editor, &VEditTab::statusChanged,
             this, &VEditWindow::handleTabStatusChanged);
 
-    QJsonObject tabJson;
-    tabJson["notebook"] = notebook;
-    tabJson["relative_path"] = relativePath;
-    tabJson["modifiable"] = modifiable;
-    int idx = appendTabWithData(editor, tabJson);
+    int idx = appendEditTab(p_file, editor);
     updateTabListMenu();
     return idx;
 }
 
-int VEditWindow::findTabByFile(const QString &notebook, const QString &relativePath) const
+int VEditWindow::findTabByFile(const VFile *p_file) const
 {
-    QTabBar *tabs = tabBar();
-    int nrTabs = tabs->count();
-
+    int nrTabs = tabBar()->count();
     for (int i = 0; i < nrTabs; ++i) {
-        QJsonObject tabJson = tabs->tabData(i).toJsonObject();
-        if (tabJson["notebook"] == notebook && tabJson["relative_path"] == relativePath) {
+        if (getTab(i)->getFile() == p_file) {
             return i;
         }
     }
@@ -217,10 +206,9 @@ bool VEditWindow::handleTabCloseRequest(int index)
 {
     VEditTab *editor = getTab(index);
     Q_ASSERT(editor);
-    bool ok = editor->requestClose();
+    bool ok = editor->closeFile(false);
     if (ok) {
-        removeTab(index);
-        delete editor;
+        removeEditTab(index);
     }
     updateTabListMenu();
     noticeStatus(currentIndex());
@@ -260,81 +248,29 @@ void VEditWindow::saveFile()
     editor->saveFile();
 }
 
-void VEditWindow::handleDirectoryRenamed(const QString &notebook, const QString &oldRelativePath,
-                                         const QString &newRelativePath)
-{
-    QTabBar *tabs = tabBar();
-    int nrTabs = tabs->count();
-    for (int i = 0; i < nrTabs; ++i) {
-        QJsonObject tabJson = tabs->tabData(i).toJsonObject();
-        if (tabJson["notebook"].toString() == notebook) {
-            QString relativePath = tabJson["relative_path"].toString();
-            if (relativePath.startsWith(oldRelativePath)) {
-                relativePath.replace(0, oldRelativePath.size(), newRelativePath);
-                tabJson["relative_path"] = relativePath;
-                tabs->setTabData(i, tabJson);
-                tabs->setTabToolTip(i, generateTooltip(tabJson));
-                QString path = QDir::cleanPath(QDir(vnote->getNotebookPath(notebook)).filePath(relativePath));
-                getTab(i)->updatePath(path);
-            }
-        }
-    }
-    updateTabListMenu();
-}
-
-void VEditWindow::handleFileRenamed(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                                    const QString &p_destNotebook, const QString &p_destRelativePath)
-{
-    QTabBar *tabs = tabBar();
-    int nrTabs = tabs->count();
-    for (int i = 0; i < nrTabs; ++i) {
-        QJsonObject tabJson = tabs->tabData(i).toJsonObject();
-        if (tabJson["notebook"].toString() == p_srcNotebook) {
-            QString relativePath = tabJson["relative_path"].toString();
-            if (relativePath == p_srcRelativePath) {
-                VEditTab *tab = getTab(i);
-                tabJson["notebook"] = p_destNotebook;
-                tabJson["relative_path"] = p_destRelativePath;
-                tabs->setTabData(i, tabJson);
-                tabs->setTabToolTip(i, generateTooltip(tabJson));
-                tabs->setTabText(i, generateTabText(VUtils::fileNameFromPath(p_destRelativePath), tab->isModified()));
-                QString path = QDir::cleanPath(QDir(vnote->getNotebookPath(p_destNotebook)).filePath(p_destRelativePath));
-                tab->updatePath(path);
-            }
-        }
-    }
-    updateTabListMenu();
-}
-
-void VEditWindow::noticeTabStatus(int index)
+void VEditWindow::noticeTabStatus(int p_index)
 {
-    if (index == -1) {
-        emit tabStatusChanged("", "", false, false, false);
+    if (p_index == -1) {
+        emit tabStatusChanged(NULL, false);
         return;
     }
 
-    QJsonObject tabJson = tabBar()->tabData(index).toJsonObject();
-    Q_ASSERT(!tabJson.isEmpty());
-
-    QString notebook = tabJson["notebook"].toString();
-    QString relativePath = tabJson["relative_path"].toString();
-    VEditTab *editor = getTab(index);
+    VEditTab *editor = getTab(p_index);
+    const VFile *file = editor->getFile();
     bool editMode = editor->getIsEditMode();
-    bool modifiable = tabJson["modifiable"].toBool();
 
     // Update tab text
-    tabBar()->setTabText(index, generateTabText(VUtils::fileNameFromPath(relativePath),
-                                                editor->isModified()));
-
-    emit tabStatusChanged(notebook, relativePath,
-                          editMode, modifiable, editor->isModified());
+    tabBar()->setTabText(p_index, generateTabText(file->getName(), file->isModified()));
+    emit tabStatusChanged(file, editMode);
 }
 
+// Be requested to report current status
 void VEditWindow::requestUpdateTabStatus()
 {
     noticeTabStatus(currentIndex());
 }
 
+// Be requested to report current outline
 void VEditWindow::requestUpdateOutline()
 {
     int idx = currentIndex();
@@ -345,6 +281,7 @@ void VEditWindow::requestUpdateOutline()
     getTab(idx)->requestUpdateOutline();
 }
 
+// Be requested to report current header
 void VEditWindow::requestUpdateCurHeader()
 {
     int idx = currentIndex();
@@ -355,6 +292,7 @@ void VEditWindow::requestUpdateCurHeader()
     getTab(idx)->requestUpdateCurHeader();
 }
 
+// Focus this windows. Try to focus current tab.
 void VEditWindow::focusWindow()
 {
     int idx = currentIndex();
@@ -392,9 +330,8 @@ void VEditWindow::tabListJump(QAction *action)
         return;
     }
 
-    QJsonObject tabJson = action->data().toJsonObject();
-    int idx = findTabByFile(tabJson["notebook"].toString(),
-                            tabJson["relative_path"].toString());
+    QPointer<VFile> file = action->data().value<QPointer<VFile>>();
+    int idx = findTabByFile(file);
     Q_ASSERT(idx >= 0);
     setCurrentIndex(idx);
     noticeStatus(idx);
@@ -416,54 +353,57 @@ void VEditWindow::updateTabListMenu()
     QTabBar *tabbar = tabBar();
     int nrTab = tabbar->count();
     for (int i = 0; i < nrTab; ++i) {
-        QAction *action = new QAction(tabbar->tabText(i), tabListAct);
-        action->setStatusTip(generateTooltip(tabbar->tabData(i).toJsonObject()));
-        action->setData(tabbar->tabData(i));
+        VEditTab *editor = getTab(i);
+        QPointer<VFile> file = editor->getFile();
+        QAction *action = new QAction(file->getName(), tabListAct);
+        action->setStatusTip(generateTooltip(file));
+        action->setData(QVariant::fromValue(file));
         menu->addAction(action);
     }
 }
 
-void VEditWindow::handleOutlineChanged(const VToc &toc)
+void VEditWindow::handleOutlineChanged(const VToc &p_toc)
 {
     // Only propagate it if it is current tab
     int idx = currentIndex();
-    QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject();
-    Q_ASSERT(!tabJson.isEmpty());
-    QString path = vnote->getNotebookPath(tabJson["notebook"].toString());
-    path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString()));
-
-    if (toc.filePath == path) {
-        emit outlineChanged(toc);
+    if (idx == -1) {
+        emit outlineChanged(VToc());
+        return;
+    }
+    const VFile *file = getTab(idx)->getFile();
+    if (p_toc.filePath == file->retrivePath()) {
+        emit outlineChanged(p_toc);
     }
 }
 
-void VEditWindow::handleCurHeaderChanged(const VAnchor &anchor)
+void VEditWindow::handleCurHeaderChanged(const VAnchor &p_anchor)
 {
     // Only propagate it if it is current tab
     int idx = currentIndex();
-    QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject();
-    Q_ASSERT(!tabJson.isEmpty());
-    QString path = vnote->getNotebookPath(tabJson["notebook"].toString());
-    path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString()));
-
-    if (anchor.filePath == path) {
-        emit curHeaderChanged(anchor);
+    if (idx == -1) {
+        emit curHeaderChanged(VAnchor());
+        return;
+    }
+    const VFile *file = getTab(idx)->getFile();
+    if (p_anchor.filePath == file->retrivePath()) {
+        emit curHeaderChanged(p_anchor);
     }
 }
 
-void VEditWindow::scrollCurTab(const VAnchor &anchor)
+void VEditWindow::scrollCurTab(const VAnchor &p_anchor)
 {
     int idx = currentIndex();
-    QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject();
-    Q_ASSERT(!tabJson.isEmpty());
-    QString path = vnote->getNotebookPath(tabJson["notebook"].toString());
-    path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString()));
-
-    if (path == anchor.filePath) {
-        getTab(idx)->scrollToAnchor(anchor);
+    if (idx == -1) {
+        emit curHeaderChanged(VAnchor());
+        return;
+    }
+    const VFile *file = getTab(idx)->getFile();
+    if (file->retrivePath() == p_anchor.filePath) {
+        getTab(idx)->scrollToAnchor(p_anchor);
     }
 }
 
+// Update tab status, outline and current header.
 void VEditWindow::noticeStatus(int index)
 {
     noticeTabStatus(index);
@@ -482,3 +422,15 @@ void VEditWindow::handleTabStatusChanged()
 {
     noticeTabStatus(currentIndex());
 }
+
+void VEditWindow::updateFileInfo(const VFile *p_file)
+{
+    if (!p_file) {
+        return;
+    }
+    int idx = findTabByFile(p_file);
+    if (idx > -1) {
+        noticeStatus(idx);
+        updateTabListMenu();
+    }
+}

+ 21 - 22
src/veditwindow.h

@@ -13,16 +13,16 @@
 class VNote;
 class QPushButton;
 class QActionGroup;
+class VFile;
 
 class VEditWindow : public QTabWidget
 {
     Q_OBJECT
 public:
     explicit VEditWindow(VNote *vnote, QWidget *parent = 0);
-    int findTabByFile(const QString &notebook, const QString &relativePath) const;
-    int openFile(const QString &notebook, const QString &relativePath,
-                 int mode);
-    bool closeFile(const QString &notebook, const QString &relativePath, bool isForced);
+    int findTabByFile(const VFile *p_file) const;
+    int openFile(VFile *p_file, OpenFileMode p_mode);
+    bool closeFile(const VFile *p_file, bool p_forced);
     void editFile();
     void saveFile();
     void readFile();
@@ -34,18 +34,14 @@ public:
     void requestUpdateCurHeader();
     // Focus to current tab's editor
     void focusWindow();
-    void scrollCurTab(const VAnchor &anchor);
-    void handleDirectoryRenamed(const QString &notebook,
-                                const QString &oldRelativePath, const QString &newRelativePath);
-    void handleFileRenamed(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                           const QString &p_destNotebook, const QString &p_destRelativePath);
+    void scrollCurTab(const VAnchor &p_anchor);
+    void updateFileInfo(const VFile *p_file);
 
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
 
 signals:
-    void tabStatusChanged(const QString &notebook, const QString &relativePath,
-                          bool editMode, bool modifiable, bool modified);
+    void tabStatusChanged(const VFile *p_file, bool p_editMode);
     void requestSplitWindow(VEditWindow *curWindow);
     void requestRemoveSplit(VEditWindow *curWindow);
     // This widget or its children get the focus
@@ -60,21 +56,21 @@ private slots:
     void handleTabbarClicked(int index);
     void contextMenuRequested(QPoint pos);
     void tabListJump(QAction *action);
-    void handleOutlineChanged(const VToc &toc);
-    void handleCurHeaderChanged(const VAnchor &anchor);
+    void handleOutlineChanged(const VToc &p_toc);
+    void handleCurHeaderChanged(const VAnchor &p_anchor);
     void handleTabStatusChanged();
 
 private:
     void setupCornerWidget();
-    void openWelcomePage();
-    int insertTabWithData(int index, QWidget *page, const QJsonObject &tabData);
-    int appendTabWithData(QWidget *page, const QJsonObject &tabData);
-    int openFileInTab(const QString &notebook, const QString &relativePath, bool modifiable);
+    void removeEditTab(int p_index);
+    int insertEditTab(int p_index, VFile *p_file, QWidget *p_page);
+    int appendEditTab(VFile *p_file, QWidget *p_page);
+    int openFileInTab(VFile *p_file, OpenFileMode p_mode);
     inline VEditTab *getTab(int tabIndex) const;
-    void noticeTabStatus(int index);
+    void noticeTabStatus(int p_index);
     void updateTabListMenu();
     void noticeStatus(int index);
-    inline QString generateTooltip(const QJsonObject &tabData) const;
+    inline QString generateTooltip(const VFile *p_file) const;
     inline QString generateTabText(const QString &p_name, bool p_modified) const;
 
     VNote *vnote;
@@ -94,10 +90,13 @@ inline VEditTab* VEditWindow::getTab(int tabIndex) const
     return dynamic_cast<VEditTab *>(widget(tabIndex));
 }
 
-inline QString VEditWindow::generateTooltip(const QJsonObject &tabData) const
+inline QString VEditWindow::generateTooltip(const VFile *p_file) const
 {
-    // [Notebook]relativePath
-    return QString("[%1] %2").arg(tabData["notebook"].toString()).arg(tabData["relative_path"].toString());
+    if (!p_file) {
+        return "";
+    }
+    // [Notebook]path
+    return QString("[%1] %2").arg(p_file->retriveNotebook()).arg(p_file->retrivePath());
 }
 
 inline QString VEditWindow::generateTabText(const QString &p_name, bool p_modified) const

+ 114 - 0
src/vfile.cpp

@@ -0,0 +1,114 @@
+#include "vfile.h"
+
+#include <QDir>
+#include <QDebug>
+#include <QTextEdit>
+#include "utils/vutils.h"
+
+VFile::VFile(const QString &p_name, QObject *p_parent)
+    : QObject(p_parent), m_name(p_name), m_opened(false), m_modified(false),
+      m_docType(VUtils::isMarkdown(p_name) ? DocType::Markdown : DocType::Html)
+{
+}
+
+bool VFile::open()
+{
+    Q_ASSERT(!m_name.isEmpty());
+    if (m_opened) {
+        return true;
+    }
+    Q_ASSERT(m_content.isEmpty());
+    Q_ASSERT(m_docType == (VUtils::isMarkdown(m_name) ? DocType::Markdown : DocType::Html));
+    QString path = retrivePath();
+    qDebug() << "path" << path;
+    m_content = VUtils::readFileFromDisk(path);
+    m_modified = false;
+    m_opened = true;
+    qDebug() << "file" << m_name << "opened";
+    return true;
+}
+
+void VFile::close()
+{
+    if (!m_opened) {
+        return;
+    }
+    m_content.clear();
+    m_opened = false;
+}
+
+void VFile::deleteDiskFile()
+{
+    Q_ASSERT(parent());
+
+    // Delete local images in ./images if it is Markdown
+    if (m_docType == DocType::Markdown) {
+        deleteLocalImages();
+    }
+
+    // Delete the file
+    QString filePath = retrivePath();
+    QFile file(filePath);
+    if (file.remove()) {
+        qDebug() << "deleted" << filePath;
+    } else {
+        qWarning() << "failed to delete" << filePath;
+    }
+}
+
+bool VFile::save()
+{
+    Q_ASSERT(m_opened);
+    bool ret = VUtils::writeFileToDisk(retrivePath(), m_content);
+    return ret;
+}
+
+void VFile::convert(DocType p_curType, DocType p_targetType)
+{
+    Q_ASSERT(!m_opened);
+    m_docType = p_targetType;
+    if (p_curType == p_targetType) {
+        return;
+    }
+    QString path = retrivePath();
+    QString fileText = VUtils::readFileFromDisk(path);
+    QTextEdit editor;
+    if (p_curType == DocType::Markdown) {
+        editor.setPlainText(fileText);
+        fileText = editor.toHtml();
+    } else {
+        editor.setHtml(fileText);
+        fileText = editor.toPlainText();
+    }
+    VUtils::writeFileToDisk(path, fileText);
+    qDebug() << getName() << "converted" << (int)p_curType << (int)p_targetType;
+}
+
+void VFile::setModified(bool p_modified)
+{
+    m_modified = p_modified;
+}
+
+void VFile::deleteLocalImages()
+{
+    Q_ASSERT(m_docType == DocType::Markdown);
+    QString filePath = retrivePath();
+    QVector<QString> images = VUtils::imagesFromMarkdownFile(filePath);
+    int deleted = 0;
+    for (int i = 0; i < images.size(); ++i) {
+        QFile file(images[i]);
+        if (file.remove()) {
+            ++deleted;
+        }
+    }
+    qDebug() << "delete" << deleted << "images for" << filePath;
+}
+
+void VFile::setName(const QString &p_name)
+{
+    m_name = p_name;
+    DocType newType = VUtils::isMarkdown(p_name) ? DocType::Markdown : DocType::Html;
+    if (newType != m_docType) {
+        qWarning() << "setName() change the DocType. A convertion should be followed";
+    }
+}

+ 125 - 0
src/vfile.h

@@ -0,0 +1,125 @@
+#ifndef VFILE_H
+#define VFILE_H
+
+#include <QObject>
+#include <QString>
+#include <QDir>
+#include "vdirectory.h"
+#include "vconstants.h"
+
+class VFile : public QObject
+{
+    Q_OBJECT
+public:
+    explicit VFile(const QString &p_name, QObject *p_parent);
+    bool open();
+    void close();
+    bool save();
+    // Convert current file type.
+    void convert(DocType p_curType, DocType p_targetType);
+
+    inline const QString &getName() const;
+    void setName(const QString &p_name);
+    inline VDirectory *getDirectory();
+    inline const VDirectory *getDirectory() const;
+    inline DocType getDocType() const;
+    inline QString &getContent();
+    inline void setContent(const QString &p_content);
+    inline QString retriveNotebook() const;
+    inline QString retrivePath() const;
+    inline QString retriveRelativePath() const;
+    inline QString retriveBasePath() const;
+    inline QString retriveImagePath() const;
+    inline bool isModified() const;
+    inline bool isOpened() const;
+
+signals:
+
+public slots:
+    void setModified(bool p_modified);
+
+private:
+    // Delete the file and corresponding images
+    void deleteDiskFile();
+    // Delete local images in ./images of DocType::Markdown
+    void deleteLocalImages();
+
+    QString m_name;
+    bool m_opened;
+    // File has been modified in editor
+    bool m_modified;
+    DocType m_docType;
+    QString m_content;
+    friend class VDirectory;
+};
+
+inline const QString &VFile::getName() const
+{
+    return m_name;
+}
+
+inline VDirectory *VFile::getDirectory()
+{
+    Q_ASSERT(parent());
+    return (VDirectory *)parent();
+}
+
+inline const VDirectory *VFile::getDirectory() const
+{
+    Q_ASSERT(parent());
+    return (const VDirectory *)parent();
+}
+
+inline DocType VFile::getDocType() const
+{
+    return m_docType;
+}
+
+inline QString &VFile::getContent()
+{
+    return m_content;
+}
+
+inline QString VFile::retriveNotebook() const
+{
+    return getDirectory()->retriveNotebook();
+}
+
+inline QString VFile::retrivePath() const
+{
+    QString dirPath = getDirectory()->retrivePath();
+    return QDir(dirPath).filePath(m_name);
+}
+
+inline QString VFile::retriveRelativePath() const
+{
+    QString dirRelativePath = getDirectory()->retriveRelativePath();
+    return QDir(dirRelativePath).filePath(m_name);
+}
+
+inline QString VFile::retriveBasePath() const
+{
+    return getDirectory()->retrivePath();
+}
+
+inline QString VFile::retriveImagePath() const
+{
+    return QDir(retriveBasePath()).filePath("images");
+}
+
+inline void VFile::setContent(const QString &p_content)
+{
+    m_content = p_content;
+}
+
+inline bool VFile::isModified() const
+{
+    return m_modified;
+}
+
+inline bool VFile::isOpened() const
+{
+    return m_opened;
+}
+
+#endif // VFILE_H

+ 159 - 447
src/vfilelist.cpp

@@ -7,9 +7,10 @@
 #include "vnote.h"
 #include "veditarea.h"
 #include "utils/vutils.h"
+#include "vfile.h"
 
-VFileList::VFileList(VNote *vnote, QWidget *parent)
-    : QWidget(parent), vnote(vnote)
+VFileList::VFileList(QWidget *parent)
+    : QWidget(parent)
 {
     setupUI();
     initActions();
@@ -43,14 +44,14 @@ void VFileList::initActions()
     deleteFileAct = new QAction(QIcon(":/resources/icons/delete_note.svg"),
                                 tr("&Delete"), this);
     deleteFileAct->setStatusTip(tr("Delete selected note"));
-    connect(deleteFileAct, &QAction::triggered,
-            this, &VFileList::deleteCurFile);
+    connect(deleteFileAct, SIGNAL(triggered(bool)),
+            this, SLOT(deleteFile()));
 
     fileInfoAct = new QAction(QIcon(":/resources/icons/note_info.svg"),
                               tr("&Info"), this);
     fileInfoAct->setStatusTip(tr("View and edit current note's information"));
-    connect(fileInfoAct, &QAction::triggered,
-            this, &VFileList::curFileInfo);
+    connect(fileInfoAct, SIGNAL(triggered(bool)),
+            this, SLOT(fileInfo()));
 
     copyAct = new QAction(QIcon(":/resources/icons/copy.svg"),
                           tr("&Copy"), this);
@@ -71,85 +72,49 @@ void VFileList::initActions()
             this, &VFileList::pasteFilesInCurDir);
 }
 
-void VFileList::setDirectory(QJsonObject dirJson)
+void VFileList::setDirectory(VDirectory *p_directory)
 {
-    fileList->clear();
-    if (dirJson.isEmpty()) {
-        clearDirectoryInfo();
-        emit directoryChanged("", "");
+    if (m_directory == p_directory) {
         return;
     }
-
-    notebook = dirJson["notebook"].toString();
-    relativePath = dirJson["relative_path"].toString();
-    rootPath = "";
-    const QVector<VNotebook *> &notebooks = vnote->getNotebooks();
-    for (int i = 0; i < notebooks.size(); ++i) {
-        if (notebooks[i]->getName() == notebook) {
-            rootPath = notebooks[i]->getPath();
-            break;
-        }
+    m_directory = p_directory;
+    if (!m_directory) {
+        fileList->clear();
+        return;
     }
-    Q_ASSERT(!rootPath.isEmpty());
 
+    qDebug() << "filelist set directory" << m_directory->getName();
     updateFileList();
-
-    emit directoryChanged(notebook, relativePath);
-}
-
-void VFileList::clearDirectoryInfo()
-{
-    notebook = relativePath = rootPath = "";
 }
 
 void VFileList::updateFileList()
 {
-    QString path = QDir(rootPath).filePath(relativePath);
-
     fileList->clear();
-    if (!QDir(path).exists()) {
-        qDebug() << "invalid notebook directory:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook directory \"%1\" either does not exist or is not valid.")
-                                  .arg(path));
-        msgBox.exec();
+    if (!m_directory->open()) {
         return;
     }
-
-    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
-    if (configJson.isEmpty()) {
-        qDebug() << "invalid notebook configuration for directory:" << path;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."),
-                           QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Notebook directory \"%1\" does not contain a valid configuration file.")
-                                  .arg(path));
-        msgBox.exec();
-        return;
-    }
-
-    // Handle files section
-    QJsonArray filesJson = configJson["files"].toArray();
-    for (int i = 0; i < filesJson.size(); ++i) {
-        QJsonObject fileItem = filesJson[i].toObject();
-        insertFileListItem(fileItem);
+    const QVector<VFile *> &files = m_directory->getFiles();
+    for (int i = 0; i < files.size(); ++i) {
+        VFile *file = files[i];
+        insertFileListItem(file);
     }
 }
 
-void VFileList::curFileInfo()
+void VFileList::fileInfo()
 {
     QListWidgetItem *curItem = fileList->currentItem();
-    QJsonObject curItemJson = curItem->data(Qt::UserRole).toJsonObject();
-    Q_ASSERT(!curItemJson.isEmpty());
-    QString curItemName = curItemJson["name"].toString();
-    fileInfo(notebook, QDir(relativePath).filePath(curItemName));
+    Q_ASSERT(curItem);
+    fileInfo(getVFile(curItem));
 }
 
-void VFileList::fileInfo(const QString &p_notebook, const QString &p_relativePath)
+void VFileList::fileInfo(VFile *p_file)
 {
-    qDebug() << "fileInfo" << p_notebook << p_relativePath;
+    if (!p_file) {
+        return;
+    }
+    VDirectory *dir = p_file->getDirectory();
     QString info;
-    QString defaultName = VUtils::fileNameFromPath(p_relativePath);
+    QString defaultName = p_file->getName();
     QString curName = defaultName;
     do {
         VFileInfoDialog dialog(tr("Note Information"), info, defaultName, this);
@@ -158,23 +123,22 @@ void VFileList::fileInfo(const QString &p_notebook, const QString &p_relativePat
             if (name == curName) {
                 return;
             }
-            if (isConflictNameWithExisting(name)) {
-                info = "Name already exists.\nPlease choose another name:";
+            if (dir->findFile(name)) {
+                info = "Name already exists.\nPlease choose another name.";
                 defaultName = name;
                 continue;
             }
-            copyFile(p_notebook, p_relativePath, p_notebook,
-                     QDir(VUtils::basePathFromPath(p_relativePath)).filePath(name), true);
+            copyFile(dir, name, p_file, true);
         }
         break;
     } while (true);
 }
 
-QListWidgetItem* VFileList::insertFileListItem(QJsonObject fileJson, bool atFront)
+QListWidgetItem* VFileList::insertFileListItem(VFile *file, bool atFront)
 {
-    Q_ASSERT(!fileJson.isEmpty());
-    QListWidgetItem *item = new QListWidgetItem(fileJson["name"].toString());
-    item->setData(Qt::UserRole, fileJson);
+    Q_ASSERT(file);
+    QListWidgetItem *item = new QListWidgetItem(file->getName());
+    item->setData(Qt::UserRole, QVariant::fromValue(file));
 
     if (atFront) {
         fileList->insertItem(0, item);
@@ -183,7 +147,7 @@ QListWidgetItem* VFileList::insertFileListItem(QJsonObject fileJson, bool atFron
     }
     // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
     fileList->update();
-    qDebug() << "add new list item:" << fileJson["name"].toString();
+    qDebug() << "VFileList adds" << file->getName();
     return item;
 }
 
@@ -198,65 +162,91 @@ void VFileList::removeFileListItem(QListWidgetItem *item)
 
 void VFileList::newFile()
 {
+    if (!m_directory) {
+        return;
+    }
+    QString info = QString("Create a new note under %1.").arg(m_directory->getName());
     QString text("&Note name:");
     QString defaultText("new_note");
     do {
-        VNewFileDialog dialog(QString("Create a new note under %1").arg(VUtils::directoryNameFromPath(relativePath)),
-                              text, defaultText, this);
+        VNewFileDialog dialog(QString("Create new note"), info, text, defaultText, this);
         if (dialog.exec() == QDialog::Accepted) {
             QString name = dialog.getNameInput();
-            if (isConflictNameWithExisting(name)) {
-                text = "Name already exists.\nPlease choose another name:";
+            if (m_directory->findFile(name)) {
+                info = "Name already exists.\nPlease choose another name.";
                 defaultText = name;
                 continue;
             }
-            QListWidgetItem *newItem = createFileAndUpdateList(name);
-            if (newItem) {
-                fileList->setCurrentItem(newItem);
-                // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
-                fileList->update();
-
-                // Open this file in edit mode
-                QJsonObject itemJson = newItem->data(Qt::UserRole).toJsonObject();
-                Q_ASSERT(!itemJson.isEmpty());
-                itemJson["notebook"] = notebook;
-                itemJson["relative_path"] = QDir::cleanPath(QDir(relativePath).filePath(name));
-                itemJson["mode"] = OpenFileMode::Edit;
-                emit fileCreated(itemJson);
+            VFile *file = m_directory->createFile(name);
+            if (!file) {
+                VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                    QString("Failed to create file %1.").arg(name), "",
+                                    QMessageBox::Ok, QMessageBox::Ok, this);
+                return;
             }
+            QVector<QListWidgetItem *> items = updateFileListAdded();
+            Q_ASSERT(items.size() == 1);
+            fileList->setCurrentItem(items[0]);
+            // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
+            fileList->update();
+
+            // Open it in edit mode
+            emit fileCreated(file, OpenFileMode::Edit);
         }
         break;
     } while (true);
 }
 
-void VFileList::deleteCurFile()
+QVector<QListWidgetItem *> VFileList::updateFileListAdded()
+{
+    QVector<QListWidgetItem *> ret;
+    const QVector<VFile *> &files = m_directory->getFiles();
+    for (int i = 0; i < files.size(); ++i) {
+        VFile *file = files[i];
+        if (i >= fileList->count()) {
+            QListWidgetItem *item = insertFileListItem(file, false);
+            ret.append(item);
+        } else {
+            VFile *itemFile = getVFile(fileList->item(i));
+            if (itemFile != file) {
+                QListWidgetItem *item = insertFileListItem(file, false);
+                ret.append(item);
+            }
+        }
+    }
+    qDebug() << ret.size() << "items added";
+    return ret;
+}
+
+// Delete the file related to current item
+void VFileList::deleteFile()
 {
     QListWidgetItem *curItem = fileList->currentItem();
     Q_ASSERT(curItem);
-    QJsonObject curItemJson = curItem->data(Qt::UserRole).toJsonObject();
-    QString curItemName = curItemJson["name"].toString();
-    deleteFile(notebook, QDir(relativePath).filePath(curItemName));
+    deleteFile(getVFile(curItem));
 }
 
-// @p_relativePath contains the file name
-void VFileList::deleteFile(const QString &p_notebook, const QString &p_relativePath)
+// @p_file may or may not be listed in VFileList
+void VFileList::deleteFile(VFile *p_file)
 {
-    QString fileName = VUtils::fileNameFromPath(p_relativePath);
-    QMessageBox msgBox(QMessageBox::Warning, tr("Warning"),
-                       QString("Are you sure you want to delete note \"%1\"?")
-                       .arg(fileName), QMessageBox::Ok | QMessageBox::Cancel,
-                       this);
-    msgBox.setInformativeText(tr("This may be not recoverable."));
-    msgBox.setDefaultButton(QMessageBox::Ok);
-    if (msgBox.exec() == QMessageBox::Ok) {
-        // First close this file forcely
-        QJsonObject curItemJson;
-        curItemJson["notebook"] = p_notebook;
-        curItemJson["relative_path"] = QDir::cleanPath(p_relativePath);
-        curItemJson["is_forced"] = true;
-        emit fileDeleted(curItemJson);
-
-        deleteFileAndUpdateList(p_notebook, p_relativePath);
+    if (!p_file) {
+        return;
+    }
+    VDirectory *dir = p_file->getDirectory();
+    QString fileName = p_file->getName();
+    int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                       QString("Are you sure to delete note %1?").arg(fileName), tr("This may be unrecoverable!"),
+                       QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this);
+    if (ret == QMessageBox::Ok) {
+        editArea->closeFile(p_file, true);
+
+        // Remove the item before deleting it totally, or p_file will be invalid.
+        QListWidgetItem *item = findItem(p_file);
+        if (item) {
+            removeFileListItem(item);
+        }
+
+        dir->deleteFile(p_file);
     }
 }
 
@@ -265,7 +255,7 @@ void VFileList::contextMenuRequested(QPoint pos)
     QListWidgetItem *item = fileList->itemAt(pos);
     QMenu menu(this);
 
-    if (notebook.isEmpty()) {
+    if (!m_directory) {
         return;
     }
     menu.addAction(newFileAct);
@@ -290,187 +280,56 @@ void VFileList::contextMenuRequested(QPoint pos)
     menu.exec(fileList->mapToGlobal(pos));
 }
 
-bool VFileList::isConflictNameWithExisting(const QString &name)
-{
-    int nrChild = fileList->count();
-    for (int i = 0; i < nrChild; ++i) {
-        QListWidgetItem *item = fileList->item(i);
-        QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject();
-        Q_ASSERT(!itemJson.isEmpty());
-        if (itemJson["name"].toString() == name) {
-            return true;
-        }
-    }
-    return false;
-}
-
-QListWidgetItem* VFileList::findItem(const QString &p_notebook, const QString &p_relativePath)
+QListWidgetItem* VFileList::findItem(const VFile *p_file)
 {
-    if (p_notebook != notebook || VUtils::basePathFromPath(p_relativePath) != QDir::cleanPath(relativePath)) {
+    if (!p_file || p_file->getDirectory() != m_directory) {
         return NULL;
     }
-    QString name = VUtils::fileNameFromPath(p_relativePath);
+
     int nrChild = fileList->count();
     for (int i = 0; i < nrChild; ++i) {
         QListWidgetItem *item = fileList->item(i);
-        QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject();
-        Q_ASSERT(!itemJson.isEmpty());
-        if (itemJson["name"].toString() == name) {
+        if (p_file == getVFile(item)) {
             return item;
         }
     }
     return NULL;
 }
 
-QListWidgetItem* VFileList::createFileAndUpdateList(const QString &name)
-{
-    QString path = QDir(rootPath).filePath(relativePath);
-    QString filePath = QDir(path).filePath(name);
-    QFile file(filePath);
-
-    if (!file.open(QIODevice::WriteOnly)) {
-        qWarning() << "error: fail to create file:" << filePath;
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create file \"%1\" under \"%2\".")
-                           .arg(name).arg(path), QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Please check if there already exists a file named \"%1\".").arg(name));
-        msgBox.exec();
-        return NULL;
-    }
-    file.close();
-    qDebug() << "create file:" << filePath;
-
-    if (!addFileInConfig(filePath, 0)) {
-        file.remove();
-        return NULL;
-    }
-
-    return insertFileListItem(readFileInConfig(filePath), true);
-}
-
-void VFileList::deleteFileAndUpdateList(const QString &p_notebook,
-                                        const QString &p_relativePath)
-{
-    QString filePath = QDir(vnote->getNotebookPath(p_notebook)).filePath(p_relativePath);
-
-    if (!removeFileInConfig(filePath)) {
-        return;
-    }
-
-    // Delete local images in ./images
-    deleteLocalImages(filePath);
-
-    // Delete the file
-    QFile file(filePath);
-    if (!file.remove()) {
-        qWarning() << "error: fail to delete" << filePath;
-    } else {
-        qDebug() << "delete" << filePath;
-    }
-
-    QListWidgetItem *item = findItem(p_notebook, p_relativePath);
-    if (item) {
-        removeFileListItem(item);
-    }
-}
-
 void VFileList::handleItemClicked(QListWidgetItem *currentItem)
 {
     if (!currentItem) {
-        emit fileClicked(QJsonObject());
+        emit fileClicked(NULL);
         return;
     }
     // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
     fileList->update();
-    QJsonObject itemJson = currentItem->data(Qt::UserRole).toJsonObject();
-    Q_ASSERT(!itemJson.isEmpty());
-    itemJson["notebook"] = notebook;
-    itemJson["relative_path"] = QDir::cleanPath(QDir(relativePath).filePath(itemJson["name"].toString()));
-    itemJson["mode"] = OpenFileMode::Read;
-    emit fileClicked(itemJson);
+    emit fileClicked(getVFile(currentItem), OpenFileMode::Read);
 }
 
-bool VFileList::importFile(const QString &name)
+bool VFileList::importFile(const QString &p_srcFilePath)
 {
-    if (name.isEmpty()) {
+    if (p_srcFilePath.isEmpty()) {
         return false;
     }
-    if (isConflictNameWithExisting(name)) {
-        return false;
-    }
-
+    Q_ASSERT(m_directory);
     // Copy file @name to current directory
-    QString targetPath = QDir(rootPath).filePath(relativePath);
-    QString srcName = QFileInfo(name).fileName();
+    QString targetPath = m_directory->retrivePath();
+    QString srcName = VUtils::fileNameFromPath(p_srcFilePath);
     if (srcName.isEmpty()) {
         return false;
     }
-    QString targetName = QDir(targetPath).filePath(srcName);
-
-    bool ret = QFile::copy(name, targetName);
+    QString targetFilePath = QDir(targetPath).filePath(srcName);
+    bool ret = VUtils::copyFile(p_srcFilePath, targetFilePath, false);
     if (!ret) {
-        qWarning() << "error: fail to copy" << name << "to" << targetName;
         return false;
     }
 
-    // Update current directory's config file to include this new file
-    QJsonObject dirJson = VConfigManager::readDirectoryConfig(targetPath);
-    Q_ASSERT(!dirJson.isEmpty());
-    QJsonObject fileJson;
-    fileJson["name"] = srcName;
-    QJsonArray fileArray = dirJson["files"].toArray();
-    fileArray.push_front(fileJson);
-    dirJson["files"] = fileArray;
-    if (!VConfigManager::writeDirectoryConfig(targetPath, dirJson)) {
-        qWarning() << "error: fail to update directory's configuration file to add a new file"
-                   << srcName;
-        QFile(targetName).remove();
-        return false;
+    VFile *destFile = m_directory->addFile(srcName, -1);
+    if (destFile) {
+        return insertFileListItem(destFile, false);
     }
-
-    return insertFileListItem(fileJson, true);
-}
-
-void VFileList::handleDirectoryRenamed(const QString &notebook,
-                                       const QString &oldRelativePath, const QString &newRelativePath)
-{
-    if (notebook == this->notebook
-        && relativePath.startsWith(oldRelativePath)) {
-        relativePath.replace(0, oldRelativePath.size(), newRelativePath);
-    }
-}
-
-void VFileList::convertFileType(const QString &notebook, const QString &fileRelativePath,
-                                DocType oldType, DocType newType)
-{
-    Q_ASSERT(oldType != newType);
-    QString filePath = QDir(vnote->getNotebookPath(notebook)).filePath(fileRelativePath);
-    QString fileText = VUtils::readFileFromDisk(filePath);
-    QTextEdit editor;
-    if (oldType == DocType::Markdown) {
-        editor.setPlainText(fileText);
-        fileText = editor.toHtml();
-    } else {
-        editor.setHtml(fileText);
-        fileText = editor.toPlainText();
-    }
-    VUtils::writeFileToDisk(filePath, fileText);
-}
-
-void VFileList::deleteLocalImages(const QString &filePath)
-{
-    if (!VUtils::isMarkdown(filePath)) {
-        return;
-    }
-
-    QVector<QString> images = VUtils::imagesFromMarkdownFile(filePath);
-    int deleted = 0;
-    for (int i = 0; i < images.size(); ++i) {
-        QFile file(images[i]);
-        if (file.remove()) {
-            ++deleted;
-        }
-    }
-    qDebug() << "delete" << deleted << "images for" << filePath;
+    return false;
 }
 
 void VFileList::copySelectedFiles(bool p_isCut)
@@ -480,14 +339,15 @@ void VFileList::copySelectedFiles(bool p_isCut)
         return;
     }
     QJsonArray files;
-    QDir dir(relativePath);
+    m_copiedFiles.clear();
     for (int i = 0; i < items.size(); ++i) {
-        QJsonObject itemJson = items[i]->data(Qt::UserRole).toJsonObject();
-        QString itemName = itemJson["name"].toString();
+        VFile *file = getVFile(items[i]);
         QJsonObject fileJson;
-        fileJson["notebook"] = notebook;
-        fileJson["relative_path"] = dir.filePath(itemName);
+        fileJson["notebook"] = file->retriveNotebook();
+        fileJson["path"] = file->retrivePath();
         files.append(fileJson);
+
+        m_copiedFiles.append(file);
     }
 
     copyFileInfoToClipboard(files, p_isCut);
@@ -511,219 +371,71 @@ void VFileList::copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut)
 
 void VFileList::pasteFilesInCurDir()
 {
-    pasteFiles(notebook, relativePath);
+    pasteFiles(m_directory);
 }
 
-void VFileList::pasteFiles(const QString &p_notebook, const QString &p_dirRelativePath)
+void VFileList::pasteFiles(VDirectory *p_destDir)
 {
-    qDebug() << "paste files to" << p_notebook << p_dirRelativePath;
+    qDebug() << "paste files to" << p_destDir->getName();
     QClipboard *clipboard = QApplication::clipboard();
     QString text = clipboard->text();
     QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
     Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyFile);
-
     bool isCut = clip["is_cut"].toBool();
-    QJsonArray sources = clip["sources"].toArray();
 
-    int nrFiles = sources.size();
-    QDir destDir(p_dirRelativePath);
     int nrPasted = 0;
-    for (int i = 0; i < nrFiles; ++i) {
-        QJsonObject file = sources[i].toObject();
-        QString srcNotebook = file["notebook"].toString();
-        QString srcRelativePath = file["relative_path"].toString();
-        bool ret = copyFile(srcNotebook, srcRelativePath, p_notebook,
-                            destDir.filePath(VUtils::fileNameFromPath(srcRelativePath)), isCut);
-        if (ret) {
+    for (int i = 0; i < m_copiedFiles.size(); ++i) {
+        QPointer<VFile> srcFile = m_copiedFiles[i];
+        if (!srcFile) {
+            continue;
+        }
+        QString fileName = srcFile->getName();
+        VDirectory *srcDir = srcFile->getDirectory();
+        if (srcDir == p_destDir && !isCut) {
+            // Copy and paste in the same directory.
+            // Rename it to xx_copy.md
+            fileName = VUtils::generateCopiedFileName(srcDir->retrivePath(), fileName);
+        }
+        if (copyFile(p_destDir, fileName, srcFile, isCut)) {
             nrPasted++;
         }
     }
+
     qDebug() << "pasted" << nrPasted << "files sucessfully";
     clipboard->clear();
+    m_copiedFiles.clear();
 }
 
-bool VFileList::copyFile(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                         const QString &p_destNotebook, const QString &p_destRelativePath,
-                         bool p_isCut)
+bool VFileList::copyFile(VDirectory *p_destDir, const QString &p_destName, VFile *p_file, bool p_cut)
 {
-    QString srcPath = QDir(vnote->getNotebookPath(p_srcNotebook)).filePath(p_srcRelativePath);
-    srcPath = QDir::cleanPath(srcPath);
-    QString destPath = QDir(vnote->getNotebookPath(p_destNotebook)).filePath(p_destRelativePath);
-    destPath = QDir::cleanPath(destPath);
+    QString srcPath = QDir::cleanPath(p_file->retrivePath());
+    QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
     if (srcPath == destPath) {
         return true;
     }
-
-    // If change the file type, we need to convert it
-    bool needConversion = false;
-    DocType docType = VUtils::isMarkdown(srcPath) ? DocType::Markdown : DocType::Html;
+    // If change the file type, we need to close it first
+    DocType docType = p_file->getDocType();
     DocType newDocType = VUtils::isMarkdown(destPath) ? DocType::Markdown : DocType::Html;
     if (docType != newDocType) {
-        if (editArea->isFileOpened(p_srcNotebook, p_srcRelativePath)) {
-            QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Rename will change the note type"),
-                               QMessageBox::Ok | QMessageBox::Cancel, this);
-            msgBox.setDefaultButton(QMessageBox::Ok);
-            msgBox.setInformativeText(QString("You should close the note %1 before continue")
-                                      .arg(VUtils::fileNameFromPath(p_srcRelativePath)));
-            if (QMessageBox::Ok == msgBox.exec()) {
-                QJsonObject curItemJson;
-                curItemJson["notebook"] = p_srcNotebook;
-                curItemJson["relative_path"] = p_srcRelativePath;
-                curItemJson["is_forced"] = false;
-                if (!editArea->closeFile(curItemJson)) {
+        if (editArea->isFileOpened(p_file)) {
+            int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                          QString("The renaming will change the note type."),
+                                          QString("You should close the note %1 before continue.").arg(p_file->getName()),
+                                          QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this);
+            if (QMessageBox::Ok == ret) {
+                if (!editArea->closeFile(p_file, false)) {
                     return false;
                 }
             } else {
                 return false;
             }
         }
-        // Convert it later
-        needConversion = true;
-    }
-
-    QVector<QString> images;
-    if (docType == DocType::Markdown) {
-        images = VUtils::imagesFromMarkdownFile(srcPath);
-    }
-
-    // Copy the file
-    if (!VUtils::copyFile(srcPath, destPath, p_isCut)) {
-        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Fail to copy %1 from %2.")
-                           .arg(p_srcRelativePath).arg(p_srcNotebook), QMessageBox::Ok, this);
-        msgBox.setInformativeText(QString("Please check if there already exists a file with the same name"));
-        msgBox.exec();
-        return false;
-    }
-
-    if (needConversion) {
-        convertFileType(p_destNotebook, p_destRelativePath, docType, newDocType);
-    }
-
-    // We need to copy images when it is still markdown
-    if (!images.isEmpty()) {
-        if (newDocType == DocType::Markdown) {
-            QString dirPath = QDir(VUtils::basePathFromPath(destPath)).filePath("images");
-            VUtils::makeDirectory(dirPath);
-            int nrPasted = 0;
-            for (int i = 0; i < images.size(); ++i) {
-                if (!QFile(images[i]).exists()) {
-                    continue;
-                }
-
-                QString destImagePath = QDir(dirPath).filePath(VUtils::fileNameFromPath(images[i]));
-                if (VUtils::copyFile(images[i], destImagePath, p_isCut)) {
-                    nrPasted++;
-                } else {
-                    QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Fail to copy image %1.")
-                                       .arg(images[i]), QMessageBox::Ok, this);
-                    msgBox.setInformativeText(QString("Please check if there already exists a file with the same name and manually copy it"));
-                    msgBox.exec();
-                }
-            }
-            qDebug() << "pasted" << nrPasted << "images sucessfully";
-        } else {
-            // Delete the images
-            for (int i = 0; i < images.size(); ++i) {
-                QFile file(images[i]);
-                file.remove();
-            }
-        }
     }
 
-    int idx = -1;
-    if (p_isCut) {
-        // Remove src in the config
-        idx = removeFileInConfig(srcPath);
-        if (VUtils::basePathFromPath(srcPath) != VUtils::basePathFromPath(destPath)) {
-            idx = -1;
-        }
-    }
-
-    // Add dest in the config
-    addFileInConfig(destPath, idx);
-
+    VFile *destFile = VDirectory::copyFile(p_destDir, p_destName, p_file, p_cut);
     updateFileList();
-
-    if (p_isCut) {
-        emit fileRenamed(p_srcNotebook, p_srcRelativePath,
-                         p_destNotebook, p_destRelativePath);
-    }
-    return true;
-}
-
-int VFileList::removeFileInConfig(const QString &p_filePath)
-{
-    QString dirPath = VUtils::basePathFromPath(p_filePath);
-    QString fileName = VUtils::fileNameFromPath(p_filePath);
-    // Update current directory's config file to exclude this file
-    QJsonObject dirJson = VConfigManager::readDirectoryConfig(dirPath);
-    Q_ASSERT(!dirJson.isEmpty());
-    QJsonArray fileArray = dirJson["files"].toArray();
-    bool deleted = false;
-    int idx = -1;
-    for (int i = 0; i < fileArray.size(); ++i) {
-        QJsonObject ele = fileArray[i].toObject();
-        if (ele["name"].toString() == fileName) {
-            fileArray.removeAt(i);
-            deleted = true;
-            idx = i;
-            break;
-        }
-    }
-    if (!deleted) {
-        qWarning() << "error: fail to find" << fileName << "to delete";
-        return idx;
-    }
-    dirJson["files"] = fileArray;
-    if (!VConfigManager::writeDirectoryConfig(dirPath, dirJson)) {
-        qWarning() << "error: fail to update directory's configuration file to delete"
-                   << fileName;
-        return idx;
-    }
-    return idx;
-}
-
-// @index = -1, add it to the end of the list
-bool VFileList::addFileInConfig(const QString &p_filePath, int p_index)
-{
-    QString dirPath = VUtils::basePathFromPath(p_filePath);
-    QString fileName = VUtils::fileNameFromPath(p_filePath);
-
-    // Update current directory's config file to include this file
-    QJsonObject dirJson = VConfigManager::readDirectoryConfig(dirPath);
-    Q_ASSERT(!dirJson.isEmpty());
-    QJsonObject fileJson;
-    fileJson["name"] = fileName;
-    QJsonArray fileArray = dirJson["files"].toArray();
-    if (p_index == -1) {
-        p_index = fileArray.size();
-    }
-    fileArray.insert(p_index, fileJson);
-    dirJson["files"] = fileArray;
-    if (!VConfigManager::writeDirectoryConfig(dirPath, dirJson)) {
-        qWarning() << "error: fail to update directory's configuration file to add a new file"
-                   << fileName;
-        return false;
-    }
-
-    return true;
-}
-
-QJsonObject VFileList::readFileInConfig(const QString &p_filePath)
-{
-    QString dirPath = VUtils::basePathFromPath(p_filePath);
-    QString fileName = VUtils::fileNameFromPath(p_filePath);
-
-    QJsonObject dirJson = VConfigManager::readDirectoryConfig(dirPath);
-    Q_ASSERT(!dirJson.isEmpty());
-
-    qDebug() << "config" << p_filePath;
-    QJsonArray fileArray = dirJson["files"].toArray();
-    for (int i = 0; i < fileArray.size(); ++i) {
-        QJsonObject ele = fileArray[i].toObject();
-        if (ele["name"].toString() == fileName) {
-            return ele;
-        }
+    if (destFile) {
+        emit fileUpdated(destFile);
     }
-    return QJsonObject();
+    return destFile != NULL;
 }

+ 30 - 41
src/vfilelist.h

@@ -5,13 +5,16 @@
 #include <QJsonObject>
 #include <QFileInfo>
 #include <QDir>
+#include <QPointer>
+#include <QListWidgetItem>
 #include "vnotebook.h"
 #include "vconstants.h"
+#include "vdirectory.h"
+#include "vfile.h"
 
 class QAction;
 class VNote;
 class QListWidget;
-class QListWidgetItem;
 class QPushButton;
 class VEditArea;
 
@@ -19,69 +22,49 @@ class VFileList : public QWidget
 {
     Q_OBJECT
 public:
-    explicit VFileList(VNote *vnote, QWidget *parent = 0);
-    bool importFile(const QString &name);
+    explicit VFileList(QWidget *parent = 0);
+    bool importFile(const QString &p_srcFilePath);
     inline void setEditArea(VEditArea *editArea);
-    void fileInfo(const QString &p_notebook, const QString &p_relativePath);
-    void deleteFile(const QString &p_notebook, const QString &p_relativePath);
+    void fileInfo(VFile *p_file);
+    void deleteFile(VFile *p_file);
 
 signals:
-    void fileClicked(QJsonObject fileJson);
-    void fileDeleted(QJsonObject fileJson);
-    void fileCreated(QJsonObject fileJson);
-    void fileRenamed(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                     const QString &p_destNotebook, const QString &p_destRelativePath);
-    void directoryChanged(const QString &notebook, const QString &relativePath);
+    void fileClicked(VFile *p_file, OpenFileMode mode = OpenFileMode::Read);
+    void fileCreated(VFile *p_file, OpenFileMode mode = OpenFileMode::Read);
+    void fileUpdated(const VFile *p_file);
 
 private slots:
     void contextMenuRequested(QPoint pos);
     void handleItemClicked(QListWidgetItem *currentItem);
-    void curFileInfo();
-    void deleteCurFile();
+    void fileInfo();
+    // m_copiedFiles will keep the files's VFile.
     void copySelectedFiles(bool p_isCut = false);
     void cutSelectedFiles();
     void pasteFilesInCurDir();
+    void deleteFile();
 
 public slots:
-    void setDirectory(QJsonObject dirJson);
-    void handleDirectoryRenamed(const QString &notebook, const QString &oldRelativePath,
-                                const QString &newRelativePath);
+    void setDirectory(VDirectory *p_directory);
     void newFile();
 
 private:
     void setupUI();
     void updateFileList();
-    QListWidgetItem *insertFileListItem(QJsonObject fileJson, bool atFront = false);
+    QListWidgetItem *insertFileListItem(VFile *file, bool atFront = false);
     void removeFileListItem(QListWidgetItem *item);
     void initActions();
-    bool isConflictNameWithExisting(const QString &name);
-    QListWidgetItem *createFileAndUpdateList(const QString &name);
-    void deleteFileAndUpdateList(const QString &p_notebook,
-                                 const QString &p_relativePath);
-    void clearDirectoryInfo();
-    void convertFileType(const QString &notebook, const QString &fileRelativePath,
-                         DocType oldType, DocType newType);
-    QListWidgetItem *findItem(const QString &p_notebook, const QString &p_relativePath);
-    void deleteLocalImages(const QString &filePath);
+    QListWidgetItem *findItem(const VFile *p_file);
     void copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut);
-    void pasteFiles(const QString &p_notebook, const QString &p_dirRelativePath);
-    bool copyFile(const QString &p_srcNotebook, const QString &p_srcRelativePath,
-                  const QString &p_destNotebook, const QString &p_destRelativePath,
-                  bool p_isCut);
-    int removeFileInConfig(const QString &p_filePath);
-    bool addFileInConfig(const QString &p_filePath, int p_index);
-    QJsonObject readFileInConfig(const QString &p_filePath);
-
-    VNote *vnote;
-    QString notebook;
-    // Current directory's relative path
-    QString relativePath;
-    // Used for cache
-    QString rootPath;
+    void pasteFiles(VDirectory *p_destDir);
+    bool copyFile(VDirectory *p_destDir, const QString &p_destName, VFile *p_file, bool p_cut);
+    // New items have been added to direcotry. Update file list accordingly.
+    QVector<QListWidgetItem *> updateFileListAdded();
+    inline QPointer<VFile> getVFile(QListWidgetItem *p_item);
 
     VEditArea *editArea;
-
     QListWidget *fileList;
+    QPointer<VDirectory> m_directory;
+    QVector<QPointer<VFile> > m_copiedFiles;
 
     // Actions
     QAction *newFileAct;
@@ -97,4 +80,10 @@ inline void VFileList::setEditArea(VEditArea *editArea)
     this->editArea = editArea;
 }
 
+inline QPointer<VFile> VFileList::getVFile(QListWidgetItem *p_item)
+{
+    Q_ASSERT(p_item);
+    return p_item->data(Qt::UserRole).value<VFile *>();
+}
+
 #endif // VFILELIST_H

+ 0 - 10
src/vfilelocation.cpp

@@ -1,10 +0,0 @@
-#include "vfilelocation.h"
-
-VFileLocation::VFileLocation()
-{
-}
-
-VFileLocation::VFileLocation(const QString &p_notebook, const QString &p_relativePath)
-    : m_notebook(p_notebook), m_relativePath(p_relativePath)
-{
-}

+ 0 - 15
src/vfilelocation.h

@@ -1,15 +0,0 @@
-#ifndef VFILELOCATION_H
-#define VFILELOCATION_H
-
-#include <QString>
-
-class VFileLocation
-{
-public:
-    VFileLocation();
-    VFileLocation(const QString &p_notebook, const QString &p_relativePath);
-    QString m_notebook;
-    QString m_relativePath;
-};
-
-#endif // VFILELOCATION_H

+ 23 - 41
src/vmainwindow.cpp

@@ -35,7 +35,7 @@ void VMainWindow::setupUI()
 {
     QWidget *directoryPanel = setupDirectoryPanel();
 
-    fileList = new VFileList(vnote);
+    fileList = new VFileList();
     fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
 
     editArea = new VEditArea(vnote);
@@ -57,23 +57,14 @@ void VMainWindow::setupUI()
 
     connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
             fileList, &VFileList::setDirectory);
-    connect(directoryTree, &VDirectoryTree::directoryRenamed,
-            fileList, &VFileList::handleDirectoryRenamed);
-    connect(fileList, &VFileList::directoryChanged,
-            this, &VMainWindow::handleFileListDirectoryChanged);
-
     connect(fileList, &VFileList::fileClicked,
             editArea, &VEditArea::openFile);
-    connect(fileList, &VFileList::fileDeleted,
-            editArea, &VEditArea::closeFile);
     connect(fileList, &VFileList::fileCreated,
             editArea, &VEditArea::openFile);
+    connect(fileList, &VFileList::fileUpdated,
+            editArea, &VEditArea::handleFileUpdated);
     connect(editArea, &VEditArea::curTabStatusChanged,
             this, &VMainWindow::handleCurTabStatusChanged);
-    connect(directoryTree, &VDirectoryTree::directoryRenamed,
-            editArea, &VEditArea::handleDirectoryRenamed);
-    connect(fileList, &VFileList::fileRenamed,
-            editArea, &VEditArea::handleFileRenamed);
 
     connect(newNotebookBtn, &QPushButton::clicked,
             this, &VMainWindow::onNewNotebookBtnClicked);
@@ -433,10 +424,10 @@ void VMainWindow::setCurNotebookIndex(int index)
     }
     Q_ASSERT(index < vnote->getNotebooks().size());
     // Update directoryTree
-    QString notebook;
+    VNotebook *notebook = NULL;
     if (index > -1) {
         vconfig.setCurNotebookIndex(index);
-        notebook = vnote->getNotebooks()[index]->getName();
+        notebook = vnote->getNotebooks()[index];
         newRootDirAct->setEnabled(true);
     } else {
         newRootDirAct->setEnabled(false);
@@ -525,7 +516,8 @@ void VMainWindow::onNotebookInfoBtnClicked()
 void VMainWindow::importNoteFromFile()
 {
     static QString lastPath = QDir::homePath();
-    QStringList files = QFileDialog::getOpenFileNames(this,tr("Select files(HTML or Markdown) to be imported as notes"),
+    QStringList files = QFileDialog::getOpenFileNames(this,
+                                                      tr("Select files(HTML or Markdown) to be imported as notes"),
                                                       lastPath);
     if (files.isEmpty()) {
         return;
@@ -668,15 +660,15 @@ void VMainWindow::setRenderBackgroundColor(QAction *action)
     vnote->updateTemplate();
 }
 
-void VMainWindow::updateToolbarFromTabChage(bool empty, bool editMode, bool modifiable)
+void VMainWindow::updateToolbarFromTabChage(const VFile *p_file, bool p_editMode)
 {
-    if (empty || !modifiable) {
+    if (!p_file) {
         editNoteAct->setEnabled(false);
         saveExitAct->setVisible(false);
         discardExitAct->setVisible(false);
         saveNoteAct->setVisible(false);
         deleteNoteAct->setEnabled(false);
-    } else if (editMode) {
+    } else if (p_editMode) {
         editNoteAct->setEnabled(false);
         saveExitAct->setVisible(true);
         discardExitAct->setVisible(true);
@@ -690,29 +682,26 @@ void VMainWindow::updateToolbarFromTabChage(bool empty, bool editMode, bool modi
         deleteNoteAct->setEnabled(true);
     }
 
-    if (empty) {
-        noteInfoAct->setEnabled(false);
-    } else {
+    if (p_file) {
         noteInfoAct->setEnabled(true);
+    } else {
+        noteInfoAct->setEnabled(false);
     }
 }
 
-void VMainWindow::handleCurTabStatusChanged(const QString &notebook, const QString &relativePath,
-                                            bool editMode, bool modifiable, bool modified)
+void VMainWindow::handleCurTabStatusChanged(const VFile *p_file, bool p_editMode)
 {
-    updateToolbarFromTabChage(notebook.isEmpty(), editMode, modifiable);
+    updateToolbarFromTabChage(p_file, p_editMode);
 
     QString title;
-    if (!notebook.isEmpty()) {
-        title = QString("[%1] %2").arg(notebook).arg(relativePath);
-        if (modified) {
+    if (p_file) {
+        title = QString("[%1] %2").arg(p_file->retriveNotebook()).arg(p_file->retrivePath());
+        if (p_file->isModified()) {
             title.append('*');
         }
     }
     updateWindowTitle(title);
-
-    curEditNotebook = notebook;
-    curEditRelativePath = relativePath;
+    m_curFile = const_cast<VFile *>(p_file);
 }
 
 void VMainWindow::changePanelView(QAction *action)
@@ -751,15 +740,6 @@ void VMainWindow::changeSplitterView(int nrPanel)
     }
 }
 
-void VMainWindow::handleFileListDirectoryChanged(const QString &notebook, const QString &relativePath)
-{
-    if (relativePath.isEmpty()) {
-        newNoteAct->setEnabled(false);
-    } else {
-        newNoteAct->setEnabled(true);
-    }
-}
-
 void VMainWindow::updateWindowTitle(const QString &str)
 {
     QString title = "VNote";
@@ -771,12 +751,14 @@ void VMainWindow::updateWindowTitle(const QString &str)
 
 void VMainWindow::curEditFileInfo()
 {
-    fileList->fileInfo(curEditNotebook, curEditRelativePath);
+    Q_ASSERT(m_curFile);
+    fileList->fileInfo(m_curFile);
 }
 
 void VMainWindow::deleteCurNote()
 {
-    fileList->deleteFile(curEditNotebook, curEditRelativePath);
+    Q_ASSERT(m_curFile);
+    fileList->deleteFile(m_curFile);
 }
 
 void VMainWindow::closeEvent(QCloseEvent *event)

+ 6 - 8
src/vmainwindow.h

@@ -4,7 +4,9 @@
 #include <QMainWindow>
 #include <QVector>
 #include <QPair>
+#include <QPointer>
 #include <QString>
+#include "vfile.h"
 
 class QLabel;
 class QComboBox;
@@ -48,15 +50,13 @@ private slots:
     void setTabStopWidth(QAction *action);
     void setEditorBackgroundColor(QAction *action);
     void setRenderBackgroundColor(QAction *action);
-    void handleCurTabStatusChanged(const QString &notebook, const QString &relativePath,
-                                   bool editMode, bool modifiable, bool modified);
+    void handleCurTabStatusChanged(const VFile *p_file, bool p_editMode);
     void changePanelView(QAction *action);
-    void handleFileListDirectoryChanged(const QString &notebook, const QString &relativePath);
     void curEditFileInfo();
     void deleteCurNote();
 
 signals:
-    void curNotebookChanged(const QString &notebookName);
+    void curNotebookChanged(VNotebook *p_notebook);
 
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
@@ -74,16 +74,14 @@ private:
     void initEditorBackgroundMenu(QMenu *menu);
     void changeSplitterView(int nrPanel);
     void updateWindowTitle(const QString &str);
-    void updateToolbarFromTabChage(bool empty, bool editMode, bool modifiable);
+    void updateToolbarFromTabChage(const VFile *p_file, bool p_editMode);
     void saveStateAndGeometry();
     void restoreStateAndGeometry();
 
     // If true, comboBox changes will not trigger any signal out
     bool notebookComboMuted;
     VNote *vnote;
-
-    QString curEditNotebook;
-    QString curEditRelativePath;
+    QPointer<VFile> m_curFile;
 
     QLabel *notebookLabel;
     QLabel *directoryLabel;

+ 12 - 16
src/vmdeditoperations.cpp

@@ -9,13 +9,13 @@
 #include <QMessageBox>
 #include "vmdeditoperations.h"
 #include "dialog/vinsertimagedialog.h"
-#include "vnotefile.h"
 #include "utils/vutils.h"
 #include "vedit.h"
 #include "vdownloader.h"
+#include "vfile.h"
 
-VMdEditOperations::VMdEditOperations(VEdit *editor, VNoteFile *noteFile)
-    : VEditOperations(editor, noteFile)
+VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
+    : VEditOperations(p_editor, p_file)
 {
 }
 
@@ -26,13 +26,11 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
         return false;
     }
     VInsertImageDialog dialog(QObject::tr("Insert image from clipboard"), QObject::tr("image_title"),
-                              "", (QWidget *)editor);
+                              "", (QWidget *)m_editor);
     dialog.setBrowseable(false);
     dialog.setImage(image);
     if (dialog.exec() == QDialog::Accepted) {
-        insertImageFromQImage(dialog.getImageTitleInput(),
-                              QDir::cleanPath(QDir(noteFile->basePath).filePath("images")),
-                              image);
+        insertImageFromQImage(dialog.getImageTitleInput(), m_file->retriveImagePath(), image);
     }
     return true;
 }
@@ -48,7 +46,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
     bool ret = image.save(filePath);
     if (!ret) {
         QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
-                           QMessageBox::Ok, (QWidget *)editor);
+                           QMessageBox::Ok, (QWidget *)m_editor);
         msgBox.exec();
         return;
     }
@@ -56,7 +54,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
     QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
     insertTextAtCurPos(md);
 
-    editor->insertImage(fileName);
+    m_editor->insertImage(fileName);
 }
 
 void VMdEditOperations::insertImageFromPath(const QString &title,
@@ -71,7 +69,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title,
     if (!ret) {
         qWarning() << "error: fail to copy" << oriImagePath << "to" << filePath;
         QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
-                           QMessageBox::Ok, (QWidget *)editor);
+                           QMessageBox::Ok, (QWidget *)m_editor);
         msgBox.exec();
         return;
     }
@@ -79,7 +77,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title,
     QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
     insertTextAtCurPos(md);
 
-    editor->insertImage(fileName);
+    m_editor->insertImage(fileName);
 }
 
 bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
@@ -105,7 +103,7 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
     }
 
 
-    VInsertImageDialog dialog(title, QObject::tr("image_title"), imagePath, (QWidget *)editor);
+    VInsertImageDialog dialog(title, QObject::tr("image_title"), imagePath, (QWidget *)m_editor);
     dialog.setBrowseable(false);
     if (isLocal) {
         dialog.setImage(image);
@@ -118,12 +116,10 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
     }
     if (dialog.exec() == QDialog::Accepted) {
         if (isLocal) {
-            insertImageFromPath(dialog.getImageTitleInput(),
-                                QDir::cleanPath(QDir(noteFile->basePath).filePath("images")),
+            insertImageFromPath(dialog.getImageTitleInput(), m_file->retriveImagePath(),
                                 imagePath);
         } else {
-            insertImageFromQImage(dialog.getImageTitleInput(),
-                                QDir::cleanPath(QDir(noteFile->basePath).filePath("images")),
+            insertImageFromQImage(dialog.getImageTitleInput(), m_file->retriveImagePath(),
                                 dialog.getImage());
         }
     }

+ 1 - 1
src/vmdeditoperations.h

@@ -11,7 +11,7 @@
 class VMdEditOperations : public VEditOperations
 {
 public:
-    VMdEditOperations(VEdit *editor, VNoteFile *noteFile);
+    VMdEditOperations(VEdit *p_editor, VFile *p_file);
     bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
     bool insertURLFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
     bool insertImageFromURL(const QUrl &imageUrl);

+ 1 - 2
src/vnote.h

@@ -10,8 +10,7 @@
 #include <QHash>
 #include <QPalette>
 #include "vnotebook.h"
-
-enum OpenFileMode {Read = 0, Edit};
+#include "vconstants.h"
 
 class VNote : public QObject
 {

+ 13 - 6
src/vnotebook.cpp

@@ -1,14 +1,16 @@
 #include "vnotebook.h"
+#include "vdirectory.h"
+#include "utils/vutils.h"
 
-VNotebook::VNotebook(QObject *parent)
-    : QObject(parent)
+VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
+    : QObject(parent), m_name(name), m_path(path)
 {
-
+    m_rootDir = new VDirectory(this, VUtils::directoryNameFromPath(path));
 }
 
-VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
-    : QObject(parent), m_name(name), m_path(path)
+VNotebook::~VNotebook()
 {
+    delete m_rootDir;
 }
 
 QString VNotebook::getName() const
@@ -33,5 +35,10 @@ void VNotebook::setPath(const QString &path)
 
 void VNotebook::close(bool p_forced)
 {
-    //TODO
+    m_rootDir->close();
+}
+
+bool VNotebook::open()
+{
+    return m_rootDir->open();
 }

+ 16 - 1
src/vnotebook.h

@@ -4,13 +4,17 @@
 #include <QObject>
 #include <QString>
 
+class VDirectory;
+
 class VNotebook : public QObject
 {
     Q_OBJECT
 public:
-    VNotebook(QObject *parent = 0);
     VNotebook(const QString &name, const QString &path, QObject *parent = 0);
+    ~VNotebook();
 
+    // Open the root directory to load contents
+    bool open();
     // Close all the directory and files of this notebook.
     // If @p_forced, unsaved files will also be closed without a confirm.
     void close(bool p_forced);
@@ -19,10 +23,21 @@ public:
     QString getPath() const;
     void setName(const QString &name);
     void setPath(const QString &path);
+    inline VDirectory *getRootDir();
+
+signals:
+    void contentChanged();
 
 private:
     QString m_name;
     QString m_path;
+    // Parent is NULL for root directory
+    VDirectory *m_rootDir;
 };
 
+inline VDirectory *VNotebook::getRootDir()
+{
+    return m_rootDir;
+}
+
 #endif // VNOTEBOOK_H

+ 0 - 9
src/vnotefile.cpp

@@ -1,9 +0,0 @@
-#include "vnotefile.h"
-
-VNoteFile::VNoteFile(const QString &basePath, const QString &fileName,
-                     const QString &content, DocType docType, bool modifiable)
-    : basePath(basePath), fileName(fileName),
-      content(content), docType(docType), modifiable(modifiable)
-{
-
-}

+ 0 - 20
src/vnotefile.h

@@ -1,20 +0,0 @@
-#ifndef VNOTEFILE_H
-#define VNOTEFILE_H
-
-#include <QString>
-#include "vconstants.h"
-
-class VNoteFile
-{
-public:
-    VNoteFile(const QString &basePath, const QString &fileName, const QString &content,
-              DocType docType, bool modifiable);
-
-    QString basePath;
-    QString fileName;
-    QString content;
-    DocType docType;
-    bool modifiable;
-};
-
-#endif // VNOTEFILE_H