From 8b32566351e0645bfb8a6e747ae69d4f7263a1f1 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 1 Jul 2026 12:22:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(vtk):=20=E5=88=97=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E5=87=BB=E5=B7=B2=E9=80=89=E4=B8=AD=E6=95=B0=E6=8D=AE=E5=8F=AF?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E9=80=89=E4=B8=AD(=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E5=85=A8=E6=99=AF=E8=BD=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 CategorySection 段体树上安装 viewport 事件过滤器,于左键按下瞬间 (默认改选前) 记录被按行及其选中态;itemClicked(释放)时若该行按下前已选中 且本次未点勾选框,则 clearSelection() 直发 datasetSelected("") → 上层 恢复全景轴、丢贴合轴。点勾选框(切渲染)与容器行不触发取消。双击仍激活: 首击可能 toggle 掉选中,itemDoubleClicked 补回选中,终态=选中+贴合轴+详情。 不影响全列互斥(Fix A)、防环、右键菜单与切片/异常高亮。 --- src/app/panels/columns/CategorySection.cpp | 32 ++++++++++++++++++++++ src/app/panels/columns/CategorySection.hpp | 8 ++++++ 2 files changed, 40 insertions(+) diff --git a/src/app/panels/columns/CategorySection.cpp b/src/app/panels/columns/CategorySection.cpp index a63cd28..b52fe03 100644 --- a/src/app/panels/columns/CategorySection.cpp +++ b/src/app/panels/columns/CategorySection.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -144,18 +145,35 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc, list_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); list_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); applyDatasetCardDelegate(list_); + list_->viewport()->installEventFilter(this); // 捕获按下瞬间选中态(默认改选前),供单击已选行 toggle 取消 connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* it, int) { + // 用户点勾选框会在按下→释放间先发 itemChanged:标记本次按下改动了勾选框,itemClicked 据此不取消选中 + // (点勾选框只切渲染,不应连带取消该行选中/丢贴合轴)。程序化改勾选均走 SignalBlocker,不到此处。 + if (it && it == pressedItem_) checkToggledThisPress_ = true; // 异常行复选框 = 该异常显隐(异常不进渲染勾选集,单独走 anomalyVisibilityChanged → setAnomalyVisible)。 if (it && it->data(0, kDsDdCodeRole).toString() == QStringLiteral("dd_anomaly")) emit anomalyVisibilityChanged(it->data(0, kDsIdRole).toString(), it->checkState(0) == Qt::Checked); emitChecked(); }); + // 单击已选中的数据行 → 取消选中(toggle-off):itemClicked 于释放时触发,此时选中态已落定; + // 仅当「按下瞬间该行已选中」且「本次未点勾选框」才取消。取消走 list_->clearSelection()(非成员 + // clearSelection,后者阻断信号)→ 直发 itemSelectionChanged(空) → datasetSelected("") → 恢复全景轴、丢贴合轴。 + // 双击仍可激活:首次释放虽可能 toggle 掉选中,随后 DblClick 会重新选中该行并发 datasetActivated(fit+详情)。 + connect(list_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* it, int) { + if (!it || it != pressedItem_ || !pressedSelected_ || checkToggledThisPress_) return; + if (it->data(0, kDsDdCodeRole).toString() == QStringLiteral("container")) return; // 容器行不参与选中 + pressedSelected_ = false; // 防重入 + list_->clearSelection(); // 发空 itemSelectionChanged → datasetSelected("") → 上层恢复全景轴 + }); connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) { const QString id = it->data(0, kDsIdRole).toString(); if (id.isEmpty()) return; // 双击=适配相机到该 ds 子树盒 + 联动中下方图表详情页(无图表页类型静默,见 main.cpp T4 接线)。 // 属性弹窗改由右键「详情」触发(detailRequested),双击不再弹属性框(决策6:三维体只适配、静默)。 + // 双击首击的 itemClicked 可能已把「本已选中」的该行 toggle 取消(见下方 toggle 逻辑);此处补回选中, + // 使双击终态 = 选中+贴合轴+详情一致(!isSelected 仅在被 toggle 掉时成立,正常双击未选行首击已选中→跳过)。 + if (!it->isSelected()) list_->setCurrentItem(it); emit datasetActivated(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString()); }); // 树选中任意数据行 → datasetSelected(各类型段通用):驱动该 ds 子树贴合坐标轴(spec §3.2), @@ -185,6 +203,20 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc, }); } +bool CategorySection::eventFilter(QObject* obj, QEvent* ev) { + // 段体树 viewport 左键按下:在 QTreeWidget 默认处理改选之前,记录被按行及其「按下瞬间是否已选中」, + // 并清本次「勾选框改动」标记。itemClicked(释放)据此判定:已选中且未点勾选框 → toggle 取消选中。 + if (list_ && obj == list_->viewport() && ev->type() == QEvent::MouseButtonPress) { + auto* me = static_cast(ev); + if (me->button() == Qt::LeftButton) { + pressedItem_ = list_->itemAt(me->position().toPoint()); + pressedSelected_ = pressedItem_ && pressedItem_->isSelected(); + checkToggledThisPress_ = false; + } + } + return QWidget::eventFilter(obj, ev); +} + bool CategorySection::isExpanded() const { return header_ && header_->isChecked(); } void CategorySection::ensureExpanded() { diff --git a/src/app/panels/columns/CategorySection.hpp b/src/app/panels/columns/CategorySection.hpp index 1a05562..af03d36 100644 --- a/src/app/panels/columns/CategorySection.hpp +++ b/src/app/panels/columns/CategorySection.hpp @@ -54,6 +54,10 @@ public: QStringList subtreeDsIds(const QString& dsId) const; bool hasRenderableRows() const; // 段体是否含可渲染数据行(非 container 容器节点),供单列动态显隐 +protected: + // 段体树 viewport 事件过滤:在默认处理改选之前记录「按下瞬间该行是否已选中」,供单击已选行的 toggle 取消判定。 + bool eventFilter(QObject* obj, QEvent* ev) override; + signals: void checkedDatasetsChanged(const QStringList& dsIds); // 数据行勾选=渲染 void collapsedChanged(); // 折叠/展开切换 → 外层 CategoryAnalysisTab 重排各段 stretch @@ -106,6 +110,10 @@ private: int lastBasemapOpacity_ = 50; // 上次底图透明度(0–100,重开 popup 回显;默认 50) QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(透明度改动会触发瓦片重铺,仍需防抖) double pendingBasemapOpacity_ = 0.5; // 防抖待发的底图透明度[0,1](定时器到点发射 basemapOpacityChanged) + // 单击已选行取消选中(toggle-off):eventFilter 于按下瞬间(默认改选前)记录被按行及其选中态;itemClicked 据此取消。 + QTreeWidgetItem* pressedItem_ = nullptr; // 本次左键按下的行(itemAt(press),与 itemClicked 的行比对) + bool pressedSelected_ = false; // 按下瞬间该行是否已选中(true 且非勾选框点击时,单击取消选中) + bool checkToggledThisPress_ = false; // 本次按下是否切换了勾选框(点勾选框只改渲染,不取消选中) }; } // namespace geopro::app