feat/vtk-merged-dataset-column #10

Merged
gaozheng merged 40 commits from feat/vtk-merged-dataset-column into main 2026-07-01 14:48:38 +08:00
3 changed files with 84 additions and 84 deletions
Showing only changes of commit 11d7dd53b9 - Show all commits

View File

@ -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);

View File

@ -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/§6chevron + 标题(title 字号·半粗) 「+ 新增三维体」(右,仅反演类)。
// 数据类型段头可折叠规范§4.3/§6chevron + 标题(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<IconAction> 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<int>(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<int>(&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<int>(&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"));

View File

@ -3,7 +3,7 @@
#include <QWidget>
#include <vector>
#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<geopro::data::StructNode>& 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 值滑块 popupTask E3 实现)
void basemapPopupRequested(const QString& typeId); // Basemap 图标:弹底图选择 popupTask 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<geopro::data::DsRow> rows_;
std::vector<geopro::data::StructNode> 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 行时运行)