From 62b7cde5cd80624035c6ae7dc0370423181fce66 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 25 Jun 2026 19:42:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E7=AD=9B=E9=80=89=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=8D=A2=20QComboBox(=E4=B8=8E=E8=A3=85=E7=BD=AE=E5=90=8C?= =?UTF-8?q?=E6=AC=BE)=20+=20=E5=BC=82=E5=B8=B8=E5=A4=8D=E9=80=89=E6=A1=86?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E6=98=BE=E9=9A=90(=E2=91=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #3/#4 DateRangeEdit 重写为 QComboBox 子类(覆写 showPopup 弹双日历):外观与装置下拉完全一致 (同款原生下拉箭头/高度/边框),消除 QToolButton 方案的 popup「must be top level window」告警 - ① 创建异常后取消勾选仍渲染:异常行复选框现驱动显隐——itemChanged 对 dd_anomaly 发 anomalyVisibilityChanged→setAnomalyVisible;异常默认勾选=显示(新项默认勾,曾取消的保留); refreshAnomalies 按三维体段复选框设各异常可见性;异常创建回调改为先 refreshAnalysis 再 refreshAnomalies 构建:app 链接通过 --- src/app/main.cpp | 13 ++++++-- src/app/panels/columns/CategorySection.cpp | 26 +++++++++++---- src/app/panels/columns/CategorySection.hpp | 1 + src/app/panels/columns/DateRangeEdit.cpp | 38 +++++++--------------- src/app/panels/columns/DateRangeEdit.hpp | 15 +++++---- 5 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index 76aaa46..6091dac 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -425,7 +425,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 异常刷新渲染(#4b/4c):恒「全部显示」——旧三维分析栏的过滤档位 UI 已退役,新分段 tab 暂无档位 // 控件(功能缺失,待补)。异常列表由 refreshAnalysis 经 voxelTree 全量注入三维体段,此处只管渲染。 // (loadAnomalyTree 空 key=全部。mock 同步回调。) - auto refreshAnomalies = [sceneView, scene3dRepo, renderWindowPtr]() { + auto refreshAnomalies = [sceneView, scene3dRepo, renderWindowPtr, drawer]() { sceneView->clearAnomalies(); std::vector set; scene3dRepo->loadAnomalyTree( @@ -437,6 +437,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re }, [](const std::string&) {}); for (const auto& a : set) sceneView->addAnomaly(a); + // 异常显隐跟随三维体段复选框:取消勾选的异常隐藏(修「创建异常后取消勾选仍渲染」)。 + // 须在树已含该异常后调用(创建回调里先 refreshAnalysis 再 refreshAnomalies)。 + if (auto* sec = drawer->analysisTab()->section("voxel")) { + const QStringList checked = sec->checkedIds(); + for (const auto& a : set) + sceneView->setAnomalyVisible(a.id, checked.contains(QString::fromStdString(a.id))); + } renderWindowPtr->Render(); // 必须重绘:clear+addAnomaly 改了 prop,否则 VTK 不刷新 }; @@ -562,8 +569,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re [sceneView, renderWindowPtr, refreshAnomalies, refreshAnalysis, draftId](std::string) { sceneView->removeAnomaly(draftId); // 撤草稿 - refreshAnomalies(); // 重渲染异常 actor - refreshAnalysis(); // 新异常进三维体段三级树(挂体/切片) + refreshAnalysis(); // 先:新异常进三维体段三级树(默认勾选=显示) + refreshAnomalies(); // 后:重渲染异常 actor 并按复选框设显隐 renderWindowPtr->Render(); }, [&window](const std::string& m) { diff --git a/src/app/panels/columns/CategorySection.cpp b/src/app/panels/columns/CategorySection.cpp index a37252f..413a4d9 100644 --- a/src/app/panels/columns/CategorySection.cpp +++ b/src/app/panels/columns/CategorySection.cpp @@ -86,7 +86,13 @@ CategorySection::CategorySection(const CategorySpec& spec, geopro::data::Dataset list_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); list_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); applyDatasetCardDelegate(list_); - connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { emitChecked(); }); + connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* it, int) { + // 异常行复选框 = 该异常显隐(异常不进渲染勾选集,单独走 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(); + }); connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) { const QString id = it->data(0, kDsIdRole).toString(); if (id.isEmpty()) return; @@ -180,10 +186,13 @@ bool CategorySection::passesFilters(const DsRow& row) const { void CategorySection::rebuildList() { // 增量保留:记住当前已勾选的 ds,重建后复原(仍存在的项保持勾选)。否则每次刷新(勾选对象/建体/ // 存切片/建异常都会触发)清空全部勾选 → 渲染被重置,体验极差(用户反馈:必须增量更新)。 - std::set wasChecked; - for (QTreeWidgetItemIterator it(list_); *it; ++it) - if ((*it)->checkState(0) == Qt::Checked) - wasChecked.insert((*it)->data(0, kDsIdRole).toString().toStdString()); + std::set wasChecked, wasSeen; // wasSeen=重建前所有可勾选项(区分"新项" vs "曾取消") + for (QTreeWidgetItemIterator it(list_); *it; ++it) { + if (!((*it)->flags() & Qt::ItemIsUserCheckable)) continue; + const std::string id = (*it)->data(0, kDsIdRole).toString().toStdString(); + wasSeen.insert(id); + if ((*it)->checkState(0) == Qt::Checked) wasChecked.insert(id); + } std::vector filtered; filtered.reserve(rows_.size()); @@ -236,7 +245,12 @@ void CategorySection::rebuildList() { } (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable); const std::string id = (*it)->data(0, kDsIdRole).toString().toStdString(); - (*it)->setCheckState(0, wasChecked.count(id) ? Qt::Checked : Qt::Unchecked); // 复原勾选 + const bool isAnomaly = (*it)->data(0, kDsDdCodeRole).toString() == QStringLiteral("dd_anomaly"); + // 复原勾选:曾勾选→勾;曾出现但未勾→不勾;新项→异常默认勾(显示),其余默认不勾。 + const Qt::CheckState st = wasChecked.count(id) ? Qt::Checked + : wasSeen.count(id) ? Qt::Unchecked + : (isAnomaly ? Qt::Checked : Qt::Unchecked); + (*it)->setCheckState(0, st); } } list_->expandAll(); // 展开容器层级(项目根/GS/TM),让体/切片/异常可见 diff --git a/src/app/panels/columns/CategorySection.hpp b/src/app/panels/columns/CategorySection.hpp index 7882c57..1c0e5c1 100644 --- a/src/app/panels/columns/CategorySection.hpp +++ b/src/app/panels/columns/CategorySection.hpp @@ -33,6 +33,7 @@ public: void setStructure(const std::vector& nodes); void setDatasets(const std::vector& rows); void setChecked(const QString& dsId, bool on); // 按 dsId 勾选/取消(新建切片自动勾选等场景) + QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds(异常显隐同步用) const CategorySpec& spec() const { return spec_; } signals: diff --git a/src/app/panels/columns/DateRangeEdit.cpp b/src/app/panels/columns/DateRangeEdit.cpp index ec29508..cd43efd 100644 --- a/src/app/panels/columns/DateRangeEdit.cpp +++ b/src/app/panels/columns/DateRangeEdit.cpp @@ -5,38 +5,20 @@ #include #include #include -#include #include #include "Theme.hpp" namespace geopro::app { -DateRangeEdit::DateRangeEdit(QWidget* parent) : QWidget(parent) { - auto* lay = new QHBoxLayout(this); - lay->setContentsMargins(0, 0, 0, 0); - lay->setSpacing(0); - btn_ = new QToolButton(this); - btn_->setToolButtonStyle(Qt::ToolButtonTextOnly); - btn_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - btn_->setPopupMode(QToolButton::InstantPopup); - btn_->setToolTip(QStringLiteral("按采集时间筛选(起 ~ 止),可清空")); - // 外观与 QComboBox 完全一致(同高/同边框/同圆角/同内距)+ 右侧统一 SVG chevron(替代手写 ▾, - // 原 ▾ 字符渲染粗糙且按钮比装置下拉矮)。右侧留 24px 放 chevron,文本左对齐。 - applyTokenizedStyleSheet( - btn_, QStringLiteral( - "QToolButton{background-color:{{bg/panel}};color:{{text/primary}};" - "border:1px solid {{border/default}};border-radius:4px;" - "padding:6px 22px 6px 8px;min-height:16px;text-align:left;" - "background-image:url(:/icons/chevron-down.svg);background-repeat:no-repeat;" - "background-origin:padding;background-position:right center;}" - "QToolButton:hover{border-color:{{border/strong}};}")); - connect(btn_, &QToolButton::clicked, this, &DateRangeEdit::openPopup); - lay->addWidget(btn_); +DateRangeEdit::DateRangeEdit(QWidget* parent) : QComboBox(parent) { + addItem(QString()); // 单项承载显示文本(不展开列表,点击改为弹双日历) + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setToolTip(QStringLiteral("按采集时间筛选(起 ~ 止),可清空")); updateText(); } -void DateRangeEdit::openPopup() { +void DateRangeEdit::showPopup() { if (!popup_) { popup_ = new QFrame(this, Qt::Popup); popup_->setFrameShape(QFrame::StyledPanel); @@ -69,7 +51,7 @@ void DateRangeEdit::openPopup() { btns->addWidget(ok); v->addLayout(btns); } - // 已有范围则定位到当前值,否则日历默认今天(不再 1752)。 + // 已有范围则定位到当前值,否则日历默认今天。 if (from_.isValid()) fromCal_->setSelectedDate(from_); if (to_.isValid()) toCal_->setSelectedDate(to_); popup_->adjustSize(); @@ -77,6 +59,10 @@ void DateRangeEdit::openPopup() { popup_->show(); } +void DateRangeEdit::hidePopup() { + if (popup_) popup_->hide(); // 我们用自绘双日历 popup,不走 QComboBox 列表 +} + void DateRangeEdit::applyAndClose() { from_ = fromCal_->selectedDate(); to_ = toCal_->selectedDate(); @@ -96,12 +82,12 @@ void DateRangeEdit::clearAndClose() { void DateRangeEdit::updateText() { if (!from_.isValid() && !to_.isValid()) { - btn_->setText(QStringLiteral("全部时间")); // chevron 由 QSS background-image 绘制 + setItemText(0, QStringLiteral("全部时间")); return; } const QString f = from_.isValid() ? from_.toString(QStringLiteral("yyyy-MM-dd")) : QStringLiteral("不限"); const QString t = to_.isValid() ? to_.toString(QStringLiteral("yyyy-MM-dd")) : QStringLiteral("不限"); - btn_->setText(QStringLiteral("%1 ~ %2").arg(f, t)); + setItemText(0, QStringLiteral("%1 ~ %2").arg(f, t)); } } // namespace geopro::app diff --git a/src/app/panels/columns/DateRangeEdit.hpp b/src/app/panels/columns/DateRangeEdit.hpp index b7bb7bb..4a591ac 100644 --- a/src/app/panels/columns/DateRangeEdit.hpp +++ b/src/app/panels/columns/DateRangeEdit.hpp @@ -1,16 +1,16 @@ #pragma once +#include #include -#include -class QToolButton; class QCalendarWidget; class QFrame; namespace geopro::app { -// 日期范围选择器(spec 采集时间筛选):一个按钮显示「起 ~ 止」,点开弹双日历面板选起止; -// 可清空(回到"全部时间");默认不限,日历定位今天(不再停在 1752)。 -class DateRangeEdit : public QWidget { +// 日期范围选择器(spec 采集时间筛选):外观与普通 QComboBox **完全一致**(同款下拉箭头/高度/边框, +// 与装置类型下拉是同一种控件),但点开弹「双日历」选起止而非下拉列表。显示「起 ~ 止」/「全部时间」; +// 可清空(回到"全部时间");默认不限,日历定位今天。 +class DateRangeEdit : public QComboBox { Q_OBJECT public: explicit DateRangeEdit(QWidget* parent = nullptr); @@ -18,16 +18,17 @@ public: QDate from() const { return from_; } // 无效 QDate = 不限下限 QDate to() const { return to_; } // 无效 QDate = 不限上限 + void showPopup() override; // 覆写:弹双日历面板(而非 QComboBox 默认列表) + void hidePopup() override; + signals: void rangeChanged(); private: - void openPopup(); void applyAndClose(); void clearAndClose(); void updateText(); - QToolButton* btn_ = nullptr; QFrame* popup_ = nullptr; QCalendarWidget* fromCal_ = nullptr; QCalendarWidget* toCal_ = nullptr;