From 9895207dd44330d5fb897634f17954e0364f1b54 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sun, 14 Mar 2021 09:37:06 +0800 Subject: [PATCH] support external nodes (#1718) --- src/core/notebook/externalnode.cpp | 34 ++ src/core/notebook/externalnode.h | 41 ++ src/core/notebook/node.cpp | 62 ++- src/core/notebook/node.h | 21 +- src/core/notebook/notebook.cpp | 36 +- src/core/notebook/notebook.h | 6 +- src/core/notebook/notebook.pri | 2 + src/core/notebookbackend/inotebookbackend.h | 4 + .../notebookbackend/localnotebookbackend.cpp | 12 + .../notebookbackend/localnotebookbackend.h | 4 + .../notebookconfigmgr/inotebookconfigmgr.h | 6 +- .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 179 ++++++-- .../notebookconfigmgr/vxnotebookconfigmgr.h | 8 +- src/core/vnotex.h | 3 + src/core/widgetconfig.cpp | 48 +- src/core/widgetconfig.h | 22 +- src/data/core/vnotex.json | 8 +- src/data/extra/themes/moonlight/palette.json | 8 +- src/data/extra/themes/native/palette.json | 8 +- src/data/extra/themes/pure/palette.json | 8 +- src/export/exporter.cpp | 4 +- src/export/webviewexporter.cpp | 3 +- src/utils/pathutils.cpp | 4 +- src/widgets/editors/markdownvieweradapter.cpp | 11 + src/widgets/editors/markdownvieweradapter.h | 3 + src/widgets/markdownviewwindow.cpp | 2 + src/widgets/notebookexplorer.cpp | 63 ++- src/widgets/notebooknodeexplorer.cpp | 424 +++++++++++++++--- src/widgets/notebooknodeexplorer.h | 72 ++- src/widgets/titlebar.cpp | 5 + src/widgets/titlebar.h | 13 + 31 files changed, 931 insertions(+), 193 deletions(-) create mode 100644 src/core/notebook/externalnode.cpp create mode 100644 src/core/notebook/externalnode.h diff --git a/src/core/notebook/externalnode.cpp b/src/core/notebook/externalnode.cpp new file mode 100644 index 00000000..7d6d631b --- /dev/null +++ b/src/core/notebook/externalnode.cpp @@ -0,0 +1,34 @@ +#include "externalnode.h" + +#include "node.h" +#include + +using namespace vnotex; + +ExternalNode::ExternalNode(Node *p_parent, const QString &p_name, Type p_type) + : m_parentNode(p_parent), + m_name(p_name), + m_type(p_type) +{ + Q_ASSERT(m_parentNode); +} + +Node *ExternalNode::getNode() const +{ + return m_parentNode; +} + +const QString &ExternalNode::getName() const +{ + return m_name; +} + +bool ExternalNode::isFolder() const +{ + return m_type == Type::Folder; +} + +QString ExternalNode::fetchAbsolutePath() const +{ + return PathUtils::concatenateFilePath(m_parentNode->fetchAbsolutePath(), m_name); +} diff --git a/src/core/notebook/externalnode.h b/src/core/notebook/externalnode.h new file mode 100644 index 00000000..a8d033e8 --- /dev/null +++ b/src/core/notebook/externalnode.h @@ -0,0 +1,41 @@ +#ifndef EXTERNALNODE_H +#define EXTERNALNODE_H + +#include + +namespace vnotex +{ + class Node; + + // External node not managed by VNote. + class ExternalNode + { + public: + enum class Type + { + File, + Folder + }; + + ExternalNode(Node *p_parent, const QString &p_name, Type p_type); + + Node *getNode() const; + + const QString &getName() const; + + bool isFolder() const; + + QString fetchAbsolutePath() const; + + private: + // Parent node. + // We support only one level further the external folder. + Node *m_parentNode = nullptr; + + QString m_name; + + Type m_type = Type::File; + }; +} + +#endif // EXTERNALNODE_H diff --git a/src/core/notebook/node.cpp b/src/core/notebook/node.cpp index 668ccbd7..0f54c2b1 100644 --- a/src/core/notebook/node.cpp +++ b/src/core/notebook/node.cpp @@ -103,13 +103,13 @@ bool Node::containsChild(const QString &p_name, bool p_caseSensitive) const bool Node::containsChild(const QSharedPointer &p_node) const { - return getChildren().indexOf(p_node) != -1; + return m_children.indexOf(p_node) != -1; } QSharedPointer Node::findChild(const QString &p_name, bool p_caseSensitive) const { auto targetName = p_caseSensitive ? p_name : p_name.toLower(); - for (auto &child : getChildren()) { + for (const auto &child : m_children) { if (p_caseSensitive ? child->getName() == targetName : child->getName().toLower() == targetName) { return child; @@ -164,7 +164,12 @@ void Node::setModifiedTimeUtc() m_modifiedTimeUtc = QDateTime::currentDateTimeUtc(); } -const QVector> &Node::getChildren() const +const QVector> &Node::getChildrenRef() const +{ + return m_children; +} + +QVector> Node::getChildren() const { return m_children; } @@ -347,3 +352,54 @@ void Node::sortChildren(const QVector &p_beforeIdx, const QVector &p_a save(); } + +QVector> Node::fetchExternalChildren() const +{ + return getConfigMgr()->fetchExternalChildren(const_cast(this)); +} + +bool Node::containsContainerChild(const QString &p_name) const +{ + // TODO: we assume that m_children is sorted first the container children. + for (auto &child : m_children) { + if (!child->isContainer()) { + break; + } + + if (child->getName() == p_name) { + return true; + } + } + + return false; +} + +bool Node::containsContentChild(const QString &p_name) const +{ + // TODO: we assume that m_children is sorted: first the container children then content children. + for (int i = m_children.size() - 1; i >= 0; --i) { + if (m_children[i]->isContainer()) { + break; + } + + if (m_children[i]->getName() == p_name) { + return true; + } + } + + return false; +} + +bool Node::exists() const +{ + return m_flags & Flag::Exists; +} + +void Node::setExists(bool p_exists) +{ + if (p_exists) { + m_flags |= Flag::Exists; + } else { + m_flags &= ~Flag::Exists; + } +} diff --git a/src/core/notebook/node.h b/src/core/notebook/node.h index 70d60dcc..130eb57f 100644 --- a/src/core/notebook/node.h +++ b/src/core/notebook/node.h @@ -15,6 +15,7 @@ namespace vnotex class INotebookConfigMgr; class INotebookBackend; class File; + class ExternalNode; // Used when add/new a node. struct NodeParameters @@ -35,7 +36,9 @@ namespace vnotex Content = 0x1, // A node with children. Container = 0x2, - ReadOnly = 0x4 + ReadOnly = 0x4, + // Whether a node exists on disk. + Exists = 0x10 }; Q_DECLARE_FLAGS(Flags, Flag) @@ -86,6 +89,11 @@ namespace vnotex bool hasContent() const; + // Whether the node exists on disk. + bool exists() const; + + void setExists(bool p_exists); + Node::Flags getFlags() const; Node::Use getUse() const; @@ -98,7 +106,8 @@ namespace vnotex const QDateTime &getModifiedTimeUtc() const; void setModifiedTimeUtc(); - const QVector> &getChildren() const; + const QVector> &getChildrenRef() const; + QVector> getChildren() const; int getChildrenCount() const; QSharedPointer findChild(const QString &p_name, bool p_caseSensitive = true) const; @@ -107,12 +116,20 @@ namespace vnotex bool containsChild(const QSharedPointer &p_node) const; + // Case sensitive. + bool containsContainerChild(const QString &p_name) const; + + // Case sensitive. + bool containsContentChild(const QString &p_name) const; + void addChild(const QSharedPointer &p_node); void insertChild(int p_idx, const QSharedPointer &p_node); void removeChild(const QSharedPointer &p_node); + QVector> fetchExternalChildren() const; + void setParent(Node *p_parent); Node *getParent() const; diff --git a/src/core/notebook/notebook.cpp b/src/core/notebook/notebook.cpp index a0625564..5cc45d03 100644 --- a/src/core/notebook/notebook.cpp +++ b/src/core/notebook/notebook.cpp @@ -161,7 +161,7 @@ const QSharedPointer &Notebook::getRootNode() const QSharedPointer Notebook::getRecycleBinNode() const { auto root = getRootNode(); - auto children = root->getChildren(); + const auto &children = root->getChildrenRef(); auto it = std::find_if(children.begin(), children.end(), [this](const QSharedPointer &p_node) { @@ -203,7 +203,7 @@ QSharedPointer Notebook::loadNodeByPath(const QString &p_path) relativePath = p_path; } - return m_configMgr->loadNodeByPath(m_root, relativePath); + return m_configMgr->loadNodeByPath(getRootNode(), relativePath); } QSharedPointer Notebook::copyNodeAsChildOf(const QSharedPointer &p_src, Node *p_dest, bool p_move) @@ -227,17 +227,15 @@ QSharedPointer Notebook::copyNodeAsChildOf(const QSharedPointer &p_s void Notebook::removeNode(const QSharedPointer &p_node, bool p_force, bool p_configOnly) { + Q_ASSERT(p_node && !p_node->isRoot()); Q_ASSERT(p_node->getNotebook() == this); m_configMgr->removeNode(p_node, p_force, p_configOnly); } -void Notebook::removeNode(const Node *p_node, bool p_force, bool p_configOnly) +void Notebook::removeNode(Node *p_node, bool p_force, bool p_configOnly) { - Q_ASSERT(p_node && !p_node->isRoot()); - auto children = p_node->getParent()->getChildren(); - auto it = std::find(children.begin(), children.end(), p_node); - Q_ASSERT(it != children.end()); - removeNode(*it, p_force, p_configOnly); + Q_ASSERT(p_node); + removeNode(p_node->sharedFromThis(), p_force, p_configOnly); } bool Notebook::isRecycleBinNode(const Node *p_node) const @@ -261,22 +259,14 @@ bool Notebook::isNodeInRecycleBin(const Node *p_node) const return false; } -void Notebook::moveNodeToRecycleBin(const Node *p_node) +void Notebook::moveNodeToRecycleBin(Node *p_node) { - Q_ASSERT(p_node && !p_node->isRoot()); - auto children = p_node->getParent()->getChildren(); - for (auto &child : children) { - if (p_node == child) { - moveNodeToRecycleBin(child); - return; - } - } - - Q_ASSERT(false); + moveNodeToRecycleBin(p_node->sharedFromThis()); } void Notebook::moveNodeToRecycleBin(const QSharedPointer &p_node) { + Q_ASSERT(p_node && !p_node->isRoot()); auto destNode = getOrCreateRecycleBinDateNode(); copyNodeAsChildOf(p_node, destNode.data(), true); } @@ -299,8 +289,9 @@ QSharedPointer Notebook::getOrCreateRecycleBinDateNode() void Notebook::emptyNode(const Node *p_node, bool p_force) { + // Copy the children. auto children = p_node->getChildren(); - for (auto &child : children) { + for (const auto &child : children) { removeNode(child, p_force); } } @@ -355,3 +346,8 @@ QSharedPointer Notebook::copyAsNode(Node *p_parent, { return m_configMgr->copyAsNode(p_parent, p_flags, p_path); } + +void Notebook::reloadNode(Node *p_node) +{ + m_configMgr->reloadNode(p_node); +} diff --git a/src/core/notebook/notebook.h b/src/core/notebook/notebook.h index 2288fcdd..a6d6d970 100644 --- a/src/core/notebook/notebook.h +++ b/src/core/notebook/notebook.h @@ -99,11 +99,11 @@ namespace vnotex // @p_configOnly: if true, will just remove node from config. void removeNode(const QSharedPointer &p_node, bool p_force = false, bool p_configOnly = false); - void removeNode(const Node *p_node, bool p_force = false, bool p_configOnly = false); + void removeNode(Node *p_node, bool p_force = false, bool p_configOnly = false); void moveNodeToRecycleBin(const QSharedPointer &p_node); - void moveNodeToRecycleBin(const Node *p_node); + void moveNodeToRecycleBin(Node *p_node); // Move @p_filePath to the recycle bin, without adding it as a child node. void moveFileToRecycleBin(const QString &p_filePath); @@ -127,6 +127,8 @@ namespace vnotex bool isBuiltInFolder(const Node *p_node, const QString &p_name) const; + void reloadNode(Node *p_node); + static const QString c_defaultAttachmentFolder; static const QString c_defaultImageFolder; diff --git a/src/core/notebook/notebook.pri b/src/core/notebook/notebook.pri index 68ef64bf..917a31fc 100644 --- a/src/core/notebook/notebook.pri +++ b/src/core/notebook/notebook.pri @@ -1,4 +1,5 @@ SOURCES += \ + $$PWD/externalnode.cpp \ $$PWD/notebook.cpp \ $$PWD/bundlenotebookfactory.cpp \ $$PWD/notebookparameters.cpp \ @@ -8,6 +9,7 @@ SOURCES += \ $$PWD/vxnodefile.cpp HEADERS += \ + $$PWD/externalnode.h \ $$PWD/notebook.h \ $$PWD/inotebookfactory.h \ $$PWD/bundlenotebookfactory.h \ diff --git a/src/core/notebookbackend/inotebookbackend.h b/src/core/notebookbackend/inotebookbackend.h index e284cbbe..a2f1199f 100644 --- a/src/core/notebookbackend/inotebookbackend.h +++ b/src/core/notebookbackend/inotebookbackend.h @@ -67,6 +67,10 @@ namespace vnotex virtual bool exists(const QString &p_path) const = 0; + virtual bool existsFile(const QString &p_path) const = 0; + + virtual bool existsDir(const QString &p_path) const = 0; + virtual bool childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const = 0; virtual bool isFile(const QString &p_path) const = 0; diff --git a/src/core/notebookbackend/localnotebookbackend.cpp b/src/core/notebookbackend/localnotebookbackend.cpp index 79c8c872..e365ef0f 100644 --- a/src/core/notebookbackend/localnotebookbackend.cpp +++ b/src/core/notebookbackend/localnotebookbackend.cpp @@ -86,6 +86,18 @@ bool LocalNotebookBackend::exists(const QString &p_path) const return QFileInfo::exists(getFullPath(p_path)); } +bool LocalNotebookBackend::existsFile(const QString &p_path) const +{ + QFileInfo fi(getFullPath(p_path)); + return fi.exists() && fi.isFile(); +} + +bool LocalNotebookBackend::existsDir(const QString &p_path) const +{ + QFileInfo fi(getFullPath(p_path)); + return fi.exists() && fi.isDir(); +} + bool LocalNotebookBackend::childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const { return FileUtils::childExistsCaseInsensitive(getFullPath(p_dirPath), p_name); diff --git a/src/core/notebookbackend/localnotebookbackend.h b/src/core/notebookbackend/localnotebookbackend.h index 2eaf68c6..9a32d63f 100644 --- a/src/core/notebookbackend/localnotebookbackend.h +++ b/src/core/notebookbackend/localnotebookbackend.h @@ -47,6 +47,10 @@ namespace vnotex bool exists(const QString &p_path) const Q_DECL_OVERRIDE; + bool existsFile(const QString &p_path) const Q_DECL_OVERRIDE; + + bool existsDir(const QString &p_path) const Q_DECL_OVERRIDE; + bool childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const Q_DECL_OVERRIDE; bool isFile(const QString &p_path) const Q_DECL_OVERRIDE; diff --git a/src/core/notebookconfigmgr/inotebookconfigmgr.h b/src/core/notebookconfigmgr/inotebookconfigmgr.h index 7070d647..5a69980d 100644 --- a/src/core/notebookconfigmgr/inotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/inotebookconfigmgr.h @@ -36,7 +36,7 @@ namespace vnotex const QSharedPointer &getBackend() const; - virtual QSharedPointer loadRootNode() const = 0; + virtual QSharedPointer loadRootNode() = 0; virtual void loadNode(Node *p_node) const = 0; virtual void saveNode(const Node *p_node) = 0; @@ -75,6 +75,10 @@ namespace vnotex virtual QString fetchNodeAttachmentFolderPath(Node *p_node) = 0; + virtual QVector> fetchExternalChildren(Node *p_node) const = 0; + + virtual void reloadNode(Node *p_node) = 0; + protected: // Version of the config processing code. virtual QString getCodeVersion() const; diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index b7ae387f..22b88707 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -205,11 +206,12 @@ void VXNotebookConfigMgr::createEmptyRootNode() writeNodeConfig(c_nodeConfigName, node); } -QSharedPointer VXNotebookConfigMgr::loadRootNode() const +QSharedPointer VXNotebookConfigMgr::loadRootNode() { auto nodeConfig = readNodeConfig(""); QSharedPointer root = nodeConfigToNode(*nodeConfig, "", nullptr); root->setUse(Node::Use::Root); + root->setExists(true); Q_ASSERT(root->isLoaded()); if (!markRecycleBinNode(root)) { @@ -219,11 +221,16 @@ QSharedPointer VXNotebookConfigMgr::loadRootNode() const return root; } -bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer &p_root) const +bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer &p_root) { auto node = p_root->findChild(c_recycleBinFolderName, FileUtils::isPlatformNameCaseSensitive()); if (node) { + if (!node->exists()) { + removeNode(node, true, true); + return false; + } + node->setUse(Node::Use::RecycleBin); markNodeReadOnly(node.data()); return true; @@ -239,7 +246,7 @@ void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const } p_node->setReadOnly(true); - for (auto &child : p_node->getChildren()) { + for (const auto &child : p_node->getChildrenRef()) { markNodeReadOnly(child.data()); } } @@ -305,16 +312,30 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi { QVector> children; children.reserve(p_config.m_files.size() + p_config.m_folders.size()); + const auto basePath = p_node->fetchPath(); for (const auto &folder : p_config.m_folders) { + if (folder.m_name.isEmpty()) { + // Skip empty name node. + qWarning() << "skipped loading node with empty name under" << p_node->fetchPath(); + continue; + } + auto folderNode = QSharedPointer::create(folder.m_name, getNotebook(), p_node); inheritNodeFlags(p_node, folderNode.data()); + folderNode->setExists(getBackend()->existsDir(PathUtils::concatenateFilePath(basePath, folder.m_name))); children.push_back(folderNode); } for (const auto &file : p_config.m_files) { + if (file.m_name.isEmpty()) { + // Skip empty name node. + qWarning() << "skipped loading node with empty name under" << p_node->fetchPath(); + continue; + } + auto fileNode = QSharedPointer::create(file.m_id, file.m_name, file.m_createdTimeUtc, @@ -324,6 +345,7 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi getNotebook(), p_node); inheritNodeFlags(p_node, fileNode.data()); + fileNode->setExists(getBackend()->existsFile(PathUtils::concatenateFilePath(basePath, file.m_name))); children.push_back(fileNode); } @@ -338,7 +360,7 @@ QSharedPointer VXNotebookConfigMgr::newNode(Node *p_parent, Node::Flags p_flags, const QString &p_name) { - Q_ASSERT(p_parent && p_parent->isContainer()); + Q_ASSERT(p_parent && p_parent->isContainer() && !p_name.isEmpty()); QSharedPointer node; @@ -359,6 +381,7 @@ QSharedPointer VXNotebookConfigMgr::addAsNode(Node *p_parent, { Q_ASSERT(p_parent && p_parent->isContainer()); + // TODO: reuse the config if available. QSharedPointer node; if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); @@ -407,6 +430,9 @@ QSharedPointer VXNotebookConfigMgr::newFileNode(Node *p_parent, // Write empty file. if (p_create) { getBackend()->writeFile(node->fetchPath(), QString()); + node->setExists(true); + } else { + node->setExists(getBackend()->existsFile(node->fetchPath())); } addChildNode(p_parent, node); @@ -433,6 +459,9 @@ QSharedPointer VXNotebookConfigMgr::newFolderNode(Node *p_parent, // Make folder. if (p_create) { getBackend()->makePath(node->fetchPath()); + node->setExists(true); + } else { + node->setExists(getBackend()->existsDir(node->fetchPath())); } writeNodeConfig(node.data()); @@ -452,7 +481,7 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeC p_node->getCreatedTimeUtc(), p_node->getModifiedTimeUtc()); - for (const auto &child : p_node->getChildren()) { + for (const auto &child : p_node->getChildrenRef()) { if (child->hasContent()) { NodeFileConfig fileConfig; fileConfig.m_name = child->getName(); @@ -477,7 +506,7 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeC void VXNotebookConfigMgr::loadNode(Node *p_node) const { - if (p_node->isLoaded()) { + if (p_node->isLoaded() || !p_node->exists()) { return; } @@ -513,7 +542,7 @@ void VXNotebookConfigMgr::addChildNode(Node *p_parent, const QSharedPointerisContainer()) { int idx = 0; - auto children = p_parent->getChildren(); + const auto &children = p_parent->getChildrenRef(); for (; idx < children.size(); ++idx) { if (!children[idx]->isContainer()) { break; @@ -558,6 +587,13 @@ QSharedPointer VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer { Q_ASSERT(p_dest->isContainer()); + if (!p_src->exists()) { + if (p_move) { + p_src->getNotebook()->removeNode(p_src); + } + return nullptr; + } + QSharedPointer node; if (p_src->isContainer()) { node = copyFolderNodeAsChildOf(p_src, p_dest, p_move); @@ -605,6 +641,7 @@ QSharedPointer VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi attachmentFolder, notebook, p_dest); + destNode->setExists(true); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); @@ -643,6 +680,7 @@ QSharedPointer VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP p_src->getModifiedTimeUtc(), QStringList(), QVector>()); + destNode->setExists(true); writeNodeConfig(destNode.data()); @@ -650,7 +688,8 @@ QSharedPointer VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP writeNodeConfig(p_dest); // Copy children node. - for (const auto &childNode : p_src->getChildren()) { + auto children = p_src->getChildren(); + for (const auto &childNode : children) { copyNodeAsChildOf(childNode, destNode.data(), p_move); } @@ -664,13 +703,18 @@ QSharedPointer VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP void VXNotebookConfigMgr::removeNode(const QSharedPointer &p_node, bool p_force, bool p_configOnly) { auto parentNode = p_node->getParent(); - if (!p_configOnly) { + if (!p_configOnly && p_node->exists()) { // Remove all children. - for (auto &childNode : p_node->getChildren()) { + auto children = p_node->getChildren(); + for (const auto &childNode : children) { removeNode(childNode, p_force, p_configOnly); } - removeFilesOfNode(p_node.data(), p_force); + try { + removeFilesOfNode(p_node.data(), p_force); + } catch (Exception &p_e) { + qWarning() << "failed to remove files of node" << p_node->fetchPath() << p_e.what(); + } } if (parentNode) { @@ -771,7 +815,9 @@ bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_n const auto name = p_name.toLower(); if (name == c_recycleBinFolderName || name == getNotebook()->getImageFolder().toLower() - || name == getNotebook()->getAttachmentFolder().toLower()) { + || name == getNotebook()->getAttachmentFolder().toLower() + || name == QStringLiteral("_v_images") + || name == QStringLiteral("_v_attachments")) { return true; } return BundleNotebookConfigMgr::isBuiltInFolder(p_node, p_name); @@ -779,25 +825,36 @@ bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_n QSharedPointer VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest) { - // Copy source file itself. - auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchPath(), + // Skip copy if it already locates in dest folder. + auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchAbsolutePath(), PathUtils::fileName(p_srcPath)); - destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath); - getBackend()->copyFile(p_srcPath, destFilePath); + if (!PathUtils::areSamePaths(p_srcPath, destFilePath)) { + // Copy source file itself. + destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath); + getBackend()->copyFile(p_srcPath, destFilePath); - // Copy media files fetched from content. - ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath); + // Copy media files fetched from content. + ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath); + } + + const auto name = PathUtils::fileName(destFilePath); + auto destNode = p_dest->findChild(name, true); + if (destNode) { + // Already have the node. + return destNode; + } // Create a file node. auto currentTime = QDateTime::currentDateTimeUtc(); - auto destNode = QSharedPointer::create(getNotebook()->getAndUpdateNextNodeId(), - PathUtils::fileName(destFilePath), - currentTime, - currentTime, - QStringList(), - QString(), - getNotebook(), - p_dest); + destNode = QSharedPointer::create(getNotebook()->getAndUpdateNextNodeId(), + name, + currentTime, + currentTime, + QStringList(), + QString(), + getNotebook(), + p_dest); + destNode->setExists(true); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); @@ -806,24 +863,33 @@ QSharedPointer VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src QSharedPointer VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest) { - auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(), + // Skip copy if it already locates in dest folder. + auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchAbsolutePath(), PathUtils::fileName(p_srcPath)); - destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); + if (!PathUtils::areSamePaths(p_srcPath, destFolderPath)) { + destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); - // Copy folder. - getBackend()->copyDir(p_srcPath, destFolderPath); + // Copy folder. + getBackend()->copyDir(p_srcPath, destFolderPath); + } + + const auto name = PathUtils::fileName(destFolderPath); + auto destNode = p_dest->findChild(name, true); + if (destNode) { + // Already have the node. + return destNode; + } // Create a folder node. auto notebook = getNotebook(); - auto destNode = QSharedPointer::create(PathUtils::fileName(destFolderPath), - notebook, - p_dest); + destNode = QSharedPointer::create(name, notebook, p_dest); auto currentTime = QDateTime::currentDateTimeUtc(); destNode->loadCompleteInfo(notebook->getAndUpdateNextNodeId(), currentTime, currentTime, QStringList(), QVector>()); + destNode->setExists(true); writeNodeConfig(destNode.data()); @@ -839,3 +905,50 @@ void VXNotebookConfigMgr::inheritNodeFlags(const Node *p_node, Node *p_child) co markNodeReadOnly(p_child); } } + +QVector> VXNotebookConfigMgr::fetchExternalChildren(Node *p_node) const +{ + Q_ASSERT(p_node->isContainer()); + QVector> externalNodes; + + auto dir = p_node->toDir(); + + // Folders. + { + const auto folders = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); + for (const auto &folder : folders) { + if (isBuiltInFolder(p_node, folder)) { + continue; + } + + if (p_node->containsContainerChild(folder)) { + continue; + } + + externalNodes.push_back(QSharedPointer::create(p_node, folder, ExternalNode::Type::Folder)); + } + } + + // Files. + { + const auto files = dir.entryList(QDir::Files); + for (const auto &file : files) { + if (isBuiltInFile(p_node, file)) { + continue; + } + + if (p_node->containsContentChild(file)) { + continue; + } + + externalNodes.push_back(QSharedPointer::create(p_node, file, ExternalNode::Type::File)); + } + } + + return externalNodes; +} + +void VXNotebookConfigMgr::reloadNode(Node *p_node) +{ + // TODO. +} diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h index db387335..5184dc07 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h @@ -31,7 +31,7 @@ namespace vnotex void createEmptySkeleton(const NotebookParameters &p_paras) Q_DECL_OVERRIDE; - QSharedPointer loadRootNode() const Q_DECL_OVERRIDE; + QSharedPointer loadRootNode() Q_DECL_OVERRIDE; void loadNode(Node *p_node) const Q_DECL_OVERRIDE; void saveNode(const Node *p_node) Q_DECL_OVERRIDE; @@ -68,6 +68,10 @@ namespace vnotex QString fetchNodeAttachmentFolderPath(Node *p_node) Q_DECL_OVERRIDE; + QVector> fetchExternalChildren(Node *p_node) const Q_DECL_OVERRIDE; + + void reloadNode(Node *p_node) Q_DECL_OVERRIDE; + private: // Config of a file child. struct NodeFileConfig @@ -173,7 +177,7 @@ namespace vnotex void removeFilesOfNode(Node *p_node, bool p_force); - bool markRecycleBinNode(const QSharedPointer &p_root) const; + bool markRecycleBinNode(const QSharedPointer &p_root); void markNodeReadOnly(Node *p_node) const; diff --git a/src/core/vnotex.h b/src/core/vnotex.h index 7f836eb4..8e512471 100644 --- a/src/core/vnotex.h +++ b/src/core/vnotex.h @@ -91,6 +91,9 @@ namespace vnotex // @m_response of @p_event: true to continue the rename, false to cancel the rename. void nodeAboutToRename(Node *p_node, const QSharedPointer &p_event); + // @m_response of @p_event: true to continue the reload, false to cancel the reload. + void nodeAboutToReload(Node *p_node, const QSharedPointer &p_event); + // Requested to open @p_filePath. void openFileRequested(const QString &p_filePath, const QSharedPointer &p_paras); diff --git a/src/core/widgetconfig.cpp b/src/core/widgetconfig.cpp index d846dfb0..d59ba8cb 100644 --- a/src/core/widgetconfig.cpp +++ b/src/core/widgetconfig.cpp @@ -24,8 +24,10 @@ void WidgetConfig::init(const QJsonObject &p_app, m_findAndReplaceOptions = static_cast(READINT(QStringLiteral("find_and_replace_options"))); - m_noteExplorerViewOrder = READINT(QStringLiteral("note_explorer_view_order")); - m_noteExplorerRecycleBinNodeShown = READBOOL(QStringLiteral("note_explorer_recycle_bin_node_shown")); + 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")); } QJsonObject WidgetConfig::toJson() const @@ -33,8 +35,10 @@ QJsonObject WidgetConfig::toJson() const QJsonObject obj; obj[QStringLiteral("outline_auto_expanded_level")] = m_outlineAutoExpandedLevel; obj[QStringLiteral("find_and_replace_options")] = static_cast(m_findAndReplaceOptions); - obj[QStringLiteral("note_explorer_view_order")] = m_noteExplorerViewOrder; - obj[QStringLiteral("note_explorer_recycle_bin_node_shown")] = m_noteExplorerRecycleBinNodeShown; + 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; return obj; } @@ -58,22 +62,42 @@ void WidgetConfig::setFindAndReplaceOptions(FindOptions p_options) updateConfig(m_findAndReplaceOptions, p_options, this); } -int WidgetConfig::getNoteExplorerViewOrder() const +int WidgetConfig::getNodeExplorerViewOrder() const { - return m_noteExplorerViewOrder; + return m_nodeExplorerViewOrder; } -void WidgetConfig::setNoteExplorerViewOrder(int p_viewOrder) +void WidgetConfig::setNodeExplorerViewOrder(int p_viewOrder) { - updateConfig(m_noteExplorerViewOrder, p_viewOrder, this); + updateConfig(m_nodeExplorerViewOrder, p_viewOrder, this); } -bool WidgetConfig::isNoteExplorerRecycleBinNodeShown() const +bool WidgetConfig::isNodeExplorerRecycleBinNodeVisible() const { - return m_noteExplorerRecycleBinNodeShown; + return m_nodeExplorerRecycleBinNodeVisible; } -void WidgetConfig::setNoteExplorerRecycleBinNodeShown(bool p_shown) +void WidgetConfig::setNodeExplorerRecycleBinNodeVisible(bool p_visible) { - updateConfig(m_noteExplorerRecycleBinNodeShown, p_shown, this); + updateConfig(m_nodeExplorerRecycleBinNodeVisible, p_visible, this); +} + +bool WidgetConfig::isNodeExplorerExternalFilesVisible() const +{ + return m_nodeExplorerExternalFilesVisible; +} + +void WidgetConfig::setNodeExplorerExternalFilesVisible(bool p_visible) +{ + updateConfig(m_nodeExplorerExternalFilesVisible, p_visible, this); +} + +bool WidgetConfig::getNodeExplorerAutoImportExternalFilesEnabled() const +{ + return m_nodeExplorerAutoImportExternalFilesEnabled; +} + +void WidgetConfig::setNodeExplorerAutoImportExternalFilesEnabled(bool p_enabled) +{ + updateConfig(m_nodeExplorerAutoImportExternalFilesEnabled, p_enabled, this); } diff --git a/src/core/widgetconfig.h b/src/core/widgetconfig.h index 3db92a4f..0da1ce85 100644 --- a/src/core/widgetconfig.h +++ b/src/core/widgetconfig.h @@ -24,20 +24,30 @@ namespace vnotex FindOptions getFindAndReplaceOptions() const; void setFindAndReplaceOptions(FindOptions p_options); - int getNoteExplorerViewOrder() const; - void setNoteExplorerViewOrder(int p_viewOrder); + int getNodeExplorerViewOrder() const; + void setNodeExplorerViewOrder(int p_viewOrder); - bool isNoteExplorerRecycleBinNodeShown() const; - void setNoteExplorerRecycleBinNodeShown(bool p_shown); + bool isNodeExplorerRecycleBinNodeVisible() const; + void setNodeExplorerRecycleBinNodeVisible(bool p_visible); + + bool isNodeExplorerExternalFilesVisible() const; + void setNodeExplorerExternalFilesVisible(bool p_visible); + + bool getNodeExplorerAutoImportExternalFilesEnabled() const; + void setNodeExplorerAutoImportExternalFilesEnabled(bool p_enabled); private: int m_outlineAutoExpandedLevel = 6; FindOptions m_findAndReplaceOptions = FindOption::None; - int m_noteExplorerViewOrder = 0; + int m_nodeExplorerViewOrder = 0; - bool m_noteExplorerRecycleBinNodeShown = false; + bool m_nodeExplorerRecycleBinNodeVisible = false; + + bool m_nodeExplorerExternalFilesVisible = true; + + bool m_nodeExplorerAutoImportExternalFilesEnabled = true; }; } diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index d7c24fe7..7ced0529 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -286,8 +286,10 @@ "outline_auto_expanded_level" : 6, "//comment" : "Default find options in FindAndReplace", "find_and_replace_options" : 16, - "//comment" : "View order of the note explorer", - "note_explorer_view_order" : 0, - "note_explorer_recycle_bin_node_shown" : false + "//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 } } diff --git a/src/data/extra/themes/moonlight/palette.json b/src/data/extra/themes/moonlight/palette.json index 667c3323..b22f6820 100644 --- a/src/data/extra/themes/moonlight/palette.json +++ b/src/data/extra/themes/moonlight/palette.json @@ -224,7 +224,13 @@ }, "notebookexplorer" : { "node_icon" : { - "fg" : "@base#icon#fg" + "fg" : "@base#icon#fg", + "invalid" : { + "fg" : "@base#icon#warning#fg" + } + }, + "external_node_icon" : { + "fg" : "@base#icon#inactive#fg" } }, "viewsplit" : { diff --git a/src/data/extra/themes/native/palette.json b/src/data/extra/themes/native/palette.json index 7bf94559..96ff0826 100644 --- a/src/data/extra/themes/native/palette.json +++ b/src/data/extra/themes/native/palette.json @@ -83,7 +83,13 @@ }, "notebookexplorer" : { "node_icon" : { - "fg" : "@base#icon#fg" + "fg" : "@base#icon#fg", + "invalid" : { + "fg" : "@base#icon#warning#fg" + } + }, + "external_node_icon" : { + "fg" : "@base#icon#disabled#fg" } }, "viewsplit" : { diff --git a/src/data/extra/themes/pure/palette.json b/src/data/extra/themes/pure/palette.json index a0dcbe23..e90bc92f 100644 --- a/src/data/extra/themes/pure/palette.json +++ b/src/data/extra/themes/pure/palette.json @@ -220,7 +220,13 @@ }, "notebookexplorer" : { "node_icon" : { - "fg" : "@base#icon#fg" + "fg" : "@base#icon#fg", + "invalid" : { + "fg" : "@base#icon#warning#fg" + } + }, + "external_node_icon" : { + "fg" : "@base#icon#inactive#fg" } }, "viewsplit" : { diff --git a/src/export/exporter.cpp b/src/export/exporter.cpp index cfbaf8ec..acb4425c 100644 --- a/src/export/exporter.cpp +++ b/src/export/exporter.cpp @@ -119,7 +119,7 @@ QStringList Exporter::doExport(const ExportOption &p_option, const QString &p_ou } p_folder->load(); - const auto &children = p_folder->getChildren(); + const auto &children = p_folder->getChildrenRef(); emit progressUpdated(0, children.size()); for (int i = 0; i < children.size(); ++i) { if (checkAskedToStop()) { @@ -192,7 +192,7 @@ QStringList Exporter::doExport(const ExportOption &p_option, Notebook *p_noteboo auto rootNode = p_notebook->getRootNode(); Q_ASSERT(rootNode->isLoaded()); - const auto &children = rootNode->getChildren(); + const auto &children = rootNode->getChildrenRef(); emit progressUpdated(0, children.size()); for (int i = 0; i < children.size(); ++i) { if (checkAskedToStop()) { diff --git a/src/export/webviewexporter.cpp b/src/export/webviewexporter.cpp index f36929bc..e7b8494d 100644 --- a/src/export/webviewexporter.cpp +++ b/src/export/webviewexporter.cpp @@ -61,6 +61,7 @@ bool WebViewExporter::doExport(const ExportOption &p_option, m_webViewStates = WebViewState::Started; auto baseUrl = PathUtils::pathToUrl(p_file->getContentPath()); + m_viewer->adapter()->reset(); m_viewer->setHtml(m_htmlTemplate, baseUrl); auto textContent = p_file->read(); @@ -93,7 +94,7 @@ bool WebViewExporter::doExport(const ExportOption &p_option, switch (p_option.m_targetFormat) { case ExportFormat::HTML: - // TODO: not supported yet. + // TODO: MIME HTML format is not supported yet. Q_ASSERT(!p_option.m_htmlOption.m_useMimeHtmlFormat); ret = doExportHtml(p_option.m_htmlOption, p_outputFile, baseUrl); break; diff --git a/src/utils/pathutils.cpp b/src/utils/pathutils.cpp index a79836f4..f3beb4d3 100644 --- a/src/utils/pathutils.cpp +++ b/src/utils/pathutils.cpp @@ -48,7 +48,7 @@ bool PathUtils::isEmptyDir(const QString &p_path) QString PathUtils::concatenateFilePath(const QString &p_dirPath, const QString &p_name) { - auto dirPath = cleanPath(p_dirPath); + QString dirPath = cleanPath(p_dirPath); if (p_name.isEmpty()) { return dirPath; } @@ -57,7 +57,7 @@ QString PathUtils::concatenateFilePath(const QString &p_dirPath, const QString & return p_name; } - return dirPath + '/' + p_name; + return dirPath + "/" + p_name; } QString PathUtils::dirName(const QString &p_path) diff --git a/src/widgets/editors/markdownvieweradapter.cpp b/src/widgets/editors/markdownvieweradapter.cpp index 94503c01..a0955d28 100644 --- a/src/widgets/editors/markdownvieweradapter.cpp +++ b/src/widgets/editors/markdownvieweradapter.cpp @@ -356,3 +356,14 @@ void MarkdownViewerAdapter::setSavedContent(const QString &p_headContent, { emit contentReady(p_headContent, p_styleContent, p_content, p_bodyClassList); } + +void MarkdownViewerAdapter::reset() +{ + m_revision = 0; + m_viewerReady = false; + m_pendingData.reset(); + m_topLineNumber = -1; + m_headings.clear(); + m_currentHeadingIndex = -1; + m_crossCopyTargets.clear(); +} diff --git a/src/widgets/editors/markdownvieweradapter.h b/src/widgets/editors/markdownvieweradapter.h index cf2501f7..5c7d27bc 100644 --- a/src/widgets/editors/markdownvieweradapter.h +++ b/src/widgets/editors/markdownvieweradapter.h @@ -123,6 +123,9 @@ namespace vnotex void saveContent(); + // Should be called before WebViewer.setHtml(). + void reset(); + // Functions to be called from web side. public slots: void setReady(bool p_ready); diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp index b2266360..0e40e07a 100644 --- a/src/widgets/markdownviewwindow.cpp +++ b/src/widgets/markdownviewwindow.cpp @@ -497,10 +497,12 @@ void MarkdownViewWindow::syncViewerFromBuffer(bool p_syncPositionFromEditMode) // TODO: Check buffer for last position recover. // Use getPath() instead of getBasePath() to make in-page anchor work. + adapter()->reset(); m_viewer->setHtml(HtmlTemplateHelper::getMarkdownViewerTemplate(), PathUtils::pathToUrl(buffer->getContentPath())); adapter()->setText(m_bufferRevision, buffer->getContent(), lineNumber); } else { + adapter()->reset(); m_viewer->setHtml(""); adapter()->setText(0, "", -1); } diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index b611151b..363b72aa 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -63,10 +63,15 @@ void NotebookExplorer::setupUI() }); mainLayout->addWidget(m_selector); + const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig(); m_nodeExplorer = new NotebookNodeExplorer(this); - m_nodeExplorer->setRecycleBinNodeVisible(ConfigMgr::getInst().getWidgetConfig().isNoteExplorerRecycleBinNodeShown()); + m_nodeExplorer->setRecycleBinNodeVisible(widgetConfig.isNodeExplorerRecycleBinNodeVisible()); + m_nodeExplorer->setViewOrder(widgetConfig.getNodeExplorerViewOrder()); + m_nodeExplorer->setExternalFilesVisible(widgetConfig.isNodeExplorerExternalFilesVisible()); connect(m_nodeExplorer, &NotebookNodeExplorer::nodeActivated, &VNoteX::getInst(), &VNoteX::openNodeRequested); + connect(m_nodeExplorer, &NotebookNodeExplorer::fileActivated, + &VNoteX::getInst(), &VNoteX::openFileRequested); connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToMove, &VNoteX::getInst(), &VNoteX::nodeAboutToMove); connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToRemove, @@ -78,6 +83,8 @@ void NotebookExplorer::setupUI() TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) { + const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig(); + auto titleBar = new TitleBar(tr("Notebook"), TitleBar::Action::Menu, p_parent); @@ -95,11 +102,11 @@ 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(ConfigMgr::getInst().getWidgetConfig().isNoteExplorerRecycleBinNodeShown()); + btn->defaultAction()->setChecked(widgetConfig.isNodeExplorerRecycleBinNodeVisible()); connect(btn, &QToolButton::triggered, this, [this](QAction *p_act) { const bool checked = p_act->isChecked(); - ConfigMgr::getInst().getWidgetConfig().setNoteExplorerRecycleBinNodeShown(checked); + ConfigMgr::getInst().getWidgetConfig().setNodeExplorerRecycleBinNodeVisible(checked); m_nodeExplorer->setRecycleBinNodeVisible(checked); }); } @@ -113,6 +120,33 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) dialog.exec(); }); + titleBar->addMenuSeparator(); + + // External Files menu. + { + auto subMenu = titleBar->addMenuSubMenu(tr("External Files")); + auto showAct = titleBar->addMenuAction( + subMenu, + tr("Show External Files"), + titleBar, + [this](bool p_checked) { + ConfigMgr::getInst().getWidgetConfig().setNodeExplorerExternalFilesVisible(p_checked); + m_nodeExplorer->setExternalFilesVisible(p_checked); + }); + showAct->setCheckable(true); + showAct->setChecked(widgetConfig.isNodeExplorerExternalFilesVisible()); + + auto importAct = titleBar->addMenuAction( + subMenu, + tr("Import External Files When Activated"), + titleBar, + [this](bool p_checked) { + ConfigMgr::getInst().getWidgetConfig().setNodeExplorerAutoImportExternalFilesEnabled(p_checked); + }); + importAct->setCheckable(true); + importAct->setChecked(widgetConfig.getNodeExplorerAutoImportExternalFilesEnabled()); + } + return titleBar; } @@ -206,21 +240,7 @@ void NotebookExplorer::newNote() Node *NotebookExplorer::currentExploredFolderNode() const { - if (!m_currentNotebook) { - return nullptr; - } - - auto node = m_nodeExplorer->getCurrentNode(); - if (node) { - if (!node->isContainer()) { - node = node->getParent(); - } - Q_ASSERT(node && node->isContainer()); - } else { - node = m_currentNotebook->getRootNode().data(); - } - - return node; + return m_nodeExplorer->currentExploredFolderNode(); } Node *NotebookExplorer::checkNotebookAndGetCurrentExploredFolderNode() const @@ -372,7 +392,7 @@ void NotebookExplorer::setupViewMenu(QMenu *p_menu) act->setData(NotebookNodeExplorer::ViewOrder::OrderedByModifiedTimeReversed); p_menu->addAction(act); - int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); + int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerViewOrder(); for (const auto &act : ag->actions()) { if (act->data().toInt() == viewOrder) { act->setChecked(true); @@ -382,8 +402,7 @@ void NotebookExplorer::setupViewMenu(QMenu *p_menu) connect(ag, &QActionGroup::triggered, this, [this](QAction *p_action) { int order = p_action->data().toInt(); - ConfigMgr::getInst().getWidgetConfig().setNoteExplorerViewOrder(order); - - m_nodeExplorer->reload(); + ConfigMgr::getInst().getWidgetConfig().setNodeExplorerViewOrder(order); + m_nodeExplorer->setViewOrder(order); }); } diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index 2ab13dbc..8652e873 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -6,9 +6,11 @@ #include #include #include +#include #include #include +#include #include "exception.h" #include "messageboxhelper.h" #include "vnotex.h" @@ -29,18 +31,23 @@ #include #include #include -#include using namespace vnotex; -const QString NotebookNodeExplorer::c_nodeIconForegroundName = "widgets#notebookexplorer#node_icon#fg"; - QIcon NotebookNodeExplorer::s_folderNodeIcon; QIcon NotebookNodeExplorer::s_fileNodeIcon; +QIcon NotebookNodeExplorer::s_invalidFolderNodeIcon; + +QIcon NotebookNodeExplorer::s_invalidFileNodeIcon; + QIcon NotebookNodeExplorer::s_recycleBinNodeIcon; +QIcon NotebookNodeExplorer::s_externalFolderNodeIcon; + +QIcon NotebookNodeExplorer::s_externalFileNodeIcon; + NotebookNodeExplorer::NodeData::NodeData() { } @@ -52,9 +59,9 @@ NotebookNodeExplorer::NodeData::NodeData(Node *p_node, bool p_loaded) { } -NotebookNodeExplorer::NodeData::NodeData(const QString &p_name) - : m_type(NodeType::Attachment), - m_name(p_name), +NotebookNodeExplorer::NodeData::NodeData(const QSharedPointer &p_externalNode) + : m_type(NodeType::ExternalNode), + m_externalNode(p_externalNode), m_loaded(true) { } @@ -67,13 +74,12 @@ NotebookNodeExplorer::NodeData::NodeData(const NodeData &p_other) m_node = p_other.m_node; break; - case NodeType::Attachment: - m_name = p_other.m_name; + case NodeType::ExternalNode: + m_externalNode = p_other.m_externalNode; break; default: - m_node = p_other.m_node; - m_name = p_other.m_name; + Q_ASSERT(false); break; } @@ -96,13 +102,12 @@ NotebookNodeExplorer::NodeData &NotebookNodeExplorer::NodeData::operator=(const m_node = p_other.m_node; break; - case NodeType::Attachment: - m_name = p_other.m_name; + case NodeType::ExternalNode: + m_externalNode = p_other.m_externalNode; break; default: - m_node = p_other.m_node; - m_name = p_other.m_name; + Q_ASSERT(false); break; } @@ -121,9 +126,9 @@ bool NotebookNodeExplorer::NodeData::isNode() const return m_type == NodeType::Node; } -bool NotebookNodeExplorer::NodeData::isAttachment() const +bool NotebookNodeExplorer::NodeData::isExternalNode() const { - return m_type == NodeType::Attachment; + return m_type == NodeType::ExternalNode; } NotebookNodeExplorer::NodeData::NodeType NotebookNodeExplorer::NodeData::getType() const @@ -137,17 +142,17 @@ Node *NotebookNodeExplorer::NodeData::getNode() const return m_node; } -const QString &NotebookNodeExplorer::NodeData::getName() const +ExternalNode *NotebookNodeExplorer::NodeData::getExternalNode() const { - Q_ASSERT(isAttachment()); - return m_name; + Q_ASSERT(isExternalNode()); + return m_externalNode.data(); } void NotebookNodeExplorer::NodeData::clear() { m_type = NodeType::Invalid; m_node = nullptr; - m_name.clear(); + m_externalNode.clear(); m_loaded = false; } @@ -180,15 +185,26 @@ void NotebookNodeExplorer::initNodeIcons() const return; } + const QString nodeIconFgName = "widgets#notebookexplorer#node_icon#fg"; + const QString invalidNodeIconFgName = "widgets#notebookexplorer#node_icon#invalid#fg"; + const QString externalNodeIconFgName = "widgets#notebookexplorer#external_node_icon#fg"; + const auto &themeMgr = VNoteX::getInst().getThemeMgr(); - const auto fg = themeMgr.paletteColor(c_nodeIconForegroundName); + const auto fg = themeMgr.paletteColor(nodeIconFgName); + const auto invalidFg = themeMgr.paletteColor(invalidNodeIconFgName); + const auto externalFg = themeMgr.paletteColor(externalNodeIconFgName); + const QString folderIconName("folder_node.svg"); const QString fileIconName("file_node.svg"); const QString recycleBinIconName("recycle_bin.svg"); s_folderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg); s_fileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), fg); + s_invalidFolderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), invalidFg); + s_invalidFileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), invalidFg); s_recycleBinNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(recycleBinIconName), fg); + s_externalFolderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), externalFg); + s_externalFileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), externalFg); } void NotebookNodeExplorer::setupUI() @@ -244,8 +260,8 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent) if (data.isNode()) { createContextMenuOnNode(menu.data(), data.getNode()); - } else if (data.isAttachment()) { - createContextMenuOnAttachment(menu.data(), data.getName()); + } else if (data.isExternalNode()) { + createContextMenuOnExternalNode(menu.data(), data.getExternalNode()); } } @@ -263,9 +279,23 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent) } if (data.isNode()) { + if (checkInvalidNode(data.getNode())) { + return; + } emit nodeActivated(data.getNode(), QSharedPointer::create()); - } else if (data.isAttachment()) { - // TODO. + } else if (data.isExternalNode()) { + // Import to config first. + if (m_autoImportExternalFiles) { + auto importedNode = importToIndex(data.getExternalNode()); + if (importedNode) { + emit nodeActivated(importedNode.data(), QSharedPointer::create()); + } + return; + } + + // Just open it. + emit fileActivated(data.getExternalNode()->fetchAbsolutePath(), + QSharedPointer::create()); } }); } @@ -333,7 +363,7 @@ void NotebookNodeExplorer::generateNodeTree() void NotebookNodeExplorer::loadRootNode(const Node *p_node) const { - Q_ASSERT(p_node->isLoaded()); + Q_ASSERT(p_node->isLoaded() && p_node->isContainer()); // Render recycle bin node first. auto recycleBinNode = m_notebook->getRecycleBinNode(); @@ -341,9 +371,20 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const loadRecycleBinNode(recycleBinNode.data()); } + // External children. + if (m_externalFilesVisible) { + auto externalChildren = p_node->fetchExternalChildren(); + // TODO: Sort external children. + for (const auto &child : externalChildren) { + auto item = new QTreeWidgetItem(m_masterExplorer); + loadNode(item, child); + } + } + + // Children. auto children = p_node->getChildren(); sortNodes(children); - for (auto &child : children) { + for (const auto &child : children) { if (recycleBinNode == child) { continue; } @@ -378,15 +419,34 @@ void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, Node *p_node, int p } } +void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const +{ + clearTreeWigetItemChildren(p_item); + + fillTreeItem(p_item, p_node); + + // No children for external node. +} + void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const { if (p_level < 0) { return; } + // External children. + if (m_externalFilesVisible && p_node->isContainer()) { + auto externalChildren = p_node->fetchExternalChildren(); + // TODO: Sort external children. + for (const auto &child : externalChildren) { + auto item = new QTreeWidgetItem(p_item); + loadNode(item, child); + } + } + auto children = p_node->getChildren(); sortNodes(children); - for (auto &child : children) { + for (const auto &child : children) { auto item = new QTreeWidgetItem(p_item); loadNode(item, child.data(), p_level); } @@ -434,22 +494,33 @@ void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, b setItemNodeData(p_item, NodeData(p_node, p_loaded)); p_item->setText(Column::Name, p_node->getName()); p_item->setIcon(Column::Name, getNodeItemIcon(p_node)); - p_item->setToolTip(Column::Name, p_node->getName()); + p_item->setToolTip(Column::Name, p_node->exists() ? p_node->getName() : (tr("[Invalid] %1").arg(p_node->getName()))); } -QIcon NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const +void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const +{ + setItemNodeData(p_item, NodeData(p_node)); + p_item->setText(Column::Name, p_node->getName()); + p_item->setIcon(Column::Name, getNodeItemIcon(p_node.data())); + p_item->setToolTip(Column::Name, tr("[External] %1").arg(p_node->getName())); +} + +const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const { if (p_node->hasContent()) { - return s_fileNodeIcon; + return p_node->exists() ? s_fileNodeIcon : s_invalidFileNodeIcon; } else { if (p_node->getUse() == Node::Use::RecycleBin) { return s_recycleBinNodeIcon; } - return s_folderNodeIcon; + return p_node->exists() ? s_folderNodeIcon : s_invalidFolderNodeIcon; } +} - return QIcon(); +const QIcon &NotebookNodeExplorer::getNodeItemIcon(const ExternalNode *p_node) const +{ + return p_node->isFolder() ? s_externalFolderNodeIcon : s_externalFileNodeIcon; } Node *NotebookNodeExplorer::getCurrentNode() const @@ -457,7 +528,7 @@ Node *NotebookNodeExplorer::getCurrentNode() const auto item = m_masterExplorer->currentItem(); if (item) { auto data = getItemNodeData(item); - while (data.isAttachment()) { + while (item && !data.isNode()) { item = item->parent(); if (item) { data = getItemNodeData(item); @@ -687,6 +758,9 @@ void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu) p_menu->addSeparator(); + act = createAction(Action::Reload, p_menu); + p_menu->addAction(act); + act = createAction(Action::OpenLocation, p_menu); p_menu->addAction(act); } @@ -698,6 +772,9 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ if (m_notebook->isRecycleBinNode(p_node)) { // Recycle bin node. + act = createAction(Action::Reload, p_menu); + p_menu->addAction(act); + if (selectedSize == 1) { act = createAction(Action::EmptyRecycleBin, p_menu); p_menu->addAction(act); @@ -707,12 +784,22 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ } } else if (m_notebook->isNodeInRecycleBin(p_node)) { // Node in recycle bin. + act = createAction(Action::Open, p_menu); + p_menu->addAction(act); + + p_menu->addSeparator(); + act = createAction(Action::Cut, p_menu); p_menu->addAction(act); act = createAction(Action::DeleteFromRecycleBin, p_menu); p_menu->addAction(act); + p_menu->addSeparator(); + + act = createAction(Action::Reload, p_menu); + p_menu->addAction(act); + if (selectedSize == 1) { p_menu->addSeparator(); @@ -723,6 +810,11 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ p_menu->addAction(act); } } else { + act = createAction(Action::Open, p_menu); + p_menu->addAction(act); + + p_menu->addSeparator(); + act = createAction(Action::NewNote, p_menu); p_menu->addAction(act); @@ -750,6 +842,9 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ p_menu->addSeparator(); + act = createAction(Action::Reload, p_menu); + p_menu->addAction(act); + act = createAction(Action::Sort, p_menu); p_menu->addAction(act); @@ -768,10 +863,28 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ } } -void NotebookNodeExplorer::createContextMenuOnAttachment(QMenu *p_menu, const QString &p_name) +void NotebookNodeExplorer::createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node) { - Q_UNUSED(p_menu); - Q_UNUSED(p_name); + Q_UNUSED(p_node); + + const int selectedSize = m_masterExplorer->selectedItems().size(); + QAction *act = nullptr; + + act = createAction(Action::Open, p_menu); + p_menu->addAction(act); + + act = createAction(Action::ImportToConfig, p_menu); + p_menu->addAction(act); + + if (selectedSize == 1) { + p_menu->addSeparator(); + + act = createAction(Action::CopyPath, p_menu); + p_menu->addAction(act); + + act = createAction(Action::OpenLocation, p_menu); + p_menu->addAction(act); + } } static QIcon generateMenuActionIcon(const QString &p_name) @@ -811,7 +924,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) connect(act, &QAction::triggered, this, [this]() { auto node = getCurrentNode(); - if (!node) { + if (checkInvalidNode(node)) { return; } @@ -834,15 +947,32 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) act = new QAction(tr("Open &Location"), p_parent); connect(act, &QAction::triggered, this, [this]() { + auto item = m_masterExplorer->currentItem(); + if (!item) { + if (m_notebook) { + auto locationPath = m_notebook->getRootFolderAbsolutePath(); + WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(locationPath)); + } + return; + } + auto data = getItemNodeData(item); QString locationPath; - auto node = getCurrentNode(); - if (node) { + if (data.isNode()) { + auto node = data.getNode(); + if (checkInvalidNode(node)) { + return; + } + locationPath = node->fetchAbsolutePath(); if (!node->isContainer()) { locationPath = PathUtils::parentDirPath(locationPath); } - } else if (m_notebook) { - locationPath = m_notebook->getRootFolderAbsolutePath(); + } else if (data.isExternalNode()) { + auto externalNode = data.getExternalNode(); + locationPath = externalNode->fetchAbsolutePath(); + if (!externalNode->isFolder()) { + locationPath = PathUtils::parentDirPath(locationPath); + } } if (!locationPath.isEmpty()) { @@ -855,9 +985,22 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) act = new QAction(tr("Cop&y Path"), p_parent); connect(act, &QAction::triggered, this, [this]() { - auto node = getCurrentNode(); - if (node) { - auto nodePath = node->fetchAbsolutePath(); + auto item = m_masterExplorer->currentItem(); + if (!item) { + return; + } + auto data = getItemNodeData(item); + QString nodePath; + if (data.isNode()) { + auto node = data.getNode(); + if (checkInvalidNode(node)) { + return; + } + nodePath = node->fetchAbsolutePath(); + } else if (data.isExternalNode()) { + nodePath = data.getExternalNode()->fetchAbsolutePath(); + } + if (!nodePath.isEmpty()) { ClipboardUtils::setTextToClipboard(nodePath); VNoteX::getInst().showStatusMessageShort(tr("Copied path: %1").arg(nodePath)); } @@ -906,9 +1049,8 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) 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()); + tr("Failed to empty recycle bin (%1) (%2).").arg(rbNodePath, p_e.what()), + VNoteX::getInst().getMainWindow()); } updateNode(rbNode); @@ -935,18 +1077,42 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) case Action::RemoveFromConfig: act = new QAction(tr("&Remove From Index"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - removeSelectedNodesFromConfig(); - }); + this, &NotebookNodeExplorer::removeSelectedNodesFromConfig); break; case Action::Sort: act = new QAction(generateMenuActionIcon("sort.svg"), tr("&Sort"), p_parent); + connect(act, &QAction::triggered, + this, &NotebookNodeExplorer::manualSort); + break; + + case Action::Reload: + act = new QAction(tr("Re&load"), p_parent); connect(act, &QAction::triggered, this, [this]() { - manualSort(); + auto node = currentExploredFolderNode(); + if (m_notebook && node) { + // TODO: emit signals to notify other components. + m_notebook->reloadNode(node); + } + updateNode(node); }); break; + + case Action::ImportToConfig: + act = new QAction(tr("&Import To Index"), p_parent); + connect(act, &QAction::triggered, + this, [this]() { + auto nodes = getSelectedNodes().second; + importToIndex(nodes); + }); + break; + + case Action::Open: + act = new QAction(tr("&Open"), p_parent); + connect(act, &QAction::triggered, + this, &NotebookNodeExplorer::openSelectedNodes); + break; } return act; @@ -954,7 +1120,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) void NotebookNodeExplorer::copySelectedNodes(bool p_move) { - auto nodes = getSelectedNodes(); + auto nodes = getSelectedNodes().first; if (nodes.isEmpty()) { return; } @@ -964,6 +1130,10 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move) ClipboardData cdata(VNoteX::getInst().getInstanceId(), p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode); for (auto node : nodes) { + if (checkInvalidNode(node)) { + continue; + } + auto item = QSharedPointer::create(node->getNotebook()->getId(), node->fetchPath()); cdata.addItem(item); @@ -976,15 +1146,17 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move) VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast(nrItems))); } -QVector NotebookNodeExplorer::getSelectedNodes() const +QPair, QVector> NotebookNodeExplorer::getSelectedNodes() const { - QVector nodes; + QPair, QVector> nodes; auto items = m_masterExplorer->selectedItems(); for (auto &item : items) { auto data = getItemNodeData(item); if (data.isNode()) { - nodes.push_back(data.getNode()); + nodes.first.push_back(data.getNode()); + } else if (data.isExternalNode()) { + nodes.second.push_back(data.getExternalNode()); } } @@ -1051,6 +1223,8 @@ void NotebookNodeExplorer::pasteNodesFromClipboard() // Current node may be a file node. if (!destNode->isContainer()) { destNode = destNode->getParent(); + } else if (checkInvalidNode(destNode)) { + return; } } @@ -1088,6 +1262,8 @@ void NotebookNodeExplorer::pasteNodesFromClipboard() QVector pastedNodes; QSet nodesNeedUpdate; for (auto srcNode : srcNodes) { + Q_ASSERT(srcNode->exists()); + if (isMove) { // Notice the view area to close any opened view windows. auto event = QSharedPointer::create(); @@ -1176,7 +1352,7 @@ QVector NotebookNodeExplorer::confirmSelectedNodes(const QString &p_titl const QString &p_text, const QString &p_info) const { - auto nodes = getSelectedNodes(); + auto nodes = getSelectedNodes().first; if (nodes.isEmpty()) { return nodes; } @@ -1270,7 +1446,7 @@ void NotebookNodeExplorer::removeSelectedNodesFromConfig() void NotebookNodeExplorer::filterAwayChildrenNodes(QVector &p_nodes) { - for (int i = p_nodes.size() - 1; i > 0; --i) { + for (int i = p_nodes.size() - 1; i >= 0; --i) { // Check if j is i's ancestor. for (int j = p_nodes.size() - 1; j >= 0; --j) { if (i == j) { @@ -1351,8 +1527,7 @@ void NotebookNodeExplorer::focusNormalNode() void NotebookNodeExplorer::sortNodes(QVector> &p_nodes) const { - int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); - if (viewOrder == ViewOrder::OrderedByConfiguration) { + if (m_viewOrder == ViewOrder::OrderedByConfiguration) { return; } @@ -1369,10 +1544,10 @@ void NotebookNodeExplorer::sortNodes(QVector> &p_nodes) con } // Sort containers. - sortNodes(p_nodes, 0, firstFileIndex, viewOrder); + sortNodes(p_nodes, 0, firstFileIndex, m_viewOrder); // Sort non-containers. - sortNodes(p_nodes, firstFileIndex, p_nodes.size(), viewOrder); + sortNodes(p_nodes, firstFileIndex, p_nodes.size(), m_viewOrder); } void NotebookNodeExplorer::sortNodes(QVector> &p_nodes, int p_start, int p_end, int p_viewOrder) const @@ -1437,6 +1612,25 @@ void NotebookNodeExplorer::setRecycleBinNodeVisible(bool p_visible) reload(); } +void NotebookNodeExplorer::setExternalFilesVisible(bool p_visible) +{ + if (m_externalFilesVisible == p_visible) { + return; + } + + m_externalFilesVisible = p_visible; + reload(); +} + +void NotebookNodeExplorer::setAutoImportExternalFiles(bool p_enabled) +{ + if (m_autoImportExternalFiles == p_enabled) { + return; + } + + m_autoImportExternalFiles = p_enabled; +} + void NotebookNodeExplorer::manualSort() { auto node = getCurrentNode(); @@ -1465,7 +1659,7 @@ void NotebookNodeExplorer::manualSort() treeWidget->setColumnCount(2); treeWidget->setHeaderLabels({tr("Name"), tr("Created Time"), tr("Modified Time")}); - const auto &children = parentNode->getChildren(); + const auto &children = parentNode->getChildrenRef(); for (int i = 0; i < children.size(); ++i) { const auto &child = children[i]; if (m_notebook->isRecycleBinNode(child.data())) { @@ -1498,3 +1692,107 @@ void NotebookNodeExplorer::manualSort() updateNode(parentNode); } } + +Node *NotebookNodeExplorer::currentExploredFolderNode() const +{ + if (!m_notebook) { + return nullptr; + } + + auto node = getCurrentNode(); + if (node) { + if (!node->isContainer()) { + node = node->getParent(); + } + Q_ASSERT(node && node->isContainer()); + } else { + node = m_notebook->getRootNode().data(); + } + + return node; +} + +void NotebookNodeExplorer::setViewOrder(int p_order) +{ + if (m_viewOrder == p_order) { + return; + } + + m_viewOrder = p_order; + reload(); +} + +void NotebookNodeExplorer::openSelectedNodes() +{ + // Support nodes and external nodes. + // Do nothing for folders. + auto selectedNodes = getSelectedNodes(); + for (const auto &externalNode : selectedNodes.second) { + if (!externalNode->isFolder()) { + emit fileActivated(externalNode->fetchAbsolutePath(), QSharedPointer::create()); + } + } + + for (const auto &node : selectedNodes.first) { + if (checkInvalidNode(node)) { + continue; + } + + if (node->hasContent()) { + emit nodeActivated(node, QSharedPointer::create()); + } + } +} + +QSharedPointer NotebookNodeExplorer::importToIndex(const ExternalNode *p_node) +{ + auto node = m_notebook->addAsNode(p_node->getNode(), + p_node->isFolder() ? Node::Flag::Container : Node::Flag::Content, + p_node->getName(), + NodeParameters()); + updateNode(p_node->getNode()); + if (node) { + setCurrentNode(node.data()); + } + return node; +} + +void NotebookNodeExplorer::importToIndex(const QVector &p_nodes) +{ + QSet nodesToUpdate; + Node *currentNode = nullptr; + + for (auto externalNode : p_nodes) { + auto node = m_notebook->addAsNode(externalNode->getNode(), + externalNode->isFolder() ? Node::Flag::Container : Node::Flag::Content, + externalNode->getName(), + NodeParameters()); + nodesToUpdate.insert(externalNode->getNode()); + currentNode = node.data(); + } + + for (auto node : nodesToUpdate) { + updateNode(node); + } + if (currentNode) { + setCurrentNode(currentNode); + } +} + +bool NotebookNodeExplorer::checkInvalidNode(const Node *p_node) const +{ + if (!p_node) { + return true; + } + + if (!p_node->exists()) { + MessageBoxHelper::notify(MessageBoxHelper::Warning, + tr("Invalid node (%1).").arg(p_node->getName()), + tr("Please check if the node exists on the disk."), + p_node->fetchAbsolutePath(), + VNoteX::getInst().getMainWindow()); + return true; + } + + return false; +} diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index 788a17a0..8167f9a4 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "qtreewidgetstatecache.h" #include "clipboarddata.h" @@ -22,6 +23,7 @@ namespace vnotex class TreeWidget; struct FileOpenParameters; class Event; + class ExternalNode; class NotebookNodeExplorer : public QWidget { @@ -32,13 +34,13 @@ namespace vnotex class NodeData { public: - enum class NodeType { Node, Attachment, Invalid }; + enum class NodeType { Node, ExternalNode, Invalid }; NodeData(); explicit NodeData(Node *p_node, bool p_loaded); - explicit NodeData(const QString &p_name); + explicit NodeData(const QSharedPointer &p_externalNode); NodeData(const NodeData &p_other); @@ -50,13 +52,13 @@ namespace vnotex bool isNode() const; - bool isAttachment() const; + bool isExternalNode() const; NodeData::NodeType getType() const; Node *getNode() const; - const QString &getName() const; + ExternalNode *getExternalNode() const; void clear(); @@ -67,11 +69,9 @@ namespace vnotex private: NodeType m_type = NodeType::Invalid; - union - { - Node *m_node = nullptr; - QString m_name; - }; + Node *m_node = nullptr; + + QSharedPointer m_externalNode; bool m_loaded = false; }; @@ -104,10 +104,18 @@ namespace vnotex void setRecycleBinNodeVisible(bool p_visible); + void setViewOrder(int p_order); + + void setExternalFilesVisible(bool p_visible); + + void setAutoImportExternalFiles(bool p_enabled); + + Node *currentExploredFolderNode() const; + signals: void nodeActivated(Node *p_node, const QSharedPointer &p_paras); - void fileActivated(const QString &p_path); + void fileActivated(const QString &p_path, const QSharedPointer &p_paras); // @m_response of @p_event: true to continue the move, false to cancel the move. void nodeAboutToMove(Node *p_node, const QSharedPointer &p_event); @@ -132,7 +140,10 @@ namespace vnotex Delete, DeleteFromRecycleBin, RemoveFromConfig, - Sort + Sort, + Reload, + ImportToConfig, + Open }; void setupUI(); @@ -149,13 +160,19 @@ namespace vnotex void loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; + void loadNode(QTreeWidgetItem *p_item, const QSharedPointer &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; - QIcon getNodeItemIcon(const Node *p_node) const; + void fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const; + + const QIcon &getNodeItemIcon(const Node *p_node) const; + + const QIcon &getNodeItemIcon(const ExternalNode *p_node) const; void initNodeIcons() const; @@ -177,7 +194,7 @@ namespace vnotex void createContextMenuOnNode(QMenu *p_menu, const Node *p_node); - void createContextMenuOnAttachment(QMenu *p_menu, const QString &p_name); + void createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node); // Factory function to create action. QAction *createAction(Action p_act, QObject *p_parent); @@ -186,8 +203,7 @@ namespace vnotex void pasteNodesFromClipboard(); - // Only return selected Nodes. - QVector getSelectedNodes() const; + QPair, QVector> getSelectedNodes() const; void removeSelectedNodes(bool p_skipRecycleBin); @@ -226,6 +242,16 @@ namespace vnotex // Sort nodes in config file. void manualSort(); + void openSelectedNodes(); + + QSharedPointer importToIndex(const ExternalNode *p_node); + + void importToIndex(const QVector &p_nodes); + + // Check whether @p_node is a valid node. Will notify user. + // Return true if it is invalid. + bool checkInvalidNode(const Node *p_node) const; + static NotebookNodeExplorer::NodeData getItemNodeData(const QTreeWidgetItem *p_item); static void setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data); @@ -242,11 +268,25 @@ namespace vnotex bool m_recycleBinNodeVisible = false; + int m_viewOrder = ViewOrder::OrderedByConfiguration; + + bool m_externalFilesVisible = true; + + bool m_autoImportExternalFiles = true; + static QIcon s_folderNodeIcon; + static QIcon s_fileNodeIcon; + + static QIcon s_invalidFolderNodeIcon; + + static QIcon s_invalidFileNodeIcon; + static QIcon s_recycleBinNodeIcon; - static const QString c_nodeIconForegroundName; + static QIcon s_externalFolderNodeIcon; + + static QIcon s_externalFileNodeIcon; }; } diff --git a/src/widgets/titlebar.cpp b/src/widgets/titlebar.cpp index 5ea4d9da..fcc68ecc 100644 --- a/src/widgets/titlebar.cpp +++ b/src/widgets/titlebar.cpp @@ -127,6 +127,11 @@ QAction *TitleBar::addMenuAction(const QString &p_iconName, const QString &p_tex return act; } +QMenu *TitleBar::addMenuSubMenu(const QString &p_text) +{ + return m_menu->addMenu(p_text); +} + void TitleBar::addMenuSeparator() { Q_ASSERT(m_menu); diff --git a/src/widgets/titlebar.h b/src/widgets/titlebar.h index 38097b78..c5ac4d35 100644 --- a/src/widgets/titlebar.h +++ b/src/widgets/titlebar.h @@ -39,6 +39,11 @@ namespace vnotex template QAction *addMenuAction(const QString &p_text, const QObject *p_context, Functor p_functor); + template + QAction *addMenuAction(QMenu *p_subMenu, const QString &p_text, const QObject *p_context, Functor p_functor); + + QMenu *addMenuSubMenu(const QString &p_text); + void addMenuSeparator(); protected: @@ -91,6 +96,14 @@ namespace vnotex auto act = m_menu->addAction(p_text, p_context, p_functor); return act; } + + template + QAction *TitleBar::addMenuAction(QMenu *p_subMenu, const QString &p_text, const QObject *p_context, Functor p_functor) + { + Q_ASSERT(p_subMenu->parent() == m_menu); + auto act = p_subMenu->addAction(p_text, p_context, p_functor); + return act; + } } // ns vnotex #endif // TITLEBAR_H