diff --git a/src/app/AnomalyPropertiesDialog.cpp b/src/app/AnomalyPropertiesDialog.cpp new file mode 100644 index 0000000..9d18a87 --- /dev/null +++ b/src/app/AnomalyPropertiesDialog.cpp @@ -0,0 +1,74 @@ +#include "AnomalyPropertiesDialog.hpp" + +#include +#include +#include +#include +#include + +namespace geopro::app { + +namespace { +QString markTypeLabel(geopro::core::AnomalyMarkType t) { + switch (t) { + case geopro::core::AnomalyMarkType::Point: return QStringLiteral("点"); + case geopro::core::AnomalyMarkType::Polyline: return QStringLiteral("折线"); + case geopro::core::AnomalyMarkType::Polygon: return QStringLiteral("多边形"); + } + return QStringLiteral("—"); +} + +QString orDash(const std::string& s) { + return s.empty() ? QStringLiteral("—") : QString::fromStdString(s); +} +} // namespace + +AnomalyPropertiesDialog::AnomalyPropertiesDialog(const geopro::core::Anomaly& a, QWidget* parent) + : QDialog(parent) { + setWindowTitle(QStringLiteral("异常属性")); + setModal(true); + + auto* root = new QVBoxLayout(this); + + auto* form = new QFormLayout(); + form->addRow(QStringLiteral("名称"), new QLabel(orDash(a.name))); + form->addRow(QStringLiteral("类型"), new QLabel(orDash(a.typeName))); + form->addRow(QStringLiteral("标记类型"), new QLabel(markTypeLabel(a.markType))); + form->addRow(QStringLiteral("归属三维体"), new QLabel(orDash(a.volumeDsId))); + form->addRow(QStringLiteral("异常体"), + new QLabel(a.consortiumId.empty() ? QStringLiteral("(未分组)") + : QString::fromStdString(a.consortiumId))); + root->addLayout(form); + + // 顶点世界坐标(只读列表,x/y/z 每行一个点)。 + root->addWidget(new QLabel(QStringLiteral("顶点坐标(%1 个)").arg(a.worldPts.size()))); + auto* pts = new QPlainTextEdit(); + pts->setReadOnly(true); + pts->setFixedHeight(120); + QString text; + for (std::size_t i = 0; i < a.worldPts.size(); ++i) { + const auto& p = a.worldPts[i]; + text += QStringLiteral("%1: (%2, %3, %4)\n") + .arg(i + 1) + .arg(p.x, 0, 'f', 2) + .arg(p.y, 0, 'f', 2) + .arg(p.z, 0, 'f', 2); + } + pts->setPlainText(text); + root->addWidget(pts); + + // 备注(只读)。 + root->addWidget(new QLabel(QStringLiteral("备注"))); + auto* remark = new QPlainTextEdit(); + remark->setReadOnly(true); + remark->setFixedHeight(60); + remark->setPlainText(QString::fromStdString(a.remark)); + root->addWidget(remark); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + root->addWidget(buttons); +} + +} // namespace geopro::app diff --git a/src/app/AnomalyPropertiesDialog.hpp b/src/app/AnomalyPropertiesDialog.hpp new file mode 100644 index 0000000..4377700 --- /dev/null +++ b/src/app/AnomalyPropertiesDialog.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +#include "model/Anomaly.hpp" + +namespace geopro::app { + +// 异常属性对话框(#4c-3,需求 R83):双击异常列表项弹出,只读展示选中异常的 +// 名称/类型/标记类型/备注/归属三维体/异常体分组/顶点世界坐标。 +// 截图:模型与异常端点均无截图字段(保存对话框的截图仅为 mock 预览、未持久化),故不展示。 +class AnomalyPropertiesDialog : public QDialog { + Q_OBJECT +public: + AnomalyPropertiesDialog(const geopro::core::Anomaly& a, QWidget* parent = nullptr); +}; + +} // namespace geopro::app diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index b181dd7..1b16a6f 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(geopro_desktop WIN32 ImportDatasetDialog.cpp ExportDatasetDialog.cpp AnomalySaveDialog.cpp + AnomalyPropertiesDialog.cpp SettingsDialog.cpp SliceExport.cpp VolumeParamsDialog.cpp diff --git a/src/app/main.cpp b/src/app/main.cpp index 75aa2fb..a240158 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -98,6 +98,7 @@ #include "PanelHeader.hpp" #include "Theme.hpp" #include "AnomalySaveDialog.hpp" +#include "AnomalyPropertiesDialog.hpp" #include "SettingsDialog.hpp" #include "SliceExport.hpp" #include "TopBar.hpp" @@ -785,6 +786,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re [sceneView](const QString& id) { sceneView->setSelectedAnomaly(id.toStdString()); }); + // 双击异常 → 只读属性对话框(R83,名称/类型/标记/归属/坐标/备注)。 + QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyPropertiesRequested, &window, + [&window](const geopro::core::Anomaly& a) { + geopro::app::AnomalyPropertiesDialog dlg(a, &window); + dlg.exec(); + }); // 删除异常 → 删 mock + 刷新渲染/列表。 QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyDeleteRequested, &window, [scene3dRepo, refreshAnomalies](const QString& id) { diff --git a/src/app/panels/columns/Column3DAnalysis.cpp b/src/app/panels/columns/Column3DAnalysis.cpp index 01720ad..1fc4bb7 100644 --- a/src/app/panels/columns/Column3DAnalysis.cpp +++ b/src/app/panels/columns/Column3DAnalysis.cpp @@ -57,6 +57,17 @@ Column3DAnalysis::Column3DAnalysis(QWidget* parent) : QWidget(parent) { [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); @@ -92,6 +103,7 @@ int Column3DAnalysis::anomalyFilterMode() const { } void Column3DAnalysis::setAnomalies(const std::vector& anoms) { + anomalies_ = anoms; // 留存供双击查属性(R83) QSignalBlocker block(anomalyTree_); // 填充不触发 visibilityChanged anomalyTree_->clear(); for (const auto& a : anoms) { diff --git a/src/app/panels/columns/Column3DAnalysis.hpp b/src/app/panels/columns/Column3DAnalysis.hpp index 80285b7..e936ae7 100644 --- a/src/app/panels/columns/Column3DAnalysis.hpp +++ b/src/app/panels/columns/Column3DAnalysis.hpp @@ -41,6 +41,7 @@ signals: void anomalyDisplayFilterChanged(int mode); // 过滤档位 0..3 void anomalySelected(const QString& anomalyId); // 列表选中→VTK 高亮 void anomalyDeleteRequested(const QString& anomalyId); // 右键删除 + void anomalyPropertiesRequested(const geopro::core::Anomaly& a); // 双击→属性对话框(R83) private: void onContextMenu(const QPoint& pos); @@ -48,6 +49,7 @@ private: QTreeWidget* tree_ = nullptr; QTreeWidget* anomalyTree_ = nullptr; QComboBox* anomalyFilter_ = nullptr; + std::vector anomalies_; // 当前展示集合(双击查属性按 id 回查) }; } // namespace geopro::app