数据层大重构:文件→分组 / 时间轴一等公民 / 加载器抽象 + CSV 多时间轴 + MF4 支持

一、数据模型解耦(详见 Documents/refactor_report_2026-10-15.md 与 agent_mem §9.27)
1. DataFile → SignalGroup:删除 filePath / fileName / time / timeColumnName / timeMonotonic /
   minTime / maxTime —— 「分组」是应用内的逻辑容器,加载完成后与磁盘文件再无关系。
2. 新增 TimeAxis(name / time / minTime / maxTime / monotonic / finiteCount),一个分组可含 1..N 条;
   SignalData 新增 axisIndex 指向所属时间轴,采样值与时间的对应关系自包含。
3. SignalData 删除冗余的 rawHeader(name 的副本)与 unit(由列名猜出、全仓无人使用);
   名字只保留 name(源名称)+ displayName(用户重命名),Title() 统一展示。
4. SignalRef::fileIndex → groupIndex;DataSet:AddGroup/RemoveGroup/GroupAt/GroupCount/Groups()/
   SetGroupDisplayName/RemapSelectionAfterGroupRemoval + 新增 Axis(ref);
   TabConfig:SetGroupVisible/GroupHasVisible/GroupAllVisible/RemapAfterGroupRemoval +
   RowKind::GROUP / CMD_GROUP_*。
5. 采样与统计 API 首参由 const DataFile& 改为 const TimeAxis&:
   SampleSignalAt / SampleDisplayedSignalAt / FindNearestSample(Utilities)与
   SampleSpacingPixels / VisibleIndexRange / EdgeIndexRange / MeanFilterValueAt / WeakPeakValueAt /
   EdgeCountsFor / EdgeCenterCountsFor / EdgeScanFor(PlotView)。
6. 视图层:ResolvedSignal = { data, style, group, axis, visible };PolylineCacheEntry::file→group;
   EdgeScanState::file→axis(统计身份 = 时间轴 + 信号);
   ComputeVisibleTimeBounds 只统计可见信号真正用到的时间轴。

二、加载层抽象(格式与模型彻底解耦)
- 新增 Utilities/Inc/DataTable.hpp:DataTable(通用列优先表格)+ LoadProgress/Info +
  TableBlock(一个时间轴块)+ LoadedSource(源文件全部块)。
- 新增 Utilities/Inc|Src/DataLoader.*:唯一入口 LoadSignalSource 按扩展名分发 +
  IsSupportedSignalFile + SignalFileDialogFilter。
- 新增 Utilities/Inc|Src/CsvLoader.*:CSV 解析 + 多时间轴切分 + 各轴按自己的时间列裁剪。
- 新增 Utilities/Inc|Src/Mf4Loader.*:ASAM MDF v4 解析。
- 删除 Utilities/Inc/CsvTable.hpp 与 Utilities/Src/CsvTable.cpp。
- 唯一装配点 BuildSignalGroup(LoadedSource&&, paletteBase, group, error, progress):
  时间轴去重合并 + 信号命名 + 调色板取色 + 统计与粗粒度包络;新增格式只需写一个加载器。

三、CSV 多时间轴(修正用户报告的缺陷)
- 只有名为 t/time/timestamp/simulation time/时间/时间轴 的列是时间轴(判据收紧,
  不再用「包含 time」以免把 timeout 误判成时间轴)。
- 每个时间列开启一个新块,其后直到下一个时间列之前的列都属于该块;各块按自己的时间列裁剪
  ⇒ 支持各轴长度不同(Multi_Timeline.CSV 实测切出 3 条轴,长度 7/4/7);
  **不再把其它 time 列当成信号显示**。
- 完全没有时间列时保留旧行为(第一列当时间轴,单轴)。

四、MF4 支持(新功能)
- 加载后新建一个分组、组名 = 文件名,文件内全部信号归入该分组。
- 每个通道组(CG)是一条时间轴,内容相同的时间轴自动合并(实测 27 个 CG → 3 条轴 / 124 条信号)。
- 实现 MDF v4 块解析(HD/DG/CG/CN/CC/DT)、1–64 位整数(任意位偏移、含符号扩展)与
  32/64 位 IEEE 浮点解码、无效位(inval bit)处理、线性换算 phys = cc_val[0] + cc_val[1]*raw。
- TX 文本块同时兼容规范写法(U32 长度 + UTF-16/UTF-8)与 CANape 的「无长度前缀 NUL 结尾裸文本」。
- 未实现(跳过该组):压缩块 ##DZ/##HL/##SD、##DL 数据列表、字符串/MIME 通道。

五、顺带修复
- Application::Initialize:窗口类已注册(ERROR_CLASS_ALREADY_EXISTS)时复用而不是弹模态错误框
  (旧实现让同一进程第二次构造 Application 永久阻塞);Shutdown 注销窗口类并销毁窗口。
- UI 文案统一到「分组」:选中该分组全部信号 / 取消该分组全部信号 / 关闭该分组(从工作区移除)/
  请关闭部分分组或信号后重试。/ 尚未加载数据,请打开数据文件 / 打开数据文件以查看波形 /
  (可同时加载多个文件,各自成为一个分组);对话框过滤器增加 *.mf4/*.mdf。

六、验证(全部离屏:BELOW_NORMAL 优先级 + SW_HIDE 窗口 + 只注入合成消息)
- 新增 .preflight/perf/datacheck_main.cpp:69/69 通过(单轴 CSV 回归、多时间轴 CSV、MF4 组名/信号数/
  时间轴/主时间轴 ns→s/setSpeed 首样本与原始字节人工核对、DataSet 不变量、派生信号沿用源轴、
  MF4 集成 124 信号并一格 60~68ms/帧)。
- 新增 .preflight/perf/edgecheck_main.cpp(ASCII-only,替代被编码破坏的 panelcheck):46/46 通过
  (「调整」符号规则走真实菜单路径、边沿统计按帧推进/不饿死/逐位一致/无鼠标输入也完成、
  面板两列等宽与信号名限宽、弱峰复位控件)。
- 既有 .preflight/perf/verify_main.cpp(已适配新 API):47/47,另有 2 条本机未同步的旧规则断言
  (derived offsetTime kept / mouse in lane1 lower half),与本轮无关。
- make clean && make all 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-15 03:48:58 +08:00
1 parent fba40ee1a2
commit 6eedbe3c5d
18 files changed
+2545 -817

No files matched your search

+7 -7
View File
@@ -28,9 +28,9 @@ namespace jdi
CMD_SIGNAL_SOLO = 2, /**< 仅显示该信号。 */
CMD_SIGNAL_SHOW_ALL = 3, /**< 显示全部信号。 */
CMD_SIGNAL_HIDE_ALL = 4, /**< 隐藏全部信号。 */
CMD_FILE_SHOW_ALL = 10, /**< 选中该文件全部信号。 */
CMD_FILE_HIDE_ALL = 11, /**< 取消该文件全部信号。 */
CMD_FILE_REMOVE = 12, /**< 从工作区移除该文件。 */
CMD_GROUP_SHOW_ALL = 10, /**< 选中该文件全部信号。 */
CMD_GROUP_HIDE_ALL = 11, /**< 取消该文件全部信号。 */
CMD_GROUP_REMOVE = 12, /**< 从工作区移除该文件。 */
CMD_COLOR_BASE = 100, /**< 预设颜色基准(配合色块下标)。 */
CMD_MODE_BASE = 200, /**< 绘制样式基准(配合下标)。 */
CMD_LANE_BASE = 300, /**< 网格归属基准(配合下标)。 */
@@ -315,7 +315,7 @@ namespace jdi
*/
enum class RowKind : int
{
FILE = 0, /**< 文件分组标题行。 */
GROUP = 0, /**< 文件分组标题行。 */
SIGNAL = 1 /**< 信号行。 */
};
@@ -359,7 +359,7 @@ namespace jdi
struct ListRow
{
RowKind kind = RowKind::SIGNAL; /**< 行类型。 */
int fileIndex = -1; /**< 所属文件下标。 */
int groupIndex = -1; /**< 所属文件下标。 */
int signalIndex = -1; /**< 信号下标(仅信号行有效)。 */
float height = 0.0f; /**< 行高(像素)。 */
};
@@ -478,11 +478,11 @@ namespace jdi
/**
* @brief 弹出文件分组右键菜单。
* @param [IN] fileIndex 目标文件下标。
* @param [IN] groupIndex 目标文件下标。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
*/
void OpenFileMenu(int fileIndex, int x, int y);
void OpenFileMenu(int groupIndex, int x, int y);
/**
* @brief 弹出网格归属菜单(由信号行的网格徽标触发)。
+63 -63
View File
@@ -448,17 +448,17 @@ namespace jdi
const float signalHeight = kSignalRowHeight * m_scale;
const bool filtering = !m_filterText.empty();
const std::vector<DataFile>& files = m_dataSet->Files();
for (std::size_t fileIndex = 0; fileIndex < files.size(); ++fileIndex)
const std::vector<SignalGroup>& files = m_dataSet->Groups();
for (std::size_t groupIndex = 0; groupIndex < files.size(); ++groupIndex)
{
// 筛选时先挑出本组内匹配的信号;整组都没有匹配则**不显示该组**。
std::vector<int> matched;
if (filtering)
{
const std::size_t signalCount = files[fileIndex].signals.size();
const std::size_t signalCount = files[groupIndex].signals.size();
for (std::size_t signalIndex = 0; signalIndex < signalCount; ++signalIndex)
{
if (FilterMatchesSignal(files[fileIndex].signals[signalIndex]))
if (FilterMatchesSignal(files[groupIndex].signals[signalIndex]))
{
matched.push_back(static_cast<int>(signalIndex));
}
@@ -470,13 +470,13 @@ namespace jdi
}
ListRow fileRow;
fileRow.kind = RowKind::FILE;
fileRow.fileIndex = static_cast<int>(fileIndex);
fileRow.kind = RowKind::GROUP;
fileRow.groupIndex = static_cast<int>(groupIndex);
fileRow.height = fileHeight;
m_rows.push_back(fileRow);
// 筛选结果**默认展开**(忽略折叠状态),便于直接看到匹配项。
if (!filtering && m_tab->IsCollapsed(static_cast<int>(fileIndex)))
if (!filtering && m_tab->IsCollapsed(static_cast<int>(groupIndex)))
{
continue;
}
@@ -487,7 +487,7 @@ namespace jdi
{
ListRow signalRow;
signalRow.kind = RowKind::SIGNAL;
signalRow.fileIndex = static_cast<int>(fileIndex);
signalRow.groupIndex = static_cast<int>(groupIndex);
signalRow.signalIndex = signalIndex;
signalRow.height = signalHeight;
m_rows.push_back(signalRow);
@@ -495,12 +495,12 @@ namespace jdi
continue;
}
const std::size_t signalCount = files[fileIndex].signals.size();
const std::size_t signalCount = files[groupIndex].signals.size();
for (std::size_t signalIndex = 0; signalIndex < signalCount; ++signalIndex)
{
ListRow signalRow;
signalRow.kind = RowKind::SIGNAL;
signalRow.fileIndex = static_cast<int>(fileIndex);
signalRow.groupIndex = static_cast<int>(groupIndex);
signalRow.signalIndex = static_cast<int>(signalIndex);
signalRow.height = signalHeight;
m_rows.push_back(signalRow);
@@ -639,7 +639,7 @@ namespace jdi
{
const int rowIndex = static_cast<int>(index);
const gd::RectF rect = RowRect(rowIndex);
const bool isFile = (row.kind == RowKind::FILE);
const bool isFile = (row.kind == RowKind::GROUP);
const float baseX = rect.X + kPadding * m_scale + (isFile ? 0.0f : kIndent * m_scale);
const gd::RectF disclosureRect(baseX, rect.Y, kDisclosureWidth * m_scale, rect.Height);
@@ -668,7 +668,7 @@ namespace jdi
}
else
{
const SignalRef ref{row.fileIndex, row.signalIndex};
const SignalRef ref{row.groupIndex, row.signalIndex};
const bool badgeVisible = (m_tab != nullptr) && m_tab->IsVisible(ref);
// ✕ 命中优先于复选框:两者紧邻,顺序保证不会互相抢命中。
@@ -708,7 +708,7 @@ namespace jdi
for (std::size_t index = 0; index < m_rows.size(); ++index)
{
const ListRow& row = m_rows[index];
if (row.kind == RowKind::SIGNAL && row.fileIndex == ref.fileIndex &&
if (row.kind == RowKind::SIGNAL && row.groupIndex == ref.groupIndex &&
row.signalIndex == ref.signalIndex)
{
if (top < m_scrollOffset)
@@ -968,7 +968,7 @@ namespace jdi
if (m_dataSet == nullptr || m_dataSet->Empty())
{
UiText(g, L"尚未加载数据,请打开 CSV 文件", m_fonts->uiSmall, m_listRect, m_theme->textMuted,
UiText(g, L"尚未加载数据,请打开数据文件", m_fonts->uiSmall, m_listRect, m_theme->textMuted,
TextAlign::CENTER);
}
else if (m_rows.empty() && !m_filterText.empty())
@@ -987,7 +987,7 @@ namespace jdi
{
continue;
}
if (m_rows[static_cast<std::size_t>(row)].kind == RowKind::FILE)
if (m_rows[static_cast<std::size_t>(row)].kind == RowKind::GROUP)
{
PaintFileRow(g, row);
}
@@ -1025,8 +1025,8 @@ namespace jdi
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const DataFile* file = m_dataSet->FileAt(item.fileIndex);
if (file == nullptr)
const SignalGroup* group = m_dataSet->GroupAt(item.groupIndex);
if (group == nullptr)
{
return;
}
@@ -1042,7 +1042,7 @@ namespace jdi
const float baseX = rect.X + kPadding * m_scale;
const float centerY = rect.Y + rect.Height * 0.5f;
const bool collapsed = m_tab->IsCollapsed(item.fileIndex);
const bool collapsed = m_tab->IsCollapsed(item.groupIndex);
// 折叠三角。
{
@@ -1057,15 +1057,15 @@ namespace jdi
// 分组复选框(三态:全选 / 部分 / 未选)。
const gd::RectF checkRect(baseX + kDisclosureWidth * m_scale, centerY - kCheckSize * m_scale * 0.5f,
kCheckSize * m_scale, kCheckSize * m_scale);
const bool allVisible = m_tab->FileAllVisible(item.fileIndex);
const bool anyVisible = m_tab->FileHasVisible(item.fileIndex);
const bool allVisible = m_tab->GroupAllVisible(item.groupIndex);
const bool anyVisible = m_tab->GroupHasVisible(item.groupIndex);
UiDrawCheckBox(g, checkRect, allVisible ? 1 : (anyVisible ? 2 : 0), 3.0f * m_scale);
const gd::RectF closeRect = FileCloseRect(row);
// 分组标题:左侧为“组X”标签,右侧以绘制的竖线分隔分组名(不使用字符),再显示可重命名的名称。
const std::wstring groupLabel = L"组" + std::to_wstring(item.fileIndex + 1);
const std::wstring stats = std::to_wstring(file->signals.size()) + L" 项";
const std::wstring groupLabel = L"组" + std::to_wstring(item.groupIndex + 1);
const std::wstring stats = std::to_wstring(group->signals.size()) + L" 项";
const float groupWidth = FileGroupLabelWidth(row);
const float statsWidth = UiMeasureTextWidth(m_fonts->uiSmall, stats);
const float statsRight = closeRect.X - 6.0f * m_scale;
@@ -1091,7 +1091,7 @@ namespace jdi
}
else
{
UiText(g, UiEllipsize(file->Title(), m_fonts->uiBold, nameWidth), m_fonts->uiBold,
UiText(g, UiEllipsize(group->Title(), m_fonts->uiBold, nameWidth), m_fonts->uiBold,
gd::RectF(nameAreaLeft, rect.Y, nameWidth, rect.Height), m_theme->textStrong,
TextAlign::LEFT);
}
@@ -1116,7 +1116,7 @@ namespace jdi
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalRef ref{item.groupIndex, item.signalIndex};
const SignalData* signal = m_dataSet->Resolve(ref);
const SignalStyle* style = StyleOf(ref);
if (signal == nullptr || style == nullptr)
@@ -1451,7 +1451,7 @@ namespace jdi
}
const gd::RectF rect = RowRect(row);
const bool isFile = (m_rows[static_cast<std::size_t>(row)].kind == RowKind::FILE);
const bool isFile = (m_rows[static_cast<std::size_t>(row)].kind == RowKind::GROUP);
const float baseX = rect.X + kPadding * m_scale + (isFile ? 0.0f : kIndent * m_scale);
const float checkRight = baseX + kDisclosureWidth * m_scale + kCheckSize * m_scale;
@@ -1468,7 +1468,7 @@ namespace jdi
{
left = checkRight + 6.0f * m_scale + kSwatchSize * m_scale + 6.0f * m_scale;
// 行尾的可用右界:有网格徽标时让位给徽标,否则可直接延伸到行尾 ✕ 之前。
const SignalRef ref{m_rows[static_cast<std::size_t>(row)].fileIndex,
const SignalRef ref{m_rows[static_cast<std::size_t>(row)].groupIndex,
m_rows[static_cast<std::size_t>(row)].signalIndex};
const bool badgeVisible = (m_tab != nullptr) && m_tab->IsVisible(ref) &&
(m_tab->LaneOfSignal(ref) >= 0);
@@ -1489,7 +1489,7 @@ namespace jdi
{
return 0.0f;
}
const std::wstring label = L"组" + std::to_wstring(m_rows[static_cast<std::size_t>(row)].fileIndex + 1);
const std::wstring label = L"组" + std::to_wstring(m_rows[static_cast<std::size_t>(row)].groupIndex + 1);
return UiMeasureTextWidth(m_fonts->uiBold, label);
}
@@ -1534,7 +1534,7 @@ namespace jdi
return false;
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const SignalData* signal = m_dataSet->Resolve(SignalRef{item.fileIndex, item.signalIndex});
const SignalData* signal = m_dataSet->Resolve(SignalRef{item.groupIndex, item.signalIndex});
if (signal == nullptr)
{
return false;
@@ -1548,12 +1548,12 @@ namespace jdi
{
return false;
}
const DataFile* file = m_dataSet->FileAt(m_rows[static_cast<std::size_t>(row)].fileIndex);
if (file == nullptr)
const SignalGroup* group = m_dataSet->GroupAt(m_rows[static_cast<std::size_t>(row)].groupIndex);
if (group == nullptr)
{
return false;
}
initial = file->Title();
initial = group->Title();
break;
}
default:
@@ -1638,7 +1638,7 @@ namespace jdi
break;
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
m_dataSetWritable->SetSignalDisplayName(item.fileIndex, item.signalIndex, trimmed);
m_dataSetWritable->SetSignalDisplayName(item.groupIndex, item.signalIndex, trimmed);
break;
}
case StyleEditField::FILE_NAME:
@@ -1647,7 +1647,7 @@ namespace jdi
{
break;
}
m_dataSetWritable->SetFileDisplayName(m_rows[static_cast<std::size_t>(row)].fileIndex, trimmed);
m_dataSetWritable->SetGroupDisplayName(m_rows[static_cast<std::size_t>(row)].groupIndex, trimmed);
break;
}
default:
@@ -2458,12 +2458,12 @@ namespace jdi
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
if (item.kind == RowKind::FILE)
if (item.kind == RowKind::GROUP)
{
if (part == RowPart::CHECK)
{
const bool allVisible = m_tab->FileAllVisible(item.fileIndex);
m_tab->SetFileVisible(item.fileIndex, !allVisible);
const bool allVisible = m_tab->GroupAllVisible(item.groupIndex);
m_tab->SetGroupVisible(item.groupIndex, !allVisible);
m_rowsDirty = true;
return true;
}
@@ -2475,12 +2475,12 @@ namespace jdi
m_pendingCloseRow = row;
return true;
}
m_tab->ToggleCollapsed(item.fileIndex);
m_tab->ToggleCollapsed(item.groupIndex);
m_rowsDirty = true;
return true;
}
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalRef ref{item.groupIndex, item.signalIndex};
// ✕:用户明确表示不再需要该信号 → 交由宿主从数据层移除并释放其采样内存。
// 必须在 SelectSignal 之前处理,避免选中一条即将被删除的信号;
@@ -2542,12 +2542,12 @@ namespace jdi
{
if (pendingPart == RowPart::CLOSE)
{
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(item.fileIndex), 0);
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(item.groupIndex), 0);
}
else
{
// wParam = 文件下标,lParam = 文件内信号下标(见 Application 的消息处理)。
PostMessageW(m_host, WM_APP + 2, static_cast<WPARAM>(item.fileIndex),
PostMessageW(m_host, WM_APP + 2, static_cast<WPARAM>(item.groupIndex),
static_cast<LPARAM>(item.signalIndex));
}
}
@@ -2616,7 +2616,7 @@ namespace jdi
if (row >= 0 && part == RowPart::LANE_BADGE)
{
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalRef ref{item.groupIndex, item.signalIndex};
if (ref == m_pendingLaneRef)
{
const gd::RectF badge = SignalBadgeRect(row);
@@ -2660,11 +2660,11 @@ namespace jdi
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
// 分组行:双击折叠三角用于折叠,双击名称区域进入重命名。
if (item.kind == RowKind::FILE)
if (item.kind == RowKind::GROUP)
{
if (part == RowPart::DISCLOSURE)
{
m_tab->ToggleCollapsed(item.fileIndex);
m_tab->ToggleCollapsed(item.groupIndex);
m_rowsDirty = true;
return true;
}
@@ -2683,7 +2683,7 @@ namespace jdi
return false;
}
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalRef ref{item.groupIndex, item.signalIndex};
SelectSignal(ref);
m_rowsDirty = true;
return BeginEdit(StyleEditField::SIGNAL_NAME, row);
@@ -2704,13 +2704,13 @@ namespace jdi
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
if (item.kind == RowKind::FILE)
if (item.kind == RowKind::GROUP)
{
OpenFileMenu(item.fileIndex, x, y);
OpenFileMenu(item.groupIndex, x, y);
return true;
}
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalRef ref{item.groupIndex, item.signalIndex};
SelectSignal(ref);
OpenSignalMenu(ref, x, y);
return true;
@@ -2825,37 +2825,37 @@ namespace jdi
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, x, y, items);
}
void SignalPanel::OpenFileMenu(int fileIndex, int x, int y)
void SignalPanel::OpenFileMenu(int groupIndex, int x, int y)
{
if (m_menu == nullptr || m_tab == nullptr || m_dataSet == nullptr)
{
return;
}
const DataFile* file = m_dataSet->FileAt(fileIndex);
if (file == nullptr)
const SignalGroup* group = m_dataSet->GroupAt(groupIndex);
if (group == nullptr)
{
return;
}
m_menuTargetFile = fileIndex;
m_menuTargetFile = groupIndex;
m_menuTargetSignal = SignalRef();
std::vector<MenuItem> items;
MenuItem header;
header.kind = MenuItemKind::HEADER;
header.label = file->Title();
header.label = group->Title();
items.push_back(header);
MenuItem showAll;
showAll.label = L"选中该文件全部信号";
showAll.id = CMD_FILE_SHOW_ALL;
showAll.label = L"选中该分组全部信号";
showAll.id = CMD_GROUP_SHOW_ALL;
items.push_back(showAll);
MenuItem hideAll;
hideAll.label = L"取消该文件全部信号";
hideAll.id = CMD_FILE_HIDE_ALL;
hideAll.label = L"取消该分组全部信号";
hideAll.id = CMD_GROUP_HIDE_ALL;
items.push_back(hideAll);
MenuItem separator;
@@ -2863,8 +2863,8 @@ namespace jdi
items.push_back(separator);
MenuItem remove;
remove.label = L"关闭该文件(从工作区移除)";
remove.id = CMD_FILE_REMOVE;
remove.label = L"关闭该分组(从工作区移除)";
remove.id = CMD_GROUP_REMOVE;
items.push_back(remove);
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
@@ -3086,23 +3086,23 @@ namespace jdi
m_rowsDirty = true;
needRelayout = true;
break;
case CMD_FILE_SHOW_ALL:
case CMD_GROUP_SHOW_ALL:
if (m_menuTargetFile >= 0)
{
m_tab->SetFileVisible(m_menuTargetFile, true);
m_tab->SetGroupVisible(m_menuTargetFile, true);
m_rowsDirty = true;
needRelayout = true;
}
break;
case CMD_FILE_HIDE_ALL:
case CMD_GROUP_HIDE_ALL:
if (m_menuTargetFile >= 0)
{
m_tab->SetFileVisible(m_menuTargetFile, false);
m_tab->SetGroupVisible(m_menuTargetFile, false);
m_rowsDirty = true;
needRelayout = true;
}
break;
case CMD_FILE_REMOVE:
case CMD_GROUP_REMOVE:
if (m_menuTargetFile >= 0 && m_host != nullptr)
{
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(m_menuTargetFile), 0);