fix(ui): 筛选时间换 QComboBox(与装置同款) + 异常复选框驱动显隐(①)

- #3/#4 DateRangeEdit 重写为 QComboBox 子类(覆写 showPopup 弹双日历):外观与装置下拉完全一致
  (同款原生下拉箭头/高度/边框),消除 QToolButton 方案的 popup「must be top level window」告警
- ① 创建异常后取消勾选仍渲染:异常行复选框现驱动显隐——itemChanged 对 dd_anomaly 发
  anomalyVisibilityChanged→setAnomalyVisible;异常默认勾选=显示(新项默认勾,曾取消的保留);
  refreshAnomalies 按三维体段复选框设各异常可见性;异常创建回调改为先 refreshAnalysis 再 refreshAnomalies

构建:app 链接通过
This commit is contained in:
gaozheng 2026-06-25 19:42:37 +08:00
parent 2bd1c36579
commit 62b7cde5cd
5 changed files with 51 additions and 42 deletions

View File

@ -425,7 +425,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 异常刷新渲染(#4b/4c恒「全部显示」——旧三维分析栏的过滤档位 UI 已退役,新分段 tab 暂无档位 // 异常刷新渲染(#4b/4c恒「全部显示」——旧三维分析栏的过滤档位 UI 已退役,新分段 tab 暂无档位
// 控件(功能缺失,待补)。异常列表由 refreshAnalysis 经 voxelTree 全量注入三维体段,此处只管渲染。 // 控件(功能缺失,待补)。异常列表由 refreshAnalysis 经 voxelTree 全量注入三维体段,此处只管渲染。
// loadAnomalyTree 空 key=全部。mock 同步回调。) // loadAnomalyTree 空 key=全部。mock 同步回调。)
auto refreshAnomalies = [sceneView, scene3dRepo, renderWindowPtr]() { auto refreshAnomalies = [sceneView, scene3dRepo, renderWindowPtr, drawer]() {
sceneView->clearAnomalies(); sceneView->clearAnomalies();
std::vector<geopro::core::Anomaly> set; std::vector<geopro::core::Anomaly> set;
scene3dRepo->loadAnomalyTree( scene3dRepo->loadAnomalyTree(
@ -437,6 +437,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}, },
[](const std::string&) {}); [](const std::string&) {});
for (const auto& a : set) sceneView->addAnomaly(a); 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 不刷新 renderWindowPtr->Render(); // 必须重绘clear+addAnomaly 改了 prop否则 VTK 不刷新
}; };
@ -562,8 +569,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
[sceneView, renderWindowPtr, refreshAnomalies, refreshAnalysis, [sceneView, renderWindowPtr, refreshAnomalies, refreshAnalysis,
draftId](std::string) { draftId](std::string) {
sceneView->removeAnomaly(draftId); // 撤草稿 sceneView->removeAnomaly(draftId); // 撤草稿
refreshAnomalies(); // 重渲染异常 actor refreshAnalysis(); // 先:新异常进三维体段三级树(默认勾选=显示)
refreshAnalysis(); // 新异常进三维体段三级树(挂体/切片) refreshAnomalies(); // 后:重渲染异常 actor 并按复选框设显隐
renderWindowPtr->Render(); renderWindowPtr->Render();
}, },
[&window](const std::string& m) { [&window](const std::string& m) {

View File

@ -86,7 +86,13 @@ CategorySection::CategorySection(const CategorySpec& spec, geopro::data::Dataset
list_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); list_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
list_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); list_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
applyDatasetCardDelegate(list_); 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) { connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) {
const QString id = it->data(0, kDsIdRole).toString(); const QString id = it->data(0, kDsIdRole).toString();
if (id.isEmpty()) return; if (id.isEmpty()) return;
@ -180,10 +186,13 @@ bool CategorySection::passesFilters(const DsRow& row) const {
void CategorySection::rebuildList() { void CategorySection::rebuildList() {
// 增量保留:记住当前已勾选的 ds重建后复原仍存在的项保持勾选。否则每次刷新(勾选对象/建体/ // 增量保留:记住当前已勾选的 ds重建后复原仍存在的项保持勾选。否则每次刷新(勾选对象/建体/
// 存切片/建异常都会触发)清空全部勾选 → 渲染被重置,体验极差(用户反馈:必须增量更新)。 // 存切片/建异常都会触发)清空全部勾选 → 渲染被重置,体验极差(用户反馈:必须增量更新)。
std::set<std::string> wasChecked; std::set<std::string> wasChecked, wasSeen; // wasSeen=重建前所有可勾选项(区分"新项" vs "曾取消")
for (QTreeWidgetItemIterator it(list_); *it; ++it) for (QTreeWidgetItemIterator it(list_); *it; ++it) {
if ((*it)->checkState(0) == Qt::Checked) if (!((*it)->flags() & Qt::ItemIsUserCheckable)) continue;
wasChecked.insert((*it)->data(0, kDsIdRole).toString().toStdString()); const std::string id = (*it)->data(0, kDsIdRole).toString().toStdString();
wasSeen.insert(id);
if ((*it)->checkState(0) == Qt::Checked) wasChecked.insert(id);
}
std::vector<DsRow> filtered; std::vector<DsRow> filtered;
filtered.reserve(rows_.size()); filtered.reserve(rows_.size());
@ -236,7 +245,12 @@ void CategorySection::rebuildList() {
} }
(*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable); (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);
const std::string id = (*it)->data(0, kDsIdRole).toString().toStdString(); 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让体/切片/异常可见 list_->expandAll(); // 展开容器层级(项目根/GS/TM让体/切片/异常可见

View File

@ -33,6 +33,7 @@ public:
void setStructure(const std::vector<geopro::data::StructNode>& nodes); void setStructure(const std::vector<geopro::data::StructNode>& nodes);
void setDatasets(const std::vector<geopro::data::DsRow>& rows); void setDatasets(const std::vector<geopro::data::DsRow>& rows);
void setChecked(const QString& dsId, bool on); // 按 dsId 勾选/取消(新建切片自动勾选等场景) void setChecked(const QString& dsId, bool on); // 按 dsId 勾选/取消(新建切片自动勾选等场景)
QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds异常显隐同步用
const CategorySpec& spec() const { return spec_; } const CategorySpec& spec() const { return spec_; }
signals: signals:

View File

@ -5,38 +5,20 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Theme.hpp" #include "Theme.hpp"
namespace geopro::app { namespace geopro::app {
DateRangeEdit::DateRangeEdit(QWidget* parent) : QWidget(parent) { DateRangeEdit::DateRangeEdit(QWidget* parent) : QComboBox(parent) {
auto* lay = new QHBoxLayout(this); addItem(QString()); // 单项承载显示文本(不展开列表,点击改为弹双日历)
lay->setContentsMargins(0, 0, 0, 0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
lay->setSpacing(0); setToolTip(QStringLiteral("按采集时间筛选(起 ~ 止),可清空"));
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_);
updateText(); updateText();
} }
void DateRangeEdit::openPopup() { void DateRangeEdit::showPopup() {
if (!popup_) { if (!popup_) {
popup_ = new QFrame(this, Qt::Popup); popup_ = new QFrame(this, Qt::Popup);
popup_->setFrameShape(QFrame::StyledPanel); popup_->setFrameShape(QFrame::StyledPanel);
@ -69,7 +51,7 @@ void DateRangeEdit::openPopup() {
btns->addWidget(ok); btns->addWidget(ok);
v->addLayout(btns); v->addLayout(btns);
} }
// 已有范围则定位到当前值,否则日历默认今天(不再 1752 // 已有范围则定位到当前值,否则日历默认今天
if (from_.isValid()) fromCal_->setSelectedDate(from_); if (from_.isValid()) fromCal_->setSelectedDate(from_);
if (to_.isValid()) toCal_->setSelectedDate(to_); if (to_.isValid()) toCal_->setSelectedDate(to_);
popup_->adjustSize(); popup_->adjustSize();
@ -77,6 +59,10 @@ void DateRangeEdit::openPopup() {
popup_->show(); popup_->show();
} }
void DateRangeEdit::hidePopup() {
if (popup_) popup_->hide(); // 我们用自绘双日历 popup不走 QComboBox 列表
}
void DateRangeEdit::applyAndClose() { void DateRangeEdit::applyAndClose() {
from_ = fromCal_->selectedDate(); from_ = fromCal_->selectedDate();
to_ = toCal_->selectedDate(); to_ = toCal_->selectedDate();
@ -96,12 +82,12 @@ void DateRangeEdit::clearAndClose() {
void DateRangeEdit::updateText() { void DateRangeEdit::updateText() {
if (!from_.isValid() && !to_.isValid()) { if (!from_.isValid() && !to_.isValid()) {
btn_->setText(QStringLiteral("全部时间")); // chevron 由 QSS background-image 绘制 setItemText(0, QStringLiteral("全部时间"));
return; return;
} }
const QString f = from_.isValid() ? from_.toString(QStringLiteral("yyyy-MM-dd")) : QStringLiteral("不限"); const QString f = from_.isValid() ? from_.toString(QStringLiteral("yyyy-MM-dd")) : QStringLiteral("不限");
const QString t = to_.isValid() ? to_.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 } // namespace geopro::app

View File

@ -1,16 +1,16 @@
#pragma once #pragma once
#include <QComboBox>
#include <QDate> #include <QDate>
#include <QWidget>
class QToolButton;
class QCalendarWidget; class QCalendarWidget;
class QFrame; class QFrame;
namespace geopro::app { namespace geopro::app {
// 日期范围选择器spec 采集时间筛选):一个按钮显示「起 ~ 止」,点开弹双日历面板选起止; // 日期范围选择器spec 采集时间筛选):外观与普通 QComboBox **完全一致**(同款下拉箭头/高度/边框,
// 可清空(回到"全部时间");默认不限,日历定位今天(不再停在 1752 // 与装置类型下拉是同一种控件),但点开弹「双日历」选起止而非下拉列表。显示「起 ~ 止」/「全部时间」;
class DateRangeEdit : public QWidget { // 可清空(回到"全部时间");默认不限,日历定位今天。
class DateRangeEdit : public QComboBox {
Q_OBJECT Q_OBJECT
public: public:
explicit DateRangeEdit(QWidget* parent = nullptr); explicit DateRangeEdit(QWidget* parent = nullptr);
@ -18,16 +18,17 @@ public:
QDate from() const { return from_; } // 无效 QDate = 不限下限 QDate from() const { return from_; } // 无效 QDate = 不限下限
QDate to() const { return to_; } // 无效 QDate = 不限上限 QDate to() const { return to_; } // 无效 QDate = 不限上限
void showPopup() override; // 覆写:弹双日历面板(而非 QComboBox 默认列表)
void hidePopup() override;
signals: signals:
void rangeChanged(); void rangeChanged();
private: private:
void openPopup();
void applyAndClose(); void applyAndClose();
void clearAndClose(); void clearAndClose();
void updateText(); void updateText();
QToolButton* btn_ = nullptr;
QFrame* popup_ = nullptr; QFrame* popup_ = nullptr;
QCalendarWidget* fromCal_ = nullptr; QCalendarWidget* fromCal_ = nullptr;
QCalendarWidget* toCal_ = nullptr; QCalendarWidget* toCal_ = nullptr;