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

211 lines
8.7 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/CategorySection.hpp"
#include <QComboBox>
#include <QDate>
#include "panels/columns/DateRangeEdit.hpp"
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QPoint>
#include <QPushButton>
#include <QSet>
#include <QSignalBlocker>
#include <QToolButton>
#include <QTreeWidget>
#include <QTreeWidgetItemIterator>
#include <QVBoxLayout>
#include "Theme.hpp"
#include "panels/DatasetListPanel.hpp"
#include "repo/DatasetFieldDictionary.hpp"
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) {
auto* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
// 数据类型标题行spec §7折叠箭头+标题(左) | 「+新增三维体」(右,仅反演类,在标题行而非筛选行)。
auto* headerRow = new QWidget(this);
auto* hl = new QHBoxLayout(headerRow);
hl->setContentsMargins(space::kSm, 0, space::kSm, 0);
hl->setSpacing(space::kSm);
header_ = new QToolButton(headerRow);
header_->setText(QString::fromStdString(spec_.title));
header_->setCheckable(true);
header_->setChecked(true);
header_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
header_->setArrowType(Qt::DownArrow);
header_->setAutoRaise(true);
hl->addWidget(header_);
hl->addStretch(1);
if (spec_.canGenerateVolume) {
auto* gen = new QToolButton(headerRow);
gen->setText(QStringLiteral("+ 新增三维体"));
gen->setAutoRaise(true);
connect(gen, &QToolButton::clicked, this, [this] {
emit generateVolumeRequested(QString::fromStdString(spec_.dsTypeCode), checkedDsIds());
});
hl->addWidget(gen);
}
root->addWidget(headerRow);
body_ = new QWidget(this);
auto* body = new QVBoxLayout(body_);
body->setContentsMargins(space::kMd, space::kSm, space::kMd, space::kMd);
body->setSpacing(space::kSm);
// 筛选行:装置类型(仅 ERT 类)+ 采集时间段(起止;最小日期显示"不限",不再露 1752
auto* filterRow = new QHBoxLayout();
if (spec_.hasArrayTypeFilter) {
arrayCombo_ = new QComboBox(body_);
arrayCombo_->addItem(QStringLiteral("全部装置"), QString());
connect(arrayCombo_, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int) { rebuildList(); });
filterRow->addWidget(arrayCombo_);
}
filterRow->addWidget(new QLabel(QStringLiteral("采集"), body_));
dateRange_ = new DateRangeEdit(body_);
connect(dateRange_, &DateRangeEdit::rangeChanged, this, [this] { rebuildList(); });
filterRow->addWidget(dateRange_, 1);
body->addLayout(filterRow);
// 段体:可勾选数据树(勾选=渲染)。复用数据列表卡片委托与 populateDatasetList。
list_ = new QTreeWidget(body_);
list_->setHeaderHidden(true);
list_->setRootIsDecorated(true);
applyDatasetCardDelegate(list_);
connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { emitChecked(); });
connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) {
const QString id = it->data(0, kDsIdRole).toString();
if (id.isEmpty()) return;
emit detailRequested(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString());
});
list_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(list_, &QTreeWidget::customContextMenuRequested, this, &CategorySection::showContextMenu);
body->addWidget(list_, 1);
root->addWidget(body_, 1);
connect(header_, &QToolButton::toggled, this, [this](bool on) {
body_->setVisible(on);
header_->setArrowType(on ? Qt::DownArrow : Qt::RightArrow);
});
}
void CategorySection::setStructure(const std::vector<geopro::data::StructNode>& nodes) {
structure_ = nodes; // 容器分层(项目根/GS/TM→ds在 Task 12 接入真实结构后据此构建。
}
void CategorySection::setDatasets(const std::vector<DsRow>& rows) {
rows_ = rows;
refreshArrayCombo();
rebuildList();
}
void CategorySection::refreshArrayCombo() {
if (!spec_.hasArrayTypeFilter || !arrayCombo_) return;
const QString prev = arrayCombo_->currentData().toString();
const QSignalBlocker block(arrayCombo_);
arrayCombo_->clear();
arrayCombo_->addItem(QStringLiteral("全部装置"), QString());
if (dict_) {
const auto& en = dict_->arrayTypeEnum(); // 全局装置枚举 itemValue→中文
QSet<QString> seen;
// 列出当前数据里出现过、且命中枚举的装置值 → 中文名。
for (const auto& r : rows_) {
for (const auto& kv : r.properties) {
const auto it = en.find(kv.value);
if (it == en.end()) continue; // 非装置类型值
const QString val = QString::fromStdString(kv.value);
if (seen.contains(val)) continue;
seen.insert(val);
arrayCombo_->addItem(QString::fromStdString(it->second), val);
}
}
}
const int idx = arrayCombo_->findData(prev); // 尽量保留上次选择
arrayCombo_->setCurrentIndex(idx >= 0 ? idx : 0);
}
bool CategorySection::passesFilters(const DsRow& row) const {
// 装置类型筛选(全局枚举;"全部"=空不筛ds.properties 任一 value 命中选中 itemValue。
if (spec_.hasArrayTypeFilter && arrayCombo_) {
const QString sel = arrayCombo_->currentData().toString();
if (!sel.isEmpty()) {
bool hit = false;
for (const auto& kv : row.properties)
if (QString::fromStdString(kv.value) == sel) { hit = true; break; }
if (!hit) return false;
}
}
// 采集时间范围collectTime 经 dict 取;缺则回退 createTime空范围不约束
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;
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"));
if (d.isValid()) {
if (from.isValid() && d < from) return false;
if (to.isValid() && d > to) return false;
}
}
return true;
}
void CategorySection::rebuildList() {
std::vector<DsRow> filtered;
filtered.reserve(rows_.size());
for (const auto& r : rows_)
if (passesFilters(r)) filtered.push_back(r);
{
const QSignalBlocker block(list_);
populateDatasetList(list_, filtered, /*append=*/false);
for (QTreeWidgetItemIterator it(list_); *it; ++it) {
// 容器节点(项目根/GS/TM只作层级骨架不可勾选。
if ((*it)->data(0, kDsDdCodeRole).toString() == QStringLiteral("container")) continue;
(*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);
(*it)->setCheckState(0, Qt::Unchecked);
}
}
list_->expandAll(); // 展开容器层级(项目根/GS/TM让体/切片/异常可见
emitChecked(); // 重建后必为空选,清掉上次渲染勾选
}
QStringList CategorySection::checkedDsIds() const {
QStringList ids;
for (QTreeWidgetItemIterator it(list_); *it; ++it)
if ((*it)->checkState(0) == Qt::Checked) ids << (*it)->data(0, kDsIdRole).toString();
return ids;
}
void CategorySection::emitChecked() { emit checkedDatasetsChanged(checkedDsIds()); }
void CategorySection::showContextMenu(const QPoint& pos) {
QTreeWidgetItem* it = list_->itemAt(pos);
if (!it) return;
const QString id = it->data(0, kDsIdRole).toString();
const QString ddCode = it->data(0, kDsDdCodeRole).toString();
if (id.isEmpty() || ddCode == QStringLiteral("container")) return; // 容器节点无操作菜单
const QString name = it->data(0, kDsNameRole).toString();
QMenu menu(this);
menu.addAction(QStringLiteral("详情"), this,
[this, id, ddCode, name] { emit detailRequested(id, ddCode, name); });
// 切片 / 异常可删除(三维体段无 deleteVolume 端点)。
if (ddCode == QStringLiteral("dd_slice") || ddCode == QStringLiteral("dd_anomaly")) {
menu.addAction(QStringLiteral("删除"), this,
[this, id, ddCode] { emit deleteDatasetRequested(id, ddCode); });
}
menu.exec(list_->viewport()->mapToGlobal(pos));
}
} // namespace geopro::app