From 3724eb35dd9484532157b2ce117631bd75a54119 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 29 Sep 2017 20:31:13 +0800 Subject: [PATCH] refactor VDirectoryTree - Refine folder deletion logics; - Refine folder copy/paste logics; - Add folder sort logics; --- src/dialog/vsortdialog.cpp | 5 + src/vattachmentlist.cpp | 1 - src/vconstants.h | 1 + src/vdirectory.cpp | 203 +++++++++--- src/vdirectory.h | 35 +- src/vdirectorytree.cpp | 635 ++++++++++++++++++++++++++----------- src/vdirectorytree.h | 105 ++++-- src/vfilelist.cpp | 11 +- src/vmainwindow.cpp | 17 +- src/vnote.cpp | 13 + src/vnote.h | 5 + src/vnotebook.cpp | 35 +- src/vnotebook.h | 10 +- 13 files changed, 796 insertions(+), 280 deletions(-) diff --git a/src/dialog/vsortdialog.cpp b/src/dialog/vsortdialog.cpp index 9cf4854a..8d8ca9b1 100644 --- a/src/dialog/vsortdialog.cpp +++ b/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) { diff --git a/src/vattachmentlist.cpp b/src/vattachmentlist.cpp index 695df7bc..2dd5b14a 100644 --- a/src/vattachmentlist.cpp +++ b/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); diff --git a/src/vconstants.h b/src/vconstants.h index 6ee62039..ae1a2205 100644 --- a/src/vconstants.h +++ b/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}; diff --git a/src/vdirectory.cpp b/src/vdirectory.cpp index bee8ae72..563f06a1 100644 --- a/src/vdirectory.cpp +++ b/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); - - QString dirPath = p_subDir->fetchPath(); - - p_subDir->close(); - - removeSubDirectory(p_subDir); + Q_ASSERT(!m_opened); + Q_ASSERT(parent()); // Delete the entire directory. + bool ret = true; + QString dirPath = fetchPath(); 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"); + VUtils::addErrMsg(p_errMsg, tr("Fail to delete the directory %1.").arg(dirPath)); + ret = false; } - delete p_subDir; + return ret; +} + +bool VDirectory::deleteDirectory(VDirectory *p_dir, bool p_skipRecycleBin, QString *p_errMsg) +{ + p_dir->close(); + + 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_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; + QString opStr = p_isCut ? tr("cut") : tr("copy"); + VDirectory *paDir = p_dir->getParentDirectory(); + + 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_cut) { - // Remove the directory from config - srcParentDir->removeSubDirectory(p_srcDir); - - p_srcDir->setName(p_destName); - + 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 &p_sortedIdx) { V_ASSERT(m_opened); @@ -611,3 +695,24 @@ bool VDirectory::sortFiles(const QVector &p_sortedIdx) return ret; } + +bool VDirectory::sortSubDirectories(const QVector &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; +} diff --git a/src/vdirectory.h b/src/vdirectory.h index 06c37618..6d1983e3 100644 --- a/src/vdirectory.h +++ b/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 &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 &p_sortedIdx); + // Reorder sub-directories in m_subDirs by index. + bool sortSubDirectories(const QVector &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 m_notebook; diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index 7e98bd56..2529c1b2 100644 --- a/src/vdirectorytree.cpp +++ b/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 %2.") - .arg(g_config->c_dataTextStyle).arg(m_notebook->getName()), - tr("Please check if path %2 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 %2 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 &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 %2.") - .arg(g_config->c_dataTextStyle).arg(dir->getName()), - tr("Please check if path %2 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 %2 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 parentDir; if (p_item) { @@ -281,6 +297,7 @@ void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item) } else { parentDir = m_notebook->getRootDir(); } + const QVector &dirs = parentDir->getSubDirs(); QHash 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 %2.") - .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 %2.") - .arg(g_config->c_dataTextStyle).arg(name), "", - QMessageBox::Ok, QMessageBox::Ok, this); + VUtils::showMessage(QMessageBox::Warning, + tr("Warning"), + tr("Fail to create subfolder %2.") + .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 %2.") - .arg(g_config->c_dataTextStyle).arg(m_notebook->getName()); + VDirectory *rootDir = m_notebook->getRootDir(); + + QString info = tr("Create a root folder in notebook %2.") + .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 %2.") - .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 %2.") + .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 %2?") - .arg(g_config->c_dataTextStyle).arg(curDir->getName()), + .arg(g_config->c_dataTextStyle) + .arg(curDir->getName()), tr("WARNING: " "VNote will delete the whole directory " "%3." "You could find deleted files in the recycle bin " - "of this notebook.
The operation is IRREVERSIBLE!") - .arg(g_config->c_warningTextStyle).arg(g_config->c_dataTextStyle).arg(curDir->fetchPath()), + "of this folder.
" + "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 %2.
" + "Please check %3 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 %2.") - .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 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; } - QTreeWidgetItem *item = currentItem(); - VDirectory *destDir = m_notebook->getRootDir(); - if (item) { - destDir = getVDirectory(item); + QJsonObject obj = VUtils::clipboardToJson(); + QJsonArray dirs = obj[ClipboardConfig::c_dirs].toArray(); + bool isCut = obj[ClipboardConfig::c_isCut].toBool(); + QVector dirsToPaste(dirs.size()); + for (int i = 0; i < dirs.size(); ++i) { + dirsToPaste[i] = dirs[i].toString(); } - pasteDirectories(destDir); + VDirectory *destDir; + QTreeWidgetItem *item = currentItem(); + if (item) { + destDir = getVDirectory(item); + } else { + destDir = m_notebook->getRootDir(); + } + + pasteDirectories(destDir, dirsToPaste, isCut); + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->clear(); } -void VDirectoryTree::pasteDirectories(VDirectory *p_destDir) +void VDirectoryTree::pasteDirectories(VDirectory *p_destDir, + const QVector &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 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 %2.") + .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 %2.") + .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 %2.") - .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 VDirectoryTree::getVisibleItems() const } } } + return items; } @@ -1019,5 +1190,103 @@ QList 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 &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 %3 " + "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 data = dialog.getSortedData(); + Q_ASSERT(data.size() == dirs.size()); + QVector 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 %3.") + .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)); + } +} diff --git a/src/vdirectorytree.h b/src/vdirectorytree.h index 6d390c52..16740a8f 100644 --- a/src/vdirectorytree.h +++ b/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 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 getVDirectory(QTreeWidgetItem *p_item) const; + + // Paste @p_dirs as sub-directory of @p_destDir. + void pasteDirectories(VDirectory *p_destDir, + const QVector &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 getVisibleItems() const; + QList 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 m_notebook; - QVector > m_copiedDirs; + VEditArea *m_editArea; // Each notebook's current item's VDirectory. QHash 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; diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index a70c162f..663002a4 100644 --- a/src/vfilelist.cpp +++ b/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 &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 %2.") + tr("Fail to paste note %2.") .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); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 76cbb795..2622a6bb 100644 --- a/src/vmainwindow.cpp +++ b/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); diff --git a/src/vnote.cpp b/src/vnote.cpp index 6c54e484..8a025351 100644 --- a/src/vnote.cpp +++ b/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; + +} diff --git a/src/vnote.h b/src/vnote.h index a5f73cbc..dd320ce5 100644 --- a/src/vnote.h +++ b/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(); diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp index da911183..43b9ba22 100644 --- a/src/vnotebook.cpp +++ b/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 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()) { diff --git a/src/vnotebook.h b/src/vnotebook.h index bd99bd26..80bf9585 100644 --- a/src/vnotebook.h +++ b/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;