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
4 changed files with 48 additions and 4 deletions
Showing only changes of commit 286054720e - Show all commits

View File

@ -1,6 +1,7 @@
#include "panels/columns/CategoryAnalysisTab.hpp"
#include <QAbstractItemView>
#include <QLabel>
#include <QPointer>
#include <QScrollArea>
#include <QScrollBar>
@ -10,6 +11,7 @@
#include "Theme.hpp"
#include "panels/columns/CategorySection.hpp"
#include "repo/CategoryDescriptor.hpp" // categoryCatalog含 trajectory 段)
namespace geopro::app {
@ -44,6 +46,7 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d
[this, segId](const QStringList& ids) {
checkedBySeg_[segId] = ids;
recomputeCheckedUnion();
updatePlaceholderAndVisibility(); // 勾选/段内容变化后段「有无行」可能变 → 刷新显隐
});
connect(sec, &CategorySection::generateVolumeRequested, this,
&CategoryAnalysisTab::generateVolumeRequested);
@ -72,6 +75,18 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d
// 尾部弹簧(末项):默认 0全部段折叠时由 relayoutSections 置 1吸收余量把段头顶到顶部。
col->addStretch(0);
scroll->setWidget(content);
// 全空占位:所有段都无可渲染数据行时显示,与 scroll 同级、互斥显隐(由 updatePlaceholderAndVisibility 切换)。
auto* placeholder = new QLabel(QStringLiteral("请在左侧对象树勾选测线 / 数据集"), this);
placeholder->setAlignment(Qt::AlignCenter);
placeholder->setWordWrap(true);
applyTokenizedStyleSheet(placeholder,
QStringLiteral("QLabel{color:{{text/tertiary}};padding:24px;}"));
outer->addWidget(placeholder, 0);
placeholder->hide();
placeholder_ = placeholder;
updatePlaceholderAndVisibility(); // 初始无数据 → 直接进入占位态
}
void CategoryAnalysisTab::relayoutSections() {
@ -86,14 +101,29 @@ void CategoryAnalysisTab::relayoutSections() {
}
void CategoryAnalysisTab::setBuckets(const CategoryBuckets& b) {
const auto& cfg = categoryConfigs();
for (std::size_t i = 0; i < cfg.size() && i < b.segments.size(); ++i) {
// splitByCategory 现按 categoryCatalog() 分桶(含 trajectory 第 5 桶,自然分发到轨迹段)。
// 注trajectory 段在 Task C2 才加入构造,当前 section("trajectory") 返回 nullptr → guard 跳过。
const auto& cat = geopro::data::categoryCatalog();
for (std::size_t i = 0; i < cat.size() && i < b.segments.size(); ++i) {
// voxel(三维体) 段数据来自 mock voxelTree(体/切片/异常),由调用方单独 section("voxel")->setDatasets
// 注入splitByCategory 对它永远是空桶,若在此用空桶覆盖会先清掉其勾选(随后重填但勾选已丢) →
// 表现为「创建切片/异常后体/切片选择被清空」(用户 issue 1)。故跳过 voxel勿覆盖。
if (cfg[i].id == "voxel") continue;
if (auto* sec = section(cfg[i].id)) sec->setDatasets(b.segments[i]);
if (cat[i].id == "voxel") continue;
if (auto* sec = section(cat[i].id)) sec->setDatasets(b.segments[i]);
}
updatePlaceholderAndVisibility(); // 分发后据各段有无数据刷新显隐 + 占位
}
void CategoryAnalysisTab::updatePlaceholderAndVisibility() {
bool anyVisible = false;
for (auto* sec : ordered_) {
const bool has = sec->hasRenderableRows();
sec->setVisible(has);
if (has) anyVisible = true;
}
if (scroll_) scroll_->setVisible(anyVisible);
if (placeholder_) placeholder_->setVisible(!anyVisible);
relayoutSections();
}
void CategoryAnalysisTab::setStructure(const std::vector<geopro::data::StructNode>& nodes) {

View File

@ -35,6 +35,7 @@ public:
void setItemBusy(const QString& dsId, bool busy); // 复选框↔等待 spinner 切换
void clearAllBusy(); // 撤回所有 spinner失败兜底
void scrollItemToTop(const QString& dsId); // 把某行尽量滚到面板顶部(新建三维体后定位)
void refreshVisibility() { updatePlaceholderAndVisibility(); } // 外部注入(如 voxel setDatasets)后刷新段显隐/占位
signals:
void checkedDatasetsChanged(const QStringList& dsIds); // 5 段勾选并集
@ -61,12 +62,15 @@ private:
// 据各段折叠态重排 stretch折叠段=0(仅段头高)、展开段=1(吸收余量);全折叠时尾部弹簧吸收→段头顶到顶部。
// 仅在面板不产生滚动条(内容short于视口)时有可见效果——正是用户反馈的"折叠后停在原位中间"场景。
void relayoutSections();
// 据各段是否有可渲染数据行 显隐段;全空时显示占位提示、隐藏滚动区。数据变化(分发/勾选/注入)后调用。
void updatePlaceholderAndVisibility();
std::map<std::string, CategorySection*> sections_;
std::vector<CategorySection*> ordered_; // 按 categoryConfigs 顺序relayout 遍历用)
QScrollArea* scroll_ = nullptr; // 外层滚动区scrollItemToTop 定位用)
QWidget* content_ = nullptr; // 滚动内容容器(坐标映射用)
QVBoxLayout* col_ = nullptr; // 段堆叠布局(末项=尾部弹簧)
QWidget* placeholder_ = nullptr; // 全空时显示的占位提示(与 scroll_ 同级,互斥显隐)
std::map<std::string, QStringList> checkedBySeg_; // 各段当前勾选(合并成并集)
};

View File

@ -189,6 +189,15 @@ QTreeWidgetItem* CategorySection::itemFor(const QString& dsId) const {
return nullptr;
}
bool CategorySection::hasRenderableRows() const {
if (!list_) return false;
// 数据行 = 非 container 的可勾选行;只有容器节点(分组)不算「有数据」。
for (QTreeWidgetItemIterator it(list_); *it; ++it)
if ((*it)->data(0, kDsDdCodeRole).toString() != QStringLiteral("container"))
return true;
return false;
}
void CategorySection::setStructure(const std::vector<geopro::data::StructNode>& nodes) {
structure_ = nodes; // 容器分层(项目根/GS/TM→ds在 Task 12 接入真实结构后据此构建。
}

View File

@ -46,6 +46,7 @@ public:
void ensureExpanded(); // 展开本段(折叠则展开):滚动定位前确保目标行可见
QTreeWidget* listWidget() const { return list_; } // 段体树(外层滚动定位用)
QTreeWidgetItem* itemFor(const QString& dsId) const; // 按 dsId 取行(无则 nullptr
bool hasRenderableRows() const; // 段体是否含可渲染数据行(非 container 容器节点),供单列动态显隐
signals:
void checkedDatasetsChanged(const QStringList& dsIds); // 数据行勾选=渲染