|
|
|
@@ -3,6 +3,8 @@ |
|
|
|
#include "project_service.h" |
|
|
|
|
|
|
|
#include <algorithm> |
|
|
|
#include <cctype> |
|
|
|
#include <cstddef> |
|
|
|
#include <utility> |
|
|
|
|
|
|
|
namespace { |
|
|
|
@@ -36,10 +38,17 @@ std::string controlPrefix(HmiControlType type) |
|
|
|
return "numeric-input"; |
|
|
|
} |
|
|
|
case HmiControlType::Label: |
|
|
|
default: |
|
|
|
{ |
|
|
|
return "label"; |
|
|
|
} |
|
|
|
case HmiControlType::PageJump: |
|
|
|
{ |
|
|
|
return "page-jump"; |
|
|
|
} |
|
|
|
default: |
|
|
|
{ |
|
|
|
return "control"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@@ -64,10 +73,17 @@ std::string defaultText(HmiControlType type) |
|
|
|
return "数值输入"; |
|
|
|
} |
|
|
|
case HmiControlType::Label: |
|
|
|
default: |
|
|
|
{ |
|
|
|
return "文本"; |
|
|
|
} |
|
|
|
case HmiControlType::PageJump: |
|
|
|
{ |
|
|
|
return "页面跳转"; |
|
|
|
} |
|
|
|
default: |
|
|
|
{ |
|
|
|
return "控件"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@@ -76,6 +92,31 @@ HmiEditorResult failure(HmiEditorError error, const std::string &message) |
|
|
|
return {false, error, message, {}}; |
|
|
|
} |
|
|
|
|
|
|
|
bool isBlank(const std::string &value) |
|
|
|
{ |
|
|
|
return value.empty() |
|
|
|
|| std::all_of( |
|
|
|
value.cbegin(), value.cend(), |
|
|
|
[](unsigned char character) { return std::isspace(character) != 0; }); |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace |
|
|
|
|
|
|
|
HmiEditorService::HmiEditorService(ProjectService &project_service) |
|
|
|
@@ -128,6 +169,11 @@ 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()}; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -138,9 +184,157 @@ HmiEditorResult HmiEditorService::ensureDefaultPage() |
|
|
|
|
|
|
|
Project &project = project_service_.editProject(); |
|
|
|
project.hmiPages.push_back(std::move(page)); |
|
|
|
project.initialHmiPageId = project.hmiPages.back().id; |
|
|
|
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 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; |
|
|
|
Project &project = project_service_.editProject(); |
|
|
|
project.hmiPages.push_back(std::move(page)); |
|
|
|
if (project.initialHmiPageId.empty()) |
|
|
|
{ |
|
|
|
project.initialHmiPageId = project.hmiPages.back().id; |
|
|
|
} |
|
|
|
return {true, HmiEditorError::None, {}, project.hmiPages.back().id}; |
|
|
|
} |
|
|
|
|
|
|
|
HmiEditorResult HmiEditorService::renamePage( |
|
|
|
const std::string &page_id, const std::string &name) |
|
|
|
{ |
|
|
|
if (isBlank(name)) |
|
|
|
{ |
|
|
|
return failure(HmiEditorError::InvalidPage, "HMI 页面名称不能为空"); |
|
|
|
} |
|
|
|
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 页面名称必须唯一"); |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
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 + " 引用"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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()); |
|
|
|
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 页面已经位于目标边界"); |
|
|
|
} |
|
|
|
|
|
|
|
Project &project = project_service_.editProject(); |
|
|
|
std::iter_swap( |
|
|
|
project.hmiPages.begin() + index, |
|
|
|
project.hmiPages.begin() + target_index); |
|
|
|
return {true, HmiEditorError::None, {}, page_id}; |
|
|
|
} |
|
|
|
|
|
|
|
HmiEditorResult HmiEditorService::setInitialPage(const std::string &page_id) |
|
|
|
{ |
|
|
|
if (findPage(page_id) == nullptr) |
|
|
|
{ |
|
|
|
return failure(HmiEditorError::PageNotFound, "未找到 HMI 页面"); |
|
|
|
} |
|
|
|
project_service_.editProject().initialHmiPageId = page_id; |
|
|
|
return {true, HmiEditorError::None, {}, page_id}; |
|
|
|
} |
|
|
|
|
|
|
|
HmiEditorResult HmiEditorService::addControl( |
|
|
|
const std::string &page_id, HmiControlType type) |
|
|
|
{ |
|
|
|
@@ -280,8 +474,21 @@ HmiEditorResult HmiEditorService::updateControl( |
|
|
|
} |
|
|
|
|
|
|
|
bool HmiEditorService::validateEditableControl( |
|
|
|
const HmiPage &page, const HmiControl &control, std::string *error) |
|
|
|
const HmiPage &page, const HmiControl &control, std::string *error) const |
|
|
|
{ |
|
|
|
switch (control.type) |
|
|
|
{ |
|
|
|
case HmiControlType::Button: |
|
|
|
case HmiControlType::Indicator: |
|
|
|
case HmiControlType::NumericDisplay: |
|
|
|
case HmiControlType::NumericInput: |
|
|
|
case HmiControlType::Label: |
|
|
|
case HmiControlType::PageJump: |
|
|
|
break; |
|
|
|
default: |
|
|
|
setError(error, "不支持的 HMI 控件类型"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (control.id.empty()) |
|
|
|
{ |
|
|
|
setError(error, "HMI 控件 ID 不能为空"); |
|
|
|
@@ -325,6 +532,32 @@ bool HmiEditorService::validateEditableControl( |
|
|
|
setError(error, "数值控件必须绑定 D 区地址"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if ((control.type == HmiControlType::Label |
|
|
|
|| control.type == HmiControlType::PageJump) |
|
|
|
&& control.binding.has_value()) |
|
|
|
{ |
|
|
|
setError(error, "文本和页面跳转控件不能绑定寄存器"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (control.type == HmiControlType::PageJump) |
|
|
|
{ |
|
|
|
if (!control.pageJump.has_value()) |
|
|
|
{ |
|
|
|
setError(error, "页面跳转控件缺少跳转配置"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (!control.pageJump->targetPageId.empty() |
|
|
|
&& findPage(control.pageJump->targetPageId) == nullptr) |
|
|
|
{ |
|
|
|
setError(error, "页面跳转控件的目标页面不存在"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (control.pageJump.has_value()) |
|
|
|
{ |
|
|
|
setError(error, "非页面跳转控件不能包含跳转配置"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -351,6 +584,10 @@ HmiControl HmiEditorService::makeControl( |
|
|
|
control.text = defaultText(type); |
|
|
|
control.bounds.width = type == HmiControlType::Indicator ? 64 : 120; |
|
|
|
control.bounds.height = type == HmiControlType::Indicator ? 64 : 40; |
|
|
|
if (type == HmiControlType::PageJump) |
|
|
|
{ |
|
|
|
control.pageJump = HmiPageJumpConfig{}; |
|
|
|
} |
|
|
|
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); |
|
|
|
|