|
- #pragma once
-
- #include "domain/control_logic_model.h"
- #include "editor_history.h"
-
- #include <string>
- #include <utility>
- #include <vector>
-
- class ProjectService;
-
- // 梯形图编辑失败分类
- enum class LogicEditorError
- {
- None, // 操作成功或没有错误
- LogicNotFound, // 控制逻辑不存在
- RungNotFound, // 网络不存在
- ExpressionNotFound, // 条件表达式不存在
- NodeNotFound, // 节点不存在
- InvalidNode, // 节点配置或节点位置无效
- InvalidOperation, // 操作参数或当前结构不允许该操作
- UnsupportedNodeChange, // 不允许把条件节点改成输出节点,或反向修改
- DuplicateName, // 控制逻辑名称与其他逻辑重复
- LastLogicRequired // 删除后不能少于一个控制逻辑
- };
-
- // 梯形图编辑结果;成功时 id 通常是新建或更新对象的稳定 ID
- struct LogicEditorResult
- {
- bool succeeded = false; // 操作是否成功
- LogicEditorError error = LogicEditorError::None; // 失败时的分类
- std::string message; // 面向用户的 UTF-8 成功说明或失败原因
- std::string id; // 成功时返回新建或更新对象的稳定 ID
- };
-
- enum class LogicConditionPasteTargetKind
- {
- Append,
- EmptyColumn,
- BranchEmptyColumn,
- AfterNode,
- ReplaceWire,
- ReplaceWireColumn
- };
-
- struct LogicConditionPasteTarget
- {
- LogicConditionPasteTargetKind kind = LogicConditionPasteTargetKind::Append;
- std::string expressionId;
- int column = 0;
- };
-
- // 负责把 UI 编辑命令转换为结构化表达式树操作,并维护撤销/重做
- class LogicEditorService
- {
- public:
- /**
- * @brief 创建梯形图编辑服务
- * @param project_service 用于读取和修改当前工程的项目服务
- */
- explicit LogicEditorService(ProjectService &project_service);
-
- // 以下查询接口只读工程模型,供编辑器投影和属性面板使用
- /** @brief 按 ID 查找控制逻辑,未找到时返回空指针 */
- const ControlLogic *findLogic(const std::string &logic_id) const;
- /** @brief 在指定逻辑中按 ID 查找网络,未找到时返回空指针 */
- const LadderRung *findRung(
- const std::string &logic_id, const std::string &rung_id) const;
- /** @brief 在指定逻辑的所有网络中按 ID 查找节点,未找到时返回空指针 */
- const LogicNode *findNode(
- const std::string &logic_id, const std::string &node_id) const;
- /** @brief 在指定网络中按 ID 查找条件表达式,未找到时返回空指针 */
- const ConditionExpression *findExpression(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &expression_id) const;
- /** @brief 返回工程中第一个控制逻辑 ID,没有逻辑时返回空字符串 */
- std::string firstLogicId() const;
- /** @brief 返回指定逻辑中第一个网络 ID,没有网络或逻辑不存在时返回空字符串 */
- std::string firstRungId(const std::string &logic_id) const;
- /** @brief 查找节点所属网络 ID,未找到时返回空字符串 */
- std::string rungIdForNode(
- const std::string &logic_id, const std::string &node_id) const;
- /** @brief 返回指定 M/D 地址的工程注释,没有注释时返回空字符串 */
- std::string registerCommentFor(const RegisterAddress &address) const;
-
- /**
- * @brief 确保工程至少有一组可编辑逻辑
- * @return 已有或新建逻辑的成功结果及其 ID
- *
- * 新建逻辑时允许暂时没有网络,第一次实际编辑网络内容时再创建网络
- */
- LogicEditorResult ensureDefaultLogic();
- /** @brief 添加控制逻辑;名称不能为空且必须唯一 */
- LogicEditorResult addLogic(const std::string &name);
- /** @brief 修改控制逻辑名称;名称不能为空且必须唯一 */
- LogicEditorResult renameLogic(
- const std::string &logic_id, const std::string &name);
- /** @brief 删除控制逻辑;工程至少保留一组逻辑 */
- LogicEditorResult removeLogic(const std::string &logic_id);
- /** @brief 将逻辑在工程列表中上移或下移一位,offset 只能为 -1 或 1 */
- LogicEditorResult moveLogic(const std::string &logic_id, int offset);
- /** @brief 启用或停用指定控制逻辑 */
- LogicEditorResult setLogicEnabled(const std::string &logic_id, bool enabled);
- /** @brief 在指定逻辑末尾添加空网络 */
- LogicEditorResult addRung(const std::string &logic_id);
- /** @brief 删除指定网络 */
- LogicEditorResult removeRung(
- const std::string &logic_id, const std::string &rung_id);
- /** @brief 修改网络注释;注释长度和换行规则由领域校验约束 */
- LogicEditorResult updateRungComment(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &comment);
- // 条件区编辑:串联、按列插入、横线和并联分支
- /** @brief 在网络条件末尾追加一个条件节点 */
- LogicEditorResult appendCondition(
- const std::string &logic_id,
- const std::string &rung_id,
- const LogicNodeConfig &config);
- /**
- * @brief 按绝对条件列插入条件节点
- * @param column 从 0 开始的条件列号;插入位置必须位于允许的条件区
- */
- LogicEditorResult insertConditionAtColumn(
- const std::string &logic_id,
- const std::string &rung_id,
- int column,
- const LogicNodeConfig &config);
- /**
- * @brief 在直属并联分支的指定视觉列插入条件节点
- *
- * 目标列可以是分支已有横线或 UI 投影出的补线格,服务会原子补齐必要横线
- */
- LogicEditorResult insertConditionInBranchAtColumn(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &branch_expression_id,
- int column,
- const LogicNodeConfig &config);
- /** @brief 在网络条件末尾追加指定列宽的横线 */
- LogicEditorResult appendWire(
- const std::string &logic_id,
- const std::string &rung_id,
- int column_span = 1);
- /** @brief 在目标节点后串联插入条件节点 */
- LogicEditorResult insertConditionAfter(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &target_node_id,
- const LogicNodeConfig &config);
- /** @brief 在目标表达式后串联插入横线 */
- LogicEditorResult insertWireAfter(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &target_expression_id,
- int column_span = 1);
- /** @brief 用条件节点整体替换一条横线表达式 */
- LogicEditorResult replaceWireWithCondition(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &wire_expression_id,
- const LogicNodeConfig &config);
- /** @brief 只替换横线表达式中的一个指定列单元格 */
- LogicEditorResult replaceWireColumnWithCondition(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &wire_expression_id,
- int column_offset,
- const LogicNodeConfig &config);
- /**
- * @brief 将选中的条件节点建立为并联分支
- * @param selected_node_ids 同一网络中按视觉连续范围选择的条件节点 ID
- */
- LogicEditorResult addParallelBranch(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<std::string> &selected_node_ids,
- const LogicNodeConfig &config);
- /** @brief 将选中的横线表达式建立为并联旁路 */
- LogicEditorResult addParallelWireBranch(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<std::string> &selected_expression_ids);
- /**
- * @brief 按选中的横线网格建立并联旁路
- *
- * 所有网格必须来自同一条横线,且列号连续;操作失败时不保留部分修改
- */
- LogicEditorResult addParallelWireBranchAtCells(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<std::pair<std::string, int>> &selected_wire_cells);
- /**
- * @brief 设置网络唯一的输出节点
- * @param configured 是否将新输出标记为已完成配置
- *
- * 输出节点固定位于网络输出槽,替换已有输出时保持一次原子编辑
- */
- LogicEditorResult setOutput(
- const std::string &logic_id,
- const std::string &rung_id,
- const LogicNodeConfig &config,
- bool configured = false);
- // 节点属性和删除操作
- /** @brief 更新节点配置;条件节点和输出节点不能互相改型 */
- LogicEditorResult updateNodeConfig(
- const std::string &logic_id,
- const std::string &node_id,
- const LogicNodeConfig &config);
- /**
- * @brief 批量粘贴条件节点到目标网络末尾
- * @param logic_id 目标控制逻辑
- * @param rung_id 目标网络;为空时自动创建网络
- * @param nodes 待复制节点,只读取配置和 configured 状态
- * @return 成功时返回第一个新节点 ID,失败时整批回滚
- */
- LogicEditorResult pasteConditionNodes(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<LogicNode> &nodes,
- const LogicConditionPasteTarget &target = {});
- /** 判断所选条件是否位于同一串联层级并且视觉连续 */
- bool areConditionNodesContiguous(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<std::string> &node_ids) const;
- /**
- * @brief 将整条网络复制到目标控制逻辑末尾
- * @param logic_id 目标控制逻辑
- * @param source 要复制的网络
- * @return 成功时返回新网络 ID,所有节点和表达式都会获得新 ID
- */
- LogicEditorResult pasteRung(
- const std::string &logic_id,
- const LadderRung &source);
- /** @brief 删除指定节点并归一化受影响的表达式树 */
- LogicEditorResult removeNode(
- const std::string &logic_id, const std::string &node_id);
- /**
- * @brief 批量删除节点
- * @param node_ids 节点 ID 列表,不能为空且不能包含重复或不存在的 ID
- * @return 成功时作为一次编辑记录,失败时整体回滚
- */
- LogicEditorResult removeNodes(
- const std::string &logic_id,
- const std::vector<std::string> &node_ids);
- /** @brief 删除指定条件表达式并归一化表达式树 */
- LogicEditorResult removeExpression(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::string &expression_id);
- /** @brief 批量删除条件表达式,失败时整体回滚 */
- LogicEditorResult removeExpressions(
- const std::string &logic_id,
- const std::string &rung_id,
- const std::vector<std::string> &expression_ids);
-
- // 当前编辑会话的撤销/重做
- /** @brief 判断是否存在可撤销的梯形图编辑操作 */
- bool canUndo() const;
- /** @brief 判断是否存在可重做的梯形图编辑操作 */
- bool canRedo() const;
- /** @brief 撤销最近一次成功的梯形图编辑操作 */
- LogicEditorResult undo();
- /** @brief 重做最近一次被撤销的梯形图编辑操作 */
- LogicEditorResult redo();
- /** @brief 清空撤销/重做历史,不修改当前工程内容 */
- void clearHistory();
-
- private:
- // 只保存逻辑集合快照,当前选择等 UI 会话状态不进入历史
- struct HistoryState
- {
- std::vector<ControlLogic> logics; // 编辑前或编辑后的完整逻辑集合
- };
-
- // 捕获当前工程中的全部控制逻辑
- HistoryState captureState() const;
- // 比较编辑前后状态并记录实际发生的修改
- void recordHistory(HistoryState before);
- // 失败时恢复工程内容和操作前脏标记,保证编辑原子性
- void rollbackEdit(HistoryState before, bool modified_before);
- // 比较两个历史快照是否完全相同
- static bool statesEqual(
- const HistoryState &left, const HistoryState &right);
- static bool logicsEqual(
- const ControlLogic &left, const ControlLogic &right);
- static bool rungsEqual(
- const LadderRung &left, const LadderRung &right);
- static bool expressionsEqual(
- const ConditionExpression &left, const ConditionExpression &right);
- static bool nodesEqual(const LogicNode &left, const LogicNode &right);
- static bool configsEqual(
- const LogicNodeConfig &left, const LogicNodeConfig &right);
- static LogicEditorResult historyFailure(const std::string &message);
-
- // 判断节点配置是否属于条件节点
- static bool isConditionConfig(const LogicNodeConfig &config);
- // 判断节点配置是否属于输出节点
- static bool isOutputConfig(const LogicNodeConfig &config);
- // 返回节点配置对应的稳定 ID 前缀
- static std::string nodePrefix(const LogicNodeConfig &config);
- // 在一个控制逻辑的全部网络中生成唯一节点 ID
- static std::string makeUniqueNodeId(
- const ControlLogic &logic, const std::string &prefix);
- // 生成唯一横线表达式 ID
- static std::string makeUniqueWireId(const ControlLogic &logic);
- // 生成唯一容器表达式 ID
- static std::string makeUniqueExpressionId(const ControlLogic &logic);
- // 生成唯一网络 ID
- static std::string makeUniqueRungId(const ControlLogic &logic);
- // 创建统一的失败结果
- static LogicEditorResult failure(
- LogicEditorError error, const std::string &message);
-
- ProjectService &project_service_; // 不拥有的工程服务依赖
- EditorHistory<HistoryState> history_; // 当前编辑会话的撤销/重做历史
- bool suppress_history_ = false; // 复合粘贴期间由外层统一记录一次历史
- };
|