|
- #include "hmi_editor_service.h"
-
- #include "domain/hmi_control_registry.h"
- #include "project_service.h"
- #include "domain/project_limits.h"
-
- #include <algorithm>
- #include <cctype>
- #include <cstddef>
- #include <map>
- #include <optional>
- #include <set>
- #include <utility>
-
- namespace {
-
- // 仅在调用方提供错误字符串时写入失败原因
- void setError(std::string *error, const std::string &message)
- {
- if (error != nullptr)
- {
- *error = message;
- }
- }
-
- // 创建统一的 HMI 编辑失败结果
- HmiEditorResult failure(HmiEditorError error, const std::string &message)
- {
- return {false, error, message, {}};
- }
-
- // 返回控件绑定值连续占用的 D 字数量
- int hmiBindingWordCount(const HmiControl &control)
- {
- if (control.binding.has_value() && control.binding->area() == RegisterArea::M)
- {
- return 1;
- }
- if (control.type == HmiControlType::NumericDisplay
- || control.type == HmiControlType::NumericInput
- || (control.type == HmiControlType::StatusText
- && control.binding.has_value()
- && control.binding->area() == RegisterArea::D))
- {
- return registerDataTypeWordCount(control.dataType);
- }
- return 1;
- }
-
- // 在指定寄存器区域内查找控件可用的首个连续地址
- std::optional<RegisterAddress> nextHmiBindingAddress(
- const Project &project,
- const HmiControlDescriptor &descriptor,
- RegisterDataType data_type)
- {
- std::optional<RegisterArea> area = hmiBindingArea(descriptor.bindingKind);
- if (descriptor.bindingKind == HmiBindingKind::BitOrWord)
- {
- area = RegisterArea::M;
- }
- if (!area.has_value())
- {
- return std::nullopt;
- }
-
- int next_index = RegisterAddress::kMinimumIndex;
- for (const HmiPage &page : project.hmiPages)
- {
- for (const HmiControl &control : page.controls)
- {
- if (!control.binding.has_value()
- || control.binding->area() != *area)
- {
- continue;
- }
- next_index = std::max(
- next_index,
- control.binding->index() + hmiBindingWordCount(control));
- }
- }
- for (int index = next_index;
- index <= RegisterAddress::kMaximumIndex;
- ++index)
- {
- const RegisterAddress candidate{*area, index};
- if (*area == RegisterArea::D
- && !registerDataTypeAddressIsValid(data_type, candidate))
- {
- continue;
- }
- return candidate;
- }
- return std::nullopt;
- }
-
- // 判断页面名称是否只包含空白字符
- bool isBlank(const std::string &value)
- {
- return value.empty()
- || std::all_of(
- value.cbegin(), value.cend(),
- [](unsigned char character) { return std::isspace(character) != 0; });
- }
-
- // 从 page-1 开始递增生成工程内唯一的页面 ID
- std::string makeUniquePageId(const Project &project)
- {
- int suffix = 1;
- while (true)
- {
- const std::string candidate = "page-" + std::to_string(suffix);
- const bool found = std::any_of(
- project.hmiPages.cbegin(), project.hmiPages.cend(),
- [&candidate](const HmiPage &page) { return page.id == candidate; });
- if (!found)
- {
- return candidate;
- }
- ++suffix;
- }
- }
-
- // 统计当前工程全部 HMI 页面中的控件总数
- std::size_t totalControlCount(const Project &project)
- {
- std::size_t total = 0U;
- for (const HmiPage &page : project.hmiPages)
- {
- total += page.controls.size();
- }
- return total;
- }
-
- } // namespace
-
- // 保存工程服务和 HMI 默认配置的引用
- HmiEditorService::HmiEditorService(
- ProjectService &project_service,
- const HmiDefaultSettings &hmi_defaults)
- : project_service_(project_service),
- hmi_defaults_(hmi_defaults)
- {
- }
-
- // 深拷贝当前页面集合和初始页面 ID 作为历史快照
- HmiEditorService::HistoryState HmiEditorService::captureState() const
- {
- const Project &project = project_service_.project();
- return {project.hmiPages, project.initialHmiPageId};
- }
-
- // 仅在编辑确实改变工程状态时记录撤销快照
- void HmiEditorService::recordHistory(HistoryState before)
- {
- const HistoryState after = captureState();
- history_.record(std::move(before), after, &HmiEditorService::statesEqual);
- }
-
- // 比较两个完整 HMI 编辑快照是否一致
- bool HmiEditorService::statesEqual(
- const HistoryState &left, const HistoryState &right)
- {
- if (left.initial_page_id != right.initial_page_id
- || left.pages.size() != right.pages.size())
- {
- return false;
- }
- for (std::size_t index = 0; index < left.pages.size(); ++index)
- {
- if (!pagesEqual(left.pages[index], right.pages[index]))
- {
- return false;
- }
- }
- return true;
- }
-
- // 比较两个页面的基础属性和全部控件
- bool HmiEditorService::pagesEqual(
- const HmiPage &left, const HmiPage &right)
- {
- if (left.id != right.id || left.name != right.name
- || left.width != right.width || left.height != right.height
- || left.controls.size() != right.controls.size())
- {
- return false;
- }
- for (std::size_t index = 0; index < left.controls.size(); ++index)
- {
- if (!controlsEqual(left.controls[index], right.controls[index]))
- {
- return false;
- }
- }
- return true;
- }
-
- // 比较两个控件中所有会保存到工程的属性
- bool HmiEditorService::controlsEqual(
- const HmiControl &left, const HmiControl &right)
- {
- const bool page_jump_equal = left.pageJump.has_value() == right.pageJump.has_value()
- && (!left.pageJump.has_value()
- || left.pageJump->targetPageId == right.pageJump->targetPageId);
- return left.id == right.id
- && left.type == right.type
- && left.bounds.x == right.bounds.x
- && left.bounds.y == right.bounds.y
- && left.bounds.width == right.bounds.width
- && left.bounds.height == right.bounds.height
- && left.text == right.text
- && left.binding == right.binding
- && left.dataType == right.dataType
- && left.properties == right.properties
- && left.buttonOperation == right.buttonOperation
- && left.buttonEnableCondition == right.buttonEnableCondition
- && left.statusText == right.statusText
- && page_jump_equal;
- }
-
- // 创建撤销或重做不可执行时的统一失败结果
- HmiEditorResult HmiEditorService::historyFailure(const std::string &message)
- {
- return {false, HmiEditorError::InvalidOperation, message, {}};
- }
-
- // 遍历工程所有 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())
- {
- if (project_service_.project().initialHmiPageId.empty())
- {
- Project &project = project_service_.editProject();
- project.initialHmiPageId = project.hmiPages.front().id;
- }
- return {true, HmiEditorError::None, {}, firstPageId()};
- }
-
- // 仅在首个控件操作前创建默认页面,空工程仍可正常保存
- HistoryState before = captureState();
- HmiPage page;
- page.id = "page-1";
- page.name = "主操作页面";
- page.width = hmi_defaults_.pageWidth;
- page.height = hmi_defaults_.pageHeight;
-
- Project &project = project_service_.editProject();
- project.hmiPages.push_back(std::move(page));
- project.initialHmiPageId = project.hmiPages.back().id;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, project.hmiPages.back().id};
- }
-
- // 新建并校验一个页面,成功后记录单步撤销历史
- HmiEditorResult HmiEditorService::addPage(const std::string &name)
- {
- if (isBlank(name))
- {
- return failure(HmiEditorError::InvalidPage, "HMI 页面名称不能为空");
- }
- const Project ¤t = project_service_.project();
- const ProjectLimitSettings &limits = project_service_.projectLimits();
- if (current.hmiPages.size() >= limits.maximumHmiPages)
- {
- return failure(
- HmiEditorError::InvalidPage,
- "当前配置最多允许 "
- + std::to_string(limits.maximumHmiPages) + " 个 HMI 页面");
- }
- if (name.size() > ProjectLimits::kMaximumTextBytes)
- {
- return failure(HmiEditorError::InvalidPage, "HMI 页面名称不能超过 256 个 UTF-8 字节");
- }
- const bool duplicate = std::any_of(
- current.hmiPages.cbegin(), current.hmiPages.cend(),
- [&name](const HmiPage &page) { return page.name == name; });
- if (duplicate)
- {
- return failure(HmiEditorError::DuplicateName, "HMI 页面名称必须唯一");
- }
-
- HmiPage page;
- page.id = makeUniquePageId(current);
- page.name = name;
- page.width = hmi_defaults_.pageWidth;
- page.height = hmi_defaults_.pageHeight;
- HistoryState before = captureState();
- Project &project = project_service_.editProject();
- project.hmiPages.push_back(std::move(page));
- if (project.initialHmiPageId.empty())
- {
- project.initialHmiPageId = project.hmiPages.back().id;
- }
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, project.hmiPages.back().id};
- }
-
- // 校验新尺寸不会让已有控件越界后调整页面大小
- HmiEditorResult HmiEditorService::resizePage(
- const std::string &page_id, int width, int height)
- {
- const HmiPage *page = findPage(page_id);
- if (page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (page->width == width && page->height == height)
- {
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- // 先在副本上校验新页面边界,失败时不触碰当前工程
- HmiPage candidate = *page;
- candidate.width = width;
- candidate.height = height;
- std::string error;
- if (!candidate.validate(project_service_.projectLimits(), &error))
- {
- return failure(HmiEditorError::InvalidPage, error);
- }
-
- HistoryState before = captureState();
- Project &project = project_service_.editProject();
- auto editable = std::find_if(
- project.hmiPages.begin(), project.hmiPages.end(),
- [&page_id](const HmiPage &item) { return item.id == page_id; });
- editable->width = width;
- editable->height = height;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- // 校验名称非空且不重复后修改页面名称
- HmiEditorResult HmiEditorService::renamePage(
- const std::string &page_id, const std::string &name)
- {
- if (isBlank(name))
- {
- return failure(HmiEditorError::InvalidPage, "HMI 页面名称不能为空");
- }
- if (name.size() > ProjectLimits::kMaximumTextBytes)
- {
- return failure(HmiEditorError::InvalidPage, "HMI 页面名称不能超过 256 个 UTF-8 字节");
- }
- const Project ¤t = project_service_.project();
- const auto existing = std::find_if(
- current.hmiPages.cbegin(), current.hmiPages.cend(),
- [&page_id](const HmiPage &page) { return page.id == page_id; });
- if (existing == current.hmiPages.cend())
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- const bool duplicate = std::any_of(
- current.hmiPages.cbegin(), current.hmiPages.cend(),
- [&page_id, &name](const HmiPage &page)
- {
- return page.id != page_id && page.name == name;
- });
- if (duplicate)
- {
- return failure(HmiEditorError::DuplicateName, "HMI 页面名称必须唯一");
- }
- if (existing->name == name)
- {
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- HistoryState before = captureState();
- 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->name = name;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- // 确认页面可删除且未被引用后从工程中移除页面
- HmiEditorResult HmiEditorService::removePage(const std::string &page_id)
- {
- const Project ¤t = project_service_.project();
- const auto page = std::find_if(
- current.hmiPages.cbegin(), current.hmiPages.cend(),
- [&page_id](const HmiPage &candidate) { return candidate.id == page_id; });
- if (page == current.hmiPages.cend())
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (current.hmiPages.size() <= 1)
- {
- return failure(HmiEditorError::LastPageRequired, "工程至少需要保留一个 HMI 页面");
- }
- if (current.initialHmiPageId == page_id)
- {
- return failure(
- HmiEditorError::InitialPageCannotBeRemoved,
- "请先设置其他初始页面,再删除当前页面");
- }
- for (const HmiPage &source_page : current.hmiPages)
- {
- const auto reference = std::find_if(
- source_page.controls.cbegin(), source_page.controls.cend(),
- [&page_id](const HmiControl &control)
- {
- return control.type == HmiControlType::PageJump
- && control.pageJump.has_value()
- && control.pageJump->targetPageId == page_id;
- });
- if (reference != source_page.controls.cend())
- {
- return failure(
- HmiEditorError::PageReferenced,
- "页面仍被跳转控件 " + reference->id + " 引用");
- }
- }
-
- HistoryState before = captureState();
- Project &project = project_service_.editProject();
- project.hmiPages.erase(
- std::remove_if(
- project.hmiPages.begin(), project.hmiPages.end(),
- [&page_id](const HmiPage &candidate) { return candidate.id == page_id; }),
- project.hmiPages.end());
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- // 将指定页面在页面列表中上移或下移一位
- HmiEditorResult HmiEditorService::movePage(const std::string &page_id, int offset)
- {
- if (offset != -1 && offset != 1)
- {
- return failure(HmiEditorError::InvalidOperation, "页面每次只能上移或下移一位");
- }
- const Project ¤t = project_service_.project();
- const auto page = std::find_if(
- current.hmiPages.cbegin(), current.hmiPages.cend(),
- [&page_id](const HmiPage &candidate) { return candidate.id == page_id; });
- if (page == current.hmiPages.cend())
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- const auto index = std::distance(current.hmiPages.cbegin(), page);
- const std::ptrdiff_t target_index = index + offset;
- if (target_index < 0
- || target_index >= static_cast<std::ptrdiff_t>(current.hmiPages.size()))
- {
- return failure(HmiEditorError::InvalidOperation, "HMI 页面已经位于目标边界");
- }
-
- HistoryState before = captureState();
- Project &project = project_service_.editProject();
- std::iter_swap(
- project.hmiPages.begin() + index,
- project.hmiPages.begin() + target_index);
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, page_id};
- }
-
- // 将指定页面设置为工程启动时显示的初始页面
- HmiEditorResult HmiEditorService::setInitialPage(const std::string &page_id)
- {
- if (findPage(page_id) == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (project_service_.project().initialHmiPageId == page_id)
- {
- return {true, HmiEditorError::None, {}, page_id};
- }
- HistoryState before = captureState();
- project_service_.editProject().initialHmiPageId = page_id;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, page_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 页面");
- }
- const ProjectLimitSettings &limits = project_service_.projectLimits();
- if (page->controls.size() >= limits.maximumHmiControlsPerPage)
- {
- return failure(
- HmiEditorError::InvalidControl,
- "当前配置下单个 HMI 页面最多允许 "
- + std::to_string(limits.maximumHmiControlsPerPage) + " 个控件");
- }
- if (totalControlCount(project_service_.project())
- >= ProjectLimits::kMaximumHmiControlsPerProject)
- {
- return failure(HmiEditorError::InvalidControl, "单个工程最多包含 2048 个 HMI 控件");
- }
- const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type);
- if (descriptor == nullptr)
- {
- return failure(HmiEditorError::InvalidControl, "不支持的 HMI 控件类型");
- }
-
- HmiControl control = makeControl(*page, *descriptor);
- const std::optional<RegisterAddress> binding = nextHmiBindingAddress(
- project_service_.project(), *descriptor, control.dataType);
- if (descriptor->bindingKind != HmiBindingKind::None && !binding.has_value())
- {
- return failure(
- HmiEditorError::InvalidControl,
- "没有可用的 HMI 默认寄存器地址");
- }
- control.binding = binding;
- HistoryState before = captureState();
- 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));
- recordHistory(std::move(before));
- return {true,
- HmiEditorError::None,
- {},
- target_page->controls.back().id};
- }
-
- // 通过批量删除入口删除单个控件
- HmiEditorResult HmiEditorService::removeControl(
- const std::string &page_id, const std::string &control_id)
- {
- return removeControls(page_id, {control_id});
- }
-
- // 完整校验所选控件后一次性删除,避免留下部分修改
- HmiEditorResult HmiEditorService::removeControls(
- const std::string &page_id,
- const std::vector<std::string> &control_ids)
- {
- if (findPage(page_id) == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (control_ids.empty())
- {
- return failure(HmiEditorError::InvalidOperation, "请先选择要删除的 HMI 控件");
- }
- std::set<std::string> selected_ids;
- for (const std::string &control_id : control_ids)
- {
- if (!selected_ids.insert(control_id).second)
- {
- return failure(HmiEditorError::InvalidOperation, "删除列表中存在重复控件");
- }
- if (findControl(page_id, control_id) == nullptr)
- {
- return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
- }
- }
-
- HistoryState before = captureState();
- 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(),
- [&selected_ids](const HmiControl &candidate)
- {
- return selected_ids.find(candidate.id) != selected_ids.end();
- }),
- page->controls.end());
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, control_ids.front()};
- }
-
- // 校验候选矩形后移动或缩放指定控件
- 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);
- }
- if (control->bounds.x == bounds.x
- && control->bounds.y == bounds.y
- && control->bounds.width == bounds.width
- && control->bounds.height == bounds.height)
- {
- return {true, HmiEditorError::None, {}, control_id};
- }
-
- HistoryState before = captureState();
- 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;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, control_id};
- }
-
- // 按所选控件的整体范围批量计算并应用对齐位置
- HmiEditorResult HmiEditorService::alignControls(
- const std::string &page_id,
- const std::vector<std::string> &control_ids,
- HmiAlignment alignment)
- {
- const HmiPage *page = findPage(page_id);
- if (page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (control_ids.size() < 2U)
- {
- return failure(
- HmiEditorError::InvalidOperation,
- "请至少选择两个 HMI 控件");
- }
-
- std::set<std::string> selected_ids;
- for (const std::string &control_id : control_ids)
- {
- if (!selected_ids.insert(control_id).second)
- {
- return failure(
- HmiEditorError::InvalidOperation,
- "对齐列表中存在重复控件");
- }
- if (findControl(page_id, control_id) == nullptr)
- {
- return failure(HmiEditorError::ControlNotFound, "未找到 HMI 控件");
- }
- }
-
- const HmiControl *first = findControl(page_id, control_ids.front());
- int left = first->bounds.x;
- int top = first->bounds.y;
- int right = first->bounds.x + first->bounds.width;
- int bottom = first->bounds.y + first->bounds.height;
- for (std::size_t index = 1U; index < control_ids.size(); ++index)
- {
- const HmiControl *control = findControl(page_id, control_ids[index]);
- left = std::min(left, control->bounds.x);
- top = std::min(top, control->bounds.y);
- right = std::max(
- right, control->bounds.x + control->bounds.width);
- bottom = std::max(
- bottom, control->bounds.y + control->bounds.height);
- }
-
- bool supported = true;
- switch (alignment)
- {
- case HmiAlignment::Left:
- case HmiAlignment::HorizontalCenter:
- case HmiAlignment::Right:
- case HmiAlignment::Top:
- case HmiAlignment::VerticalCenter:
- case HmiAlignment::Bottom:
- break;
- default:
- supported = false;
- break;
- }
- if (!supported)
- {
- return failure(HmiEditorError::InvalidOperation, "不支持的 HMI 对齐方式");
- }
-
- HmiPage candidate_page = *page;
- const int horizontal_center = (left + right) / 2;
- const int vertical_center = (top + bottom) / 2;
- bool changed = false;
- for (HmiControl &control : candidate_page.controls)
- {
- if (selected_ids.find(control.id) == selected_ids.end())
- {
- continue;
- }
-
- const HmiRect original = control.bounds;
- switch (alignment)
- {
- case HmiAlignment::Left:
- control.bounds.x = left;
- break;
- case HmiAlignment::HorizontalCenter:
- control.bounds.x = horizontal_center - control.bounds.width / 2;
- break;
- case HmiAlignment::Right:
- control.bounds.x = right - control.bounds.width;
- break;
- case HmiAlignment::Top:
- control.bounds.y = top;
- break;
- case HmiAlignment::VerticalCenter:
- control.bounds.y = vertical_center - control.bounds.height / 2;
- break;
- case HmiAlignment::Bottom:
- control.bounds.y = bottom - control.bounds.height;
- break;
- default:
- break;
- }
- changed = changed || original.x != control.bounds.x
- || original.y != control.bounds.y;
- }
-
- if (!changed)
- {
- return {
- true,
- HmiEditorError::None,
- {},
- control_ids.front()};
- }
-
- std::string error;
- if (!candidate_page.validate(project_service_.projectLimits(), &error))
- {
- return failure(HmiEditorError::InvalidControl, error);
- }
-
- const HistoryState before = captureState();
- Project &project = project_service_.editProject();
- auto target_page = std::find_if(
- project.hmiPages.begin(), project.hmiPages.end(),
- [&page_id](const HmiPage &item) { return item.id == page_id; });
- for (const HmiControl &candidate : candidate_page.controls)
- {
- if (selected_ids.find(candidate.id) == selected_ids.end())
- {
- continue;
- }
- auto editable = std::find_if(
- target_page->controls.begin(), target_page->controls.end(),
- [&candidate](const HmiControl &item)
- {
- return item.id == candidate.id;
- });
- editable->bounds = candidate.bounds;
- }
- recordHistory(before);
- return {true, HmiEditorError::None, {}, control_ids.front()};
- }
-
- // 校验并整体更新控件的全部可编辑属性
- 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 必须唯一");
- }
-
- if (controlsEqual(*existing, control))
- {
- return {true, HmiEditorError::None, {}, control.id};
- }
- HistoryState before = captureState();
- 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;
- recordHistory(std::move(before));
- return {true, HmiEditorError::None, {}, control.id};
- }
-
- // 保留相对布局批量粘贴控件,任一控件失败时整体回滚
- HmiEditorResult HmiEditorService::pasteControls(
- const std::string &page_id,
- const std::vector<HmiControl> &controls,
- int offset_x,
- int offset_y)
- {
- const HmiPage *existing_page = findPage(page_id);
- if (existing_page == nullptr)
- {
- return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面");
- }
- if (controls.empty())
- {
- return failure(HmiEditorError::InvalidOperation, "请先复制 HMI 控件");
- }
- const ProjectLimitSettings &limits = project_service_.projectLimits();
- if (controls.size() > limits.maximumHmiControlsPerPage
- || existing_page->controls.size() + controls.size()
- > limits.maximumHmiControlsPerPage)
- {
- return failure(
- HmiEditorError::InvalidControl,
- "当前配置下单个 HMI 页面最多允许 "
- + std::to_string(limits.maximumHmiControlsPerPage) + " 个控件");
- }
- if (totalControlCount(project_service_.project()) + controls.size()
- > ProjectLimits::kMaximumHmiControlsPerProject)
- {
- return failure(HmiEditorError::InvalidControl, "单个工程最多包含 2048 个 HMI 控件");
- }
-
- // 让整组控件尽量保留相对位置,靠近页面边界时自动向页面内收拢
- int minimum_x = controls.front().bounds.x;
- int minimum_y = controls.front().bounds.y;
- int maximum_x = controls.front().bounds.x + controls.front().bounds.width;
- int maximum_y = controls.front().bounds.y + controls.front().bounds.height;
- for (const HmiControl &control : controls)
- {
- minimum_x = std::min(minimum_x, control.bounds.x);
- minimum_y = std::min(minimum_y, control.bounds.y);
- maximum_x = std::max(
- maximum_x, control.bounds.x + control.bounds.width);
- maximum_y = std::max(
- maximum_y, control.bounds.y + control.bounds.height);
- }
- if (maximum_x - minimum_x > existing_page->width
- || maximum_y - minimum_y > existing_page->height)
- {
- return failure(HmiEditorError::InvalidControl, "复制的控件尺寸超出目标 HMI 页面");
- }
- int final_offset_x = offset_x;
- int final_offset_y = offset_y;
- if (minimum_x + final_offset_x < 0)
- {
- final_offset_x = -minimum_x;
- }
- if (minimum_y + final_offset_y < 0)
- {
- final_offset_y = -minimum_y;
- }
- if (maximum_x + final_offset_x > existing_page->width)
- {
- final_offset_x = existing_page->width - maximum_x;
- }
- if (maximum_y + final_offset_y > existing_page->height)
- {
- final_offset_y = existing_page->height - maximum_y;
- }
-
- const HistoryState before = captureState();
- const bool modified_before = project_service_.isModified();
- 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; });
- std::vector<std::string> new_ids;
- new_ids.reserve(controls.size());
- for (const HmiControl &source : controls)
- {
- const HmiControlDescriptor *descriptor = findHmiControlDescriptor(source.type);
- if (descriptor == nullptr)
- {
- project.hmiPages = before.pages;
- project.initialHmiPageId = before.initial_page_id;
- project_service_.restoreModifiedState(modified_before);
- return failure(HmiEditorError::InvalidControl, "不支持的 HMI 控件类型");
- }
- HmiControl candidate = source;
- candidate.id = makeUniqueId(*target_page, descriptor->idPrefix);
- candidate.bounds.x += final_offset_x;
- candidate.bounds.y += final_offset_y;
- std::string error;
- if (!validateEditableControl(*target_page, candidate, &error))
- {
- project.hmiPages = before.pages;
- project.initialHmiPageId = before.initial_page_id;
- project_service_.restoreModifiedState(modified_before);
- return failure(HmiEditorError::InvalidControl, error);
- }
- new_ids.push_back(candidate.id);
- target_page->controls.push_back(std::move(candidate));
- }
- recordHistory(before);
- return {true, HmiEditorError::None, {}, new_ids.front()};
- }
-
- // 判断当前 HMI 编辑会话是否有可撤销操作
- bool HmiEditorService::canUndo() const
- {
- return history_.canUndo();
- }
-
- // 判断当前 HMI 编辑会话是否有可重做操作
- bool HmiEditorService::canRedo() const
- {
- return history_.canRedo();
- }
-
- // 恢复最近一份编辑前快照
- HmiEditorResult HmiEditorService::undo()
- {
- const std::optional<HistoryState> target = history_.undo(captureState());
- if (!target.has_value())
- {
- return historyFailure("没有可撤销的 HMI 编辑操作");
- }
- Project &project = project_service_.editProject();
- project.hmiPages = target->pages;
- project.initialHmiPageId = target->initial_page_id;
- return {true, HmiEditorError::None, {}, {}};
- }
-
- // 重新应用最近一份被撤销的快照
- HmiEditorResult HmiEditorService::redo()
- {
- const std::optional<HistoryState> target = history_.redo(captureState());
- if (!target.has_value())
- {
- return historyFailure("没有可重做的 HMI 编辑操作");
- }
- Project &project = project_service_.editProject();
- project.hmiPages = target->pages;
- project.initialHmiPageId = target->initial_page_id;
- return {true, HmiEditorError::None, {}, {}};
- }
-
- // 清空当前 HMI 编辑会话的撤销和重做历史
- void HmiEditorService::clearHistory()
- {
- history_.clear();
- }
-
- // 校验控件自身配置、页面边界和页面跳转目标
- bool HmiEditorService::validateEditableControl(
- const HmiPage &page, const HmiControl &control, std::string *error) const
- {
- if (!control.validate(error))
- {
- return false;
- }
- if (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;
- }
- if (control.type == HmiControlType::PageJump
- && !control.pageJump->targetPageId.empty()
- && findPage(control.pageJump->targetPageId) == nullptr)
- {
- setError(error, "页面跳转控件的目标页面不存在");
- return false;
- }
- return true;
- }
-
- // 检查候选控件 ID 是否与页面内其他控件重复
- 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;
- });
- }
-
- // 根据控件类型生成默认样式、文字、大小、初始坐标
- HmiControl HmiEditorService::makeControl(
- const HmiPage &page, const HmiControlDescriptor &descriptor)
- {
- HmiControl control;
- control.id = makeUniqueId(page, descriptor.idPrefix);
- control.type = descriptor.type;
- control.text = descriptor.defaultText;
- control.bounds.width = std::min(descriptor.defaultBounds.width, page.width);
- control.bounds.height = std::min(descriptor.defaultBounds.height, page.height);
- if (descriptor.type == HmiControlType::PageJump)
- {
- control.pageJump = HmiPageJumpConfig{};
- }
- if (descriptor.type == HmiControlType::StatusText)
- {
- control.statusText = HmiStatusBitTextConfig{
- "设备停止", "设备运行"};
- }
- 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;
- }
- }
|