support external nodes (#1718)

This commit is contained in:
Le Tan 2021-03-14 09:37:06 +08:00 committed by GitHub
parent aa00164dff
commit 9895207dd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 931 additions and 193 deletions

View File

@ -0,0 +1,34 @@
#include "externalnode.h"
#include "node.h"
#include <utils/pathutils.h>
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);
}

View File

@ -0,0 +1,41 @@
#ifndef EXTERNALNODE_H
#define EXTERNALNODE_H
#include <QString>
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

View File

@ -103,13 +103,13 @@ bool Node::containsChild(const QString &p_name, bool p_caseSensitive) const
bool Node::containsChild(const QSharedPointer<Node> &p_node) const bool Node::containsChild(const QSharedPointer<Node> &p_node) const
{ {
return getChildren().indexOf(p_node) != -1; return m_children.indexOf(p_node) != -1;
} }
QSharedPointer<Node> Node::findChild(const QString &p_name, bool p_caseSensitive) const QSharedPointer<Node> Node::findChild(const QString &p_name, bool p_caseSensitive) const
{ {
auto targetName = p_caseSensitive ? p_name : p_name.toLower(); 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 if (p_caseSensitive ? child->getName() == targetName
: child->getName().toLower() == targetName) { : child->getName().toLower() == targetName) {
return child; return child;
@ -164,7 +164,12 @@ void Node::setModifiedTimeUtc()
m_modifiedTimeUtc = QDateTime::currentDateTimeUtc(); m_modifiedTimeUtc = QDateTime::currentDateTimeUtc();
} }
const QVector<QSharedPointer<Node>> &Node::getChildren() const const QVector<QSharedPointer<Node>> &Node::getChildrenRef() const
{
return m_children;
}
QVector<QSharedPointer<Node>> Node::getChildren() const
{ {
return m_children; return m_children;
} }
@ -347,3 +352,54 @@ void Node::sortChildren(const QVector<int> &p_beforeIdx, const QVector<int> &p_a
save(); save();
} }
QVector<QSharedPointer<ExternalNode>> Node::fetchExternalChildren() const
{
return getConfigMgr()->fetchExternalChildren(const_cast<Node *>(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;
}
}

View File

@ -15,6 +15,7 @@ namespace vnotex
class INotebookConfigMgr; class INotebookConfigMgr;
class INotebookBackend; class INotebookBackend;
class File; class File;
class ExternalNode;
// Used when add/new a node. // Used when add/new a node.
struct NodeParameters struct NodeParameters
@ -35,7 +36,9 @@ namespace vnotex
Content = 0x1, Content = 0x1,
// A node with children. // A node with children.
Container = 0x2, Container = 0x2,
ReadOnly = 0x4 ReadOnly = 0x4,
// Whether a node exists on disk.
Exists = 0x10
}; };
Q_DECLARE_FLAGS(Flags, Flag) Q_DECLARE_FLAGS(Flags, Flag)
@ -86,6 +89,11 @@ namespace vnotex
bool hasContent() const; bool hasContent() const;
// Whether the node exists on disk.
bool exists() const;
void setExists(bool p_exists);
Node::Flags getFlags() const; Node::Flags getFlags() const;
Node::Use getUse() const; Node::Use getUse() const;
@ -98,7 +106,8 @@ namespace vnotex
const QDateTime &getModifiedTimeUtc() const; const QDateTime &getModifiedTimeUtc() const;
void setModifiedTimeUtc(); void setModifiedTimeUtc();
const QVector<QSharedPointer<Node>> &getChildren() const; const QVector<QSharedPointer<Node>> &getChildrenRef() const;
QVector<QSharedPointer<Node>> getChildren() const;
int getChildrenCount() const; int getChildrenCount() const;
QSharedPointer<Node> findChild(const QString &p_name, bool p_caseSensitive = true) const; QSharedPointer<Node> findChild(const QString &p_name, bool p_caseSensitive = true) const;
@ -107,12 +116,20 @@ namespace vnotex
bool containsChild(const QSharedPointer<Node> &p_node) const; bool containsChild(const QSharedPointer<Node> &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<Node> &p_node); void addChild(const QSharedPointer<Node> &p_node);
void insertChild(int p_idx, const QSharedPointer<Node> &p_node); void insertChild(int p_idx, const QSharedPointer<Node> &p_node);
void removeChild(const QSharedPointer<Node> &p_node); void removeChild(const QSharedPointer<Node> &p_node);
QVector<QSharedPointer<ExternalNode>> fetchExternalChildren() const;
void setParent(Node *p_parent); void setParent(Node *p_parent);
Node *getParent() const; Node *getParent() const;

View File

@ -161,7 +161,7 @@ const QSharedPointer<Node> &Notebook::getRootNode() const
QSharedPointer<Node> Notebook::getRecycleBinNode() const QSharedPointer<Node> Notebook::getRecycleBinNode() const
{ {
auto root = getRootNode(); auto root = getRootNode();
auto children = root->getChildren(); const auto &children = root->getChildrenRef();
auto it = std::find_if(children.begin(), auto it = std::find_if(children.begin(),
children.end(), children.end(),
[this](const QSharedPointer<Node> &p_node) { [this](const QSharedPointer<Node> &p_node) {
@ -203,7 +203,7 @@ QSharedPointer<Node> Notebook::loadNodeByPath(const QString &p_path)
relativePath = p_path; relativePath = p_path;
} }
return m_configMgr->loadNodeByPath(m_root, relativePath); return m_configMgr->loadNodeByPath(getRootNode(), relativePath);
} }
QSharedPointer<Node> Notebook::copyNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move) QSharedPointer<Node> Notebook::copyNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move)
@ -227,17 +227,15 @@ QSharedPointer<Node> Notebook::copyNodeAsChildOf(const QSharedPointer<Node> &p_s
void Notebook::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly) void Notebook::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly)
{ {
Q_ASSERT(p_node && !p_node->isRoot());
Q_ASSERT(p_node->getNotebook() == this); Q_ASSERT(p_node->getNotebook() == this);
m_configMgr->removeNode(p_node, p_force, p_configOnly); 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()); Q_ASSERT(p_node);
auto children = p_node->getParent()->getChildren(); removeNode(p_node->sharedFromThis(), p_force, p_configOnly);
auto it = std::find(children.begin(), children.end(), p_node);
Q_ASSERT(it != children.end());
removeNode(*it, p_force, p_configOnly);
} }
bool Notebook::isRecycleBinNode(const Node *p_node) const bool Notebook::isRecycleBinNode(const Node *p_node) const
@ -261,22 +259,14 @@ bool Notebook::isNodeInRecycleBin(const Node *p_node) const
return false; return false;
} }
void Notebook::moveNodeToRecycleBin(const Node *p_node) void Notebook::moveNodeToRecycleBin(Node *p_node)
{ {
Q_ASSERT(p_node && !p_node->isRoot()); moveNodeToRecycleBin(p_node->sharedFromThis());
auto children = p_node->getParent()->getChildren();
for (auto &child : children) {
if (p_node == child) {
moveNodeToRecycleBin(child);
return;
}
}
Q_ASSERT(false);
} }
void Notebook::moveNodeToRecycleBin(const QSharedPointer<Node> &p_node) void Notebook::moveNodeToRecycleBin(const QSharedPointer<Node> &p_node)
{ {
Q_ASSERT(p_node && !p_node->isRoot());
auto destNode = getOrCreateRecycleBinDateNode(); auto destNode = getOrCreateRecycleBinDateNode();
copyNodeAsChildOf(p_node, destNode.data(), true); copyNodeAsChildOf(p_node, destNode.data(), true);
} }
@ -299,8 +289,9 @@ QSharedPointer<Node> Notebook::getOrCreateRecycleBinDateNode()
void Notebook::emptyNode(const Node *p_node, bool p_force) void Notebook::emptyNode(const Node *p_node, bool p_force)
{ {
// Copy the children.
auto children = p_node->getChildren(); auto children = p_node->getChildren();
for (auto &child : children) { for (const auto &child : children) {
removeNode(child, p_force); removeNode(child, p_force);
} }
} }
@ -355,3 +346,8 @@ QSharedPointer<Node> Notebook::copyAsNode(Node *p_parent,
{ {
return m_configMgr->copyAsNode(p_parent, p_flags, p_path); return m_configMgr->copyAsNode(p_parent, p_flags, p_path);
} }
void Notebook::reloadNode(Node *p_node)
{
m_configMgr->reloadNode(p_node);
}

View File

@ -99,11 +99,11 @@ namespace vnotex
// @p_configOnly: if true, will just remove node from config. // @p_configOnly: if true, will just remove node from config.
void removeNode(const QSharedPointer<Node> &p_node, bool p_force = false, bool p_configOnly = false); void removeNode(const QSharedPointer<Node> &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<Node> &p_node); void moveNodeToRecycleBin(const QSharedPointer<Node> &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. // Move @p_filePath to the recycle bin, without adding it as a child node.
void moveFileToRecycleBin(const QString &p_filePath); void moveFileToRecycleBin(const QString &p_filePath);
@ -127,6 +127,8 @@ namespace vnotex
bool isBuiltInFolder(const Node *p_node, const QString &p_name) const; 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_defaultAttachmentFolder;
static const QString c_defaultImageFolder; static const QString c_defaultImageFolder;

View File

@ -1,4 +1,5 @@
SOURCES += \ SOURCES += \
$$PWD/externalnode.cpp \
$$PWD/notebook.cpp \ $$PWD/notebook.cpp \
$$PWD/bundlenotebookfactory.cpp \ $$PWD/bundlenotebookfactory.cpp \
$$PWD/notebookparameters.cpp \ $$PWD/notebookparameters.cpp \
@ -8,6 +9,7 @@ SOURCES += \
$$PWD/vxnodefile.cpp $$PWD/vxnodefile.cpp
HEADERS += \ HEADERS += \
$$PWD/externalnode.h \
$$PWD/notebook.h \ $$PWD/notebook.h \
$$PWD/inotebookfactory.h \ $$PWD/inotebookfactory.h \
$$PWD/bundlenotebookfactory.h \ $$PWD/bundlenotebookfactory.h \

View File

@ -67,6 +67,10 @@ namespace vnotex
virtual bool exists(const QString &p_path) const = 0; 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 childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const = 0;
virtual bool isFile(const QString &p_path) const = 0; virtual bool isFile(const QString &p_path) const = 0;

View File

@ -86,6 +86,18 @@ bool LocalNotebookBackend::exists(const QString &p_path) const
return QFileInfo::exists(getFullPath(p_path)); 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 bool LocalNotebookBackend::childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const
{ {
return FileUtils::childExistsCaseInsensitive(getFullPath(p_dirPath), p_name); return FileUtils::childExistsCaseInsensitive(getFullPath(p_dirPath), p_name);

View File

@ -47,6 +47,10 @@ namespace vnotex
bool exists(const QString &p_path) const Q_DECL_OVERRIDE; 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 childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const Q_DECL_OVERRIDE;
bool isFile(const QString &p_path) const Q_DECL_OVERRIDE; bool isFile(const QString &p_path) const Q_DECL_OVERRIDE;

View File

@ -36,7 +36,7 @@ namespace vnotex
const QSharedPointer<INotebookBackend> &getBackend() const; const QSharedPointer<INotebookBackend> &getBackend() const;
virtual QSharedPointer<Node> loadRootNode() const = 0; virtual QSharedPointer<Node> loadRootNode() = 0;
virtual void loadNode(Node *p_node) const = 0; virtual void loadNode(Node *p_node) const = 0;
virtual void saveNode(const Node *p_node) = 0; virtual void saveNode(const Node *p_node) = 0;
@ -75,6 +75,10 @@ namespace vnotex
virtual QString fetchNodeAttachmentFolderPath(Node *p_node) = 0; virtual QString fetchNodeAttachmentFolderPath(Node *p_node) = 0;
virtual QVector<QSharedPointer<ExternalNode>> fetchExternalChildren(Node *p_node) const = 0;
virtual void reloadNode(Node *p_node) = 0;
protected: protected:
// Version of the config processing code. // Version of the config processing code.
virtual QString getCodeVersion() const; virtual QString getCodeVersion() const;

View File

@ -8,6 +8,7 @@
#include <notebookbackend/inotebookbackend.h> #include <notebookbackend/inotebookbackend.h>
#include <notebook/notebookparameters.h> #include <notebook/notebookparameters.h>
#include <notebook/vxnode.h> #include <notebook/vxnode.h>
#include <notebook/externalnode.h>
#include <notebook/bundlenotebook.h> #include <notebook/bundlenotebook.h>
#include <utils/utils.h> #include <utils/utils.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
@ -205,11 +206,12 @@ void VXNotebookConfigMgr::createEmptyRootNode()
writeNodeConfig(c_nodeConfigName, node); writeNodeConfig(c_nodeConfigName, node);
} }
QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode() const QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode()
{ {
auto nodeConfig = readNodeConfig(""); auto nodeConfig = readNodeConfig("");
QSharedPointer<Node> root = nodeConfigToNode(*nodeConfig, "", nullptr); QSharedPointer<Node> root = nodeConfigToNode(*nodeConfig, "", nullptr);
root->setUse(Node::Use::Root); root->setUse(Node::Use::Root);
root->setExists(true);
Q_ASSERT(root->isLoaded()); Q_ASSERT(root->isLoaded());
if (!markRecycleBinNode(root)) { if (!markRecycleBinNode(root)) {
@ -219,11 +221,16 @@ QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode() const
return root; return root;
} }
bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer<Node> &p_root) const bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer<Node> &p_root)
{ {
auto node = p_root->findChild(c_recycleBinFolderName, auto node = p_root->findChild(c_recycleBinFolderName,
FileUtils::isPlatformNameCaseSensitive()); FileUtils::isPlatformNameCaseSensitive());
if (node) { if (node) {
if (!node->exists()) {
removeNode(node, true, true);
return false;
}
node->setUse(Node::Use::RecycleBin); node->setUse(Node::Use::RecycleBin);
markNodeReadOnly(node.data()); markNodeReadOnly(node.data());
return true; return true;
@ -239,7 +246,7 @@ void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const
} }
p_node->setReadOnly(true); p_node->setReadOnly(true);
for (auto &child : p_node->getChildren()) { for (const auto &child : p_node->getChildrenRef()) {
markNodeReadOnly(child.data()); markNodeReadOnly(child.data());
} }
} }
@ -305,16 +312,30 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi
{ {
QVector<QSharedPointer<Node>> children; QVector<QSharedPointer<Node>> children;
children.reserve(p_config.m_files.size() + p_config.m_folders.size()); 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) { 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<VXNode>::create(folder.m_name, auto folderNode = QSharedPointer<VXNode>::create(folder.m_name,
getNotebook(), getNotebook(),
p_node); p_node);
inheritNodeFlags(p_node, folderNode.data()); inheritNodeFlags(p_node, folderNode.data());
folderNode->setExists(getBackend()->existsDir(PathUtils::concatenateFilePath(basePath, folder.m_name)));
children.push_back(folderNode); children.push_back(folderNode);
} }
for (const auto &file : p_config.m_files) { 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<VXNode>::create(file.m_id, auto fileNode = QSharedPointer<VXNode>::create(file.m_id,
file.m_name, file.m_name,
file.m_createdTimeUtc, file.m_createdTimeUtc,
@ -324,6 +345,7 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi
getNotebook(), getNotebook(),
p_node); p_node);
inheritNodeFlags(p_node, fileNode.data()); inheritNodeFlags(p_node, fileNode.data());
fileNode->setExists(getBackend()->existsFile(PathUtils::concatenateFilePath(basePath, file.m_name)));
children.push_back(fileNode); children.push_back(fileNode);
} }
@ -338,7 +360,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::newNode(Node *p_parent,
Node::Flags p_flags, Node::Flags p_flags,
const QString &p_name) const QString &p_name)
{ {
Q_ASSERT(p_parent && p_parent->isContainer()); Q_ASSERT(p_parent && p_parent->isContainer() && !p_name.isEmpty());
QSharedPointer<Node> node; QSharedPointer<Node> node;
@ -359,6 +381,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::addAsNode(Node *p_parent,
{ {
Q_ASSERT(p_parent && p_parent->isContainer()); Q_ASSERT(p_parent && p_parent->isContainer());
// TODO: reuse the config if available.
QSharedPointer<Node> node; QSharedPointer<Node> node;
if (p_flags & Node::Flag::Content) { if (p_flags & Node::Flag::Content) {
Q_ASSERT(!(p_flags & Node::Flag::Container)); Q_ASSERT(!(p_flags & Node::Flag::Container));
@ -407,6 +430,9 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFileNode(Node *p_parent,
// Write empty file. // Write empty file.
if (p_create) { if (p_create) {
getBackend()->writeFile(node->fetchPath(), QString()); getBackend()->writeFile(node->fetchPath(), QString());
node->setExists(true);
} else {
node->setExists(getBackend()->existsFile(node->fetchPath()));
} }
addChildNode(p_parent, node); addChildNode(p_parent, node);
@ -433,6 +459,9 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFolderNode(Node *p_parent,
// Make folder. // Make folder.
if (p_create) { if (p_create) {
getBackend()->makePath(node->fetchPath()); getBackend()->makePath(node->fetchPath());
node->setExists(true);
} else {
node->setExists(getBackend()->existsDir(node->fetchPath()));
} }
writeNodeConfig(node.data()); writeNodeConfig(node.data());
@ -452,7 +481,7 @@ QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::nodeToNodeC
p_node->getCreatedTimeUtc(), p_node->getCreatedTimeUtc(),
p_node->getModifiedTimeUtc()); p_node->getModifiedTimeUtc());
for (const auto &child : p_node->getChildren()) { for (const auto &child : p_node->getChildrenRef()) {
if (child->hasContent()) { if (child->hasContent()) {
NodeFileConfig fileConfig; NodeFileConfig fileConfig;
fileConfig.m_name = child->getName(); fileConfig.m_name = child->getName();
@ -477,7 +506,7 @@ QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::nodeToNodeC
void VXNotebookConfigMgr::loadNode(Node *p_node) const void VXNotebookConfigMgr::loadNode(Node *p_node) const
{ {
if (p_node->isLoaded()) { if (p_node->isLoaded() || !p_node->exists()) {
return; return;
} }
@ -513,7 +542,7 @@ void VXNotebookConfigMgr::addChildNode(Node *p_parent, const QSharedPointer<Node
{ {
if (p_child->isContainer()) { if (p_child->isContainer()) {
int idx = 0; int idx = 0;
auto children = p_parent->getChildren(); const auto &children = p_parent->getChildrenRef();
for (; idx < children.size(); ++idx) { for (; idx < children.size(); ++idx) {
if (!children[idx]->isContainer()) { if (!children[idx]->isContainer()) {
break; break;
@ -558,6 +587,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer
{ {
Q_ASSERT(p_dest->isContainer()); Q_ASSERT(p_dest->isContainer());
if (!p_src->exists()) {
if (p_move) {
p_src->getNotebook()->removeNode(p_src);
}
return nullptr;
}
QSharedPointer<Node> node; QSharedPointer<Node> node;
if (p_src->isContainer()) { if (p_src->isContainer()) {
node = copyFolderNodeAsChildOf(p_src, p_dest, p_move); node = copyFolderNodeAsChildOf(p_src, p_dest, p_move);
@ -605,6 +641,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
attachmentFolder, attachmentFolder,
notebook, notebook,
p_dest); p_dest);
destNode->setExists(true);
addChildNode(p_dest, destNode); addChildNode(p_dest, destNode);
writeNodeConfig(p_dest); writeNodeConfig(p_dest);
@ -643,6 +680,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
p_src->getModifiedTimeUtc(), p_src->getModifiedTimeUtc(),
QStringList(), QStringList(),
QVector<QSharedPointer<Node>>()); QVector<QSharedPointer<Node>>());
destNode->setExists(true);
writeNodeConfig(destNode.data()); writeNodeConfig(destNode.data());
@ -650,7 +688,8 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
writeNodeConfig(p_dest); writeNodeConfig(p_dest);
// Copy children node. // 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); copyNodeAsChildOf(childNode, destNode.data(), p_move);
} }
@ -664,13 +703,18 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly) void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly)
{ {
auto parentNode = p_node->getParent(); auto parentNode = p_node->getParent();
if (!p_configOnly) { if (!p_configOnly && p_node->exists()) {
// Remove all children. // 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); 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) { if (parentNode) {
@ -771,7 +815,9 @@ bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_n
const auto name = p_name.toLower(); const auto name = p_name.toLower();
if (name == c_recycleBinFolderName if (name == c_recycleBinFolderName
|| name == getNotebook()->getImageFolder().toLower() || name == getNotebook()->getImageFolder().toLower()
|| name == getNotebook()->getAttachmentFolder().toLower()) { || name == getNotebook()->getAttachmentFolder().toLower()
|| name == QStringLiteral("_v_images")
|| name == QStringLiteral("_v_attachments")) {
return true; return true;
} }
return BundleNotebookConfigMgr::isBuiltInFolder(p_node, p_name); return BundleNotebookConfigMgr::isBuiltInFolder(p_node, p_name);
@ -779,25 +825,36 @@ bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_n
QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest) QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest)
{ {
// Copy source file itself. // Skip copy if it already locates in dest folder.
auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchPath(), auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchAbsolutePath(),
PathUtils::fileName(p_srcPath)); PathUtils::fileName(p_srcPath));
destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath); if (!PathUtils::areSamePaths(p_srcPath, destFilePath)) {
getBackend()->copyFile(p_srcPath, destFilePath); // Copy source file itself.
destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
getBackend()->copyFile(p_srcPath, destFilePath);
// Copy media files fetched from content. // Copy media files fetched from content.
ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath); 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. // Create a file node.
auto currentTime = QDateTime::currentDateTimeUtc(); auto currentTime = QDateTime::currentDateTimeUtc();
auto destNode = QSharedPointer<VXNode>::create(getNotebook()->getAndUpdateNextNodeId(), destNode = QSharedPointer<VXNode>::create(getNotebook()->getAndUpdateNextNodeId(),
PathUtils::fileName(destFilePath), name,
currentTime, currentTime,
currentTime, currentTime,
QStringList(), QStringList(),
QString(), QString(),
getNotebook(), getNotebook(),
p_dest); p_dest);
destNode->setExists(true);
addChildNode(p_dest, destNode); addChildNode(p_dest, destNode);
writeNodeConfig(p_dest); writeNodeConfig(p_dest);
@ -806,24 +863,33 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src
QSharedPointer<Node> VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest) QSharedPointer<Node> 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)); PathUtils::fileName(p_srcPath));
destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); if (!PathUtils::areSamePaths(p_srcPath, destFolderPath)) {
destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
// Copy folder. // Copy folder.
getBackend()->copyDir(p_srcPath, destFolderPath); 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. // Create a folder node.
auto notebook = getNotebook(); auto notebook = getNotebook();
auto destNode = QSharedPointer<VXNode>::create(PathUtils::fileName(destFolderPath), destNode = QSharedPointer<VXNode>::create(name, notebook, p_dest);
notebook,
p_dest);
auto currentTime = QDateTime::currentDateTimeUtc(); auto currentTime = QDateTime::currentDateTimeUtc();
destNode->loadCompleteInfo(notebook->getAndUpdateNextNodeId(), destNode->loadCompleteInfo(notebook->getAndUpdateNextNodeId(),
currentTime, currentTime,
currentTime, currentTime,
QStringList(), QStringList(),
QVector<QSharedPointer<Node>>()); QVector<QSharedPointer<Node>>());
destNode->setExists(true);
writeNodeConfig(destNode.data()); writeNodeConfig(destNode.data());
@ -839,3 +905,50 @@ void VXNotebookConfigMgr::inheritNodeFlags(const Node *p_node, Node *p_child) co
markNodeReadOnly(p_child); markNodeReadOnly(p_child);
} }
} }
QVector<QSharedPointer<ExternalNode>> VXNotebookConfigMgr::fetchExternalChildren(Node *p_node) const
{
Q_ASSERT(p_node->isContainer());
QVector<QSharedPointer<ExternalNode>> 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<ExternalNode>::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<ExternalNode>::create(p_node, file, ExternalNode::Type::File));
}
}
return externalNodes;
}
void VXNotebookConfigMgr::reloadNode(Node *p_node)
{
// TODO.
}

View File

@ -31,7 +31,7 @@ namespace vnotex
void createEmptySkeleton(const NotebookParameters &p_paras) Q_DECL_OVERRIDE; void createEmptySkeleton(const NotebookParameters &p_paras) Q_DECL_OVERRIDE;
QSharedPointer<Node> loadRootNode() const Q_DECL_OVERRIDE; QSharedPointer<Node> loadRootNode() Q_DECL_OVERRIDE;
void loadNode(Node *p_node) const Q_DECL_OVERRIDE; void loadNode(Node *p_node) const Q_DECL_OVERRIDE;
void saveNode(const Node *p_node) 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; QString fetchNodeAttachmentFolderPath(Node *p_node) Q_DECL_OVERRIDE;
QVector<QSharedPointer<ExternalNode>> fetchExternalChildren(Node *p_node) const Q_DECL_OVERRIDE;
void reloadNode(Node *p_node) Q_DECL_OVERRIDE;
private: private:
// Config of a file child. // Config of a file child.
struct NodeFileConfig struct NodeFileConfig
@ -173,7 +177,7 @@ namespace vnotex
void removeFilesOfNode(Node *p_node, bool p_force); void removeFilesOfNode(Node *p_node, bool p_force);
bool markRecycleBinNode(const QSharedPointer<Node> &p_root) const; bool markRecycleBinNode(const QSharedPointer<Node> &p_root);
void markNodeReadOnly(Node *p_node) const; void markNodeReadOnly(Node *p_node) const;

View File

@ -91,6 +91,9 @@ namespace vnotex
// @m_response of @p_event: true to continue the rename, false to cancel the rename. // @m_response of @p_event: true to continue the rename, false to cancel the rename.
void nodeAboutToRename(Node *p_node, const QSharedPointer<Event> &p_event); void nodeAboutToRename(Node *p_node, const QSharedPointer<Event> &p_event);
// @m_response of @p_event: true to continue the reload, false to cancel the reload.
void nodeAboutToReload(Node *p_node, const QSharedPointer<Event> &p_event);
// Requested to open @p_filePath. // Requested to open @p_filePath.
void openFileRequested(const QString &p_filePath, const QSharedPointer<FileOpenParameters> &p_paras); void openFileRequested(const QString &p_filePath, const QSharedPointer<FileOpenParameters> &p_paras);

View File

@ -24,8 +24,10 @@ void WidgetConfig::init(const QJsonObject &p_app,
m_findAndReplaceOptions = static_cast<FindOptions>(READINT(QStringLiteral("find_and_replace_options"))); m_findAndReplaceOptions = static_cast<FindOptions>(READINT(QStringLiteral("find_and_replace_options")));
m_noteExplorerViewOrder = READINT(QStringLiteral("note_explorer_view_order")); m_nodeExplorerViewOrder = READINT(QStringLiteral("node_explorer_view_order"));
m_noteExplorerRecycleBinNodeShown = READBOOL(QStringLiteral("note_explorer_recycle_bin_node_shown")); 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 QJsonObject WidgetConfig::toJson() const
@ -33,8 +35,10 @@ QJsonObject WidgetConfig::toJson() const
QJsonObject obj; QJsonObject obj;
obj[QStringLiteral("outline_auto_expanded_level")] = m_outlineAutoExpandedLevel; obj[QStringLiteral("outline_auto_expanded_level")] = m_outlineAutoExpandedLevel;
obj[QStringLiteral("find_and_replace_options")] = static_cast<int>(m_findAndReplaceOptions); obj[QStringLiteral("find_and_replace_options")] = static_cast<int>(m_findAndReplaceOptions);
obj[QStringLiteral("note_explorer_view_order")] = m_noteExplorerViewOrder; obj[QStringLiteral("node_explorer_view_order")] = m_nodeExplorerViewOrder;
obj[QStringLiteral("note_explorer_recycle_bin_node_shown")] = m_noteExplorerRecycleBinNodeShown; 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; return obj;
} }
@ -58,22 +62,42 @@ void WidgetConfig::setFindAndReplaceOptions(FindOptions p_options)
updateConfig(m_findAndReplaceOptions, p_options, this); 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);
} }

View File

@ -24,20 +24,30 @@ namespace vnotex
FindOptions getFindAndReplaceOptions() const; FindOptions getFindAndReplaceOptions() const;
void setFindAndReplaceOptions(FindOptions p_options); void setFindAndReplaceOptions(FindOptions p_options);
int getNoteExplorerViewOrder() const; int getNodeExplorerViewOrder() const;
void setNoteExplorerViewOrder(int p_viewOrder); void setNodeExplorerViewOrder(int p_viewOrder);
bool isNoteExplorerRecycleBinNodeShown() const; bool isNodeExplorerRecycleBinNodeVisible() const;
void setNoteExplorerRecycleBinNodeShown(bool p_shown); void setNodeExplorerRecycleBinNodeVisible(bool p_visible);
bool isNodeExplorerExternalFilesVisible() const;
void setNodeExplorerExternalFilesVisible(bool p_visible);
bool getNodeExplorerAutoImportExternalFilesEnabled() const;
void setNodeExplorerAutoImportExternalFilesEnabled(bool p_enabled);
private: private:
int m_outlineAutoExpandedLevel = 6; int m_outlineAutoExpandedLevel = 6;
FindOptions m_findAndReplaceOptions = FindOption::None; 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;
}; };
} }

View File

@ -286,8 +286,10 @@
"outline_auto_expanded_level" : 6, "outline_auto_expanded_level" : 6,
"//comment" : "Default find options in FindAndReplace", "//comment" : "Default find options in FindAndReplace",
"find_and_replace_options" : 16, "find_and_replace_options" : 16,
"//comment" : "View order of the note explorer", "//comment" : "View order of the node explorer",
"note_explorer_view_order" : 0, "node_explorer_view_order" : 0,
"note_explorer_recycle_bin_node_shown" : false "node_explorer_recycle_bin_node_visible" : false,
"node_explorer_external_files_visible" : true,
"node_explorer_auto_import_external_files_enabled" : true
} }
} }

View File

@ -224,7 +224,13 @@
}, },
"notebookexplorer" : { "notebookexplorer" : {
"node_icon" : { "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" : { "viewsplit" : {

View File

@ -83,7 +83,13 @@
}, },
"notebookexplorer" : { "notebookexplorer" : {
"node_icon" : { "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" : { "viewsplit" : {

View File

@ -220,7 +220,13 @@
}, },
"notebookexplorer" : { "notebookexplorer" : {
"node_icon" : { "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" : { "viewsplit" : {

View File

@ -119,7 +119,7 @@ QStringList Exporter::doExport(const ExportOption &p_option, const QString &p_ou
} }
p_folder->load(); p_folder->load();
const auto &children = p_folder->getChildren(); const auto &children = p_folder->getChildrenRef();
emit progressUpdated(0, children.size()); emit progressUpdated(0, children.size());
for (int i = 0; i < children.size(); ++i) { for (int i = 0; i < children.size(); ++i) {
if (checkAskedToStop()) { if (checkAskedToStop()) {
@ -192,7 +192,7 @@ QStringList Exporter::doExport(const ExportOption &p_option, Notebook *p_noteboo
auto rootNode = p_notebook->getRootNode(); auto rootNode = p_notebook->getRootNode();
Q_ASSERT(rootNode->isLoaded()); Q_ASSERT(rootNode->isLoaded());
const auto &children = rootNode->getChildren(); const auto &children = rootNode->getChildrenRef();
emit progressUpdated(0, children.size()); emit progressUpdated(0, children.size());
for (int i = 0; i < children.size(); ++i) { for (int i = 0; i < children.size(); ++i) {
if (checkAskedToStop()) { if (checkAskedToStop()) {

View File

@ -61,6 +61,7 @@ bool WebViewExporter::doExport(const ExportOption &p_option,
m_webViewStates = WebViewState::Started; m_webViewStates = WebViewState::Started;
auto baseUrl = PathUtils::pathToUrl(p_file->getContentPath()); auto baseUrl = PathUtils::pathToUrl(p_file->getContentPath());
m_viewer->adapter()->reset();
m_viewer->setHtml(m_htmlTemplate, baseUrl); m_viewer->setHtml(m_htmlTemplate, baseUrl);
auto textContent = p_file->read(); auto textContent = p_file->read();
@ -93,7 +94,7 @@ bool WebViewExporter::doExport(const ExportOption &p_option,
switch (p_option.m_targetFormat) { switch (p_option.m_targetFormat) {
case ExportFormat::HTML: case ExportFormat::HTML:
// TODO: not supported yet. // TODO: MIME HTML format is not supported yet.
Q_ASSERT(!p_option.m_htmlOption.m_useMimeHtmlFormat); Q_ASSERT(!p_option.m_htmlOption.m_useMimeHtmlFormat);
ret = doExportHtml(p_option.m_htmlOption, p_outputFile, baseUrl); ret = doExportHtml(p_option.m_htmlOption, p_outputFile, baseUrl);
break; break;

View File

@ -48,7 +48,7 @@ bool PathUtils::isEmptyDir(const QString &p_path)
QString PathUtils::concatenateFilePath(const QString &p_dirPath, const QString &p_name) 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()) { if (p_name.isEmpty()) {
return dirPath; return dirPath;
} }
@ -57,7 +57,7 @@ QString PathUtils::concatenateFilePath(const QString &p_dirPath, const QString &
return p_name; return p_name;
} }
return dirPath + '/' + p_name; return dirPath + "/" + p_name;
} }
QString PathUtils::dirName(const QString &p_path) QString PathUtils::dirName(const QString &p_path)

View File

@ -356,3 +356,14 @@ void MarkdownViewerAdapter::setSavedContent(const QString &p_headContent,
{ {
emit contentReady(p_headContent, p_styleContent, p_content, p_bodyClassList); 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();
}

View File

@ -123,6 +123,9 @@ namespace vnotex
void saveContent(); void saveContent();
// Should be called before WebViewer.setHtml().
void reset();
// Functions to be called from web side. // Functions to be called from web side.
public slots: public slots:
void setReady(bool p_ready); void setReady(bool p_ready);

View File

@ -497,10 +497,12 @@ void MarkdownViewWindow::syncViewerFromBuffer(bool p_syncPositionFromEditMode)
// TODO: Check buffer for last position recover. // TODO: Check buffer for last position recover.
// Use getPath() instead of getBasePath() to make in-page anchor work. // Use getPath() instead of getBasePath() to make in-page anchor work.
adapter()->reset();
m_viewer->setHtml(HtmlTemplateHelper::getMarkdownViewerTemplate(), m_viewer->setHtml(HtmlTemplateHelper::getMarkdownViewerTemplate(),
PathUtils::pathToUrl(buffer->getContentPath())); PathUtils::pathToUrl(buffer->getContentPath()));
adapter()->setText(m_bufferRevision, buffer->getContent(), lineNumber); adapter()->setText(m_bufferRevision, buffer->getContent(), lineNumber);
} else { } else {
adapter()->reset();
m_viewer->setHtml(""); m_viewer->setHtml("");
adapter()->setText(0, "", -1); adapter()->setText(0, "", -1);
} }

View File

@ -63,10 +63,15 @@ void NotebookExplorer::setupUI()
}); });
mainLayout->addWidget(m_selector); mainLayout->addWidget(m_selector);
const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig();
m_nodeExplorer = new NotebookNodeExplorer(this); 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, connect(m_nodeExplorer, &NotebookNodeExplorer::nodeActivated,
&VNoteX::getInst(), &VNoteX::openNodeRequested); &VNoteX::getInst(), &VNoteX::openNodeRequested);
connect(m_nodeExplorer, &NotebookNodeExplorer::fileActivated,
&VNoteX::getInst(), &VNoteX::openFileRequested);
connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToMove, connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToMove,
&VNoteX::getInst(), &VNoteX::nodeAboutToMove); &VNoteX::getInst(), &VNoteX::nodeAboutToMove);
connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToRemove, connect(m_nodeExplorer, &NotebookNodeExplorer::nodeAboutToRemove,
@ -78,6 +83,8 @@ void NotebookExplorer::setupUI()
TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
{ {
const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig();
auto titleBar = new TitleBar(tr("Notebook"), auto titleBar = new TitleBar(tr("Notebook"),
TitleBar::Action::Menu, TitleBar::Action::Menu,
p_parent); 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")); auto btn = titleBar->addActionButton(QStringLiteral("recycle_bin.svg"), tr("Toggle Recycle Bin Node"));
btn->defaultAction()->setCheckable(true); btn->defaultAction()->setCheckable(true);
btn->defaultAction()->setChecked(ConfigMgr::getInst().getWidgetConfig().isNoteExplorerRecycleBinNodeShown()); btn->defaultAction()->setChecked(widgetConfig.isNodeExplorerRecycleBinNodeVisible());
connect(btn, &QToolButton::triggered, connect(btn, &QToolButton::triggered,
this, [this](QAction *p_act) { this, [this](QAction *p_act) {
const bool checked = p_act->isChecked(); const bool checked = p_act->isChecked();
ConfigMgr::getInst().getWidgetConfig().setNoteExplorerRecycleBinNodeShown(checked); ConfigMgr::getInst().getWidgetConfig().setNodeExplorerRecycleBinNodeVisible(checked);
m_nodeExplorer->setRecycleBinNodeVisible(checked); m_nodeExplorer->setRecycleBinNodeVisible(checked);
}); });
} }
@ -113,6 +120,33 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
dialog.exec(); 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; return titleBar;
} }
@ -206,21 +240,7 @@ void NotebookExplorer::newNote()
Node *NotebookExplorer::currentExploredFolderNode() const Node *NotebookExplorer::currentExploredFolderNode() const
{ {
if (!m_currentNotebook) { return m_nodeExplorer->currentExploredFolderNode();
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;
} }
Node *NotebookExplorer::checkNotebookAndGetCurrentExploredFolderNode() const Node *NotebookExplorer::checkNotebookAndGetCurrentExploredFolderNode() const
@ -372,7 +392,7 @@ void NotebookExplorer::setupViewMenu(QMenu *p_menu)
act->setData(NotebookNodeExplorer::ViewOrder::OrderedByModifiedTimeReversed); act->setData(NotebookNodeExplorer::ViewOrder::OrderedByModifiedTimeReversed);
p_menu->addAction(act); p_menu->addAction(act);
int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerViewOrder();
for (const auto &act : ag->actions()) { for (const auto &act : ag->actions()) {
if (act->data().toInt() == viewOrder) { if (act->data().toInt() == viewOrder) {
act->setChecked(true); act->setChecked(true);
@ -382,8 +402,7 @@ void NotebookExplorer::setupViewMenu(QMenu *p_menu)
connect(ag, &QActionGroup::triggered, connect(ag, &QActionGroup::triggered,
this, [this](QAction *p_action) { this, [this](QAction *p_action) {
int order = p_action->data().toInt(); int order = p_action->data().toInt();
ConfigMgr::getInst().getWidgetConfig().setNoteExplorerViewOrder(order); ConfigMgr::getInst().getWidgetConfig().setNodeExplorerViewOrder(order);
m_nodeExplorer->setViewOrder(order);
m_nodeExplorer->reload();
}); });
} }

View File

@ -6,9 +6,11 @@
#include <QTreeWidget> #include <QTreeWidget>
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
#include <QSet>
#include <notebook/notebook.h> #include <notebook/notebook.h>
#include <notebook/node.h> #include <notebook/node.h>
#include <notebook/externalnode.h>
#include "exception.h" #include "exception.h"
#include "messageboxhelper.h" #include "messageboxhelper.h"
#include "vnotex.h" #include "vnotex.h"
@ -29,18 +31,23 @@
#include <core/fileopenparameters.h> #include <core/fileopenparameters.h>
#include <core/events.h> #include <core/events.h>
#include <core/configmgr.h> #include <core/configmgr.h>
#include <core/widgetconfig.h>
using namespace vnotex; using namespace vnotex;
const QString NotebookNodeExplorer::c_nodeIconForegroundName = "widgets#notebookexplorer#node_icon#fg";
QIcon NotebookNodeExplorer::s_folderNodeIcon; QIcon NotebookNodeExplorer::s_folderNodeIcon;
QIcon NotebookNodeExplorer::s_fileNodeIcon; QIcon NotebookNodeExplorer::s_fileNodeIcon;
QIcon NotebookNodeExplorer::s_invalidFolderNodeIcon;
QIcon NotebookNodeExplorer::s_invalidFileNodeIcon;
QIcon NotebookNodeExplorer::s_recycleBinNodeIcon; QIcon NotebookNodeExplorer::s_recycleBinNodeIcon;
QIcon NotebookNodeExplorer::s_externalFolderNodeIcon;
QIcon NotebookNodeExplorer::s_externalFileNodeIcon;
NotebookNodeExplorer::NodeData::NodeData() NotebookNodeExplorer::NodeData::NodeData()
{ {
} }
@ -52,9 +59,9 @@ NotebookNodeExplorer::NodeData::NodeData(Node *p_node, bool p_loaded)
{ {
} }
NotebookNodeExplorer::NodeData::NodeData(const QString &p_name) NotebookNodeExplorer::NodeData::NodeData(const QSharedPointer<ExternalNode> &p_externalNode)
: m_type(NodeType::Attachment), : m_type(NodeType::ExternalNode),
m_name(p_name), m_externalNode(p_externalNode),
m_loaded(true) m_loaded(true)
{ {
} }
@ -67,13 +74,12 @@ NotebookNodeExplorer::NodeData::NodeData(const NodeData &p_other)
m_node = p_other.m_node; m_node = p_other.m_node;
break; break;
case NodeType::Attachment: case NodeType::ExternalNode:
m_name = p_other.m_name; m_externalNode = p_other.m_externalNode;
break; break;
default: default:
m_node = p_other.m_node; Q_ASSERT(false);
m_name = p_other.m_name;
break; break;
} }
@ -96,13 +102,12 @@ NotebookNodeExplorer::NodeData &NotebookNodeExplorer::NodeData::operator=(const
m_node = p_other.m_node; m_node = p_other.m_node;
break; break;
case NodeType::Attachment: case NodeType::ExternalNode:
m_name = p_other.m_name; m_externalNode = p_other.m_externalNode;
break; break;
default: default:
m_node = p_other.m_node; Q_ASSERT(false);
m_name = p_other.m_name;
break; break;
} }
@ -121,9 +126,9 @@ bool NotebookNodeExplorer::NodeData::isNode() const
return m_type == NodeType::Node; 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 NotebookNodeExplorer::NodeData::NodeType NotebookNodeExplorer::NodeData::getType() const
@ -137,17 +142,17 @@ Node *NotebookNodeExplorer::NodeData::getNode() const
return m_node; return m_node;
} }
const QString &NotebookNodeExplorer::NodeData::getName() const ExternalNode *NotebookNodeExplorer::NodeData::getExternalNode() const
{ {
Q_ASSERT(isAttachment()); Q_ASSERT(isExternalNode());
return m_name; return m_externalNode.data();
} }
void NotebookNodeExplorer::NodeData::clear() void NotebookNodeExplorer::NodeData::clear()
{ {
m_type = NodeType::Invalid; m_type = NodeType::Invalid;
m_node = nullptr; m_node = nullptr;
m_name.clear(); m_externalNode.clear();
m_loaded = false; m_loaded = false;
} }
@ -180,15 +185,26 @@ void NotebookNodeExplorer::initNodeIcons() const
return; 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 &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 folderIconName("folder_node.svg");
const QString fileIconName("file_node.svg"); const QString fileIconName("file_node.svg");
const QString recycleBinIconName("recycle_bin.svg"); const QString recycleBinIconName("recycle_bin.svg");
s_folderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg); s_folderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg);
s_fileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), 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_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() void NotebookNodeExplorer::setupUI()
@ -244,8 +260,8 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent)
if (data.isNode()) { if (data.isNode()) {
createContextMenuOnNode(menu.data(), data.getNode()); createContextMenuOnNode(menu.data(), data.getNode());
} else if (data.isAttachment()) { } else if (data.isExternalNode()) {
createContextMenuOnAttachment(menu.data(), data.getName()); createContextMenuOnExternalNode(menu.data(), data.getExternalNode());
} }
} }
@ -263,9 +279,23 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent)
} }
if (data.isNode()) { if (data.isNode()) {
if (checkInvalidNode(data.getNode())) {
return;
}
emit nodeActivated(data.getNode(), QSharedPointer<FileOpenParameters>::create()); emit nodeActivated(data.getNode(), QSharedPointer<FileOpenParameters>::create());
} else if (data.isAttachment()) { } else if (data.isExternalNode()) {
// TODO. // Import to config first.
if (m_autoImportExternalFiles) {
auto importedNode = importToIndex(data.getExternalNode());
if (importedNode) {
emit nodeActivated(importedNode.data(), QSharedPointer<FileOpenParameters>::create());
}
return;
}
// Just open it.
emit fileActivated(data.getExternalNode()->fetchAbsolutePath(),
QSharedPointer<FileOpenParameters>::create());
} }
}); });
} }
@ -333,7 +363,7 @@ void NotebookNodeExplorer::generateNodeTree()
void NotebookNodeExplorer::loadRootNode(const Node *p_node) const 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. // Render recycle bin node first.
auto recycleBinNode = m_notebook->getRecycleBinNode(); auto recycleBinNode = m_notebook->getRecycleBinNode();
@ -341,9 +371,20 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const
loadRecycleBinNode(recycleBinNode.data()); 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(); auto children = p_node->getChildren();
sortNodes(children); sortNodes(children);
for (auto &child : children) { for (const auto &child : children) {
if (recycleBinNode == child) { if (recycleBinNode == child) {
continue; continue;
} }
@ -378,15 +419,34 @@ void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, Node *p_node, int p
} }
} }
void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &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 void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
{ {
if (p_level < 0) { if (p_level < 0) {
return; 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(); auto children = p_node->getChildren();
sortNodes(children); sortNodes(children);
for (auto &child : children) { for (const auto &child : children) {
auto item = new QTreeWidgetItem(p_item); auto item = new QTreeWidgetItem(p_item);
loadNode(item, child.data(), p_level); 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)); setItemNodeData(p_item, NodeData(p_node, p_loaded));
p_item->setText(Column::Name, p_node->getName()); p_item->setText(Column::Name, p_node->getName());
p_item->setIcon(Column::Name, getNodeItemIcon(p_node)); 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<ExternalNode> &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()) { if (p_node->hasContent()) {
return s_fileNodeIcon; return p_node->exists() ? s_fileNodeIcon : s_invalidFileNodeIcon;
} else { } else {
if (p_node->getUse() == Node::Use::RecycleBin) { if (p_node->getUse() == Node::Use::RecycleBin) {
return s_recycleBinNodeIcon; 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 Node *NotebookNodeExplorer::getCurrentNode() const
@ -457,7 +528,7 @@ Node *NotebookNodeExplorer::getCurrentNode() const
auto item = m_masterExplorer->currentItem(); auto item = m_masterExplorer->currentItem();
if (item) { if (item) {
auto data = getItemNodeData(item); auto data = getItemNodeData(item);
while (data.isAttachment()) { while (item && !data.isNode()) {
item = item->parent(); item = item->parent();
if (item) { if (item) {
data = getItemNodeData(item); data = getItemNodeData(item);
@ -687,6 +758,9 @@ void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu)
p_menu->addSeparator(); p_menu->addSeparator();
act = createAction(Action::Reload, p_menu);
p_menu->addAction(act);
act = createAction(Action::OpenLocation, p_menu); act = createAction(Action::OpenLocation, p_menu);
p_menu->addAction(act); p_menu->addAction(act);
} }
@ -698,6 +772,9 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_
if (m_notebook->isRecycleBinNode(p_node)) { if (m_notebook->isRecycleBinNode(p_node)) {
// Recycle bin node. // Recycle bin node.
act = createAction(Action::Reload, p_menu);
p_menu->addAction(act);
if (selectedSize == 1) { if (selectedSize == 1) {
act = createAction(Action::EmptyRecycleBin, p_menu); act = createAction(Action::EmptyRecycleBin, p_menu);
p_menu->addAction(act); p_menu->addAction(act);
@ -707,12 +784,22 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_
} }
} else if (m_notebook->isNodeInRecycleBin(p_node)) { } else if (m_notebook->isNodeInRecycleBin(p_node)) {
// Node in recycle bin. // Node in recycle bin.
act = createAction(Action::Open, p_menu);
p_menu->addAction(act);
p_menu->addSeparator();
act = createAction(Action::Cut, p_menu); act = createAction(Action::Cut, p_menu);
p_menu->addAction(act); p_menu->addAction(act);
act = createAction(Action::DeleteFromRecycleBin, p_menu); act = createAction(Action::DeleteFromRecycleBin, p_menu);
p_menu->addAction(act); p_menu->addAction(act);
p_menu->addSeparator();
act = createAction(Action::Reload, p_menu);
p_menu->addAction(act);
if (selectedSize == 1) { if (selectedSize == 1) {
p_menu->addSeparator(); p_menu->addSeparator();
@ -723,6 +810,11 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_
p_menu->addAction(act); p_menu->addAction(act);
} }
} else { } else {
act = createAction(Action::Open, p_menu);
p_menu->addAction(act);
p_menu->addSeparator();
act = createAction(Action::NewNote, p_menu); act = createAction(Action::NewNote, p_menu);
p_menu->addAction(act); p_menu->addAction(act);
@ -750,6 +842,9 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_
p_menu->addSeparator(); p_menu->addSeparator();
act = createAction(Action::Reload, p_menu);
p_menu->addAction(act);
act = createAction(Action::Sort, p_menu); act = createAction(Action::Sort, p_menu);
p_menu->addAction(act); 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_node);
Q_UNUSED(p_name);
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) static QIcon generateMenuActionIcon(const QString &p_name)
@ -811,7 +924,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
connect(act, &QAction::triggered, connect(act, &QAction::triggered,
this, [this]() { this, [this]() {
auto node = getCurrentNode(); auto node = getCurrentNode();
if (!node) { if (checkInvalidNode(node)) {
return; return;
} }
@ -834,15 +947,32 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
act = new QAction(tr("Open &Location"), p_parent); act = new QAction(tr("Open &Location"), p_parent);
connect(act, &QAction::triggered, connect(act, &QAction::triggered,
this, [this]() { 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; QString locationPath;
auto node = getCurrentNode(); if (data.isNode()) {
if (node) { auto node = data.getNode();
if (checkInvalidNode(node)) {
return;
}
locationPath = node->fetchAbsolutePath(); locationPath = node->fetchAbsolutePath();
if (!node->isContainer()) { if (!node->isContainer()) {
locationPath = PathUtils::parentDirPath(locationPath); locationPath = PathUtils::parentDirPath(locationPath);
} }
} else if (m_notebook) { } else if (data.isExternalNode()) {
locationPath = m_notebook->getRootFolderAbsolutePath(); auto externalNode = data.getExternalNode();
locationPath = externalNode->fetchAbsolutePath();
if (!externalNode->isFolder()) {
locationPath = PathUtils::parentDirPath(locationPath);
}
} }
if (!locationPath.isEmpty()) { 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); act = new QAction(tr("Cop&y Path"), p_parent);
connect(act, &QAction::triggered, connect(act, &QAction::triggered,
this, [this]() { this, [this]() {
auto node = getCurrentNode(); auto item = m_masterExplorer->currentItem();
if (node) { if (!item) {
auto nodePath = node->fetchAbsolutePath(); 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); ClipboardUtils::setTextToClipboard(nodePath);
VNoteX::getInst().showStatusMessageShort(tr("Copied path: %1").arg(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); m_notebook->emptyNode(rbNode, true);
} catch (Exception &p_e) { } catch (Exception &p_e) {
MessageBoxHelper::notify(MessageBoxHelper::Critical, MessageBoxHelper::notify(MessageBoxHelper::Critical,
tr("Failed to empty recycle bin (%1) (%2).") tr("Failed to empty recycle bin (%1) (%2).").arg(rbNodePath, p_e.what()),
.arg(rbNodePath, p_e.what()), VNoteX::getInst().getMainWindow());
VNoteX::getInst().getMainWindow());
} }
updateNode(rbNode); updateNode(rbNode);
@ -935,18 +1077,42 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
case Action::RemoveFromConfig: case Action::RemoveFromConfig:
act = new QAction(tr("&Remove From Index"), p_parent); act = new QAction(tr("&Remove From Index"), p_parent);
connect(act, &QAction::triggered, connect(act, &QAction::triggered,
this, [this]() { this, &NotebookNodeExplorer::removeSelectedNodesFromConfig);
removeSelectedNodesFromConfig();
});
break; break;
case Action::Sort: case Action::Sort:
act = new QAction(generateMenuActionIcon("sort.svg"), tr("&Sort"), p_parent); 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, connect(act, &QAction::triggered,
this, [this]() { this, [this]() {
manualSort(); auto node = currentExploredFolderNode();
if (m_notebook && node) {
// TODO: emit signals to notify other components.
m_notebook->reloadNode(node);
}
updateNode(node);
}); });
break; 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; return act;
@ -954,7 +1120,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
void NotebookNodeExplorer::copySelectedNodes(bool p_move) void NotebookNodeExplorer::copySelectedNodes(bool p_move)
{ {
auto nodes = getSelectedNodes(); auto nodes = getSelectedNodes().first;
if (nodes.isEmpty()) { if (nodes.isEmpty()) {
return; return;
} }
@ -964,6 +1130,10 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move)
ClipboardData cdata(VNoteX::getInst().getInstanceId(), ClipboardData cdata(VNoteX::getInst().getInstanceId(),
p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode); p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode);
for (auto node : nodes) { for (auto node : nodes) {
if (checkInvalidNode(node)) {
continue;
}
auto item = QSharedPointer<NodeClipboardDataItem>::create(node->getNotebook()->getId(), auto item = QSharedPointer<NodeClipboardDataItem>::create(node->getNotebook()->getId(),
node->fetchPath()); node->fetchPath());
cdata.addItem(item); cdata.addItem(item);
@ -976,15 +1146,17 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move)
VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast<int>(nrItems))); VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast<int>(nrItems)));
} }
QVector<Node *> NotebookNodeExplorer::getSelectedNodes() const QPair<QVector<Node *>, QVector<ExternalNode *>> NotebookNodeExplorer::getSelectedNodes() const
{ {
QVector<Node *> nodes; QPair<QVector<Node *>, QVector<ExternalNode *>> nodes;
auto items = m_masterExplorer->selectedItems(); auto items = m_masterExplorer->selectedItems();
for (auto &item : items) { for (auto &item : items) {
auto data = getItemNodeData(item); auto data = getItemNodeData(item);
if (data.isNode()) { 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. // Current node may be a file node.
if (!destNode->isContainer()) { if (!destNode->isContainer()) {
destNode = destNode->getParent(); destNode = destNode->getParent();
} else if (checkInvalidNode(destNode)) {
return;
} }
} }
@ -1088,6 +1262,8 @@ void NotebookNodeExplorer::pasteNodesFromClipboard()
QVector<const Node *> pastedNodes; QVector<const Node *> pastedNodes;
QSet<Node *> nodesNeedUpdate; QSet<Node *> nodesNeedUpdate;
for (auto srcNode : srcNodes) { for (auto srcNode : srcNodes) {
Q_ASSERT(srcNode->exists());
if (isMove) { if (isMove) {
// Notice the view area to close any opened view windows. // Notice the view area to close any opened view windows.
auto event = QSharedPointer<Event>::create(); auto event = QSharedPointer<Event>::create();
@ -1176,7 +1352,7 @@ QVector<Node *> NotebookNodeExplorer::confirmSelectedNodes(const QString &p_titl
const QString &p_text, const QString &p_text,
const QString &p_info) const const QString &p_info) const
{ {
auto nodes = getSelectedNodes(); auto nodes = getSelectedNodes().first;
if (nodes.isEmpty()) { if (nodes.isEmpty()) {
return nodes; return nodes;
} }
@ -1270,7 +1446,7 @@ void NotebookNodeExplorer::removeSelectedNodesFromConfig()
void NotebookNodeExplorer::filterAwayChildrenNodes(QVector<Node *> &p_nodes) void NotebookNodeExplorer::filterAwayChildrenNodes(QVector<Node *> &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. // Check if j is i's ancestor.
for (int j = p_nodes.size() - 1; j >= 0; --j) { for (int j = p_nodes.size() - 1; j >= 0; --j) {
if (i == j) { if (i == j) {
@ -1351,8 +1527,7 @@ void NotebookNodeExplorer::focusNormalNode()
void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes) const void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes) const
{ {
int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); if (m_viewOrder == ViewOrder::OrderedByConfiguration) {
if (viewOrder == ViewOrder::OrderedByConfiguration) {
return; return;
} }
@ -1369,10 +1544,10 @@ void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes) con
} }
// Sort containers. // Sort containers.
sortNodes(p_nodes, 0, firstFileIndex, viewOrder); sortNodes(p_nodes, 0, firstFileIndex, m_viewOrder);
// Sort non-containers. // Sort non-containers.
sortNodes(p_nodes, firstFileIndex, p_nodes.size(), viewOrder); sortNodes(p_nodes, firstFileIndex, p_nodes.size(), m_viewOrder);
} }
void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes, int p_start, int p_end, int p_viewOrder) const void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes, int p_start, int p_end, int p_viewOrder) const
@ -1437,6 +1612,25 @@ void NotebookNodeExplorer::setRecycleBinNodeVisible(bool p_visible)
reload(); 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() void NotebookNodeExplorer::manualSort()
{ {
auto node = getCurrentNode(); auto node = getCurrentNode();
@ -1465,7 +1659,7 @@ void NotebookNodeExplorer::manualSort()
treeWidget->setColumnCount(2); treeWidget->setColumnCount(2);
treeWidget->setHeaderLabels({tr("Name"), tr("Created Time"), tr("Modified Time")}); 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) { for (int i = 0; i < children.size(); ++i) {
const auto &child = children[i]; const auto &child = children[i];
if (m_notebook->isRecycleBinNode(child.data())) { if (m_notebook->isRecycleBinNode(child.data())) {
@ -1498,3 +1692,107 @@ void NotebookNodeExplorer::manualSort()
updateNode(parentNode); 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<FileOpenParameters>::create());
}
}
for (const auto &node : selectedNodes.first) {
if (checkInvalidNode(node)) {
continue;
}
if (node->hasContent()) {
emit nodeActivated(node, QSharedPointer<FileOpenParameters>::create());
}
}
}
QSharedPointer<Node> 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<ExternalNode *> &p_nodes)
{
QSet<Node *> 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;
}

View File

@ -5,6 +5,7 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QHash> #include <QHash>
#include <QScopedPointer> #include <QScopedPointer>
#include <QPair>
#include "qtreewidgetstatecache.h" #include "qtreewidgetstatecache.h"
#include "clipboarddata.h" #include "clipboarddata.h"
@ -22,6 +23,7 @@ namespace vnotex
class TreeWidget; class TreeWidget;
struct FileOpenParameters; struct FileOpenParameters;
class Event; class Event;
class ExternalNode;
class NotebookNodeExplorer : public QWidget class NotebookNodeExplorer : public QWidget
{ {
@ -32,13 +34,13 @@ namespace vnotex
class NodeData class NodeData
{ {
public: public:
enum class NodeType { Node, Attachment, Invalid }; enum class NodeType { Node, ExternalNode, Invalid };
NodeData(); NodeData();
explicit NodeData(Node *p_node, bool p_loaded); explicit NodeData(Node *p_node, bool p_loaded);
explicit NodeData(const QString &p_name); explicit NodeData(const QSharedPointer<ExternalNode> &p_externalNode);
NodeData(const NodeData &p_other); NodeData(const NodeData &p_other);
@ -50,13 +52,13 @@ namespace vnotex
bool isNode() const; bool isNode() const;
bool isAttachment() const; bool isExternalNode() const;
NodeData::NodeType getType() const; NodeData::NodeType getType() const;
Node *getNode() const; Node *getNode() const;
const QString &getName() const; ExternalNode *getExternalNode() const;
void clear(); void clear();
@ -67,11 +69,9 @@ namespace vnotex
private: private:
NodeType m_type = NodeType::Invalid; NodeType m_type = NodeType::Invalid;
union Node *m_node = nullptr;
{
Node *m_node = nullptr; QSharedPointer<ExternalNode> m_externalNode;
QString m_name;
};
bool m_loaded = false; bool m_loaded = false;
}; };
@ -104,10 +104,18 @@ namespace vnotex
void setRecycleBinNodeVisible(bool p_visible); void setRecycleBinNodeVisible(bool p_visible);
void setViewOrder(int p_order);
void setExternalFilesVisible(bool p_visible);
void setAutoImportExternalFiles(bool p_enabled);
Node *currentExploredFolderNode() const;
signals: signals:
void nodeActivated(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras); void nodeActivated(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras);
void fileActivated(const QString &p_path); void fileActivated(const QString &p_path, const QSharedPointer<FileOpenParameters> &p_paras);
// @m_response of @p_event: true to continue the move, false to cancel the move. // @m_response of @p_event: true to continue the move, false to cancel the move.
void nodeAboutToMove(Node *p_node, const QSharedPointer<Event> &p_event); void nodeAboutToMove(Node *p_node, const QSharedPointer<Event> &p_event);
@ -132,7 +140,10 @@ namespace vnotex
Delete, Delete,
DeleteFromRecycleBin, DeleteFromRecycleBin,
RemoveFromConfig, RemoveFromConfig,
Sort Sort,
Reload,
ImportToConfig,
Open
}; };
void setupUI(); void setupUI();
@ -149,13 +160,19 @@ namespace vnotex
void loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; void loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const;
void loadNode(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const;
void loadRecycleBinNode(Node *p_node) const; void loadRecycleBinNode(Node *p_node) const;
void loadRecycleBinNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) 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, Node *p_node, bool p_loaded) const;
QIcon getNodeItemIcon(const Node *p_node) const; void fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const;
const QIcon &getNodeItemIcon(const Node *p_node) const;
const QIcon &getNodeItemIcon(const ExternalNode *p_node) const;
void initNodeIcons() const; void initNodeIcons() const;
@ -177,7 +194,7 @@ namespace vnotex
void createContextMenuOnNode(QMenu *p_menu, const Node *p_node); 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. // Factory function to create action.
QAction *createAction(Action p_act, QObject *p_parent); QAction *createAction(Action p_act, QObject *p_parent);
@ -186,8 +203,7 @@ namespace vnotex
void pasteNodesFromClipboard(); void pasteNodesFromClipboard();
// Only return selected Nodes. QPair<QVector<Node *>, QVector<ExternalNode *>> getSelectedNodes() const;
QVector<Node *> getSelectedNodes() const;
void removeSelectedNodes(bool p_skipRecycleBin); void removeSelectedNodes(bool p_skipRecycleBin);
@ -226,6 +242,16 @@ namespace vnotex
// Sort nodes in config file. // Sort nodes in config file.
void manualSort(); void manualSort();
void openSelectedNodes();
QSharedPointer<Node> importToIndex(const ExternalNode *p_node);
void importToIndex(const QVector<ExternalNode *> &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 NotebookNodeExplorer::NodeData getItemNodeData(const QTreeWidgetItem *p_item);
static void setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data); static void setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data);
@ -242,11 +268,25 @@ namespace vnotex
bool m_recycleBinNodeVisible = false; bool m_recycleBinNodeVisible = false;
int m_viewOrder = ViewOrder::OrderedByConfiguration;
bool m_externalFilesVisible = true;
bool m_autoImportExternalFiles = true;
static QIcon s_folderNodeIcon; static QIcon s_folderNodeIcon;
static QIcon s_fileNodeIcon; static QIcon s_fileNodeIcon;
static QIcon s_invalidFolderNodeIcon;
static QIcon s_invalidFileNodeIcon;
static QIcon s_recycleBinNodeIcon; static QIcon s_recycleBinNodeIcon;
static const QString c_nodeIconForegroundName; static QIcon s_externalFolderNodeIcon;
static QIcon s_externalFileNodeIcon;
}; };
} }

View File

@ -127,6 +127,11 @@ QAction *TitleBar::addMenuAction(const QString &p_iconName, const QString &p_tex
return act; return act;
} }
QMenu *TitleBar::addMenuSubMenu(const QString &p_text)
{
return m_menu->addMenu(p_text);
}
void TitleBar::addMenuSeparator() void TitleBar::addMenuSeparator()
{ {
Q_ASSERT(m_menu); Q_ASSERT(m_menu);

View File

@ -39,6 +39,11 @@ namespace vnotex
template <typename Functor> template <typename Functor>
QAction *addMenuAction(const QString &p_text, const QObject *p_context, Functor p_functor); QAction *addMenuAction(const QString &p_text, const QObject *p_context, Functor p_functor);
template <typename Functor>
QAction *addMenuAction(QMenu *p_subMenu, const QString &p_text, const QObject *p_context, Functor p_functor);
QMenu *addMenuSubMenu(const QString &p_text);
void addMenuSeparator(); void addMenuSeparator();
protected: protected:
@ -91,6 +96,14 @@ namespace vnotex
auto act = m_menu->addAction(p_text, p_context, p_functor); auto act = m_menu->addAction(p_text, p_context, p_functor);
return act; return act;
} }
template <typename Functor>
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 } // ns vnotex
#endif // TITLEBAR_H #endif // TITLEBAR_H