综合平台编程器项目的远程存储
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

321 line
14 KiB

  1. #pragma once
  2. #include "domain/control_logic_model.h"
  3. #include "editor_history.h"
  4. #include <string>
  5. #include <utility>
  6. #include <vector>
  7. class ProjectService;
  8. // 梯形图编辑失败分类
  9. enum class LogicEditorError
  10. {
  11. None, // 操作成功或没有错误
  12. LogicNotFound, // 控制逻辑不存在
  13. RungNotFound, // 网络不存在
  14. ExpressionNotFound, // 条件表达式不存在
  15. NodeNotFound, // 节点不存在
  16. InvalidNode, // 节点配置或节点位置无效
  17. InvalidOperation, // 操作参数或当前结构不允许该操作
  18. UnsupportedNodeChange, // 不允许把条件节点改成输出节点,或反向修改
  19. DuplicateName, // 控制逻辑名称与其他逻辑重复
  20. LastLogicRequired // 删除后不能少于一个控制逻辑
  21. };
  22. // 梯形图编辑结果;成功时 id 通常是新建或更新对象的稳定 ID
  23. struct LogicEditorResult
  24. {
  25. bool succeeded = false; // 操作是否成功
  26. LogicEditorError error = LogicEditorError::None; // 失败时的分类
  27. std::string message; // 面向用户的 UTF-8 成功说明或失败原因
  28. std::string id; // 成功时返回新建或更新对象的稳定 ID
  29. };
  30. enum class LogicConditionPasteTargetKind
  31. {
  32. Append,
  33. EmptyColumn,
  34. BranchEmptyColumn,
  35. AfterNode,
  36. ReplaceWire,
  37. ReplaceWireColumn
  38. };
  39. struct LogicConditionPasteTarget
  40. {
  41. LogicConditionPasteTargetKind kind = LogicConditionPasteTargetKind::Append;
  42. std::string expressionId;
  43. int column = 0;
  44. };
  45. // 负责把 UI 编辑命令转换为结构化表达式树操作,并维护撤销/重做
  46. class LogicEditorService
  47. {
  48. public:
  49. /**
  50. * @brief 创建梯形图编辑服务
  51. * @param project_service 用于读取和修改当前工程的项目服务
  52. */
  53. explicit LogicEditorService(ProjectService &project_service);
  54. // 以下查询接口只读工程模型,供编辑器投影和属性面板使用
  55. /** @brief 按 ID 查找控制逻辑,未找到时返回空指针 */
  56. const ControlLogic *findLogic(const std::string &logic_id) const;
  57. /** @brief 在指定逻辑中按 ID 查找网络,未找到时返回空指针 */
  58. const LadderRung *findRung(
  59. const std::string &logic_id, const std::string &rung_id) const;
  60. /** @brief 在指定逻辑的所有网络中按 ID 查找节点,未找到时返回空指针 */
  61. const LogicNode *findNode(
  62. const std::string &logic_id, const std::string &node_id) const;
  63. /** @brief 在指定网络中按 ID 查找条件表达式,未找到时返回空指针 */
  64. const ConditionExpression *findExpression(
  65. const std::string &logic_id,
  66. const std::string &rung_id,
  67. const std::string &expression_id) const;
  68. /** @brief 返回工程中第一个控制逻辑 ID,没有逻辑时返回空字符串 */
  69. std::string firstLogicId() const;
  70. /** @brief 返回指定逻辑中第一个网络 ID,没有网络或逻辑不存在时返回空字符串 */
  71. std::string firstRungId(const std::string &logic_id) const;
  72. /** @brief 查找节点所属网络 ID,未找到时返回空字符串 */
  73. std::string rungIdForNode(
  74. const std::string &logic_id, const std::string &node_id) const;
  75. /** @brief 返回指定 M/D 地址的工程注释,没有注释时返回空字符串 */
  76. std::string registerCommentFor(const RegisterAddress &address) const;
  77. /**
  78. * @brief 确保工程至少有一组可编辑逻辑
  79. * @return 已有或新建逻辑的成功结果及其 ID
  80. *
  81. * 新建逻辑时允许暂时没有网络,第一次实际编辑网络内容时再创建网络
  82. */
  83. LogicEditorResult ensureDefaultLogic();
  84. /** @brief 添加控制逻辑;名称不能为空且必须唯一 */
  85. LogicEditorResult addLogic(const std::string &name);
  86. /** @brief 修改控制逻辑名称;名称不能为空且必须唯一 */
  87. LogicEditorResult renameLogic(
  88. const std::string &logic_id, const std::string &name);
  89. /** @brief 删除控制逻辑;工程至少保留一组逻辑 */
  90. LogicEditorResult removeLogic(const std::string &logic_id);
  91. /** @brief 将逻辑在工程列表中上移或下移一位,offset 只能为 -1 或 1 */
  92. LogicEditorResult moveLogic(const std::string &logic_id, int offset);
  93. /** @brief 启用或停用指定控制逻辑 */
  94. LogicEditorResult setLogicEnabled(const std::string &logic_id, bool enabled);
  95. /** @brief 在指定逻辑末尾添加空网络 */
  96. LogicEditorResult addRung(const std::string &logic_id);
  97. /** @brief 删除指定网络 */
  98. LogicEditorResult removeRung(
  99. const std::string &logic_id, const std::string &rung_id);
  100. /** @brief 修改网络注释;注释长度和换行规则由领域校验约束 */
  101. LogicEditorResult updateRungComment(
  102. const std::string &logic_id,
  103. const std::string &rung_id,
  104. const std::string &comment);
  105. // 条件区编辑:串联、按列插入、横线和并联分支
  106. /** @brief 在网络条件末尾追加一个条件节点 */
  107. LogicEditorResult appendCondition(
  108. const std::string &logic_id,
  109. const std::string &rung_id,
  110. const LogicNodeConfig &config);
  111. /**
  112. * @brief 按绝对条件列插入条件节点
  113. * @param column 从 0 开始的条件列号;插入位置必须位于允许的条件区
  114. */
  115. LogicEditorResult insertConditionAtColumn(
  116. const std::string &logic_id,
  117. const std::string &rung_id,
  118. int column,
  119. const LogicNodeConfig &config);
  120. /**
  121. * @brief 在直属并联分支的指定视觉列插入条件节点
  122. *
  123. * 目标列可以是分支已有横线或 UI 投影出的补线格,服务会原子补齐必要横线
  124. */
  125. LogicEditorResult insertConditionInBranchAtColumn(
  126. const std::string &logic_id,
  127. const std::string &rung_id,
  128. const std::string &branch_expression_id,
  129. int column,
  130. const LogicNodeConfig &config);
  131. /** @brief 在网络条件末尾追加指定列宽的横线 */
  132. LogicEditorResult appendWire(
  133. const std::string &logic_id,
  134. const std::string &rung_id,
  135. int column_span = 1);
  136. /** @brief 在目标节点后串联插入条件节点 */
  137. LogicEditorResult insertConditionAfter(
  138. const std::string &logic_id,
  139. const std::string &rung_id,
  140. const std::string &target_node_id,
  141. const LogicNodeConfig &config);
  142. /** @brief 在目标表达式后串联插入横线 */
  143. LogicEditorResult insertWireAfter(
  144. const std::string &logic_id,
  145. const std::string &rung_id,
  146. const std::string &target_expression_id,
  147. int column_span = 1);
  148. /** @brief 用条件节点整体替换一条横线表达式 */
  149. LogicEditorResult replaceWireWithCondition(
  150. const std::string &logic_id,
  151. const std::string &rung_id,
  152. const std::string &wire_expression_id,
  153. const LogicNodeConfig &config);
  154. /** @brief 只替换横线表达式中的一个指定列单元格 */
  155. LogicEditorResult replaceWireColumnWithCondition(
  156. const std::string &logic_id,
  157. const std::string &rung_id,
  158. const std::string &wire_expression_id,
  159. int column_offset,
  160. const LogicNodeConfig &config);
  161. /**
  162. * @brief 将选中的条件节点建立为并联分支
  163. * @param selected_node_ids 同一网络中按视觉连续范围选择的条件节点 ID
  164. */
  165. LogicEditorResult addParallelBranch(
  166. const std::string &logic_id,
  167. const std::string &rung_id,
  168. const std::vector<std::string> &selected_node_ids,
  169. const LogicNodeConfig &config);
  170. /** @brief 将选中的横线表达式建立为并联旁路 */
  171. LogicEditorResult addParallelWireBranch(
  172. const std::string &logic_id,
  173. const std::string &rung_id,
  174. const std::vector<std::string> &selected_expression_ids);
  175. /**
  176. * @brief 按选中的横线网格建立并联旁路
  177. *
  178. * 所有网格必须来自同一条横线,且列号连续;操作失败时不保留部分修改
  179. */
  180. LogicEditorResult addParallelWireBranchAtCells(
  181. const std::string &logic_id,
  182. const std::string &rung_id,
  183. const std::vector<std::pair<std::string, int>> &selected_wire_cells);
  184. /**
  185. * @brief 设置网络唯一的输出节点
  186. * @param configured 是否将新输出标记为已完成配置
  187. *
  188. * 输出节点固定位于网络输出槽,替换已有输出时保持一次原子编辑
  189. */
  190. LogicEditorResult setOutput(
  191. const std::string &logic_id,
  192. const std::string &rung_id,
  193. const LogicNodeConfig &config,
  194. bool configured = false);
  195. // 节点属性和删除操作
  196. /** @brief 更新节点配置;条件节点和输出节点不能互相改型 */
  197. LogicEditorResult updateNodeConfig(
  198. const std::string &logic_id,
  199. const std::string &node_id,
  200. const LogicNodeConfig &config);
  201. /**
  202. * @brief 批量粘贴条件节点到目标网络末尾
  203. * @param logic_id 目标控制逻辑
  204. * @param rung_id 目标网络;为空时自动创建网络
  205. * @param nodes 待复制节点,只读取配置和 configured 状态
  206. * @return 成功时返回第一个新节点 ID,失败时整批回滚
  207. */
  208. LogicEditorResult pasteConditionNodes(
  209. const std::string &logic_id,
  210. const std::string &rung_id,
  211. const std::vector<LogicNode> &nodes,
  212. const LogicConditionPasteTarget &target = {});
  213. /** 判断所选条件是否位于同一串联层级并且视觉连续 */
  214. bool areConditionNodesContiguous(
  215. const std::string &logic_id,
  216. const std::string &rung_id,
  217. const std::vector<std::string> &node_ids) const;
  218. /**
  219. * @brief 将整条网络复制到目标控制逻辑末尾
  220. * @param logic_id 目标控制逻辑
  221. * @param source 要复制的网络
  222. * @return 成功时返回新网络 ID,所有节点和表达式都会获得新 ID
  223. */
  224. LogicEditorResult pasteRung(
  225. const std::string &logic_id,
  226. const LadderRung &source);
  227. /** @brief 删除指定节点并归一化受影响的表达式树 */
  228. LogicEditorResult removeNode(
  229. const std::string &logic_id, const std::string &node_id);
  230. /**
  231. * @brief 批量删除节点
  232. * @param node_ids 节点 ID 列表,不能为空且不能包含重复或不存在的 ID
  233. * @return 成功时作为一次编辑记录,失败时整体回滚
  234. */
  235. LogicEditorResult removeNodes(
  236. const std::string &logic_id,
  237. const std::vector<std::string> &node_ids);
  238. /** @brief 删除指定条件表达式并归一化表达式树 */
  239. LogicEditorResult removeExpression(
  240. const std::string &logic_id,
  241. const std::string &rung_id,
  242. const std::string &expression_id);
  243. /** @brief 批量删除条件表达式,失败时整体回滚 */
  244. LogicEditorResult removeExpressions(
  245. const std::string &logic_id,
  246. const std::string &rung_id,
  247. const std::vector<std::string> &expression_ids);
  248. // 当前编辑会话的撤销/重做
  249. /** @brief 判断是否存在可撤销的梯形图编辑操作 */
  250. bool canUndo() const;
  251. /** @brief 判断是否存在可重做的梯形图编辑操作 */
  252. bool canRedo() const;
  253. /** @brief 撤销最近一次成功的梯形图编辑操作 */
  254. LogicEditorResult undo();
  255. /** @brief 重做最近一次被撤销的梯形图编辑操作 */
  256. LogicEditorResult redo();
  257. /** @brief 清空撤销/重做历史,不修改当前工程内容 */
  258. void clearHistory();
  259. private:
  260. // 只保存逻辑集合快照,当前选择等 UI 会话状态不进入历史
  261. struct HistoryState
  262. {
  263. std::vector<ControlLogic> logics; // 编辑前或编辑后的完整逻辑集合
  264. };
  265. // 捕获当前工程中的全部控制逻辑
  266. HistoryState captureState() const;
  267. // 比较编辑前后状态并记录实际发生的修改
  268. void recordHistory(HistoryState before);
  269. // 失败时恢复工程内容和操作前脏标记,保证编辑原子性
  270. void rollbackEdit(HistoryState before, bool modified_before);
  271. // 比较两个历史快照是否完全相同
  272. static bool statesEqual(
  273. const HistoryState &left, const HistoryState &right);
  274. static bool logicsEqual(
  275. const ControlLogic &left, const ControlLogic &right);
  276. static bool rungsEqual(
  277. const LadderRung &left, const LadderRung &right);
  278. static bool expressionsEqual(
  279. const ConditionExpression &left, const ConditionExpression &right);
  280. static bool nodesEqual(const LogicNode &left, const LogicNode &right);
  281. static bool configsEqual(
  282. const LogicNodeConfig &left, const LogicNodeConfig &right);
  283. static LogicEditorResult historyFailure(const std::string &message);
  284. // 判断节点配置是否属于条件节点
  285. static bool isConditionConfig(const LogicNodeConfig &config);
  286. // 判断节点配置是否属于输出节点
  287. static bool isOutputConfig(const LogicNodeConfig &config);
  288. // 返回节点配置对应的稳定 ID 前缀
  289. static std::string nodePrefix(const LogicNodeConfig &config);
  290. // 在一个控制逻辑的全部网络中生成唯一节点 ID
  291. static std::string makeUniqueNodeId(
  292. const ControlLogic &logic, const std::string &prefix);
  293. // 生成唯一横线表达式 ID
  294. static std::string makeUniqueWireId(const ControlLogic &logic);
  295. // 生成唯一容器表达式 ID
  296. static std::string makeUniqueExpressionId(const ControlLogic &logic);
  297. // 生成唯一网络 ID
  298. static std::string makeUniqueRungId(const ControlLogic &logic);
  299. // 创建统一的失败结果
  300. static LogicEditorResult failure(
  301. LogicEditorError error, const std::string &message);
  302. ProjectService &project_service_; // 不拥有的工程服务依赖
  303. EditorHistory<HistoryState> history_; // 当前编辑会话的撤销/重做历史
  304. bool suppress_history_ = false; // 复合粘贴期间由外层统一记录一次历史
  305. };