一、重采样面板统计信息不再被省略为 `…`
- 根因:EdgePanelWidth 里数值列按"典型样例"(1h 1min 2s(1.23456 GHz))估宽,
真实文本(16ms 182.614us(61.795 Hz))更长 => 绘制矩形不够宽 => 尾部被裁。
- 新增 FormatIntervalWithFrequency() 作为「间隔(频率)」文本的唯一实现,
宽度计算与绘制共用它;EdgePanelWidth 改为逐信号实测真实文本宽度取最大值。
二、筛选状态下组复选框只作用于「筛选出来的信号」
- TabConfig 新增 SetGroupVisibleSubset / GroupAllVisibleSubset / GroupHasVisibleSubset,
只读写掩码内元素,未通过筛选的信号可见性保持不变。
- SignalPanel::BuildFilterMask 提供逐信号掩码(未筛选时返回 false,走整组语义)。
- 组复选框的点击、绘制、行位图缓存键三处统一改用子集判定
(只改绘制不改缓存键会导致勾选后复选框不刷新)。
三、新增实验性功能「选区截取」(EdgePanelKind::CLIP = 6,PLOT_CMD_LANE_CLIP = 14)
- 菜单位置:实验性功能二级菜单,「重采样」之下、「自由标记」上方,带勾选。
- 面板:逐信号一行(信号色叠底条目栏 + 右侧复选框,不可展开),内容可滚动;
底部两个不滚动的按钮「截取为新信号」与「原始信号截取」;
未启用双光标时两个按钮为空矩形并给出提示。
- 截取为新信号:区间内原始数据(不烘焙)+ 继承源信号整套调整,
名称为 <源信号名> (AAAA - BBBB)(显示时刻、保留三位小数、带 s),插入源信号下方,
使用 AppendGroupAxis 新建的独立时间轴。
- 原始信号截取:DataSet::ClipSignalRange 就地裁剪时间轴与采样,
用新建+swap+shrink_to_fit 真正归还内存,并重算统计与轴的 minTime/maxTime/monotonic。
调用前先 PauseDerivedAsync() 避免后台派生读到已释放采样。
- 时间轴偏移参与区间比较:双光标是显示时刻(time+offsetTime),
BuildClippedSignal 比较时加偏移,ClipSignalInPlace 换算回采样时刻(low-offsetTime)。
- 同步点已全部补齐:LaneCarry(三处)/SplitLane/PruneLaneSignalItems/RemapAfterSignalInsert/
purgeRefs/PanelSlotKey/CollectDebugBoxes/SceneContentHash/kEdgePanelKinds/kOrder/
EdgePanelLayoutFor/EDGE_PANEL_MOVE/m_pendingPanelClose/MEAN_SCROLL/滚轮/悬停/
SetLanePanelCollapsed/LanePanelCollapsed/QueryCursor。
make release 零 warning,产物 860,160 B。
477 lines
18 KiB
C++
477 lines
18 KiB
C++
#ifndef __DATAMODEL_INTERNAL_HPP__
|
||
#define __DATAMODEL_INTERNAL_HPP__
|
||
|
||
// DataModel 组件内部共享的常量与工具函数(私有头,仅本组件的 .cpp 使用)。
|
||
//
|
||
// 本组件的实现按功能拆成多个协作文件,它们共用这一份内部工具集。
|
||
//
|
||
// ⚠️ 为让多个翻译单元都能使用,原匿名命名空间改为**具名**命名空间 `dmdetail`,
|
||
// 并把其中每个函数与常量标为 `inline` —— 单一定义、单一副本,不会产生代码重复。
|
||
// 各实现文件在包含本头之后用 `using namespace dmdetail;` 引入这些工具,
|
||
// 因此函数体里原有的非限定调用一行都不用改。
|
||
|
||
#include <windows.h>
|
||
#include <objidl.h>
|
||
#include <gdiplus.h>
|
||
|
||
#include <algorithm>
|
||
#include <atomic>
|
||
#include <cmath>
|
||
#include <cstdio>
|
||
#include <cstdint>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
#include <cwctype>
|
||
#include <deque>
|
||
#include <limits>
|
||
#include <new>
|
||
#include <string>
|
||
#include <utility>
|
||
#include <vector>
|
||
|
||
#include "DataModel.hpp"
|
||
#include "Theme.hpp"
|
||
#include "Utf8Util.hpp"
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
namespace dmdetail
|
||
{
|
||
/**
|
||
* @brief DataModel 内部工具集(常量与自由函数)。
|
||
*
|
||
* 全部符号都是 `inline` ⇒ 多个翻译单元共用同一个定义:既不重复符号,也不产生多份代码。
|
||
* 不要把它们移回匿名命名空间(那样每个 .cpp 都会各自复制一份)。
|
||
*/
|
||
inline const double kMissing = std::numeric_limits<double>::quiet_NaN(); /**< 缺失值占位。 */
|
||
inline const double kDefaultLineWidth = 1.6; /**< 默认线宽。 */
|
||
|
||
/**
|
||
* @brief 为网格范围补充留白,避免波形紧贴边界。
|
||
*
|
||
* ⚠️ 实际规则实现在 `DataModel.hpp` 的 `PadValueRange()`:它同时被
|
||
* 「选区 Y 轴自适应」使用,两处必须逐位一致(否则两种自适应会算出不同区间)。
|
||
*
|
||
* @param [IN, OUT] yMin 范围下限。
|
||
* @param [IN, OUT] yMax 范围上限。
|
||
*/
|
||
inline void PadRange(double& yMin, double& yMax)
|
||
{
|
||
PadValueRange(yMin, yMax);
|
||
}
|
||
|
||
/**
|
||
* @brief 清掉网格里**不属于本网格信号**的逐信号条目(均值滤波 / 中心轴 / 弱峰调整 / 自由标记)。
|
||
*
|
||
* 这些条目按信号保存,但只有“该信号就在本网格里”时才有意义。
|
||
* 网格重建(合并 → 拆分)时上一帧的条目是按网格整体携带的,若不过滤就会出现:
|
||
* 拆分出来的新网格里仍然画着**其它信号**的固定点(用户 2026-10-21 报告:
|
||
* 第二个网格上出现“野生的”自由标记点,而浮动面板里又没有任何条目)。
|
||
*
|
||
* @param [IN,OUT] lane 目标网格(`signals` 必须已经就位)。
|
||
*/
|
||
inline void PruneLaneSignalItems(Lane& lane)
|
||
{
|
||
const auto inLane = [&lane](const SignalRef& ref)
|
||
{
|
||
return std::find(lane.signals.begin(), lane.signals.end(), ref) != lane.signals.end();
|
||
};
|
||
const auto prune = [&inLane](auto& items)
|
||
{
|
||
items.erase(std::remove_if(items.begin(), items.end(),
|
||
[&inLane](const auto& item) { return !inLane(item.ref); }),
|
||
items.end());
|
||
};
|
||
prune(lane.meanFilterItems);
|
||
prune(lane.edgeCenterItems);
|
||
prune(lane.weakPeakItems);
|
||
prune(lane.freeMarkerItems);
|
||
prune(lane.resampleItems);
|
||
prune(lane.clipItems);
|
||
lane.freeMarkerCollapsedGroups.erase(
|
||
std::remove_if(lane.freeMarkerCollapsedGroups.begin(), lane.freeMarkerCollapsedGroups.end(),
|
||
[&inLane](const SignalRef& ref) { return !inLane(ref); }),
|
||
lane.freeMarkerCollapsedGroups.end());
|
||
}
|
||
|
||
/**
|
||
* @brief 依据全部可见信号重新生成网格划分。
|
||
* @param [IN] visible 各信号的可见性(按 [分组][信号] 索引)。
|
||
* @param [IN] lanes 网格集合(原地更新)。
|
||
* @param [IN] mode 网格划分策略。
|
||
*/
|
||
inline void RebuildLanes(const std::vector<std::vector<bool>>& visible, std::vector<Lane>& lanes,
|
||
LaneMode mode)
|
||
{
|
||
// 随网格携带的“网格自身状态”(按信号继承):重建网格时不能丢。
|
||
struct LaneCarry
|
||
{
|
||
bool matched = false; /**< 是否找到该信号原本所在的网格(false = 新出现的信号)。 */
|
||
/**
|
||
* @brief 该信号是否是原网格的**首个信号**(= 这个网格的“实验性功能”持有者)。
|
||
*
|
||
* ⚠️ 实验性功能(边沿计数 / 中心轴 / 均值滤波 / 弱峰调整 / 自由标记)是**网格级**开关,
|
||
* 但一个网格可能与其它信号共用(合并网格)。若把“开着”的状态按信号逐个继承,
|
||
* 那么「合并网格 → 拆分网格」之后**每个**拆出来的网格都会带着这些功能
|
||
* (用户 2026-10-21 报告:第二个网格也自动启用了实验性功能、并出现了
|
||
* 其它信号的固定点镜像)。因此只有**首个信号**继承功能状态,其余信号视为默认(关闭)。
|
||
*/
|
||
bool owner = false;
|
||
bool fitYToSelection = false;
|
||
bool edgeCount = false;
|
||
double edgeValue = 0.0;
|
||
bool edgeValueValid = false;
|
||
LanePanelState edgePanel;
|
||
bool edgeCenter = false;
|
||
std::vector<Lane::EdgeCenterItem> edgeCenterItems;
|
||
LanePanelState edgeCenterPanel;
|
||
bool meanFilter = false;
|
||
std::vector<Lane::MeanFilterItem> meanFilterItems;
|
||
LanePanelState meanFilterPanel;
|
||
bool weakPeak = false;
|
||
std::vector<Lane::WeakPeakItem> weakPeakItems;
|
||
LanePanelState weakPeakPanel;
|
||
bool freeMarker = false;
|
||
bool freeMarkerCursor = false;
|
||
std::vector<Lane::FreeMarkerItem> freeMarkerItems;
|
||
LanePanelState freeMarkerPanel;
|
||
float freeMarkerOpacity = 1.0f;
|
||
float freeMarkerScroll = 0.0f;
|
||
int freeMarkerSelected = -1;
|
||
bool freeMarkerShowDelta = false;
|
||
bool freeMarkerShowEnum = false;
|
||
std::vector<SignalRef> freeMarkerCollapsedGroups;
|
||
/** @brief 「重采样」开关与逐信号参数(详见 `Lane::resample`)。 */
|
||
bool resample = false;
|
||
std::vector<Lane::ResampleItem> resampleItems;
|
||
LanePanelState resamplePanel;
|
||
float resampleScroll = 0.0f;
|
||
/** @brief 「选区截取」开关与逐信号目标标记(详见 `Lane::clip`)。 */
|
||
bool clip = false;
|
||
std::vector<Lane::ClipItem> clipItems;
|
||
LanePanelState clipPanel;
|
||
float clipScroll = 0.0f;
|
||
/** @brief 「均值滤波」面板内容的滚动偏移(详见 `Lane::meanFilterScroll`)。 */
|
||
float meanFilterScroll = 0.0f;
|
||
/** @brief 「中心轴边沿计数」面板内容的滚动偏移(详见 `Lane::edgeCenterScroll`)。 */
|
||
float edgeCenterScroll = 0.0f;
|
||
/** @brief 「弱峰调整」面板内容的滚动偏移(详见 `Lane::weakPeakScroll`)。 */
|
||
float weakPeakScroll = 0.0f;
|
||
/** @brief 「边沿计数」面板内容的滚动偏移(详见 `Lane::edgeCountScroll`)。 */
|
||
float edgeCountScroll = 0.0f;
|
||
};
|
||
|
||
// 先记下每个信号原来所在网格的网格自身状态:
|
||
// 勾选可见性会重建网格,若不携带,用户刚打开的开关会被悄悄清掉。
|
||
//
|
||
// ⚠️ 同时记下「重建之前是否**每个**网格都开着 `fitYToSelection`」:
|
||
// 那就是「全局 Y 轴自适应」的正式定义。新勾选信号产生的**新网格**必须继承它,
|
||
// 否则全局开关看着是开的、新网格却不按当前视界自适应(用户报告的缺陷)。
|
||
bool previousAllAutoFit = !lanes.empty();
|
||
for (const Lane& lane : lanes)
|
||
{
|
||
if (!lane.fitYToSelection)
|
||
{
|
||
previousAllAutoFit = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
std::vector<std::pair<SignalRef, LaneCarry>> previousCarry;
|
||
for (const Lane& lane : lanes)
|
||
{
|
||
LaneCarry carry;
|
||
carry.matched = true;
|
||
carry.fitYToSelection = lane.fitYToSelection;
|
||
carry.edgeCount = lane.edgeCount;
|
||
carry.edgeValue = lane.edgeValue;
|
||
carry.edgeValueValid = lane.edgeValueValid;
|
||
carry.edgePanel = lane.edgePanel;
|
||
carry.edgeCenter = lane.edgeCenter;
|
||
carry.edgeCenterItems = lane.edgeCenterItems;
|
||
carry.edgeCenterPanel = lane.edgeCenterPanel;
|
||
carry.edgeCenterScroll = lane.edgeCenterScroll;
|
||
carry.weakPeakScroll = lane.weakPeakScroll;
|
||
carry.edgeCountScroll = lane.edgeCountScroll;
|
||
carry.meanFilter = lane.meanFilter;
|
||
carry.meanFilterItems = lane.meanFilterItems;
|
||
carry.meanFilterPanel = lane.meanFilterPanel;
|
||
carry.meanFilterScroll = lane.meanFilterScroll;
|
||
carry.weakPeak = lane.weakPeak;
|
||
carry.weakPeakItems = lane.weakPeakItems;
|
||
carry.weakPeakPanel = lane.weakPeakPanel;
|
||
carry.freeMarker = lane.freeMarker;
|
||
carry.freeMarkerCursor = lane.freeMarkerCursor;
|
||
carry.freeMarkerItems = lane.freeMarkerItems;
|
||
carry.freeMarkerPanel = lane.freeMarkerPanel;
|
||
carry.freeMarkerOpacity = lane.freeMarkerOpacity;
|
||
carry.freeMarkerScroll = lane.freeMarkerScroll;
|
||
carry.freeMarkerSelected = lane.freeMarkerSelected;
|
||
carry.freeMarkerShowDelta = lane.freeMarkerShowDelta;
|
||
carry.freeMarkerShowEnum = lane.freeMarkerShowEnum;
|
||
carry.freeMarkerCollapsedGroups = lane.freeMarkerCollapsedGroups;
|
||
carry.resample = lane.resample;
|
||
carry.resampleItems = lane.resampleItems;
|
||
carry.resamplePanel = lane.resamplePanel;
|
||
carry.resampleScroll = lane.resampleScroll;
|
||
carry.clip = lane.clip;
|
||
carry.clipItems = lane.clipItems;
|
||
carry.clipPanel = lane.clipPanel;
|
||
carry.clipScroll = lane.clipScroll;
|
||
for (std::size_t signalIndex = 0; signalIndex < lane.signals.size(); ++signalIndex)
|
||
{
|
||
LaneCarry entry = carry;
|
||
// 只有首个信号是这些网格级开关的持有者(见 `LaneCarry::owner` 说明)。
|
||
entry.owner = (signalIndex == 0);
|
||
previousCarry.emplace_back(lane.signals[signalIndex], entry);
|
||
}
|
||
}
|
||
const auto inheritedCarry = [&previousCarry](const SignalRef& ref)
|
||
{
|
||
for (const std::pair<SignalRef, LaneCarry>& entry : previousCarry)
|
||
{
|
||
if (entry.first == ref)
|
||
{
|
||
return entry.second;
|
||
}
|
||
}
|
||
return LaneCarry();
|
||
};
|
||
const auto applyCarry = [&inheritedCarry, previousAllAutoFit](Lane& lane, const SignalRef& ref)
|
||
{
|
||
const LaneCarry carry = inheritedCarry(ref);
|
||
// 新出现的信号(之前没有网格):继承「全局 Y 轴自适应」是否开启。
|
||
lane.fitYToSelection = carry.matched ? carry.fitYToSelection : previousAllAutoFit;
|
||
// ⚠️ 实验性功能的状态**只由原网格的首个信号继承**(见 `LaneCarry::owner`):
|
||
// 否则「合并网格 → 拆分网格」会把一个网格的功能复制到全部拆分结果上。
|
||
if (!carry.matched || !carry.owner)
|
||
{
|
||
return; // 其它信号:保持 `Lane` 的默认值(全部功能关闭、条目为空)。
|
||
}
|
||
lane.edgeCount = carry.edgeCount;
|
||
lane.edgeValue = carry.edgeValue;
|
||
lane.edgeValueValid = carry.edgeValueValid;
|
||
lane.edgePanel.open = carry.edgePanel.open;
|
||
lane.edgePanel.collapsed = carry.edgePanel.collapsed;
|
||
lane.edgePanel.x = carry.edgePanel.x;
|
||
lane.edgePanel.y = carry.edgePanel.y;
|
||
lane.edgeCenter = carry.edgeCenter;
|
||
lane.edgeCenterItems = carry.edgeCenterItems;
|
||
lane.edgeCenterPanel.open = carry.edgeCenterPanel.open;
|
||
lane.edgeCenterPanel.collapsed = carry.edgeCenterPanel.collapsed;
|
||
lane.edgeCenterPanel.x = carry.edgeCenterPanel.x;
|
||
lane.edgeCenterPanel.y = carry.edgeCenterPanel.y;
|
||
lane.edgeCenterScroll = carry.edgeCenterScroll;
|
||
lane.weakPeakScroll = carry.weakPeakScroll;
|
||
lane.edgeCountScroll = carry.edgeCountScroll;
|
||
lane.meanFilter = carry.meanFilter;
|
||
lane.meanFilterItems = carry.meanFilterItems;
|
||
lane.meanFilterPanel.open = carry.meanFilterPanel.open;
|
||
lane.meanFilterPanel.collapsed = carry.meanFilterPanel.collapsed;
|
||
lane.meanFilterPanel.x = carry.meanFilterPanel.x;
|
||
lane.meanFilterPanel.y = carry.meanFilterPanel.y;
|
||
lane.weakPeak = carry.weakPeak;
|
||
lane.weakPeakItems = carry.weakPeakItems;
|
||
lane.weakPeakPanel.open = carry.weakPeakPanel.open;
|
||
lane.weakPeakPanel.collapsed = carry.weakPeakPanel.collapsed;
|
||
lane.weakPeakPanel.x = carry.weakPeakPanel.x;
|
||
lane.weakPeakPanel.y = carry.weakPeakPanel.y;
|
||
lane.freeMarker = carry.freeMarker;
|
||
lane.freeMarkerCursor = carry.freeMarkerCursor;
|
||
lane.freeMarkerItems = carry.freeMarkerItems;
|
||
lane.freeMarkerPanel.open = carry.freeMarkerPanel.open;
|
||
lane.freeMarkerPanel.collapsed = carry.freeMarkerPanel.collapsed;
|
||
lane.freeMarkerPanel.x = carry.freeMarkerPanel.x;
|
||
lane.freeMarkerPanel.y = carry.freeMarkerPanel.y;
|
||
lane.freeMarkerOpacity = carry.freeMarkerOpacity;
|
||
lane.freeMarkerScroll = carry.freeMarkerScroll;
|
||
lane.freeMarkerSelected = carry.freeMarkerSelected;
|
||
lane.freeMarkerShowDelta = carry.freeMarkerShowDelta;
|
||
lane.freeMarkerShowEnum = carry.freeMarkerShowEnum;
|
||
lane.freeMarkerCollapsedGroups = carry.freeMarkerCollapsedGroups;
|
||
lane.meanFilterScroll = carry.meanFilterScroll;
|
||
lane.resample = carry.resample;
|
||
lane.resampleItems = carry.resampleItems;
|
||
lane.resamplePanel.open = carry.resamplePanel.open;
|
||
lane.resamplePanel.collapsed = carry.resamplePanel.collapsed;
|
||
lane.resamplePanel.x = carry.resamplePanel.x;
|
||
lane.resamplePanel.y = carry.resamplePanel.y;
|
||
lane.resampleScroll = carry.resampleScroll;
|
||
lane.clip = carry.clip;
|
||
lane.clipItems = carry.clipItems;
|
||
lane.clipPanel.open = carry.clipPanel.open;
|
||
lane.clipPanel.collapsed = carry.clipPanel.collapsed;
|
||
lane.clipPanel.x = carry.clipPanel.x;
|
||
lane.clipPanel.y = carry.clipPanel.y;
|
||
lane.clipScroll = carry.clipScroll;
|
||
};
|
||
|
||
// 收集全部可见信号引用。
|
||
std::vector<SignalRef> visibleRefs;
|
||
for (std::size_t groupIndex = 0; groupIndex < visible.size(); ++groupIndex)
|
||
{
|
||
for (std::size_t signalIndex = 0; signalIndex < visible[groupIndex].size(); ++signalIndex)
|
||
{
|
||
if (visible[groupIndex][signalIndex])
|
||
{
|
||
SignalRef ref;
|
||
ref.groupIndex = static_cast<int>(groupIndex);
|
||
ref.signalIndex = static_cast<int>(signalIndex);
|
||
visibleRefs.push_back(ref);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 判断某引用对应的信号是否可见(越界视为不可见)。
|
||
const auto isVisibleRef = [&visible](const SignalRef& ref)
|
||
{
|
||
if (ref.groupIndex < 0 || static_cast<std::size_t>(ref.groupIndex) >= visible.size())
|
||
{
|
||
return false;
|
||
}
|
||
const std::vector<bool>& fileVisible = visible[static_cast<std::size_t>(ref.groupIndex)];
|
||
if (ref.signalIndex < 0 || static_cast<std::size_t>(ref.signalIndex) >= fileVisible.size())
|
||
{
|
||
return false;
|
||
}
|
||
return fileVisible[static_cast<std::size_t>(ref.signalIndex)];
|
||
};
|
||
|
||
// 某信号当前所属的网格下标(-1 表示尚未分配网格)。
|
||
const auto laneIndexOf = [&lanes](const SignalRef& ref)
|
||
{
|
||
for (std::size_t index = 0; index < lanes.size(); ++index)
|
||
{
|
||
const std::vector<SignalRef>& refs = lanes[index].signals;
|
||
if (std::find(refs.begin(), refs.end(), ref) != refs.end())
|
||
{
|
||
return static_cast<int>(index);
|
||
}
|
||
}
|
||
return -1;
|
||
};
|
||
|
||
if (mode == LaneMode::PER_SIGNAL)
|
||
{
|
||
// 每条可见信号独占一个网格:网格数量严格跟随可见信号数量。
|
||
// **顺序按"原有网格位置"优先**:已经分配过网格的信号保持其相对次序,
|
||
// 新勾选(此前没有网格)的信号统一追加到最下方。
|
||
// 若直接按 visibleRefs(文件/列序)重建,新勾选的信号会按其列序
|
||
// 插到中间,位置看起来"不确定"。
|
||
std::vector<SignalRef> ordered;
|
||
ordered.reserve(visibleRefs.size());
|
||
for (const SignalRef& ref : visibleRefs)
|
||
{
|
||
if (laneIndexOf(ref) >= 0)
|
||
{
|
||
ordered.push_back(ref);
|
||
}
|
||
}
|
||
std::stable_sort(ordered.begin(), ordered.end(),
|
||
[&laneIndexOf](const SignalRef& a, const SignalRef& b)
|
||
{ return laneIndexOf(a) < laneIndexOf(b); });
|
||
for (const SignalRef& ref : visibleRefs)
|
||
{
|
||
if (laneIndexOf(ref) < 0)
|
||
{
|
||
ordered.push_back(ref);
|
||
}
|
||
}
|
||
|
||
lanes.clear();
|
||
lanes.reserve(ordered.size());
|
||
for (const SignalRef& ref : ordered)
|
||
{
|
||
Lane lane;
|
||
lane.autoRange = true;
|
||
applyCarry(lane, ref);
|
||
lane.signals.push_back(ref);
|
||
lanes.push_back(std::move(lane));
|
||
}
|
||
}
|
||
else if (mode == LaneMode::MERGED)
|
||
{
|
||
// 全部可见信号合并到同一网格;无可见信号时不保留空网格。
|
||
bool previousAutoRange = true;
|
||
double previousYMin = -1.0;
|
||
double previousYMax = 1.0;
|
||
if (lanes.size() == 1)
|
||
{
|
||
previousAutoRange = lanes[0].autoRange;
|
||
previousYMin = lanes[0].yMin;
|
||
previousYMax = lanes[0].yMax;
|
||
}
|
||
|
||
lanes.clear();
|
||
if (!visibleRefs.empty())
|
||
{
|
||
Lane lane;
|
||
lane.autoRange = previousAutoRange;
|
||
lane.yMin = previousYMin;
|
||
lane.yMax = previousYMax;
|
||
applyCarry(lane, visibleRefs.front());
|
||
lane.signals = visibleRefs;
|
||
lanes.push_back(std::move(lane));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 自定义划分:仅移除不可见信号与由此产生的空网格,保持用户排布。
|
||
for (Lane& lane : lanes)
|
||
{
|
||
std::vector<SignalRef>& refs = lane.signals;
|
||
refs.erase(std::remove_if(refs.begin(), refs.end(),
|
||
[&isVisibleRef](const SignalRef& ref)
|
||
{ return !isVisibleRef(ref); }),
|
||
refs.end());
|
||
}
|
||
|
||
lanes.erase(std::remove_if(lanes.begin(), lanes.end(),
|
||
[](const Lane& lane) { return lane.signals.empty(); }),
|
||
lanes.end());
|
||
|
||
// 未分配网格的可见信号各自独占一个新网格。
|
||
// 注意:不能把它们塞进已有的第一个网格——那会导致“拖拽合并后新勾选的信号
|
||
// 被悄悄并入合并网格”,用户看不到新增的独立泳道。
|
||
for (const SignalRef& ref : visibleRefs)
|
||
{
|
||
bool placed = false;
|
||
for (const Lane& lane : lanes)
|
||
{
|
||
if (std::find(lane.signals.begin(), lane.signals.end(), ref) != lane.signals.end())
|
||
{
|
||
placed = true;
|
||
break;
|
||
}
|
||
}
|
||
if (placed)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Lane lane;
|
||
lane.autoRange = true;
|
||
applyCarry(lane, ref);
|
||
lane.signals.push_back(ref);
|
||
lanes.push_back(std::move(lane));
|
||
}
|
||
}
|
||
|
||
// 清掉“不属于本网格信号”的逐信号条目(合并网格时它们会被整块携带过来)。
|
||
// 统一重命名,保证编号连续。
|
||
for (std::size_t index = 0; index < lanes.size(); ++index)
|
||
{
|
||
PruneLaneSignalItems(lanes[index]);
|
||
lanes[index].name = L"网格 " + std::to_wstring(index + 1);
|
||
}
|
||
}
|
||
} // namespace dmdetail
|
||
|
||
using namespace dmdetail;
|
||
|
||
} // namespace jdi
|
||
|
||
#endif // __DATAMODEL_INTERNAL_HPP__
|
||
|