From 02d53008215c089abc571afe296da6800f2cd2b1 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 1 Dec 2016 21:15:21 +0800 Subject: [PATCH] support copy and paste directory Signed-off-by: Le Tan --- src/utils/vutils.cpp | 66 ++++++++++ src/utils/vutils.h | 2 + src/vdirectory.cpp | 160 +++++++++++++++++++---- src/vdirectory.h | 44 +++++-- src/vdirectorytree.cpp | 280 +++++++++++++++++++++++++++++++++++------ src/vdirectorytree.h | 25 +++- src/veditarea.cpp | 8 ++ src/veditarea.h | 2 + src/veditwindow.cpp | 20 ++- src/veditwindow.h | 3 +- src/vfile.h | 6 +- src/vfilelist.cpp | 5 +- src/vmainwindow.cpp | 12 +- src/vmainwindow.h | 1 + src/vnote.cpp | 2 +- src/vnotebook.cpp | 2 +- src/vnotebook.h | 4 +- 17 files changed, 545 insertions(+), 97 deletions(-) diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 406fd841..aebacec9 100644 --- a/src/utils/vutils.cpp +++ b/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; +} diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 3823551b..709f0ef6 100644 --- a/src/utils/vutils.h +++ b/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 > &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); diff --git a/src/vdirectory.cpp b/src/vdirectory.cpp index ace3a144..40b9c7a0 100644 --- a/src/vdirectory.cpp +++ b/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; +} diff --git a/src/vdirectory.h b/src/vdirectory.h index e0352d92..d534328a 100644 --- a/src/vdirectory.h +++ b/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); - - inline const QVector &getSubDirs() const; - inline const QString &getName() const; - inline bool isOpened() const; - inline VDirectory *getParentDirectory(); - inline const QVector &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: + static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName, + VDirectory *p_srcDir, bool p_cut); -public slots: + inline const QVector &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 &getFiles() const; + inline QString retrivePath() const; + inline QString retriveRelativePath() const; + 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 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 &VDirectory::getFiles() const { return m_files; } -inline QString VDirectory::retriveNotebook() const +inline QString VDirectory::getNotebook() const { return m_notebook->getName(); } diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index c34fa543..58ac3b4e 100644 --- a/src/vdirectorytree.cpp +++ b/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 VDirectoryTree::updateItemChildrenAdded(QTreeWidgetItem *p_item) +void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item) { - QVector ret; QPointer parentDir; if (p_item) { parentDir = getVDirectory(p_item); } else { parentDir = m_notebook->getRootDir(); } - const QVector &subDirs = parentDir->getSubDirs(); - for (int i = 0; i < subDirs.size(); ++i) { - int nrChild; - if (p_item) { - nrChild = p_item->childCount(); + const QVector &dirs = parentDir->getSubDirs(); + + QHash 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 VDirectoryTree::updateItemChildrenAdded(QTreeWidgetIt } fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg")); updateDirectoryTreeOne(item, 1); - ret.append(item); - } else { - VDirectory *itemDir; - if (p_item) { - itemDir = getVDirectory(p_item->child(i)); - } else { - itemDir = getVDirectory(topLevelItem(i)); - } - if (itemDir != dir) { - item = new QTreeWidgetItem(); - if (p_item) { - p_item->insertChild(i, item); - } else { - this->insertTopLevelItem(i, item); - } - fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg")); - updateDirectoryTreeOne(item, 1); - ret.append(item); - } } } - qDebug() << ret.size() << "items added"; - return ret; + + // 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 { + int topIdx = indexOfTopLevelItem(item); + takeTopLevelItem(topIdx); + } + delete item; + } } 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 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 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 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 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; +} diff --git a/src/vdirectorytree.h b/src/vdirectorytree.h index 6460376f..d211ff36 100644 --- a/src/vdirectorytree.h +++ b/src/vdirectorytree.h @@ -18,8 +18,7 @@ public: signals: void currentDirectoryChanged(VDirectory *p_directory); - void directoryRenamed(const QString ¬ebook, 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 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 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 m_notebook; + QVector > m_copiedDirs; // Actions QAction *newRootDirAct; @@ -53,6 +65,9 @@ private: QAction *newSubDirAct; QAction *deleteDirAct; QAction *dirInfoAct; + QAction *copyAct; + QAction *cutAct; + QAction *pasteAct; }; inline QPointer VDirectoryTree::getVDirectory(QTreeWidgetItem *p_item) diff --git a/src/veditarea.cpp b/src/veditarea.cpp index c6715189..397586d2 100644 --- a/src/veditarea.cpp +++ b/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); + } +} diff --git a/src/veditarea.h b/src/veditarea.h index 2e2875ff..a907cff5 100644 --- a/src/veditarea.h +++ b/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); diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 5bd0297a..d8bec891 100644 --- a/src/veditwindow.cpp +++ b/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 file = editor->getFile(); + if (p_dir->containsFile(file)) { + noticeStatus(i); + } + } +} diff --git a/src/veditwindow.h b/src/veditwindow.h index eef52462..65f56322 100644 --- a/src/veditwindow.h +++ b/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 diff --git a/src/vfile.h b/src/vfile.h index 52213b2d..e4758ce3 100644 --- a/src/vfile.h +++ b/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 diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index ce359428..0fc61c13 100644 --- a/src/vfilelist.cpp +++ b/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); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 4e6caaec..3c29c0df 100644 --- a/src/vmainwindow.cpp +++ b/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 >& VMainWindow::getPalette() const { return vnote->getPallete(); } + +void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir) +{ + newNoteAct->setEnabled(p_dir); +} diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 97b93179..4832e4a1 100644 --- a/src/vmainwindow.h +++ b/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); diff --git a/src/vnote.cpp b/src/vnote.cpp index b2883688..6315a842 100644 --- a/src/vnote.cpp +++ b/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"; diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp index 27867454..78419ed4 100644 --- a/src/vnotebook.cpp +++ b/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(); } diff --git a/src/vnotebook.h b/src/vnotebook.h index cd2de0c7..cf7280ad 100644 --- a/src/vnotebook.h +++ b/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;