#ifndef __DATAMODEL_INTERNAL_HPP__ #define __DATAMODEL_INTERNAL_HPP__ // DataModel 组件内部共享的常量与工具函数(私有头,仅本组件的 .cpp 使用)。 // // 本组件的实现按功能拆成多个协作文件,它们共用这一份内部工具集。 // // ⚠️ 为让多个翻译单元都能使用,原匿名命名空间改为**具名**命名空间 `dmdetail`, // 并把其中每个函数与常量标为 `inline` —— 单一定义、单一副本,不会产生代码重复。 // 各实现文件在包含本头之后用 `using namespace dmdetail;` 引入这些工具, // 因此函数体里原有的非限定调用一行都不用改。 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DataModel.hpp" #include "Theme.hpp" #include "Utf8Util.hpp" namespace jdi { namespace dmdetail { /** * @brief DataModel 内部工具集(常量与自由函数)。 * * 全部符号都是 `inline` ⇒ 多个翻译单元共用同一个定义:既不重复符号,也不产生多份代码。 * 不要把它们移回匿名命名空间(那样每个 .cpp 都会各自复制一份)。 */ inline const double kMissing = std::numeric_limits::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 依据全部可见信号重新生成网格划分。 * @param [IN] visible 各信号的可见性(按 [分组][信号] 索引)。 * @param [IN] lanes 网格集合(原地更新)。 * @param [IN] mode 网格划分策略。 */ inline void RebuildLanes(const std::vector>& visible, std::vector& lanes, LaneMode mode) { // 随网格携带的“网格自身状态”(按信号继承):重建网格时不能丢。 struct LaneCarry { bool matched = false; /**< 是否找到该信号原本所在的网格(false = 新出现的信号)。 */ bool fitYToSelection = false; bool edgeCount = false; double edgeValue = 0.0; bool edgeValueValid = false; LanePanelState edgePanel; bool edgeCenter = false; std::vector edgeCenterItems; LanePanelState edgeCenterPanel; bool meanFilter = false; std::vector meanFilterItems; LanePanelState meanFilterPanel; bool weakPeak = false; std::vector weakPeakItems; LanePanelState weakPeakPanel; bool freeMarker = false; bool freeMarkerCursor = false; std::vector freeMarkerItems; LanePanelState freeMarkerPanel; float freeMarkerOpacity = 1.0f; float freeMarkerScroll = 0.0f; std::vector freeMarkerCollapsedGroups; }; // 先记下每个信号原来所在网格的网格自身状态: // 勾选可见性会重建网格,若不携带,用户刚打开的开关会被悄悄清掉。 // // ⚠️ 同时记下「重建之前是否**每个**网格都开着 `fitYToSelection`」: // 那就是「全局 Y 轴自适应」的正式定义。新勾选信号产生的**新网格**必须继承它, // 否则全局开关看着是开的、新网格却不按当前视界自适应(用户报告的缺陷)。 bool previousAllAutoFit = !lanes.empty(); for (const Lane& lane : lanes) { if (!lane.fitYToSelection) { previousAllAutoFit = false; break; } } std::vector> 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.meanFilter = lane.meanFilter; carry.meanFilterItems = lane.meanFilterItems; carry.meanFilterPanel = lane.meanFilterPanel; 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.freeMarkerCollapsedGroups = lane.freeMarkerCollapsedGroups; for (const SignalRef& ref : lane.signals) { previousCarry.emplace_back(ref, carry); } } const auto inheritedCarry = [&previousCarry](const SignalRef& ref) { for (const std::pair& 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; 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.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.freeMarkerCollapsedGroups = carry.freeMarkerCollapsedGroups; }; // 收集全部可见信号引用。 std::vector 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(groupIndex); ref.signalIndex = static_cast(signalIndex); visibleRefs.push_back(ref); } } } // 判断某引用对应的信号是否可见(越界视为不可见)。 const auto isVisibleRef = [&visible](const SignalRef& ref) { if (ref.groupIndex < 0 || static_cast(ref.groupIndex) >= visible.size()) { return false; } const std::vector& fileVisible = visible[static_cast(ref.groupIndex)]; if (ref.signalIndex < 0 || static_cast(ref.signalIndex) >= fileVisible.size()) { return false; } return fileVisible[static_cast(ref.signalIndex)]; }; // 某信号当前所属的网格下标(-1 表示尚未分配网格)。 const auto laneIndexOf = [&lanes](const SignalRef& ref) { for (std::size_t index = 0; index < lanes.size(); ++index) { const std::vector& refs = lanes[index].signals; if (std::find(refs.begin(), refs.end(), ref) != refs.end()) { return static_cast(index); } } return -1; }; if (mode == LaneMode::PER_SIGNAL) { // 每条可见信号独占一个网格:网格数量严格跟随可见信号数量。 // **顺序按"原有网格位置"优先**:已经分配过网格的信号保持其相对次序, // 新勾选(此前没有网格)的信号统一追加到最下方。 // 若直接按 visibleRefs(文件/列序)重建,新勾选的信号会按其列序 // 插到中间,位置看起来"不确定"。 std::vector 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& 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) { lanes[index].name = L"网格 " + std::to_wstring(index + 1); } } } // namespace dmdetail using namespace dmdetail; } // namespace jdi #endif // __DATAMODEL_INTERNAL_HPP__