Browse Source

refactor VDirectoryTree

- Refine folder deletion logics;
- Refine folder copy/paste logics;
- Add folder sort logics;
Le Tan 8 years ago
parent
commit
3724eb35dd
13 changed files with 791 additions and 275 deletions
  1. 5 0
      src/dialog/vsortdialog.cpp
  2. 0 1
      src/vattachmentlist.cpp
  3. 1 0
      src/vconstants.h
  4. 152 47
      src/vdirectory.cpp
  5. 28 7
      src/vdirectory.h
  6. 449 180
      src/vdirectorytree.cpp
  7. 78 27
      src/vdirectorytree.h
  8. 5 6
      src/vfilelist.cpp
  9. 15 2
      src/vmainwindow.cpp
  10. 13 0
      src/vnote.cpp
  11. 5 0
      src/vnote.h
  12. 33 2
      src/vnotebook.cpp
  13. 7 3
      src/vnotebook.h

+ 5 - 0
src/dialog/vsortdialog.cpp

@@ -132,6 +132,11 @@ void VSortDialog::treeUpdated()
         m_treeWidget->resizeColumnToContents(i);
     }
 
+    QHeaderView *header = m_treeWidget->header();
+    if (header) {
+        header->setStretchLastSection(true);
+    }
+
     // We just need single level.
     int cnt = m_treeWidget->topLevelItemCount();
     for (int i = 0; i < cnt; ++i) {

+ 0 - 1
src/vattachmentlist.cpp

@@ -374,7 +374,6 @@ void VAttachmentList::sortItems()
     QTreeWidget *tree = dialog.getTreeWidget();
     tree->clear();
     tree->setColumnCount(1);
-    tree->header()->setStretchLastSection(true);
     QStringList headers;
     headers << tr("Name");
     tree->setHeaderLabels(headers);

+ 1 - 0
src/vconstants.h

@@ -19,6 +19,7 @@ namespace ClipboardConfig
     static const QString c_magic = "magic";
     static const QString c_isCut = "is_cut";
     static const QString c_files = "files";
+    static const QString c_dirs = "dirs";
 }
 
 enum class OpenFileMode {Read = 0, Edit};

+ 152 - 47
src/vdirectory.cpp

@@ -10,8 +10,8 @@
 extern VConfigManager *g_config;
 
 VDirectory::VDirectory(VNotebook *p_notebook,
+                       VDirectory *p_parent,
                        const QString &p_name,
-                       QObject *p_parent,
                        QDateTime p_createdTimeUtc)
     : QObject(p_parent),
       m_notebook(p_notebook),
@@ -45,7 +45,7 @@ bool VDirectory::open()
     QJsonArray dirJson = configJson[DirConfig::c_subDirectories].toArray();
     for (int i = 0; i < dirJson.size(); ++i) {
         QJsonObject dirItem = dirJson[i].toObject();
-        VDirectory *dir = new VDirectory(m_notebook, dirItem[DirConfig::c_name].toString(), this);
+        VDirectory *dir = new VDirectory(m_notebook, this, dirItem[DirConfig::c_name].toString());
         m_subDirs.append(dir);
     }
 
@@ -188,28 +188,37 @@ void VDirectory::addNotebookConfig(QJsonObject &p_json) const
     }
 }
 
-VDirectory *VDirectory::createSubDirectory(const QString &p_name)
+VDirectory *VDirectory::createSubDirectory(const QString &p_name,
+                                           QString *p_errMsg)
 {
     Q_ASSERT(!p_name.isEmpty());
-    // First open current directory
+
     if (!open()) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to open folder %1.").arg(m_name));
         return NULL;
     }
 
-    qDebug() << "create subfolder" << p_name << "in" << m_name;
+    QDir dir(fetchPath());
+    if (dir.exists(p_name)) {
+        VUtils::addErrMsg(p_errMsg, tr("%1 already exists in directory %2.")
+                                      .arg(p_name)
+                                      .arg(fetchPath()));
+        return NULL;
+    }
 
-    QString path = fetchPath();
-    QDir dir(path);
     if (!dir.mkdir(p_name)) {
-        qWarning() << "fail to create directory" << p_name << "under" << path;
+        VUtils::addErrMsg(p_errMsg, tr("Fail to create folder in %1.")
+                                      .arg(m_name));
         return NULL;
     }
 
     VDirectory *ret = new VDirectory(m_notebook,
-                                     p_name,
                                      this,
+                                     p_name,
                                      QDateTime::currentDateTimeUtc());
     if (!ret->writeToConfig()) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to write configuration of folder %1.")
+                                      .arg(p_name));
         dir.rmdir(p_name);
         delete ret;
         return NULL;
@@ -217,11 +226,13 @@ VDirectory *VDirectory::createSubDirectory(const QString &p_name)
 
     m_subDirs.append(ret);
     if (!writeToConfig()) {
-        VConfigManager::deleteDirectoryConfig(QDir(path).filePath(p_name));
-        dir.rmdir(p_name);
+        VUtils::addErrMsg(p_errMsg, tr("Fail to write configuration of folder %1.")
+                                      .arg(p_name));
+
+        QDir subdir(dir.filePath(p_name));
+        subdir.removeRecursively();
         delete ret;
         m_subDirs.removeLast();
-
         return NULL;
     }
 
@@ -399,13 +410,13 @@ bool VDirectory::addSubDirectory(VDirectory *p_dir, int p_index)
 
 VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index)
 {
-    if (!open()) {
+    if (!open() || p_name.isEmpty()) {
         return NULL;
     }
 
     VDirectory *dir = new VDirectory(m_notebook,
-                                     p_name,
                                      this,
+                                     p_name,
                                      QDateTime::currentDateTimeUtc());
     if (!dir) {
         return NULL;
@@ -419,24 +430,45 @@ VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index)
     return dir;
 }
 
-void VDirectory::deleteSubDirectory(VDirectory *p_subDir, bool p_skipRecycleBin)
+bool VDirectory::deleteDirectory(bool p_skipRecycleBin, QString *p_errMsg)
 {
-    Q_ASSERT(p_subDir->getNotebook() == m_notebook);
+    Q_ASSERT(!m_opened);
+    Q_ASSERT(parent());
 
-    QString dirPath = p_subDir->fetchPath();
+    // Delete the entire directory.
+    bool ret = true;
+    QString dirPath = fetchPath();
+    if (!VUtils::deleteDirectory(m_notebook, dirPath, p_skipRecycleBin)) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to delete the directory %1.").arg(dirPath));
+        ret = false;
+    }
 
-    p_subDir->close();
+    return ret;
+}
 
-    removeSubDirectory(p_subDir);
+bool VDirectory::deleteDirectory(VDirectory *p_dir, bool p_skipRecycleBin, QString *p_errMsg)
+{
+    p_dir->close();
 
-    // Delete the entire directory.
-    if (!VUtils::deleteDirectory(m_notebook, dirPath, p_skipRecycleBin)) {
-        qWarning() << "fail to remove directory" << dirPath << "recursively";
-    } else {
-        qDebug() << "deleted" << dirPath << (p_skipRecycleBin ? "from disk" : "to recycle bin");
+    bool ret = true;
+
+    QString name = p_dir->getName();
+    QString path = p_dir->fetchPath();
+
+    if (!p_dir->deleteDirectory(p_skipRecycleBin, p_errMsg)) {
+        ret = false;
+    }
+
+    VDirectory *paDir = p_dir->getParentDirectory();
+    Q_ASSERT(paDir);
+    if (!paDir->removeSubDirectory(p_dir)) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to remove the folder from the folder configuration."));
+        ret = false;
     }
 
-    delete p_subDir;
+    delete p_dir;
+
+    return ret;
 }
 
 bool VDirectory::removeSubDirectory(VDirectory *p_dir)
@@ -452,8 +484,6 @@ bool VDirectory::removeSubDirectory(VDirectory *p_dir)
         return false;
     }
 
-    qDebug() << "folder" << p_dir->getName() << "removed from folder" << m_name;
-
     return true;
 }
 
@@ -504,35 +534,48 @@ bool VDirectory::rename(const QString &p_name)
     return true;
 }
 
-// Copy @p_srcDir to be a sub-directory of @p_destDir with name @p_destName.
-VDirectory *VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
-                                      VDirectory *p_srcDir, bool p_cut)
+bool VDirectory::copyDirectory(VDirectory *p_destDir,
+                               const QString &p_destName,
+                               VDirectory *p_dir,
+                               bool p_isCut,
+                               VDirectory **p_targetDir,
+                               QString *p_errMsg)
 {
-    QString srcPath = QDir::cleanPath(p_srcDir->fetchPath());
+    bool ret = true;
+    *p_targetDir = NULL;
+
+    QString srcPath = QDir::cleanPath(p_dir->fetchPath());
     QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
     if (VUtils::equalPath(srcPath, destPath)) {
-        return p_srcDir;
+        *p_targetDir = p_dir;
+        return false;
     }
 
-    VDirectory *srcParentDir = p_srcDir->getParentDirectory();
-
-    // Copy the directory
-    if (!VUtils::copyDirectory(srcPath, destPath, p_cut)) {
-        return NULL;
+    if (!p_destDir->isOpened()) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to open target folder."));
+        return false;
     }
 
-    // Handle VDirectory
-    int index = -1;
-    VDirectory *destDir = NULL;
-    if (p_cut) {
-        // Remove the directory from config
-        srcParentDir->removeSubDirectory(p_srcDir);
+    QString opStr = p_isCut ? tr("cut") : tr("copy");
+    VDirectory *paDir = p_dir->getParentDirectory();
 
-        p_srcDir->setName(p_destName);
+    Q_ASSERT(paDir->isOpened());
 
+    // Copy the directory.
+    if (!VUtils::copyDirectory(srcPath, destPath, p_isCut)) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to %1 the folder.").arg(opStr));
+        qWarning() << "fail to" << opStr << "the folder directory" << srcPath << "to" << destPath;
+        return false;
+    }
+
+    // Add directory to VDirectory.
+    VDirectory *destDir = NULL;
+    if (p_isCut) {
+        paDir->removeSubDirectory(p_dir);
+        p_dir->setName(p_destName);
         // Add the directory to new dir's config
-        if (p_destDir->addSubDirectory(p_srcDir, index)) {
-            destDir = p_srcDir;
+        if (p_destDir->addSubDirectory(p_dir, -1)) {
+            destDir = p_dir;
         } else {
             destDir = NULL;
         }
@@ -540,7 +583,15 @@ VDirectory *VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_de
         destDir = p_destDir->addSubDirectory(p_destName, -1);
     }
 
-    return destDir;
+    if (!destDir) {
+        VUtils::addErrMsg(p_errMsg, tr("Fail to add the folder to target folder's configuration."));
+        return false;
+    }
+
+    qDebug() << "copyDirectory:" << p_dir << "to" << destDir;
+
+    *p_targetDir = destDir;
+    return ret;
 }
 
 void VDirectory::setExpanded(bool p_expanded)
@@ -591,6 +642,39 @@ VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath)
     return file;
 }
 
+VDirectory *VDirectory::tryLoadDirectory(QStringList &p_filePath)
+{
+    qDebug() << "directory" << m_name << "tryLoadDirectory()" << p_filePath.join("/");
+    if (p_filePath.isEmpty()) {
+        return NULL;
+    }
+
+    bool opened = isOpened();
+    if (!open()) {
+        return NULL;
+    }
+
+#if defined(Q_OS_WIN)
+    bool caseSensitive = false;
+#else
+    bool caseSensitive = true;
+#endif
+
+    VDirectory *dir = findSubDirectory(p_filePath.at(0), caseSensitive);
+    if (dir) {
+        if (p_filePath.size() > 1) {
+            p_filePath.removeFirst();
+            dir = dir->tryLoadDirectory(p_filePath);
+        }
+    }
+
+    if (!dir && !opened) {
+        close();
+    }
+
+    return dir;
+}
+
 bool VDirectory::sortFiles(const QVector<int> &p_sortedIdx)
 {
     V_ASSERT(m_opened);
@@ -611,3 +695,24 @@ bool VDirectory::sortFiles(const QVector<int> &p_sortedIdx)
 
     return ret;
 }
+
+bool VDirectory::sortSubDirectories(const QVector<int> &p_sortedIdx)
+{
+    V_ASSERT(m_opened);
+    V_ASSERT(p_sortedIdx.size() == m_subDirs.size());
+
+    auto ori = m_subDirs;
+
+    for (int i = 0; i < p_sortedIdx.size(); ++i) {
+        m_subDirs[i] = ori[p_sortedIdx[i]];
+    }
+
+    bool ret = true;
+    if (!writeToConfig()) {
+        qWarning() << "fail to reorder sub-directories in config" << p_sortedIdx;
+        m_subDirs = ori;
+        ret = false;
+    }
+
+    return ret;
+}

+ 28 - 7
src/vdirectory.h

@@ -17,13 +17,16 @@ class VDirectory : public QObject
     Q_OBJECT
 public:
     VDirectory(VNotebook *p_notebook,
+               VDirectory *p_parent,
                const QString &p_name,
-               QObject *p_parent = 0,
                QDateTime p_createdTimeUtc = QDateTime());
 
     bool open();
     void close();
-    VDirectory *createSubDirectory(const QString &p_name);
+
+    // Create a sub-directory with name @p_name.
+    VDirectory *createSubDirectory(const QString &p_name,
+                                   QString *p_errMsg = NULL);
 
     // Returns the VDirectory with the name @p_name directly in this directory.
     VDirectory *findSubDirectory(const QString &p_name, bool p_caseSensitive);
@@ -36,9 +39,6 @@ public:
 
     VNoteFile *createFile(const QString &p_name);
 
-    // Remove and delete subdirectory @p_subDir.
-    void deleteSubDirectory(VDirectory *p_subDir, bool p_skipRecycleBin = false);
-
     // Remove the file in the config and m_files without deleting it in the disk.
     // It won't change the parent of @p_file to enable it find its path.
     bool removeFile(VNoteFile *p_file);
@@ -58,10 +58,17 @@ public:
     // Rename current directory to @p_name.
     bool rename(const QString &p_name);
 
-    static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
-                                     VDirectory *p_srcDir, bool p_cut);
+    // Copy @p_dir as a sub-directory of @p_destDir with the new name @p_destName.
+    // Return a directory representing the destination directory after copy/cut.
+    static bool copyDirectory(VDirectory *p_destDir,
+                              const QString &p_destName,
+                              VDirectory *p_dir,
+                              bool p_isCut,
+                              VDirectory **p_targetDir,
+                              QString *p_errMsg = NULL);
 
     const QVector<VDirectory *> &getSubDirs() const;
+
     const QString &getName() const;
     void setName(const QString &p_name);
     bool isOpened() const;
@@ -96,11 +103,22 @@ public:
     // Try to load file given relative path @p_filePath.
     VNoteFile *tryLoadFile(QStringList &p_filePath);
 
+    // Try to load directory given relative path @p_filePath.
+    VDirectory *tryLoadDirectory(QStringList &p_filePath);
+
     QDateTime getCreatedTimeUtc() const;
 
     // Reorder files in m_files by index.
     bool sortFiles(const QVector<int> &p_sortedIdx);
 
+    // Reorder sub-directories in m_subDirs by index.
+    bool sortSubDirectories(const QVector<int> &p_sortedIdx);
+
+    // Delete directory @p_dir.
+    static bool deleteDirectory(VDirectory *p_dir,
+                                bool p_skipRecycleBin = false,
+                                QString *p_errMsg = NULL);
+
 private:
     // Get the path of @p_dir recursively
     QString fetchPath(const VDirectory *p_dir) const;
@@ -121,6 +139,9 @@ private:
     // Add the directory in the config and m_subDirs. If @p_index is -1, add it at the end.
     bool addSubDirectory(VDirectory *p_dir, int p_index);
 
+    // Delete this directory in disk.
+    bool deleteDirectory(bool p_skipRecycleBin = false, QString *p_errMsg = NULL);
+
     // Notebook containing this folder.
     QPointer<VNotebook> m_notebook;
 

+ 449 - 180
src/vdirectorytree.cpp

@@ -9,6 +9,7 @@
 #include "veditarea.h"
 #include "vconfigmanager.h"
 #include "vmainwindow.h"
+#include "dialog/vsortdialog.h"
 
 extern VMainWindow *g_mainWin;
 
@@ -20,13 +21,14 @@ const QString VDirectoryTree::c_copyShortcutSequence = "Ctrl+C";
 const QString VDirectoryTree::c_cutShortcutSequence = "Ctrl+X";
 const QString VDirectoryTree::c_pasteShortcutSequence = "Ctrl+V";
 
-VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
+VDirectoryTree::VDirectoryTree(QWidget *parent)
     : QTreeWidget(parent), VNavigationMode(),
-      vnote(vnote), m_editArea(NULL)
+      m_editArea(NULL)
 {
     setColumnCount(1);
     setHeaderHidden(true);
     setContextMenuPolicy(Qt::CustomContextMenu);
+
     initShortcuts();
     initActions();
 
@@ -67,7 +69,7 @@ void VDirectoryTree::initShortcuts()
     pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
     connect(pasteShortcut, &QShortcut::activated,
             this, [this](){
-                pasteDirectoriesInCurDir();
+                pasteDirectoriesFromClipboard();
             });
 }
 
@@ -88,7 +90,7 @@ void VDirectoryTree::initActions()
                                tr("&Delete"), this);
     deleteDirAct->setToolTip(tr("Delete selected folder"));
     connect(deleteDirAct, &QAction::triggered,
-            this, &VDirectoryTree::deleteDirectory);
+            this, &VDirectoryTree::deleteSelectedDirectory);
 
     dirInfoAct = new QAction(QIcon(":/resources/icons/dir_info.svg"),
                              tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), this);
@@ -112,7 +114,7 @@ void VDirectoryTree::initActions()
                            tr("&Paste\t%1").arg(VUtils::getShortcutText(c_pasteShortcutSequence)), this);
     pasteAct->setToolTip(tr("Paste folders in this folder"));
     connect(pasteAct, &QAction::triggered,
-            this, &VDirectoryTree::pasteDirectoriesInCurDir);
+            this, &VDirectoryTree::pasteDirectoriesFromClipboard);
 
     m_openLocationAct = new QAction(tr("&Open Folder Location"), this);
     m_openLocationAct->setToolTip(tr("Open the folder containing this folder in operating system"));
@@ -123,66 +125,71 @@ void VDirectoryTree::initActions()
     m_reloadAct->setToolTip(tr("Reload the content of this folder (or notebook) from disk"));
     connect(m_reloadAct, &QAction::triggered,
             this, &VDirectoryTree::reloadFromDisk);
+
+    m_sortAct = new QAction(QIcon(":/resources/icons/sort.svg"),
+                            tr("&Sort"),
+                            this);
+    m_sortAct->setToolTip(tr("Sort folders in this folder/notebook manually"));
+    connect(m_sortAct, SIGNAL(triggered(bool)),
+            this, SLOT(sortItems()));
 }
 
 void VDirectoryTree::setNotebook(VNotebook *p_notebook)
 {
-    setFocus();
     if (m_notebook == p_notebook) {
         return;
     }
 
-    if (m_notebook) {
-        // Disconnect
-        disconnect((VNotebook *)m_notebook, &VNotebook::contentChanged,
-                   this, &VDirectoryTree::updateDirectoryTree);
-    }
+    clear();
     m_notebook = p_notebook;
-    if (m_notebook) {
-        connect((VNotebook *)m_notebook, &VNotebook::contentChanged,
-                this, &VDirectoryTree::updateDirectoryTree);
-    } else {
-        clear();
+    if (!m_notebook) {
         return;
     }
+
     if (!m_notebook->open()) {
-        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+        VUtils::showMessage(QMessageBox::Warning,
+                            tr("Warning"),
                             tr("Fail to open notebook <span style=\"%1\">%2</span>.")
-                              .arg(g_config->c_dataTextStyle).arg(m_notebook->getName()),
-                            tr("Please check if path <span style=\"%1\">%2</span> exists.")
-                              .arg(g_config->c_dataTextStyle).arg(m_notebook->getPath()),
-                            QMessageBox::Ok, QMessageBox::Ok, this);
-        clear();
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(m_notebook->getName()),
+                            tr("Please check if the notebook's root folder <span style=\"%1\">%2</span> exists.")
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(m_notebook->getPath()),
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            this);
         return;
     }
+
     updateDirectoryTree();
 }
 
-void VDirectoryTree::fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name,
-                                  VDirectory *p_directory, const QIcon &p_icon)
+void VDirectoryTree::fillTreeItem(QTreeWidgetItem *p_item, VDirectory *p_directory)
 {
-    p_item.setText(0, p_name);
-    p_item.setToolTip(0, p_name);
-    p_item.setData(0, Qt::UserRole, QVariant::fromValue(p_directory));
-    p_item.setIcon(0, p_icon);
+    int col = 0;
+    QString name = p_directory->getName();
+    p_item->setText(col, name);
+    p_item->setToolTip(col, name);
+    p_item->setData(col, Qt::UserRole, QVariant::fromValue(p_directory));
+    p_item->setIcon(col, QIcon(":/resources/icons/dir_item.svg"));
 }
 
 void VDirectoryTree::updateDirectoryTree()
 {
     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);
 
-        fillTreeItem(*item, dir->getName(), dir,
-                     QIcon(":/resources/icons/dir_item.svg"));
+        fillTreeItem(item, dir);
 
         buildSubTree(item, 1);
     }
 
-    if (!restoreCurrentItem()) {
+    if (!restoreCurrentItem() && topLevelItemCount() > 0) {
         setCurrentItem(topLevelItem(0));
     }
 }
@@ -191,8 +198,7 @@ bool VDirectoryTree::restoreCurrentItem()
 {
     auto it = m_notebookCurrentDirMap.find(m_notebook);
     if (it != m_notebookCurrentDirMap.end()) {
-        bool rootDirectory;
-        QTreeWidgetItem *item = findVDirectory(it.value(), rootDirectory);
+        QTreeWidgetItem *item = findVDirectory(it.value());
         if (item) {
             setCurrentItem(item);
             return true;
@@ -208,20 +214,28 @@ void VDirectoryTree::buildSubTree(QTreeWidgetItem *p_parent, int p_depth)
         return;
     }
 
+    Q_ASSERT(p_parent);
+
     VDirectory *dir = getVDirectory(p_parent);
     if (!dir->open()) {
-        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+        VUtils::showMessage(QMessageBox::Warning,
+                            tr("Warning"),
                             tr("Fail to open folder <span style=\"%1\">%2</span>.")
-                              .arg(g_config->c_dataTextStyle).arg(dir->getName()),
-                            tr("Please check if path <span style=\"%1\">%2</span> exists.")
-                              .arg(g_config->c_dataTextStyle).arg(dir->fetchPath()),
-                            QMessageBox::Ok, QMessageBox::Ok, this);
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(dir->getName()),
+                            tr("Please check if directory <span style=\"%1\">%2</span> exists.")
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(dir->fetchPath()),
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            this);
         return;
     }
 
     if (p_parent->childCount() > 0) {
         // This directory has been built before. Try its children directly.
-        for (int i = 0; i < p_parent->childCount(); ++i) {
+        int cnt = p_parent->childCount();
+        for (int i = 0; i < cnt; ++i) {
             buildSubTree(p_parent->child(i), p_depth -1);
         }
     } else {
@@ -229,10 +243,7 @@ void VDirectoryTree::buildSubTree(QTreeWidgetItem *p_parent, int p_depth)
         for (int i = 0; i < subDirs.size(); ++i) {
             VDirectory *subDir = subDirs[i];
             QTreeWidgetItem *item = new QTreeWidgetItem(p_parent);
-
-            fillTreeItem(*item, subDir->getName(), subDir,
-                         QIcon(":/resources/icons/dir_item.svg"));
-
+            fillTreeItem(item, subDir);
             buildSubTree(item, p_depth - 1);
         }
     }
@@ -244,18 +255,23 @@ void VDirectoryTree::buildSubTree(QTreeWidgetItem *p_parent, int p_depth)
 
 void VDirectoryTree::handleItemCollapsed(QTreeWidgetItem *p_item)
 {
-    VDirectory *dir = getVDirectory(p_item);
-    dir->setExpanded(false);
+    if (p_item) {
+        VDirectory *dir = getVDirectory(p_item);
+        dir->setExpanded(false);
+    }
 }
 
 void VDirectoryTree::handleItemExpanded(QTreeWidgetItem *p_item)
 {
-    updateChildren(p_item);
-    VDirectory *dir = getVDirectory(p_item);
-    dir->setExpanded(true);
+    if (p_item) {
+        buildChildren(p_item);
+
+        VDirectory *dir = getVDirectory(p_item);
+        dir->setExpanded(true);
+    }
 }
 
-void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item)
+void VDirectoryTree::buildChildren(QTreeWidgetItem *p_item)
 {
     Q_ASSERT(p_item);
     int nrChild = p_item->childCount();
@@ -273,7 +289,7 @@ void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item)
     }
 }
 
-void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
+void VDirectoryTree::updateItemDirectChildren(QTreeWidgetItem *p_item)
 {
     QPointer<VDirectory> parentDir;
     if (p_item) {
@@ -281,6 +297,7 @@ void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
     } else {
         parentDir = m_notebook->getRootDir();
     }
+
     const QVector<VDirectory *> &dirs = parentDir->getSubDirs();
 
     QHash<VDirectory *, QTreeWidgetItem *> itemDirMap;
@@ -302,6 +319,7 @@ void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
                 takeTopLevelItem(topIdx);
                 insertTopLevelItem(i, item);
             }
+
             itemDirMap.remove(dir);
         } else {
             // Insert a new item
@@ -310,10 +328,12 @@ void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
             } else {
                 item = new QTreeWidgetItem(this);
             }
-            fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg"));
+
+            fillTreeItem(item, dir);
             buildSubTree(item, 1);
         }
-        expandItemTree(item);
+
+        expandSubTree(item);
     }
 
     // Delete items without corresponding VDirectory
@@ -325,6 +345,7 @@ void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
             int topIdx = indexOfTopLevelItem(item);
             takeTopLevelItem(topIdx);
         }
+
         delete item;
     }
 }
@@ -343,27 +364,40 @@ void VDirectoryTree::contextMenuRequested(QPoint pos)
     if (!item) {
         // Context menu on the free space of the QTreeWidget
         menu.addAction(newRootDirAct);
+
+        if (topLevelItemCount() > 1) {
+            menu.addAction(m_sortAct);
+        }
     } else {
         // Context menu on a QTreeWidgetItem
         if (item->parent()) {
             // Low-level item
             menu.addAction(newSubDirAct);
+
+            if (item->parent()->childCount() > 1) {
+                menu.addAction(m_sortAct);
+            }
         } else {
             // Top-level item
             menu.addAction(newRootDirAct);
             menu.addAction(newSubDirAct);
+
+            if (topLevelItemCount() > 1) {
+                menu.addAction(m_sortAct);
+            }
         }
-        menu.addAction(deleteDirAct);
+
         menu.addSeparator();
+        menu.addAction(deleteDirAct);
         menu.addAction(copyAct);
         menu.addAction(cutAct);
     }
 
-    if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyDir
-        && !m_copiedDirs.isEmpty()) {
+    if (pasteAvailable()) {
         if (!item) {
             menu.addSeparator();
         }
+
         menu.addAction(pasteAct);
     }
 
@@ -383,30 +417,39 @@ void VDirectoryTree::newSubDirectory()
     if (!m_notebook) {
         return;
     }
+
     QTreeWidgetItem *curItem = currentItem();
     if (!curItem) {
         return;
     }
+
     VDirectory *curDir = getVDirectory(curItem);
 
     QString info = tr("Create a subfolder in <span style=\"%1\">%2</span>.")
-                     .arg(g_config->c_dataTextStyle).arg(curDir->getName());
+                     .arg(g_config->c_dataTextStyle)
+                     .arg(curDir->getName());
     QString defaultName("new_folder");
     defaultName = VUtils::getFileNameWithSequence(curDir->fetchPath(), defaultName);
     VNewDirDialog dialog(tr("Create Folder"), info, defaultName, curDir, this);
     if (dialog.exec() == QDialog::Accepted) {
         QString name = dialog.getNameInput();
-
-        VDirectory *subDir = curDir->createSubDirectory(name);
+        QString msg;
+        VDirectory *subDir = curDir->createSubDirectory(name, &msg);
         if (!subDir) {
-            VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
-                                tr("Fail to create folder <span style=\"%1\">%2</span>.")
-                                  .arg(g_config->c_dataTextStyle).arg(name), "",
-                                QMessageBox::Ok, QMessageBox::Ok, this);
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to create subfolder <span style=\"%1\">%2</span>.")
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(name),
+                                msg,
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
             return;
         }
 
-        updateItemChildren(curItem);
+        updateItemDirectChildren(curItem);
+
         locateDirectory(subDir);
     }
 }
@@ -416,53 +459,94 @@ void VDirectoryTree::newRootDirectory()
     if (!m_notebook) {
         return;
     }
-    QString info = tr("Create a root folder in notebook <span style=\"%1\">%2</span>.")
-                     .arg(g_config->c_dataTextStyle).arg(m_notebook->getName());
+
     VDirectory *rootDir = m_notebook->getRootDir();
+
+    QString info = tr("Create a root folder in notebook <span style=\"%1\">%2</span>.")
+                     .arg(g_config->c_dataTextStyle)
+                     .arg(m_notebook->getName());
     QString defaultName("new_folder");
     defaultName = VUtils::getFileNameWithSequence(rootDir->fetchPath(), defaultName);
     VNewDirDialog dialog(tr("Create Root Folder"), info, defaultName, rootDir, this);
     if (dialog.exec() == QDialog::Accepted) {
         QString name = dialog.getNameInput();
-
-        VDirectory *subDir = rootDir->createSubDirectory(name);
-        if (!subDir) {
-            VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
-                                tr("Fail to create folder <span style=\"%1\">%2</span>.")
-                                  .arg(g_config->c_dataTextStyle).arg(name), "",
-                                QMessageBox::Ok, QMessageBox::Ok, this);
+        QString msg;
+        VDirectory *dir = rootDir->createSubDirectory(name, &msg);
+        if (!dir) {
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to create root folder <span style=\"%1\">%2</span>.")
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(name),
+                                msg,
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
             return;
         }
 
-        updateItemChildren(NULL);
-        locateDirectory(subDir);
+        updateItemDirectChildren(NULL);
+
+        locateDirectory(dir);
     }
 }
 
-void VDirectoryTree::deleteDirectory()
+void VDirectoryTree::deleteSelectedDirectory()
 {
+    Q_ASSERT(selectedItems().size() <= 1);
+
     QTreeWidgetItem *curItem = currentItem();
     if (!curItem) {
         return;
     }
+
     VDirectory *curDir = getVDirectory(curItem);
-    int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+    int ret = VUtils::showMessage(QMessageBox::Warning,
+                                  tr("Warning"),
                                   tr("Are you sure to delete folder <span style=\"%1\">%2</span>?")
-                                    .arg(g_config->c_dataTextStyle).arg(curDir->getName()),
+                                    .arg(g_config->c_dataTextStyle)
+                                    .arg(curDir->getName()),
                                   tr("<span style=\"%1\">WARNING</span>: "
                                      "VNote will delete the whole directory "
                                      "<span style=\"%2\">%3</span>."
                                      "You could find deleted files in the recycle bin "
-                                     "of this notebook.<br>The operation is IRREVERSIBLE!")
-                                    .arg(g_config->c_warningTextStyle).arg(g_config->c_dataTextStyle).arg(curDir->fetchPath()),
+                                     "of this folder.<br>"
+                                     "The operation is IRREVERSIBLE!")
+                                    .arg(g_config->c_warningTextStyle)
+                                    .arg(g_config->c_dataTextStyle)
+                                    .arg(curDir->fetchPath()),
                                   QMessageBox::Ok | QMessageBox::Cancel,
-                                  QMessageBox::Ok, this, MessageBoxType::Danger);
+                                  QMessageBox::Ok,
+                                  this,
+                                  MessageBoxType::Danger);
+
     if (ret == QMessageBox::Ok) {
+        int nrDeleted = 1;
         m_editArea->closeFile(curDir, true);
-        VDirectory *parentDir = curDir->getParentDirectory();
-        Q_ASSERT(parentDir);
-        parentDir->deleteSubDirectory(curDir);
+
+        // Remove the item from the tree.
         delete curItem;
+
+        QString msg;
+        QString dirName = curDir->getName();
+        QString dirPath = curDir->fetchPath();
+        if (!VDirectory::deleteDirectory(curDir, false, &msg)) {
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to delete folder <span style=\"%1\">%2</span>.<br>"
+                                   "Please check <span style=\"%1\">%3</span> and manually delete it.")
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(dirName)
+                                  .arg(dirPath),
+                                msg,
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
+        } else {
+            g_mainWin->showStatusMessage(tr("%1 %2 deleted")
+                                           .arg(nrDeleted)
+                                           .arg(nrDeleted > 1 ? tr("folders") : tr("folder")));
+        }
     }
 }
 
@@ -486,10 +570,13 @@ void VDirectoryTree::editDirectoryInfo()
     }
 
     VDirectory *curDir = getVDirectory(curItem);
-    VDirectory *parentDir = curDir->getParentDirectory();
     QString curName = curDir->getName();
 
-    VDirInfoDialog dialog(tr("Folder Information"), "", curDir, parentDir, this);
+    VDirInfoDialog dialog(tr("Folder Information"),
+                          "",
+                          curDir,
+                          curDir->getParentDirectory(),
+                          this);
     if (dialog.exec() == QDialog::Accepted) {
         QString name = dialog.getNameInput();
         if (name == curName) {
@@ -497,15 +584,20 @@ void VDirectoryTree::editDirectoryInfo()
         }
 
         if (!curDir->rename(name)) {
-            VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
                                 tr("Fail to rename folder <span style=\"%1\">%2</span>.")
-                                  .arg(g_config->c_dataTextStyle).arg(curName), "",
-                                QMessageBox::Ok, QMessageBox::Ok, this);
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(curName),
+                                "",
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
             return;
         }
 
-        curItem->setText(0, name);
-        curItem->setToolTip(0, name);
+        fillTreeItem(curItem, curDir);
+
         emit directoryUpdated(curDir);
     }
 }
@@ -613,36 +705,35 @@ void VDirectoryTree::reloadFromDisk()
     }
 }
 
-void VDirectoryTree::copySelectedDirectories(bool p_cut)
+void VDirectoryTree::copySelectedDirectories(bool p_isCut)
 {
     QList<QTreeWidgetItem *> items = selectedItems();
     if (items.isEmpty()) {
         return;
     }
+
     QJsonArray dirs;
-    m_copiedDirs.clear();
     for (int i = 0; i < items.size(); ++i) {
         VDirectory *dir = getVDirectory(items[i]);
-        QJsonObject dirJson;
-        dirJson["notebook"] = dir->getNotebookName();
-        dirJson["path"] = dir->fetchPath();
-        dirs.append(dirJson);
-
-        m_copiedDirs.append(dir);
+        dirs.append(dir->fetchPath());
     }
 
-    copyDirectoryInfoToClipboard(dirs, p_cut);
-}
-
-void VDirectoryTree::copyDirectoryInfoToClipboard(const QJsonArray &p_dirs, bool p_cut)
-{
     QJsonObject clip;
-    clip["operation"] = (int)ClipboardOpType::CopyDir;
-    clip["is_cut"] = p_cut;
-    clip["sources"] = p_dirs;
+    clip[ClipboardConfig::c_magic] = getNewMagic();
+    clip[ClipboardConfig::c_type] = (int)ClipboardOpType::CopyDir;
+    clip[ClipboardConfig::c_isCut] = p_isCut;
+    clip[ClipboardConfig::c_dirs] = dirs;
 
     QClipboard *clipboard = QApplication::clipboard();
     clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
+
+    qDebug() << "copied directories info" << clipboard->text();
+
+    int cnt = dirs.size();
+    g_mainWin->showStatusMessage(tr("%1 %2 %3")
+                                   .arg(cnt)
+                                   .arg(cnt > 1 ? tr("folders") : tr("folder"))
+                                   .arg(p_isCut ? tr("cut") : tr("copied")));
 }
 
 void VDirectoryTree::cutSelectedDirectories()
@@ -650,50 +741,160 @@ void VDirectoryTree::cutSelectedDirectories()
     copySelectedDirectories(true);
 }
 
-void VDirectoryTree::pasteDirectoriesInCurDir()
+void VDirectoryTree::pasteDirectoriesFromClipboard()
 {
-    if (m_copiedDirs.isEmpty()) {
+    if (!pasteAvailable()) {
         return;
     }
 
+    QJsonObject obj = VUtils::clipboardToJson();
+    QJsonArray dirs = obj[ClipboardConfig::c_dirs].toArray();
+    bool isCut = obj[ClipboardConfig::c_isCut].toBool();
+    QVector<QString> dirsToPaste(dirs.size());
+    for (int i = 0; i < dirs.size(); ++i) {
+        dirsToPaste[i] = dirs[i].toString();
+    }
+
+    VDirectory *destDir;
     QTreeWidgetItem *item = currentItem();
-    VDirectory *destDir = m_notebook->getRootDir();
     if (item) {
         destDir = getVDirectory(item);
+    } else {
+        destDir = m_notebook->getRootDir();
     }
 
-    pasteDirectories(destDir);
+    pasteDirectories(destDir, dirsToPaste, isCut);
+
+    QClipboard *clipboard = QApplication::clipboard();
+    clipboard->clear();
 }
 
-void VDirectoryTree::pasteDirectories(VDirectory *p_destDir)
+void VDirectoryTree::pasteDirectories(VDirectory *p_destDir,
+                                      const QVector<QString> &p_dirs,
+                                      bool p_isCut)
 {
-    QClipboard *clipboard = QApplication::clipboard();
-    QString text = clipboard->text();
-    QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
-    Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyDir);
-    bool isCut = clip["is_cut"].toBool();
+    if (!p_destDir || p_dirs.isEmpty()) {
+        return;
+    }
 
     int nrPasted = 0;
-    for (int i = 0; i < m_copiedDirs.size(); ++i) {
-        QPointer<VDirectory> srcDir = m_copiedDirs[i];
-        if (!srcDir || srcDir == p_destDir) {
+    for (int i = 0; i < p_dirs.size(); ++i) {
+        VDirectory *dir = g_vnote->getInternalDirectory(p_dirs[i]);
+        if (!dir) {
+            qWarning() << "Copied dir is not an internal folder" << p_dirs[i];
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to paste folder <span style=\"%1\">%2</span>.")
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(p_dirs[i]),
+                                tr("VNote could not find this folder in any notebook."),
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
+
             continue;
         }
 
-        QString dirName = srcDir->getName();
-        VDirectory *srcParentDir = srcDir->getParentDirectory();
-        if (srcParentDir == p_destDir && !isCut) {
-            // Copy and paste in the same directory.
-            // Rename it to xx_copy
-            dirName = VUtils::generateCopiedDirName(srcParentDir->fetchPath(), dirName);
+        if (dir == p_destDir) {
+            continue;
         }
-        if (copyDirectory(p_destDir, dirName, srcDir, isCut)) {
-            nrPasted++;
+
+        QString dirName = dir->getName();
+        VDirectory *paDir = dir->getParentDirectory();
+        if (paDir == p_destDir) {
+            if (p_isCut) {
+                continue;
+            }
+
+            // Copy and paste in the same folder.
+            // Rename it to xxx_copy.
+            dirName = VUtils::generateCopiedDirName(paDir->fetchPath(), dirName);
+        } else {
+            // Rename it to xxx_copy if needed.
+            dirName = VUtils::generateCopiedDirName(p_destDir->fetchPath(), dirName);
+        }
+
+        QString msg;
+        VDirectory *destDir = NULL;
+        bool ret = VDirectory::copyDirectory(p_destDir,
+                                             dirName,
+                                             dir,
+                                             p_isCut,
+                                             &destDir,
+                                             &msg);
+        if (!ret) {
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to copy folder <span style=\"%1\">%2</span>.")
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(p_dirs[i]),
+                                msg,
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
+        }
+
+        if (destDir) {
+            ++nrPasted;
+
+            // Update QTreeWidget.
+            bool isWidget;
+            QTreeWidgetItem *destItem = findVDirectory(p_destDir, &isWidget);
+            if (destItem || isWidget) {
+                updateItemDirectChildren(destItem);
+            }
+
+            if (p_isCut) {
+                QTreeWidgetItem *srcItem = findVDirectory(paDir, &isWidget);
+                if (srcItem || isWidget) {
+                    updateItemDirectChildren(srcItem);
+                }
+            }
+
+            // Broadcast this update
+            emit directoryUpdated(destDir);
         }
     }
-    qDebug() << "pasted" << nrPasted << "files successfully";
-    clipboard->clear();
-    m_copiedDirs.clear();
+
+    qDebug() << "pasted" << nrPasted << "directories";
+    if (nrPasted > 0) {
+        g_mainWin->showStatusMessage(tr("%1 %2 pasted")
+                                       .arg(nrPasted)
+                                       .arg(nrPasted > 1 ? tr("folders") : tr("folder")));
+    }
+
+    getNewMagic();
+}
+
+bool VDirectoryTree::pasteAvailable() const
+{
+    QJsonObject obj = VUtils::clipboardToJson();
+    if (obj.isEmpty()) {
+        return false;
+    }
+
+    if (!obj.contains(ClipboardConfig::c_type)) {
+        return false;
+    }
+
+    ClipboardOpType type = (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
+    if (type != ClipboardOpType::CopyDir) {
+        return false;
+    }
+
+    if (!obj.contains(ClipboardConfig::c_magic)
+        || !obj.contains(ClipboardConfig::c_isCut)
+        || !obj.contains(ClipboardConfig::c_dirs)) {
+        return false;
+    }
+
+    int magic = obj[ClipboardConfig::c_magic].toInt();
+    if (!checkMagic(magic)) {
+        return false;
+    }
+
+    QJsonArray dirs = obj[ClipboardConfig::c_dirs].toArray();
+    return !dirs.isEmpty();
 }
 
 void VDirectoryTree::mousePressEvent(QMouseEvent *event)
@@ -702,6 +903,7 @@ void VDirectoryTree::mousePressEvent(QMouseEvent *event)
     if (!item) {
         setCurrentItem(NULL);
     }
+
     QTreeWidget::mousePressEvent(event);
 }
 
@@ -768,63 +970,25 @@ void VDirectoryTree::keyPressEvent(QKeyEvent *event)
     QTreeWidget::keyPressEvent(event);
 }
 
-bool VDirectoryTree::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
-                                   VDirectory *p_srcDir, bool p_cut)
+QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool *p_widget)
 {
-    qDebug() << "copy" << p_srcDir->getName() << "to" << p_destDir->getName()
-             << "as" << p_destName;
-    QString srcName = p_srcDir->getName();
-    QString srcPath = QDir::cleanPath(p_srcDir->fetchPath());
-    QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
-    if (VUtils::equalPath(srcPath, destPath)) {
-        return true;
+    if (p_widget) {
+        *p_widget = false;
     }
 
-    VDirectory *srcParentDir = p_srcDir->getParentDirectory();
-    VDirectory *destDir = VDirectory::copyDirectory(p_destDir, p_destName, p_srcDir, p_cut);
-
-    if (destDir) {
-        // Update QTreeWidget
-        bool isWidget;
-        QTreeWidgetItem *destItem = findVDirectory(p_destDir, isWidget);
-        if (destItem || isWidget) {
-            updateItemChildren(destItem);
-        }
-
-        if (p_cut) {
-            QTreeWidgetItem *srcItem = findVDirectory(srcParentDir, isWidget);
-            if (srcItem || isWidget) {
-                updateItemChildren(srcItem);
-            }
-        }
-
-        // Broadcast this update
-        emit directoryUpdated(destDir);
-    } else {
-        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
-                            tr("Fail to copy folder <span style=\"%1\">%2</span>.")
-                              .arg(g_config->c_dataTextStyle).arg(srcName),
-                            tr("Please check if there already exists a folder with the same name."),
-                            QMessageBox::Ok, QMessageBox::Ok, this);
-    }
-
-    return destDir;
-}
-
-QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool &p_widget)
-{
-    p_widget = false;
     if (!p_dir) {
         return NULL;
     } else if (p_dir->getNotebookName() != m_notebook->getName()) {
         return NULL;
     } else if (p_dir == m_notebook->getRootDir()) {
-        p_widget = true;
+        if (p_widget) {
+            *p_widget = true;
+        }
         return NULL;
     }
 
     bool isWidget;
-    QTreeWidgetItem *pItem = findVDirectory(p_dir->getParentDirectory(), isWidget);
+    QTreeWidgetItem *pItem = findVDirectory(p_dir->getParentDirectory(), &isWidget);
     if (pItem) {
         // Iterate all its children to find the match.
         int nrChild = pItem->childCount();
@@ -844,29 +1008,32 @@ QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool &p
             }
         }
     }
+
     return NULL;
 }
 
 bool VDirectoryTree::locateDirectory(const VDirectory *p_directory)
 {
     if (p_directory) {
-        qDebug() << "locate folder" << p_directory->fetchPath()
-                 << "in" << m_notebook->getName();
         if (p_directory->getNotebook() != m_notebook) {
             return false;
         }
+
         QTreeWidgetItem *item = expandToVDirectory(p_directory);
         if (item) {
             setCurrentItem(item);
         }
+
         return item;
     }
+
     return false;
 }
 
 QTreeWidgetItem *VDirectoryTree::expandToVDirectory(const VDirectory *p_directory)
 {
-    if (!p_directory || p_directory->getNotebook() != m_notebook
+    if (!p_directory
+        || p_directory->getNotebook() != m_notebook
         || p_directory == m_notebook->getRootDir()) {
         return NULL;
     }
@@ -885,10 +1052,12 @@ QTreeWidgetItem *VDirectoryTree::expandToVDirectory(const VDirectory *p_director
         if (!pItem) {
             return NULL;
         }
+
         int nrChild = pItem->childCount();
         if (nrChild == 0) {
             buildSubTree(pItem, 1);
         }
+
         nrChild = pItem->childCount();
         for (int i = 0; i < nrChild; ++i) {
             QTreeWidgetItem *item = pItem->child(i);
@@ -897,10 +1066,11 @@ QTreeWidgetItem *VDirectoryTree::expandToVDirectory(const VDirectory *p_director
             }
         }
     }
+
     return NULL;
 }
 
-void VDirectoryTree::expandItemTree(QTreeWidgetItem *p_item)
+void VDirectoryTree::expandSubTree(QTreeWidgetItem *p_item)
 {
     if (!p_item) {
         return;
@@ -909,7 +1079,7 @@ void VDirectoryTree::expandItemTree(QTreeWidgetItem *p_item)
     VDirectory *dir = getVDirectory(p_item);
     int nrChild = p_item->childCount();
     for (int i = 0; i < nrChild; ++i) {
-        expandItemTree(p_item->child(i));
+        expandSubTree(p_item->child(i));
     }
 
     if (dir->isExpanded()) {
@@ -1002,6 +1172,7 @@ QList<QTreeWidgetItem *> VDirectoryTree::getVisibleItems() const
             }
         }
     }
+
     return items;
 }
 
@@ -1019,5 +1190,103 @@ QList<QTreeWidgetItem *> VDirectoryTree::getVisibleChildItems(const QTreeWidgetI
             }
         }
     }
+
     return items;
 }
+
+int VDirectoryTree::getNewMagic()
+{
+    m_magicForClipboard = (int)QDateTime::currentDateTime().toTime_t();
+    m_magicForClipboard |= qrand();
+
+    return m_magicForClipboard;
+}
+
+bool VDirectoryTree::checkMagic(int p_magic) const
+{
+    return m_magicForClipboard == p_magic;
+}
+
+void VDirectoryTree::sortItems()
+{
+    if (!m_notebook) {
+        return;
+    }
+
+    QTreeWidgetItem *item = currentItem();
+    if (item && item->parent()) {
+        sortItems(getVDirectory(item->parent()));
+    } else {
+        sortItems(m_notebook->getRootDir());
+    }
+
+    if (item) {
+        setCurrentItem(item);
+    }
+}
+
+void VDirectoryTree::sortItems(VDirectory *p_dir)
+{
+    if (!p_dir) {
+        return;
+    }
+
+    const QVector<VDirectory *> &dirs = p_dir->getSubDirs();
+    if (dirs.size() < 2) {
+        return;
+    }
+
+    bool isNotebook = p_dir->parent() == NULL;
+
+    VSortDialog dialog(tr("Sort Folders"),
+                       tr("Sort folders in %1 <span style=\"%2\">%3</span> "
+                          "in the configuration file.")
+                         .arg(isNotebook ? tr("notebook") : tr("folder"))
+                         .arg(g_config->c_dataTextStyle)
+                         .arg(isNotebook ? p_dir->getNotebook()->getName() : p_dir->getName()),
+                       this);
+    QTreeWidget *tree = dialog.getTreeWidget();
+    tree->clear();
+    tree->setColumnCount(2);
+    QStringList headers;
+    headers << tr("Name") << tr("Created Time");
+    tree->setHeaderLabels(headers);
+
+    for (int i = 0; i < dirs.size(); ++i) {
+        const VDirectory *dir = dirs[i];
+        Q_ASSERT(dir->isOpened());
+        QString createdTime = VUtils::displayDateTime(dir->getCreatedTimeUtc().toLocalTime());
+        QStringList cols;
+        cols << dir->getName() << createdTime;
+        QTreeWidgetItem *item = new QTreeWidgetItem(tree, cols);
+
+        item->setData(0, Qt::UserRole, i);
+    }
+
+    dialog.treeUpdated();
+
+    if (dialog.exec()) {
+        QVector<QVariant> data = dialog.getSortedData();
+        Q_ASSERT(data.size() == dirs.size());
+        QVector<int> sortedIdx(data.size(), -1);
+        for (int i = 0; i < data.size(); ++i) {
+            sortedIdx[i] = data[i].toInt();
+        }
+
+        qDebug() << "sort dirs" << sortedIdx;
+        if (!p_dir->sortSubDirectories(sortedIdx)) {
+            VUtils::showMessage(QMessageBox::Warning,
+                                tr("Warning"),
+                                tr("Fail to sort folders in %1 <span style=\"%2\">%3</span>.")
+                                  .arg(isNotebook ? tr("notebook") : tr("folder"))
+                                  .arg(g_config->c_dataTextStyle)
+                                  .arg(isNotebook ? p_dir->getNotebook()->getName() : p_dir->getName()),
+                                "",
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                this);
+        }
+
+        updateItemDirectChildren(findVDirectory(p_dir));
+    }
+}

+ 78 - 27
src/vdirectorytree.h

@@ -12,7 +12,6 @@
 #include "vnotebook.h"
 #include "vnavigationmode.h"
 
-class VNote;
 class VEditArea;
 class QLabel;
 
@@ -20,10 +19,14 @@ class VDirectoryTree : public QTreeWidget, public VNavigationMode
 {
     Q_OBJECT
 public:
-    explicit VDirectoryTree(VNote *vnote, QWidget *parent = 0);
-    inline void setEditArea(VEditArea *p_editArea);
+    explicit VDirectoryTree(QWidget *parent = 0);
+
+    void setEditArea(VEditArea *p_editArea);
+
+    // Locate to the item representing @p_directory.
     bool locateDirectory(const VDirectory *p_directory);
-    inline const VNotebook *currentNotebook() const;
+
+    const VNotebook *currentNotebook() const;
 
     // Implementations for VNavigationMode.
     void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
@@ -33,12 +36,17 @@ public:
 
 signals:
     void currentDirectoryChanged(VDirectory *p_directory);
+
     void directoryUpdated(const VDirectory *p_directory);
 
 public slots:
+    // Set directory tree to display a given notebook @p_notebook.
     void setNotebook(VNotebook *p_notebook);
+
+    // Create a root folder.
     void newRootDirectory();
-    void deleteDirectory();
+
+    // View and edit info about directory.
     void editDirectoryInfo();
 
     // Clear and re-build the whole directory tree.
@@ -46,75 +54,117 @@ public slots:
     void updateDirectoryTree();
 
 private slots:
+    // Set the state of expansion of the directory.
     void handleItemExpanded(QTreeWidgetItem *p_item);
+
+    // Set the state of expansion of the directory.
     void handleItemCollapsed(QTreeWidgetItem *p_item);
+
     void contextMenuRequested(QPoint pos);
+
+    // Directory selected folder.
+    // Currently only support single selected item.
+    void deleteSelectedDirectory();
+
+    // Create sub-directory of current item's directory.
     void newSubDirectory();
+
+    // Current tree item changed.
     void currentDirectoryItemChanged(QTreeWidgetItem *currentItem);
-    void copySelectedDirectories(bool p_cut = false);
+
+    // Copy selected directories.
+    // Will put a Json string into the clipboard which contains the information
+    // about copied directories.
+    void copySelectedDirectories(bool p_isCut = false);
+
     void cutSelectedDirectories();
-    void pasteDirectoriesInCurDir();
+
+    // Paste directories from clipboard as sub-directories of current item.
+    void pasteDirectoriesFromClipboard();
+
+    // Open the folder's parent directory in system's file browser.
     void openDirectoryLocation() const;
 
     // Reload the content of current directory.
     void reloadFromDisk();
 
+    // Sort sub-folders of current item's folder.
+    void sortItems();
+
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
+
     void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
 
 private:
-    // Build the subtree of @p_parent recursively to the depth @p_depth.
-    // Item @p_parent must not be built before.
-    // Will expand the item if the corresponding directory was expanded before.
-    // @p_depth: valid only when greater than 0.
-    void updateDirectoryTreeOne(QTreeWidgetItem *p_parent, int p_depth);
-
     // Build the subtree of @p_parent recursively to the depth @p_depth.
     // @p_depth: negative - infinite levels.
     // Will expand the item if the corresponding directory was expanded before.
+    // Will treat items with children as items having been built before.
     void buildSubTree(QTreeWidgetItem *p_parent, int p_depth);
 
-    // Fill the content of a tree item.
-    void fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name,
-                      VDirectory *p_directory, const QIcon &p_icon);
+    // Fill the content of a tree item according to @p_directory.
+    void fillTreeItem(QTreeWidgetItem *p_item, VDirectory *p_directory);
 
     void initShortcuts();
 
     void initActions();
 
     // Update @p_item's direct children only: deleted, added, renamed.
-    void updateItemChildren(QTreeWidgetItem *p_item);
+    void updateItemDirectChildren(QTreeWidgetItem *p_item);
+
     // Find the corresponding item of @p_dir;
     // Return's NULL if no item is found and it is the root directory if @p_widget is true.
-    QTreeWidgetItem *findVDirectory(const VDirectory *p_dir, bool &p_widget);
-    inline QPointer<VDirectory> getVDirectory(QTreeWidgetItem *p_item) const;
-    void copyDirectoryInfoToClipboard(const QJsonArray &p_dirs, bool p_cut);
-    void pasteDirectories(VDirectory *p_destDir);
-    bool copyDirectory(VDirectory *p_destDir, const QString &p_destName,
-                       VDirectory *p_srcDir, bool p_cut);
+    QTreeWidgetItem *findVDirectory(const VDirectory *p_dir, bool *p_widget = NULL);
+
+    QPointer<VDirectory> getVDirectory(QTreeWidgetItem *p_item) const;
+
+    // Paste @p_dirs as sub-directory of @p_destDir.
+    void pasteDirectories(VDirectory *p_destDir,
+                          const QVector<QString> &p_dirs,
+                          bool p_isCut);
 
     // Build the subtree of @p_item's children if it has not been built yet.
-    void updateChildren(QTreeWidgetItem *p_item);
+    // We need to fill the children before showing a item to get a correct render.
+    void buildChildren(QTreeWidgetItem *p_item);
 
     // Expand/create the directory tree nodes to @p_directory.
     QTreeWidgetItem *expandToVDirectory(const VDirectory *p_directory);
 
     // Expand the currently-built subtree of @p_item according to VDirectory.isExpanded().
-    void expandItemTree(QTreeWidgetItem *p_item);
+    void expandSubTree(QTreeWidgetItem *p_item);
 
     QList<QTreeWidgetItem *> getVisibleItems() const;
+
     QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
+
+    // We use a map to save and restore current directory of each notebook.
+    // Try to restore current directory after changing notebook.
+    // Return false if no cache item found for current notebook.
     bool restoreCurrentItem();
 
-    VNote *vnote;
+    // Generate new magic to m_magicForClipboard.
+    int getNewMagic();
+
+    // Check if @p_magic equals to m_magicForClipboard.
+    bool checkMagic(int p_magic) const;
+
+    // Check if clipboard contains valid info to paste as directories.
+    bool pasteAvailable() const;
+
+    // Sort sub-directories of @p_dir.
+    void sortItems(VDirectory *p_dir);
+
     QPointer<VNotebook> m_notebook;
-    QVector<QPointer<VDirectory> > m_copiedDirs;
+
     VEditArea *m_editArea;
 
     // Each notebook's current item's VDirectory.
     QHash<VNotebook *, VDirectory *> m_notebookCurrentDirMap;
 
+    // Magic number for clipboard operations.
+    int m_magicForClipboard;
+
     // Actions
     QAction *newRootDirAct;
     QAction *newSiblingDirAct;
@@ -125,6 +175,7 @@ private:
     QAction *cutAct;
     QAction *pasteAct;
     QAction *m_openLocationAct;
+    QAction *m_sortAct;
 
     // Reload content from disk.
     QAction *m_reloadAct;

+ 5 - 6
src/vfilelist.cpp

@@ -702,15 +702,16 @@ void VFileList::pasteFilesFromClipboard()
     }
 
     pasteFiles(m_directory, filesToPaste, isCut);
+
+    QClipboard *clipboard = QApplication::clipboard();
+    clipboard->clear();
 }
 
 void VFileList::pasteFiles(VDirectory *p_destDir,
                            const QVector<QString> &p_files,
                            bool p_isCut)
 {
-    QClipboard *clipboard = QApplication::clipboard();
     if (!p_destDir || p_files.isEmpty()) {
-        clipboard->clear();
         return;
     }
 
@@ -721,7 +722,7 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
             qWarning() << "Copied file is not an internal note" << p_files[i];
             VUtils::showMessage(QMessageBox::Warning,
                                 tr("Warning"),
-                                tr("Fail to copy note <span style=\"%1\">%2</span>.")
+                                tr("Fail to paste note <span style=\"%1\">%2</span>.")
                                   .arg(g_config->c_dataTextStyle)
                                   .arg(p_files[i]),
                                 tr("VNote could not find this note in any notebook."),
@@ -794,7 +795,7 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
         }
     }
 
-    qDebug() << "copy" << nrPasted << "files";
+    qDebug() << "pasted" << nrPasted << "files";
     if (nrPasted > 0) {
         g_mainWin->showStatusMessage(tr("%1 %2 pasted")
                                        .arg(nrPasted)
@@ -802,7 +803,6 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
     }
 
     updateFileList();
-    clipboard->clear();
     getNewMagic();
 }
 
@@ -1021,7 +1021,6 @@ void VFileList::sortItems()
     QTreeWidget *tree = dialog.getTreeWidget();
     tree->clear();
     tree->setColumnCount(3);
-    tree->header()->setStretchLastSection(true);
     QStringList headers;
     headers << tr("Name") << tr("Created Time") << tr("Modified Time");
     tree->setHeaderLabels(headers);

+ 15 - 2
src/vmainwindow.cpp

@@ -177,7 +177,7 @@ QWidget *VMainWindow::setupDirectoryPanel()
     notebookSelector->setProperty("NotebookPanel", true);
     notebookSelector->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
 
-    directoryTree = new VDirectoryTree(vnote);
+    directoryTree = new VDirectoryTree;
     directoryTree->setProperty("NotebookPanel", true);
 
     QVBoxLayout *nbLayout = new QVBoxLayout;
@@ -193,7 +193,11 @@ QWidget *VMainWindow::setupDirectoryPanel()
     nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
 
     connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
-            directoryTree, &VDirectoryTree::setNotebook);
+            this, [this](VNotebook *p_notebook) {
+                directoryTree->setNotebook(p_notebook);
+                directoryTree->setFocus();
+            });
+
     connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
             this, &VMainWindow::handleCurrentNotebookChanged);
 
@@ -501,6 +505,14 @@ void VMainWindow::initHelpMenu()
                 editArea->openFile(file, OpenFileMode::Read);
             });
 
+    QAction *wikiAct = new QAction(tr("&Wiki"), this);
+    wikiAct->setToolTip(tr("View VNote's wiki on Github"));
+    connect(wikiAct, &QAction::triggered,
+            this, [this]() {
+                QString url("https://github.com/tamlok/vnote/wiki");
+                QDesktopServices::openUrl(url);
+            });
+
     QAction *updateAct = new QAction(tr("Check For &Updates"), this);
     updateAct->setToolTip(tr("Check for updates of VNote"));
     connect(updateAct, &QAction::triggered,
@@ -539,6 +551,7 @@ void VMainWindow::initHelpMenu()
 
     helpMenu->addAction(shortcutAct);
     helpMenu->addAction(mdGuideAct);
+    helpMenu->addAction(wikiAct);
     helpMenu->addAction(updateAct);
     helpMenu->addAction(starAct);
     helpMenu->addAction(feedbackAct);

+ 13 - 0
src/vnote.cpp

@@ -325,3 +325,16 @@ VNoteFile *VNote::getInternalFile(const QString &p_path)
     return file;
 }
 
+VDirectory *VNote::getInternalDirectory(const QString &p_path)
+{
+    VDirectory *dir = NULL;
+    for (auto & nb : m_notebooks) {
+        dir = nb->tryLoadDirectory(p_path);
+        if (dir) {
+            break;
+        }
+    }
+
+    return dir;
+
+}

+ 5 - 0
src/vnote.h

@@ -88,6 +88,11 @@ public:
     // Otherwise, returns NULL.
     VNoteFile *getInternalFile(const QString &p_path);
 
+    // Given the path of a folder, try to find it in all notebooks.
+    // Returns a VDirectory struct if it is a folder in one notebook.
+    // Otherwise, returns NULL.
+    VDirectory *getInternalDirectory(const QString &p_path);
+
 public slots:
     void updateTemplate();
 

+ 33 - 2
src/vnotebook.cpp

@@ -14,8 +14,8 @@ VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
     m_path = QDir::cleanPath(path);
     m_recycleBinFolder = g_config->getRecycleBinFolder();
     m_rootDir = new VDirectory(this,
-                               VUtils::directoryNameFromPath(path),
                                NULL,
+                               VUtils::directoryNameFromPath(path),
                                QDateTime::currentDateTimeUtc());
 }
 
@@ -202,7 +202,7 @@ bool VNotebook::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
         QVector<VDirectory *> subdirs = rootDir->getSubDirs();
         for (auto dir : subdirs) {
             // Skip recycle bin.
-            rootDir->deleteSubDirectory(dir, true);
+            VDirectory::deleteDirectory(dir, true);
         }
 
         // Delete the recycle bin.
@@ -280,6 +280,37 @@ VNoteFile *VNotebook::tryLoadFile(const QString &p_path)
     return NULL;
 }
 
+VDirectory *VNotebook::tryLoadDirectory(const QString &p_path)
+{
+    QFileInfo fi(p_path);
+    Q_ASSERT(fi.isAbsolute());
+    if (!fi.exists()) {
+        return NULL;
+    }
+
+    QStringList filePath;
+    if (VUtils::splitPathInBasePath(m_path, p_path, filePath)) {
+        if (filePath.isEmpty()) {
+            return NULL;
+        }
+
+        bool opened = isOpened();
+        if (!open()) {
+            return NULL;
+        }
+
+        VDirectory *dir = m_rootDir->tryLoadDirectory(filePath);
+
+        if (!dir && !opened) {
+            close();
+        }
+
+        return dir;
+    }
+
+    return NULL;
+}
+
 const QString &VNotebook::getImageFolder() const
 {
     if (m_imageFolder.isEmpty()) {

+ 7 - 3
src/vnotebook.h

@@ -36,6 +36,13 @@ public:
     // if @p_path is not inside this notebook.
     VNoteFile *tryLoadFile(const QString &p_path);
 
+    // Try to load the directory @p_path.
+    // Returns the corresponding VDirectory struct if @p_path is a folder inside this notebook.
+    // Otherwise, returns NULL.
+    // If notebook is not opened currently, it will open itself and close itself
+    // if @p_path is not inside this notebook.
+    VDirectory *tryLoadDirectory(const QString &p_path);
+
     const QString &getName() const;
     const QString &getPath() const;
 
@@ -86,9 +93,6 @@ public:
     // Need to check if this notebook has been opened.
     QDateTime getCreatedTimeUtc();
 
-signals:
-    void contentChanged();
-
 private:
     // Serialize current instance to json.
     QJsonObject toConfigJson() const;