| @@ -69,16 +69,7 @@ constexpr std::array kControlDescriptors = { | |||
| {0, 0, 360, 136}, | |||
| HmiBindingKind::None, | |||
| HmiRuntimeValueKind::None, | |||
| false}, | |||
| HmiControlDescriptor{HmiControlType::ProgressBar, | |||
| "progressBar", | |||
| "进度条", | |||
| "progress-bar", | |||
| "进度", | |||
| {0, 0, 240, 36}, | |||
| HmiBindingKind::Word, | |||
| HmiRuntimeValueKind::Word, | |||
| true} | |||
| false} | |||
| }; | |||
| static_assert( | |||
| @@ -89,23 +89,6 @@ bool validateAppearanceProperty( | |||
| } // namespace | |||
| bool HmiProgressBarConfig::isValid() const | |||
| { | |||
| return minimumValue < maximumValue; | |||
| } | |||
| int HmiProgressBarConfig::percentageForValue(std::int16_t value) const | |||
| { | |||
| if (!isValid()) | |||
| { | |||
| return 0; | |||
| } | |||
| const int minimum = minimumValue; | |||
| const int maximum = maximumValue; | |||
| const int bounded_value = std::clamp(static_cast<int>(value), minimum, maximum); | |||
| return (bounded_value - minimum) * 100 / (maximum - minimum); | |||
| } | |||
| bool HmiControl::validate(std::string *error) const | |||
| { | |||
| const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type); | |||
| @@ -206,19 +189,6 @@ bool HmiControl::validate(std::string *error) const | |||
| setError(error, "非页面跳转控件不能包含跳转配置"); | |||
| return false; | |||
| } | |||
| if (type == HmiControlType::ProgressBar) | |||
| { | |||
| if (!progressBar.has_value() || !progressBar->isValid()) | |||
| { | |||
| setError(error, "进度条最小值必须小于最大值"); | |||
| return false; | |||
| } | |||
| } | |||
| else if (progressBar.has_value()) | |||
| { | |||
| setError(error, "非进度条控件不能包含进度条配置"); | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| @@ -229,11 +199,6 @@ bool HmiControl::isConfigured() const | |||
| { | |||
| return false; | |||
| } | |||
| if (type == HmiControlType::ProgressBar | |||
| && (!progressBar.has_value() || !progressBar->isValid())) | |||
| { | |||
| return false; | |||
| } | |||
| if (!descriptor->requiresBindingForRunning | |||
| && type != HmiControlType::PageJump) | |||
| { | |||
| @@ -10,7 +10,6 @@ | |||
| #include "register_address.h" | |||
| #include "project_limits.h" | |||
| #include <cstdint> | |||
| #include <map> | |||
| #include <optional> | |||
| #include <string> | |||
| @@ -56,7 +55,6 @@ enum class HmiControlType | |||
| Label, // 标签 | |||
| PageJump, // 页面跳转 | |||
| AlarmList, // 报警列表 | |||
| ProgressBar, // 进度条 | |||
| Count // 已注册控件类型数量,不作为实际控件使用 | |||
| }; | |||
| @@ -76,19 +74,6 @@ struct HmiPageJumpConfig | |||
| std::string targetPageId; | |||
| }; | |||
| /** | |||
| * @brief 进度条的有效值范围和文字显示方式 | |||
| */ | |||
| struct HmiProgressBarConfig | |||
| { | |||
| std::int16_t minimumValue = 0; | |||
| std::int16_t maximumValue = 100; | |||
| bool showValue = true; | |||
| bool isValid() const; | |||
| int percentageForValue(std::int16_t value) const; | |||
| }; | |||
| /** | |||
| * @brief 描述一个可保存的 HMI 控件及其显示和寄存器配置 | |||
| * | |||
| @@ -104,7 +89,6 @@ struct HmiControl | |||
| std::map<std::string, std::string> properties; | |||
| HmiButtonOperation buttonOperation = HmiButtonOperation::MomentaryOn; | |||
| std::optional<HmiPageJumpConfig> pageJump; | |||
| std::optional<HmiProgressBarConfig> progressBar; | |||
| /** | |||
| * @brief 校验控件的标识、尺寸、扩展属性和寄存器绑定 | |||
| @@ -670,14 +670,6 @@ QJsonObject serializeHmiControl(const HmiControl &control) | |||
| ? control.pageJump->targetPageId | |||
| : std::string{})); | |||
| } | |||
| if (control.type == HmiControlType::ProgressBar) | |||
| { | |||
| const HmiProgressBarConfig config = control.progressBar.value_or( | |||
| HmiProgressBarConfig{}); | |||
| object.insert(QStringLiteral("minimumValue"), config.minimumValue); | |||
| object.insert(QStringLiteral("maximumValue"), config.maximumValue); | |||
| object.insert(QStringLiteral("showValue"), config.showValue); | |||
| } | |||
| return object; | |||
| } | |||
| @@ -735,37 +727,6 @@ bool parseHmiControl( | |||
| } | |||
| control->pageJump = HmiPageJumpConfig{std::move(target_page_id)}; | |||
| } | |||
| if (control->type == HmiControlType::ProgressBar) | |||
| { | |||
| int minimum_value = 0; | |||
| int maximum_value = 0; | |||
| bool show_value = true; | |||
| if (!readInt( | |||
| object, | |||
| "minimumValue", | |||
| context, | |||
| std::numeric_limits<std::int16_t>::min(), | |||
| std::numeric_limits<std::int16_t>::max(), | |||
| &minimum_value, | |||
| state) | |||
| || !readInt( | |||
| object, | |||
| "maximumValue", | |||
| context, | |||
| std::numeric_limits<std::int16_t>::min(), | |||
| std::numeric_limits<std::int16_t>::max(), | |||
| &maximum_value, | |||
| state) | |||
| || !readBool(object, "showValue", context, &show_value, state)) | |||
| { | |||
| return false; | |||
| } | |||
| control->progressBar = HmiProgressBarConfig{ | |||
| static_cast<std::int16_t>(minimum_value), | |||
| static_cast<std::int16_t>(maximum_value), | |||
| show_value}; | |||
| } | |||
| // binding 允许为 null,其余非空值必须是合法的寄存器地址对象 | |||
| if (binding.isNull()) | |||
| { | |||
| @@ -120,12 +120,6 @@ bool HmiEditorService::pagesEqual( | |||
| bool HmiEditorService::controlsEqual( | |||
| const HmiControl &left, const HmiControl &right) | |||
| { | |||
| const bool progress_equal = left.progressBar.has_value() | |||
| == right.progressBar.has_value() | |||
| && (!left.progressBar.has_value() | |||
| || (left.progressBar->minimumValue == right.progressBar->minimumValue | |||
| && left.progressBar->maximumValue == right.progressBar->maximumValue | |||
| && left.progressBar->showValue == right.progressBar->showValue)); | |||
| const bool page_jump_equal = left.pageJump.has_value() == right.pageJump.has_value() | |||
| && (!left.pageJump.has_value() | |||
| || left.pageJump->targetPageId == right.pageJump->targetPageId); | |||
| @@ -139,8 +133,7 @@ bool HmiEditorService::controlsEqual( | |||
| && left.binding == right.binding | |||
| && left.properties == right.properties | |||
| && left.buttonOperation == right.buttonOperation | |||
| && page_jump_equal | |||
| && progress_equal; | |||
| && page_jump_equal; | |||
| } | |||
| HmiEditorResult HmiEditorService::historyFailure(const std::string &message) | |||
| @@ -811,10 +804,6 @@ HmiControl HmiEditorService::makeControl( | |||
| { | |||
| control.pageJump = HmiPageJumpConfig{}; | |||
| } | |||
| if (descriptor.type == HmiControlType::ProgressBar) | |||
| { | |||
| control.progressBar = HmiProgressBarConfig{}; | |||
| } | |||
| 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); | |||
| @@ -194,48 +194,6 @@ public: | |||
| textWithValue()); | |||
| break; | |||
| } | |||
| case HmiControlType::ProgressBar: | |||
| { | |||
| const HmiProgressBarConfig config = control_.progressBar.value_or( | |||
| HmiProgressBarConfig{}); | |||
| const int percentage = has_runtime_value_ | |||
| ? config.percentageForValue(word_value_) : 0; | |||
| painter->setPen(Qt::NoPen); | |||
| painter->setBrush(QColor(QStringLiteral("#e4e9e7"))); | |||
| painter->drawRoundedRect(rect, 4, 4); | |||
| const QRectF track = rect.adjusted(2, 2, -2, -2); | |||
| if (has_runtime_value_ && percentage > 0) | |||
| { | |||
| QRectF fill = track; | |||
| fill.setWidth(track.width() * percentage / 100.0); | |||
| painter->setBrush(QColor(QStringLiteral("#3c8c62"))); | |||
| painter->drawRoundedRect(fill, 3, 3); | |||
| } | |||
| painter->setBrush(Qt::NoBrush); | |||
| painter->setPen(QPen(QColor(QStringLiteral("#617069")), 1)); | |||
| painter->drawRoundedRect(rect, 4, 4); | |||
| QString display_text = QString::fromUtf8( | |||
| control_.text.data(), static_cast<int>(control_.text.size())); | |||
| if (config.showValue) | |||
| { | |||
| const QString percentage_text = has_runtime_value_ | |||
| ? QString::number(percentage) + QLatin1Char('%') | |||
| : runtime_active_ ? QStringLiteral("--") | |||
| : QStringLiteral("0%"); | |||
| display_text = display_text.isEmpty() | |||
| ? percentage_text | |||
| : display_text + QStringLiteral(": ") + percentage_text; | |||
| } | |||
| painter->setPen(configuredTextColor(QColor(QStringLiteral("#20342a")))); | |||
| painter->drawText(rect.adjusted(6, 0, -6, 0), | |||
| Qt::AlignCenter, | |||
| display_text); | |||
| break; | |||
| } | |||
| case HmiControlType::PageJump: | |||
| { | |||
| const QColor fill = runtime_active_ && page_hovered_ | |||
| @@ -387,8 +387,6 @@ void MainWindow::configureActions() | |||
| [this] { addHmiControl(HmiControlType::NumericDisplay); }); | |||
| connect(ui_->addNumericInputAction, &QAction::triggered, this, | |||
| [this] { addHmiControl(HmiControlType::NumericInput); }); | |||
| connect(ui_->addProgressBarAction, &QAction::triggered, this, | |||
| [this] { addHmiControl(HmiControlType::ProgressBar); }); | |||
| connect(ui_->addLabelAction, &QAction::triggered, this, | |||
| [this] { addHmiControl(HmiControlType::Label); }); | |||
| connect(ui_->addPageJumpAction, &QAction::triggered, this, | |||
| @@ -403,7 +401,6 @@ void MainWindow::configureActions() | |||
| QStringLiteral("hmiMoreControlsButton"), | |||
| makeUiIcon(UiIcon::More), | |||
| QList<QAction *>{ | |||
| ui_->addProgressBarAction, | |||
| ui_->addPageJumpAction, | |||
| ui_->addAlarmListAction, | |||
| ui_->configureAlarmsAction}); | |||
| @@ -669,7 +666,6 @@ void MainWindow::configureAppearance() | |||
| ui_->addIndicatorAction->setIcon(makeUiIcon(UiIcon::Indicator)); | |||
| ui_->addNumericDisplayAction->setIcon(makeUiIcon(UiIcon::NumericDisplay)); | |||
| ui_->addNumericInputAction->setIcon(makeUiIcon(UiIcon::NumericInput)); | |||
| ui_->addProgressBarAction->setIcon(makeUiIcon(UiIcon::ProgressBar)); | |||
| ui_->addLabelAction->setIcon(makeUiIcon(UiIcon::Text)); | |||
| ui_->addPageJumpAction->setIcon(makeUiIcon(UiIcon::PageJump)); | |||
| ui_->addAlarmListAction->setIcon(makeUiIcon(UiIcon::AlarmList)); | |||
| @@ -1684,7 +1680,6 @@ void MainWindow::updateModeUi(const QString &message) | |||
| ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addNumericInputAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addProgressBarAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addLabelAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing); | |||
| ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing); | |||
| @@ -671,61 +671,13 @@ | |||
| </widget> | |||
| </item> | |||
| <item row="10" column="0"> | |||
| <widget class="QLabel" name="progressMinimumLabel"> | |||
| <property name="text"> | |||
| <string>最小值</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="10" column="1"> | |||
| <widget class="QSpinBox" name="progressMinimumSpinBox"> | |||
| <property name="minimum"> | |||
| <number>-32768</number> | |||
| </property> | |||
| <property name="maximum"> | |||
| <number>32767</number> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="11" column="0"> | |||
| <widget class="QLabel" name="progressMaximumLabel"> | |||
| <property name="text"> | |||
| <string>最大值</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="11" column="1"> | |||
| <widget class="QSpinBox" name="progressMaximumSpinBox"> | |||
| <property name="minimum"> | |||
| <number>-32768</number> | |||
| </property> | |||
| <property name="maximum"> | |||
| <number>32767</number> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="12" column="0"> | |||
| <widget class="QLabel" name="progressShowValueLabel"> | |||
| <property name="text"> | |||
| <string>数值显示</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="12" column="1"> | |||
| <widget class="QCheckBox" name="progressShowValueCheckBox"> | |||
| <property name="text"> | |||
| <string>显示百分比</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="13" column="0"> | |||
| <widget class="QLabel" name="textColorLabel"> | |||
| <property name="text"> | |||
| <string>字体颜色</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="13" column="1"> | |||
| <item row="10" column="1"> | |||
| <widget class="QWidget" name="textColorContainer"> | |||
| <layout class="QHBoxLayout" name="textColorLayout"> | |||
| <property name="leftMargin"><number>0</number></property> | |||
| @@ -749,24 +701,24 @@ | |||
| </layout> | |||
| </widget> | |||
| </item> | |||
| <item row="14" column="0"> | |||
| <item row="11" column="0"> | |||
| <widget class="QLabel" name="fontSizeLabel"> | |||
| <property name="text"> | |||
| <string>字号</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="14" column="1"> | |||
| <item row="11" column="1"> | |||
| <widget class="QSpinBox" name="fontSizeSpinBox"/> | |||
| </item> | |||
| <item row="15" column="0"> | |||
| <item row="12" column="0"> | |||
| <widget class="QLabel" name="fontBoldLabel"> | |||
| <property name="text"> | |||
| <string>字体样式</string> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| <item row="15" column="1"> | |||
| <item row="12" column="1"> | |||
| <widget class="QWidget" name="fontStyleContainer"> | |||
| <layout class="QHBoxLayout" name="fontStyleLayout"> | |||
| <property name="leftMargin"><number>0</number></property> | |||
| @@ -786,7 +738,7 @@ | |||
| </layout> | |||
| </widget> | |||
| </item> | |||
| <item row="16" column="0" colspan="2"> | |||
| <item row="13" column="0" colspan="2"> | |||
| <widget class="QPushButton" name="applyPropertiesButton"> | |||
| <property name="text"> | |||
| <string>应用属性</string> | |||
| @@ -1104,14 +1056,6 @@ | |||
| <string>添加数值输入控件</string> | |||
| </property> | |||
| </action> | |||
| <action name="addProgressBarAction"> | |||
| <property name="text"> | |||
| <string>进度条</string> | |||
| </property> | |||
| <property name="toolTip"> | |||
| <string>添加进度条控件</string> | |||
| </property> | |||
| </action> | |||
| <action name="addLabelAction"> | |||
| <property name="text"> | |||
| <string>文本</string> | |||
| @@ -193,9 +193,6 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||
| static_cast<QWidget *>(ui_.bindingIndexSpinBox), | |||
| static_cast<QWidget *>(ui_.targetPageComboBox), | |||
| static_cast<QWidget *>(ui_.buttonOperationComboBox), | |||
| 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), | |||
| @@ -218,12 +215,6 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||
| ui_.targetPageComboBox->setVisible(false); | |||
| ui_.buttonOperationLabel->setVisible(false); | |||
| ui_.buttonOperationComboBox->setVisible(false); | |||
| ui_.progressMinimumLabel->setVisible(false); | |||
| ui_.progressMinimumSpinBox->setVisible(false); | |||
| ui_.progressMaximumLabel->setVisible(false); | |||
| ui_.progressMaximumSpinBox->setVisible(false); | |||
| ui_.progressShowValueLabel->setVisible(false); | |||
| ui_.progressShowValueCheckBox->setVisible(false); | |||
| ui_.textColorEdit->clear(); | |||
| ui_.textColorButton->setStyleSheet(QString{}); | |||
| ui_.fontSizeSpinBox->setValue( | |||
| @@ -281,22 +272,6 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||
| ui_.targetPageComboBox->findData(fromUtf8(target_id))); | |||
| } | |||
| const bool is_progress_bar = control->type == HmiControlType::ProgressBar; | |||
| ui_.progressMinimumLabel->setVisible(is_progress_bar); | |||
| ui_.progressMinimumSpinBox->setVisible(is_progress_bar); | |||
| ui_.progressMaximumLabel->setVisible(is_progress_bar); | |||
| ui_.progressMaximumSpinBox->setVisible(is_progress_bar); | |||
| ui_.progressShowValueLabel->setVisible(is_progress_bar); | |||
| ui_.progressShowValueCheckBox->setVisible(is_progress_bar); | |||
| ui_.progressMinimumSpinBox->setEnabled(is_progress_bar); | |||
| ui_.progressMaximumSpinBox->setEnabled(is_progress_bar); | |||
| ui_.progressShowValueCheckBox->setEnabled(is_progress_bar); | |||
| const HmiProgressBarConfig progress_config = control->progressBar.value_or( | |||
| HmiProgressBarConfig{}); | |||
| 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() | |||
| @@ -572,13 +547,6 @@ void PropertyPanelController::applySelectedControlProperties() | |||
| control.pageJump = HmiPageJumpConfig{ | |||
| toUtf8(ui_.targetPageComboBox->currentData().toString())}; | |||
| } | |||
| if (control.type == HmiControlType::ProgressBar) | |||
| { | |||
| control.progressBar = HmiProgressBarConfig{ | |||
| static_cast<std::int16_t>(ui_.progressMinimumSpinBox->value()), | |||
| static_cast<std::int16_t>(ui_.progressMaximumSpinBox->value()), | |||
| ui_.progressShowValueCheckBox->isChecked()}; | |||
| } | |||
| const std::string text_color = toUtf8(ui_.textColorEdit->text()); | |||
| if (text_color.empty()) | |||
| { | |||
| @@ -291,14 +291,6 @@ QPixmap renderIcon(UiIcon icon, int size) | |||
| painter.drawLine(QPointF(18, 7), QPointF(18, 17)); | |||
| break; | |||
| } | |||
| case UiIcon::ProgressBar: | |||
| { | |||
| painter.drawRoundedRect(QRectF(2, 8, 20, 8), 2, 2); | |||
| painter.setPen(Qt::NoPen); | |||
| painter.setBrush(kAccent); | |||
| painter.drawRoundedRect(QRectF(4, 10, 11, 4), 1, 1); | |||
| break; | |||
| } | |||
| case UiIcon::Text: | |||
| { | |||
| painter.drawLine(QPointF(5, 5), QPointF(19, 5)); | |||
| @@ -30,7 +30,6 @@ enum class UiIcon | |||
| Indicator, // HMI 指示灯 | |||
| NumericDisplay, // HMI 数值显示 | |||
| NumericInput, // HMI 数值输入 | |||
| ProgressBar, // HMI 进度条 | |||
| Text, // HMI 文本 | |||
| PageJump, // HMI 页面跳转 | |||
| AlarmList, // HMI 报警列表 | |||
| @@ -187,45 +187,6 @@ void testHmiControlRegistryCompleteness() | |||
| "controls without bindings must not resolve a register area"); | |||
| } | |||
| void testProgressBarConfigurationBoundaries() | |||
| { | |||
| const HmiProgressBarConfig config{-20, 80, true}; | |||
| require(config.isValid(), "a progress bar range must accept increasing bounds"); | |||
| require(config.percentageForValue(-20) == 0, | |||
| "progress bar minimum must map to zero percent"); | |||
| require(config.percentageForValue(30) == 50, | |||
| "progress bar midpoint must map to fifty percent"); | |||
| require(config.percentageForValue(80) == 100, | |||
| "progress bar maximum must map to one hundred percent"); | |||
| require(config.percentageForValue(-100) == 0 | |||
| && config.percentageForValue(100) == 100, | |||
| "progress bar percentages must clamp values outside the range"); | |||
| HmiProgressBarConfig invalid_range{10, 10, true}; | |||
| require(!invalid_range.isValid() | |||
| && invalid_range.percentageForValue(10) == 0, | |||
| "progress bar must reject an empty range defensively"); | |||
| HmiControl progress; | |||
| progress.id = "progress"; | |||
| progress.type = HmiControlType::ProgressBar; | |||
| progress.progressBar = config; | |||
| require(progress.validate(), | |||
| "an unbound progress bar with valid configuration must remain a valid draft"); | |||
| require(!progress.isConfigured(), | |||
| "an unbound progress bar must not be ready for running"); | |||
| progress.binding = RegisterAddress{RegisterArea::D, 0}; | |||
| require(progress.validate() && progress.isConfigured(), | |||
| "a progress bar with a D binding must be ready for running"); | |||
| progress.progressBar->maximumValue = progress.progressBar->minimumValue; | |||
| require(!progress.validate(), | |||
| "a progress bar with an invalid range must be rejected"); | |||
| progress.progressBar = config; | |||
| progress.binding = RegisterAddress{RegisterArea::M, 0}; | |||
| require(!progress.validate(), | |||
| "a progress bar must reject an M binding"); | |||
| } | |||
| Project makeValidProject(); | |||
| void testHmiAppearancePropertyBoundaries() | |||
| @@ -851,7 +812,6 @@ int main() | |||
| testRegisterAddressParsing(); | |||
| testRegisterRepositorySeparatesAreas(); | |||
| testHmiControlRegistryCompleteness(); | |||
| testProgressBarConfigurationBoundaries(); | |||
| testHmiAppearancePropertyBoundaries(); | |||
| testLogicNodeConfigurationBoundaries(); | |||
| testEdgeAndCommentBoundaries(); | |||
| @@ -33,26 +33,13 @@ void testControlEditing() | |||
| const HmiEditorResult display = service.addControl( | |||
| page_id, HmiControlType::NumericDisplay); | |||
| const HmiEditorResult input = service.addControl(page_id, HmiControlType::NumericInput); | |||
| const HmiEditorResult progress = service.addControl( | |||
| page_id, HmiControlType::ProgressBar); | |||
| require(button.succeeded && indicator.succeeded && display.succeeded | |||
| && input.succeeded && progress.succeeded, | |||
| && input.succeeded, | |||
| "basic HMI controls must be added"); | |||
| const HmiPage *page = service.findPage(page_id); | |||
| require(page != nullptr && page->controls.size() == 5, | |||
| require(page != nullptr && page->controls.size() == 4, | |||
| "all added controls must be kept in the page model"); | |||
| const HmiControl *progress_control = service.findControl(page_id, progress.id); | |||
| require(progress_control != nullptr | |||
| && progress_control->id == "progress-bar-1" | |||
| && progress_control->text == "进度" | |||
| && progress_control->bounds.width == 240 | |||
| && progress_control->bounds.height == 36 | |||
| && progress_control->progressBar.has_value() | |||
| && progress_control->progressBar->minimumValue == 0 | |||
| && progress_control->progressBar->maximumValue == 100 | |||
| && progress_control->progressBar->showValue, | |||
| "new progress bars must use the registered defaults"); | |||
| require(service.moveControl(page_id, button.id, {120, 80, 120, 40}).succeeded, | |||
| "a valid control move must succeed"); | |||
| require(!service.moveControl(page_id, button.id, {790, 460, 120, 40}).succeeded, | |||
| @@ -77,20 +64,6 @@ void testControlEditing() | |||
| "deleting a selected control must succeed"); | |||
| require(service.findControl(page_id, indicator.id) == nullptr, | |||
| "deleted controls must not remain in the model"); | |||
| HmiControl configured_progress = *service.findControl(page_id, progress.id); | |||
| configured_progress.binding = RegisterAddress{RegisterArea::D, 8}; | |||
| configured_progress.progressBar->minimumValue = -10; | |||
| configured_progress.progressBar->maximumValue = 90; | |||
| require(service.updateControl(page_id, progress.id, configured_progress).succeeded, | |||
| "a progress bar must accept a valid D binding and value range"); | |||
| configured_progress.progressBar->maximumValue = -10; | |||
| require(!service.updateControl(page_id, progress.id, configured_progress).succeeded, | |||
| "a progress bar must reject a non-increasing value range"); | |||
| configured_progress.progressBar->maximumValue = 90; | |||
| configured_progress.binding = RegisterAddress{RegisterArea::M, 8}; | |||
| require(!service.updateControl(page_id, progress.id, configured_progress).succeeded, | |||
| "a progress bar must reject an M binding"); | |||
| } | |||
| void testRuntimeUsesRegisterRepository() | |||
| @@ -159,14 +132,6 @@ void testRuntimeUsesRegisterRepository() | |||
| require(numeric_value.succeeded && numeric_value.word_value == -18, | |||
| "numeric display must read D values through the repository"); | |||
| HmiControl progress; | |||
| progress.id = "progress"; | |||
| progress.type = HmiControlType::ProgressBar; | |||
| progress.binding = RegisterAddress{RegisterArea::D, 9}; | |||
| progress.progressBar = HmiProgressBarConfig{-20, 80, true}; | |||
| const HmiRuntimeReadResult progress_value = runtime_service.readControl(progress); | |||
| require(progress_value.succeeded && progress_value.word_value == -18, | |||
| "progress bars must read D values through the repository"); | |||
| } | |||
| void testHistoryAndAtomicBatchDelete() | |||
| @@ -81,14 +81,6 @@ Project makeExampleProject() | |||
| alarm_list.bounds = {10, 200, 360, 180}; | |||
| alarm_list.text = "Alarms"; | |||
| HmiControl progress_bar; | |||
| progress_bar.id = "progress-bar"; | |||
| progress_bar.type = HmiControlType::ProgressBar; | |||
| progress_bar.bounds = {400, 20, 240, 36}; | |||
| progress_bar.text = "Completion"; | |||
| progress_bar.binding = RegisterAddress{RegisterArea::D, 4}; | |||
| progress_bar.progressBar = HmiProgressBarConfig{-20, 80, true}; | |||
| HmiPage page; | |||
| page.id = "main-page"; | |||
| page.name = "Main"; | |||
| @@ -99,7 +91,6 @@ Project makeExampleProject() | |||
| page.controls.push_back(title_label); | |||
| page.controls.push_back(settings_jump); | |||
| page.controls.push_back(alarm_list); | |||
| page.controls.push_back(progress_bar); | |||
| HmiPage settings_page; | |||
| settings_page.id = "settings-page"; | |||
| @@ -435,10 +426,10 @@ void testExampleProjectRoundTrip() | |||
| const QString first_path = directory.filePath("example.json"); | |||
| const QString second_path = directory.filePath("example-copy.json"); | |||
| const QString invalid_operation_path = directory.filePath("invalid-operation.json"); | |||
| const QString unsupported_hmi_type_path = directory.filePath( | |||
| "unsupported-hmi-type.json"); | |||
| const QString missing_initial_path = directory.filePath("missing-initial.json"); | |||
| const QString missing_target_path = directory.filePath("missing-target.json"); | |||
| const QString missing_progress_range_path = directory.filePath( | |||
| "missing-progress-range.json"); | |||
| const QString missing_alarms_path = directory.filePath("missing-alarms.json"); | |||
| const QString missing_register_comments_path = directory.filePath( | |||
| "missing-register-comments.json"); | |||
| @@ -471,11 +462,7 @@ void testExampleProjectRoundTrip() | |||
| && saved_json.contains("\"operation\": \"add\"") | |||
| && saved_json.contains("\"operation\": \"subtract\"") | |||
| && saved_json.contains("\"comment\": \"启动条件与温度检查\"") | |||
| && saved_json.contains("\"type\": \"alarmList\"") | |||
| && saved_json.contains("\"type\": \"progressBar\"") | |||
| && saved_json.contains("\"minimumValue\": -20") | |||
| && saved_json.contains("\"maximumValue\": 80") | |||
| && saved_json.contains("\"showValue\": true"), | |||
| && saved_json.contains("\"type\": \"alarmList\""), | |||
| "version 1.0 projects must persist pages and alarm definitions"); | |||
| require(!saved_json.contains("\"stages\"") | |||
| && !saved_json.contains("\"branches\""), | |||
| @@ -491,8 +478,8 @@ void testExampleProjectRoundTrip() | |||
| require(project.hmiPages.size() == 2, "HMI page count must survive round trip"); | |||
| require(project.initialHmiPageId == "main-page", | |||
| "the initial HMI page id must survive round trip"); | |||
| require(project.hmiPages.front().controls.size() == 8, | |||
| "register, navigation, AlarmList and ProgressBar controls must survive round trip"); | |||
| require(project.hmiPages.front().controls.size() == 7, | |||
| "register, navigation and AlarmList controls must survive round trip"); | |||
| require(project.hmiPages.front().controls.front().binding->area() | |||
| == RegisterArea::M, | |||
| "HMI M binding must survive round trip"); | |||
| @@ -523,13 +510,6 @@ void testExampleProjectRoundTrip() | |||
| require(project.hmiPages.front().controls.at(6).type | |||
| == HmiControlType::AlarmList, | |||
| "AlarmList control type must survive round trip"); | |||
| require(project.hmiPages.front().controls.at(7).type | |||
| == HmiControlType::ProgressBar | |||
| && project.hmiPages.front().controls.at(7).progressBar.has_value() | |||
| && project.hmiPages.front().controls.at(7).progressBar->minimumValue == -20 | |||
| && project.hmiPages.front().controls.at(7).progressBar->maximumValue == 80 | |||
| && project.hmiPages.front().controls.at(7).progressBar->showValue, | |||
| "ProgressBar configuration must survive round trip"); | |||
| require(project.alarmDefinitions.size() == 2 | |||
| && project.alarmDefinitions.front().condition | |||
| == AlarmCondition::MOn | |||
| @@ -601,6 +581,18 @@ void testExampleProjectRoundTrip() | |||
| require(!service.load(invalid_operation_path.toStdString()).succeeded, | |||
| "unsupported HMI button operations must be rejected"); | |||
| QByteArray unsupported_hmi_type = saved_json; | |||
| unsupported_hmi_type.replace( | |||
| "\"type\": \"alarmList\"", | |||
| "\"type\": \"removedControl\""); | |||
| writeText(unsupported_hmi_type_path, unsupported_hmi_type); | |||
| const ProjectOperationResult unsupported_type_result = service.load( | |||
| unsupported_hmi_type_path.toStdString()); | |||
| require(!unsupported_type_result.succeeded | |||
| && unsupported_type_result.storageError | |||
| == ProjectStorageError::InvalidField, | |||
| "removed HMI control types must be rejected during strict loading"); | |||
| QJsonObject missing_initial = QJsonDocument::fromJson(saved_json).object(); | |||
| missing_initial.remove(QStringLiteral("initialHmiPageId")); | |||
| writeText( | |||
| @@ -704,23 +696,6 @@ void testExampleProjectRoundTrip() | |||
| && missing_result.storageError == ProjectStorageError::MissingField, | |||
| "the 1.0 schema must require PageJump targetPageId"); | |||
| QJsonObject missing_progress_range = QJsonDocument::fromJson(saved_json).object(); | |||
| pages = missing_progress_range.value(QStringLiteral("hmiPages")).toArray(); | |||
| main_page = pages.at(0).toObject(); | |||
| controls = main_page.value(QStringLiteral("controls")).toArray(); | |||
| QJsonObject progress = controls.at(7).toObject(); | |||
| progress.remove(QStringLiteral("maximumValue")); | |||
| controls.replace(7, progress); | |||
| main_page.insert(QStringLiteral("controls"), controls); | |||
| pages.replace(0, main_page); | |||
| missing_progress_range.insert(QStringLiteral("hmiPages"), pages); | |||
| writeText( | |||
| missing_progress_range_path, | |||
| QJsonDocument(missing_progress_range).toJson(QJsonDocument::Compact)); | |||
| missing_result = service.load(missing_progress_range_path.toStdString()); | |||
| require(!missing_result.succeeded | |||
| && missing_result.storageError == ProjectStorageError::MissingField, | |||
| "the 1.0 schema must require the ProgressBar value range"); | |||
| } | |||
| void testInvalidFiles() | |||