From 11d7dd53b99e03dcbd17a3c69e4c66c816326ce0 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 30 Jun 2026 22:32:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(vtk):=20=E6=AE=B5=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E8=BF=81=E6=8F=8F=E8=BF=B0=E7=AC=A6;=E6=AE=B5=E5=A4=B4?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=9D=A1=E7=94=B1operations/filters=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8+=E7=AD=9B=E9=80=89=E6=8A=98=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panels/columns/CategoryAnalysisTab.cpp | 12 +- src/app/panels/columns/CategorySection.cpp | 132 +++++++++--------- src/app/panels/columns/CategorySection.hpp | 24 ++-- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/src/app/panels/columns/CategoryAnalysisTab.cpp b/src/app/panels/columns/CategoryAnalysisTab.cpp index a29a8a7..894cf91 100644 --- a/src/app/panels/columns/CategoryAnalysisTab.cpp +++ b/src/app/panels/columns/CategoryAnalysisTab.cpp @@ -35,13 +35,13 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d col->setContentsMargins(0, space::kSm, 0, space::kSm); // 顶部留白:首段段头不贴顶 col->setSpacing(space::kSm); - for (const CategorySpec& spec : categoryConfigs()) { - auto* sec = new CategorySection(spec, dict, content); - sections_[spec.id] = sec; + for (const auto& desc : geopro::data::categoryCatalog()) { + auto* sec = new CategorySection(desc, dict, content); + sections_[desc.id] = sec; ordered_.push_back(sec); connect(sec, &CategorySection::collapsedChanged, this, &CategoryAnalysisTab::relayoutSections); // 折叠/展开 → 重排 stretch(向上收) - const std::string segId = spec.id; + const std::string segId = desc.id; connect(sec, &CategorySection::checkedDatasetsChanged, this, [this, segId](const QStringList& ids) { checkedBySeg_[segId] = ids; @@ -50,8 +50,8 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d }); connect(sec, &CategorySection::generateVolumeRequested, this, &CategoryAnalysisTab::generateVolumeRequested); - connect(sec, &CategorySection::radarImportRequested, this, - &CategoryAnalysisTab::radarImportRequested); + // 注:CategorySection 的「+导入雷达测线」入口已移除(Task D1 迁至 TopBar)。CategoryAnalysisTab + // 自身仍保留 radarImportRequested 信号(main.cpp 的连接保持有效),待 D1 由 TopBar 新发射方接入。 connect(sec, &CategorySection::detailRequested, this, &CategoryAnalysisTab::detailRequested); connect(sec, &CategorySection::deleteDatasetRequested, this, &CategoryAnalysisTab::deleteDatasetRequested); diff --git a/src/app/panels/columns/CategorySection.cpp b/src/app/panels/columns/CategorySection.cpp index 632b4c3..3ef60a0 100644 --- a/src/app/panels/columns/CategorySection.cpp +++ b/src/app/panels/columns/CategorySection.cpp @@ -20,6 +20,7 @@ #include "Theme.hpp" #include "panels/DatasetListPanel.hpp" +#include "panels/columns/SectionIconBar.hpp" #include "repo/DatasetFieldDictionary.hpp" namespace geopro::app { @@ -27,14 +28,14 @@ namespace geopro::app { using geopro::data::DsRow; using geopro::data::DsTypeFields; -CategorySection::CategorySection(const CategorySpec& spec, geopro::data::DatasetFieldDictionary* dict, - QWidget* parent) - : QWidget(parent), spec_(spec), dict_(dict) { +CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc, + geopro::data::DatasetFieldDictionary* dict, QWidget* parent) + : QWidget(parent), desc_(desc), dict_(dict) { auto* root = new QVBoxLayout(this); root->setContentsMargins(0, 0, 0, 0); root->setSpacing(0); - // 数据类型段头(可折叠,规范§4.3/§6):chevron + 标题(title 字号·半粗) |「+ 新增三维体」(右,仅反演类)。 + // 数据类型段头(可折叠,规范§4.3/§6):chevron + 标题(title 字号·半粗) | 右侧响应式图标条(SectionIconBar)。 // 段头加浅底 + 底分隔线作视觉分段;去原生小三角(难看)→chevron 文本前缀,随主题/hover 变色。 auto* headerRow = new QWidget(this); headerRow->setObjectName(QStringLiteral("secHeader")); @@ -58,57 +59,44 @@ CategorySection::CategorySection(const CategorySpec& spec, geopro::data::Dataset .arg(type::kWeightSemibold)); auto syncHeader = [this] { header_->setText((header_->isChecked() ? QStringLiteral("▾ ") : QStringLiteral("▸ ")) - + QString::fromStdString(spec_.title)); + + QString::fromStdString(desc_.title)); }; syncHeader(); hl->addWidget(header_); hl->addStretch(1); - if (spec_.canGenerateVolume) { - auto* gen = new QToolButton(headerRow); - gen->setText(QStringLiteral("+ 新增三维体")); - gen->setCursor(Qt::PointingHandCursor); - // 次级强调按钮(规范§6.7):描边 accent + accent 文字,hover 浅强调底;非裸文字。 - applyTokenizedStyleSheet( - gen, QStringLiteral( - "QToolButton{border:1px solid {{accent/primary}};border-radius:%1px;" - "color:{{accent/primary}};background:transparent;padding:%2px %3px;font-size:%4px;}" - "QToolButton:hover{background:{{bg/selected}};}" - "QToolButton:pressed{background:{{bg/hover}};}") - .arg(radius::kSm) - .arg(scaledPx(space::kXxs)) - .arg(scaledPx(space::kMd)) - .arg(scaledPx(type::kCaption))); - connect(gen, &QToolButton::clicked, this, [this] { - emit generateVolumeRequested(QString::fromStdString(spec_.dsTypeCode), checkedDsIds()); - }); - hl->addWidget(gen); - } - // 三维体段头「+ 导入雷达测线」(后端未就绪的本地过渡入口):弹出菜单选 规范化/Impulse。 - // 次级强调按钮样式同「+新增三维体」;点击发 radarImportRequested(impulse) → 上层走导入流程。 - if (spec_.id == "voxel") { - auto* imp = new QToolButton(headerRow); - imp->setText(QStringLiteral("+ 导入雷达测线")); - imp->setCursor(Qt::PointingHandCursor); - imp->setPopupMode(QToolButton::InstantPopup); - applyTokenizedStyleSheet( - imp, QStringLiteral( - "QToolButton{border:1px solid {{accent/primary}};border-radius:%1px;" - "color:{{accent/primary}};background:transparent;padding:%2px %3px;font-size:%4px;}" - "QToolButton::menu-indicator{image:none;width:0;}" - "QToolButton:hover{background:{{bg/selected}};}" - "QToolButton:pressed{background:{{bg/hover}};}") - .arg(radius::kSm) - .arg(scaledPx(space::kXxs)) - .arg(scaledPx(space::kMd)) - .arg(scaledPx(type::kCaption))); - auto* menu = new QMenu(imp); - menu->addAction(QStringLiteral("规范化测线目录(.head/.data)…"), this, - [this] { emit radarImportRequested(false); }); - menu->addAction(QStringLiteral("Impulse 测线目录(.iprb)…"), this, - [this] { emit radarImportRequested(true); }); - imp->setMenu(menu); - hl->addWidget(imp); + // 段头图标条:遍历 desc_.operations,经一处 OpKind→IconAction 映射装配(spec §6)。 + // glyph 键须命中 SectionIconBar::glyphFromKey 已识别集;z 值无专用键,复用 collapse(竖向双箭头)。 + iconBar_ = new SectionIconBar(headerRow); + std::vector acts; + for (geopro::data::OpKind op : desc_.operations) { + switch (op) { + case geopro::data::OpKind::GenerateVolume: + // dsTypeCode 不再由段配置带(描述符无此字段)→ 发空串,接收方按 sourceIds 解析类型。 + acts.push_back({QStringLiteral("plus"), QStringLiteral("新增三维体"), + [this] { emit generateVolumeRequested(QString(), checkedDsIds()); }, {}}); + break; + case geopro::data::OpKind::Filter: + acts.push_back({QStringLiteral("filter"), QStringLiteral("筛选"), + [this] { if (filterRow_) filterRow_->setVisible(!filterRow_->isVisible()); }, + {}}); + break; + case geopro::data::OpKind::PlaneZ: + acts.push_back({QStringLiteral("collapse"), QStringLiteral("z 值"), {}, + [this](QToolButton*) { // Task E3 建滑块 popup;当前先发请求信号 + emit zSliderRequested(QString::fromStdString(desc_.id)); + }}); + break; + case geopro::data::OpKind::Basemap: + acts.push_back({QStringLiteral("map"), QStringLiteral("底图"), {}, + [this](QToolButton*) { // Task F2 建底图 popup;当前先发请求信号 + emit basemapPopupRequested(QString::fromStdString(desc_.id)); + }}); + break; + } } + iconBar_->setMaxIcons(static_cast(acts.size())); // 段头操作数即可见上限(够宽全显,不够收进「…」) + iconBar_->setActions(acts); + hl->addWidget(iconBar_); root->addWidget(headerRow); body_ = new QWidget(this); @@ -116,20 +104,27 @@ CategorySection::CategorySection(const CategorySpec& spec, geopro::data::Dataset body->setContentsMargins(space::kMd, space::kSm, space::kMd, space::kMd); body->setSpacing(space::kSm); - // 筛选行:采集时间范围(在前)+ 装置类型(在后,仅 ERT 类)。 - auto* filterRow = new QHBoxLayout(); - filterRow->setSpacing(space::kSm); - dateRange_ = new DateRangeEdit(body_); - connect(dateRange_, &DateRangeEdit::rangeChanged, this, [this] { rebuildList(); }); - filterRow->addWidget(dateRange_, 1); - if (spec_.hasArrayTypeFilter) { - arrayCombo_ = new QComboBox(body_); - arrayCombo_->addItem(QStringLiteral("全部装置"), QString()); - connect(arrayCombo_, qOverload(&QComboBox::currentIndexChanged), this, - [this](int) { rebuildList(); }); - filterRow->addWidget(arrayCombo_); + // 筛选行(默认折叠,由段头 Filter 图标 toggle):按 desc_.filters 装配 —— DateRange→采集时间范围(在前); + // ArrayType→装置类型下拉(在后)。未列出的维度不建控件,passesFilters/rebuildList 视该控件缺席=不筛该维。 + filterRow_ = new QWidget(body_); + auto* filterLay = new QHBoxLayout(filterRow_); + filterLay->setContentsMargins(0, 0, 0, 0); + filterLay->setSpacing(space::kSm); + for (geopro::data::FilterKind fk : desc_.filters) { + if (fk == geopro::data::FilterKind::DateRange) { + dateRange_ = new DateRangeEdit(filterRow_); + connect(dateRange_, &DateRangeEdit::rangeChanged, this, [this] { rebuildList(); }); + filterLay->addWidget(dateRange_, 1); + } else if (fk == geopro::data::FilterKind::ArrayType) { + arrayCombo_ = new QComboBox(filterRow_); + arrayCombo_->addItem(QStringLiteral("全部装置"), QString()); + connect(arrayCombo_, qOverload(&QComboBox::currentIndexChanged), this, + [this](int) { rebuildList(); }); + filterLay->addWidget(arrayCombo_); + } } - body->addLayout(filterRow); + filterRow_->hide(); // 默认折叠,点段头筛选图标展开 + body->addWidget(filterRow_); // 段体:可勾选数据树(勾选=渲染)。复用数据列表卡片委托与 populateDatasetList。 list_ = new QTreeWidget(body_); @@ -152,7 +147,7 @@ CategorySection::CategorySection(const CategorySpec& spec, geopro::data::Dataset if (id.isEmpty()) return; emit detailRequested(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString()); }); - if (spec_.id == "voxel") { // 仅三维体段提供右键操作菜单(体/切片/异常) + if (desc_.id == "voxel") { // 仅三维体段提供右键操作菜单(体/切片/异常) list_->setContextMenuPolicy(Qt::CustomContextMenu); connect(list_, &QTreeWidget::customContextMenuRequested, this, &CategorySection::showContextMenu); // 树选中切片/异常 → VTK 高亮联动(正向 list→VTK)。 @@ -273,7 +268,7 @@ void CategorySection::clearAllBusy() { } void CategorySection::refreshArrayCombo() { - if (!spec_.hasArrayTypeFilter || !arrayCombo_) return; + if (!arrayCombo_) return; // 该段无装置筛选控件(desc_.filters 未含 ArrayType)→ 不刷 const QString prev = arrayCombo_->currentData().toString(); const QSignalBlocker block(arrayCombo_); arrayCombo_->clear(); @@ -303,8 +298,8 @@ void CategorySection::refreshArrayCombo() { } bool CategorySection::passesFilters(const DsRow& row) const { - // 类型筛选("全部"=空不筛):按 ds 自身类型值(typeName,回退 dsTypeCode)命中选中项。 - if (spec_.hasArrayTypeFilter && arrayCombo_) { + // 类型筛选("全部"=空不筛;无 arrayCombo_=该段不筛装置维度):按 ds 自身类型值(typeName,回退 dsTypeCode)命中选中项。 + if (arrayCombo_) { const QString sel = arrayCombo_->currentData().toString(); if (!sel.isEmpty()) { const QString t = !row.typeName.empty() ? QString::fromStdString(row.typeName) @@ -316,7 +311,8 @@ bool CategorySection::passesFilters(const DsRow& row) const { const QDate from = dateRange_ ? dateRange_->from() : QDate(); const QDate to = dateRange_ ? dateRange_->to() : QDate(); if (from.isValid() || to.isValid()) { - const DsTypeFields* f = dict_ ? dict_->fields(spec_.dsTypeCode) : nullptr; + // 采集时间字段定义按 ds 自身类型取(描述符无段级 dsTypeCode;逐行查更准,缺定义则回退 createTime)。 + const DsTypeFields* f = dict_ ? dict_->fields(row.dsTypeCode) : nullptr; std::string ts = f ? collectTimeOf(row, *f) : std::string(); if (ts.empty()) ts = row.createTime; const QDate d = QDate::fromString(QString::fromStdString(ts).left(10), QStringLiteral("yyyy-MM-dd")); diff --git a/src/app/panels/columns/CategorySection.hpp b/src/app/panels/columns/CategorySection.hpp index 79a6744..69c9587 100644 --- a/src/app/panels/columns/CategorySection.hpp +++ b/src/app/panels/columns/CategorySection.hpp @@ -3,7 +3,7 @@ #include #include #include "interact/SlicePlaneMath.hpp" // geopro::render::interact::SliceAxis -#include "repo/CategoryConfig.hpp" +#include "repo/CategoryDescriptor.hpp" // geopro::data::CategoryDescriptor / OpKind / FilterKind #include "repo/RepoTypes.hpp" class QTreeWidget; @@ -22,14 +22,16 @@ class DatasetFieldDictionary; namespace geopro::app { class DateRangeEdit; +class SectionIconBar; -// 单个数据类型大类段(spec §7):段头(标题/折叠 + 装置类型/日期筛选 + 「+新增三维体」)+ 段体(可勾选数据树)。 -// 勾选数据行 = 渲染(帘面/体素/切片);段头生成按钮据当前勾选源发 generateVolumeRequested。 +// 单个数据类型大类段(spec §7):段头(标题/折叠 + 段头图标条)+ 段体(折叠筛选行 + 可勾选数据树)。 +// 段头图标条由 descriptor.operations(OpKind) 驱动;筛选行由 descriptor.filters(FilterKind) 驱动、默认折叠。 +// 勾选数据行 = 渲染(帘面/体素/切片/二维面);GenerateVolume 图标据当前勾选源发 generateVolumeRequested。 class CategorySection : public QWidget { Q_OBJECT public: - CategorySection(const CategorySpec& spec, geopro::data::DatasetFieldDictionary* dict, - QWidget* parent = nullptr); + CategorySection(const geopro::data::CategoryDescriptor& desc, + geopro::data::DatasetFieldDictionary* dict, QWidget* parent = nullptr); // 对象树同源的扁平 GS/TM 节点(段体容器分层用;Task 12 接入真实结构,当前仅存储)。 void setStructure(const std::vector& nodes); @@ -41,7 +43,6 @@ public: void selectItem(const QString& dsId); // 程序化选中某行(VTK→list 反向联动);空/未找到=清选中 QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds(异常显隐同步用) void refreshArrayFilter() { refreshArrayCombo(); } // 装置枚举异步加载后重填下拉 - const CategorySpec& spec() const { return spec_; } bool isExpanded() const; // 段头展开态(供外层按折叠状态重分配 stretch,实现"折叠向上收") void ensureExpanded(); // 展开本段(折叠则展开):滚动定位前确保目标行可见 QTreeWidget* listWidget() const { return list_; } // 段体树(外层滚动定位用) @@ -51,8 +52,9 @@ public: signals: void checkedDatasetsChanged(const QStringList& dsIds); // 数据行勾选=渲染 void collapsedChanged(); // 折叠/展开切换 → 外层 CategoryAnalysisTab 重排各段 stretch - void generateVolumeRequested(const QString& dsTypeCode, const QStringList& sourceDsIds); // 段头「+新增三维体」 - void radarImportRequested(bool impulse); // 三维体段头「+导入雷达测线」(false=规范化 .head/.data, true=Impulse .iprb) + void generateVolumeRequested(const QString& dsTypeCode, const QStringList& sourceDsIds); // 段头「+新增三维体」(接收方按 sourceIds 解析类型) + void zSliderRequested(const QString& typeId); // PlaneZ 图标:弹 z 值滑块 popup(Task E3 实现) + void basemapPopupRequested(const QString& typeId); // Basemap 图标:弹底图选择 popup(Task F2 实现) void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 双击/右键=详情 void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除(切片/异常) // ── 三维体段右键操作(迁自旧 Column3DAnalysis,全接)── @@ -74,14 +76,16 @@ private: QStringList checkedDsIds() const; bool passesFilters(const geopro::data::DsRow& row) const; // 装置类型 + 采集时间范围 - CategorySpec spec_; + geopro::data::CategoryDescriptor desc_; geopro::data::DatasetFieldDictionary* dict_ = nullptr; std::vector rows_; std::vector structure_; QToolButton* header_ = nullptr; // 折叠头(标题 + 箭头) + SectionIconBar* iconBar_ = nullptr; // 段头响应式图标条(由 desc_.operations 装配) QWidget* body_ = nullptr; // 段体容器(折叠时隐藏) - QComboBox* arrayCombo_ = nullptr; // 装置类型筛选(仅 hasArrayTypeFilter) + QWidget* filterRow_ = nullptr; // 筛选行容器(默认折叠,由 Filter 图标 toggle) + QComboBox* arrayCombo_ = nullptr; // 装置类型筛选(仅 desc_.filters 含 ArrayType) DateRangeEdit* dateRange_ = nullptr; // 采集时间范围筛选(起止双日历,可清空) QTreeWidget* list_ = nullptr; QTimer* spinTimer_ = nullptr; // 驱动 busy 行 spinner 旋转(有 busy 行时运行)