分组折起热区与改名输入框修正 + 数据模型去冗余(单一名称)+ 新增 Components/Shared 组件 + 过时注释/命名大清理 + 二进制瘦身 25.6%

## 一、UI 交互修复(用户报告)

① 分组折起热区(用户要求):点分组**名称**不再折起,只有**折叠三角**或**「组X」标签**
   才折起。RowPart 新增 GROUP_TITLE;RowHitPart() 为分组行判出三角 / 复选框 / 关闭 ✕ /
   「组X」标签矩形 / 其余 BODY;OnLButtonDown 只在这两个热区调用 ToggleGroupFromRow()。
   附带:双击「组X」不再进入改名(该区域现为折起热区),双击分组名仍可改名。
② 分组改名输入框不再吞掉「N 项」:RowNameRect() 对分组行的右边界改为「项数左边缘 − 6*scale」;
   新增 GroupLabelText()/GroupStatsText() 作为文本与宽度的唯一来源(PaintGroupRow 与
   RowNameRect 共用),避免名称可用宽度与实际绘制文本漂移。

## 二、数据模型去冗余(用户指出的重点)

- SignalData / SignalGroup **只保留一个名称字段** name:删除 displayName、删除 Title()
  (31 处调用点改为直接用 name);DataSet::SetSignalDisplayName / SetGroupDisplayName
  → RenameSignal / RenameGroup,且**空串 / 纯空白一律拒绝**(名称不再有回退值)。
  依据:信号靠 SignalRef 下标定位、从不按名字匹配 ⇒ 两个字段是同一语义的两份副本。
  收益:每条信号 / 每个分组少一个 std::wstring(24B + 堆分配)与一条 Title() 分支。
- 删除 Application::m_loadingBytes(全仓既不读也不写的死成员,注释却称“用于展示体积”)。

## 三、新增 Components/Shared 组件(通用 UI 控件)

- 从 Utilities/PaintUtil.* 迁出全部「控件类」绘制/命中:图标(+ / ✕ / 三角)、复选框、
  滑块(UiSliderRatioFromX / UiDrawSlider)、内联输入框(UiDrawEditField /
  UiDrawEditFieldContent / UiEditTextLeft / UiEditOffsetAt / UiEditFieldMouseDown /
  UiEditFieldMouseMove)、颜色样块(UiDrawSwatch / UiSwatchNeedsOutline)、
  取值胶囊配色(UiInk / UiColorLuminance / UiContrastInk)。
- PaintUtil.* 只保留低层绘制原语(矩形 / 圆角 / 直线 / 批量实线 / 颜色混合 / 文本与测量)。
- 依赖方向固定:Components/* → Components/Shared → Utilities(不得反向,Utilities 不再含控件)。
- Makefile 与 .preflight/perf/build.ps1 已登记新目录;调用方改 include "UiControls.hpp"。

## 四、过时注释 / 命名批量修正(承接上一轮未做完的部分)

- 注释:文件下标→分组下标、文件内信号下标→组内信号下标、所属文件下标→所属分组下标;
  措辞统一 文件分组→分组、移除该文件→移除该分组、打开 CSV 文件→打开数据文件 等一批。
- 文不对题的形参名:VisibleIndexRange(const TimeAxis& file,…) → axis;
  Application::LoadFileAsync(…, SignalGroup& file,…) → group。
- 函数名里的 File 语义(上一轮只改了 RowKind::FILE):PaintFileRow→PaintGroupRow、
  FileCloseRect→GroupCloseRect、OpenFileMenu→OpenGroupMenu、FileGroupLabelWidth→GroupLabelWidth、
  StyleEditField::FILE_NAME→GROUP_TITLE;局部变量 fileRow/fileHeight/isFile →
  groupRow/groupHeight/isGroup。
- 修复 DataModel.hpp 两处被压扁的注释块(/**<TAB><TAB>* @brief …)与 Application::NewTab
  上方重复的文档块。
- ⚠️ 踩坑:GROUP_NAME 撞上 nb30.h(windows.h 引入)的 #define GROUP_NAME 0x80,
  导致枚举编译失败,改名 GROUP_TITLE 解决。

## 五、二进制瘦身:1 013 248 → 754 176 B(−25.6%,省 259 KB)

- 真因不是 PlotView.cpp,而是 Resources/JustDataInspector.ico 里 **256×256@32bpp 以未压缩
  BMP 存储的条目(270 376 B,占整包 27%)**。改为 PNG 存储(Vista+ 标准做法,观感零变化)
  ⇒ 该条目 270 376 → 11 280 B、整个 ico 302 430 → 43 334 B(小尺寸仍保留 BMP)。
- 实测结论:“拆分大文件 ⇒ 瘦身”不成立(同一批机器码仍要进二进制);
  -ffunction-sections/-fdata-sections/-Wl,--gc-sections 在本工程反而 +9 216 B(已回退)。
- ⚠️ .o 体积受节对齐/重定位表主导,判断“代码搬去哪了”要用 nm,不要看 .o 字节数。

## 六、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)

- panelanim 156/156(连跑 5 次稳定;新增/更新:点分组名不折起也不滚动、扫掠定位「组X」热区、
  筛选态与普通态折叠三角逐像素一致、改名输入框右边界早于项数文本);verify 49/49;
  datacheck 69/69(含“重命名写入唯一名称字段且空名被拒”);edgecheck 46/46。
- A/B:回退两处 UI 修复 ⇒ 10 项如实失败;恢复后全绿。
- make release / make debug 零警告。

## 七、本轮未完成(已在报告与 agent_mem.md 中如实记录)

- PlotView.cpp(13 695 行 / 544 KB)按功能拆分为多个协作文件:未执行。
  前置改造是把第 13–888 行的匿名命名空间(~870 行常量与工具函数)改成具名命名空间并逐个
  inline,再逐个文件搬移并反复验证;时间不足以安全完成,故主动不做(附可执行方案)。
- SignalPanel.cpp / Application.cpp / DataModel.cpp 的多文件化、Lane 抽出 LanePanelState、
  SetStatusMessage 模态阻塞解耦:均未执行。

## 八、文档

- 新增 Documents/refactor_report_2026-10-16.md(本轮详细报告);
  删除 Documents/refactor_report_2026-10-15.md(有效内容已并入 agent_mem.md 与本报告)。
- Documents/agent_mem.md 新增 §9.37 与 §11.2 第 85–90 条;更新数据层架构摘要(单一名称字段);
  §12 历史附录加“时效声明”。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-16 03:25:48 +08:00
1 parent 5831a0ea0d
commit 0871eb0cba
22 files changed
+1268 -1010

No files matched your search

+31 -44
View File
@@ -380,18 +380,16 @@ namespace jdi
/**
* @brief 单条信号的元数据与采样值(纯数据,不含可见性配置)。
*
* ⚠️ 信号**只有两个名字字段,各有明确职责**,不要再引入第三个:
* * `name` —— 源名称(列名 / 通道名),加载后**原样保留、永不修改**;
* * `displayName` —— 用户在界面上重命名后的名称,为空表示“用源名称”。
* 界面展示一律走 `Title()`。
* ⚠️ 信号**只有一个名称字段** `name` —— 它就是界面展示用的名字,也是用户重命名后写入的地方
*(加载时取列名 / 通道名,之后不再保留“原始名称”的第二份副本:应用从不靠名称定位信号,
* 定位一律走 `SignalRef` 下标)。
*
* ⚠️ 信号的时间轴由 `axisIndex` 指向所属分组的时间轴列表,
* 因此采样值本身不需要、也不应该携带时间信息。
*/
struct SignalData
{
std::wstring name; /**< 源名称(列名 / 通道名,原样保留)。 */
std::wstring displayName; /**< 用户重命名后的显示名;为空时使用 name。 */
std::wstring name; /**< 信号名称(加载时取列名 / 通道名;用户可在界面上重命名)。 */
std::size_t axisIndex = 0; /**< 所属时间轴在分组内的下标。 */
std::vector<double> values; /**< 采样值,缺失处为 NaN。 */
double minValue = 0.0; /**< 有限采样值最小值。 */
@@ -427,12 +425,6 @@ namespace jdi
std::size_t coarseStride = 0; /**< 分组的采样跨度;0 表示未构建。 */
SignalStyle style; /**< 显示外观(全局共享,跨标签页同步)。 */
/**
* @brief 获取用于界面展示的名称。
* @return 用户重命名后的显示名,未重命名时返回源名称。
*/
const std::wstring& Title() const { return displayName.empty() ? name : displayName; }
};
/**
@@ -447,17 +439,10 @@ namespace jdi
*/
struct SignalGroup
{
std::wstring name; /**< 分组默认名称(源文件名,含扩展名)。 */
std::wstring displayName; /**< 用户重命名后的分组名;为空时使用 name。 */
std::wstring name; /**< 分组名(加载时取源文件名、含扩展名;用户可重命名)。 */
std::vector<TimeAxis> axes; /**< 该分组使用的时间轴(至少一条)。 */
std::vector<SignalData> signals; /**< 该分组包含的信号。 */
/**
* @brief 获取用于界面展示的分组名。
* @return 用户重命名后的显示名,未重命名时返回源文件名。
*/
const std::wstring& Title() const { return displayName.empty() ? name : displayName; }
/**
* @brief 取某条信号所属的时间轴(下标越界时回退到第一条,保证永不返回悬空引用)。
* @param [IN] signal 目标信号。
@@ -631,21 +616,21 @@ namespace jdi
const SignalGroup* GroupAt(int groupIndex) const;
/**
* @brief 修改某条信号在界面上的显示名称。
* @brief 重命名某条信号(写入它的**唯一**名称字段 `SignalData::name`)。
* @param [IN] groupIndex 分组下标。
* @param [IN] signalIndex 组内信号下标。
* @param [IN] name 新的显示名;传入空串表示恢复使用源名称。
* @return 修改成功返回 true。
* @param [IN] name 新名称;空串(或仅空白)忽略,避免把名称抹空。
* @return 名称确实发生变化返回 true。
*/
bool SetSignalDisplayName(int groupIndex, int signalIndex, const std::wstring& name);
bool RenameSignal(int groupIndex, int signalIndex, const std::wstring& name);
/**
* @brief 修改某个分组的显示名称。
* @brief 重命名某个分组(写入它的**唯一**名称字段 `SignalGroup::name`)。
* @param [IN] groupIndex 分组下标。
* @param [IN] name 新的分组名;传入空串表示恢复使用默认名称。
* @return 修改成功返回 true。
* @param [IN] name 新名称;空串(或仅空白)忽略,避免把名称抹空。
* @return 名称确实发生变化返回 true。
*/
bool SetGroupDisplayName(int groupIndex, const std::wstring& name);
bool RenameGroup(int groupIndex, const std::wstring& name);
/**
* @brief 解析信号引用。
@@ -1068,22 +1053,22 @@ namespace jdi
void SetName(const std::wstring& name) { m_name = name; }
/**
* @brief 依据数据集合调整配置容量(新增文件补默认配置,移除文件清理配置)。
* @brief 依据数据集合调整配置容量(新增分组补默认配置,移除分组清理配置)。
* @param [IN] dataSet 数据集合。
*/
void SyncToDataSet(const DataSet& dataSet);
/**
* @brief 在数据层移除某文件之前,按旧下标修正本配置(不重建网格)。
* @brief 在数据层移除某分组之前,按旧下标修正本配置(不重建网格)。
* @param [IN] groupIndex 即将被移除的分组下标。
* @note 调用顺序必须为:各标签页 RemapAfterGroupRemoval → 数据层 RemoveFile → 各标签页 SyncToDataSet。
* @note 调用顺序必须为:各标签页 RemapAfterGroupRemoval → 数据层 RemoveGroup → 各标签页 SyncToDataSet。
*/
void RemapAfterGroupRemoval(int groupIndex);
/**
* @brief 在数据层移除某条信号之前,按旧下标修正本配置(不重建网格)。
*
* 会删除该信号的可见性记录与网格归属,并把同一文件内位于其后的引用下标前移一位。
* 会删除该信号的可见性记录与网格归属,并把同一分组内位于其后的引用下标前移一位。
* 若某个网格因此变空则一并移除(与“取消勾选后空网格消失”的策略一致)。
*
* @param [IN] groupIndex 信号所属分组下标。
@@ -1114,7 +1099,7 @@ namespace jdi
void ToggleVisible(const SignalRef& ref);
/**
* @brief 设置某个文件全部信号的可见性,并同步网格划分。
* @brief 设置某个分组全部信号的可见性,并同步网格划分。
* @param [IN] groupIndex 分组下标。
* @param [IN] visible 目标可见状态。
*/
@@ -1133,14 +1118,14 @@ namespace jdi
void SoloSignal(const SignalRef& ref);
/**
* @brief 判断某文件是否存在可见信号。
* @brief 判断某分组是否存在可见信号。
* @param [IN] groupIndex 分组下标。
* @return 存在可见信号返回 true。
*/
bool GroupHasVisible(int groupIndex) const;
/**
* @brief 判断某文件是否全部信号可见。
* @brief 判断某分组是否全部信号可见。
* @param [IN] groupIndex 分组下标。
* @return 全部可见且至少有一条信号返回 true。
*/
@@ -1215,10 +1200,11 @@ namespace jdi
*/
int SplitLane(int laneIndex);
/** * @brief 在数据层插入新信号之后,修正本页引用该文件的信号下标。
/**
* @brief 在数据层插入新信号之后,修正本页引用该分组的信号下标。
*
* 数据层在下标 `insertPos` 处插入了一条信号,因此本页可见性表需插入一列,
* 且所有指向该文件、下标不小于 `insertPos` 的信号引用都要后移一位。
* 且所有指向该分组、下标不小于 `insertPos` 的信号引用都要后移一位。
*
* @param [IN] groupIndex 发生插入的分组下标。
* @param [IN] insertPos 新信号的下标。
@@ -1255,20 +1241,21 @@ namespace jdi
*/
void RenameLanes();
/** * @brief 查询某文件的分组是否处于折叠状态。
/**
* @brief 查询某分组的折叠状态。
* @param [IN] groupIndex 分组下标。
* @return 折叠状态。
*/
bool IsCollapsed(int groupIndex) const;
/**
* @brief 切换某文件分组的折叠状态。
* @brief 切换某分组的折叠状态。
* @param [IN] groupIndex 分组下标。
*/
void ToggleCollapsed(int groupIndex);
/**
* @brief 设置「新出现的文件分组是否默认折起」。
* @brief 设置「新出现的分组是否默认折起」。
*
* 只影响**之后**新出现的分组(例如新加载的文件);已存在的分组保持用户当前的选择。
* 用户新建的标签页会把它设为 true,使信号列表默认紧凑。
@@ -1278,7 +1265,7 @@ namespace jdi
void SetGroupsCollapsedByDefault(bool collapsed) { m_groupsCollapsedByDefault = collapsed; }
/**
* @brief 把当前所有文件分组设为折叠或展开。
* @brief 把当前所有分组设为折叠或展开。
* @param [IN] collapsed true = 全部折起,false = 全部展开。
*/
void SetAllCollapsed(bool collapsed)
@@ -1339,10 +1326,10 @@ namespace jdi
private:
std::wstring m_name; /**< 标签页名称。 */
std::vector<std::vector<bool>> m_visible; /**< 信号可见性,按 [文件][信号] 索引(本页独立)。 */
std::vector<bool> m_collapsed; /**< 文件分组折叠状态,按文件索引。 */
std::vector<std::vector<bool>> m_visible; /**< 信号可见性,按 [分组][信号] 索引(本页独立)。 */
std::vector<bool> m_collapsed; /**< 分组折叠状态,按分组下标。 */
/**
* @brief 新出现的(首次见到的)文件分组是否默认折起。
* @brief 新出现的(首次见到的)分组是否默认折起。
*
* 用户新建的标签页把它设为 true(信号列表默认紧凑);启动时的首个标签页保持 false,
* 这样加载完文件就能直接勾选信号。