1) 修复样式区"点预设色块直接跳到自定义色块" - 根因:加载时 customColor 被赋成信号自身的预设色,而样式区按 "color == customColor 即视为在用自定义色" 判定高亮,于是点自身的预设色时 高亮立刻跳到自定义色块上(第 N 个信号点第 N 个预设色必然复现) - customColor 初值改为调色板中"本信号预设色向后递进一格"的颜色 (palette[(slot + 1) % count]),自定义色块因此显示与预设色块排布连续的下一色 - 同时收紧高亮判定:仅当当前颜色"不属于任何可见预设色"时才认为在用自定义色。 这样即使自定义色恰好等于某个预设色,点击该预设色也始终高亮预设色块, 彻底消除"高亮乱跳";用拾色器选出的非预设色仍正常高亮自定义色块 2)「选区 Y 轴自适应」改为切换开关(原为一次性操作) - Lane 用单一布尔量 fitYToSelection 表示(删除 viewAutoRange/viewAutoMin/viewAutoMax) - 开启后每次时间轴选区放大(ApplyTimeSelection → ApplySelectionAutoFit) 都会把该网格 Y 轴匹配到新的可见时间区间 - 任何手动幅值调整(Y 轴选区放大、纵向平移、缩放、直接输入上下限)都会关闭对应网格的开关 - RebuildLanes 按信号继承该开关:勾选/取消勾选信号不再把用户刚打开的开关悄悄清掉 3) 菜单「恢复 Y 轴自适应」移除,原位置新增「选区 Y 轴自适应(全局)」 - 全局项为切换状态:选中即把全部网格的开关置为开,取消即全部置为关; 任意网格单独关掉后,全局项立即变为未勾选(= AllLanesAutoFitToSelection) - 工具栏在「适应视图」之后新增「Y 轴自适应」切换按钮,与全局项是同一个开关, 每帧同步选中态 - 删除了 PLOT_CMD_LANE_AUTO / PlotView::LaneRangeIsAuto / ResetLaneRange 等已无用的接口 4)「时间轴自适应(匹配本网格)」改名为「按网格时间轴展开」并移到分隔线上方 - 新顺序:网格 N / Y 轴最大值 / Y 轴最小值 / 选区 Y 轴自适应 / 按网格时间轴展开 / --- / 选区 Y 轴自适应(全局)/ 全部自适应 验证(TestData/[1].CSV 11 信号,窗口 1725x996,125% DPI): - 颜色:初始高亮在预设色块 1;点预设 1 与预设 2(等于自定义色初值)均保持点中的色块高亮; 用拾色器改成非预设色后高亮正确落在自定义色块 - 逐网格:菜单项勾选/取消即时反映;开启的网格做时间轴选区放大后 Y 轴自动匹配 (仅该网格变化,其它网格保持整段范围) - 全局:工具栏按钮与菜单全局项互相联动;单独取消一个网格后两者都变为未勾选; 隐藏/显示信号后开关保持 - 「按网格时间轴展开」能把缩放后的时间轴展开回该网格信号范围;「全部自适应」仍正常 - 超大文件回归(VERY_BIG_DATA 1.77GB)菜单/悬停正常;空闲 CPU 0.00% 构建: make clean; make all 无警告无错误
987 lines
34 KiB
C++
987 lines
34 KiB
C++
#ifndef __PLOT_VIEW_HPP__
|
|
#define __PLOT_VIEW_HPP__
|
|
|
|
#include <windows.h>
|
|
#include <objidl.h>
|
|
#include <gdiplus.h>
|
|
|
|
#include <deque>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "DataModel.hpp"
|
|
#include "PaintUtil.hpp"
|
|
#include "Theme.hpp"
|
|
|
|
namespace jdi
|
|
{
|
|
|
|
/**
|
|
* @brief 绘图区拖拽交互模式。
|
|
*/
|
|
enum class PlotDragMode : int
|
|
{
|
|
NONE = 0, /**< 未拖拽。 */
|
|
PAN, /**< 常规模式:水平平移时间轴 + 纵向偏移当前网格幅值。 */
|
|
RUBBER_TIME, /**< 时间轴缩放选区(竖向贯穿全高)。 */
|
|
RUBBER_VALUE, /**< Y 轴缩放选区(限定在某个网格内)。 */
|
|
SCROLL_LANES, /**< 拖动网格滚动条。 */
|
|
CURSOR_MOVE, /**< 拖拽时间光标。 */
|
|
MOVE_SIGNAL /**< 拖拽信号曲线到其它网格。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 绘图区内的内联数值输入目标。
|
|
*/
|
|
enum class PlotEditTarget : int
|
|
{
|
|
NONE = 0, /**< 未处于输入状态。 */
|
|
CURSOR_TIME, /**< 编辑时间光标位置。 */
|
|
LANE_Y_MAX, /**< 编辑某网格的 Y 轴最大值。 */
|
|
LANE_Y_MIN, /**< 编辑某网格的 Y 轴最小值。 */
|
|
SIGNAL_NAME /**< 编辑图例中某信号的显示名称。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 绘图区右键菜单命令标识(与信号面板的命令空间相互独立)。
|
|
*/
|
|
enum PlotMenuCommand : int
|
|
{
|
|
PLOT_CMD_AUTO_FIT_ALL = 1, /**< 全局切换全部网格的「选区 Y 轴自适应」。 */
|
|
PLOT_CMD_LANE_Y_MAX = 2, /**< 输入该网格 Y 轴最大值。 */
|
|
PLOT_CMD_LANE_Y_MIN = 3, /**< 输入该网格 Y 轴最小值。 */
|
|
PLOT_CMD_FIT_ALL = 4, /**< 全部网格与时间轴自适应。 */
|
|
PLOT_CMD_LANE_FIT_TIME = 5, /**< 时间轴自适应到本网格信号范围(按网格时间轴展开)。 */
|
|
PLOT_CMD_LANE_AUTO_VIEW = 6 /**< 切换本网格的「选区 Y 轴自适应」开关。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 波形绘图区:网格、时间轴、光标、缩放、自适应与信号拖拽。
|
|
*
|
|
* 绘图区同时依赖数据层(DataSet,只读)与当前标签页配置(TabConfig,可写),
|
|
* 视图范围、网格滚动与时间光标均保存在标签页配置中,因此各标签页互不干扰。
|
|
*/
|
|
class PlotView
|
|
{
|
|
public:
|
|
PlotView();
|
|
|
|
/**
|
|
* @brief 析构:释放拖拽静态场景缓存的离屏位图。
|
|
*/
|
|
~PlotView();
|
|
|
|
/**
|
|
* @brief 设置宿主窗口句柄(用于剪贴板所有者)。
|
|
* @param [IN] host 宿主窗口句柄,允许为空。
|
|
*/
|
|
void SetHostWindow(HWND host) { m_host = host; }
|
|
|
|
/**
|
|
* @brief 绑定字体与主题资源。
|
|
* @param [IN] fonts 字体集合,需在视图生命周期内保持有效。
|
|
* @param [IN] theme 主题配色,需在视图生命周期内保持有效。
|
|
*/
|
|
void Setup(const UiFonts* fonts, const Theme* theme);
|
|
|
|
/**
|
|
* @brief 绑定数据层与当前标签页配置。
|
|
* @param [IN] dataSet 数据集合,允许为空。
|
|
* @param [IN] tab 当前标签页配置,允许为空。
|
|
*/
|
|
void SetContext(DataSet* dataSet, TabConfig* tab);
|
|
|
|
/**
|
|
* @brief 设置 DPI 缩放比例。
|
|
* @param [IN] scale 缩放比例(1.0 对应 96 DPI)。
|
|
*/
|
|
void SetScale(float scale);
|
|
|
|
/**
|
|
* @brief 按给定区域重新排布绘图区。
|
|
* @param [IN] area 可用区域。
|
|
*/
|
|
void Layout(const gd::RectF& area);
|
|
|
|
/**
|
|
* @brief 绘制绘图区。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void Paint(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 处理鼠标移动(含拖拽)。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnMouseMove(int x, int y);
|
|
|
|
/**
|
|
* @brief 处理鼠标左键按下。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnLButtonDown(int x, int y);
|
|
|
|
/**
|
|
* @brief 处理鼠标左键抬起。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnLButtonUp(int x, int y);
|
|
|
|
/**
|
|
* @brief 处理鼠标滚轮(仅用于平移与网格滚动,不再承担缩放)。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @param [IN] delta 滚轮增量。
|
|
* @param [IN] shift 是否按下 Shift(水平平移时间轴)。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnMouseWheel(int x, int y, int delta, bool shift);
|
|
|
|
/**
|
|
* @brief 处理鼠标双击(命中时间光标时进入精确时间输入)。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnDoubleClick(int x, int y);
|
|
|
|
/**
|
|
* @brief 处理鼠标离开绘图区。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool OnMouseLeave();
|
|
|
|
/**
|
|
* @brief 将时间轴自适应到指定网格内信号的时间范围。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @return 调整成功返回 true。
|
|
*/
|
|
bool FitTimeToLane(int laneIndex);
|
|
|
|
/**
|
|
* @brief 依据当前显示时间段内的数据对该网格做 Y 轴自适应(“选区 Y 轴自适应”)。
|
|
*
|
|
* 与普通自适应(用整段数据的极值)不同:这里只看时间轴当前可见区间内的采样点,
|
|
* 因此放大到某段局部波形后可以快速把该段的幅值铺满整个网格。
|
|
*
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @return 调整成功返回 true(区间内无有效数据时返回 false)。
|
|
*/
|
|
bool AutoRangeLaneToView(int laneIndex);
|
|
|
|
/**
|
|
* @brief 对全部开启了「选区 Y 轴自适应」的网格做一次 Y 轴选区自适应。
|
|
*
|
|
* 时间轴选区放大后调用:只有开关开启的网格才自动匹配新的可见时间区间。
|
|
*/
|
|
void ApplySelectionAutoFit();
|
|
|
|
/**
|
|
* @brief 把时间吸附到距今最近的数据采样点。
|
|
*
|
|
* 仅在横向放大到采样点间距足够大(可精确分辨)时生效,
|
|
* 避免缩小时光标出现“抖动”难以微调。
|
|
*
|
|
* @param [IN] time 原始时间。
|
|
* @param [OUT] snapped 吸附后的时间。
|
|
* @return 成功吸附返回 true。
|
|
*/
|
|
bool SnapTimeToSample(double time, double& snapped) const;
|
|
|
|
/**
|
|
* @brief 计算指定文件中某一时刻附近采样点的时间间隔。
|
|
* @param [IN] file 目标文件。
|
|
* @param [IN] time 目标时刻。
|
|
* @param [OUT] pixels 间隔换算到屏幕上的像素宽度。
|
|
* @return 计算成功返回 true。
|
|
*/
|
|
bool SampleSpacingPixels(const DataFile& file, double time, double& pixels) const;
|
|
|
|
/**
|
|
* @brief 判断坐标是否落在绘图区范围内。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 落在范围内返回 true。
|
|
*/
|
|
bool HitTest(int x, int y) const;
|
|
|
|
/**
|
|
* @brief 查询当前鼠标位置应使用的系统光标。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 系统光标句柄,未命中特殊区域返回 nullptr。
|
|
*/
|
|
HCURSOR QueryCursor(int x, int y) const;
|
|
|
|
/**
|
|
* @brief 时间轴与幅值轴同时自适应到可见信号范围。
|
|
*/
|
|
void FitAll();
|
|
|
|
/**
|
|
* @brief 以给定倍率缩放时间轴(以视图中心为锚点)。
|
|
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
|
*/
|
|
void ZoomTimeStep(double factor);
|
|
|
|
/**
|
|
* @brief 按当前视图跨度的比例平移时间轴。
|
|
* @param [IN] fraction 平移比例,正值向右(时间增大)平移。
|
|
*/
|
|
void PanTimeByFraction(double fraction);
|
|
|
|
/**
|
|
* @brief 按网格内容高度的比例纵向滚动网格。
|
|
* @param [IN] fraction 滚动比例,正值向下滚动。
|
|
*/
|
|
void ScrollLanesByFraction(double fraction);
|
|
|
|
/**
|
|
* @brief 获取网格当前内容总高度。
|
|
* @return 内容高度(像素)。
|
|
*/
|
|
float LaneContentHeight() const { return m_laneContentHeight; }
|
|
|
|
/**
|
|
* @brief 清除已启用的时间光标。
|
|
*/
|
|
void DisableCursor();
|
|
|
|
/**
|
|
* @brief 放弃当前正在进行的拖拽(缩放选区/平移/信号拖动),不改变视图范围。
|
|
* @return 确实取消了某个拖拽返回 true。
|
|
*/
|
|
bool CancelDrag();
|
|
|
|
/**
|
|
* @brief 退出全部缩放模式(时间轴选区 / Y 轴选区),使工具栏缩放按钮回到未选中态。
|
|
* @return 之前处于某个缩放模式返回 true。
|
|
*/
|
|
bool ExitZoomModes();
|
|
|
|
/**
|
|
* @brief 清空折线顶点缓存。
|
|
*
|
|
* 数据层结构变化(加载/移除文件、信号增删)后信号引用与文件地址都可能改变,
|
|
* 必须清空缓存,避免旧几何键与新的信号意外重合而取到过期顶点。
|
|
*/
|
|
void InvalidatePolylineCache(); /**
|
|
* @brief 切换时间光标:启用时置于当前视图中心,再调用则关闭。
|
|
* @return 状态发生变化返回 true。
|
|
*/
|
|
bool ToggleCursor();
|
|
|
|
/**
|
|
* @brief 判断时间光标是否已启用。
|
|
* @return 启用返回 true。
|
|
*/
|
|
bool CursorEnabled() const;
|
|
|
|
/**
|
|
* @brief 获取当前光标对应的时间。
|
|
* @return 光标时间。
|
|
*/
|
|
double CursorTime() const;
|
|
|
|
/**
|
|
* @brief 切换拖拽缩放模式(再次调用同一模式则取消)。
|
|
* @param [IN] mode 目标模式。
|
|
* @return 状态发生变化返回 true。
|
|
*/
|
|
bool ToggleZoomMode(PlotZoomMode mode);
|
|
|
|
/**
|
|
* @brief 获取当前拖拽缩放模式。
|
|
* @return 模式枚举。
|
|
*/
|
|
PlotZoomMode CurrentZoomMode() const;
|
|
|
|
/**
|
|
* @brief 查询坐标命中的网格下标(用于右键菜单)。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 网格下标,未命中返回 -1。
|
|
*/
|
|
int LaneIndexAt(int x, int y) const;
|
|
|
|
/**
|
|
* @brief 判断某网格是否开启了「选区 Y 轴自适应」开关(菜单勾选标记)。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @return 开启返回 true。
|
|
*/
|
|
bool LaneAutoFitToSelection(int laneIndex) const;
|
|
|
|
/**
|
|
* @brief 判断是否全部网格都开启了「选区 Y 轴自适应」(用于全局菜单项与工具栏按钮)。
|
|
* @return 存在网格且全部开启返回 true。
|
|
*/
|
|
bool AllLanesAutoFitToSelection() const;
|
|
|
|
/**
|
|
* @brief 切换某网格的「选区 Y 轴自适应」开关。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @return 切换后的状态。
|
|
*/
|
|
bool ToggleLaneAutoFitToSelection(int laneIndex);
|
|
|
|
/**
|
|
* @brief 一次性设置全部网格的「选区 Y 轴自适应」开关(全局菜单项 / 工具栏按钮)。
|
|
* @param [IN] enabled 目标状态。
|
|
* @return 任一网格状态发生变化返回 true。
|
|
*/
|
|
bool SetAllLanesAutoFitToSelection(bool enabled);
|
|
|
|
/**
|
|
* @brief 进入某网格 Y 轴上限/下限的内联输入状态。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [IN] editMax 为 true 编辑上限,为 false 编辑下限。
|
|
* @return 进入成功返回 true。
|
|
*/
|
|
bool BeginLaneValueEdit(int laneIndex, bool editMax, const gd::RectF& anchorRect);
|
|
|
|
/**
|
|
* @brief 直接把文本解析为网格 Y 轴上限/下限并应用(供菜单内联输入框提交使用)。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [IN] editMax true 写上限,false 写下限。
|
|
* @param [IN] text 输入文本。
|
|
* @return 解析并应用成功返回 true。
|
|
*/
|
|
bool ApplyLaneValueText(int laneIndex, bool editMax, const std::wstring& text);
|
|
|
|
/**
|
|
* @brief 查询某网格当前的 Y 轴显示范围。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [OUT] yMin 输出下限。
|
|
* @param [OUT] yMax 输出上限。
|
|
* @return 网格下标合法返回 true。
|
|
*/
|
|
bool LaneRange(int laneIndex, double& yMin, double& yMax) const;
|
|
|
|
/**
|
|
* @brief 判断是否处于任意内联数值输入状态。
|
|
* @return 处于编辑态返回 true。
|
|
*/
|
|
bool IsEditing() const { return m_editTarget != PlotEditTarget::NONE; }
|
|
|
|
/**
|
|
* @brief 取出并清除待应用的信号重命名请求。
|
|
* @param [OUT] ref 被重命名的信号引用。
|
|
* @param [OUT] name 新的显示名称。
|
|
* @return 存在待处理请求返回 true。
|
|
* @note 绘图区只持有只读数据层,重命名需由宿主窗口落到 DataSet 上。
|
|
*/
|
|
bool TakePendingRename(SignalRef& ref, std::wstring& name);
|
|
|
|
/**
|
|
* @brief 处理编辑态下的字符输入。
|
|
* @param [IN] character 输入字符。
|
|
* @return 界面状态发生变化返回 true。
|
|
*/
|
|
bool OnEditChar(wchar_t character);
|
|
|
|
/**
|
|
* @brief 处理编辑态下的按键。
|
|
* @param [IN] key 虚拟键码。
|
|
* @return 界面状态发生变化返回 true。
|
|
*/
|
|
bool OnEditKey(WPARAM key);
|
|
|
|
/**
|
|
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
|
|
* @return 发生改变返回 true;不在编辑态返回 false。
|
|
*/
|
|
bool CollapseEditSelectionToEnd();
|
|
|
|
/**
|
|
* @brief 编辑态下在输入框内按下鼠标左键:定位插入符并开始拖拽框选。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 命中输入框返回 true。
|
|
*/
|
|
bool OnEditMouseDown(int x, int y);
|
|
|
|
/**
|
|
* @brief 编辑态下拖拽鼠标:扩展选区。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @return 选区发生变化返回 true。
|
|
*/
|
|
bool OnEditMouseMove(int x);
|
|
|
|
/**
|
|
* @brief 编辑态下松开鼠标左键:结束拖拽框选。
|
|
* @return 之前处于拖拽中返回 true。
|
|
*/
|
|
bool OnEditMouseUp();
|
|
|
|
/**
|
|
* @brief 获取当前视图左边界时间。
|
|
* @return 视图左边界时间。
|
|
*/
|
|
double ViewMinTime() const;
|
|
|
|
/**
|
|
* @brief 获取当前视图右边界时间。
|
|
* @return 视图右边界时间。
|
|
*/
|
|
double ViewMaxTime() const;
|
|
|
|
/**
|
|
* @brief 重新生成网格几何(可见性或网格划分变化后调用)。
|
|
*/
|
|
void RefreshLanes();
|
|
|
|
private:
|
|
/**
|
|
* @brief 单个网格的绘制信息。
|
|
*/
|
|
struct LaneVisual
|
|
{
|
|
gd::RectF rect; /**< 网格波形区域。 */
|
|
double yMin = 0.0; /**< 幅值下限。 */
|
|
double yMax = 1.0; /**< 幅值上限。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 已解析的信号视图:数据与显示配置的组合。
|
|
*/
|
|
struct ResolvedSignal
|
|
{
|
|
const SignalData* data = nullptr; /**< 信号数据(含全局共享的显示外观)。 */
|
|
const SignalStyle* style = nullptr; /**< 显示外观。 */
|
|
const DataFile* file = nullptr; /**< 所属文件。 */
|
|
bool visible = false; /**< 在当前标签页中是否可见。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 获取当前生效的标签页配置(未绑定时返回内部占位对象)。
|
|
* @return 标签页配置引用。
|
|
*/
|
|
TabConfig& Tab() { return (m_tab != nullptr) ? *m_tab : m_localTab; }
|
|
|
|
/**
|
|
* @brief 获取当前生效的标签页配置(只读)。
|
|
* @return 标签页配置引用。
|
|
*/
|
|
const TabConfig& Tab() const { return (m_tab != nullptr) ? *m_tab : m_localTab; }
|
|
|
|
/**
|
|
* @brief 判断是否存在可绘制内容(已加载数据且至少有一条可见信号)。
|
|
* @return 可绘制返回 true。
|
|
*/
|
|
bool HasDrawableContent() const;
|
|
|
|
/**
|
|
* @brief 计算当前视图应使用的时间范围。
|
|
* @param [OUT] low 时间下限。
|
|
* @param [OUT] high 时间上限。
|
|
*/
|
|
void TimeBounds(double& low, double& high) const;
|
|
|
|
/**
|
|
* @brief 解析信号引用,得到数据与显示配置。
|
|
* @param [IN] ref 信号引用。
|
|
* @return 解析结果,引用无效时各指针为空。
|
|
*/
|
|
ResolvedSignal ResolveSignal(const SignalRef& ref) const;
|
|
|
|
/**
|
|
* @brief 判断某网格内的可见信号是否来自多个文件。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @return 跨越多个文件返回 true。
|
|
*/
|
|
bool LaneSpansMultipleFiles(int laneIndex) const;
|
|
|
|
/**
|
|
* @brief 获取当前选中信号(跨标签页共享,切换视图后选中项保持一致)。
|
|
* @return 信号引用。
|
|
*/
|
|
const SignalRef& SelectedRef() const;
|
|
|
|
/**
|
|
* @brief 设置当前选中信号(跨标签页共享)。
|
|
* @param [IN] ref 信号引用。
|
|
*/
|
|
void SelectSignal(const SignalRef& ref);
|
|
|
|
/**
|
|
* @brief 重新计算各网格的布局与幅值范围。
|
|
*/
|
|
void BuildLaneVisuals();
|
|
|
|
/**
|
|
* @brief 限制网格滚动偏移量。
|
|
*/
|
|
void ClampLaneScroll();
|
|
|
|
/**
|
|
* @brief 将时间映射为绘图区横坐标。
|
|
* @param [IN] time 时间值。
|
|
* @return 横坐标。
|
|
*/
|
|
float TimeToX(double time) const;
|
|
|
|
/**
|
|
* @brief 将绘图区横坐标还原为时间。
|
|
* @param [IN] x 横坐标。
|
|
* @return 时间值。
|
|
*/
|
|
double XToTime(float x) const;
|
|
|
|
/**
|
|
* @brief 将幅值映射为纵坐标。
|
|
* @param [IN] value 幅值。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @return 纵坐标。
|
|
*/
|
|
float ValueToY(double value, const LaneVisual& lane) const;
|
|
|
|
/**
|
|
* @brief 将纵坐标还原为幅值。
|
|
* @param [IN] y 纵坐标。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @return 幅值。
|
|
*/
|
|
double YToValue(float y, const LaneVisual& lane) const;
|
|
|
|
/**
|
|
* @brief 查询指定纵坐标落在哪个网格内。
|
|
* @param [IN] y 纵坐标。
|
|
* @return 网格下标,未命中返回 -1。
|
|
*/
|
|
int LaneAtY(int y) const;
|
|
|
|
/**
|
|
* @brief 限制时间视图范围,避免过度平移到数据之外。
|
|
*/
|
|
void ClampTimeView();
|
|
|
|
/**
|
|
* @brief 以指定锚点缩放时间轴。
|
|
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
|
* @param [IN] anchorTime 锚点时间。
|
|
*/
|
|
void ZoomTimeAt(double factor, double anchorTime);
|
|
|
|
/**
|
|
* @brief 将时间轴缩放到指定的时间区间(拖拽选区释放时调用)。
|
|
* @param [IN] x0 选区起点横坐标。
|
|
* @param [IN] x1 选区终点横坐标。
|
|
* @return 产生了有效缩放返回 true。
|
|
*/
|
|
bool ApplyTimeSelection(float x0, float x1);
|
|
|
|
/**
|
|
* @brief 将某个网格的幅值轴缩放到指定区间(拖拽选区释放时调用)。
|
|
* @param [IN] laneIndex 目标网格下标。
|
|
* @param [IN] y0 选区起点纵坐标。
|
|
* @param [IN] y1 选区终点纵坐标。
|
|
* @return 产生了有效缩放返回 true。
|
|
*/
|
|
bool ApplyValueSelection(int laneIndex, float y0, float y1);
|
|
|
|
/**
|
|
* @brief 以指定倍率缩放某网格的幅值轴(保持锚点幅值不动)。
|
|
* @param [IN] laneIndex 目标网格下标。
|
|
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
|
* @param [IN] anchorValue 锚点幅值。
|
|
*/
|
|
void ScaleLaneRange(int laneIndex, double factor, double anchorValue);
|
|
|
|
/**
|
|
* @brief 平移某网格的幅值轴(偏移波形显示位置)。
|
|
* @param [IN] laneIndex 目标网格下标。
|
|
* @param [IN] deltaValue 幅值偏移量。
|
|
*/
|
|
void ShiftLaneRange(int laneIndex, double deltaValue);
|
|
|
|
/**
|
|
* @brief 计算时间光标当前的横坐标。
|
|
* @return 横坐标。
|
|
*/
|
|
float CursorX() const;
|
|
|
|
/**
|
|
* @brief 计算时间读数标签(或编辑框)的矩形。
|
|
* @return 标签矩形,位于绘图区顶部以避免遮挡时间轴刻度。
|
|
*/
|
|
gd::RectF CursorLabelRect() const;
|
|
|
|
/**
|
|
* @brief 判断给定横坐标是否落在时间光标附近(允许拖拽的容差范围)。
|
|
* @param [IN] x 横坐标。
|
|
* @return 命中返回 true。
|
|
*/
|
|
bool IsOnCursorLine(int x) const;
|
|
|
|
/**
|
|
* @brief 精确检测鼠标位置是否命中某条可见信号曲线。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 命中的信号引用,未命中返回无效引用。
|
|
*/
|
|
SignalRef HitTestSignal(int x, int y) const;
|
|
|
|
/**
|
|
* @brief 计算当前内联输入框的矩形(依编辑目标而定)。
|
|
* @return 输入框矩形,未处于编辑态时返回空矩形。
|
|
*/
|
|
gd::RectF EditFieldRect() const;
|
|
|
|
/**
|
|
* @brief 以给定目标与初始文本进入内联输入状态。
|
|
* @param [IN] target 编辑目标。
|
|
* @param [IN] laneIndex 目标网格下标(光标编辑时忽略)。
|
|
* @param [IN] initialText 初始文本。
|
|
* @return 进入成功返回 true。
|
|
*/
|
|
bool BeginEdit(PlotEditTarget target, int laneIndex, const std::wstring& initialText,
|
|
const gd::RectF& anchorRect = gd::RectF());
|
|
|
|
/**
|
|
* @brief 结束内联输入。
|
|
* @param [IN] commit 是否提交当前输入。
|
|
* @return 状态发生变化返回 true。
|
|
*/
|
|
bool EndEdit(bool commit);
|
|
|
|
/**
|
|
* @brief 构建单条信号在当前视图下的折线点序列。
|
|
* @param [IN] resolved 已解析的信号视图。
|
|
* @param [IN] lane 所属网格绘制信息。
|
|
* @param [OUT] points 输出点序列。
|
|
* @param [OUT] breaks 输出每段折线的起始下标(用于处理缺失值断线)。
|
|
*/
|
|
void BuildPolyline(const ResolvedSignal& resolved, const LaneVisual& lane,
|
|
std::vector<gd::PointF>& points, std::vector<std::size_t>& breaks) const;
|
|
|
|
/**
|
|
* @brief 绘制单个网格。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @param [IN] timeTicks 时间刻度序列。
|
|
*/
|
|
void PaintLane(gd::Graphics& g, int laneIndex, const LaneVisual& lane,
|
|
const std::vector<double>& timeTicks);
|
|
|
|
/**
|
|
* @brief 绘制底部时间轴。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] timeTicks 时间刻度序列。
|
|
* @param [IN] timeStep 时间刻度步长。
|
|
*/
|
|
void PaintTimeAxis(gd::Graphics& g, const std::vector<double>& timeTicks, double timeStep);
|
|
|
|
/**
|
|
* @brief 绘制时间光标、选区及其读数。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] lanes 网格绘制信息集合。
|
|
*/
|
|
void PaintCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes);
|
|
|
|
/**
|
|
* @brief 绘制拖动中的缩放选区。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintRubberBand(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制内联数值输入框(时间光标或网格 Y 轴上下限)。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintEditField(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制信号悬停提示与拖拽虚影。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintSignalInteraction(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制网格纵向滚动条。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintScrollBar(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制网格标题(信号图例)。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @param [IN] visibleTop 网格在绘图区内的可见上边界。
|
|
* @param [IN] visibleBottom 网格在绘图区内的可见下边界。
|
|
*/
|
|
void PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,
|
|
float visibleBottom);
|
|
|
|
/**
|
|
* @brief 在网格内绘制各信号的幅值读数。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] laneIndex 网格下标。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @param [IN] visibleTop 网格在绘图区内的可见上边界。
|
|
* @param [IN] visibleBottom 网格在绘图区内的可见下边界。
|
|
*/
|
|
void PaintLaneReadout(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,
|
|
float visibleBottom);
|
|
|
|
/**
|
|
* @brief 判断给定矩形是否完整落在可见纵向区间内。
|
|
* @param [IN] rect 待测矩形。
|
|
* @param [IN] visibleTop 可见区上边界。
|
|
* @param [IN] visibleBottom 可见区下边界。
|
|
* @return 完整可见返回 true。
|
|
* @note GDI 文本绘制不会遵循 GDI+ 的裁剪区,必须显式判定,否则滚动时文字会溢出到工具栏。
|
|
*/
|
|
static bool IsFullyVisible(const gd::RectF& rect, float visibleTop, float visibleBottom);
|
|
|
|
/**
|
|
* @brief 绘制空状态提示文本。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintEmptyState(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制全部网格的时间光标读数框。
|
|
*
|
|
* 读数随时间光标实时变化,因此不能进入拖拽静态场景缓存,必须每帧单独绘制。
|
|
*
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintLaneReadouts(gd::Graphics& g);
|
|
|
|
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
|
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
|
const DataSet* m_dataSet = nullptr; /**< 数据集合(只读访问)。 */
|
|
DataSet* m_dataSetWritable = nullptr; /**< 数据集合(选中状态写入)。 */
|
|
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
|
|
TabConfig m_localTab; /**< 未绑定标签页时使用的占位配置。 */
|
|
HWND m_host = nullptr; /**< 剪贴板所有者窗口句柄。 */
|
|
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
|
|
|
gd::RectF m_area; /**< 绘图区整体区域。 */
|
|
gd::RectF m_plotRect; /**< 波形绘制区域。 */
|
|
gd::RectF m_timeAxisRect; /**< 时间轴区域。 */
|
|
gd::RectF m_scrollBarRect; /**< 网格滚动条区域。 */
|
|
|
|
float m_labelBandWidth = 66.0f; /**< 网格内侧纵轴标尺带宽度(逻辑像素)。 */
|
|
float m_timeAxisHeight = 32.0f; /**< 时间轴高度(逻辑像素)。 */
|
|
float m_minLaneHeight = 54.0f; /**< 网格最小高度(逻辑像素)。 */
|
|
float m_laneContentHeight = 0.0f; /**< 网格内容总高度。 */
|
|
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
|
|
|
|
PlotDragMode m_dragMode = PlotDragMode::NONE; /**< 当前拖拽模式。 */
|
|
int m_dragLane = -1; /**< 拖拽所作用的网格下标。 */
|
|
bool m_dragMoved = false; /**< 拖拽过程中是否发生了位移。 */
|
|
int m_dragStartX = 0; /**< 拖拽起点横坐标。 */
|
|
int m_dragStartY = 0; /**< 拖拽起点纵坐标。 */
|
|
int m_dragLastX = 0; /**< 上一次拖拽横坐标。 */
|
|
int m_dragLastY = 0; /**< 上一次拖拽纵坐标。 */
|
|
gd::RectF m_rubberRect; /**< 拖拽选区矩形。 */
|
|
|
|
SignalRef m_hoverSignal; /**< 鼠标悬停命中的信号(曲线高亮)。 */
|
|
SignalRef m_dragSignal; /**< 正在被拖拽的信号。 */
|
|
int m_dragTargetLane = -1; /**< 拖拽信号时的目标网格下标。 */
|
|
int m_dragMouseX = 0; /**< 拖拽过程中鼠标的当前横坐标(虚影跟随)。 */
|
|
int m_dragMouseY = 0; /**< 拖拽过程中鼠标的当前纵坐标(虚影跟随)。 */
|
|
bool m_cursorHover = false; /**< 鼠标是否悬停在时间光标上。 */
|
|
PlotEditTarget m_editTarget = PlotEditTarget::NONE; /**< 当前内联输入目标。 */
|
|
TextEdit m_edit; /**< 输入框编辑模型(光标/选区/剪贴板)。 */
|
|
int m_editLane = -1; /**< 内联输入所作用的网格下标。 */
|
|
SignalRef m_editSignal; /**< 内联输入所作用的信号(图例改名)。 */
|
|
gd::RectF m_editFieldRect; /**< 本次编辑输入框的矩形(用于点外提交判定)。 */
|
|
SignalRef m_pendingRenameRef; /**< 待宿主窗口应用的信号重命名引用。 */
|
|
std::wstring m_pendingRenameName; /**< 待宿主窗口应用的信号新名称。 */
|
|
bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */
|
|
|
|
int m_hoverLane = -1; /**< 当前悬停网格下标。 */
|
|
int m_mouseX = 0; /**< 最近一次鼠标横坐标(用于悬停提示定位)。 */
|
|
int m_mouseY = 0; /**< 最近一次鼠标纵坐标(用于悬停提示定位)。 */
|
|
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
|
|
|
|
/**
|
|
* @brief 图例条目的命中信息(用于点选与双击改名)。
|
|
*/
|
|
struct LegendHit
|
|
{
|
|
SignalRef ref; /**< 对应信号。 */
|
|
gd::RectF rect; /**< 条目矩形(已含色块与文本)。 */
|
|
};
|
|
|
|
std::vector<LegendHit> m_legendHits; /**< 最近一次绘制时记录的图例条目矩形。 */
|
|
|
|
/**
|
|
* @brief 折线顶点缓存条目。
|
|
*
|
|
* 折线抽稀是 O(采样点数) 的(百万级采样约 2~3ms/条),
|
|
* 而绘制、悬停命中检测每帧都要用到同一批顶点。
|
|
* 用“构建折线时读取的全部输入”作为键缓存顶点,
|
|
* 视图/网格几何未变化时可直接复用,避免重复扫描全量采样。
|
|
*/
|
|
struct PolylineCacheEntry
|
|
{
|
|
SignalRef ref; /**< 信号引用。 */
|
|
const DataFile* file = nullptr; /**< 所属文件(内容不可变)。 */
|
|
std::size_t sampleCount = 0; /**< 采样点数(数据变化时改变)。 */
|
|
double viewMin = 0.0; /**< 时间轴显示下界。 */
|
|
double viewMax = 0.0; /**< 时间轴显示上界。 */
|
|
float plotX = 0.0f; /**< 绘图区左边界。 */
|
|
float plotWidth = 0.0f; /**< 绘图区宽度。 */
|
|
float laneY = 0.0f; /**< 网格上边界。 */
|
|
float laneHeight = 0.0f; /**< 网格高度。 */
|
|
double laneYMin = 0.0; /**< 网格幅值下界。 */
|
|
double laneYMax = 0.0; /**< 网格幅值上界。 */
|
|
std::vector<gd::PointF> points; /**< 折线顶点。 */
|
|
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 取得(必要时重建)指定信号在当前几何下的折线顶点。
|
|
* @param [IN] ref 信号引用(缓存键的一部分)。
|
|
* @param [IN] resolved 已解析的信号。
|
|
* @param [IN] lane 网格绘制信息。
|
|
* @return 缓存条目指针,无法构建时返回 nullptr。
|
|
*/
|
|
const PolylineCacheEntry* AcquirePolyline(const SignalRef& ref, const ResolvedSignal& resolved,
|
|
const LaneVisual& lane) const;
|
|
|
|
mutable std::deque<PolylineCacheEntry> m_polylineCache; /**< 折线顶点缓存(按几何键复用)。 */
|
|
|
|
/**
|
|
* @brief 静态场景缓存的几何/内容键。
|
|
*
|
|
* 只要这组参数完全一致,"背景 + 全部网格 + 时间轴"的渲染结果就逐像素相同,
|
|
* 可以直接复用离屏位图,无需重算百万级采样的包络与绘制。
|
|
* 这使鼠标悬停、拖拽选区等"只改一小块叠加层"的交互不再重绘波形:
|
|
* 大文件下整幅重绘约 80ms,命中缓存时只需 1~2ms 贴图。
|
|
*/
|
|
struct SceneCacheKey
|
|
{
|
|
double viewMin = 0.0; /**< 时间轴下界。 */
|
|
double viewMax = 0.0; /**< 时间轴上界。 */
|
|
float laneScroll = 0.0f; /**< 网格滚动偏移。 */
|
|
int laneCount = 0; /**< 网格数量。 */
|
|
float areaX = 0.0f; /**< 绘图区左上角。 */
|
|
float areaY = 0.0f;
|
|
float areaW = 0.0f; /**< 绘图区尺寸。 */
|
|
float areaH = 0.0f;
|
|
float scale = 1.0f; /**< DPI 缩放。 */
|
|
int haloFile = -1; /**< 已绘入缓存的拖拽光晕信号(无则 -1)。 */
|
|
int haloSignal = -1;
|
|
std::uint64_t contentHash = 0; /**< 信号集合/显示样式/网格幅值等内容指纹。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 构造当前场景的缓存键。
|
|
* @return 缓存键。
|
|
*/
|
|
SceneCacheKey MakeSceneCacheKey() const;
|
|
|
|
/**
|
|
* @brief 判断当前缓存键是否与已捕获的场景一致。
|
|
* @return 可直接贴图返回 true。
|
|
*/
|
|
bool SceneCacheMatches() const;
|
|
|
|
/**
|
|
* @brief 计算场景内容指纹(信号集合、显示样式、网格幅值、图例文案)。
|
|
*
|
|
* 视图几何以外的任何内容变化(改颜色/线宽/透明度/样式、重命名、增删信号)
|
|
* 都会改变该指纹,从而让静态场景缓存自动失效,无需在各处手工调用失效接口。
|
|
*
|
|
* @return 指纹值。
|
|
*/
|
|
std::uint64_t SceneContentHash() const;
|
|
|
|
/**
|
|
* @brief 当前应当绘入静态场景缓存的信号光晕(仅拖拽中的信号)。
|
|
*
|
|
* 拖拽信号的光晕在整个拖拽过程中不变,可以进入缓存;
|
|
* 悬停光晕随鼠标移动,必须画在缓存之外。
|
|
*
|
|
* @return 信号引用,无则返回无效引用。
|
|
*/
|
|
SignalRef SceneHaloRef() const;
|
|
|
|
/**
|
|
* @brief 绘制静态场景缓存之外的"悬停高亮"(悬停网格底色 + 悬停信号光晕)。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
*/
|
|
void PaintHoverHighlight(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 绘制某条信号的悬停/拖拽光晕。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] ref 信号引用。
|
|
* @param [IN] resolved 已解析的信号。
|
|
* @param [IN] points 折线顶点。
|
|
* @param [IN] breaks 每段折线的起始下标。
|
|
*/
|
|
void PaintSignalHalo(gd::Graphics& g, const SignalRef& ref, const ResolvedSignal& resolved,
|
|
const std::vector<gd::PointF>& points, const std::vector<std::size_t>& breaks);
|
|
|
|
/**
|
|
* @brief 确保离屏缓存位图存在且尺寸匹配。
|
|
* @param [IN] width 位图宽度(像素)。
|
|
* @param [IN] height 位图高度(像素)。
|
|
* @return 可用返回 true。
|
|
*/
|
|
bool EnsureSceneCache(int width, int height);
|
|
|
|
/**
|
|
* @brief 把当前画布内容(绘图区底色 + 全部网格 + 时间轴)拷贝进离屏缓存。
|
|
*
|
|
* 缓存区域严格限定为绘图区(m_area)。悬停与拖拽产生的内容一律不进入缓存,
|
|
* 因此在任何交互过程中都可以安全复用,只贴图而不再重绘波形。
|
|
*
|
|
* @param [IN,OUT] g 当前绘图上下文(必须未设置裁剪区)。
|
|
* @return 拷贝成功返回 true。
|
|
*/
|
|
bool CaptureSceneCache(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 把离屏缓存贴回画布。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @return 命中缓存并贴图成功返回 true。
|
|
*/
|
|
bool BlitSceneCache(gd::Graphics& g);
|
|
|
|
/**
|
|
* @brief 丢弃缓存标记(下次绘制会重新捕获)。
|
|
*/
|
|
void InvalidateSceneCache();
|
|
|
|
/**
|
|
* @brief 释放离屏缓存占用的 GDI 对象。
|
|
*/
|
|
void ReleaseSceneCache();
|
|
|
|
HBITMAP m_sceneBitmap = nullptr; /**< 离屏缓存位图。 */
|
|
HDC m_sceneDc = nullptr; /**< 离屏缓存设备上下文。 */
|
|
HGDIOBJ m_sceneOldBitmap = nullptr; /**< 离屏 DC 原选位图。 */
|
|
int m_sceneWidth = 0; /**< 离屏位图宽度。 */
|
|
int m_sceneHeight = 0; /**< 离屏位图高度。 */
|
|
int m_sceneX = 0; /**< 缓存对应的客户区横坐标(绘图区左边界)。 */
|
|
int m_sceneY = 0; /**< 缓存对应的客户区纵坐标(绘图区上边界)。 */
|
|
bool m_sceneValid = false; /**< 缓存内容是否有效。 */
|
|
SceneCacheKey m_sceneKey; /**< 缓存对应的场景参数。 */
|
|
|
|
/**
|
|
* @brief 查询坐标命中的图例条目。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @return 命中的信号引用,未命中返回无效引用。
|
|
*/
|
|
SignalRef LegendAtPoint(int x, int y) const;
|
|
};
|
|
|
|
} // namespace jdi
|
|
|
|
#endif // __PLOT_VIEW_HPP__
|