feat/vtk-merged-dataset-column #10
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<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"));
|
||||
|
|
|
|||
|
|
@ -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 值滑块 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<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 行时运行)
|
||||
|
|
|
|||
Loading…
Reference in New Issue