Просмотр исходного кода

feat: 支持 HMI 字体外观属性

main
suyu 1 месяц назад
Родитель
Сommit
9f4bf2b9b8
11 измененных файлов: 430 добавлений и 13 удалений
  1. +75
    -0
      app/src/domain/hmi_model.cpp
  2. +10
    -0
      app/src/domain/hmi_model.h
  3. +2
    -0
      app/src/domain/project_limits.h
  4. +63
    -9
      app/src/ui/hmi_editor_widget.cpp
  5. +69
    -1
      app/src/ui/main_window.ui
  6. +98
    -0
      app/src/ui/property_panel_controller.cpp
  7. +1
    -0
      app/src/ui/property_panel_controller.h
  8. +36
    -0
      app/tests/domain_tests.cpp
  9. +42
    -0
      app/tests/hmi_editor_service_tests.cpp
  10. +20
    -3
      app/tests/main_window_tests.cpp
  11. +14
    -0
      app/tests/project_management_tests.cpp

+ 75
- 0
app/src/domain/hmi_model.cpp Просмотреть файл

@@ -4,6 +4,8 @@
#include "project_limits.h"

#include <algorithm>
#include <cctype>
#include <charconv>

namespace {

@@ -16,6 +18,75 @@ void setError(std::string *error, const std::string &message)
}
}

bool isHexDigit(char value)
{
return std::isxdigit(static_cast<unsigned char>(value)) != 0;
}

bool isValidTextColor(const std::string &value)
{
return value.size() == 7U
&& value.front() == '#'
&& std::all_of(value.cbegin() + 1, value.cend(), isHexDigit);
}

bool parseInteger(const std::string &value, int *result)
{
if (value.empty() || result == nullptr)
{
return false;
}
const char *begin = value.data();
const char *end = begin + value.size();
const auto parsed = std::from_chars(begin, end, *result);
return parsed.ec == std::errc{} && parsed.ptr == end;
}

bool isBooleanValue(const std::string &value)
{
return value == "true" || value == "false";
}

bool validateAppearanceProperty(
const std::string &key, const std::string &value, std::string *error)
{
if (key == HmiAppearanceProperty::kTextColor)
{
if (!isValidTextColor(value))
{
setError(error, "HMI 字体颜色必须是 #RRGGBB 格式");
return false;
}
}
else if (key == HmiAppearanceProperty::kFontSize)
{
int point_size = 0;
if (!parseInteger(value, &point_size)
|| point_size < ProjectLimits::kMinimumHmiFontPointSize
|| point_size > ProjectLimits::kMaximumHmiFontPointSize)
{
setError(
error,
"HMI 字号必须在 "
+ std::to_string(ProjectLimits::kMinimumHmiFontPointSize)
+ "~"
+ std::to_string(ProjectLimits::kMaximumHmiFontPointSize)
+ " 范围内");
return false;
}
}
else if (key == HmiAppearanceProperty::kFontBold
|| key == HmiAppearanceProperty::kFontItalic)
{
if (!isBooleanValue(value))
{
setError(error, "HMI 粗体和斜体属性必须是 true 或 false");
return false;
}
}
return true;
}

} // namespace

bool HmiProgressBarConfig::isValid() const
@@ -87,6 +158,10 @@ bool HmiControl::validate(std::string *error) const
setError(error, "HMI 控件属性值不能超过 4096 个 UTF-8 字节");
return false;
}
if (!validateAppearanceProperty(property.first, property.second, error))
{
return false;
}
}
const std::optional<RegisterArea> binding_area =
hmiBindingArea(descriptor->bindingKind);


+ 10
- 0
app/src/domain/hmi_model.h Просмотреть файл

@@ -15,6 +15,16 @@
#include <string>
#include <vector>

namespace HmiAppearanceProperty
{

inline constexpr const char kTextColor[] = "textColor";
inline constexpr const char kFontSize[] = "fontSize";
inline constexpr const char kFontBold[] = "fontBold";
inline constexpr const char kFontItalic[] = "fontItalic";

} // namespace HmiAppearanceProperty

/**
* @brief 描述 HMI 控件在页面坐标系中的位置和尺寸
*


+ 2
- 0
app/src/domain/project_limits.h Просмотреть файл

@@ -32,6 +32,8 @@ constexpr int kMaximumHmiPageWidth = 8192;
constexpr int kMaximumHmiPageHeight = 8192;
constexpr int kMaximumHmiControlWidth = 8192;
constexpr int kMaximumHmiControlHeight = 8192;
constexpr int kMinimumHmiFontPointSize = 6;
constexpr int kMaximumHmiFontPointSize = 72;

constexpr std::size_t kMaximumPollAddresses = 1024U;
constexpr std::size_t kMaximumPollBlocks = 64U;


+ 63
- 9
app/src/ui/hmi_editor_widget.cpp Просмотреть файл

@@ -3,8 +3,10 @@
#include "services/hmi_editor_service.h"
#include "services/hmi_runtime_service.h"
#include "services/alarm_service.h"
#include "domain/project_limits.h"

#include <QDateTime>
#include <QFont>
#include <QGraphicsItem>
#include <QGraphicsRectItem>
#include <QGraphicsScene>
@@ -98,6 +100,8 @@ public:
painter->setPen(QPen(QColor(QStringLiteral("#47545f")), 1));
}

applyConfiguredFont(painter);

switch (control_.type)
{
case HmiControlType::Button:
@@ -115,7 +119,7 @@ public:
: QColor(QStringLiteral("#5e816b"));
const QColor text = disabled
? QColor(QStringLiteral("#7a837e"))
: QColor(QStringLiteral("#205c3b"));
: configuredTextColor(QColor(QStringLiteral("#205c3b")));

// 未按下时保留下沿阴影,按下后将按钮面下移形成明确的回弹感
QRectF face = rect.adjusted(0, 0, 0, -2);
@@ -150,7 +154,7 @@ public:
painter->setBrush(bit_value_ ? QColor(QStringLiteral("#24a148"))
: QColor(QStringLiteral("#b8c1c8")));
painter->drawEllipse(lamp);
painter->setPen(QColor(QStringLiteral("#24313b")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#24313b"))));
painter->drawText(
QRectF(rect.left(), lamp.bottom() + 1, rect.width(), 16),
Qt::AlignCenter,
@@ -163,7 +167,7 @@ public:
// 数值显示为只读样式,文本由运行值刷新
painter->setBrush(QColor(QStringLiteral("#edf2f6")));
painter->drawRect(rect);
painter->setPen(QColor(QStringLiteral("#24313b")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#24313b"))));
painter->drawText(rect.adjusted(7, 0, -7, 0),
Qt::AlignVCenter | Qt::AlignLeft,
textWithValue());
@@ -174,7 +178,7 @@ public:
// 数值输入以白色编辑框样式呈现,双击后才请求写入
painter->setBrush(QColor(QStringLiteral("#ffffff")));
painter->drawRoundedRect(rect, 3, 3);
painter->setPen(QColor(QStringLiteral("#24313b")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#24313b"))));
painter->drawText(rect.adjusted(7, 0, -7, 0),
Qt::AlignVCenter | Qt::AlignLeft,
textWithValue());
@@ -216,7 +220,7 @@ public:
? percentage_text
: display_text + QStringLiteral(": ") + percentage_text;
}
painter->setPen(QColor(QStringLiteral("#20342a")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#20342a"))));
painter->drawText(rect.adjusted(6, 0, -6, 0),
Qt::AlignCenter,
display_text);
@@ -230,7 +234,7 @@ public:
painter->setBrush(fill);
painter->setPen(QPen(QColor(QStringLiteral("#4e789f")), 1));
painter->drawRoundedRect(rect, 4, 4);
painter->setPen(QColor(QStringLiteral("#244b6b")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#244b6b"))));
painter->drawText(rect, Qt::AlignCenter, textWithValue());
break;
}
@@ -257,7 +261,7 @@ public:
maximum_rows, static_cast<int>(alarm_records_.size()));
if (visible_rows == 0)
{
painter->setPen(QColor(QStringLiteral("#6f7a82")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#6f7a82"))));
painter->drawText(
QRectF(
rect.left(),
@@ -282,7 +286,7 @@ public:
QColor(QStringLiteral("#fde8e8")));
painter->setPen(QColor(QStringLiteral("#d3d8dc")));
painter->drawLine(row_rect.bottomLeft(), row_rect.bottomRight());
painter->setPen(QColor(QStringLiteral("#8d1f1f")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#8d1f1f"))));
const QString state = record.acknowledged
? QObject::tr("已确认") : QObject::tr("未确认");
painter->drawText(
@@ -309,7 +313,7 @@ public:
default:
{
// 标签只显示固定文本,不绑定寄存器运行值
painter->setPen(QColor(QStringLiteral("#24313b")));
painter->setPen(configuredTextColor(QColor(QStringLiteral("#24313b"))));
painter->drawText(rect, Qt::AlignCenter,
QString::fromUtf8(control_.text.data(),
static_cast<int>(control_.text.size())));
@@ -577,6 +581,56 @@ private:
: QString{};
}

QColor configuredTextColor(const QColor &fallback) const
{
const auto property = control_.properties.find(
HmiAppearanceProperty::kTextColor);
if (property == control_.properties.cend())
{
return fallback;
}
const QColor color = QColor(QString::fromUtf8(
property->second.data(), static_cast<int>(property->second.size())));
return color.isValid() ? color : fallback;
}

void applyConfiguredFont(QPainter *painter) const
{
if (painter == nullptr)
{
return;
}
QFont font = painter->font();
const auto font_size = control_.properties.find(
HmiAppearanceProperty::kFontSize);
if (font_size != control_.properties.cend())
{
bool ok = false;
const int point_size = QString::fromUtf8(
font_size->second.data(),
static_cast<int>(font_size->second.size())).toInt(&ok);
if (ok
&& point_size >= ProjectLimits::kMinimumHmiFontPointSize
&& point_size <= ProjectLimits::kMaximumHmiFontPointSize)
{
font.setPointSize(point_size);
}
}
const auto font_bold = control_.properties.find(
HmiAppearanceProperty::kFontBold);
if (font_bold != control_.properties.cend())
{
font.setBold(font_bold->second == "true");
}
const auto font_italic = control_.properties.find(
HmiAppearanceProperty::kFontItalic);
if (font_italic != control_.properties.cend())
{
font.setItalic(font_italic->second == "true");
}
painter->setFont(font);
}

// 根据控件类型和运行数据组合当前应绘制的文字
QString textWithValue() const
{


+ 69
- 1
app/src/ui/main_window.ui Просмотреть файл

@@ -679,7 +679,75 @@
</property>
</widget>
</item>
<item row="13" column="0" colspan="2">
<item row="13" column="0">
<widget class="QLabel" name="textColorLabel">
<property name="text">
<string>字体颜色</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QWidget" name="textColorContainer">
<layout class="QHBoxLayout" name="textColorLayout">
<property name="leftMargin"><number>0</number></property>
<property name="topMargin"><number>0</number></property>
<property name="rightMargin"><number>0</number></property>
<property name="bottomMargin"><number>0</number></property>
<item>
<widget class="QLineEdit" name="textColorEdit">
<property name="placeholderText">
<string>默认,例如 #E53935</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="textColorButton">
<property name="text">
<string>选择</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="fontSizeLabel">
<property name="text">
<string>字号</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QSpinBox" name="fontSizeSpinBox"/>
</item>
<item row="15" column="0">
<widget class="QLabel" name="fontBoldLabel">
<property name="text">
<string>字体样式</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QWidget" name="fontStyleContainer">
<layout class="QHBoxLayout" name="fontStyleLayout">
<property name="leftMargin"><number>0</number></property>
<property name="topMargin"><number>0</number></property>
<property name="rightMargin"><number>0</number></property>
<property name="bottomMargin"><number>0</number></property>
<item>
<widget class="QCheckBox" name="fontBoldCheckBox">
<property name="text"><string>粗体</string></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fontItalicCheckBox">
<property name="text"><string>斜体</string></property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="16" column="0" colspan="2">
<widget class="QPushButton" name="applyPropertiesButton">
<property name="text">
<string>应用属性</string>


+ 98
- 0
app/src/ui/property_panel_controller.cpp Просмотреть файл

@@ -1,6 +1,7 @@
#include "property_panel_controller.h"

#include "domain/hmi_control_registry.h"
#include "domain/project_limits.h"
#include "hmi_editor_widget.h"
#include "logic_editor_widget.h"
#include "logic_instruction_dialog.h"
@@ -9,7 +10,9 @@
#include "services/project_service.h"
#include "ui_main_window.h"

#include <QApplication>
#include <QCheckBox>
#include <QColorDialog>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>
@@ -19,6 +22,7 @@
#include <QStackedWidget>
#include <QWidget>

#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
@@ -68,6 +72,9 @@ PropertyPanelController::PropertyPanelController(

void PropertyPanelController::configure()
{
ui_.fontSizeSpinBox->setRange(
ProjectLimits::kMinimumHmiFontPointSize,
ProjectLimits::kMaximumHmiFontPointSize);
ui_.bindingAreaComboBox->setItemData(0, -1);
ui_.bindingAreaComboBox->setItemData(1, 0);
ui_.bindingAreaComboBox->setItemData(2, 1);
@@ -87,6 +94,8 @@ void PropertyPanelController::configure()
&parent_, [this] { applySelectedControlProperties(); });
QObject::connect(ui_.applyLogicPropertiesButton, &QPushButton::clicked,
&parent_, [this] { applySelectedLogicNodeProperties(); });
QObject::connect(ui_.textColorButton, &QPushButton::clicked,
&parent_, [this] { chooseTextColor(); });
ui_.targetPageLabel->setVisible(false);
ui_.targetPageComboBox->setVisible(false);
showControlProperties({});
@@ -160,6 +169,11 @@ void PropertyPanelController::showControlProperties(const std::string &control_i
static_cast<QWidget *>(ui_.progressMinimumSpinBox),
static_cast<QWidget *>(ui_.progressMaximumSpinBox),
static_cast<QWidget *>(ui_.progressShowValueCheckBox),
static_cast<QWidget *>(ui_.textColorEdit),
static_cast<QWidget *>(ui_.textColorButton),
static_cast<QWidget *>(ui_.fontSizeSpinBox),
static_cast<QWidget *>(ui_.fontBoldCheckBox),
static_cast<QWidget *>(ui_.fontItalicCheckBox),
static_cast<QWidget *>(ui_.applyPropertiesButton)})
{
widget->setEnabled(has_control);
@@ -181,6 +195,15 @@ void PropertyPanelController::showControlProperties(const std::string &control_i
ui_.progressMaximumSpinBox->setVisible(false);
ui_.progressShowValueLabel->setVisible(false);
ui_.progressShowValueCheckBox->setVisible(false);
ui_.textColorEdit->clear();
ui_.textColorButton->setStyleSheet(QString{});
ui_.fontSizeSpinBox->setValue(
std::clamp(
QApplication::font().pointSize(),
ProjectLimits::kMinimumHmiFontPointSize,
ProjectLimits::kMaximumHmiFontPointSize));
ui_.fontBoldCheckBox->setChecked(false);
ui_.fontItalicCheckBox->setChecked(false);
return;
}

@@ -242,6 +265,48 @@ void PropertyPanelController::showControlProperties(const std::string &control_i
ui_.progressMinimumSpinBox->setValue(progress_config.minimumValue);
ui_.progressMaximumSpinBox->setValue(progress_config.maximumValue);
ui_.progressShowValueCheckBox->setChecked(progress_config.showValue);

const auto text_color = control->properties.find(
HmiAppearanceProperty::kTextColor);
ui_.textColorEdit->setText(text_color == control->properties.cend()
? QString{}
: fromUtf8(text_color->second));
const QColor swatch_color(ui_.textColorEdit->text());
ui_.textColorButton->setStyleSheet(
swatch_color.isValid()
? QStringLiteral("background-color: %1;").arg(
swatch_color.name(QColor::HexRgb))
: QString{});

const int default_font_size = std::clamp(
QApplication::font().pointSize(),
ProjectLimits::kMinimumHmiFontPointSize,
ProjectLimits::kMaximumHmiFontPointSize);
int font_size = default_font_size;
const auto font_size_property = control->properties.find(
HmiAppearanceProperty::kFontSize);
if (font_size_property != control->properties.cend())
{
bool ok = false;
const int parsed = fromUtf8(font_size_property->second).toInt(&ok);
if (ok)
{
font_size = parsed;
}
}
ui_.fontSizeSpinBox->setValue(std::clamp(
font_size,
ProjectLimits::kMinimumHmiFontPointSize,
ProjectLimits::kMaximumHmiFontPointSize));
const auto font_bold = control->properties.find(
HmiAppearanceProperty::kFontBold);
ui_.fontBoldCheckBox->setChecked(
font_bold != control->properties.cend() && font_bold->second == "true");
const auto font_italic = control->properties.find(
HmiAppearanceProperty::kFontItalic);
ui_.fontItalicCheckBox->setChecked(
font_italic != control->properties.cend()
&& font_italic->second == "true");
}

void PropertyPanelController::showLogicNodeProperties(const std::string &node_id)
@@ -510,6 +575,21 @@ void PropertyPanelController::applySelectedControlProperties()
static_cast<std::int16_t>(ui_.progressMaximumSpinBox->value()),
ui_.progressShowValueCheckBox->isChecked()};
}
const std::string text_color = toUtf8(ui_.textColorEdit->text());
if (text_color.empty())
{
control.properties.erase(HmiAppearanceProperty::kTextColor);
}
else
{
control.properties[HmiAppearanceProperty::kTextColor] = text_color;
}
control.properties[HmiAppearanceProperty::kFontSize] = std::to_string(
ui_.fontSizeSpinBox->value());
control.properties[HmiAppearanceProperty::kFontBold] =
ui_.fontBoldCheckBox->isChecked() ? "true" : "false";
control.properties[HmiAppearanceProperty::kFontItalic] =
ui_.fontItalicCheckBox->isChecked() ? "true" : "false";
const HmiEditorResult result = hmi_editor_service_.updateControl(
current_page_id_(), selected_control_id_, control);
if (!result.succeeded)
@@ -526,6 +606,24 @@ void PropertyPanelController::applySelectedControlProperties()
status_reporter_(QObject::tr("控件属性已更新"), 3000);
}

void PropertyPanelController::chooseTextColor()
{
QColor initial_color(ui_.textColorEdit->text());
if (!initial_color.isValid())
{
initial_color = QColor(QStringLiteral("#24313b"));
}
const QColor color = QColorDialog::getColor(
initial_color, &parent_, QObject::tr("选择字体颜色"));
if (color.isValid())
{
ui_.textColorEdit->setText(color.name(QColor::HexRgb));
ui_.textColorButton->setStyleSheet(
QStringLiteral("background-color: %1;").arg(
color.name(QColor::HexRgb)));
}
}

void PropertyPanelController::applySelectedLogicNodeProperties()
{
const LogicNode *node = logic_editor_service_.findNode(


+ 1
- 0
app/src/ui/property_panel_controller.h Просмотреть файл

@@ -57,6 +57,7 @@ public:
private:
void handleHmiEditorError(const QString &message) const;
void handleLogicEditorError(const QString &message) const;
void chooseTextColor();
void reportFailure(
const QString &action, const std::string &message) const;



+ 36
- 0
app/tests/domain_tests.cpp Просмотреть файл

@@ -205,6 +205,41 @@ void testProgressBarConfigurationBoundaries()
"a progress bar must reject an M binding");
}

Project makeValidProject();

void testHmiAppearancePropertyBoundaries()
{
// 外观属性必须在领域层拒绝格式错误,但不能影响未知扩展属性
Project project = makeValidProject();
HmiControl &button = project.hmiPages.front().controls.front();
button.properties[HmiAppearanceProperty::kTextColor] = "#E53935";
button.properties[HmiAppearanceProperty::kFontSize] = "18";
button.properties[HmiAppearanceProperty::kFontBold] = "true";
button.properties[HmiAppearanceProperty::kFontItalic] = "false";
require(project.validate(), "valid HMI appearance properties must pass validation");

button.properties[HmiAppearanceProperty::kTextColor] = "red";
require(!project.validate(), "text colors must use the #RRGGBB format");

project = makeValidProject();
HmiControl &font_control = project.hmiPages.front().controls.front();
font_control.properties[HmiAppearanceProperty::kFontSize] = "5";
require(!project.validate(), "font sizes below the minimum must be rejected");
font_control.properties[HmiAppearanceProperty::kFontSize] = "73";
require(!project.validate(), "font sizes above the maximum must be rejected");
font_control.properties[HmiAppearanceProperty::kFontSize] = "large";
require(!project.validate(), "non-numeric font sizes must be rejected");

project = makeValidProject();
HmiControl &style_control = project.hmiPages.front().controls.front();
style_control.properties[HmiAppearanceProperty::kFontBold] = "yes";
require(!project.validate(), "font style flags must be true or false");

project = makeValidProject();
project.hmiPages.front().controls.front().properties["legacyColor"] = "green";
require(project.validate(), "unknown HMI extension properties must remain supported");
}

Project makeValidProject()
{
// 构造包含 HMI 绑定和完整梯形图网络的最小合法工程作为测试基线
@@ -856,6 +891,7 @@ int main()
testRegisterRepositorySeparatesAreas();
testHmiControlRegistryCompleteness();
testProgressBarConfigurationBoundaries();
testHmiAppearancePropertyBoundaries();
testLogicNodeConfigurationBoundaries();
testTimerAndCommentBoundaries();
testTimerReferencesForRunning();


+ 42
- 0
app/tests/hmi_editor_service_tests.cpp Просмотреть файл

@@ -240,6 +240,47 @@ void testHistoryAndAtomicBatchDelete()
"HMI history must retain exactly the configured 100 most recent steps");
}

void testAppearanceEditing()
{
TestProjectStorage storage;
ProjectService project_service(storage);
HmiEditorService service(project_service);
const std::string page_id = service.ensureDefaultPage().id;
const HmiEditorResult label = service.addControl(page_id, HmiControlType::Label);
require(label.succeeded, "a label must be available for appearance editing");

HmiControl appearance = *service.findControl(page_id, label.id);
appearance.properties[HmiAppearanceProperty::kTextColor] = "#E53935";
appearance.properties[HmiAppearanceProperty::kFontSize] = "18";
appearance.properties[HmiAppearanceProperty::kFontBold] = "true";
appearance.properties[HmiAppearanceProperty::kFontItalic] = "false";
require(service.updateControl(page_id, label.id, appearance).succeeded,
"valid appearance properties must be applied atomically");
const HmiControl *updated = service.findControl(page_id, label.id);
require(updated != nullptr
&& updated->properties.at(HmiAppearanceProperty::kTextColor) == "#E53935"
&& updated->properties.at(HmiAppearanceProperty::kFontSize) == "18"
&& updated->properties.at(HmiAppearanceProperty::kFontBold) == "true",
"appearance properties must be stored on the HMI control");

HmiControl invalid = *updated;
invalid.properties[HmiAppearanceProperty::kTextColor] = "invalid";
require(!service.updateControl(page_id, label.id, invalid).succeeded,
"invalid appearance properties must be rejected without a partial update");
require(service.findControl(page_id, label.id)->properties.at(
HmiAppearanceProperty::kTextColor) == "#E53935",
"failed appearance updates must leave the old color intact");

require(service.undo().succeeded,
"appearance updates must participate in HMI undo history");
require(service.findControl(page_id, label.id)->properties.empty(),
"undo must remove the applied appearance properties");
require(service.redo().succeeded
&& service.findControl(page_id, label.id)->properties.at(
HmiAppearanceProperty::kFontSize) == "18",
"redo must restore the applied appearance properties");
}

void testPageLifecycleAndNavigation()
{
TestProjectStorage storage;
@@ -316,6 +357,7 @@ int main()
// 编辑和运行场景分别验证服务层两条独立职责
testControlEditing();
testHistoryAndAtomicBatchDelete();
testAppearanceEditing();
testRuntimeUsesRegisterRepository();
testPageLifecycleAndNavigation();
}


+ 20
- 3
app/tests/main_window_tests.cpp Просмотреть файл

@@ -783,6 +783,7 @@ void testModeActionsControlEditingAvailability()
QDockWidget *properties_dock = requiredChild<QDockWidget>(window, "propertiesDock");
QLabel *selection = requiredChild<QLabel>(window, "selectionValueLabel");
QLineEdit *text_edit = requiredChild<QLineEdit>(window, "controlTextEdit");
QLineEdit *text_color_edit = requiredChild<QLineEdit>(window, "textColorEdit");
QComboBox *button_operation = requiredChild<QComboBox>(
window, "buttonOperationComboBox");
QComboBox *binding_area = requiredChild<QComboBox>(
@@ -793,6 +794,9 @@ void testModeActionsControlEditingAvailability()
window, "progressMinimumSpinBox");
QSpinBox *progress_maximum = requiredChild<QSpinBox>(
window, "progressMaximumSpinBox");
QSpinBox *font_size = requiredChild<QSpinBox>(window, "fontSizeSpinBox");
QCheckBox *font_bold = requiredChild<QCheckBox>(window, "fontBoldCheckBox");
QCheckBox *font_italic = requiredChild<QCheckBox>(window, "fontItalicCheckBox");
QCheckBox *progress_show_value = requiredChild<QCheckBox>(
window, "progressShowValueCheckBox");
QPushButton *apply_properties = requiredChild<QPushButton>(
@@ -966,10 +970,23 @@ void testModeActionsControlEditingAvailability()
"an unbound HMI control must not reserve an address label area");
button_operation->setCurrentIndex(
button_operation->findData(static_cast<int>(HmiButtonOperation::Toggle)));
text_color_edit->setText(QStringLiteral("#E53935"));
font_size->setValue(18);
font_bold->setChecked(true);
font_italic->setChecked(true);
apply_properties->click();
require(editor_service.findControl(page_id, "button-1")->buttonOperation
== HmiButtonOperation::Toggle,
"the property panel must update the HMI button operation");
const HmiControl *styled_button = editor_service.findControl(page_id, "button-1");
require(styled_button != nullptr
&& styled_button->buttonOperation == HmiButtonOperation::Toggle
&& styled_button->properties.at(HmiAppearanceProperty::kTextColor)
== "#E53935"
&& styled_button->properties.at(HmiAppearanceProperty::kFontSize)
== "18"
&& styled_button->properties.at(HmiAppearanceProperty::kFontBold)
== "true"
&& styled_button->properties.at(HmiAppearanceProperty::kFontItalic)
== "true",
"the property panel must update HMI appearance properties");

HmiControl bound_button = *editor_service.findControl(page_id, "button-1");
bound_button.binding = RegisterAddress{RegisterArea::M, 0};


+ 14
- 0
app/tests/project_management_tests.cpp Просмотреть файл

@@ -1,4 +1,5 @@
#include "domain/project_storage.h"
#include "domain/hmi_model.h"
#include "infrastructure/json_project_storage.h"
#include "services/register_comment_service.h"
#include "services/project_service.h"
@@ -37,6 +38,10 @@ Project makeExampleProject()
start_button.binding = RegisterAddress{RegisterArea::M, 0};
start_button.buttonOperation = HmiButtonOperation::SetOn;
start_button.properties.emplace("color", "green");
start_button.properties.emplace(HmiAppearanceProperty::kTextColor, "#E53935");
start_button.properties.emplace(HmiAppearanceProperty::kFontSize, "18");
start_button.properties.emplace(HmiAppearanceProperty::kFontBold, "true");
start_button.properties.emplace(HmiAppearanceProperty::kFontItalic, "false");

HmiControl running_indicator;
running_indicator.id = "running-indicator";
@@ -487,6 +492,15 @@ void testExampleProjectRoundTrip()
"HMI button operation must survive round trip");
require(project.hmiPages.front().controls.front().properties.at("color") == "green",
"HMI properties must survive round trip");
require(project.hmiPages.front().controls.front().properties.at(
HmiAppearanceProperty::kTextColor) == "#E53935"
&& project.hmiPages.front().controls.front().properties.at(
HmiAppearanceProperty::kFontSize) == "18"
&& project.hmiPages.front().controls.front().properties.at(
HmiAppearanceProperty::kFontBold) == "true"
&& project.hmiPages.front().controls.front().properties.at(
HmiAppearanceProperty::kFontItalic) == "false",
"HMI appearance properties must survive round trip");
require(project.hmiPages.front().controls.at(1).type == HmiControlType::Indicator,
"indicator control type must survive round trip");
require(project.hmiPages.front().controls.at(2).binding->area() == RegisterArea::D,


Загрузка…
Отмена
Сохранить