|
- #include "hmi_editor_service.h"
-
- #include "project_service.h"
-
- #include <algorithm>
- #include <utility>
-
- namespace {
-
- void setError(std::string *error, const std::string &message)
- {
- if (error != nullptr)
- {
- *error = message;
- }
- }
-
- std::string controlPrefix(HmiControlType type)
- {
- switch (type)
- {
- case HmiControlType::Button:
- {
- return "button";
- }
- case HmiControlType::Indicator:
- {
- return "indicator";
- }
- case HmiControlType::NumericDisplay:
- {
- return "numeric-display";
- }
- case HmiControlType::NumericInput:
- {
- return "numeric-input";
- }
- case HmiControlType::Label:
- default:
- {
- return "label";
- }
- }
- }
-
- std::string defaultText(HmiControlType type)
- {
- switch (type)
- {
- case HmiControlType::Button:
- {
- return "按钮";
- }
- case HmiControlType::Indicator:
- {
- return "指示灯";
- }
- case HmiControlType::NumericDisplay:
- {
- return "数值显示";
- }
- case HmiControlType::NumericInput:
- {
- return "数值输入";
- }
- case HmiControlType::Label:
- default:
- {
- return "文本";
- }
- }
- }
-
- HmiEditorResult failure(HmiEditorError error, const std::string &message)
- {
- return {false, error, message, {}};
- }
-
- } // namespace
-
- HmiEditorService::HmiEditorService(ProjectService &project_service)
- : project_service_(project_service)
- {
- }
-
- // 遍历工程所有 HmiPage,根据页面 ID 查找页面,找不到返回空指针
- const HmiPage *HmiEditorService::findPage(const std::string &page_id) const
- {
- const Project &project = project_service_.project();
- const auto page = std::find_if(
- project.hmiPages.cbegin(),
- project.hmiPages.cend(),
- [&page_id](const HmiPage &candidate)
- {
- return candidate.id == page_id;
- });
- return page == project.hmiPages.cend() ? nullptr : &*page;
- }
-
- // 先找到页面 → 在页面内查找指定控件;页面不存在 / 控件不存在都返回 nullptr
- const HmiControl *HmiEditorService::findControl(
- const std::string &page_id, const std::string &control_id) const
- {
- const HmiPage *page = findPage(page_id);
- if (page == nullptr)
- {
- return nullptr;
- }
- const auto control = std::find_if(
- page->controls.cbegin(),
- page->controls.cend(),
- [&control_id](const HmiControl &candidate)
- {
- return candidate.id == control_id;
- });
- return control == page->controls.cend() ? nullptr : &*control;
- }
-
- // 获取第一个页面 ID;没有页面返回空字符串
- std::string HmiEditorService::firstPageId() const
- {
- const Project &project = project_service_.project();
- return project.hmiPages.empty() ? std::string{} : project.hmiPages.front().id;
- }
-
- // 保证工程至少存在一个 HMI 页面
- HmiEditorResult HmiEditorService::ensureDefaultPage()
- {
- if (!project_service_.project().hmiPages.empty())
- {
- return {true, HmiEditorError::None, {}, firstPageId()};
- }
-
- // 仅在首个控件操作前创建默认页面,空工程仍可正常保存
- HmiPage page;
- page.id = "page-1";
- page.name = "主操作页面";
-
- Project &project = project_service_.editProject();
- project.hmiPages.push_back(std::move(page));
- return {true, HmiEditorError::None, {}, project.hmiPages.back().id};
- }
-
- HmiEditorResult HmiEditorService::addControl(
- const std::string &page_id, HmiControlType type)
- {
- const HmiPage *page = findPage(page_id);
- if (page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
-
- // 新控件初始不绑定寄存器,避免自动分配地址造成误写风险
- HmiControl control = makeControl(*page, type);
- Project &project = project_service_.editProject();
- auto target_page = std::find_if(
- project.hmiPages.begin(),
- project.hmiPages.end(),
- [&page_id](const HmiPage &candidate)
- {
- return candidate.id == page_id;
- });
- target_page->controls.push_back(std::move(control));
- return {true,
- HmiEditorError::None,
- {},
- target_page->controls.back().id};
- }
-
- HmiEditorResult HmiEditorService::removeControl(
- const std::string &page_id, const std::string &control_id)
- {
- if (findPage(page_id) == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (findControl(page_id, control_id) == nullptr)
- {
- return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
- }
-
- Project &project = project_service_.editProject();
- auto page = std::find_if(
- project.hmiPages.begin(),
- project.hmiPages.end(),
- [&page_id](const HmiPage &candidate)
- {
- return candidate.id == page_id;
- });
- page->controls.erase(
- std::remove_if(
- page->controls.begin(),
- page->controls.end(),
- [&control_id](const HmiControl &candidate)
- {
- return candidate.id == control_id;
- }),
- page->controls.end());
- return {true, HmiEditorError::None, {}, control_id};
- }
-
- // 移动 / 缩放控件
- HmiEditorResult HmiEditorService::moveControl(
- const std::string &page_id,
- const std::string &control_id,
- const HmiRect &bounds)
- {
- const HmiPage *page = findPage(page_id);
- const HmiControl *control = findControl(page_id, control_id);
- if (page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (control == nullptr)
- {
- return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
- }
-
- // 先校验候选位置,失败时不修改工程模型
- HmiControl candidate = *control;
- candidate.bounds = bounds;
- // 更新前保留原控件,只有全部编辑规则通过才覆盖原值
- std::string error;
- if (!validateEditableControl(*page, candidate, &error))
- {
- return failure(HmiEditorError::InvalidControl, error);
- }
-
- Project &project = project_service_.editProject();
- auto target = std::find_if(
- project.hmiPages.begin(), project.hmiPages.end(),
- [&page_id](const HmiPage &item) { return item.id == page_id; });
- auto editable = std::find_if(
- target->controls.begin(), target->controls.end(),
- [&control_id](const HmiControl &item) { return item.id == control_id; });
- editable->bounds = bounds;
- return {true, HmiEditorError::None, {}, control_id};
- }
-
- // 更新控件全部属性
- HmiEditorResult HmiEditorService::updateControl(
- const std::string &page_id,
- const std::string &control_id,
- const HmiControl &control)
- {
- const HmiPage *page = findPage(page_id);
- const HmiControl *existing = findControl(page_id, control_id);
- if (page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (existing == nullptr)
- {
- return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
- }
- if (control.type != existing->type)
- {
- return failure(HmiEditorError::InvalidControl, "不能修改已创建 HMI 控件的类型");
- }
-
- std::string error;
- if (!validateEditableControl(*page, control, &error))
- {
- return failure(HmiEditorError::InvalidControl, error);
- }
- if (hasDuplicateControlId(*page, control_id, control.id))
- {
- return failure(HmiEditorError::DuplicateId, "同一 HMI 页面内的控件 ID 必须唯一");
- }
-
- Project &project = project_service_.editProject();
- auto target = std::find_if(
- project.hmiPages.begin(), project.hmiPages.end(),
- [&page_id](const HmiPage &item) { return item.id == page_id; });
- auto editable = std::find_if(
- target->controls.begin(), target->controls.end(),
- [&control_id](const HmiControl &item) { return item.id == control_id; });
- *editable = control;
- return {true, HmiEditorError::None, {}, control.id};
- }
-
- bool HmiEditorService::validateEditableControl(
- const HmiPage &page, const HmiControl &control, std::string *error)
- {
- if (control.id.empty())
- {
- setError(error, "HMI 控件 ID 不能为空");
- return false;
- }
- if (control.bounds.width <= 0 || control.bounds.height <= 0
- || control.bounds.x < 0 || control.bounds.y < 0
- || control.bounds.width > page.width || control.bounds.height > page.height
- || control.bounds.x > page.width - control.bounds.width
- || control.bounds.y > page.height - control.bounds.height)
- {
- setError(error, "HMI 控件不能超出页面范围");
- return false;
- }
- for (const auto &property : control.properties)
- {
- if (property.first.empty())
- {
- setError(error, "HMI 控件属性名称不能为空");
- return false;
- }
- }
- if (control.binding.has_value() && !control.binding->isValid())
- {
- setError(error, "HMI 控件绑定了无效地址");
- return false;
- }
- if (control.binding.has_value()
- && (control.type == HmiControlType::Button
- || control.type == HmiControlType::Indicator)
- && control.binding->area() != RegisterArea::M)
- {
- setError(error, "按钮和指示灯必须绑定 M 区地址");
- return false;
- }
- if (control.binding.has_value()
- && (control.type == HmiControlType::NumericDisplay
- || control.type == HmiControlType::NumericInput)
- && control.binding->area() != RegisterArea::D)
- {
- setError(error, "数值控件必须绑定 D 区地址");
- return false;
- }
- return true;
- }
-
- bool HmiEditorService::hasDuplicateControlId(
- const HmiPage &page,
- const std::string &excluded_id,
- const std::string &candidate_id)
- {
- return std::any_of(
- page.controls.cbegin(), page.controls.cend(),
- [&excluded_id, &candidate_id](const HmiControl &item)
- {
- return item.id != excluded_id && item.id == candidate_id; // 如果这个控件不是正在编辑的原控件,并且它的 ID 等于准备使用的新 ID
- });
- }
-
- // 根据控件类型生成默认样式、文字、大小、初始坐标
- HmiControl HmiEditorService::makeControl(
- const HmiPage &page, HmiControlType type)
- {
- HmiControl control;
- control.id = makeUniqueId(page, controlPrefix(type));
- control.type = type;
- control.text = defaultText(type);
- control.bounds.width = type == HmiControlType::Indicator ? 64 : 120;
- control.bounds.height = type == HmiControlType::Indicator ? 64 : 40;
- const int offset = static_cast<int>(page.controls.size()) * 16;
- control.bounds.x = std::min(20 + offset, page.width - control.bounds.width);
- control.bounds.y = std::min(20 + offset, page.height - control.bounds.height);
- return control;
- }
-
- // 生成页面内不重复控件 ID,前缀 + 自增数字
- std::string HmiEditorService::makeUniqueId(
- const HmiPage &page, const std::string &prefix)
- {
- // 同类控件从 1 递增命名,保证页面内标识稳定且唯一
- int suffix = 1;
- while (true)
- {
- const std::string candidate = prefix + '-' + std::to_string(suffix);
- const bool found = std::any_of(
- page.controls.cbegin(), page.controls.cend(),
- [&candidate](const HmiControl &item) { return item.id == candidate; });
- if (!found)
- {
- return candidate;
- }
- ++suffix;
- }
- }
|