#include "hmi_editor_service.h" #include "project_service.h" #include #include 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 page was not found"); } // 新控件初始不绑定寄存器,避免自动分配地址造成误写风险 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 page was not found"); } if (findControl(page_id, control_id) == nullptr) { return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); } 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 page was not found"); } if (control == nullptr) { return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); } // 先校验候选位置,失败时不修改工程模型 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 page was not found"); } if (existing == nullptr) { return failure(HmiEditorError::ControlNotFound, "HMI control was not found"); } if (control.type != existing->type) { return failure(HmiEditorError::InvalidControl, "HMI control type cannot be changed"); } 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 control id must be unique within a page"); } 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 control id must not be empty"); 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 control bounds must stay within the page"); return false; } for (const auto &property : control.properties) { if (property.first.empty()) { setError(error, "HMI control property names must not be empty"); return false; } } if (control.binding.has_value() && !control.binding->isValid()) { setError(error, "HMI control binding has an invalid address"); return false; } if (control.binding.has_value() && (control.type == HmiControlType::Button || control.type == HmiControlType::Indicator) && control.binding->area() != RegisterArea::M) { setError(error, "button and indicator controls require an M address"); return false; } if (control.binding.has_value() && (control.type == HmiControlType::NumericDisplay || control.type == HmiControlType::NumericInput) && control.binding->area() != RegisterArea::D) { setError(error, "numeric controls require a D address"); 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(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; } }