Bläddra i källkod

remove recycle bin node

Le Tan 4 år sedan
förälder
incheckning
964dfbb085

+ 2 - 0
src/core/exception.h

@@ -23,6 +23,8 @@ namespace vnotex
             FailToRemoveDir,
             FileMissingOnDisk,
             EssentialFileMissing,
+            FileExistsOnCreate,
+            DirExistsOnCreate,
             InvalidArgument
         };
 

+ 5 - 0
src/core/notebook/bundlenotebook.cpp

@@ -254,3 +254,8 @@ TagI *BundleNotebook::tag()
 {
     return getTagMgr();
 }
+
+int BundleNotebook::getConfigVersion() const
+{
+    return m_configVersion;
+}

+ 2 - 0
src/core/notebook/bundlenotebook.h

@@ -41,6 +41,8 @@ namespace vnotex
 
         TagI *tag() Q_DECL_OVERRIDE;
 
+        int getConfigVersion() const;
+
         // HistoryI.
     public:
         HistoryI *history() Q_DECL_OVERRIDE;

+ 19 - 1
src/core/notebook/node.cpp

@@ -109,6 +109,24 @@ bool Node::containsChild(const QSharedPointer<Node> &p_node) const
     return m_children.indexOf(p_node) != -1;
 }
 
+bool Node::isLegalNameForNewChild(const QString &p_name) const
+{
+    if (p_name.isEmpty()) {
+        return false;
+    }
+
+    auto mgr = getConfigMgr();
+    if (mgr->isBuiltInFile(this, p_name) || mgr->isBuiltInFolder(this, p_name)) {
+        return false;
+    }
+
+    if (containsChild(p_name, false)) {
+        return false;
+    }
+
+    return true;
+}
+
 QSharedPointer<Node> Node::findChild(const QString &p_name, bool p_caseSensitive) const
 {
     auto targetName = p_caseSensitive ? p_name : p_name.toLower();
@@ -356,7 +374,7 @@ bool Node::canRename(const QString &p_newName) const
         }
     }
 
-    if (m_parent->containsChild(p_newName, false)) {
+    if (!m_parent->isLegalNameForNewChild(p_newName)) {
         return false;
     }
 

+ 2 - 1
src/core/notebook/node.h

@@ -36,7 +36,6 @@ namespace vnotex
 
         enum Use {
             Normal,
-            RecycleBin,
             Root
         };
 
@@ -115,6 +114,8 @@ namespace vnotex
         // Case sensitive.
         bool containsContentChild(const QString &p_name) const;
 
+        bool isLegalNameForNewChild(const QString &p_name) const;
+
         void addChild(const QSharedPointer<Node> &p_node);
 
         void insertChild(int p_idx, const QSharedPointer<Node> &p_node);

+ 55 - 75
src/core/notebook/notebook.cpp

@@ -16,6 +16,8 @@ const QString Notebook::c_defaultAttachmentFolder = QStringLiteral("vx_attachmen
 
 const QString Notebook::c_defaultImageFolder = QStringLiteral("vx_images");
 
+const QString Notebook::c_defaultRecycleBinFolder = QStringLiteral("vx_recycle_bin");
+
 static vnotex::ID generateNotebookID()
 {
     static vnotex::ID id = Notebook::InvalidId;
@@ -44,6 +46,9 @@ Notebook::Notebook(const NotebookParameters &p_paras,
     if (m_attachmentFolder.isEmpty()) {
         m_attachmentFolder = c_defaultAttachmentFolder;
     }
+    if (m_recycleBinFolder.isEmpty()) {
+        m_recycleBinFolder = c_defaultRecycleBinFolder;
+    }
 
     m_configMgr->setNotebook(this);
 }
@@ -151,6 +156,28 @@ const QString &Notebook::getAttachmentFolder() const
     return m_attachmentFolder;
 }
 
+const QString &Notebook::getRecycleBinFolder() const
+{
+    return m_recycleBinFolder;
+}
+
+QString Notebook::getRecycleBinFolderAbsolutePath() const
+{
+    if (QDir::isAbsolutePath(m_recycleBinFolder)) {
+        if (!QFileInfo::exists(m_recycleBinFolder)) {
+            QDir dir(m_recycleBinFolder);
+            dir.mkpath(m_recycleBinFolder);
+        }
+        return m_recycleBinFolder;
+    } else {
+        auto folderPath = getBackend()->getFullPath(m_recycleBinFolder);
+        if (!getBackend()->exists(m_recycleBinFolder)) {
+            getBackend()->makePath(m_recycleBinFolder);
+        }
+        return folderPath;
+    }
+}
+
 const QSharedPointer<INotebookBackend> &Notebook::getBackend() const
 {
     return m_backend;
@@ -176,23 +203,6 @@ const QSharedPointer<Node> &Notebook::getRootNode() const
     return m_root;
 }
 
-QSharedPointer<Node> Notebook::getRecycleBinNode() const
-{
-    auto root = getRootNode();
-    const auto &children = root->getChildrenRef();
-    auto it = std::find_if(children.begin(),
-                           children.end(),
-                           [this](const QSharedPointer<Node> &p_node) {
-                               return isRecycleBinNode(p_node.data());
-                           });
-
-    if (it != children.end()) {
-        return *it;
-    }
-
-    return nullptr;
-}
-
 QSharedPointer<Node> Notebook::newNode(Node *p_parent,
                                        Node::Flags p_flags,
                                        const QString &p_name,
@@ -259,27 +269,6 @@ void Notebook::removeNode(Node *p_node, bool p_force, bool p_configOnly)
     removeNode(p_node->sharedFromThis(), p_force, p_configOnly);
 }
 
-bool Notebook::isRecycleBinNode(const Node *p_node) const
-{
-    return p_node && p_node->getUse() == Node::Use::RecycleBin;
-}
-
-bool Notebook::isNodeInRecycleBin(const Node *p_node) const
-{
-    if (p_node) {
-        p_node = p_node->getParent();
-        while (p_node) {
-            if (isRecycleBinNode(p_node)) {
-                return true;
-            }
-
-            p_node = p_node->getParent();
-        }
-    }
-
-    return false;
-}
-
 void Notebook::moveNodeToRecycleBin(Node *p_node)
 {
     moveNodeToRecycleBin(p_node->sharedFromThis());
@@ -288,59 +277,41 @@ void Notebook::moveNodeToRecycleBin(Node *p_node)
 void Notebook::moveNodeToRecycleBin(const QSharedPointer<Node> &p_node)
 {
     Q_ASSERT(p_node && !p_node->isRoot());
-    auto destNode = getOrCreateRecycleBinDateNode();
-    copyNodeAsChildOf(p_node, destNode.data(), true);
+    m_configMgr->removeNodeToFolder(p_node, getOrCreateRecycleBinDateFolder());
 }
 
-QSharedPointer<Node> Notebook::getOrCreateRecycleBinDateNode()
+QString Notebook::getOrCreateRecycleBinDateFolder()
 {
     // Name after date.
-    auto dateNodeName = QDate::currentDate().toString(QStringLiteral("yyyyMMdd"));
-
-    auto recycleBinNode = getRecycleBinNode();
-    auto dateNode = recycleBinNode->findChild(dateNodeName,
-                                              FileUtils::isPlatformNameCaseSensitive());
-    if (!dateNode) {
-        // Create a date node.
-        dateNode = newNode(recycleBinNode.data(), Node::Flag::Container, dateNodeName);
+    auto dateFolderName = QDate::currentDate().toString(QStringLiteral("yyyyMMdd"));
+    auto folderPath = PathUtils::concatenateFilePath(getRecycleBinFolder(), dateFolderName);
+    if (QDir::isAbsolutePath(folderPath)) {
+        qDebug() << "using absolute recycle bin folder" << folderPath;
+        QDir dir(folderPath);
+        if (dir.exists()) {
+            dir.mkpath(folderPath);
+        }
+    } else {
+        if (!getBackend()->exists(folderPath)) {
+            getBackend()->makePath(folderPath);
+        }
     }
 
-    return dateNode;
-}
-
-void Notebook::emptyNode(const Node *p_node, bool p_force)
-{
-    // Empty the children.
-    auto children = p_node->getChildren();
-    for (const auto &child : children) {
-        removeNode(child, p_force);
-    }
+    return folderPath;
 }
 
 void Notebook::moveFileToRecycleBin(const QString &p_filePath)
 {
-    auto node = getOrCreateRecycleBinDateNode();
-    auto destFilePath = PathUtils::concatenateFilePath(node->fetchPath(),
-                                                       PathUtils::fileName(p_filePath));
+    auto destFilePath = PathUtils::concatenateFilePath(getOrCreateRecycleBinDateFolder(), PathUtils::fileName(p_filePath));
     destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
-    m_backend->copyFile(p_filePath, destFilePath);
-
-    getBackend()->removeFile(p_filePath);
-
-    emit nodeUpdated(node.data());
+    m_backend->copyFile(p_filePath, destFilePath, true);
 }
 
 void Notebook::moveDirToRecycleBin(const QString &p_dirPath)
 {
-    auto node = getOrCreateRecycleBinDateNode();
-    auto destDirPath = PathUtils::concatenateFilePath(node->fetchPath(),
-                                                      PathUtils::fileName(p_dirPath));
+    auto destDirPath = PathUtils::concatenateFilePath(getOrCreateRecycleBinDateFolder(), PathUtils::fileName(p_dirPath));
     destDirPath = getBackend()->renameIfExistsCaseInsensitive(destDirPath);
-    m_backend->copyDir(p_dirPath, destDirPath);
-
-    getBackend()->removeDir(p_dirPath);
-
-    emit nodeUpdated(node.data());
+    m_backend->copyDir(p_dirPath, destDirPath, true);
 }
 
 QSharedPointer<Node> Notebook::addAsNode(Node *p_parent,
@@ -416,3 +387,12 @@ TagI *Notebook::tag()
 {
     return nullptr;
 }
+
+void Notebook::emptyRecycleBin()
+{
+    QDir dir(getRecycleBinFolderAbsolutePath());
+    auto children = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+    for (const auto &child : children) {
+        FileUtils::removeDir(dir.filePath(child));
+    }
+}

+ 12 - 11
src/core/notebook/notebook.h

@@ -61,6 +61,10 @@ namespace vnotex
 
         const QString &getAttachmentFolder() const;
 
+        const QString &getRecycleBinFolder() const;
+
+        QString getRecycleBinFolderAbsolutePath() const;
+
         const QDateTime &getCreatedTimeUtc() const;
 
         const QSharedPointer<INotebookBackend> &getBackend() const;
@@ -71,8 +75,6 @@ namespace vnotex
 
         const QSharedPointer<Node> &getRootNode() const;
 
-        QSharedPointer<Node> getRecycleBinNode() const;
-
         QSharedPointer<Node> newNode(Node *p_parent,
                                      Node::Flags p_flags,
                                      const QString &p_name,
@@ -116,17 +118,11 @@ namespace vnotex
         // Move @p_dirPath to the recycle bin, without adding it as a child node.
         void moveDirToRecycleBin(const QString &p_dirPath);
 
+        virtual void emptyRecycleBin();
+
         // Remove all files of this notebook from disk.
         virtual void remove() = 0;
 
-        bool isRecycleBinNode(const Node *p_node) const;
-
-        bool isNodeInRecycleBin(const Node *p_node) const;
-
-        // Remove all children node of @p_node.
-        // @p_force: if true, just delete all folders and files under @p_node.
-        void emptyNode(const Node *p_node, bool p_force = false);
-
         // Whether @p_name is a built-in file under @p_node.
         bool isBuiltInFile(const Node *p_node, const QString &p_name) const;
 
@@ -150,6 +146,8 @@ namespace vnotex
 
         static const QString c_defaultImageFolder;
 
+        static const QString c_defaultRecycleBinFolder;
+
     public:
         // Return null if history is not suported.
         virtual HistoryI *history();
@@ -168,7 +166,7 @@ namespace vnotex
         virtual void initializeInternal() = 0;
 
     private:
-        QSharedPointer<Node> getOrCreateRecycleBinDateNode();
+        QString getOrCreateRecycleBinDateFolder();
 
         bool m_initialized = false;
 
@@ -196,6 +194,9 @@ namespace vnotex
         // Name of the folder to hold attachments.
         QString m_attachmentFolder;
 
+        // Name or path of the folder to hold deleted files.
+        QString m_recycleBinFolder;
+
         QDateTime m_createdTimeUtc;
 
         // Backend for file access and synchronization.

+ 2 - 2
src/core/notebookbackend/inotebookbackend.h

@@ -83,13 +83,13 @@ namespace vnotex
 
         // Copy @p_filePath to @p_destPath.
         // @p_filePath could be outside notebook.
-        virtual void copyFile(const QString &p_filePath, const QString &p_destPath) = 0;
+        virtual void copyFile(const QString &p_filePath, const QString &p_destPath, bool p_move = false) = 0;
 
         // Delete @p_filePath from disk.
         virtual void removeFile(const QString &p_filePath) = 0;
 
         // Copy  @p_dirPath to as @p_destPath.
-        virtual void copyDir(const QString &p_dirPath, const QString &p_destPath) = 0;
+        virtual void copyDir(const QString &p_dirPath, const QString &p_destPath, bool p_move = false) = 0;
 
         // Delete @p_dirPath from disk if it is empty.
         // Return false if it is not deleted due to non-empty.

+ 4 - 4
src/core/notebookbackend/localnotebookbackend.cpp

@@ -123,7 +123,7 @@ void LocalNotebookBackend::renameDir(const QString &p_dirPath, const QString &p_
     FileUtils::renameFile(dirPath, p_name);
 }
 
-void LocalNotebookBackend::copyFile(const QString &p_filePath, const QString &p_destPath)
+void LocalNotebookBackend::copyFile(const QString &p_filePath, const QString &p_destPath, bool p_move)
 {
     auto filePath = p_filePath;
     if (QFileInfo(filePath).isRelative()) {
@@ -132,10 +132,10 @@ void LocalNotebookBackend::copyFile(const QString &p_filePath, const QString &p_
 
     Q_ASSERT(QFileInfo(filePath).isFile());
 
-    FileUtils::copyFile(filePath, getFullPath(p_destPath));
+    FileUtils::copyFile(filePath, getFullPath(p_destPath), p_move);
 }
 
-void LocalNotebookBackend::copyDir(const QString &p_dirPath, const QString &p_destPath)
+void LocalNotebookBackend::copyDir(const QString &p_dirPath, const QString &p_destPath, bool p_move)
 {
     auto dirPath = p_dirPath;
     if (QFileInfo(dirPath).isRelative()) {
@@ -144,7 +144,7 @@ void LocalNotebookBackend::copyDir(const QString &p_dirPath, const QString &p_de
 
     Q_ASSERT(QFileInfo(dirPath).isDir());
 
-    FileUtils::copyDir(dirPath, getFullPath(p_destPath));
+    FileUtils::copyDir(dirPath, getFullPath(p_destPath), p_move);
 }
 
 void LocalNotebookBackend::removeFile(const QString &p_filePath)

+ 2 - 2
src/core/notebookbackend/localnotebookbackend.h

@@ -69,10 +69,10 @@ namespace vnotex
 
         // Copy @p_filePath to @p_destPath.
         // @p_filePath may beyond this notebook backend.
-        void copyFile(const QString &p_filePath, const QString &p_destPath) Q_DECL_OVERRIDE;
+        void copyFile(const QString &p_filePath, const QString &p_destPath, bool p_move = false) Q_DECL_OVERRIDE;
 
         // Copy @p_dirPath to as @p_destPath.
-        void copyDir(const QString &p_dirPath, const QString &p_destPath) Q_DECL_OVERRIDE;
+        void copyDir(const QString &p_dirPath, const QString &p_destPath, bool p_move = false) Q_DECL_OVERRIDE;
 
         QString renameIfExistsCaseInsensitive(const QString &p_path) const Q_DECL_OVERRIDE;
 

+ 4 - 2
src/core/notebookconfigmgr/bundlenotebookconfigmgr.cpp

@@ -95,12 +95,14 @@ bool BundleNotebookConfigMgr::isBuiltInFile(const Node *p_node, const QString &p
 bool BundleNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_name) const
 {
     if (p_node->isRoot()) {
-        return p_name.toLower() == c_configFolderName;
+        const auto name = p_name.toLower();
+        return (name == c_configFolderName
+                || name == getNotebook()->getRecycleBinFolder().toLower());
     }
     return false;
 }
 
 int BundleNotebookConfigMgr::getCodeVersion() const
 {
-    return 2;
+    return 3;
 }

+ 2 - 0
src/core/notebookconfigmgr/inotebookconfigmgr.h

@@ -69,6 +69,8 @@ namespace vnotex
 
         virtual void removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly) = 0;
 
+        virtual void removeNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder) = 0;
+
         // Whether @p_name is a built-in file under @p_node.
         virtual bool isBuiltInFile(const Node *p_node, const QString &p_name) const = 0;
 

+ 102 - 63
src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp

@@ -22,6 +22,7 @@
 #include <utils/contentmediautils.h>
 
 #include "vxnodeconfig.h"
+#include "vxnotebookconfigmgrfactory.h"
 
 using namespace vnotex;
 
@@ -29,19 +30,12 @@ using namespace vnotex::vx_node_config;
 
 const QString VXNotebookConfigMgr::c_nodeConfigName = "vx.json";
 
-const QString VXNotebookConfigMgr::c_recycleBinFolderName = "vx_recycle_bin";
-
 bool VXNotebookConfigMgr::s_initialized = false;
 
 QVector<QRegExp> VXNotebookConfigMgr::s_externalNodeExcludePatterns;
 
-VXNotebookConfigMgr::VXNotebookConfigMgr(const QString &p_name,
-                                         const QString &p_displayName,
-                                         const QString &p_description,
-                                         const QSharedPointer<INotebookBackend> &p_backend,
-                                         QObject *p_parent)
-    : BundleNotebookConfigMgr(p_backend, p_parent),
-      m_info(p_name, p_displayName, p_description)
+VXNotebookConfigMgr::VXNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend, QObject *p_parent)
+    : BundleNotebookConfigMgr(p_backend, p_parent)
 {
     if (!s_initialized) {
         s_initialized = true;
@@ -58,17 +52,17 @@ VXNotebookConfigMgr::VXNotebookConfigMgr(const QString &p_name,
 
 QString VXNotebookConfigMgr::getName() const
 {
-    return m_info.m_name;
+    return VXNotebookConfigMgrFactory::c_name;
 }
 
 QString VXNotebookConfigMgr::getDisplayName() const
 {
-    return m_info.m_displayName;
+    return VXNotebookConfigMgrFactory::c_displayName;
 }
 
 QString VXNotebookConfigMgr::getDescription() const
 {
-    return m_info.m_description;
+    return VXNotebookConfigMgrFactory::c_description;
 }
 
 void VXNotebookConfigMgr::createEmptySkeleton(const NotebookParameters &p_paras)
@@ -97,29 +91,21 @@ QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode()
     root->setExists(true);
     Q_ASSERT(root->isLoaded());
 
-    if (!markRecycleBinNode(root)) {
-        const_cast<VXNotebookConfigMgr *>(this)->createRecycleBinNode(root);
+    if (static_cast<BundleNotebook *>(getNotebook())->getConfigVersion() < 3) {
+        removeLegacyRecycleBinNode(root);
     }
 
     return root;
 }
 
-bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer<Node> &p_root)
+void VXNotebookConfigMgr::removeLegacyRecycleBinNode(const QSharedPointer<Node> &p_root)
 {
-    auto node = p_root->findChild(c_recycleBinFolderName,
+    // Do not support recycle bin node as it complicates everything.
+    auto node = p_root->findChild(QStringLiteral("vx_recycle_bin"),
                                   FileUtils::isPlatformNameCaseSensitive());
     if (node) {
-        if (!node->exists()) {
-            removeNode(node, true, true);
-            return false;
-        }
-
-        node->setUse(Node::Use::RecycleBin);
-        markNodeReadOnly(node.data());
-        return true;
+        removeNode(node, true, true);
     }
-
-    return false;
 }
 
 void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const
@@ -134,15 +120,6 @@ void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const
     }
 }
 
-void VXNotebookConfigMgr::createRecycleBinNode(const QSharedPointer<Node> &p_root)
-{
-    Q_ASSERT(p_root->isRoot());
-
-    auto node = newNode(p_root.data(), Node::Flag::Container, c_recycleBinFolderName, "");
-    node->setUse(Node::Use::RecycleBin);
-    markNodeReadOnly(node.data());
-}
-
 QSharedPointer<NodeConfig> VXNotebookConfigMgr::readNodeConfig(const QString &p_path) const
 {
     auto backend = getBackend();
@@ -314,6 +291,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFileNode(Node *p_parent,
 
     // Write empty file.
     if (p_create) {
+        if (getBackend()->childExistsCaseInsensitive(p_parent->fetchPath(), p_name)) {
+            // File already exists. Exception.
+            Exception::throwOne(Exception::Type::FileExistsOnCreate,
+                                QString("file (%1) already exists when creating new node").arg(node->fetchPath()));
+            return nullptr;
+        }
+
         getBackend()->writeFile(node->fetchPath(), p_content);
         node->setExists(true);
     } else {
@@ -343,6 +327,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFolderNode(Node *p_parent,
 
     // Make folder.
     if (p_create) {
+        if (getBackend()->childExistsCaseInsensitive(p_parent->fetchPath(), p_name)) {
+            // Dir already exists. Exception.
+            Exception::throwOne(Exception::Type::DirExistsOnCreate,
+                                QString("dir (%1) already exists when creating new node").arg(node->fetchPath()));
+            return nullptr;
+        }
+
         getBackend()->makePath(node->fetchPath());
         node->setExists(true);
     } else {
@@ -513,22 +504,9 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
                                                                 bool p_move,
                                                                 bool p_updateDatabase)
 {
-    // Copy source file itself.
-    auto srcFilePath = p_src->fetchAbsolutePath();
-    auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
-                                                       PathUtils::fileName(srcFilePath));
-    destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
-    getBackend()->copyFile(srcFilePath, destFilePath);
-
-    // Copy media files fetched from content.
-    ContentMediaUtils::copyMediaFiles(p_src.data(), getBackend().data(), destFilePath);
-
-    // Copy attachment folder. Rename attachment folder if conflicts.
-    QString attachmentFolder = p_src->getAttachmentFolder();
-    if (!attachmentFolder.isEmpty()) {
-        auto destAttachmentFolderPath = fetchNodeAttachmentFolder(destFilePath, attachmentFolder);
-        ContentMediaUtils::copyAttachment(p_src.data(), getBackend().data(), destFilePath, destAttachmentFolderPath);
-    }
+    QString destFilePath;
+    QString attachmentFolder;
+    copyFilesOfFileNode(p_src, p_dest->fetchPath(), destFilePath, attachmentFolder);
 
     // Create a file node.
     auto notebook = getNotebook();
@@ -571,7 +549,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
 
     if (p_move) {
         if (sameNotebook) {
-            // The same notebook. Do not directly call removeNode() since we need to update the record
+            // The same notebook. Do not directly call removeNode() since we already update the record
             // in database directly.
             removeNode(p_src, false, false, false);
         } else {
@@ -587,9 +565,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
                                                                   bool p_move,
                                                                   bool p_updateDatabase)
 {
-    auto srcFolderPath = p_src->fetchAbsolutePath();
-    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
-                                                         PathUtils::fileName(srcFolderPath));
+    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(), p_src->getName());
     destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
 
     // Make folder.
@@ -662,7 +638,6 @@ void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node,
                                      bool p_configOnly,
                                      bool p_updateDatabase)
 {
-    auto parentNode = p_node->getParent();
     if (!p_configOnly && p_node->exists()) {
         // Remove all children.
         auto children = p_node->getChildren();
@@ -683,7 +658,7 @@ void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node,
         removeNodeFromDatabase(p_node.data());
     }
 
-    if (parentNode) {
+    if (auto parentNode = p_node->getParent()) {
         parentNode->removeChild(p_node);
         writeNodeConfig(parentNode);
     }
@@ -722,10 +697,74 @@ void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
     }
 }
 
+void VXNotebookConfigMgr::removeNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder)
+{
+    if (p_node->isContainer()) {
+        removeFolderNodeToFolder(p_node, p_destFolder);
+    } else {
+        removeFileNodeToFolder(p_node, p_destFolder);
+    }
+}
+
+void VXNotebookConfigMgr::removeFolderNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder)
+{
+    auto destFolderPath = PathUtils::concatenateFilePath(p_destFolder, p_node->getName());
+    destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
+
+    // Make folder.
+    getBackend()->makePath(destFolderPath);
+
+    // Children.
+    auto children = p_node->getChildren();
+    for (const auto &child : children) {
+        removeNodeToFolder(child, destFolderPath);
+    }
+
+    removeNode(p_node, false, false);
+}
+
+void VXNotebookConfigMgr::removeFileNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder)
+{
+    // Use a wrapper folder.
+    auto destFolderPath = PathUtils::concatenateFilePath(p_destFolder, p_node->getName());
+    destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
+
+    // Make folder.
+    getBackend()->makePath(destFolderPath);
+
+    QString destFilePath;
+    QString attachmentFolder;
+    copyFilesOfFileNode(p_node, destFolderPath, destFilePath, attachmentFolder);
+
+    removeNode(p_node, false, false);
+}
+
+void VXNotebookConfigMgr::copyFilesOfFileNode(const QSharedPointer<Node> &p_node,
+                                              const QString &p_destFolder,
+                                              QString &p_destFilePath,
+                                              QString &p_attachmentFolder)
+{
+    // Copy source file itself.
+    auto nodeFilePath = p_node->fetchAbsolutePath();
+    p_destFilePath = PathUtils::concatenateFilePath(p_destFolder, PathUtils::fileName(nodeFilePath));
+    p_destFilePath = getBackend()->renameIfExistsCaseInsensitive(p_destFilePath);
+    getBackend()->copyFile(nodeFilePath, p_destFilePath);
+
+    // Copy media files fetched from content.
+    ContentMediaUtils::copyMediaFiles(p_node.data(), getBackend().data(), p_destFilePath);
+
+    // Copy attachment folder. Rename attachment folder if conflicts.
+    p_attachmentFolder = p_node->getAttachmentFolder();
+    if (!p_attachmentFolder.isEmpty()) {
+        auto destAttachmentFolderPath = fetchNodeAttachmentFolder(p_destFilePath, p_attachmentFolder);
+        ContentMediaUtils::copyAttachment(p_node.data(), getBackend().data(), p_destFilePath, destAttachmentFolderPath);
+    }
+}
+
 QString VXNotebookConfigMgr::fetchNodeImageFolderPath(Node *p_node)
 {
     auto pa = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_node->fetchAbsolutePath()),
-                                             getNotebook()->getImageFolder());
+            getNotebook()->getImageFolder());
     // Do not make the folder when it is a folder node request.
     if (p_node->hasContent()) {
         getBackend()->makePath(pa);
@@ -736,7 +775,7 @@ QString VXNotebookConfigMgr::fetchNodeImageFolderPath(Node *p_node)
 QString VXNotebookConfigMgr::fetchNodeAttachmentFolderPath(Node *p_node)
 {
     auto notebookFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_node->fetchAbsolutePath()),
-                                                         getNotebook()->getAttachmentFolder());
+            getNotebook()->getAttachmentFolder());
     if (p_node->hasContent()) {
         auto nodeFolder = p_node->getAttachmentFolder();
         if (nodeFolder.isEmpty()) {
@@ -783,9 +822,9 @@ bool VXNotebookConfigMgr::isBuiltInFile(const Node *p_node, const QString &p_nam
 bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_name) const
 {
     const auto name = p_name.toLower();
-    if (name == c_recycleBinFolderName
-        || name == getNotebook()->getImageFolder().toLower()
-        || name == getNotebook()->getAttachmentFolder().toLower()
+    const auto &nb = getNotebook();
+    if (name == nb->getImageFolder().toLower()
+        || name == nb->getAttachmentFolder().toLower()
         || name == QStringLiteral("_v_images")
         || name == QStringLiteral("_v_attachments")) {
         return true;
@@ -946,7 +985,7 @@ bool VXNotebookConfigMgr::checkNodeExists(Node *p_node)
 QStringList VXNotebookConfigMgr::scanAndImportExternalFiles(Node *p_node)
 {
     QStringList files;
-    if (!p_node->isContainer() || p_node->getUse() == Node::Use::RecycleBin) {
+    if (!p_node->isContainer()) {
         return files;
     }
 

+ 13 - 13
src/core/notebookconfigmgr/vxnotebookconfigmgr.h

@@ -27,11 +27,7 @@ namespace vnotex
     {
         Q_OBJECT
     public:
-        explicit VXNotebookConfigMgr(const QString &p_name,
-                                     const QString &p_displayName,
-                                     const QString &p_description,
-                                     const QSharedPointer<INotebookBackend> &p_backend,
-                                     QObject *p_parent = nullptr);
+        VXNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend, QObject *p_parent = nullptr);
 
         QString getName() const Q_DECL_OVERRIDE;
 
@@ -71,6 +67,8 @@ namespace vnotex
 
         void removeNode(const QSharedPointer<Node> &p_node, bool p_force = false, bool p_configOnly = false) Q_DECL_OVERRIDE;
 
+        void removeNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder) Q_DECL_OVERRIDE;
+
         bool isBuiltInFile(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
 
         bool isBuiltInFolder(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
@@ -137,11 +135,9 @@ namespace vnotex
 
         void removeFilesOfNode(Node *p_node, bool p_force);
 
-        bool markRecycleBinNode(const QSharedPointer<Node> &p_root);
-
         void markNodeReadOnly(Node *p_node) const;
 
-        void createRecycleBinNode(const QSharedPointer<Node> &p_root);
+        void removeLegacyRecycleBinNode(const QSharedPointer<Node> &p_root);
 
         // Generate node attachment folder.
         // @p_folderName: suggested folder name if not empty, may be renamed due to conflicts.
@@ -171,9 +167,16 @@ namespace vnotex
 
         bool sameNotebook(const Node *p_node) const;
 
-        static bool isLikelyImageFolder(const QString &p_dirPath);
+        void removeFolderNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder);
+
+        void removeFileNodeToFolder(const QSharedPointer<Node> &p_node, const QString &p_destFolder);
 
-        Info m_info;
+        void copyFilesOfFileNode(const QSharedPointer<Node> &p_node,
+                                 const QString &p_destFolder,
+                                 QString &p_destFilePath,
+                                 QString &p_attachmentFolder);
+
+        static bool isLikelyImageFolder(const QString &p_dirPath);
 
         static bool s_initialized;
 
@@ -181,9 +184,6 @@ namespace vnotex
 
         // Name of the node's config file.
         static const QString c_nodeConfigName;
-
-        // Name of the recycle bin folder which should be a child of the root node.
-        static const QString c_recycleBinFolderName;
     };
 } // ns vnotex
 

+ 10 - 7
src/core/notebookconfigmgr/vxnotebookconfigmgrfactory.cpp

@@ -7,29 +7,32 @@
 
 using namespace vnotex;
 
+const QString VXNotebookConfigMgrFactory::c_name = QStringLiteral("vx.vnotex");
+
+const QString VXNotebookConfigMgrFactory::c_displayName = QObject::tr("VNoteX Notebook Configuration");
+
+const QString VXNotebookConfigMgrFactory::c_description = QObject::tr("Built-in VNoteX notebook configuration");
+
 VXNotebookConfigMgrFactory::VXNotebookConfigMgrFactory()
 {
 }
 
 QString VXNotebookConfigMgrFactory::getName() const
 {
-    return QStringLiteral("vx.vnotex");
+    return c_name;
 }
 
 QString VXNotebookConfigMgrFactory::getDisplayName() const
 {
-    return QObject::tr("VNoteX Notebook Configuration");
+    return c_displayName;
 }
 
 QString VXNotebookConfigMgrFactory::getDescription() const
 {
-    return QObject::tr("Built-in VNoteX notebook configuration");
+    return c_description;
 }
 
 QSharedPointer<INotebookConfigMgr> VXNotebookConfigMgrFactory::createNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend)
 {
-    return QSharedPointer<VXNotebookConfigMgr>::create(getName(),
-                                                       getDisplayName(),
-                                                       getDescription(),
-                                                       p_backend);
+    return QSharedPointer<VXNotebookConfigMgr>::create(p_backend);
 }

+ 6 - 0
src/core/notebookconfigmgr/vxnotebookconfigmgrfactory.h

@@ -19,6 +19,12 @@ namespace vnotex
         QString getDescription()const Q_DECL_OVERRIDE;
 
         QSharedPointer<INotebookConfigMgr> createNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend) Q_DECL_OVERRIDE;
+
+        static const QString c_name;
+
+        static const QString c_displayName;
+
+        static const QString c_description;
     };
 } // ns vnotex
 

+ 0 - 7
src/core/sessionconfig.cpp

@@ -114,11 +114,7 @@ void SessionConfig::loadCore(const QJsonObject &p_session)
     if (!isUndefinedKey(coreObj, QStringLiteral("system_title_bar"))) {
         m_systemTitleBarEnabled = readBool(coreObj, QStringLiteral("system_title_bar"));
     } else {
-#ifdef Q_OS_WIN
-        m_systemTitleBarEnabled = false;
-#else
         m_systemTitleBarEnabled = true;
-#endif
     }
 
     if (!isUndefinedKey(coreObj, QStringLiteral("minimize_to_system_tray"))) {
@@ -318,9 +314,6 @@ void SessionConfig::doVersionSpecificOverride()
 {
     // In a new version, we may want to change one value by force.
     // SHOULD set the in memory variable only, or will override the notebook list.
-#ifdef Q_OS_WIN
-    m_systemTitleBarEnabled = false;
-#endif
 }
 
 const ExportOption &SessionConfig::getExportOption() const

+ 0 - 12
src/core/widgetconfig.cpp

@@ -31,7 +31,6 @@ void WidgetConfig::init(const QJsonObject &p_app,
 
     {
         m_nodeExplorerViewOrder = READINT(QStringLiteral("node_explorer_view_order"));
-        m_nodeExplorerRecycleBinNodeVisible = READBOOL(QStringLiteral("node_explorer_recycle_bin_node_visible"));
         m_nodeExplorerExternalFilesVisible = READBOOL(QStringLiteral("node_explorer_external_files_visible"));
         m_nodeExplorerAutoImportExternalFilesEnabled = READBOOL(QStringLiteral("node_explorer_auto_import_external_files_enabled"));
         m_nodeExplorerCloseBeforeOpenWithEnabled = READBOOL(QStringLiteral("node_explorer_close_before_open_with_enabled"));
@@ -55,7 +54,6 @@ QJsonObject WidgetConfig::toJson() const
     obj[QStringLiteral("find_and_replace_options")] = static_cast<int>(m_findAndReplaceOptions);
 
     obj[QStringLiteral("node_explorer_view_order")] = m_nodeExplorerViewOrder;
-    obj[QStringLiteral("node_explorer_recycle_bin_node_visible")] = m_nodeExplorerRecycleBinNodeVisible;
     obj[QStringLiteral("node_explorer_external_files_visible")] = m_nodeExplorerExternalFilesVisible;
     obj[QStringLiteral("node_explorer_auto_import_external_files_enabled")] = m_nodeExplorerAutoImportExternalFilesEnabled;
     obj[QStringLiteral("node_explorer_close_before_open_with_enabled")] = m_nodeExplorerCloseBeforeOpenWithEnabled;
@@ -108,16 +106,6 @@ void WidgetConfig::setNodeExplorerViewOrder(int p_viewOrder)
     updateConfig(m_nodeExplorerViewOrder, p_viewOrder, this);
 }
 
-bool WidgetConfig::isNodeExplorerRecycleBinNodeVisible() const
-{
-    return m_nodeExplorerRecycleBinNodeVisible;
-}
-
-void WidgetConfig::setNodeExplorerRecycleBinNodeVisible(bool p_visible)
-{
-    updateConfig(m_nodeExplorerRecycleBinNodeVisible, p_visible, this);
-}
-
 bool WidgetConfig::isNodeExplorerExternalFilesVisible() const
 {
     return m_nodeExplorerExternalFilesVisible;

+ 0 - 5
src/core/widgetconfig.h

@@ -30,9 +30,6 @@ namespace vnotex
         int getNodeExplorerViewOrder() const;
         void setNodeExplorerViewOrder(int p_viewOrder);
 
-        bool isNodeExplorerRecycleBinNodeVisible() const;
-        void setNodeExplorerRecycleBinNodeVisible(bool p_visible);
-
         bool isNodeExplorerExternalFilesVisible() const;
         void setNodeExplorerExternalFilesVisible(bool p_visible);
 
@@ -63,8 +60,6 @@ namespace vnotex
 
         int m_nodeExplorerViewOrder = 0;
 
-        bool m_nodeExplorerRecycleBinNodeVisible = false;
-
         bool m_nodeExplorerExternalFilesVisible = true;
 
         bool m_nodeExplorerAutoImportExternalFilesEnabled = true;

+ 0 - 1
src/data/core/vnotex.json

@@ -373,7 +373,6 @@
         "find_and_replace_options" : 16,
         "//comment" : "View order of the node explorer",
         "node_explorer_view_order" : 0,
-        "node_explorer_recycle_bin_node_visible" : false,
         "node_explorer_external_files_visible" : true,
         "node_explorer_auto_import_external_files_enabled" : true,
         "//comment" : "Whether close the file before opening it with external program",

+ 0 - 5
src/search/searcher.cpp

@@ -470,11 +470,6 @@ bool Searcher::firstPhaseSearch(Notebook *p_notebook, QVector<SearchSecondPhaseI
             return true;
         }
 
-        if (p_notebook->isRecycleBinNode(child.data())) {
-            qDebug() << "skipped searching recycle bin";
-            continue;
-        }
-
         if (child->hasContent() && testTarget(SearchTarget::SearchFile)) {
             if (!firstPhaseSearch(child.data(), p_secondPhaseItems)) {
                 return false;

+ 1 - 1
src/utils/fileutils.cpp

@@ -118,7 +118,7 @@ void FileUtils::copyFile(const QString &p_filePath,
     QDir dir;
     if (!dir.mkpath(PathUtils::parentDirPath(p_destPath))) {
         Exception::throwOne(Exception::Type::FailToCreateDir,
-            QString("failed to create directory: %1").arg(PathUtils::parentDirPath(p_destPath)));
+                            QString("failed to create directory: %1").arg(PathUtils::parentDirPath(p_destPath)));
     }
 
     bool failed = false;

+ 7 - 3
src/widgets/dialogs/managenotebooksdialog.cpp

@@ -260,7 +260,7 @@ void ManageNotebooksDialog::closeNotebook(const Notebook *p_notebook)
     int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Question,
                                                  tr("Close notebook (%1)?")
                                                    .arg(p_notebook->getName()),
-                                                 tr("The notebook could be imported again later."),
+                                                 tr("The notebook could be opened by VNote again."),
                                                  tr("Notebook location: %1").arg(p_notebook->getRootFolderAbsolutePath()),
                                                  this);
     if (ret != QMessageBox::Ok) {
@@ -289,14 +289,18 @@ void ManageNotebooksDialog::removeNotebook(const Notebook *p_notebook)
 
     int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
                                                  tr("Please close the notebook in VNote first and delete the notebook root folder files manually."),
-                                                 tr("Press \"Ok\" to open the location of the notebook root folder."),
+                                                 tr("Press \"Ok\" to close the notebook and open the location of the notebook root folder."),
                                                  tr("Notebook location: %1").arg(p_notebook->getRootFolderAbsolutePath()),
                                                  this);
     if (ret != QMessageBox::Ok) {
         return;
     }
 
-    WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(p_notebook->getRootFolderAbsolutePath()));
+    const auto rootFolder = p_notebook->getRootFolderAbsolutePath();
+
+    closeNotebook(p_notebook);
+
+    WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(rootFolder));
 }
 
 bool ManageNotebooksDialog::checkUnsavedChanges()

+ 2 - 2
src/widgets/dialogs/newfolderdialog.cpp

@@ -57,8 +57,8 @@ bool NewFolderDialog::validateNameInput(QString &p_msg)
         return false;
     }
 
-    if (m_infoWidget->getParentNode()->containsChild(name, false)) {
-        p_msg = tr("Name conflicts with existing folder.");
+    if (!m_infoWidget->getParentNode()->isLegalNameForNewChild(name)) {
+        p_msg = tr("Name conflicts with existing or built-in folder.");
         return false;
     }
 

+ 2 - 2
src/widgets/dialogs/newnotedialog.cpp

@@ -93,8 +93,8 @@ bool NewNoteDialog::validateNameInput(QString &p_msg)
         return false;
     }
 
-    if (m_infoWidget->getParentNode()->containsChild(name, false)) {
-        p_msg = tr("Name conflicts with existing note.");
+    if (!m_infoWidget->getParentNode()->isLegalNameForNewChild(name)) {
+        p_msg = tr("Name conflicts with existing or built-in note.");
         return false;
     }
 

+ 1 - 1
src/widgets/mainwindow.cpp

@@ -629,7 +629,7 @@ void MainWindow::exportNotes()
     auto currentNotebook = m_notebookExplorer->currentNotebook().data();
     auto viewWindow = m_viewArea->getCurrentViewWindow();
     auto folderNode = m_notebookExplorer->currentExploredFolderNode();
-    if (folderNode && (folderNode->isRoot() || currentNotebook->isRecycleBinNode(folderNode))) {
+    if (folderNode && (folderNode->isRoot())) {
         folderNode = nullptr;
     }
     auto noteNode = m_notebookExplorer->currentExploredNode();

+ 30 - 46
src/widgets/notebookexplorer.cpp

@@ -81,7 +81,6 @@ void NotebookExplorer::setupUI()
 
     const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig();
     m_nodeExplorer = new NotebookNodeExplorer(this);
-    m_nodeExplorer->setRecycleBinNodeVisible(widgetConfig.isNodeExplorerRecycleBinNodeVisible());
     m_nodeExplorer->setViewOrder(widgetConfig.getNodeExplorerViewOrder());
     m_nodeExplorer->setExternalFilesVisible(widgetConfig.isNodeExplorerExternalFilesVisible());
     connect(m_nodeExplorer, &NotebookNodeExplorer::nodeActivated,
@@ -122,15 +121,9 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
     }
 
     {
-        auto btn = titleBar->addActionButton(QStringLiteral("recycle_bin.svg"), tr("Toggle Recycle Bin Node"));
-        btn->defaultAction()->setCheckable(true);
-        btn->defaultAction()->setChecked(widgetConfig.isNodeExplorerRecycleBinNodeVisible());
-        connect(btn, &QToolButton::triggered,
-                this, [this](QAction *p_act) {
-                    const bool checked = p_act->isChecked();
-                    ConfigMgr::getInst().getWidgetConfig().setNodeExplorerRecycleBinNodeVisible(checked);
-                    m_nodeExplorer->setRecycleBinNodeVisible(checked);
-                });
+        auto recycleBinMenu = WidgetsFactory::createMenu(titleBar);
+        setupRecycleBinMenu(recycleBinMenu);
+        titleBar->addActionButton(QStringLiteral("recycle_bin.svg"), tr("Recycle Bin"), recycleBinMenu);
     }
 
     {
@@ -260,15 +253,6 @@ void NotebookExplorer::newFolder()
         return;
     }
 
-    if (m_currentNotebook->isRecycleBinNode(node)) {
-        node = m_currentNotebook->getRootNode().data();
-    } else if (m_currentNotebook->isNodeInRecycleBin(node)) {
-        MessageBoxHelper::notify(MessageBoxHelper::Information,
-                                 tr("Could not create folder within Recycle Bin."),
-                                 VNoteX::getInst().getMainWindow());
-        return;
-    }
-
     NewFolderDialog dialog(node, VNoteX::getInst().getMainWindow());
     if (dialog.exec() == QDialog::Accepted) {
         m_nodeExplorer->setCurrentNode(dialog.getNewNode().data());
@@ -282,15 +266,6 @@ void NotebookExplorer::newNote()
         return;
     }
 
-    if (m_currentNotebook->isRecycleBinNode(node)) {
-        node = m_currentNotebook->getRootNode().data();
-    } else if (m_currentNotebook->isNodeInRecycleBin(node)) {
-        MessageBoxHelper::notify(MessageBoxHelper::Information,
-                                 tr("Could not create note within Recycle Bin."),
-                                 VNoteX::getInst().getMainWindow());
-        return;
-    }
-
     NewNoteDialog dialog(node, VNoteX::getInst().getMainWindow());
     if (dialog.exec() == QDialog::Accepted) {
         m_nodeExplorer->setCurrentNode(dialog.getNewNode().data());
@@ -340,15 +315,6 @@ void NotebookExplorer::importFile()
         return;
     }
 
-    if (m_currentNotebook->isRecycleBinNode(node)) {
-        node = m_currentNotebook->getRootNode().data();
-    } else if (m_currentNotebook->isNodeInRecycleBin(node)) {
-        MessageBoxHelper::notify(MessageBoxHelper::Information,
-                                 tr("Could not create note within Recycle Bin."),
-                                 VNoteX::getInst().getMainWindow());
-        return;
-    }
-
     static QString lastFolderPath = QDir::homePath();
     QStringList files = QFileDialog::getOpenFileNames(VNoteX::getInst().getMainWindow(),
                                                       tr("Select Files To Import"),
@@ -381,15 +347,6 @@ void NotebookExplorer::importFolder()
         return;
     }
 
-    if (m_currentNotebook->isRecycleBinNode(node)) {
-        node = m_currentNotebook->getRootNode().data();
-    } else if (m_currentNotebook->isNodeInRecycleBin(node)) {
-        MessageBoxHelper::notify(MessageBoxHelper::Information,
-                                 tr("Could not create folder within Recycle Bin."),
-                                 VNoteX::getInst().getMainWindow());
-        return;
-    }
-
     ImportFolderDialog dialog(node, VNoteX::getInst().getMainWindow());
     if (dialog.exec() == QDialog::Accepted) {
         m_nodeExplorer->setCurrentNode(dialog.getNewNode().data());
@@ -483,6 +440,33 @@ void NotebookExplorer::setupViewMenu(QMenu *p_menu)
             });
 }
 
+void NotebookExplorer::setupRecycleBinMenu(QMenu *p_menu)
+{
+    p_menu->addAction(tr("Open Recycle Bin"),
+                      this,
+                      [this]() {
+                          if (m_currentNotebook) {
+                              WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(m_currentNotebook->getRecycleBinFolderAbsolutePath()));
+                          }
+                      });
+
+    p_menu->addAction(tr("Empty Recycle Bin"),
+                      this,
+                      [this]() {
+                          if (!m_currentNotebook) {
+                              return;
+                          }
+                          int okRet = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
+                              tr("Empty the recycle bin of notebook (%1)?").arg(m_currentNotebook->getName()),
+                              tr("CAUTION! All the files under the recycle bin folder will be deleted and unrecoverable!"),
+                              tr("Recycle bin folder: %1").arg(m_currentNotebook->getRecycleBinFolderAbsolutePath()),
+                              VNoteX::getInst().getMainWindow());
+                          if (okRet == QMessageBox::Ok) {
+                              m_currentNotebook->emptyRecycleBin();
+                          }
+                      });
+}
+
 void NotebookExplorer::saveSession()
 {
     updateSession();

+ 2 - 0
src/widgets/notebookexplorer.h

@@ -69,6 +69,8 @@ namespace vnotex
 
         void setupViewMenu(QMenu *p_menu);
 
+        void setupRecycleBinMenu(QMenu *p_menu);
+
         void saveSession();
 
         void loadSession();

+ 46 - 244
src/widgets/notebooknodeexplorer.cpp

@@ -192,13 +192,11 @@ void NotebookNodeExplorer::initNodeIcons() const
 
     const QString folderIconName("folder_node.svg");
     const QString fileIconName("file_node.svg");
-    const QString recycleBinIconName("recycle_bin.svg");
 
     s_nodeIcons[NodeIcon::FolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg);
     s_nodeIcons[NodeIcon::FileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), fg);
     s_nodeIcons[NodeIcon::InvalidFolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), invalidFg);
     s_nodeIcons[NodeIcon::InvalidFileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), invalidFg);
-    s_nodeIcons[NodeIcon::RecycleBinNode] = IconUtils::fetchIcon(themeMgr.getIconFile(recycleBinIconName), fg);
     s_nodeIcons[NodeIcon::ExternalFolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), externalFg);
     s_nodeIcons[NodeIcon::ExternalFileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), externalFg);
 }
@@ -341,7 +339,6 @@ void NotebookNodeExplorer::generateNodeTree()
     if (currentNode) {
         setCurrentNode(currentNode);
     } else {
-        // Do not focus the recycle bin.
         focusNormalNode();
     }
 
@@ -352,12 +349,6 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const
 {
     Q_ASSERT(p_node->isLoaded() && p_node->isContainer());
 
-    // Render recycle bin node first.
-    auto recycleBinNode = m_notebook->getRecycleBinNode();
-    if (recycleBinNode) {
-        loadRecycleBinNode(recycleBinNode.data());
-    }
-
     // External children.
     if (m_externalFilesVisible) {
         auto externalChildren = p_node->fetchExternalChildren();
@@ -372,10 +363,6 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const
     auto children = p_node->getChildren();
     sortNodes(children);
     for (const auto &child : children) {
-        if (recycleBinNode == child) {
-            continue;
-        }
-
         auto item = new QTreeWidgetItem(m_masterExplorer);
         loadNode(item, child.data(), 1);
     }
@@ -444,43 +431,6 @@ void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, i
     }
 }
 
-void NotebookNodeExplorer::loadRecycleBinNode(Node *p_node) const
-{
-    if (!m_recycleBinNodeVisible) {
-        return;
-    }
-
-    auto item = new QTreeWidgetItem();
-    item->setWhatsThis(Column::Name,
-                       tr("Recycle bin of this notebook. Deleted files could be found here. "
-                          "It is organized in folders named by date. Nodes could be moved to "
-                          "other folders by Cut and Paste."));
-    m_masterExplorer->insertTopLevelItem(0, item);
-
-    loadRecycleBinNode(item, p_node, 1);
-}
-
-void NotebookNodeExplorer::loadRecycleBinNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
-{
-    if (!m_recycleBinNodeVisible) {
-        return;
-    }
-
-    if (!p_node->isLoaded()) {
-        p_node->load();
-    }
-
-    clearTreeWigetItemChildren(p_item);
-
-    setItemNodeData(p_item, NodeData(p_node, true));
-    p_item->setText(Column::Name, tr("Recycle Bin"));
-    p_item->setIcon(Column::Name, getNodeItemIcon(p_node));
-
-    loadChildren(p_item, p_node, p_level - 1);
-
-    // No need to restore state.
-}
-
 void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const
 {
     setItemNodeData(p_item, NodeData(p_node, p_loaded));
@@ -502,10 +452,6 @@ const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const
     if (p_node->hasContent()) {
         return p_node->exists() ? s_nodeIcons[NodeIcon::FileNode] : s_nodeIcons[NodeIcon::InvalidFileNode];
     } else {
-        if (p_node->getUse() == Node::Use::RecycleBin) {
-            return s_nodeIcons[NodeIcon::RecycleBinNode];
-        }
-
         return p_node->exists() ? s_nodeIcons[NodeIcon::FolderNode] : s_nodeIcons[NodeIcon::InvalidFolderNode];
     }
 }
@@ -561,20 +507,9 @@ void NotebookNodeExplorer::updateNode(Node *p_node)
     if (item) {
         bool expanded = item->isExpanded();
         item->setExpanded(false);
-
-        if (m_notebook->isRecycleBinNode(p_node)) {
-            loadRecycleBinNode(item, p_node, 1);
-        } else {
-            loadNode(item, p_node, 1);
-        }
-
+        loadNode(item, p_node, 1);
         item->setExpanded(expanded);
     } else {
-        if (m_notebook->isRecycleBinNode(p_node) && !m_recycleBinNodeVisible) {
-            // No need to update.
-            return;
-        }
-
         saveNotebookTreeState(false);
 
         generateNodeTree();
@@ -762,106 +697,63 @@ void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu)
 void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_node)
 {
     const int selectedSize = m_masterExplorer->selectedItems().size();
-    if (m_notebook->isRecycleBinNode(p_node)) {
-        // Recycle bin node.
-        createAndAddAction(Action::Reload, p_menu);
-
-        createAndAddAction(Action::ReloadIndex, p_menu);
-
-        if (selectedSize == 1) {
-            createAndAddAction(Action::EmptyRecycleBin, p_menu);
-
-            createAndAddAction(Action::OpenLocation, p_menu);
-        }
-    } else if (m_notebook->isNodeInRecycleBin(p_node)) {
-        // Node in recycle bin.
-        createAndAddAction(Action::Open, p_menu);
-
-        addOpenWithMenu(p_menu);
-
-        p_menu->addSeparator();
-
-        if (selectedSize == 1 && p_node->isContainer()) {
-            createAndAddAction(Action::ExpandAll, p_menu);
-        }
-
-        p_menu->addSeparator();
 
-        createAndAddAction(Action::Cut, p_menu);
-
-        createAndAddAction(Action::DeleteFromRecycleBin, p_menu);
-
-        p_menu->addSeparator();
+    createAndAddAction(Action::Open, p_menu);
 
-        createAndAddAction(Action::Reload, p_menu);
+    addOpenWithMenu(p_menu);
 
-        createAndAddAction(Action::ReloadIndex, p_menu);
+    p_menu->addSeparator();
 
-        if (selectedSize == 1) {
-            p_menu->addSeparator();
+    if (selectedSize == 1 && p_node->isContainer()) {
+        createAndAddAction(Action::ExpandAll, p_menu);
+    }
 
-            createAndAddAction(Action::CopyPath, p_menu);
+    p_menu->addSeparator();
 
-            createAndAddAction(Action::OpenLocation, p_menu);
-        }
-    } else {
-        createAndAddAction(Action::Open, p_menu);
+    createAndAddAction(Action::NewNote, p_menu);
 
-        addOpenWithMenu(p_menu);
+    createAndAddAction(Action::NewFolder, p_menu);
 
-        p_menu->addSeparator();
+    p_menu->addSeparator();
 
-        if (selectedSize == 1 && p_node->isContainer()) {
-            createAndAddAction(Action::ExpandAll, p_menu);
-        }
+    createAndAddAction(Action::Copy, p_menu);
 
-        p_menu->addSeparator();
+    createAndAddAction(Action::Cut, p_menu);
 
-        createAndAddAction(Action::NewNote, p_menu);
-
-        createAndAddAction(Action::NewFolder, p_menu);
+    if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) {
+        createAndAddAction(Action::Paste, p_menu);
+    }
 
-        p_menu->addSeparator();
+    createAndAddAction(Action::Delete, p_menu);
 
-        createAndAddAction(Action::Copy, p_menu);
+    createAndAddAction(Action::RemoveFromConfig, p_menu);
 
-        createAndAddAction(Action::Cut, p_menu);
+    p_menu->addSeparator();
 
-        if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) {
-            createAndAddAction(Action::Paste, p_menu);
-        }
+    createAndAddAction(Action::Reload, p_menu);
 
-        createAndAddAction(Action::Delete, p_menu);
+    createAndAddAction(Action::ReloadIndex, p_menu);
 
-        createAndAddAction(Action::RemoveFromConfig, p_menu);
+    createAndAddAction(Action::Sort, p_menu);
 
+    if (selectedSize == 1
+        && m_notebook->tag()
+        && !p_node->isContainer()) {
         p_menu->addSeparator();
 
-        createAndAddAction(Action::Reload, p_menu);
-
-        createAndAddAction(Action::ReloadIndex, p_menu);
-
-        createAndAddAction(Action::Sort, p_menu);
-
-        if (selectedSize == 1
-            && m_notebook->tag()
-            && !p_node->isContainer()) {
-            p_menu->addSeparator();
-
-            createAndAddAction(Action::Tag, p_menu);
-        }
+        createAndAddAction(Action::Tag, p_menu);
+    }
 
-        p_menu->addSeparator();
+    p_menu->addSeparator();
 
-        createAndAddAction(Action::PinToQuickAccess, p_menu);
+    createAndAddAction(Action::PinToQuickAccess, p_menu);
 
-        if (selectedSize == 1) {
-            createAndAddAction(Action::CopyPath, p_menu);
+    if (selectedSize == 1) {
+        createAndAddAction(Action::CopyPath, p_menu);
 
-            createAndAddAction(Action::OpenLocation, p_menu);
+        createAndAddAction(Action::OpenLocation, p_menu);
 
-            createAndAddAction(Action::Properties, p_menu);
-        }
+        createAndAddAction(Action::Properties, p_menu);
     }
 }
 
@@ -901,7 +793,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
     switch (p_act) {
     case Action::NewNote:
         act = new QAction(generateMenuActionIcon("new_note.svg"),
-                          tr("New N&ote"),
+                          tr("New &Note"),
                           p_parent);
         connect(act, &QAction::triggered,
                 this, []() {
@@ -1033,47 +925,10 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
                 });
         break;
 
-    case Action::EmptyRecycleBin:
-        act = new QAction(tr("&Empty"), p_parent);
-        connect(act, &QAction::triggered,
-                this, [this]() {
-                    auto rbNode = m_notebook->getRecycleBinNode().data();
-                    auto rbNodePath = rbNode->fetchAbsolutePath();
-                    int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
-                                                                 tr("Empty the recycle bin of this notebook?"),
-                                                                 tr("All files in recycle bin will be deleted permanently."),
-                                                                 tr("Location of recycle bin: %1").arg(rbNodePath));
-                    if (ret != QMessageBox::Ok) {
-                        return;
-                    }
-
-                    try {
-                        m_notebook->emptyNode(rbNode, true);
-                    } catch (Exception &p_e) {
-                        MessageBoxHelper::notify(MessageBoxHelper::Critical,
-                                                 tr("Failed to empty recycle bin (%1) (%2).").arg(rbNodePath, p_e.what()),
-                                                 VNoteX::getInst().getMainWindow());
-                    }
-
-                    updateNode(rbNode);
-                });
-        break;
-
     case Action::Delete:
         act = new QAction(tr("&Delete"), p_parent);
         connect(act, &QAction::triggered,
-                this, [this]() {
-                    removeSelectedNodes(false);
-                });
-        break;
-
-    case Action::DeleteFromRecycleBin:
-        // It is fine to have &D with Action::Delete since they won't be at the same context.
-        act = new QAction(tr("&Delete From Recycle Bin"), p_parent);
-        connect(act, &QAction::triggered,
-                this, [this]() {
-                    removeSelectedNodes(true);
-                });
+                this, &NotebookNodeExplorer::removeSelectedNodes);
         break;
 
     case Action::RemoveFromConfig:
@@ -1417,21 +1272,12 @@ void NotebookNodeExplorer::selectNodes(const QVector<const Node *> &p_nodes)
     }
 }
 
-void NotebookNodeExplorer::removeSelectedNodes(bool p_skipRecycleBin)
+void NotebookNodeExplorer::removeSelectedNodes()
 {
-    QString text;
-    QString info;
-    if (p_skipRecycleBin) {
-        text = tr("Delete these folders and notes permanently?");
-        info = tr("Files will be deleted permanently and could not be found even "
-                  "in operating system's recycle bin.");
-    } else {
-        text = tr("Delete these folders and notes?");
-        info = tr("Deleted files could be found in the recycle bin of notebook.");
-    }
-
+    const QString text = tr("Delete these folders and notes?");
+    const QString info = tr("Deleted files could be found in the recycle bin of notebook.");
     auto nodes = confirmSelectedNodes(tr("Confirm Deletion"), text, info);
-    removeNodes(nodes, p_skipRecycleBin, false);
+    removeNodes(nodes, false);
 }
 
 QVector<Node *> NotebookNodeExplorer::confirmSelectedNodes(const QString &p_title,
@@ -1471,9 +1317,7 @@ QVector<Node *> NotebookNodeExplorer::confirmSelectedNodes(const QString &p_titl
     return nodesToDelete;
 }
 
-void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes,
-                                       bool p_skipRecycleBin,
-                                       bool p_configOnly)
+void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes, bool p_configOnly)
 {
     if (p_nodes.isEmpty()) {
         return;
@@ -1494,8 +1338,8 @@ void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes,
                 continue;
             }
 
-            if (p_configOnly || p_skipRecycleBin) {
-                m_notebook->removeNode(node, false, p_configOnly);
+            if (p_configOnly) {
+                m_notebook->removeNode(node, false, true);
             } else {
                 m_notebook->moveNodeToRecycleBin(node);
             }
@@ -1515,10 +1359,6 @@ void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes,
         updateNode(node);
     }
 
-    if (!p_configOnly && !p_skipRecycleBin && m_recycleBinNodeVisible) {
-        updateNode(m_notebook->getRecycleBinNode().data());
-    }
-
     VNoteX::getInst().showStatusMessageShort(tr("Deleted/Removed %n item(s)", "", nrDeleted));
 }
 
@@ -1527,7 +1367,7 @@ void NotebookNodeExplorer::removeSelectedNodesFromConfig()
     auto nodes = confirmSelectedNodes(tr("Confirm Removal"),
                                       tr("Remove these folders and notes from index?"),
                                       tr("Files are not touched but just removed from notebook index."));
-    removeNodes(nodes, false, true);
+    removeNodes(nodes, true);
 }
 
 void NotebookNodeExplorer::filterAwayChildrenNodes(QVector<Node *> &p_nodes)
@@ -1569,29 +1409,6 @@ bool NotebookNodeExplorer::allSelectedItemsSameType() const
         }
     }
 
-    if (type == NodeData::NodeType::Node) {
-        bool hasNormalNode = false;
-        bool hasNodeInRecycleBin = false;
-        for (auto &item : items) {
-            auto node = getItemNodeData(item).getNode();
-            if (m_notebook->isRecycleBinNode(node)) {
-                return false;
-            } else if (m_notebook->isNodeInRecycleBin(node)) {
-                if (hasNormalNode) {
-                    return false;
-                }
-
-                hasNodeInRecycleBin = true;
-            } else {
-                if (hasNodeInRecycleBin) {
-                    return false;
-                }
-
-                hasNormalNode = true;
-            }
-        }
-    }
-
     return true;
 }
 
@@ -1603,12 +1420,11 @@ void NotebookNodeExplorer::reload()
 void NotebookNodeExplorer::focusNormalNode()
 {
     auto item = m_masterExplorer->currentItem();
-    if (item && (!m_recycleBinNodeVisible || item != m_masterExplorer->topLevelItem(0))) {
-        // Not recycle bin.
+    if (item) {
         return;
     }
 
-    m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(m_recycleBinNodeVisible ? 1 : 0));
+    m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(0));
 }
 
 void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes) const
@@ -1688,16 +1504,6 @@ void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes, int
     }
 }
 
-void NotebookNodeExplorer::setRecycleBinNodeVisible(bool p_visible)
-{
-    if (m_recycleBinNodeVisible == p_visible) {
-        return;
-    }
-
-    m_recycleBinNodeVisible = p_visible;
-    reload();
-}
-
 void NotebookNodeExplorer::setExternalFilesVisible(bool p_visible)
 {
     if (m_externalFilesVisible == p_visible) {
@@ -1748,11 +1554,7 @@ void NotebookNodeExplorer::manualSort()
         const auto &children = parentNode->getChildrenRef();
         for (int i = 0; i < children.size(); ++i) {
             const auto &child = children[i];
-            if (m_notebook->isRecycleBinNode(child.data())) {
-                continue;
-            }
-
-            bool selected = sortFolders ? child->isContainer() : !child->isContainer();
+            const bool selected = sortFolders ? child->isContainer() : !child->isContainer();
             if (selected) {
                 selectedIdx.push_back(i);
 

+ 2 - 15
src/widgets/notebooknodeexplorer.h

@@ -103,8 +103,6 @@ namespace vnotex
 
         void reload();
 
-        void setRecycleBinNodeVisible(bool p_visible);
-
         void setViewOrder(int p_order);
 
         void setExternalFilesVisible(bool p_visible);
@@ -146,9 +144,7 @@ namespace vnotex
             Copy,
             Cut,
             Paste,
-            EmptyRecycleBin,
             Delete,
-            DeleteFromRecycleBin,
             RemoveFromConfig,
             Sort,
             Reload,
@@ -180,10 +176,6 @@ namespace vnotex
 
         void loadNode(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const;
 
-        void loadRecycleBinNode(Node *p_node) const;
-
-        void loadRecycleBinNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const;
-
         void fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const;
 
         void fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const;
@@ -225,7 +217,7 @@ namespace vnotex
 
         QPair<QVector<Node *>, QVector<QSharedPointer<ExternalNode>>> getSelectedNodes() const;
 
-        void removeSelectedNodes(bool p_skipRecycleBin);
+        void removeSelectedNodes();
 
         void removeSelectedNodesFromConfig();
 
@@ -241,8 +233,7 @@ namespace vnotex
 
         void selectNodes(const QVector<const Node *> &p_nodes);
 
-        // @p_skipRecycleBin is irrelevant if @p_configOnly is true.
-        void removeNodes(QVector<Node *> p_nodes, bool p_skipRecycleBin, bool p_configOnly);
+        void removeNodes(QVector<Node *> p_nodes, bool p_configOnly);
 
         void filterAwayChildrenNodes(QVector<Node *> &p_nodes);
 
@@ -251,7 +242,6 @@ namespace vnotex
         // Check if all selected items are the same type for operations.
         bool allSelectedItemsSameType() const;
 
-        // Skip the recycle bin node if possible.
         void focusNormalNode();
 
         void sortNodes(QVector<QSharedPointer<Node>> &p_nodes) const;
@@ -298,8 +288,6 @@ namespace vnotex
 
         QScopedPointer<NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>> m_navigationWrapper;
 
-        bool m_recycleBinNodeVisible = false;
-
         int m_viewOrder = ViewOrder::OrderedByConfiguration;
 
         bool m_externalFilesVisible = true;
@@ -312,7 +300,6 @@ namespace vnotex
             FileNode,
             InvalidFolderNode,
             InvalidFileNode,
-            RecycleBinNode,
             ExternalFolderNode,
             ExternalFileNode,
             MaxIcons

+ 1 - 1
src/widgets/searchpanel.cpp

@@ -448,7 +448,7 @@ SearchState SearchPanel::search(const QSharedPointer<SearchOption> &p_option)
             break;
         }
         auto folder = m_provider->getCurrentFolder();
-        if (folder && (folder->isRoot() || notebook->isRecycleBinNode(folder))) {
+        if (folder && (folder->isRoot())) {
             folder = nullptr;
         }
         if (!folder) {

+ 0 - 5
src/widgets/tagexplorer.cpp

@@ -239,11 +239,6 @@ void TagExplorer::updateNodeList(const QString &p_tag)
             continue;
         }
 
-        if (m_notebook->isNodeInRecycleBin(node.data())) {
-            qDebug() << "skipped node in recycle bin" << p_tag << pa;
-            continue;
-        }
-
         auto item = new QListWidgetItem(m_nodeList);
         item->setText(node->getName());
         item->setToolTip(NotebookNodeExplorer::generateToolTip(node.data()));