From 40c8d6ccb4506def77545d7f0fe0aefefcf51ea2 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 25 Jun 2026 19:54:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E5=88=9B=E5=BB=BA=E4=B8=89=E7=BB=B4?= =?UTF-8?q?=E4=BD=93=E5=AF=B9=E8=AF=9D=E6=A1=86=20=E5=B7=A6=E4=BE=A7?= =?UTF-8?q?=E6=BA=90/=E5=8F=B3=E4=BE=A7=E7=94=9F=E6=88=90=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=20=E6=94=B9=E6=A0=91=E5=9E=8B=E7=BB=93=E6=9E=84(4b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 左「已选择对象」:QListWidget 平铺 → QTreeWidget(项目/GS/TM 容器树 + 可勾选源 ds 挂各自结构归属下, 容器按源 ds 的 structParentId 向上剪枝) - 右「生成位置」:QComboBox 平铺 → QTreeWidget(GS/项目根/TM 层级单选);confType 据所选节点 type 推导 - ctor 改签名:传 VolumeSourceItem(含 structParentId)+ StructNode 结构 + defaultMountId;main 同步传 structParentId 构建:app 链接通过 --- src/app/VolumeParamsDialog.cpp | 141 ++++++++++++++++++++++++--------- src/app/VolumeParamsDialog.hpp | 30 +++---- src/app/main.cpp | 25 +++--- 3 files changed, 134 insertions(+), 62 deletions(-) diff --git a/src/app/VolumeParamsDialog.cpp b/src/app/VolumeParamsDialog.cpp index b0e5454..b3c9b82 100644 --- a/src/app/VolumeParamsDialog.cpp +++ b/src/app/VolumeParamsDialog.cpp @@ -1,17 +1,22 @@ #include "VolumeParamsDialog.hpp" -#include +#include +#include -#include "EmptyAwareComboBox.hpp" +#include #include #include #include +#include #include #include -#include #include +#include +#include +#include #include +#include "EmptyAwareComboBox.hpp" #include "FormKit.hpp" #include "Theme.hpp" @@ -23,13 +28,14 @@ constexpr double kDefCellXY = 1.0; constexpr double kDefCellZ = 0.5; constexpr double kDefPower = 2.0; constexpr double kDefMaxDist = 4.0; -constexpr int kRoleDsId = Qt::UserRole + 1; // 源列表项存 dsId -constexpr int kRoleMountId = Qt::UserRole + 1; // 生成位置项存 id(itemData 默认存 confType) +constexpr int kRoleDsId = Qt::UserRole + 1; // 源树项存 dsId +constexpr int kRoleMountId = Qt::UserRole + 1; // 生成位置树项存 id +constexpr int kRoleMountConfType = Qt::UserRole + 2; // 生成位置树项存 confType } // namespace -VolumeParamsDialog::VolumeParamsDialog(const QVector>& sources, - const QVector& mounts, - int defaultMountIndex, QWidget* parent) +VolumeParamsDialog::VolumeParamsDialog(const QVector& sources, + const std::vector& structure, + const QString& defaultMountId, QWidget* parent) : QDialog(parent) { setWindowTitle(QStringLiteral("生成三维体")); setModal(true); @@ -40,21 +46,58 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector>& s geopro::app::applyTokenizedStyleSheet(intro, QStringLiteral("color:{{text/secondary}};")); root->addWidget(intro); - // 左右两栏:左·源数据集列表(可勾选/取消,二次确认或修改) | 右·参数卡片。 auto* cols = new QHBoxLayout(); - // ── 左:源数据集列表 ── + // ── 左:源数据集树(项目/GS/TM 容器 + 可勾选源 ds,挂到各自结构归属下)── auto* leftCard = formkit::formCard(this); auto* leftLay = formkit::cardBody(leftCard); formkit::addSection(leftLay, QStringLiteral("源数据集"), leftCard, false); - sourceList_ = new QListWidget(); - for (const auto& s : sources) { - auto* it = new QListWidgetItem(s.second.isEmpty() ? s.first : s.second, sourceList_); - it->setData(kRoleDsId, s.first); - it->setFlags(it->flags() | Qt::ItemIsUserCheckable); - it->setCheckState(Qt::Checked); // 默认全勾(=传入的当前勾选源) + sourceTree_ = new QTreeWidget(); + sourceTree_->setHeaderHidden(true); + sourceTree_->setIndentation(14); + { + std::map byId; + for (const auto& n : structure) byId[n.id] = &n; + // 仅保留「源 ds 结构归属」向上的祖先链容器(树剪枝,不展示无关 GS/TM)。 + std::set keep; + for (const auto& s : sources) { + std::string p = s.structParentId.toStdString(); + while (!p.empty() && byId.count(p) && !keep.count(p)) { + keep.insert(p); + p = byId[p]->parentId; + } + } + QHash cont; // 容器 id → item + for (const auto& n : structure) + if (keep.count(n.id)) { + auto* it = new QTreeWidgetItem(); + it->setText(0, QString::fromStdString(n.name)); + cont.insert(QString::fromStdString(n.id), it); + } + for (const auto& n : structure) + if (keep.count(n.id)) { + auto* it = cont.value(QString::fromStdString(n.id)); + auto* par = cont.value(QString::fromStdString(n.parentId), nullptr); + if (par) + par->addChild(it); + else + sourceTree_->addTopLevelItem(it); + } + for (const auto& s : sources) { + auto* it = new QTreeWidgetItem(); + it->setText(0, s.name.isEmpty() ? s.id : s.name); + it->setData(0, kRoleDsId, s.id); + it->setFlags(it->flags() | Qt::ItemIsUserCheckable); + it->setCheckState(0, Qt::Checked); // 默认全勾(=传入的当前勾选源) + auto* par = cont.value(s.structParentId, nullptr); + if (par) + par->addChild(it); + else + sourceTree_->addTopLevelItem(it); + } } - leftLay->addWidget(sourceList_); + sourceTree_->expandAll(); + leftLay->addWidget(sourceTree_); cols->addWidget(leftCard, 1); // ── 右:参数 ── @@ -66,18 +109,40 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector>& s name_ = new QLineEdit(QStringLiteral("三维体")); formkit::capField(name_); form->addRow(formkit::editLabel(QStringLiteral("名称")), name_); + cardLay->addLayout(form); - // 生成位置下拉:项目内 GS/项目根/TM(itemData 存 confType,UserRole+1 存 id)。 - mount_ = new EmptyAwareComboBox(); - for (const auto& m : mounts) { - mount_->addItem(m.name, m.confType); - mount_->setItemData(mount_->count() - 1, m.id, kRoleMountId); + // 生成位置:GS/项目根/TM 层级树(单选),替代原扁平下拉。 + formkit::addSection(cardLay, QStringLiteral("生成位置"), card, false); + mountTree_ = new QTreeWidget(); + mountTree_->setHeaderHidden(true); + mountTree_->setIndentation(14); + mountTree_->setMaximumHeight(geopro::app::scaledPx(160)); + { + QHash mItems; + for (const auto& n : structure) { + auto* it = new QTreeWidgetItem(); + it->setText(0, QString::fromStdString(n.name)); + it->setData(0, kRoleMountId, QString::fromStdString(n.id)); + it->setData(0, kRoleMountConfType, n.type == 2 ? 2 : 1); + mItems.insert(QString::fromStdString(n.id), it); + } + for (const auto& n : structure) { + auto* it = mItems.value(QString::fromStdString(n.id)); + auto* par = mItems.value(QString::fromStdString(n.parentId), nullptr); + if (par) + par->addChild(it); + else + mountTree_->addTopLevelItem(it); + } + mountTree_->expandAll(); + // 默认选中:指定的 defaultMountId,否则首个节点。 + QTreeWidgetItem* def = mItems.value(defaultMountId, nullptr); + if (!def && mountTree_->topLevelItemCount() > 0) def = mountTree_->topLevelItem(0); + if (def) mountTree_->setCurrentItem(def); } - if (defaultMountIndex >= 0 && defaultMountIndex < mounts.size()) - mount_->setCurrentIndex(defaultMountIndex); - formkit::capField(mount_); - form->addRow(formkit::editLabel(QStringLiteral("生成位置")), mount_); + cardLay->addWidget(mountTree_); + auto* form2 = formkit::makeEditForm(); model_ = new EmptyAwareComboBox(); model_->addItem(QStringLiteral("反距离加权 (IDW)"), static_cast(geopro::data::VolumeBuildParams::Model::Idw)); @@ -88,7 +153,7 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector>& s } model_->setCurrentIndex(0); formkit::capField(model_); - form->addRow(formkit::editLabel(QStringLiteral("插值模型")), model_); + form2->addRow(formkit::editLabel(QStringLiteral("插值模型")), model_); auto makeSpin = [this](double val, double min, double max, double step, int decimals) { auto* s = new QDoubleSpinBox(); @@ -103,12 +168,12 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector>& s cellZ_ = makeSpin(kDefCellZ, 0.01, 1000.0, 0.5, 2); power_ = makeSpin(kDefPower, 0.5, 6.0, 0.5, 1); maxDist_ = makeSpin(kDefMaxDist, 0.1, 10000.0, 1.0, 2); - form->addRow(formkit::editLabel(QStringLiteral("水平间距 (米)")), cellXY_); - form->addRow(formkit::editLabel(QStringLiteral("竖向间距 (米)")), cellZ_); - form->addRow(formkit::editLabel(QStringLiteral("IDW 幂次")), power_); - form->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_); + form2->addRow(formkit::editLabel(QStringLiteral("水平间距 (米)")), cellXY_); + form2->addRow(formkit::editLabel(QStringLiteral("竖向间距 (米)")), cellZ_); + form2->addRow(formkit::editLabel(QStringLiteral("IDW 幂次")), power_); + form2->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_); + cardLay->addLayout(form2); - cardLay->addLayout(form); cols->addWidget(card, 1); root->addLayout(cols); @@ -132,19 +197,21 @@ geopro::data::VolumeBuildParams VolumeParamsDialog::params() const { QStringList VolumeParamsDialog::sourceDatasetIds() const { QStringList ids; - for (int i = 0; i < sourceList_->count(); ++i) { - QListWidgetItem* it = sourceList_->item(i); - if (it->checkState() == Qt::Checked) ids << it->data(kRoleDsId).toString(); + for (QTreeWidgetItemIterator it(sourceTree_); *it; ++it) { + const QString id = (*it)->data(0, kRoleDsId).toString(); + if (!id.isEmpty() && (*it)->checkState(0) == Qt::Checked) ids << id; } return ids; } QString VolumeParamsDialog::mountTargetId() const { - return mount_->currentData(kRoleMountId).toString(); + auto* it = mountTree_->currentItem(); + return it ? it->data(0, kRoleMountId).toString() : QString(); } int VolumeParamsDialog::mountConfType() const { - const int ct = mount_->currentData().toInt(); + auto* it = mountTree_->currentItem(); + const int ct = it ? it->data(0, kRoleMountConfType).toInt() : 0; return ct == 0 ? 1 : ct; // 缺省按 GS/项目根 } diff --git a/src/app/VolumeParamsDialog.hpp b/src/app/VolumeParamsDialog.hpp index ce41469..dccafbb 100644 --- a/src/app/VolumeParamsDialog.hpp +++ b/src/app/VolumeParamsDialog.hpp @@ -1,51 +1,53 @@ #pragma once #include -#include #include #include #include +#include +#include "repo/RepoTypes.hpp" // StructNode #include "repo/VolumeBuildParams.hpp" class QLineEdit; class QComboBox; class QDoubleSpinBox; -class QListWidget; +class QTreeWidget; namespace geopro::app { -// 生成位置候选(spec §7/§8):项目内 GS/项目根/TM。confType:1=GS/项目根 2=TM。 -struct VolumeMountTarget { +// 源数据集(参与生成):id + 显示名 + 结构归属(structParentId,用于在左侧树里挂到其 GS/TM 下)。 +struct VolumeSourceItem { QString id; QString name; - int confType = 1; + QString structParentId; }; -// 「生成三维体」对话框(spec §7/§8):左侧·源数据集列表(可勾选/取消,二次确认或修改); -// 右侧·参数(名称 + 生成位置下拉 + 插值模型 + cellXY/cellZ/power/maxDist)。 +// 「生成三维体」对话框(spec §7/§8):左侧·源数据集**树**(项目/GS/TM/ds,可勾选/取消); +// 右侧·参数(名称 + 生成位置**树**[GS/项目根/TM 层级单选] + 插值模型 + cellXY/cellZ/power/maxDist)。 class VolumeParamsDialog : public QDialog { Q_OBJECT public: - // sources:参与生成的源数据集 (dsId, 显示名);mounts:生成位置候选;defaultMountIndex:默认选中位置下标。 - VolumeParamsDialog(const QVector>& sources, - const QVector& mounts, int defaultMountIndex, - QWidget* parent = nullptr); + // sources:参与生成的源 ds(含结构归属);structure:项目结构(GS/TM),左右两树共用; + // defaultMountId:默认选中的生成位置 id(空=选首个)。 + VolumeParamsDialog(const QVector& sources, + const std::vector& structure, + const QString& defaultMountId, QWidget* parent = nullptr); QString volumeName() const; geopro::data::VolumeBuildParams params() const; - QStringList sourceDatasetIds() const; // 左侧最终勾选的源 ds id + QStringList sourceDatasetIds() const; // 左侧树最终勾选的源 ds id QString mountTargetId() const; // 生成位置 id(structParentId) int mountConfType() const; // 生成位置 confType(1=GS/项目根 2=TM) private: QLineEdit* name_ = nullptr; - QComboBox* mount_ = nullptr; QComboBox* model_ = nullptr; QDoubleSpinBox* cellXY_ = nullptr; QDoubleSpinBox* cellZ_ = nullptr; QDoubleSpinBox* power_ = nullptr; QDoubleSpinBox* maxDist_ = nullptr; - QListWidget* sourceList_ = nullptr; + QTreeWidget* sourceTree_ = nullptr; // 左:源数据集树(可勾选) + QTreeWidget* mountTree_ = nullptr; // 右:生成位置树(单选) }; } // namespace geopro::app diff --git a/src/app/main.cpp b/src/app/main.cpp index 723370e..e7314a5 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -718,20 +718,23 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re [&window, &nav, scene3dRepo, refreshAnalysis, lastSourceRows, lastStructNodes](const QString& /*dsTypeCode*/, const QStringList& sourceIds) { if (sourceIds.isEmpty()) return; - // 源 ds (id,名称):名称从最近拉取的行查(缺则用 id)。 - QVector> sources; + // 源 ds(id,名称,结构归属):名称/structParentId 从最近拉取的行查(缺则用 id)。 + QVector sources; for (const QString& id : sourceIds) { - QString name = id; + geopro::app::VolumeSourceItem s; + s.id = id; + s.name = id; for (const auto& r : *lastSourceRows) - if (r.id == id.toStdString()) { name = QString::fromStdString(r.dsName); break; } - sources.push_back({id, name}); + if (r.id == id.toStdString()) { + s.name = QString::fromStdString(r.dsName); + s.structParentId = QString::fromStdString(r.structParentId); + break; + } + sources.push_back(std::move(s)); } - // 生成位置候选:项目内 GS/项目根/TM(StructNode.type==2 → TM(2),其余 → GS/项目根(1))。 - QVector mounts; - for (const auto& n : *lastStructNodes) - mounts.push_back({QString::fromStdString(n.id), QString::fromStdString(n.name), - n.type == 2 ? 2 : 1}); - geopro::app::VolumeParamsDialog dlg(sources, mounts, /*defaultMountIndex=*/0, &window); + // 左右两树共用项目结构(GS/项目根/TM 层级);生成位置默认选首个节点。 + geopro::app::VolumeParamsDialog dlg(sources, *lastStructNodes, /*defaultMountId=*/QString(), + &window); if (dlg.exec() != QDialog::Accepted) return; const QStringList chosen = dlg.sourceDatasetIds(); if (chosen.isEmpty()) return; // 全取消则不生成