综合平台编程器项目的远程存储
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.
 
 
 
 

379 lines
11 KiB

  1. #include "hmi_editor_service.h"
  2. #include "project_service.h"
  3. #include <algorithm>
  4. #include <utility>
  5. namespace {
  6. void setError(std::string *error, const std::string &message)
  7. {
  8. if (error != nullptr)
  9. {
  10. *error = message;
  11. }
  12. }
  13. std::string controlPrefix(HmiControlType type)
  14. {
  15. switch (type)
  16. {
  17. case HmiControlType::Button:
  18. {
  19. return "button";
  20. }
  21. case HmiControlType::Indicator:
  22. {
  23. return "indicator";
  24. }
  25. case HmiControlType::NumericDisplay:
  26. {
  27. return "numeric-display";
  28. }
  29. case HmiControlType::NumericInput:
  30. {
  31. return "numeric-input";
  32. }
  33. case HmiControlType::Label:
  34. default:
  35. {
  36. return "label";
  37. }
  38. }
  39. }
  40. std::string defaultText(HmiControlType type)
  41. {
  42. switch (type)
  43. {
  44. case HmiControlType::Button:
  45. {
  46. return "按钮";
  47. }
  48. case HmiControlType::Indicator:
  49. {
  50. return "指示灯";
  51. }
  52. case HmiControlType::NumericDisplay:
  53. {
  54. return "数值显示";
  55. }
  56. case HmiControlType::NumericInput:
  57. {
  58. return "数值输入";
  59. }
  60. case HmiControlType::Label:
  61. default:
  62. {
  63. return "文本";
  64. }
  65. }
  66. }
  67. HmiEditorResult failure(HmiEditorError error, const std::string &message)
  68. {
  69. return {false, error, message, {}};
  70. }
  71. } // namespace
  72. HmiEditorService::HmiEditorService(ProjectService &project_service)
  73. : project_service_(project_service)
  74. {
  75. }
  76. // 遍历工程所有 HmiPage,根据页面 ID 查找页面,找不到返回空指针
  77. const HmiPage *HmiEditorService::findPage(const std::string &page_id) const
  78. {
  79. const Project &project = project_service_.project();
  80. const auto page = std::find_if(
  81. project.hmiPages.cbegin(),
  82. project.hmiPages.cend(),
  83. [&page_id](const HmiPage &candidate)
  84. {
  85. return candidate.id == page_id;
  86. });
  87. return page == project.hmiPages.cend() ? nullptr : &*page;
  88. }
  89. // 先找到页面 → 在页面内查找指定控件;页面不存在 / 控件不存在都返回 nullptr
  90. const HmiControl *HmiEditorService::findControl(
  91. const std::string &page_id, const std::string &control_id) const
  92. {
  93. const HmiPage *page = findPage(page_id);
  94. if (page == nullptr)
  95. {
  96. return nullptr;
  97. }
  98. const auto control = std::find_if(
  99. page->controls.cbegin(),
  100. page->controls.cend(),
  101. [&control_id](const HmiControl &candidate)
  102. {
  103. return candidate.id == control_id;
  104. });
  105. return control == page->controls.cend() ? nullptr : &*control;
  106. }
  107. // 获取第一个页面 ID;没有页面返回空字符串
  108. std::string HmiEditorService::firstPageId() const
  109. {
  110. const Project &project = project_service_.project();
  111. return project.hmiPages.empty() ? std::string{} : project.hmiPages.front().id;
  112. }
  113. // 保证工程至少存在一个 HMI 页面
  114. HmiEditorResult HmiEditorService::ensureDefaultPage()
  115. {
  116. if (!project_service_.project().hmiPages.empty())
  117. {
  118. return {true, HmiEditorError::None, {}, firstPageId()};
  119. }
  120. // 仅在首个控件操作前创建默认页面,空工程仍可正常保存
  121. HmiPage page;
  122. page.id = "page-1";
  123. page.name = "主操作页面";
  124. Project &project = project_service_.editProject();
  125. project.hmiPages.push_back(std::move(page));
  126. return {true, HmiEditorError::None, {}, project.hmiPages.back().id};
  127. }
  128. HmiEditorResult HmiEditorService::addControl(
  129. const std::string &page_id, HmiControlType type)
  130. {
  131. const HmiPage *page = findPage(page_id);
  132. if (page == nullptr)
  133. {
  134. return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
  135. }
  136. // 新控件初始不绑定寄存器,避免自动分配地址造成误写风险
  137. HmiControl control = makeControl(*page, type);
  138. Project &project = project_service_.editProject();
  139. auto target_page = std::find_if(
  140. project.hmiPages.begin(),
  141. project.hmiPages.end(),
  142. [&page_id](const HmiPage &candidate)
  143. {
  144. return candidate.id == page_id;
  145. });
  146. target_page->controls.push_back(std::move(control));
  147. return {true,
  148. HmiEditorError::None,
  149. {},
  150. target_page->controls.back().id};
  151. }
  152. HmiEditorResult HmiEditorService::removeControl(
  153. const std::string &page_id, const std::string &control_id)
  154. {
  155. if (findPage(page_id) == nullptr)
  156. {
  157. return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
  158. }
  159. if (findControl(page_id, control_id) == nullptr)
  160. {
  161. return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
  162. }
  163. Project &project = project_service_.editProject();
  164. auto page = std::find_if(
  165. project.hmiPages.begin(),
  166. project.hmiPages.end(),
  167. [&page_id](const HmiPage &candidate)
  168. {
  169. return candidate.id == page_id;
  170. });
  171. page->controls.erase(
  172. std::remove_if(
  173. page->controls.begin(),
  174. page->controls.end(),
  175. [&control_id](const HmiControl &candidate)
  176. {
  177. return candidate.id == control_id;
  178. }),
  179. page->controls.end());
  180. return {true, HmiEditorError::None, {}, control_id};
  181. }
  182. // 移动 / 缩放控件
  183. HmiEditorResult HmiEditorService::moveControl(
  184. const std::string &page_id,
  185. const std::string &control_id,
  186. const HmiRect &bounds)
  187. {
  188. const HmiPage *page = findPage(page_id);
  189. const HmiControl *control = findControl(page_id, control_id);
  190. if (page == nullptr)
  191. {
  192. return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
  193. }
  194. if (control == nullptr)
  195. {
  196. return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
  197. }
  198. // 先校验候选位置,失败时不修改工程模型
  199. HmiControl candidate = *control;
  200. candidate.bounds = bounds;
  201. // 更新前保留原控件,只有全部编辑规则通过才覆盖原值
  202. std::string error;
  203. if (!validateEditableControl(*page, candidate, &error))
  204. {
  205. return failure(HmiEditorError::InvalidControl, error);
  206. }
  207. Project &project = project_service_.editProject();
  208. auto target = std::find_if(
  209. project.hmiPages.begin(), project.hmiPages.end(),
  210. [&page_id](const HmiPage &item) { return item.id == page_id; });
  211. auto editable = std::find_if(
  212. target->controls.begin(), target->controls.end(),
  213. [&control_id](const HmiControl &item) { return item.id == control_id; });
  214. editable->bounds = bounds;
  215. return {true, HmiEditorError::None, {}, control_id};
  216. }
  217. // 更新控件全部属性
  218. HmiEditorResult HmiEditorService::updateControl(
  219. const std::string &page_id,
  220. const std::string &control_id,
  221. const HmiControl &control)
  222. {
  223. const HmiPage *page = findPage(page_id);
  224. const HmiControl *existing = findControl(page_id, control_id);
  225. if (page == nullptr)
  226. {
  227. return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
  228. }
  229. if (existing == nullptr)
  230. {
  231. return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
  232. }
  233. if (control.type != existing->type)
  234. {
  235. return failure(HmiEditorError::InvalidControl, "不能修改已创建 HMI 控件的类型");
  236. }
  237. std::string error;
  238. if (!validateEditableControl(*page, control, &error))
  239. {
  240. return failure(HmiEditorError::InvalidControl, error);
  241. }
  242. if (hasDuplicateControlId(*page, control_id, control.id))
  243. {
  244. return failure(HmiEditorError::DuplicateId, "同一 HMI 页面内的控件 ID 必须唯一");
  245. }
  246. Project &project = project_service_.editProject();
  247. auto target = std::find_if(
  248. project.hmiPages.begin(), project.hmiPages.end(),
  249. [&page_id](const HmiPage &item) { return item.id == page_id; });
  250. auto editable = std::find_if(
  251. target->controls.begin(), target->controls.end(),
  252. [&control_id](const HmiControl &item) { return item.id == control_id; });
  253. *editable = control;
  254. return {true, HmiEditorError::None, {}, control.id};
  255. }
  256. bool HmiEditorService::validateEditableControl(
  257. const HmiPage &page, const HmiControl &control, std::string *error)
  258. {
  259. if (control.id.empty())
  260. {
  261. setError(error, "HMI 控件 ID 不能为空");
  262. return false;
  263. }
  264. if (control.bounds.width <= 0 || control.bounds.height <= 0
  265. || control.bounds.x < 0 || control.bounds.y < 0
  266. || control.bounds.width > page.width || control.bounds.height > page.height
  267. || control.bounds.x > page.width - control.bounds.width
  268. || control.bounds.y > page.height - control.bounds.height)
  269. {
  270. setError(error, "HMI 控件不能超出页面范围");
  271. return false;
  272. }
  273. for (const auto &property : control.properties)
  274. {
  275. if (property.first.empty())
  276. {
  277. setError(error, "HMI 控件属性名称不能为空");
  278. return false;
  279. }
  280. }
  281. if (control.binding.has_value() && !control.binding->isValid())
  282. {
  283. setError(error, "HMI 控件绑定了无效地址");
  284. return false;
  285. }
  286. if (control.binding.has_value()
  287. && (control.type == HmiControlType::Button
  288. || control.type == HmiControlType::Indicator)
  289. && control.binding->area() != RegisterArea::M)
  290. {
  291. setError(error, "按钮和指示灯必须绑定 M 区地址");
  292. return false;
  293. }
  294. if (control.binding.has_value()
  295. && (control.type == HmiControlType::NumericDisplay
  296. || control.type == HmiControlType::NumericInput)
  297. && control.binding->area() != RegisterArea::D)
  298. {
  299. setError(error, "数值控件必须绑定 D 区地址");
  300. return false;
  301. }
  302. return true;
  303. }
  304. bool HmiEditorService::hasDuplicateControlId(
  305. const HmiPage &page,
  306. const std::string &excluded_id,
  307. const std::string &candidate_id)
  308. {
  309. return std::any_of(
  310. page.controls.cbegin(), page.controls.cend(),
  311. [&excluded_id, &candidate_id](const HmiControl &item)
  312. {
  313. return item.id != excluded_id && item.id == candidate_id; // 如果这个控件不是正在编辑的原控件,并且它的 ID 等于准备使用的新 ID
  314. });
  315. }
  316. // 根据控件类型生成默认样式、文字、大小、初始坐标
  317. HmiControl HmiEditorService::makeControl(
  318. const HmiPage &page, HmiControlType type)
  319. {
  320. HmiControl control;
  321. control.id = makeUniqueId(page, controlPrefix(type));
  322. control.type = type;
  323. control.text = defaultText(type);
  324. control.bounds.width = type == HmiControlType::Indicator ? 64 : 120;
  325. control.bounds.height = type == HmiControlType::Indicator ? 64 : 40;
  326. const int offset = static_cast<int>(page.controls.size()) * 16;
  327. control.bounds.x = std::min(20 + offset, page.width - control.bounds.width);
  328. control.bounds.y = std::min(20 + offset, page.height - control.bounds.height);
  329. return control;
  330. }
  331. // 生成页面内不重复控件 ID,前缀 + 自增数字
  332. std::string HmiEditorService::makeUniqueId(
  333. const HmiPage &page, const std::string &prefix)
  334. {
  335. // 同类控件从 1 递增命名,保证页面内标识稳定且唯一
  336. int suffix = 1;
  337. while (true)
  338. {
  339. const std::string candidate = prefix + '-' + std::to_string(suffix);
  340. const bool found = std::any_of(
  341. page.controls.cbegin(), page.controls.cend(),
  342. [&candidate](const HmiControl &item) { return item.id == candidate; });
  343. if (!found)
  344. {
  345. return candidate;
  346. }
  347. ++suffix;
  348. }
  349. }