Browse Source

support copy and paste directory

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 years ago
parent
commit
02d5300821

+ 66 - 0
src/utils/vutils.cpp

@@ -202,6 +202,54 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
     return true;
 }
 
+// Copy @p_srcDirPath to be @p_destDirPath.
+bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
+{
+    QString srcPath = QDir::cleanPath(p_srcDirPath);
+    QString destPath = QDir::cleanPath(p_destDirPath);
+    if (srcPath == destPath) {
+        return true;
+    }
+
+    // Make a directory
+    QDir parentDir(VUtils::basePathFromPath(p_destDirPath));
+    QString dirName = VUtils::fileNameFromPath(p_destDirPath);
+    if (!parentDir.mkdir(dirName)) {
+        qWarning() << QString("failed to create target directory %1: already exists").arg(p_destDirPath);
+        return false;
+    }
+
+    // Handle sub-dirs recursively and copy files.
+    QDir srcDir(p_srcDirPath);
+    QDir destDir(p_destDirPath);
+    Q_ASSERT(srcDir.exists() && destDir.exists());
+    QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
+                                               | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+    for (int i = 0; i < nodes.size(); ++i) {
+        const QFileInfo &fileInfo = nodes.at(i);
+        QString name = fileInfo.fileName();
+        if (fileInfo.isDir()) {
+            if (!copyDirectory(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
+                return false;
+            }
+        } else {
+            Q_ASSERT(fileInfo.isFile());
+            if (!copyFile(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
+                return false;
+            }
+        }
+    }
+
+    // Delete the src dir if p_isCut
+    if (p_isCut) {
+        if (!srcDir.removeRecursively()) {
+            qWarning() << "failed to remove directory" << p_srcDirPath;
+            return false;
+        }
+    }
+    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)
 {
@@ -236,3 +284,21 @@ QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &
     }
     return name;
 }
+
+QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
+{
+    QDir dir(p_parentDirPath);
+    QString name = p_dirName;
+    QString dirPath = dir.filePath(name);
+    int index = 0;
+    while (QDir(dirPath).exists()) {
+        QString seq;
+        if (index > 0) {
+            seq = QString::number(index);
+        }
+        index++;
+        name = QString("%1_copy%2").arg(p_dirName).arg(seq);
+        dirPath = dir.filePath(name);
+    }
+    return name;
+}

+ 2 - 0
src/utils/vutils.h

@@ -21,6 +21,7 @@ public:
     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 QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
     static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
     static bool isMarkdown(const QString &fileName);
     static inline QString directoryNameFromPath(const QString& path);
@@ -30,6 +31,7 @@ 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 bool copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, 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);

+ 133 - 27
src/vdirectory.cpp

@@ -126,15 +126,7 @@ VDirectory *VDirectory::createSubDirectory(const QString &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)) {
+    if (!createSubDirectoryInConfig(p_name)) {
         VConfigManager::deleteDirectoryConfig(QDir(path).filePath(p_name));
         dir.rmdir(p_name);
         return NULL;
@@ -145,6 +137,26 @@ VDirectory *VDirectory::createSubDirectory(const QString &p_name)
     return ret;
 }
 
+bool VDirectory::createSubDirectoryInConfig(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 subDirArray = dirJson["sub_directories"].toArray();
+    if (p_index == -1) {
+        subDirArray.append(itemJson);
+    } else {
+        subDirArray.insert(p_index, itemJson);
+    }
+    dirJson["sub_directories"] = subDirArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        return false;
+    }
+    return true;
+}
+
 VDirectory *VDirectory::findSubDirectory(const QString &p_name)
 {
     if (!m_opened && !open()) {
@@ -171,6 +183,22 @@ VFile *VDirectory::findFile(const QString &p_name)
     return NULL;
 }
 
+bool VDirectory::containsFile(const VFile *p_file) const
+{
+    if (!p_file) {
+        return false;
+    }
+    QObject *paDir = p_file->parent();
+    while (paDir) {
+        if (paDir == this) {
+            return true;
+        } else {
+            paDir = paDir->parent();
+        }
+    }
+    return false;
+}
+
 VFile *VDirectory::createFile(const QString &p_name)
 {
     Q_ASSERT(!p_name.isEmpty());
@@ -254,24 +282,77 @@ VFile *VDirectory::addFile(const QString &p_name, int p_index)
     return file;
 }
 
-void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
+VDirectory *VDirectory::addSubDirectory(VDirectory *p_dir, int p_index)
 {
     if (!open()) {
-        return;
+        return NULL;
+    }
+    if (!createSubDirectoryInConfig(p_dir->getName(), p_index)) {
+        return NULL;
+    }
+    if (p_index == -1) {
+        m_subDirs.append(p_dir);
+    } else {
+        m_subDirs.insert(p_index, p_dir);
+    }
+    p_dir->setParent(this);
+    return p_dir;
+}
+
+VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index)
+{
+    if (!open()) {
+        return NULL;
+    }
+    if (!createSubDirectoryInConfig(p_name, p_index)) {
+        return NULL;
+    }
+    VDirectory *dir = new VDirectory(m_notebook, p_name, this);
+    if (!dir) {
+        return NULL;
     }
+    if (p_index == -1) {
+        m_subDirs.append(dir);
+    } else {
+        m_subDirs.insert(p_index, dir);
+    }
+    return dir;
+}
+
+void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
+{
+    QString dirPath = p_subDir->retrivePath();
+
+    removeSubDirectory(p_subDir);
+
+    // Delete the entire directory
+    p_subDir->close();
+    QDir dir(dirPath);
+    if (!dir.removeRecursively()) {
+        qWarning() << "failed to remove" << dirPath << "recursively";
+    } else {
+        qDebug() << "deleted" << dirPath;
+    }
+    delete p_subDir;
+}
+
+int VDirectory::removeSubDirectory(VDirectory *p_dir)
+{
+    Q_ASSERT(m_opened);
+    Q_ASSERT(p_dir);
+
     QString path = retrivePath();
 
     int index;
     for (index = 0; index < m_subDirs.size(); ++index) {
-        if (m_subDirs[index] == p_subDir) {
+        if (m_subDirs[index] == p_dir) {
             break;
         }
     }
-    if (index == m_subDirs.size()) {
-        return;
-    }
+    Q_ASSERT(index != m_subDirs.size());
     m_subDirs.remove(index);
-    QString name = p_subDir->getName();
+
+    QString name = p_dir->getName();
     // Update config to exclude this directory
     QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
     QJsonArray subDirArray = dirJson["sub_directories"].toArray();
@@ -281,6 +362,7 @@ void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
         if (ele["name"].toString() == name) {
             subDirArray.removeAt(i);
             deleted = true;
+            index = i;
             break;
         }
     }
@@ -289,17 +371,7 @@ void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
     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;
-    }
+    return index;
 }
 
 // After calling this, p_file->parent() remain the same.
@@ -466,3 +538,37 @@ VFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
 
     return destFile;
 }
+
+// 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)
+{
+    QString srcPath = QDir::cleanPath(p_srcDir->retrivePath());
+    QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
+    if (srcPath == destPath) {
+        return p_srcDir;
+    }
+    VDirectory *srcParentDir = p_srcDir->getParentDirectory();
+
+    // Copy the directory
+    if (!VUtils::copyDirectory(srcPath, destPath, p_cut)) {
+        return NULL;
+    }
+
+    // Handle VDirectory
+    int index = -1;
+    VDirectory *destDir = NULL;
+    if (p_cut) {
+        // Remove the directory from config
+        index = srcParentDir->removeSubDirectory(p_srcDir);
+        p_srcDir->setName(p_destName);
+        if (srcParentDir != p_destDir) {
+            index = -1;
+        }
+        // Add the directory to new dir's config
+        destDir = p_destDir->addSubDirectory(p_srcDir, index);
+    } else {
+        destDir = p_destDir->addSubDirectory(p_destName, -1);
+    }
+    return destDir;
+}

+ 28 - 10
src/vdirectory.h

@@ -20,34 +20,41 @@ public:
     void close();
     VDirectory *createSubDirectory(const QString &p_name);
     VDirectory *findSubDirectory(const QString &p_name);
+    // Returns the VFile with the name @p_name.
     VFile *findFile(const QString &p_name);
+    // If current dir or its sub-dir contains @p_file.
+    bool containsFile(const VFile *p_file) const;
     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);
+    // Remove the directory in the config and m_subDirs without deleting it in the disk.
+    int removeSubDirectory(VDirectory *p_dir);
     // 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);
+    VDirectory *addSubDirectory(VDirectory *p_dir, int p_index);
+    VDirectory *addSubDirectory(const QString &p_name, int p_index);
     void deleteFile(VFile *p_file);
     bool rename(const QString &p_name);
+    // 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);
+    static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
+                                     VDirectory *p_srcDir, bool p_cut);
 
     inline const QVector<VDirectory *> &getSubDirs() const;
     inline const QString &getName() const;
+    inline void setName(const QString &p_name);
     inline bool isOpened() const;
     inline VDirectory *getParentDirectory();
+    inline const VDirectory *getParentDirectory() const;
     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:
+    inline QString getNotebook() const;
 
 private:
     // Get the path of @p_dir recursively
@@ -56,6 +63,7 @@ private:
     QString retriveRelativePath(const VDirectory *p_dir) const;
     QJsonObject createDirectoryJson() const;
     bool createFileInConfig(const QString &p_name, int p_index = -1);
+    bool createSubDirectoryInConfig(const QString &p_name, int p_index = -1);
 
     QPointer<VNotebook> m_notebook;
     QString m_name;
@@ -76,6 +84,11 @@ inline const QString &VDirectory::getName() const
     return m_name;
 }
 
+inline void VDirectory::setName(const QString &p_name)
+{
+    m_name = p_name;
+}
+
 inline bool VDirectory::isOpened() const
 {
     return m_opened;
@@ -86,12 +99,17 @@ inline VDirectory *VDirectory::getParentDirectory()
     return (VDirectory *)this->parent();
 }
 
+inline const VDirectory *VDirectory::getParentDirectory() const
+{
+    return (const VDirectory *)this->parent();
+}
+
 inline const QVector<VFile *> &VDirectory::getFiles() const
 {
     return m_files;
 }
 
-inline QString VDirectory::retriveNotebook() const
+inline QString VDirectory::getNotebook() const
 {
     return m_notebook->getName();
 }

+ 239 - 39
src/vdirectorytree.cpp

@@ -47,6 +47,24 @@ void VDirectoryTree::initActions()
     dirInfoAct->setStatusTip(tr("View and edit current directory's information"));
     connect(dirInfoAct, &QAction::triggered,
             this, &VDirectoryTree::editDirectoryInfo);
+
+    copyAct = new QAction(QIcon(":/resources/icons/copy.svg"),
+                          tr("&Copy"), this);
+    copyAct->setStatusTip(tr("Copy selected directories"));
+    connect(copyAct, &QAction::triggered,
+            this, &VDirectoryTree::copySelectedDirectories);
+
+    cutAct = new QAction(QIcon(":/resources/icons/cut.svg"),
+                          tr("&Cut"), this);
+    cutAct->setStatusTip(tr("Cut selected directories"));
+    connect(cutAct, &QAction::triggered,
+            this, &VDirectoryTree::cutSelectedDirectories);
+
+    pasteAct = new QAction(QIcon(":/resources/icons/paste.svg"),
+                          tr("&Paste"), this);
+    pasteAct->setStatusTip(tr("Paste directories"));
+    connect(pasteAct, &QAction::triggered,
+            this, &VDirectoryTree::pasteDirectoriesInCurDir);
 }
 
 void VDirectoryTree::setNotebook(VNotebook *p_notebook)
@@ -145,26 +163,38 @@ void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item)
     }
 }
 
-QVector<QTreeWidgetItem *> VDirectoryTree::updateItemChildrenAdded(QTreeWidgetItem *p_item)
+void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
 {
-    QVector<QTreeWidgetItem *> ret;
     QPointer<VDirectory> parentDir;
     if (p_item) {
         parentDir = getVDirectory(p_item);
     } else {
         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();
+    const QVector<VDirectory *> &dirs = parentDir->getSubDirs();
+
+    QHash<VDirectory *, QTreeWidgetItem *> itemDirMap;
+    int nrChild = p_item ? p_item->childCount() : topLevelItemCount();
+    for (int i = 0; i < nrChild; ++i) {
+        QTreeWidgetItem *item = p_item ? p_item->child(i) : topLevelItem(i);
+        itemDirMap.insert(getVDirectory(item), item);
+    }
+
+    for (int i = 0; i < dirs.size(); ++i) {
+        VDirectory *dir = dirs[i];
+        QTreeWidgetItem *item = itemDirMap.value(dir, NULL);
+        if (item) {
+            if (p_item) {
+                p_item->removeChild(item);
+                p_item->insertChild(i, item);
+            } else {
+                int topIdx = indexOfTopLevelItem(item);
+                takeTopLevelItem(topIdx);
+                insertTopLevelItem(i, item);
+            }
+            itemDirMap.remove(dir);
         } else {
-            nrChild = this->topLevelItemCount();
-        }
-        VDirectory *dir = subDirs[i];
-        QTreeWidgetItem *item;
-        if (i >= nrChild) {
+            // Insert a new item
             if (p_item) {
                 item = new QTreeWidgetItem(p_item);
             } else {
@@ -172,37 +202,30 @@ QVector<QTreeWidgetItem *> VDirectoryTree::updateItemChildrenAdded(QTreeWidgetIt
             }
             fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg"));
             updateDirectoryTreeOne(item, 1);
-            ret.append(item);
+        }
+    }
+
+    // Delete items without corresponding VDirectory
+    for (auto iter = itemDirMap.begin(); iter != itemDirMap.end(); ++iter) {
+        QTreeWidgetItem *item = iter.value();
+        if (p_item) {
+            p_item->removeChild(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);
-            }
+            int topIdx = indexOfTopLevelItem(item);
+            takeTopLevelItem(topIdx);
         }
+        delete item;
     }
-    qDebug() << ret.size() << "items added";
-    return ret;
 }
 
 void VDirectoryTree::contextMenuRequested(QPoint pos)
 {
+    QTreeWidgetItem *item = itemAt(pos);
+
     if (!m_notebook) {
         return;
     }
-    QTreeWidgetItem *item = itemAt(pos);
+
     QMenu menu(this);
 
     if (!item) {
@@ -219,8 +242,24 @@ void VDirectoryTree::contextMenuRequested(QPoint pos)
             menu.addAction(newSubDirAct);
         }
         menu.addAction(deleteDirAct);
+        menu.addSeparator();
+        menu.addAction(copyAct);
+        menu.addAction(cutAct);
+    }
+
+    if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyDir
+        && !m_copiedDirs.isEmpty()) {
+        if (!item) {
+            menu.addSeparator();
+        }
+        menu.addAction(pasteAct);
+    }
+
+    if (item) {
+        menu.addSeparator();
         menu.addAction(dirInfoAct);
     }
+
     menu.exec(mapToGlobal(pos));
 }
 
@@ -255,9 +294,7 @@ void VDirectoryTree::newSubDirectory()
                                     QMessageBox::Ok, QMessageBox::Ok, this);
                 return;
             }
-            QVector<QTreeWidgetItem *> items = updateItemChildrenAdded(curItem);
-            Q_ASSERT(items.size() == 1);
-            setCurrentItem(items[0]);
+            updateItemChildren(curItem);
         }
         break;
     } while (true);
@@ -288,9 +325,7 @@ void VDirectoryTree::newRootDirectory()
                                     QMessageBox::Ok, QMessageBox::Ok, this);
                 return;
             }
-            QVector<QTreeWidgetItem *> items = updateItemChildrenAdded(NULL);
-            Q_ASSERT(items.size() == 1);
-            setCurrentItem(items[0]);
+            updateItemChildren(NULL);
         }
         break;
     } while (true);
@@ -356,7 +391,172 @@ void VDirectoryTree::editDirectoryInfo()
                 return;
             }
             curItem->setText(0, name);
+            emit directoryUpdated(curDir);
         }
         break;
     } while (true);
 }
+
+void VDirectoryTree::copySelectedDirectories(bool p_cut)
+{
+    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->getNotebook();
+        dirJson["path"] = dir->retrivePath();
+        dirs.append(dirJson);
+
+        m_copiedDirs.append(dir);
+    }
+
+    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;
+
+    QClipboard *clipboard = QApplication::clipboard();
+    clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
+}
+
+void VDirectoryTree::cutSelectedDirectories()
+{
+    copySelectedDirectories(true);
+}
+
+void VDirectoryTree::pasteDirectoriesInCurDir()
+{
+    QTreeWidgetItem *item = currentItem();
+    VDirectory *destDir = m_notebook->getRootDir();
+    if (item) {
+        destDir = getVDirectory(item);
+    }
+    pasteDirectories(destDir);
+}
+
+void VDirectoryTree::pasteDirectories(VDirectory *p_destDir)
+{
+    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();
+
+    int nrPasted = 0;
+    for (int i = 0; i < m_copiedDirs.size(); ++i) {
+        QPointer<VDirectory> srcDir = m_copiedDirs[i];
+        if (!srcDir) {
+            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->retrivePath(), dirName);
+        }
+        if (copyDirectory(p_destDir, dirName, srcDir, isCut)) {
+            nrPasted++;
+        }
+    }
+    qDebug() << "pasted" << nrPasted << "files successfully";
+    clipboard->clear();
+    m_copiedDirs.clear();
+}
+
+void VDirectoryTree::mousePressEvent(QMouseEvent *event)
+{
+    QTreeWidgetItem *item = itemAt(event->pos());
+    if (!item) {
+        setCurrentItem(NULL);
+    }
+    QTreeWidget::mousePressEvent(event);
+}
+
+bool VDirectoryTree::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
+                                   VDirectory *p_srcDir, bool p_cut)
+{
+    qDebug() << "copy" << p_srcDir->getName() << "to" << p_destDir->getName()
+             << "as" << p_destName;
+    QString srcName = p_srcDir->getName();
+    QString srcPath = QDir::cleanPath(p_srcDir->retrivePath());
+    QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
+    if (srcPath == destPath) {
+        return true;
+    }
+
+    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"),
+                            QString("Failed to copy directory %1.").arg(srcName),
+                            QString("Please check if there alread exists a directory 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->getNotebook() != m_notebook->getName()) {
+        return NULL;
+    } else if (p_dir == m_notebook->getRootDir()) {
+        p_widget = true;
+        return NULL;
+    }
+
+    bool isWidget;
+    QTreeWidgetItem *pItem = findVDirectory(p_dir->getParentDirectory(), isWidget);
+    if (pItem) {
+        // Iterate all its children to find the match.
+        int nrChild = pItem->childCount();
+        for (int i = 0; i < nrChild; ++i) {
+            QTreeWidgetItem *item = pItem->child(i);
+            if (getVDirectory(item) == p_dir) {
+                return item;
+            }
+        }
+    } else if (isWidget) {
+        // Iterate all the top-level items.
+        int nrChild = topLevelItemCount();
+        for (int i = 0; i < nrChild; ++i) {
+            QTreeWidgetItem *item = topLevelItem(i);
+            if (getVDirectory(item) == p_dir) {
+                return item;
+            }
+        }
+    }
+    return NULL;
+}

+ 20 - 5
src/vdirectorytree.h

@@ -18,8 +18,7 @@ public:
 
 signals:
     void currentDirectoryChanged(VDirectory *p_directory);
-    void directoryRenamed(const QString &notebook, const QString &oldRelativePath,
-                          const QString &newRelativePath);
+    void directoryUpdated(const VDirectory *p_directory);
 
 public slots:
     void setNotebook(VNotebook *p_notebook);
@@ -33,19 +32,32 @@ private slots:
     void contextMenuRequested(QPoint pos);
     void newSubDirectory();
     void currentDirectoryItemChanged(QTreeWidgetItem *currentItem);
+    void copySelectedDirectories(bool p_cut = false);
+    void cutSelectedDirectories();
+    void pasteDirectoriesInCurDir();
+
+protected:
+    void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
 
 private:
     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();
-    // 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);
+    // Update @p_item's direct children only: deleted, added, renamed.
+    void updateItemChildren(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);
+    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);
 
     VNote *vnote;
     QPointer<VNotebook> m_notebook;
+    QVector<QPointer<VDirectory> > m_copiedDirs;
 
     // Actions
     QAction *newRootDirAct;
@@ -53,6 +65,9 @@ private:
     QAction *newSubDirAct;
     QAction *deleteDirAct;
     QAction *dirInfoAct;
+    QAction *copyAct;
+    QAction *cutAct;
+    QAction *pasteAct;
 };
 
 inline QPointer<VDirectory> VDirectoryTree::getVDirectory(QTreeWidgetItem *p_item)

+ 8 - 0
src/veditarea.cpp

@@ -313,3 +313,11 @@ void VEditArea::handleFileUpdated(const VFile *p_file)
         getWindow(i)->updateFileInfo(p_file);
     }
 }
+
+void VEditArea::handleDirectoryUpdated(const VDirectory *p_dir)
+{
+    int nrWin = splitter->count();
+    for (int i = 0; i < nrWin; ++i) {
+        getWindow(i)->updateDirectoryInfo(p_dir);
+    }
+}

+ 2 - 0
src/veditarea.h

@@ -16,6 +16,7 @@
 
 class VNote;
 class VFile;
+class VDirectory;
 
 class VEditArea : public QWidget
 {
@@ -42,6 +43,7 @@ public slots:
     void saveAndReadFile();
     void handleOutlineItemActivated(const VAnchor &anchor);
     void handleFileUpdated(const VFile *p_file);
+    void handleDirectoryUpdated(const VDirectory *p_dir);
 
 private slots:
     void handleSplitWindowRequest(VEditWindow *curWindow);

+ 19 - 1
src/veditwindow.cpp

@@ -258,7 +258,9 @@ void VEditWindow::noticeTabStatus(int p_index)
     bool editMode = editor->getIsEditMode();
 
     // Update tab text
-    tabBar()->setTabText(p_index, generateTabText(file->getName(), file->isModified()));
+    QTabBar *tabs = tabBar();
+    tabs->setTabText(p_index, generateTabText(file->getName(), file->isModified()));
+    tabs->setTabToolTip(p_index, generateTooltip(file));
     emit tabStatusChanged(file, editMode);
 }
 
@@ -431,3 +433,19 @@ void VEditWindow::updateFileInfo(const VFile *p_file)
         noticeStatus(idx);
     }
 }
+
+void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
+{
+    if (!p_dir) {
+        return;
+    }
+
+    int nrTab = tabBar()->count();
+    for (int i = 0; i < nrTab; ++i) {
+        VEditTab *editor = getTab(i);
+        QPointer<VFile> file = editor->getFile();
+        if (p_dir->containsFile(file)) {
+            noticeStatus(i);
+        }
+    }
+}

+ 2 - 1
src/veditwindow.h

@@ -36,6 +36,7 @@ public:
     void focusWindow();
     void scrollCurTab(const VAnchor &p_anchor);
     void updateFileInfo(const VFile *p_file);
+    void updateDirectoryInfo(const VDirectory *p_dir);
 
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
@@ -96,7 +97,7 @@ inline QString VEditWindow::generateTooltip(const VFile *p_file) const
         return "";
     }
     // [Notebook]path
-    return QString("[%1] %2").arg(p_file->retriveNotebook()).arg(p_file->retrivePath());
+    return QString("[%1] %2").arg(p_file->getNotebook()).arg(p_file->retrivePath());
 }
 
 inline QString VEditWindow::generateTabText(const QString &p_name, bool p_modified) const

+ 3 - 3
src/vfile.h

@@ -25,7 +25,7 @@ public:
     inline DocType getDocType() const;
     inline QString &getContent();
     inline void setContent(const QString &p_content);
-    inline QString retriveNotebook() const;
+    inline QString getNotebook() const;
     inline QString retrivePath() const;
     inline QString retriveRelativePath() const;
     inline QString retriveBasePath() const;
@@ -80,9 +80,9 @@ inline QString &VFile::getContent()
     return m_content;
 }
 
-inline QString VFile::retriveNotebook() const
+inline QString VFile::getNotebook() const
 {
-    return getDirectory()->retriveNotebook();
+    return getDirectory()->getNotebook();
 }
 
 inline QString VFile::retrivePath() const

+ 3 - 2
src/vfilelist.cpp

@@ -266,7 +266,8 @@ void VFileList::contextMenuRequested(QPoint pos)
         menu.addAction(cutAct);
     }
 
-    if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyFile) {
+    if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyFile
+        && !m_copiedFiles.isEmpty()) {
         if (!item) {
             menu.addSeparator();
         }
@@ -343,7 +344,7 @@ void VFileList::copySelectedFiles(bool p_isCut)
     for (int i = 0; i < items.size(); ++i) {
         VFile *file = getVFile(items[i]);
         QJsonObject fileJson;
-        fileJson["notebook"] = file->retriveNotebook();
+        fileJson["notebook"] = file->getNotebook();
         fileJson["path"] = file->retrivePath();
         files.append(fileJson);
 

+ 11 - 1
src/vmainwindow.cpp

@@ -57,6 +57,11 @@ void VMainWindow::setupUI()
 
     connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
             fileList, &VFileList::setDirectory);
+    connect(directoryTree, &VDirectoryTree::directoryUpdated,
+            editArea, &VEditArea::handleDirectoryUpdated);
+    connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
+            this, &VMainWindow::handleCurrentDirectoryChanged);
+
     connect(fileList, &VFileList::fileClicked,
             editArea, &VEditArea::openFile);
     connect(fileList, &VFileList::fileCreated,
@@ -695,7 +700,7 @@ void VMainWindow::handleCurTabStatusChanged(const VFile *p_file, bool p_editMode
 
     QString title;
     if (p_file) {
-        title = QString("[%1] %2").arg(p_file->retriveNotebook()).arg(p_file->retrivePath());
+        title = QString("[%1] %2").arg(p_file->getNotebook()).arg(p_file->retrivePath());
         if (p_file->isModified()) {
             title.append('*');
         }
@@ -796,3 +801,8 @@ const QVector<QPair<QString, QString> >& VMainWindow::getPalette() const
 {
     return vnote->getPallete();
 }
+
+void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir)
+{
+    newNoteAct->setEnabled(p_dir);
+}

+ 1 - 0
src/vmainwindow.h

@@ -54,6 +54,7 @@ private slots:
     void changePanelView(QAction *action);
     void curEditFileInfo();
     void deleteCurNote();
+    void handleCurrentDirectoryChanged(const VDirectory *p_dir);
 
 signals:
     void curNotebookChanged(VNotebook *p_notebook);

+ 1 - 1
src/vnote.cpp

@@ -121,7 +121,7 @@ void VNote::removeNotebook(int idx)
 
     // Close all the directory and files
     notebookPathHash.remove(name);
-    nb->close(true);
+    nb->close();
     delete nb;
     qDebug() << "notebook" << name << "deleted";
 

+ 1 - 1
src/vnotebook.cpp

@@ -33,7 +33,7 @@ void VNotebook::setPath(const QString &path)
     m_path = path;
 }
 
-void VNotebook::close(bool p_forced)
+void VNotebook::close()
 {
     m_rootDir->close();
 }

+ 2 - 2
src/vnotebook.h

@@ -16,8 +16,8 @@ public:
     // 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);
+    // Please make sure all files belonging to this notebook have been closed in the tab.
+    void close();
 
     QString getName() const;
     QString getPath() const;