feat: 支持多视图标签页、按文件分组的多文件信号管理与网格随勾选自动收敛

数据层与视图配置解耦:新增 DataSet(多文件只读数据层)与 TabConfig(每标签页独立配置),
以 SignalRef{fileIndex,signalIndex} 标识全局信号,替换原有单文件 SignalSet。

- 标签页:新增 TabBar 组件,支持新建/切换/关闭标签页;每页独立保存信号勾选、
  颜色/线宽/显示方式、网格划分、视图范围与时间光标,互不影响。
- 网格收敛:可见性变更统一经 TabConfig 的 Set*Visible 接口并同步重算网格,
  取消勾选后对应的空网格立即回收(修复此前留下空轨道的问题)。
- 多文件分组:左侧面板改为「文件分组 + 信号」树形列表,分组可折叠、可整组勾选,
  分组复选框支持全选/部分/未选三态;新加载文件的信号默认未勾选,由用户自行选择。
- 来源标识:信号行、样式编辑区标题、波形图例与光标读数在跨文件时标注来源文件名。
- 交互补充:面板滚动条支持点击跳转与拖拽,切换标签页自动滚动到已选信号。
- 工具栏精简为「打开 CSV/适应视图/放大/缩小/合并网格/每信号一格/全选信号/清空勾选」,
  按钮宽度改为按实际文本像素测量;打开对话框支持多选文件,拖放支持批量载入。
- 新增 TestData/[2].CSV 作为跨文件对比测试数据。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-09-29 15:48:04 +08:00
1 parent 132b305544
commit 9c6c374cce
21 files changed
+4086 -1461

No files matched your search

+138 -20
View File
@@ -7,16 +7,20 @@
#include <array>
#include <string>
#include <vector>
#include "DataModel.hpp"
#include "PaintUtil.hpp"
#include "SignalSet.hpp"
#include "Theme.hpp"
namespace jdi
{
/**
* @brief 左侧信号面板:信号列表、可见性开关与样式编辑。
* @brief 左侧信号面板:按文件分组的信号树、可见性勾选与样式编辑。
*
* 面板只读取数据层(DataSet),全部显示配置读写均落在当前标签页(TabConfig)上,
* 因此不同标签页的信号勾选与样式互不影响。
*/
class SignalPanel
{
@@ -32,10 +36,28 @@ namespace jdi
void Setup(HWND host, const UiFonts* fonts, const Theme* theme);
/**
* @brief 绑定数据集。
* @param [IN] document 数据集指针,允许为空。
* @brief 绑定数据层与当前标签页配置。
* @param [IN] dataSet 数据集合,允许为空。
* @param [IN] tab 当前标签页配置,允许为空。
*/
void SetDocument(SignalSet* document);
void SetContext(const DataSet* dataSet, TabConfig* tab);
/**
* @brief 标记列表结构失效(数据或折叠状态变化后调用)。
*/
void InvalidateRows()
{
m_rowsDirty = true;
}
/**
* @brief 判断列表内容是否需要纵向滚动。
* @return 内容超出可视区域返回 true。
*/
bool NeedsScrollBar() const
{
return m_contentHeight > m_listRect.Height + 0.5f;
}
/**
* @brief 设置 DPI 缩放比例。
@@ -88,7 +110,7 @@ namespace jdi
bool OnDoubleClick(int x, int y);
/**
* @brief 处理鼠标右键按下(弹出信号上下文菜单)。
* @brief 处理鼠标右键按下(弹出上下文菜单)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
@@ -116,7 +138,33 @@ namespace jdi
*/
bool HitTest(int x, int y) const;
/**
* @brief 将列表滚动到指定信号所在位置。
* @param [IN] ref 目标信号引用。
*/
void ScrollToSignal(const SignalRef& ref);
private:
/**
* @brief 列表行的类型。
*/
enum class RowKind : int
{
FILE = 0, /**< 文件分组标题行。 */
SIGNAL = 1 /**< 信号行。 */
};
/**
* @brief 行的可交互区域。
*/
enum class RowPart : int
{
NONE = 0, /**< 未命中。 */
DISCLOSURE, /**< 折叠三角。 */
CHECK, /**< 复选框。 */
BODY /**< 行主体。 */
};
/**
* @brief 样式编辑区中的可点击条目类型。
*/
@@ -131,8 +179,34 @@ namespace jdi
};
/**
* @brief 计算列表区域内容高度。
* @return 内容高度。
* @brief 列表中的一行(文件分组或信号)。
*/
struct ListRow
{
RowKind kind = RowKind::SIGNAL; /**< 行类型。 */
int fileIndex = -1; /**< 所属文件下标。 */
int signalIndex = -1; /**< 信号下标(仅信号行有效)。 */
float height = 0.0f; /**< 行高(像素)。 */
};
/**
* @brief 重新构建列表行集合。
*/
void BuildRows();
/**
* @brief 在需要时重建列表行集合。
*/
void EnsureRows();
/**
* @brief 依据内容高度重算滚动条可见性与列表可用宽度。
*/
void RefreshScrollBar();
/**
* @brief 计算列表内容总高度。
* @return 内容高度(像素)。
*/
float ContentHeight() const;
@@ -142,11 +216,13 @@ namespace jdi
void ClampScroll();
/**
* @brief 查询指定坐标对应的信号行下标。
* @brief 查询坐标命中的行下标与行内区域。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @param [OUT] part 行内区域类型。
* @return 行下标,未命中返回 -1。
*/
int RowAtY(int y) const;
int HitRow(int x, int y, RowPart& part);
/**
* @brief 计算行的纵向矩形。
@@ -156,11 +232,18 @@ namespace jdi
gd::RectF RowRect(int row) const;
/**
* @brief 绘制列表区域的单行。
* @brief 绘制文件分组标题行。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] row 行下标。
*/
void PaintRow(gd::Graphics& g, int row);
void PaintFileRow(gd::Graphics& g, int row);
/**
* @brief 绘制信号行。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] row 行下标。
*/
void PaintSignalRow(gd::Graphics& g, int row);
/**
* @brief 绘制样式编辑区。
@@ -168,13 +251,33 @@ namespace jdi
*/
void PaintStyleSection(gd::Graphics& g);
/**
* @brief 绘制列表滚动条。
* @param [IN,OUT] g 绘图上下文。
*/
void PaintScrollBar(gd::Graphics& g);
/**
* @brief 计算滚动条滑块矩形。
* @return 滑块矩形,无需滚动时返回空矩形。
*/
gd::RectF ScrollThumbRect() const;
/**
* @brief 弹出信号右键菜单。
* @param [IN] signalIndex 目标信号下标。
* @param [IN] ref 目标信号引用。
* @param [IN] screenX 屏幕坐标横坐标。
* @param [IN] screenY 屏幕坐标纵坐标。
*/
void ShowSignalMenu(int signalIndex, int screenX, int screenY);
void ShowSignalMenu(const SignalRef& ref, int screenX, int screenY);
/**
* @brief 弹出文件分组右键菜单。
* @param [IN] fileIndex 目标文件下标。
* @param [IN] screenX 屏幕坐标横坐标。
* @param [IN] screenY 屏幕坐标纵坐标。
*/
void ShowFileMenu(int fileIndex, int screenX, int screenY);
/**
* @brief 弹出网格分配菜单。
@@ -182,15 +285,22 @@ namespace jdi
void ShowLaneMenu();
/**
* @brief 判断全部信号是否均为可见状态。
* @return 全部可见返回 true。
* @brief 判断当前标签页中全部信号是否均为可见状态。
* @return 全部可见且至少有一条信号返回 true。
*/
bool AreAllSignalsVisible() const;
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
/**
* @brief 统计全部信号数量。
* @return 信号总数。
*/
int TotalSignalCount() const;
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
const Theme* m_theme = nullptr; /**< 主题资源。 */
SignalSet* m_document = nullptr; /**< 数据集。 */
const DataSet* m_dataSet = nullptr; /**< 数据集合。 */
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
float m_scale = 1.0f; /**< DPI 缩放比例。 */
gd::RectF m_area; /**< 面板整体区域。 */
@@ -199,16 +309,24 @@ namespace jdi
gd::RectF m_styleRect; /**< 样式编辑区域。 */
gd::RectF m_toggleAllRect; /**< 全部显示/隐藏按钮。 */
gd::RectF m_laneButtonRect; /**< 网格选择按钮。 */
gd::RectF m_scrollBarRect; /**< 滚动条区域。 */
std::array<gd::RectF, 12> m_colorRects; /**< 颜色色块矩形。 */
std::array<gd::RectF, 4> m_widthRects; /**< 线宽按钮矩形。 */
std::array<gd::RectF, 4> m_modeRects; /**< 绘制方式按钮矩形。 */
int m_colorCount = 0; /**< 实际可用的颜色数量。 */
float m_widthRowLabelX = 0.0f; /**< 样式标签列起始横坐标。 */
float m_styleRowHeight = 0.0f; /**< 样式行高。 */
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
float m_contentHeight = 0.0f; /**< 列表内容总高度。 */
float m_listFullWidth = 0.0f; /**< 列表区不含滚动条的完整宽度。 */
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
float m_scrollOffset = 0.0f; /**< 列表滚动偏移量。 */
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
int m_hoverRow = -1; /**< 当前悬停行。 */
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */
};