diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index dff8680..2215046 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -21,7 +21,8 @@ add_executable(geopro_desktop WIN32 PanelHeader.cpp login/LoginWindow.cpp panels/AnomalyListPanel.cpp - panels/DatasetListPanel.cpp) + panels/DatasetListPanel.cpp + panels/ObjectTreePanel.cpp) target_include_directories(geopro_desktop PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -34,6 +35,7 @@ target_link_libraries(geopro_desktop PRIVATE geopro_data # Phase 2:本地样本仓储(对象树 / 网格 / 色阶) geopro_net # Phase 3:登录(验证码 + RSA + login2) geopro_render # Phase 4:render 层(Scene / GridContourActor / 相机预设) + geopro_controller # Phase 5:导航编排(WorkbenchNavController) ) vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES}) diff --git a/src/app/panels/ObjectTreePanel.cpp b/src/app/panels/ObjectTreePanel.cpp new file mode 100644 index 0000000..39aabe6 --- /dev/null +++ b/src/app/panels/ObjectTreePanel.cpp @@ -0,0 +1,95 @@ +#include "panels/ObjectTreePanel.hpp" + +#include +#include +#include +#include +#include +#include + +#include "Glyphs.hpp" +#include "dto/NavDto.hpp" + +namespace geopro::app { + +namespace { +// TM 节点把 tmObjectId 存在该角色;GS/项目根节点为空。 +constexpr int kRoleTmId = Qt::UserRole + 2; + +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)); + if (n.isTm) { + item->setData(0, kRoleTmId, QString::fromStdString(n.node.id)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, Qt::Unchecked); // 真实数据渲染下一轮接入,默认不勾 + } + addNodes(item, n.children); + } +} +} // namespace + +ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) { + auto* lay = new QVBoxLayout(this); + lay->setContentsMargins(0, 0, 0, 0); + lay->setSpacing(0); + + tree_ = new QTreeWidget(this); + tree_->setHeaderHidden(true); + { + const QString openArrow = writeChevronIcon(true, QColor("#8A93A3")); + const QString closedArrow = writeChevronIcon(false, QColor("#8A93A3")); + tree_->setStyleSheet( + QStringLiteral("QTreeView::branch { background: #FFFFFF; }" + "QTreeView::branch:has-children:!has-siblings:closed," + "QTreeView::branch:closed:has-children:has-siblings { image: url(%1); }" + "QTreeView::branch:open:has-children:!has-siblings," + "QTreeView::branch:open:has-children:has-siblings { image: url(%2); }") + .arg(closedArrow, openArrow)); + } + lay->addWidget(tree_, 1); + + hint_ = new QLabel(QStringLiteral("(加载中…)"), this); + hint_->setAlignment(Qt::AlignCenter); + hint_->setStyleSheet(QStringLiteral("color:#9AA6B6; padding:16px;")); + hint_->setVisible(false); + 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); + }); + 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); + }); +} + +void ObjectTreePanel::setStructure(const QString& projectName, + const std::vector& nodes) { + const QSignalBlocker block(tree_); // 重建触发 itemChanged,先屏蔽 + tree_->clear(); + const auto roots = data::dto::buildStructTree(nodes); + if (roots.empty()) { + showMessage(projectName.isEmpty() ? QStringLiteral("(暂无项目)") + : QStringLiteral("(该项目暂无结构)")); + return; + } + hint_->setVisible(false); + tree_->setVisible(true); + auto* rootItem = new QTreeWidgetItem(tree_); + rootItem->setText(0, projectName.isEmpty() ? QStringLiteral("项目") : projectName); + addNodes(rootItem, roots); + tree_->expandAll(); +} + +void ObjectTreePanel::showMessage(const QString& message) { + tree_->clear(); + tree_->setVisible(false); + hint_->setText(message); + hint_->setVisible(true); +} + +} // namespace geopro::app diff --git a/src/app/panels/ObjectTreePanel.hpp b/src/app/panels/ObjectTreePanel.hpp new file mode 100644 index 0000000..58d54a8 --- /dev/null +++ b/src/app/panels/ObjectTreePanel.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include "repo/RepoTypes.hpp" + +class QTreeWidget; +class QLabel; + +namespace geopro::app { + +// 被动对象树:项目根 → GS → TM(叶子=TM,可勾选)。数据来自控制器;自身不发请求。 +class ObjectTreePanel : public QWidget { + Q_OBJECT +public: + explicit ObjectTreePanel(QWidget* parent = nullptr); + + // 用扁平结构节点重建树(内部调 dto::buildStructTree)。 + void setStructure(const QString& projectName, const std::vector& nodes); + void showMessage(const QString& message); // 错误/空状态占位 + +signals: + void tmClicked(const QString& tmObjectId); + void tmCheckToggled(const QString& tmObjectId, bool checked); + +private: + QTreeWidget* tree_ = nullptr; + QLabel* hint_ = nullptr; +}; + +} // namespace geopro::app