From 9f4bf2b9b85b10ae66a78674aecf5ed70043acf4 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Thu, 20 Aug 2026 10:20:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20HMI=20=E5=AD=97?= =?UTF-8?q?=E4=BD=93=E5=A4=96=E8=A7=82=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/domain/hmi_model.cpp | 75 ++++++++++++++++++ app/src/domain/hmi_model.h | 10 +++ app/src/domain/project_limits.h | 2 + app/src/ui/hmi_editor_widget.cpp | 72 ++++++++++++++--- app/src/ui/main_window.ui | 70 ++++++++++++++++- app/src/ui/property_panel_controller.cpp | 98 ++++++++++++++++++++++++ app/src/ui/property_panel_controller.h | 1 + app/tests/domain_tests.cpp | 36 +++++++++ app/tests/hmi_editor_service_tests.cpp | 42 ++++++++++ app/tests/main_window_tests.cpp | 23 +++++- app/tests/project_management_tests.cpp | 14 ++++ 11 files changed, 430 insertions(+), 13 deletions(-) diff --git a/app/src/domain/hmi_model.cpp b/app/src/domain/hmi_model.cpp index 495d202..f20cea3 100644 --- a/app/src/domain/hmi_model.cpp +++ b/app/src/domain/hmi_model.cpp @@ -4,6 +4,8 @@ #include "project_limits.h" #include +#include +#include namespace { @@ -16,6 +18,75 @@ void setError(std::string *error, const std::string &message) } } +bool isHexDigit(char value) +{ + return std::isxdigit(static_cast(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 binding_area = hmiBindingArea(descriptor->bindingKind); diff --git a/app/src/domain/hmi_model.h b/app/src/domain/hmi_model.h index ad1c844..0469504 100644 --- a/app/src/domain/hmi_model.h +++ b/app/src/domain/hmi_model.h @@ -15,6 +15,16 @@ #include #include +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 控件在页面坐标系中的位置和尺寸 * diff --git a/app/src/domain/project_limits.h b/app/src/domain/project_limits.h index 4dc60cf..61b7c1c 100644 --- a/app/src/domain/project_limits.h +++ b/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; diff --git a/app/src/ui/hmi_editor_widget.cpp b/app/src/ui/hmi_editor_widget.cpp index 37013cf..4e7ac64 100644 --- a/app/src/ui/hmi_editor_widget.cpp +++ b/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 +#include #include #include #include @@ -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(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(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(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(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 { diff --git a/app/src/ui/main_window.ui b/app/src/ui/main_window.ui index 0eb60d0..daa00f0 100644 --- a/app/src/ui/main_window.ui +++ b/app/src/ui/main_window.ui @@ -679,7 +679,75 @@ - + + + + 字体颜色 + + + + + + + 0 + 0 + 0 + 0 + + + + 默认,例如 #E53935 + + + + + + + 选择 + + + + + + + + + + 字号 + + + + + + + + + + 字体样式 + + + + + + + 0 + 0 + 0 + 0 + + + 粗体 + + + + + 斜体 + + + + + + 应用属性 diff --git a/app/src/ui/property_panel_controller.cpp b/app/src/ui/property_panel_controller.cpp index d5b0848..5d7bf92 100644 --- a/app/src/ui/property_panel_controller.cpp +++ b/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 #include +#include #include #include #include @@ -19,6 +22,7 @@ #include #include +#include #include #include #include @@ -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(ui_.progressMinimumSpinBox), static_cast(ui_.progressMaximumSpinBox), static_cast(ui_.progressShowValueCheckBox), + static_cast(ui_.textColorEdit), + static_cast(ui_.textColorButton), + static_cast(ui_.fontSizeSpinBox), + static_cast(ui_.fontBoldCheckBox), + static_cast(ui_.fontItalicCheckBox), static_cast(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(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( diff --git a/app/src/ui/property_panel_controller.h b/app/src/ui/property_panel_controller.h index aec4f57..24abb2e 100644 --- a/app/src/ui/property_panel_controller.h +++ b/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; diff --git a/app/tests/domain_tests.cpp b/app/tests/domain_tests.cpp index 5916bdf..46c4177 100644 --- a/app/tests/domain_tests.cpp +++ b/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(); diff --git a/app/tests/hmi_editor_service_tests.cpp b/app/tests/hmi_editor_service_tests.cpp index cc09dc6..ec9e531 100644 --- a/app/tests/hmi_editor_service_tests.cpp +++ b/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(); } diff --git a/app/tests/main_window_tests.cpp b/app/tests/main_window_tests.cpp index 419d52f..ebcd495 100644 --- a/app/tests/main_window_tests.cpp +++ b/app/tests/main_window_tests.cpp @@ -783,6 +783,7 @@ void testModeActionsControlEditingAvailability() QDockWidget *properties_dock = requiredChild(window, "propertiesDock"); QLabel *selection = requiredChild(window, "selectionValueLabel"); QLineEdit *text_edit = requiredChild(window, "controlTextEdit"); + QLineEdit *text_color_edit = requiredChild(window, "textColorEdit"); QComboBox *button_operation = requiredChild( window, "buttonOperationComboBox"); QComboBox *binding_area = requiredChild( @@ -793,6 +794,9 @@ void testModeActionsControlEditingAvailability() window, "progressMinimumSpinBox"); QSpinBox *progress_maximum = requiredChild( window, "progressMaximumSpinBox"); + QSpinBox *font_size = requiredChild(window, "fontSizeSpinBox"); + QCheckBox *font_bold = requiredChild(window, "fontBoldCheckBox"); + QCheckBox *font_italic = requiredChild(window, "fontItalicCheckBox"); QCheckBox *progress_show_value = requiredChild( window, "progressShowValueCheckBox"); QPushButton *apply_properties = requiredChild( @@ -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(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}; diff --git a/app/tests/project_management_tests.cpp b/app/tests/project_management_tests.cpp index abda382..55db3fc 100644 --- a/app/tests/project_management_tests.cpp +++ b/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,