diff --git a/src/app/main.cpp b/src/app/main.cpp index bbafbf4..0d557bc 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -80,10 +80,13 @@ #include "CentralScene.hpp" #include "ProjectListDialog.hpp" #include "WorkbenchNavController.hpp" +#include "DatasetDetailController.hpp" #include "api/ApiProjectRepository.hpp" +#include "api/ApiDatasetRepository.hpp" #include "panels/ObjectTreePanel.hpp" #include "login/LoginWindow.hpp" #include "panels/DatasetListPanel.hpp" +#include "panels/DatasetDetailPanel.hpp" #include "panels/DynamicFormView.hpp" #include "panels/ObjectExceptionPanel.hpp" @@ -155,44 +158,6 @@ private: QWidget* host_; }; -// 相机补间 + actor 淡入:从 from 位姿平滑过渡到 to 位姿,同时 actors 透明度 0→1。 -// vtkCameraInterpolator 两关键帧线性插值(缓动交给 QEasingCurve),单条 QVariantAnimation -// 逐帧驱动并 Render;结束回调锁定到目标态(防插值末值误差/残留半透明)。 -// 渐进增强:动效只是过渡,最终一帧永远是正确的目标态,故即使观感不佳也不破坏功能。 -void animateReveal(vtkRenderer* renderer, vtkGenericOpenGLRenderWindow* rw, - vtkSmartPointer fromCam, vtkSmartPointer toCam, - std::vector> actors, int durationMs, QObject* owner) -{ - auto interp = vtkSmartPointer::New(); - interp->SetInterpolationTypeToLinear(); - interp->AddCamera(0.0, fromCam); - interp->AddCamera(1.0, toCam); - - auto* anim = new QVariantAnimation(owner); - anim->setDuration(durationMs); - anim->setStartValue(0.0); - anim->setEndValue(1.0); - anim->setEasingCurve(QEasingCurve::OutCubic); - QObject::connect(anim, &QVariantAnimation::valueChanged, owner, - [interp, renderer, rw, actors](const QVariant& v) { - const double t = v.toDouble(); - interp->InterpolateCamera(t, renderer->GetActiveCamera()); - for (const auto& a : actors) - if (a) a->GetProperty()->SetOpacity(t); - renderer->ResetCameraClippingRange(); - rw->Render(); - }); - QObject::connect(anim, &QVariantAnimation::finished, owner, - [renderer, rw, actors, toCam]() { - renderer->GetActiveCamera()->DeepCopy(toCam); - for (const auto& a : actors) - if (a) a->GetProperty()->SetOpacity(1.0); - renderer->ResetCameraClippingRange(); - rw->Render(); - }); - anim->start(QAbstractAnimation::DeleteWhenStopped); -} - // 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。 std::string readPem(const std::string& path) { @@ -215,12 +180,6 @@ double median(std::vector v) // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 using geopro::app::ViewMode; -// 数据详情显示内容(默认网格数据)。网格数据=#18 banded;原数据=#17 散点(对齐原型命名)。 -enum class DetailMode { Section18, Scatter17 }; - -// #17 散点屏幕像素方块边长。 -constexpr float kScatterPointSize = 4.0F; - // 纵向夸张倍数(Z 基准统一,M-3):全项目共用同一倍数,使 帘面(z) / 体素 / 切片 / // 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。 // 单一可调常量:要整体调纵向观感改这一处即可。 @@ -234,7 +193,8 @@ constexpr const char* kWgs84 = "EPSG:4326"; // repo 生命周期须覆盖到事件循环结束(由调用方保证)。 void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo, geopro::data::IProjectRepository& projectRepo, - geopro::controller::WorkbenchNavController& nav) + geopro::controller::WorkbenchNavController& nav, + geopro::controller::DatasetDetailController& detailCtrl) { // ── 世界系:启动取一次 grid1 的 lat/lon,用中位数作 GeoLocalFrame 原点 ── // 全项目共享(shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。 @@ -322,23 +282,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re centerLayout->setContentsMargins(0, 0, 0, 0); centerLayout->setSpacing(0); - // 分段工具条按钮样式(QToolButton + 主题化 QSS):选中=强调色文字 + 强调色下划线,明暗都清晰。 - // ElaToolButton 选中只画极淡 BasicHover、且不可经 QSS 改,故这类需清晰选中态的用 QToolButton。 - const QString kBarBtnQss = - QStringLiteral( - "QToolButton{ border:none; border-radius:6px; padding:6px 12px; color:{{text/primary}};" - " font-size:%1px; }" - "QToolButton:hover{ background:{{bg/hover}}; }" - "QToolButton:checked{ color:{{accent/primary}}; font-weight:%2;" - " border-bottom:2px solid {{accent/primary}}; }" - "QToolButton#dataTab{ border:none; border-radius:0; background:transparent;" - " border-bottom:2px solid transparent; color:{{text/secondary}}; padding:8px 8px; }" - "QToolButton#dataTab:hover{ color:{{text/primary}}; background:transparent; }" - "QToolButton#dataTab:checked{ color:{{accent/primary}}; font-weight:%2;" - " border-bottom:2px solid {{accent/primary}}; }") - .arg(geopro::app::scaledPx(geopro::app::type::kBody)) - .arg(geopro::app::type::kWeightSemibold); - // 「二维地图/三维视图」分段切换表头:与「异常/属性」面板表头同款(42px 表头底 + 强调色下划线页签)。 auto seg = geopro::app::buildSegmentedHeader( {QStringLiteral("二维地图"), QStringLiteral("三维视图")}, @@ -448,77 +391,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re vtkDock->setWidget(centerWidget); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); - // ── 下方「数据详情」dock:独立 QVTK 小视图(独立 renderer/renderWindow)── - // 单击 DS → 显示该数据集平面反演剖面(#18 banded,平躺俯视正交)。 - auto* detailWidget = new QVTKOpenGLStereoWidget(); - vtkNew detailRenderWindow; - vtkNew detailRenderer; - { - double r, g, b; - geopro::app::vtkBackground(r, g, b); // 背景随主题 - detailRenderer->SetBackground(r, g, b); - } - detailWidget->setRenderWindow(detailRenderWindow); - detailRenderWindow->AddRenderer(detailRenderer); - vtkRenderer* detailRendererPtr = detailRenderer.Get(); - vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get(); - // 注:VTK 背景随主题切换的连接放在 rebuildCentral/rebuildDetail 定义之后(直接重跑它们, - // 走完整渲染路径必重绘,比手动 SetBackground+Render 稳)。 - - // 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。 - auto* detailContainer = new QWidget(); - auto* detailLayout = new QVBoxLayout(detailContainer); - detailLayout->setContentsMargins(0, 0, 0, 0); - detailLayout->setSpacing(0); - - // 工具条对齐原型:「原数据 | 网格数据」互斥 +「显示异常/电极/等值线」开关。 - // QToolButton + 主题化 QSS(选中=强调色文字+下划线,明暗都清晰)。 - auto* detailToolBar = new QWidget(); - auto* detailBarLay = new QHBoxLayout(detailToolBar); - detailBarLay->setContentsMargins(8, 6, 8, 6); - detailBarLay->setSpacing(6); - auto makeBarBtn = [detailToolBar](const QString& text, bool checkable) { - auto* b = new QToolButton(detailToolBar); - b->setText(text); - b->setCheckable(checkable); - return b; - }; - auto* detailGroup = new QButtonGroup(detailToolBar); - detailGroup->setExclusive(true); - auto* actScatter = makeBarBtn(QStringLiteral("原数据"), true); - auto* actSection = makeBarBtn(QStringLiteral("网格数据"), true); - actScatter->setObjectName(QStringLiteral("dataTab")); - actSection->setObjectName(QStringLiteral("dataTab")); - detailGroup->addButton(actScatter); - detailGroup->addButton(actSection); - detailBarLay->addWidget(actScatter); - detailBarLay->addWidget(actSection); - actSection->setChecked(true); // 默认网格数据 (#18) - auto* barSep = new QFrame(detailToolBar); - barSep->setFrameShape(QFrame::VLine); - barSep->setObjectName(QStringLiteral("topDivider")); - detailBarLay->addSpacing(4); - detailBarLay->addWidget(barSep); - detailBarLay->addSpacing(4); - auto* actShowAnomaly = makeBarBtn(QStringLiteral("显示异常"), true); - actShowAnomaly->setChecked(true); // 默认显示异常(对齐原型 ☑显示异常) - auto* actShowElectrodes = makeBarBtn(QStringLiteral("显示电极"), true); - actShowElectrodes->setChecked(true); // 默认显示电极 ▼(对齐原型) - auto* actShowContour = makeBarBtn(QStringLiteral("显示等值线"), true); - actShowContour->setChecked(true); // 默认显示等值线(对齐原型) - detailBarLay->addWidget(actShowAnomaly); - detailBarLay->addWidget(actShowElectrodes); - detailBarLay->addWidget(actShowContour); - detailBarLay->addStretch(); - geopro::app::applyTokenizedStyleSheet(detailToolBar, kBarBtnQss); - detailLayout->addWidget(detailToolBar); - detailLayout->addWidget(detailWidget, 1); - + // ── 下方「数据详情」dock:平面图表多 Tab 面板(QGraphicsView,VTK 仅算几何)── + // 单击数据集 → 聚焦已开页;双击 → 新建/聚焦页(真实反演剖面/散点/异常/色阶)。 + auto* detailPanel = new geopro::app::DatasetDetailPanel(); auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情")); detailDock->setWidget(wrapWithHeader( - geopro::app::Glyph::Detail, QStringLiteral("数据详情"), detailContainer, - {{geopro::app::Glyph::Collapse, QStringLiteral("折叠")}, - {geopro::app::Glyph::Download, QStringLiteral("导出")}})); + geopro::app::Glyph::Detail, QStringLiteral("数据详情"), detailPanel)); // 放在中央视图下方。 dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea); @@ -599,146 +477,45 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re *frame, kVerticalExaggeration); }; - // ── 数据详情共享状态 + 重建 ────────────────────────────────────────── - // 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 - auto currentDsId = std::make_shared(); - auto detailMode = std::make_shared(DetailMode::Section18); - auto showAnomalies = std::make_shared(true); // 默认显示异常(对齐原型) - auto showElectrodes = std::make_shared(true); // 默认显示电极 ▼ - auto showContour = std::make_shared(true); // 默认显示等值线 - auto hiddenAnoms = std::make_shared>(); // 异常列表中被取消勾选(隐藏)的异常下标 - auto prevDsId = std::make_shared(); // 上次渲染的 DS id:判定“切换数据集”以触发揭示过渡 - - // 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。 - // 勾选「显示异常/电极/等值线」控制对应叠加(同纵向夸张对齐)。 - // overdrive(A):仅“切换数据集”这一加载时刻播放相机补间 + actor 淡入揭示;模式/叠加层开关 - // 属同一数据集内微调,直接落定不放动画(特殊时刻才特殊,避免每次交互都动的疲劳)。 - auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, detailWidget, currentDsId, - prevDsId, detailMode, showAnomalies, showElectrodes, showContour, - hiddenAnoms]() { - const bool dsChanged = (*currentDsId != *prevDsId); - const bool animate = dsChanged && !prevDsId->isEmpty() && !currentDsId->isEmpty(); - - // 过渡起点:清场景前先快照当前相机位姿。 - auto fromCam = vtkSmartPointer::New(); - fromCam->DeepCopy(detailRendererPtr->GetActiveCamera()); - - detailRendererPtr->RemoveAllViewProps(); - { // 背景随主题 - double r, g, b; - geopro::app::vtkBackground(r, g, b); - detailRendererPtr->SetBackground(r, g, b); - } - if (currentDsId->isEmpty()) { // 未选数据集:清空即可 - *prevDsId = *currentDsId; - detailRenderWindowPtr->Render(); - return; - } - std::vector> added; // 本次加入的 actor,供淡入 - const std::string id = currentDsId->toStdString(); - if (*detailMode == DetailMode::Section18) { - // 网格数据:#18 banded 等值面(+「显示等值线」时叠黑色等值线),纵向夸张 1.5x(沿 y)。 - const auto g = repo.loadGrid(id); - const auto cs = repo.loadColorScale(id); - const auto actors = geopro::render::buildGridContour(g, cs); - if (actors.bands) { - actors.bands->SetScale(1.0, kVerticalExaggeration, 1.0); - detailRendererPtr->AddViewProp(actors.bands); - added.push_back(actors.bands); - } - if (actors.edges && *showContour) { - actors.edges->SetScale(1.0, kVerticalExaggeration, 1.0); - detailRendererPtr->AddViewProp(actors.edges); - added.push_back(actors.edges); - } - // 顶部电极标记 ▼(仅网格数据;同纵向夸张对齐)。 - if (*showElectrodes) { - auto elec = geopro::render::buildElectrodes(g); - if (elec) { - elec->SetScale(1.0, kVerticalExaggeration, 1.0); - detailRendererPtr->AddViewProp(elec); - added.push_back(elec); - } - } - } else { - // 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。 - const auto s = repo.loadScatter(id); - const auto scs = repo.loadScatterColorScale(id); - auto a = geopro::render::buildScatter(s, scs, kScatterPointSize); - if (a) { - a->SetScale(1.0, kVerticalExaggeration, 1.0); - detailRendererPtr->AddViewProp(a); - added.push_back(a); - } - } - // 异常叠加(与剖面同坐标系/同纵向夸张)。逐异常构建以按列表显隐(下标=原 vector 序)过滤。 - if (*showAnomalies) { - const auto anomalies = repo.loadAnomalies(id); - for (int i = 0; i < static_cast(anomalies.size()); ++i) { - if (hiddenAnoms->count(i)) continue; // 列表中取消勾选→隐藏 - for (auto& act : geopro::render::buildAnomalies({anomalies[i]})) { - act->SetScale(1.0, kVerticalExaggeration, 1.0); - detailRendererPtr->AddViewProp(act); - added.push_back(act); - } - } - } - geopro::render::applyTop2D(detailRendererPtr); - detailRendererPtr->ResetCamera(); - *prevDsId = *currentDsId; - - if (animate) { - // 目标位姿快照 → 相机回退到旧位姿 + actors 透明 → 补间到目标并淡入。 - auto toCam = vtkSmartPointer::New(); - toCam->DeepCopy(detailRendererPtr->GetActiveCamera()); - for (const auto& a : added) a->GetProperty()->SetOpacity(0.0); - detailRendererPtr->GetActiveCamera()->DeepCopy(fromCam); - detailRendererPtr->ResetCameraClippingRange(); - animateReveal(detailRendererPtr, detailRenderWindowPtr, fromCam, toCam, added, 450, - detailWidget); - } else { - detailRenderWindowPtr->Render(); - } - }; - - // ── 单击左下数据列表的采集批次(DS) → 加载数据集动态表单(数据集属性面板)── + // ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ── QObject::connect(datasetList, &QListWidget::itemClicked, datasetList, - [&nav](QListWidgetItem* item) { + [&nav, &detailCtrl](QListWidgetItem* item) { if (item->data(geopro::app::kDsLoadMoreRole).toBool()) { nav.loadMoreData(); return; } const QString dsId = item->data(geopro::app::kDsIdRole).toString(); - if (!dsId.isEmpty()) nav.selectDataset(dsId); + if (dsId.isEmpty()) return; + nav.selectDataset(dsId); // 属性表单(现状) + detailCtrl.focusDataset(dsId); // 单击=聚焦已开页 }); - // ── 数据详情工具条「反演剖面/原数据」:切模式 → 重建数据详情 ── - QObject::connect(actSection, &QAbstractButton::clicked, detailWidget, - [detailMode, rebuildDetail]() { - *detailMode = DetailMode::Section18; - rebuildDetail(); - }); - QObject::connect(actScatter, &QAbstractButton::clicked, detailWidget, - [detailMode, rebuildDetail]() { - *detailMode = DetailMode::Scatter17; - rebuildDetail(); + // ── 双击 → 打开/聚焦该数据集的详情图表页(拉真实反演剖面/散点/异常/色阶)── + QObject::connect(datasetList, &QListWidget::itemDoubleClicked, datasetList, + [&detailCtrl](QListWidgetItem* item) { + const QString dsId = item->data(geopro::app::kDsIdRole).toString(); + const QString ddCode = item->data(geopro::app::kDsDdCodeRole).toString(); + if (!dsId.isEmpty()) detailCtrl.openDataset(dsId, ddCode); }); - // ──「显示异常 / 显示电极 / 显示等值线」开关:切换叠加 → 重建数据详情 ── - QObject::connect(actShowAnomaly, &QAbstractButton::toggled, detailWidget, - [showAnomalies, rebuildDetail](bool on) { - *showAnomalies = on; - rebuildDetail(); + // ── 控制器信号 → 详情面板:数据就绪开页 / 聚焦请求 ── + QObject::connect( + &detailCtrl, &geopro::controller::DatasetDetailController::chartReady, detailPanel, + [detailPanel](const geopro::controller::DatasetDetailController::ChartData& d) { + detailPanel->openOrUpdate(d); + }); + QObject::connect(&detailCtrl, &geopro::controller::DatasetDetailController::focusRequested, + detailPanel, [detailPanel](const QString& dsId) { + detailPanel->focusDataset(dsId); }); - QObject::connect(actShowElectrodes, &QAbstractButton::toggled, detailWidget, - [showElectrodes, rebuildDetail](bool on) { - *showElectrodes = on; - rebuildDetail(); - }); - QObject::connect(actShowContour, &QAbstractButton::toggled, detailWidget, - [showContour, rebuildDetail](bool on) { - *showContour = on; - rebuildDetail(); + + // ── 详情面板切 Tab → 反向高亮数据集列表对应行 ── + QObject::connect(detailPanel, &geopro::app::DatasetDetailPanel::activeDatasetChanged, + datasetList, [datasetList](const QString& dsId) { + for (int i = 0; i < datasetList->count(); ++i) + if (datasetList->item(i)->data(geopro::app::kDsIdRole).toString() == + dsId) + datasetList->setCurrentRow(i); }); // 「视图详情」浮层显隐:仅三维显示,置于 QVTK 左上(工具条下方)并置顶。 @@ -792,12 +569,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。 rebuildCentral(); - // VTK 背景随主题切换:直接重跑 rebuildCentral/rebuildDetail(走完整渲染路径、末尾必 Render, + // VTK 背景随主题切换:直接重跑 rebuildCentral(走完整渲染路径、末尾必 Render, // 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。 QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window, - [rebuildCentral, rebuildDetail]() { + [rebuildCentral]() { rebuildCentral(); - rebuildDetail(); }); // 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备), @@ -1050,6 +826,10 @@ int main(int argc, char* argv[]) geopro::data::ApiProjectRepository projectRepo(api); geopro::controller::WorkbenchNavController nav(projectRepo); + // 数据详情仓储 + 控制器(接真实反演 API):同一共享会话 ApiClient。 + geopro::data::ApiDatasetRepository datasetRepo(api); + geopro::controller::DatasetDetailController detailCtrl(datasetRepo); + // ── 外壳:标准 QMainWindow(原生标题栏)。buildWorkbench 直接用其 // setCentralWidget/setMenuWidget/statusBar 承载工作台。 const QString kTitle = QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"); @@ -1057,7 +837,7 @@ int main(int argc, char* argv[]) window->setWindowTitle(kTitle); window->resize(1280, 800); window->setMinimumSize(1024, 680); - buildWorkbench(*window, repo, projectRepo, nav); + buildWorkbench(*window, repo, projectRepo, nav, detailCtrl); // 主题桥:ThemeManager 明/暗切换 → 重应用全局 QSS+调色板(标准控件 + ADS;内联 chrome 经各自连接)。 QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, diff --git a/src/app/panels/DatasetListPanel.cpp b/src/app/panels/DatasetListPanel.cpp index 4856a85..8faf540 100644 --- a/src/app/panels/DatasetListPanel.cpp +++ b/src/app/panels/DatasetListPanel.cpp @@ -116,6 +116,7 @@ void populateDatasetList(QListWidget* list, const std::vectorsetData(kDsIdRole, QString::fromStdString(d.id)); item->setData(kDsDdTypeRole, QString::fromStdString(d.ddCode)); + item->setData(kDsDdCodeRole, QString::fromStdString(d.ddCode)); } } diff --git a/src/app/panels/DatasetListPanel.hpp b/src/app/panels/DatasetListPanel.hpp index 8ac2887..d6b7f9f 100644 --- a/src/app/panels/DatasetListPanel.hpp +++ b/src/app/panels/DatasetListPanel.hpp @@ -12,6 +12,7 @@ constexpr int kDsIdRole = 0x0100; // Qt::UserRole constexpr int kDsDdTypeRole = 0x0101; // Qt::UserRole + 1 constexpr int kDsFileUrlRole = 0x0102; // Qt::UserRole + 2(文件下载 url,备用) constexpr int kDsLoadMoreRole = 0x0103; // 标记"加载更多"行 +constexpr int kDsDdCodeRole = 0x0104; // Qt::UserRole + 4(ddCode,双击详情选策略用) // 数据页签:每条 = dsName +(类型名);UserRole 存 dsId、+1 存 ddCode。 void populateDatasetList(QListWidget* list, const std::vector& rows, bool append);