From a8b8e39e249d6bcd7f47a71583abf6df0618f09e Mon Sep 17 00:00:00 2001 From: chendapao Date: Fri, 1 Aug 2025 13:05:37 +0800 Subject: [PATCH 1/5] feature-add-node-visual --- .gitignore | 3 +- src/core/CMakeLists.txt | 1 + src/core/notebook/node.cpp | 72 ++++ src/core/notebook/node.h | 23 ++ src/core/notebook/nodeparameters.h | 71 ++-- src/core/notebook/nodevisual.cpp | 29 ++ src/core/notebook/nodevisual.h | 48 +++ .../notebookconfigmgr/inotebookconfigmgr.h | 3 + src/core/notebookconfigmgr/vxnodeconfig.cpp | 84 +++- src/core/notebookconfigmgr/vxnodeconfig.h | 18 + .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 27 +- .../notebookconfigmgr/vxnotebookconfigmgr.h | 2 + src/widgets/notebooknodeexplorer.cpp | 376 ++++++++++++++++++ src/widgets/notebooknodeexplorer.h | 41 +- 14 files changed, 751 insertions(+), 47 deletions(-) create mode 100644 src/core/notebook/nodevisual.cpp create mode 100644 src/core/notebook/nodevisual.h diff --git a/.gitignore b/.gitignore index aa77ced955..f4675eb6cb 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ build build.* build* .DS_Store -.vscode \ No newline at end of file +.vscode +dev.sh \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a654be4716..7ff7bc7a71 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -44,6 +44,7 @@ target_sources(vnote PRIVATE notebook/inotebookfactory.h notebook/node.cpp notebook/node.h notebook/nodeparameters.cpp notebook/nodeparameters.h + notebook/nodevisual.cpp notebook/nodevisual.h notebook/notebook.cpp notebook/notebook.h notebook/notebookdatabaseaccess.cpp notebook/notebookdatabaseaccess.h notebook/notebookparameters.cpp notebook/notebookparameters.h diff --git a/src/core/notebook/node.cpp b/src/core/notebook/node.cpp index 06de7f1ab4..a9eb8c273c 100644 --- a/src/core/notebook/node.cpp +++ b/src/core/notebook/node.cpp @@ -27,6 +27,7 @@ Node::Node(Flags p_flags, m_modifiedTimeUtc(p_paras.m_modifiedTimeUtc), m_tags(p_paras.m_tags), m_attachmentFolder(p_paras.m_attachmentFolder), + m_visual(p_paras.m_visual), m_parent(p_parent) { Q_ASSERT(m_notebook); @@ -66,6 +67,8 @@ void Node::loadCompleteInfo(const NodeParameters &p_paras, m_modifiedTimeUtc = p_paras.m_modifiedTimeUtc; Q_ASSERT(p_paras.m_tags.isEmpty()); Q_ASSERT(p_paras.m_attachmentFolder.isEmpty()); + + m_visual = p_paras.m_visual; m_children = p_children; m_loaded = true; @@ -493,3 +496,72 @@ void Node::checkSignature() m_signature = generateSignature(); } } + +// 视觉效果相关方法 +const NodeVisual &Node::getVisual() const +{ + return m_visual; +} + +void Node::setVisual(const NodeVisual &p_visual) +{ + m_visual = p_visual; +} + +// 视觉效果便捷访问方法 +const QString &Node::getBackgroundColor() const +{ + return m_visual.getBackgroundColor(); +} + +void Node::setBackgroundColor(const QString &p_backgroundColor) +{ + m_visual.setBackgroundColor(p_backgroundColor); +} + +const QString &Node::getBorderColor() const +{ + return m_visual.getBorderColor(); +} + +void Node::setBorderColor(const QString &p_borderColor) +{ + m_visual.setBorderColor(p_borderColor); +} + +const QString &Node::getNameColor() const +{ + return m_visual.getNameColor(); +} + +void Node::setNameColor(const QString &p_nameColor) +{ + m_visual.setNameColor(p_nameColor); +} + +QString Node::getEffectiveBackgroundColor() const +{ + return getBackgroundColor(); +} + +QString Node::getEffectiveBorderColor() const +{ + return getBorderColor(); +} + +void Node::updateNodeVisual(const NodeVisual &p_visual) +{ + if (m_visual.getBackgroundColor() == p_visual.getBackgroundColor() && + m_visual.getBorderColor() == p_visual.getBorderColor() && + m_visual.getNameColor() == p_visual.getNameColor()) { + return; + } + + m_visual = p_visual; + + // 持久化更新 + getConfigMgr()->updateNodeVisual(this, p_visual); + + // 界面更新 + emit m_notebook->nodeUpdated(this); +} diff --git a/src/core/notebook/node.h b/src/core/notebook/node.h index eb4908168b..4b562eddd7 100644 --- a/src/core/notebook/node.h +++ b/src/core/notebook/node.h @@ -8,6 +8,7 @@ #include #include +#include "nodevisual.h" namespace vnotex { @@ -140,6 +141,26 @@ namespace vnotex QString fetchAttachmentFolderPath(); + // 视觉效果相关方法 + const NodeVisual &getVisual() const; + void setVisual(const NodeVisual &p_visual); + + // 视觉效果便捷访问方法 + const QString &getBackgroundColor() const; + void setBackgroundColor(const QString &p_backgroundColor); + + const QString &getBorderColor() const; + void setBorderColor(const QString &p_borderColor); + + const QString &getNameColor() const; + void setNameColor(const QString &p_nameColor); + + // 获取有效颜色(直接返回设置的颜色) + QString getEffectiveBackgroundColor() const; + QString getEffectiveBorderColor() const; + + void updateNodeVisual(const NodeVisual &p_visual); + virtual QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) = 0; virtual QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) = 0; @@ -204,6 +225,8 @@ namespace vnotex QString m_attachmentFolder; + NodeVisual m_visual; + Node *m_parent = nullptr; QVector> m_children; diff --git a/src/core/notebook/nodeparameters.h b/src/core/notebook/nodeparameters.h index b36d597097..c7010aed67 100644 --- a/src/core/notebook/nodeparameters.h +++ b/src/core/notebook/nodeparameters.h @@ -1,34 +1,37 @@ -#ifndef NODEPARAMETERS_H -#define NODEPARAMETERS_H - -#include -#include - -#include - -#include "node.h" - -namespace vnotex -{ - class NodeParameters - { - public: - NodeParameters() = default; - - NodeParameters(ID p_id); - - ID m_id = Node::InvalidId; - - ID m_signature = Node::InvalidId; - - QDateTime m_createdTimeUtc = QDateTime::currentDateTimeUtc(); - - QDateTime m_modifiedTimeUtc = QDateTime::currentDateTimeUtc(); - - QStringList m_tags; - - QString m_attachmentFolder; - }; -} - -#endif // NODEPARAMETERS_H +#ifndef NODEPARAMETERS_H +#define NODEPARAMETERS_H + +#include +#include + +#include + +#include "node.h" +#include "nodevisual.h" + +namespace vnotex +{ + class NodeParameters + { + public: + NodeParameters() = default; + + NodeParameters(ID p_id); + + ID m_id = Node::InvalidId; + + ID m_signature = Node::InvalidId; + + QDateTime m_createdTimeUtc = QDateTime::currentDateTimeUtc(); + + QDateTime m_modifiedTimeUtc = QDateTime::currentDateTimeUtc(); + + QStringList m_tags; + + QString m_attachmentFolder; + + NodeVisual m_visual; + }; +} + +#endif // NODEPARAMETERS_H diff --git a/src/core/notebook/nodevisual.cpp b/src/core/notebook/nodevisual.cpp new file mode 100644 index 0000000000..87cf05d583 --- /dev/null +++ b/src/core/notebook/nodevisual.cpp @@ -0,0 +1,29 @@ +#include "nodevisual.h" + +#include +#include + +using namespace vnotex; + +NodeVisual::NodeVisual(const QString &p_backgroundColor, + const QString &p_borderColor, + const QString &p_nameColor) + : m_backgroundColor(p_backgroundColor) + , m_borderColor(p_borderColor) + , m_nameColor(p_nameColor) +{ +} + +bool NodeVisual::hasAnyVisualEffect() const +{ + return !m_backgroundColor.isEmpty() || + !m_borderColor.isEmpty() || + !m_nameColor.isEmpty(); +} + +void NodeVisual::clearAllColors() +{ + m_backgroundColor.clear(); + m_borderColor.clear(); + m_nameColor.clear(); +} \ No newline at end of file diff --git a/src/core/notebook/nodevisual.h b/src/core/notebook/nodevisual.h new file mode 100644 index 0000000000..0d5d6f0475 --- /dev/null +++ b/src/core/notebook/nodevisual.h @@ -0,0 +1,48 @@ +#ifndef NODEVISUAL_H +#define NODEVISUAL_H + +#include + +/* + * 节点视觉效果类 + * 自定义节点名称,背景颜色,边框颜色,节点名称颜色 + * 支持清除所有颜色,包括背景颜色,边框颜色,节点名称颜色 + * 支持级联修改,包括背景颜色,边框颜色,节点名称颜色 +*/ +namespace vnotex +{ + class NodeVisual + { + public: + NodeVisual() = default; + + NodeVisual(const QString &p_backgroundColor, + const QString &p_borderColor, + const QString &p_nameColor); + + // 背景颜色 + const QString &getBackgroundColor() const { return m_backgroundColor; } + void setBackgroundColor(const QString &p_color) { m_backgroundColor = p_color; } + + // 边框颜色 + const QString &getBorderColor() const { return m_borderColor; } + void setBorderColor(const QString &p_color) { m_borderColor = p_color; } + + // 节点名称颜色 + const QString &getNameColor() const { return m_nameColor; } + void setNameColor(const QString &p_color) { m_nameColor = p_color; } + + // 判断是否有任何视觉效果 + bool hasAnyVisualEffect() const; + + // 清除所有颜色 + void clearAllColors(); + + private: + QString m_backgroundColor; // 背景颜色 + QString m_borderColor; // 边框颜色 + QString m_nameColor; // 节点名称颜色 + }; +} + +#endif // NODEVISUAL_H \ No newline at end of file diff --git a/src/core/notebookconfigmgr/inotebookconfigmgr.h b/src/core/notebookconfigmgr/inotebookconfigmgr.h index c230432862..9588bd40b3 100644 --- a/src/core/notebookconfigmgr/inotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/inotebookconfigmgr.h @@ -5,6 +5,7 @@ #include #include "notebook/node.h" +#include "notebook/nodevisual.h" namespace vnotex { @@ -84,6 +85,8 @@ namespace vnotex virtual QStringList scanAndImportExternalFiles(Node *p_node) = 0; + virtual void updateNodeVisual(Node *p_node, const NodeVisual &p_visual) = 0; + // Version of the config processing code. virtual int getCodeVersion() const = 0; diff --git a/src/core/notebookconfigmgr/vxnodeconfig.cpp b/src/core/notebookconfigmgr/vxnodeconfig.cpp index c5f148dd26..9056d6cd8d 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.cpp +++ b/src/core/notebookconfigmgr/vxnodeconfig.cpp @@ -27,6 +27,12 @@ const QString NodeConfig::c_attachmentFolder = "attachment_folder"; const QString NodeConfig::c_tags = "tags"; +const QString NodeConfig::c_backgroundColor = "background_color"; + +const QString NodeConfig::c_borderColor = "border_color"; + +const QString NodeConfig::c_nameColor = "name_color"; + static ID stringToNodeId(const QString &p_idStr) { auto ret = stringToID(p_idStr); @@ -48,6 +54,17 @@ QJsonObject NodeFileConfig::toJson() const jobj[NodeConfig::c_attachmentFolder] = m_attachmentFolder; jobj[NodeConfig::c_tags] = QJsonArray::fromStringList(m_tags); + // Visual settings + if (!m_backgroundColor.isEmpty()) { + jobj[NodeConfig::c_backgroundColor] = m_backgroundColor; + } + if (!m_borderColor.isEmpty()) { + jobj[NodeConfig::c_borderColor] = m_borderColor; + } + if (!m_nameColor.isEmpty()) { + jobj[NodeConfig::c_nameColor] = m_nameColor; + } + return jobj; } @@ -69,6 +86,17 @@ void NodeFileConfig::fromJson(const QJsonObject &p_jobj) m_tags << arr[i].toString(); } } + + // Visual settings (check if fields exist for backward compatibility) + if (p_jobj.contains(NodeConfig::c_backgroundColor)) { + m_backgroundColor = p_jobj[NodeConfig::c_backgroundColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_borderColor)) { + m_borderColor = p_jobj[NodeConfig::c_borderColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_nameColor)) { + m_nameColor = p_jobj[NodeConfig::c_nameColor].toString(); + } } NodeParameters NodeFileConfig::toNodeParameters() const @@ -80,6 +108,12 @@ NodeParameters NodeFileConfig::toNodeParameters() const paras.m_modifiedTimeUtc = m_modifiedTimeUtc; paras.m_tags = m_tags; paras.m_attachmentFolder = m_attachmentFolder; + + // Visual settings + paras.m_visual.setBackgroundColor(m_backgroundColor); + paras.m_visual.setBorderColor(m_borderColor); + paras.m_visual.setNameColor(m_nameColor); + return paras; } @@ -89,12 +123,46 @@ QJsonObject NodeFolderConfig::toJson() const jobj[NodeConfig::c_name] = m_name; + // Visual settings + if (!m_backgroundColor.isEmpty()) { + jobj[NodeConfig::c_backgroundColor] = m_backgroundColor; + } + if (!m_borderColor.isEmpty()) { + jobj[NodeConfig::c_borderColor] = m_borderColor; + } + if (!m_nameColor.isEmpty()) { + jobj[NodeConfig::c_nameColor] = m_nameColor; + } + return jobj; } void NodeFolderConfig::fromJson(const QJsonObject &p_jobj) { m_name = p_jobj[NodeConfig::c_name].toString(); + + // Visual settings (check if fields exist for backward compatibility) + if (p_jobj.contains(NodeConfig::c_backgroundColor)) { + m_backgroundColor = p_jobj[NodeConfig::c_backgroundColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_borderColor)) { + m_borderColor = p_jobj[NodeConfig::c_borderColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_nameColor)) { + m_nameColor = p_jobj[NodeConfig::c_nameColor].toString(); + } +} + +NodeParameters NodeFolderConfig::toNodeParameters() const +{ + NodeParameters paras; + + // Visual settings + paras.m_visual.setBackgroundColor(m_backgroundColor); + paras.m_visual.setBorderColor(m_borderColor); + paras.m_visual.setNameColor(m_nameColor); + + return paras; } NodeConfig::NodeConfig() @@ -149,16 +217,16 @@ void NodeConfig::fromJson(const QJsonObject &p_jobj) m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); - auto filesJson = p_jobj[NodeConfig::c_files].toArray(); - m_files.resize(filesJson.size()); - for (int i = 0; i < filesJson.size(); ++i) { - m_files[i].fromJson(filesJson[i].toObject()); + auto filesJson = p_jobj[NodeConfig::c_files].toArray(); + m_files.resize(filesJson.size()); + for (int i = 0; i < filesJson.size(); ++i) { + m_files[i].fromJson(filesJson[i].toObject()); } - auto foldersJson = p_jobj[NodeConfig::c_folders].toArray(); - m_folders.resize(foldersJson.size()); - for (int i = 0; i < foldersJson.size(); ++i) { - m_folders[i].fromJson(foldersJson[i].toObject()); + auto foldersJson = p_jobj[NodeConfig::c_folders].toArray(); + m_folders.resize(foldersJson.size()); + for (int i = 0; i < foldersJson.size(); ++i) { + m_folders[i].fromJson(foldersJson[i].toObject()); } } diff --git a/src/core/notebookconfigmgr/vxnodeconfig.h b/src/core/notebookconfigmgr/vxnodeconfig.h index 24066b4069..af74992ad9 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.h +++ b/src/core/notebookconfigmgr/vxnodeconfig.h @@ -36,6 +36,11 @@ namespace vnotex QString m_attachmentFolder; QStringList m_tags; + + // Visual settings + QString m_backgroundColor; + QString m_borderColor; + QString m_nameColor; }; @@ -46,7 +51,14 @@ namespace vnotex void fromJson(const QJsonObject &p_jobj); + NodeParameters toNodeParameters() const; + QString m_name; + + // Visual settings + QString m_backgroundColor; + QString m_borderColor; + QString m_nameColor; }; @@ -100,6 +112,12 @@ namespace vnotex static const QString c_attachmentFolder; static const QString c_tags; + + static const QString c_backgroundColor; + + static const QString c_borderColor; + + static const QString c_nameColor; }; } } diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index f0e8841655..0fbc0c6940 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -187,7 +187,6 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi } if (seenNames.contains(folder.m_name)) { - qWarning() << "skipped loading node with duplicated name under" << p_node->fetchPath(); continue; } seenNames.insert(folder.m_name); @@ -208,7 +207,6 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi } if (seenNames.contains(file.m_name)) { - qWarning() << "skipped loading node with duplicated name under" << p_node->fetchPath(); continue; } seenNames.insert(file.m_name); @@ -375,7 +373,7 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_n p_node->getSignature(), p_node->getCreatedTimeUtc(), p_node->getModifiedTimeUtc()); - + for (const auto &child : p_node->getChildrenRef()) { if (child->hasContent()) { NodeFileConfig fileConfig; @@ -386,12 +384,22 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_n fileConfig.m_modifiedTimeUtc = child->getModifiedTimeUtc(); fileConfig.m_attachmentFolder = child->getAttachmentFolder(); fileConfig.m_tags = child->getTags(); + + // Visual settings + fileConfig.m_backgroundColor = child->getBackgroundColor(); + fileConfig.m_borderColor = child->getBorderColor(); + fileConfig.m_nameColor = child->getNameColor(); config->m_files.push_back(fileConfig); } else { Q_ASSERT(child->isContainer()); NodeFolderConfig folderConfig; folderConfig.m_name = child->getName(); + + // Visual settings + folderConfig.m_backgroundColor = child->getBackgroundColor(); + folderConfig.m_borderColor = child->getBorderColor(); + folderConfig.m_nameColor = child->getNameColor(); config->m_folders.push_back(folderConfig); } @@ -1112,3 +1120,16 @@ bool VXNotebookConfigMgr::sameNotebook(const Node *p_node) const { return p_node ? p_node->getNotebook() == getNotebook() : true; } + +void VXNotebookConfigMgr::updateNodeVisual(Node *p_node, const NodeVisual &p_visual) +{ + Q_ASSERT(sameNotebook(p_node)); + + p_node->setVisual(p_visual); + + if (p_node->isRoot()) { + return; + } + + saveNode(p_node); +} diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h index 787ecaf0dc..ee4578db1a 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h @@ -83,6 +83,8 @@ namespace vnotex QStringList scanAndImportExternalFiles(Node *p_node) Q_DECL_OVERRIDE; + void updateNodeVisual(Node *p_node, const NodeVisual &p_visual) Q_DECL_OVERRIDE; + private: void createEmptyRootNode(); diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index 64da0b26d3..76d80b9299 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -6,11 +6,17 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include #include +#include #include #include "messageboxhelper.h" #include "vnotex.h" @@ -44,6 +50,39 @@ using namespace vnotex; QIcon NotebookNodeExplorer::s_nodeIcons[NodeIcon::MaxIcons]; +// 节点视觉委托类 避免修改颜色后整个树刷新 +NodeColorDelegate::NodeColorDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +void NodeColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + // 先绘制默认内容 + QStyledItemDelegate::paint(painter, option, index); + + // 获取节点数据 + auto item = static_cast(index.internalPointer()); + if (!item) { + return; + } + + // 从 item 的 UserRole 中获取颜色信息 + QVariant borderColorVar = item->data(0, Qt::UserRole + 1000); // 使用特殊的角色存储边框颜色 + if (borderColorVar.isValid() && !borderColorVar.toString().isEmpty()) { + QString borderColor = borderColorVar.toString(); + QColor color(borderColor); + + if (color.isValid()) { + painter->save(); + QPen pen(color, 2, Qt::SolidLine); + painter->setPen(pen); + painter->drawRect(option.rect.adjusted(1, 1, -1, -1)); + painter->restore(); + } + } +} + NotebookNodeExplorer::NodeData::NodeData() { } @@ -284,6 +323,9 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent) auto data = getItemNodeData(p_item); activateItemNode(data); }); + + // 设置自定义委托以支持边框绘制 + m_masterExplorer->setItemDelegate(new NodeColorDelegate(this)); } void NotebookNodeExplorer::activateItemNode(const NodeData &p_data) @@ -542,6 +584,7 @@ void NotebookNodeExplorer::fillMasterItem(QTreeWidgetItem *p_item, Node *p_node, p_item->setText(Column::Name, p_node->getName()); p_item->setIcon(Column::Name, getIcon(p_node)); p_item->setToolTip(Column::Name, generateToolTip(p_node)); + applyNodeColors(p_item, p_node); } void NotebookNodeExplorer::fillMasterItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const @@ -558,6 +601,7 @@ void NotebookNodeExplorer::fillSlaveItem(QListWidgetItem *p_item, Node *p_node) p_item->setText(p_node->getName()); p_item->setIcon(getIcon(p_node)); p_item->setToolTip(generateToolTip(p_node)); + applyNodeColors(p_item, p_node); } void NotebookNodeExplorer::fillSlaveItem(QListWidgetItem *p_item, const QSharedPointer &p_node) const @@ -568,6 +612,181 @@ void NotebookNodeExplorer::fillSlaveItem(QListWidgetItem *p_item, const QSharedP p_item->setToolTip(tr("[External] %1").arg(p_node->getName())); } +void NotebookNodeExplorer::applyNodeColors(QTreeWidgetItem *p_item, Node *p_node) const +{ + if (!p_item || !p_node) { + return; + } + + QString backgroundColor = p_node->getEffectiveBackgroundColor(); + QString borderColor = p_node->getEffectiveBorderColor(); + QString nameColor = p_node->getNameColor(); + + // 设置背景色 + if (!backgroundColor.isEmpty()) { + p_item->setBackground(Column::Name, QBrush(QColor(backgroundColor))); + } else { + p_item->setBackground(Column::Name, QBrush()); + } + + // 设置节点名称颜色 + if (!nameColor.isEmpty()) { + p_item->setForeground(Column::Name, QBrush(QColor(nameColor))); + } else { + p_item->setForeground(Column::Name, QBrush()); + } + + // 设置边框色(通过 UserRole 存储,由自定义委托绘制) + if (!borderColor.isEmpty()) { + p_item->setData(Column::Name, Qt::UserRole + 1000, borderColor); + } else { + p_item->setData(Column::Name, Qt::UserRole + 1000, QVariant()); + } +} + +void NotebookNodeExplorer::applyNodeColors(QListWidgetItem *p_item, Node *p_node) const +{ + if (!p_item || !p_node) { + return; + } + + QString backgroundColor = p_node->getEffectiveBackgroundColor(); + QString nameColor = p_node->getNameColor(); + + // 设置背景色 + if (!backgroundColor.isEmpty()) { + p_item->setBackground(QBrush(QColor(backgroundColor))); + } else { + p_item->setBackground(QBrush()); + } + + // 设置节点名称颜色 + if (!nameColor.isEmpty()) { + p_item->setForeground(QBrush(QColor(nameColor))); + } else { + p_item->setForeground(QBrush()); + } + + // 对于 ListWidget,边框效果暂时不实现 + // TODO: 如果需要,也可以为 ListWidget 创建自定义委托 +} + +void NotebookNodeExplorer::updateCurrentNodeVisualDirectly(Node *p_node, const NodeVisual &p_visual) +{ + if (!p_node) { + return; + } + + // 直接更新 Node 对象的视觉信息,但不发送刷新信号 + p_node->setVisual(p_visual); + + // 更新数据库 + p_node->getConfigMgr()->updateNodeVisual(p_node, p_visual); + + // 直接更新当前显示的 item,避免整个树刷新 + auto treeItem = findCurrentTreeWidgetItem(p_node); + if (treeItem) { + applyNodeColors(treeItem, p_node); + } + + auto listItem = findCurrentListWidgetItem(p_node); + if (listItem) { + applyNodeColors(listItem, p_node); + } +} + +void NotebookNodeExplorer::setCascadeColorRecursively(Node *p_node, const QString &p_backgroundColor, const QString &p_borderColor, const QString &p_nameColor) +{ + if (!p_node) { + return; + } + + // 设置当前节点的颜色 + NodeVisual visual = p_node->getVisual(); + + if (!p_backgroundColor.isEmpty()) { + visual.setBackgroundColor(p_backgroundColor); + } + if (!p_borderColor.isEmpty()) { + visual.setBorderColor(p_borderColor); + } + if (!p_nameColor.isEmpty()) { + visual.setNameColor(p_nameColor); + } + + updateCurrentNodeVisualDirectly(p_node, visual); + + // 如果是容器节点,递归设置所有子节点 + if (p_node->isContainer() && p_node->isLoaded()) { + for (const auto &child : p_node->getChildrenRef()) { + setCascadeColorRecursively(child.data(), p_backgroundColor, p_borderColor, p_nameColor); + } + } +} + +QTreeWidgetItem *NotebookNodeExplorer::findCurrentTreeWidgetItem(Node *p_node) const +{ + if (!p_node || !m_masterExplorer) { + return nullptr; + } + + // 遍历所有可见的 TreeWidgetItem 查找匹配的节点 + std::function findInTree = [&](QTreeWidgetItem *parent) -> QTreeWidgetItem* { + if (!parent) { + // 搜索顶级项目 + for (int i = 0; i < m_masterExplorer->topLevelItemCount(); ++i) { + auto item = m_masterExplorer->topLevelItem(i); + auto data = getItemNodeData(item); + if (data.isNode() && data.getNode() == p_node) { + return item; + } + + // 递归搜索子项目 + auto result = findInTree(item); + if (result) { + return result; + } + } + } else { + // 搜索子项目 + for (int i = 0; i < parent->childCount(); ++i) { + auto item = parent->child(i); + auto data = getItemNodeData(item); + if (data.isNode() && data.getNode() == p_node) { + return item; + } + + // 递归搜索子项目 + auto result = findInTree(item); + if (result) { + return result; + } + } + } + return nullptr; + }; + + return findInTree(nullptr); +} + +QListWidgetItem *NotebookNodeExplorer::findCurrentListWidgetItem(Node *p_node) const +{ + if (!p_node || !m_slaveExplorer) { + return nullptr; + } + + // 遍历所有 ListWidgetItem 查找匹配的节点 + for (int i = 0; i < m_slaveExplorer->count(); ++i) { + auto item = m_slaveExplorer->item(i); + auto data = getItemNodeData(item); + if (data.isNode() && data.getNode() == p_node) { + return item; + } + } + + return nullptr; +} + const QIcon &NotebookNodeExplorer::getIcon(const Node *p_node) const { if (p_node->hasContent()) { @@ -1011,6 +1230,25 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ createAndAddAction(Action::OpenLocation, p_menu, p_master); + // 添加视觉设置子菜单 + auto visualMenu = WidgetsFactory::createMenu(tr("Visual Settings"), p_menu); + createAndAddAction(Action::SetBackgroundColor, visualMenu, p_master); + createAndAddAction(Action::SetBorderColor, visualMenu, p_master); + createAndAddAction(Action::SetNameColor, visualMenu, p_master); + + // 只有文件夹节点才能设置级联色 + if (p_node->isContainer()) { + visualMenu->addSeparator(); + auto cascadeMenu = WidgetsFactory::createMenu(tr("Cascade Color Settings"), visualMenu); + createAndAddAction(Action::SetCascadeBackgroundColor, cascadeMenu, p_master); + createAndAddAction(Action::SetCascadeBorderColor, cascadeMenu, p_master); + visualMenu->addMenu(cascadeMenu); + } + + visualMenu->addSeparator(); + createAndAddAction(Action::ClearColors, visualMenu, p_master); + p_menu->addMenu(visualMenu); + createAndAddAction(Action::Properties, p_menu, p_master); } } @@ -1375,6 +1613,144 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent, boo }); break; + case Action::SetBackgroundColor: + act = new QAction(tr("Set Background Color"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + QColor defaultColor = node->getBackgroundColor().isEmpty() ? QColor(Qt::white) : QColor(node->getBackgroundColor()); + auto color = QColorDialog::getColor(defaultColor, this, tr("Select Background Color")); + if (color.isValid()) { + NodeVisual visual = node->getVisual(); + visual.setBackgroundColor(color.name()); + updateCurrentNodeVisualDirectly(node, visual); + } + }); + break; + + case Action::SetBorderColor: + act = new QAction(tr("Set Border Color"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + QColor defaultColor = node->getBorderColor().isEmpty() ? QColor(Qt::black) : QColor(node->getBorderColor()); + auto color = QColorDialog::getColor(defaultColor, this, tr("Select Border Color")); + if (color.isValid()) { + NodeVisual visual = node->getVisual(); + visual.setBorderColor(color.name()); + updateCurrentNodeVisualDirectly(node, visual); + } + }); + break; + + case Action::SetNameColor: + act = new QAction(tr("Set Name Color"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + QColor defaultColor = node->getNameColor().isEmpty() ? QColor(Qt::black) : QColor(node->getNameColor()); + auto color = QColorDialog::getColor(defaultColor, this, tr("Select Name Color")); + if (color.isValid()) { + NodeVisual visual = node->getVisual(); + visual.setNameColor(color.name()); + updateCurrentNodeVisualDirectly(node, visual); + } + }); + break; + + case Action::SetCascadeBackgroundColor: + act = new QAction(tr("Set Cascade Background Color"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + if (!node->isContainer()) { + return; // 只有文件夹才能设置级联色 + } + + QColor defaultColor = node->getBackgroundColor().isEmpty() ? QColor("#e6f3ff") : QColor(node->getBackgroundColor()); + auto color = QColorDialog::getColor(defaultColor, this, tr("Select Cascade Background Color")); + if (color.isValid()) { + setCascadeColorRecursively(node, color.name(), QString(), QString()); + } + }); + break; + + case Action::SetCascadeBorderColor: + act = new QAction(tr("Set Cascade Border Color"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + if (!node->isContainer()) { + return; // 只有文件夹才能设置级联色 + } + + QColor defaultColor = node->getBorderColor().isEmpty() ? QColor("#3A75F2") : QColor(node->getBorderColor()); + auto color = QColorDialog::getColor(defaultColor, this, tr("Select Cascade Border Color")); + if (color.isValid()) { + setCascadeColorRecursively(node, QString(), color.name(), QString()); + } + }); + break; + + case Action::ClearColors: + act = new QAction(tr("Clear Colors"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + NodeVisual visual; + visual.clearAllColors(); + updateCurrentNodeVisualDirectly(node, visual); + }); + break; + default: Q_ASSERT(false); break; diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index 88fdcdbe8a..e6f0d33f52 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -6,14 +6,19 @@ #include #include #include +#include #include "qtreewidgetstatecache.h" #include "clipboarddata.h" #include "navigationmodewrapper.h" #include "global.h" +#include class QSplitter; class QMenu; +class QPainter; +class QStyleOptionViewItem; +class QModelIndex; namespace vnotex { @@ -25,6 +30,21 @@ namespace vnotex class Event; class ExternalNode; + /* + * 自定义委托类,用于绘制节点边框 + * 支持节点名称,背景颜色,边框颜色,节点名称颜色 + * 支持级联修改,包括背景颜色,边框颜色,节点名称颜色 + */ + class NodeColorDelegate : public QStyledItemDelegate + { + Q_OBJECT + public: + explicit NodeColorDelegate(QObject *parent = nullptr); + + protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + }; + class NotebookNodeExplorer : public QWidget { Q_OBJECT @@ -153,7 +173,14 @@ namespace vnotex Read, ExpandAll, PinToQuickAccess, - Tag + Tag, + VisualSettings, + SetBackgroundColor, + SetBorderColor, + SetNameColor, + SetCascadeBackgroundColor, + SetCascadeBorderColor, + ClearColors }; struct CacheData @@ -195,6 +222,18 @@ namespace vnotex void fillSlaveItem(QListWidgetItem *p_item, const QSharedPointer &p_node) const; + void applyNodeColors(QTreeWidgetItem *p_item, Node *p_node) const; + + void applyNodeColors(QListWidgetItem *p_item, Node *p_node) const; + + void updateCurrentNodeVisualDirectly(Node *p_node, const NodeVisual &p_visual); + + void setCascadeColorRecursively(Node *p_node, const QString &p_backgroundColor, const QString &p_borderColor, const QString &p_nameColor); + + QTreeWidgetItem *findCurrentTreeWidgetItem(Node *p_node) const; + + QListWidgetItem *findCurrentListWidgetItem(Node *p_node) const; + const QIcon &getIcon(const Node *p_node) const; const QIcon &getIcon(const ExternalNode *p_node) const; From 59fc35c06e3d12c1f62cb3041deeff94fe155f34 Mon Sep 17 00:00:00 2001 From: chendapao Date: Fri, 1 Aug 2025 18:26:37 +0800 Subject: [PATCH 2/5] adj --- src/core/notebook/nodevisual.h | 2 +- src/core/notebookconfigmgr/vxnodeconfig.cpp | 16 ++++++++-------- .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 4 +++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/core/notebook/nodevisual.h b/src/core/notebook/nodevisual.h index 0d5d6f0475..055d8dbc3e 100644 --- a/src/core/notebook/nodevisual.h +++ b/src/core/notebook/nodevisual.h @@ -45,4 +45,4 @@ namespace vnotex }; } -#endif // NODEVISUAL_H \ No newline at end of file +#endif // NODEVISUAL_H diff --git a/src/core/notebookconfigmgr/vxnodeconfig.cpp b/src/core/notebookconfigmgr/vxnodeconfig.cpp index 9056d6cd8d..add5b7a0a1 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.cpp +++ b/src/core/notebookconfigmgr/vxnodeconfig.cpp @@ -217,16 +217,16 @@ void NodeConfig::fromJson(const QJsonObject &p_jobj) m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); - auto filesJson = p_jobj[NodeConfig::c_files].toArray(); - m_files.resize(filesJson.size()); - for (int i = 0; i < filesJson.size(); ++i) { - m_files[i].fromJson(filesJson[i].toObject()); + auto filesJson = p_jobj[NodeConfig::c_files].toArray(); + m_files.resize(filesJson.size()); + for (int i = 0; i < filesJson.size(); ++i) { + m_files[i].fromJson(filesJson[i].toObject()); } - auto foldersJson = p_jobj[NodeConfig::c_folders].toArray(); - m_folders.resize(foldersJson.size()); - for (int i = 0; i < foldersJson.size(); ++i) { - m_folders[i].fromJson(foldersJson[i].toObject()); + auto foldersJson = p_jobj[NodeConfig::c_folders].toArray(); + m_folders.resize(foldersJson.size()); + for (int i = 0; i < foldersJson.size(); ++i) { + m_folders[i].fromJson(foldersJson[i].toObject()); } } diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index 0fbc0c6940..545e14f81c 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -187,6 +187,7 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi } if (seenNames.contains(folder.m_name)) { + qWarning() << "skipped loading node with duplicated name under" << p_node->fetchPath(); continue; } seenNames.insert(folder.m_name); @@ -207,6 +208,7 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi } if (seenNames.contains(file.m_name)) { + qWarning() << "skipped loading node with duplicated name under" << p_node->fetchPath(); continue; } seenNames.insert(file.m_name); @@ -373,7 +375,7 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_n p_node->getSignature(), p_node->getCreatedTimeUtc(), p_node->getModifiedTimeUtc()); - + for (const auto &child : p_node->getChildrenRef()) { if (child->hasContent()) { NodeFileConfig fileConfig; From 11fb841639be704f64707a30081060705aca0b1e Mon Sep 17 00:00:00 2001 From: chendapao Date: Fri, 1 Aug 2025 18:31:29 +0800 Subject: [PATCH 3/5] adj --- .gitignore | 2 +- src/core/notebook/nodevisual.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f4675eb6cb..a0053ef13a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ build.* build* .DS_Store .vscode -dev.sh \ No newline at end of file +dev.sh diff --git a/src/core/notebook/nodevisual.cpp b/src/core/notebook/nodevisual.cpp index 87cf05d583..4841ce031c 100644 --- a/src/core/notebook/nodevisual.cpp +++ b/src/core/notebook/nodevisual.cpp @@ -26,4 +26,4 @@ void NodeVisual::clearAllColors() m_backgroundColor.clear(); m_borderColor.clear(); m_nameColor.clear(); -} \ No newline at end of file +} From b51f38743047c2e72f6fc288465a4a1c0cf27cf9 Mon Sep 17 00:00:00 2001 From: chendapao Date: Sat, 2 Aug 2025 00:00:50 +0800 Subject: [PATCH 4/5] adj --- src/core/notebookconfigmgr/vxnodeconfig.cpp | 34 +++++++++++++++++-- src/core/notebookconfigmgr/vxnodeconfig.h | 5 +++ .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 7 ++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/core/notebookconfigmgr/vxnodeconfig.cpp b/src/core/notebookconfigmgr/vxnodeconfig.cpp index add5b7a0a1..f6b4e0b320 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.cpp +++ b/src/core/notebookconfigmgr/vxnodeconfig.cpp @@ -75,10 +75,10 @@ void NodeFileConfig::fromJson(const QJsonObject &p_jobj) m_id = stringToNodeId(p_jobj[NodeConfig::c_id].toString()); m_signature = stringToNodeId(p_jobj[NodeConfig::c_signature].toString()); - m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); - m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); + m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); + m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); - m_attachmentFolder = p_jobj[NodeConfig::c_attachmentFolder].toString(); + m_attachmentFolder = p_jobj[NodeConfig::c_attachmentFolder].toString(); { auto arr = p_jobj[NodeConfig::c_tags].toArray(); @@ -204,6 +204,17 @@ QJsonObject NodeConfig::toJson() const } jobj[NodeConfig::c_folders] = folders; + // Visual settings for the container node itself + if (!m_backgroundColor.isEmpty()) { + jobj[NodeConfig::c_backgroundColor] = m_backgroundColor; + } + if (!m_borderColor.isEmpty()) { + jobj[NodeConfig::c_borderColor] = m_borderColor; + } + if (!m_nameColor.isEmpty()) { + jobj[NodeConfig::c_nameColor] = m_nameColor; + } + return jobj; } @@ -228,6 +239,17 @@ void NodeConfig::fromJson(const QJsonObject &p_jobj) for (int i = 0; i < foldersJson.size(); ++i) { m_folders[i].fromJson(foldersJson[i].toObject()); } + + // Visual settings for the container node itself (check if fields exist for backward compatibility) + if (p_jobj.contains(NodeConfig::c_backgroundColor)) { + m_backgroundColor = p_jobj[NodeConfig::c_backgroundColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_borderColor)) { + m_borderColor = p_jobj[NodeConfig::c_borderColor].toString(); + } + if (p_jobj.contains(NodeConfig::c_nameColor)) { + m_nameColor = p_jobj[NodeConfig::c_nameColor].toString(); + } } NodeParameters NodeConfig::toNodeParameters() const @@ -237,5 +259,11 @@ NodeParameters NodeConfig::toNodeParameters() const paras.m_signature = m_signature; paras.m_createdTimeUtc = m_createdTimeUtc; paras.m_modifiedTimeUtc = m_modifiedTimeUtc; + + // Visual settings for the container node itself + paras.m_visual.setBackgroundColor(m_backgroundColor); + paras.m_visual.setBorderColor(m_borderColor); + paras.m_visual.setNameColor(m_nameColor); + return paras; } diff --git a/src/core/notebookconfigmgr/vxnodeconfig.h b/src/core/notebookconfigmgr/vxnodeconfig.h index af74992ad9..8df5504b6d 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.h +++ b/src/core/notebookconfigmgr/vxnodeconfig.h @@ -93,6 +93,11 @@ namespace vnotex QVector m_folders; + // Visual settings for the container node itself + QString m_backgroundColor; + QString m_borderColor; + QString m_nameColor; + static const QString c_version; static const QString c_id; diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index 545e14f81c..01f1e3ee37 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -193,6 +194,7 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi seenNames.insert(folder.m_name); auto folderNode = QSharedPointer::create(folder.m_name, + folder.toNodeParameters(), getNotebook(), p_node); inheritNodeFlags(p_node, folderNode.data()); @@ -376,6 +378,11 @@ QSharedPointer VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_n p_node->getCreatedTimeUtc(), p_node->getModifiedTimeUtc()); + // Set visual settings for the container node itself + config->m_backgroundColor = p_node->getBackgroundColor(); + config->m_borderColor = p_node->getBorderColor(); + config->m_nameColor = p_node->getNameColor(); + for (const auto &child : p_node->getChildrenRef()) { if (child->hasContent()) { NodeFileConfig fileConfig; From 236b03c4cbb7093d13d3c65d8493a4d3329ef2f6 Mon Sep 17 00:00:00 2001 From: chendapao Date: Mon, 4 Aug 2025 20:32:11 +0800 Subject: [PATCH 5/5] fix dir index bug --- src/core/notebookconfigmgr/vxnodeconfig.cpp | 6 ++-- .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 5 ++- src/widgets/notebooknodeexplorer.cpp | 31 +++++++++++++------ src/widgets/notebooknodeexplorer.h | 1 + 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/core/notebookconfigmgr/vxnodeconfig.cpp b/src/core/notebookconfigmgr/vxnodeconfig.cpp index f6b4e0b320..1782d62b55 100644 --- a/src/core/notebookconfigmgr/vxnodeconfig.cpp +++ b/src/core/notebookconfigmgr/vxnodeconfig.cpp @@ -75,10 +75,10 @@ void NodeFileConfig::fromJson(const QJsonObject &p_jobj) m_id = stringToNodeId(p_jobj[NodeConfig::c_id].toString()); m_signature = stringToNodeId(p_jobj[NodeConfig::c_signature].toString()); - m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); - m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); + m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString()); + m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString()); - m_attachmentFolder = p_jobj[NodeConfig::c_attachmentFolder].toString(); + m_attachmentFolder = p_jobj[NodeConfig::c_attachmentFolder].toString(); { auto arr = p_jobj[NodeConfig::c_tags].toArray(); diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index 01f1e3ee37..3b053ca59d 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -194,11 +194,14 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi seenNames.insert(folder.m_name); auto folderNode = QSharedPointer::create(folder.m_name, - folder.toNodeParameters(), getNotebook(), p_node); inheritNodeFlags(p_node, folderNode.data()); folderNode->setExists(getBackend()->existsDir(PathUtils::concatenateFilePath(basePath, folder.m_name))); + + // 设置视觉效果信息 + NodeParameters visualParams = folder.toNodeParameters(); + folderNode->setVisual(visualParams.m_visual); children.push_back(folderNode); } diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index 76d80b9299..0855dcc8b6 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -704,15 +704,9 @@ void NotebookNodeExplorer::setCascadeColorRecursively(Node *p_node, const QStrin // 设置当前节点的颜色 NodeVisual visual = p_node->getVisual(); - if (!p_backgroundColor.isEmpty()) { - visual.setBackgroundColor(p_backgroundColor); - } - if (!p_borderColor.isEmpty()) { - visual.setBorderColor(p_borderColor); - } - if (!p_nameColor.isEmpty()) { - visual.setNameColor(p_nameColor); - } + visual.setBackgroundColor(p_backgroundColor); + visual.setBorderColor(p_borderColor); + visual.setNameColor(p_nameColor); updateCurrentNodeVisualDirectly(p_node, visual); @@ -1242,6 +1236,7 @@ void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_ auto cascadeMenu = WidgetsFactory::createMenu(tr("Cascade Color Settings"), visualMenu); createAndAddAction(Action::SetCascadeBackgroundColor, cascadeMenu, p_master); createAndAddAction(Action::SetCascadeBorderColor, cascadeMenu, p_master); + createAndAddAction(Action::ClearCascadeColors, cascadeMenu, p_master); visualMenu->addMenu(cascadeMenu); } @@ -1731,6 +1726,24 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent, boo } }); break; + + case Action::ClearCascadeColors: + act = new QAction(tr("Clear Cascade Colors"), p_parent); + connect(act, &QAction::triggered, + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + if (!node) { + return; + } + + if (checkInvalidNode(node)) { + return; + } + + // 递归清除子节点 + setCascadeColorRecursively(node, QString(), QString(), QString()); + }); + break; case Action::ClearColors: act = new QAction(tr("Clear Colors"), p_parent); diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index e6f0d33f52..dceb0f2d08 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -180,6 +180,7 @@ namespace vnotex SetNameColor, SetCascadeBackgroundColor, SetCascadeBorderColor, + ClearCascadeColors, ClearColors };