geopro/src/app/panels/columns/Column3DAnalysis.cpp

210 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "panels/columns/Column3DAnalysis.hpp"
#include <QComboBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QSet>
#include <QSignalBlocker>
#include <QSplitter>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QTreeWidgetItemIterator>
#include <QVBoxLayout>
#include "Theme.hpp"
#include "panels/DatasetListPanel.hpp"
namespace geopro::app {
Column3DAnalysis::Column3DAnalysis(QWidget* parent) : QWidget(parent) {
auto* root = new QVBoxLayout(this);
root->setContentsMargins(space::kMd, space::kMd, space::kMd, space::kMd);
root->setSpacing(space::kMd);
tree_ = new QTreeWidget();
tree_->setHeaderHidden(true);
tree_->setRootIsDecorated(true);
applyDatasetCardDelegate(tree_);
tree_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(tree_, &QTreeWidget::customContextMenuRequested, this, &Column3DAnalysis::onContextMenu);
connect(tree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) {
QStringList ids;
for (QTreeWidgetItemIterator it(tree_); *it; ++it) {
if ((*it)->checkState(0) == Qt::Checked)
ids << (*it)->data(0, kDsIdRole).toString();
}
emit checkedItemsChanged(ids);
});
// ── 数据集树(上) + 「异常」分组(下) 放进竖向 Splitter可拖拽、清晰分隔数据集树占多数 ──
// ── 3D 异常控制(#4c分组框内含 显示过滤下拉 + 异常列表(每条显隐勾选;选中联动 VTK──
anomalyTree_ = new QTreeWidget();
anomalyTree_->setHeaderHidden(true);
anomalyTree_->setRootIsDecorated(false);
anomalyTree_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(anomalyTree_, &QTreeWidget::customContextMenuRequested, this,
&Column3DAnalysis::onAnomalyContextMenu);
connect(anomalyTree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* it, int) {
if (it == nullptr) return;
emit anomalyVisibilityChanged(it->data(0, kDsIdRole).toString(),
it->checkState(0) == Qt::Checked);
});
connect(anomalyTree_, &QTreeWidget::currentItemChanged, this,
[this](QTreeWidgetItem* cur, QTreeWidgetItem*) {
if (cur != nullptr) emit anomalySelected(cur->data(0, kDsIdRole).toString());
});
// 双击异常项 → 属性对话框R83按 id 回查当前集合发出整条异常。
connect(anomalyTree_, &QTreeWidget::itemDoubleClicked, this,
[this](QTreeWidgetItem* it, int) {
if (it == nullptr) return;
const QString id = it->data(0, kDsIdRole).toString();
for (const auto& a : anomalies_)
if (QString::fromStdString(a.id) == id) {
emit anomalyPropertiesRequested(a);
return;
}
});
auto* anomGroup = new QGroupBox(QStringLiteral("异常"));
auto* gv = new QVBoxLayout(anomGroup);
gv->setContentsMargins(space::kSm, space::kSm, space::kSm, space::kSm);
gv->setSpacing(space::kSm);
{
auto* fr = new QHBoxLayout();
fr->addWidget(new QLabel(QStringLiteral("显示")));
anomalyFilter_ = new QComboBox();
anomalyFilter_->addItem(QStringLiteral("全部显示")); // 0
anomalyFilter_->addItem(QStringLiteral("随GS")); // 1
anomalyFilter_->addItem(QStringLiteral("随数据集")); // 2
anomalyFilter_->addItem(QStringLiteral("全部隐藏")); // 3
anomalyFilter_->setCurrentIndex(2); // 默认随数据集(= 跟当前三维体显隐)
connect(anomalyFilter_, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int idx) { emit anomalyDisplayFilterChanged(idx); });
fr->addWidget(anomalyFilter_, 1);
gv->addLayout(fr);
}
gv->addWidget(anomalyTree_, 1);
auto* splitter = new QSplitter(Qt::Vertical);
splitter->setChildrenCollapsible(false);
splitter->addWidget(tree_);
splitter->addWidget(anomGroup);
splitter->setStretchFactor(0, 3); // 数据集树占多
splitter->setStretchFactor(1, 2);
root->addWidget(splitter, 1);
}
int Column3DAnalysis::anomalyFilterMode() const {
return anomalyFilter_ ? anomalyFilter_->currentIndex() : 2;
}
void Column3DAnalysis::setAnomalies(const std::vector<geopro::core::Anomaly>& anoms) {
anomalies_ = anoms; // 留存供双击查属性R83
QSignalBlocker block(anomalyTree_); // 填充不触发 visibilityChanged
anomalyTree_->clear();
for (const auto& a : anoms) {
auto* item = new QTreeWidgetItem(anomalyTree_);
const QString name = a.name.empty() ? QStringLiteral("异常") : QString::fromStdString(a.name);
const QString type = a.typeName.empty() ? QString() : QString::fromStdString(a.typeName);
item->setText(0, type.isEmpty() ? name : QStringLiteral("%1%2").arg(name, type));
item->setData(0, kDsIdRole, QString::fromStdString(a.id));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(0, Qt::Checked); // 默认显示
}
}
void Column3DAnalysis::onAnomalyContextMenu(const QPoint& pos) {
QTreeWidgetItem* it = anomalyTree_->itemAt(pos);
if (it == nullptr) return;
const QString id = it->data(0, kDsIdRole).toString();
QMenu menu(this);
menu.addAction(QStringLiteral("删除异常"), this, [this, id] { emit anomalyDeleteRequested(id); });
menu.exec(anomalyTree_->viewport()->mapToGlobal(pos));
}
void Column3DAnalysis::setDatasets(const std::vector<geopro::data::DsRow>& rows) {
// 按 dsId 保留刷新前的勾选态:列表重建(保存切片/生成体追加一行也会整树重建)不应丢已勾选项
// 的渲染态——否则保存切片会连带取消三维体勾选、把它从场景移除(实测 bug
// 切换测线(新数据)时旧 id 不匹配 → 自然全空,行为与原先一致。
QSet<QString> wasChecked;
for (QTreeWidgetItemIterator it(tree_); *it; ++it)
if ((*it)->checkState(0) == Qt::Checked)
wasChecked.insert((*it)->data(0, kDsIdRole).toString());
{
QSignalBlocker blocker(tree_);
populateDatasetList(tree_, rows, /*append=*/false);
for (QTreeWidgetItemIterator it(tree_); *it; ++it) {
(*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);
const QString id = (*it)->data(0, kDsIdRole).toString();
(*it)->setCheckState(0, wasChecked.contains(id) ? Qt::Checked : Qt::Unchecked);
}
} // blocker released here
// 仅当勾选集真正变化才发信号:重建但勾选集不变(如保存切片仅追加一行)→ 不发,
// 避免下游 syncSlices 用"尚未勾选新切片"的中间态误隐藏刚链接的切片(闪烁/重复)。
QStringList ids;
QSet<QString> nowChecked;
for (QTreeWidgetItemIterator it(tree_); *it; ++it)
if ((*it)->checkState(0) == Qt::Checked) {
const QString id = (*it)->data(0, kDsIdRole).toString();
ids << id;
nowChecked.insert(id);
}
if (nowChecked != wasChecked) emit checkedItemsChanged(ids);
}
void Column3DAnalysis::setItemChecked(const QString& dsId, bool checked) {
for (QTreeWidgetItemIterator it(tree_); *it; ++it) {
if ((*it)->data(0, kDsIdRole).toString() != dsId) continue;
for (QTreeWidgetItem* p = (*it)->parent(); p != nullptr; p = p->parent())
p->setExpanded(true); // 展开父链 → 新勾选行可见
// setCheckState 仅在状态变化时发 itemChanged → checkedItemsChanged驱动渲染同步
(*it)->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
return;
}
}
void Column3DAnalysis::onContextMenu(const QPoint& pos) {
QTreeWidgetItem* it = tree_->itemAt(pos);
if (!it) return;
const QString dsId = it->data(0, kDsIdRole).toString();
const QString ddCode = it->data(0, kDsDdCodeRole).toString();
const QString name = it->data(0, kDsNameRole).toString();
const bool isSlice = (ddCode == QStringLiteral("dd_slice"));
QMenu menu(this);
if (!isSlice) {
// 三维体数据集:切片▸(上下/前后/左右/任意) / 色阶 / 数据详情。
// 显示/隐藏 = 勾选框,故菜单不再重复提供(去冗余)。
QMenu* sub = menu.addMenu(QStringLiteral("切片"));
using SA = geopro::render::interact::SliceAxis;
sub->addAction(QStringLiteral("上下"), this, [this]{ emit sliceRequested(SA::UpDown); });
sub->addAction(QStringLiteral("前后"), this, [this]{ emit sliceRequested(SA::FrontBack); });
sub->addAction(QStringLiteral("左右"), this, [this]{ emit sliceRequested(SA::LeftRight); });
sub->addAction(QStringLiteral("任意"), this, [this]{ emit sliceRequested(SA::Oblique); });
menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); });
menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); });
} else {
// 切片数据集:保存(覆盖位姿) / 保存为(另存新切片) / 导出▸(图片·dat) / 删除 / 色阶 / 数据详情。
// 显示/隐藏 = 勾选框,去冗余。导出与 VTK 视图切片右键统一为二级菜单。
menu.addAction(QStringLiteral("保存"), this, [this, dsId]{ emit sliceSaveRequested(dsId); });
menu.addAction(QStringLiteral("保存为"), this, [this, dsId]{ emit sliceSaveAsRequested(dsId); });
QMenu* exp = menu.addMenu(QStringLiteral("导出"));
exp->addAction(QStringLiteral("图片"), this, [this, dsId]{ emit sliceExportImageRequested(dsId); });
exp->addAction(QStringLiteral("dat"), this, [this, dsId]{ emit sliceExportDatRequested(dsId); });
menu.addAction(QStringLiteral("删除"), this, [this, dsId]{ emit sliceDeleteRequested(dsId); });
menu.addSeparator();
menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); });
menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); });
}
menu.exec(tree_->viewport()->mapToGlobal(pos));
}
} // namespace geopro::app