diff --git a/src/app/panels/ObjectTreePanel.cpp b/src/app/panels/ObjectTreePanel.cpp index 074d785..0e48af4 100644 --- a/src/app/panels/ObjectTreePanel.cpp +++ b/src/app/panels/ObjectTreePanel.cpp @@ -2,9 +2,12 @@ #include #include +#include +#include #include #include #include +#include #include "Glyphs.hpp" #include "Theme.hpp" @@ -13,17 +16,22 @@ namespace geopro::app { namespace { -// TM 节点把 tmObjectId 存在该角色;GS/项目根节点为空。 -constexpr int kRoleTmId = Qt::UserRole + 2; +constexpr int kRoleObjId = Qt::UserRole + 2; // 节点对象 id(GS/TM 都存) +constexpr int kRoleConfType = Qt::UserRole + 3; // 1=GS 2=TM void addNodes(QTreeWidgetItem* parent, const std::vector& nodes) { for (const auto& n : nodes) { auto* item = new QTreeWidgetItem(parent); item->setText(0, QString::fromStdString(n.node.name)); + item->setData(0, kRoleObjId, QString::fromStdString(n.node.id)); if (n.isTm) { - item->setData(0, kRoleTmId, QString::fromStdString(n.node.id)); + item->setData(0, kRoleConfType, 2); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(0, Qt::Unchecked); // 真实数据渲染下一轮接入,默认不勾 + item->setCheckState(0, Qt::Unchecked); + } else { + item->setData(0, kRoleConfType, 1); // GS + item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate); + item->setCheckState(0, Qt::Unchecked); } addNodes(item, n.children); } @@ -49,12 +57,28 @@ ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) { lay->addWidget(hint_); QObject::connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int) { - const QString tmId = item->data(0, kRoleTmId).toString(); - if (!tmId.isEmpty()) emit tmClicked(tmId); + const QString id = item->data(0, kRoleObjId).toString(); + const int confType = item->data(0, kRoleConfType).toInt(); + if (!id.isEmpty() && confType != 0) emit objectClicked(id, confType); }); - QObject::connect(tree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* item, int) { - const QString tmId = item->data(0, kRoleTmId).toString(); - if (!tmId.isEmpty()) emit tmCheckToggled(tmId, item->checkState(0) == Qt::Checked); + // 勾选变化:GS 级联会触发多次 itemChanged,用 0ms 单发合并成一次「收集勾选叶子并发射」。 + QObject::connect(tree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { + if (checkPending_) return; + checkPending_ = true; + QTimer::singleShot(0, this, [this]() { + checkPending_ = false; + QStringList tmIds; + std::function walk = [&](QTreeWidgetItem* node) { + for (int i = 0; i < node->childCount(); ++i) { + QTreeWidgetItem* c = node->child(i); + if (c->data(0, kRoleConfType).toInt() == 2 && c->checkState(0) == Qt::Checked) + tmIds << c->data(0, kRoleObjId).toString(); + walk(c); + } + }; + walk(tree_->invisibleRootItem()); + emit checkedTmsChanged(tmIds); + }); }); } diff --git a/src/app/panels/ObjectTreePanel.hpp b/src/app/panels/ObjectTreePanel.hpp index 1ba37e8..2fb454d 100644 --- a/src/app/panels/ObjectTreePanel.hpp +++ b/src/app/panels/ObjectTreePanel.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include "repo/RepoTypes.hpp" @@ -19,13 +20,15 @@ public: void showMessage(const QString& message); // 错误/空状态占位 signals: - void tmClicked(const QString& tmObjectId); - // 前瞻钩子:勾选驱动中央渲染留待下一轮接真实 DS(本轮暂无消费者)。 - void tmCheckToggled(const QString& tmObjectId, bool checked); + // confType: 1=GS 2=TM。单击行(驱动数据列表 + 对象属性)。 + void objectClicked(const QString& objectId, int confType); + // 当前全部被勾选的 TM 叶子 id(已合并发射)。 + void checkedTmsChanged(const QStringList& tmObjectIds); private: QTreeWidget* tree_ = nullptr; // Qt 原生标准树(复选框/箭头由 Fusion 绘制,清晰可控) QLabel* hint_ = nullptr; + bool checkPending_ = false; // 勾选合并发射防重入 }; } // namespace geopro::app