| @@ -69,7 +69,16 @@ constexpr std::array kControlDescriptors = { | |||||
| {0, 0, 360, 180}, | {0, 0, 360, 180}, | ||||
| HmiBindingKind::None, | HmiBindingKind::None, | ||||
| HmiRuntimeValueKind::None, | HmiRuntimeValueKind::None, | ||||
| false} | |||||
| false}, | |||||
| HmiControlDescriptor{HmiControlType::ProgressBar, | |||||
| "progressBar", | |||||
| "进度条", | |||||
| "progress-bar", | |||||
| "进度", | |||||
| {0, 0, 240, 36}, | |||||
| HmiBindingKind::Word, | |||||
| HmiRuntimeValueKind::Word, | |||||
| true} | |||||
| }; | }; | ||||
| static_assert( | static_assert( | ||||
| @@ -17,6 +17,23 @@ void setError(std::string *error, const std::string &message) | |||||
| } // namespace | } // 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 | bool HmiControl::validate(std::string *error) const | ||||
| { | { | ||||
| const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type); | const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type); | ||||
| @@ -47,7 +64,7 @@ bool HmiControl::validate(std::string *error) const | |||||
| hmiBindingArea(descriptor->bindingKind); | hmiBindingArea(descriptor->bindingKind); | ||||
| if (binding.has_value() && !binding_area.has_value()) | if (binding.has_value() && !binding_area.has_value()) | ||||
| { | { | ||||
| setError(error, "文本、页面跳转和报警列表控件不能绑定寄存器"); | |||||
| setError(error, "该 HMI 控件不能绑定寄存器"); | |||||
| return false; | return false; | ||||
| } | } | ||||
| if (binding.has_value() && binding_area.has_value() | if (binding.has_value() && binding_area.has_value() | ||||
| @@ -55,11 +72,11 @@ bool HmiControl::validate(std::string *error) const | |||||
| { | { | ||||
| if (descriptor->bindingKind == HmiBindingKind::Bit) | if (descriptor->bindingKind == HmiBindingKind::Bit) | ||||
| { | { | ||||
| setError(error, "按钮和指示灯必须绑定 M 区地址"); | |||||
| setError(error, "HMI 位控件必须绑定 M 区地址"); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| setError(error, "数值控件必须绑定 D 区地址"); | |||||
| setError(error, "HMI 字控件必须绑定 D 区地址"); | |||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -81,6 +98,19 @@ bool HmiControl::validate(std::string *error) const | |||||
| setError(error, "非页面跳转控件不能包含跳转配置"); | setError(error, "非页面跳转控件不能包含跳转配置"); | ||||
| return false; | 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; | return true; | ||||
| } | } | ||||
| @@ -91,6 +121,11 @@ bool HmiControl::isConfigured() const | |||||
| { | { | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (type == HmiControlType::ProgressBar | |||||
| && (!progressBar.has_value() || !progressBar->isValid())) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| if (!descriptor->requiresBindingForRunning | if (!descriptor->requiresBindingForRunning | ||||
| && type != HmiControlType::PageJump) | && type != HmiControlType::PageJump) | ||||
| { | { | ||||
| @@ -9,6 +9,7 @@ | |||||
| #include "register_address.h" | #include "register_address.h" | ||||
| #include <cstdint> | |||||
| #include <map> | #include <map> | ||||
| #include <optional> | #include <optional> | ||||
| #include <string> | #include <string> | ||||
| @@ -44,6 +45,7 @@ enum class HmiControlType | |||||
| Label, // 标签 | Label, // 标签 | ||||
| PageJump, // 页面跳转 | PageJump, // 页面跳转 | ||||
| AlarmList, // 报警列表 | AlarmList, // 报警列表 | ||||
| ProgressBar, // 进度条 | |||||
| Count // 已注册控件类型数量,不作为实际控件使用 | Count // 已注册控件类型数量,不作为实际控件使用 | ||||
| }; | }; | ||||
| @@ -63,6 +65,19 @@ struct HmiPageJumpConfig | |||||
| std::string targetPageId; | 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 控件及其显示和寄存器配置 | * @brief 描述一个可保存的 HMI 控件及其显示和寄存器配置 | ||||
| * | * | ||||
| @@ -78,13 +93,14 @@ struct HmiControl | |||||
| std::map<std::string, std::string> properties; | std::map<std::string, std::string> properties; | ||||
| HmiButtonOperation buttonOperation = HmiButtonOperation::MomentaryOn; | HmiButtonOperation buttonOperation = HmiButtonOperation::MomentaryOn; | ||||
| std::optional<HmiPageJumpConfig> pageJump; | std::optional<HmiPageJumpConfig> pageJump; | ||||
| std::optional<HmiProgressBarConfig> progressBar; | |||||
| /** | /** | ||||
| * @brief 校验控件的标识、尺寸、扩展属性和寄存器绑定 | * @brief 校验控件的标识、尺寸、扩展属性和寄存器绑定 | ||||
| * @param error 可选错误输出,失败时写入首个校验原因 | * @param error 可选错误输出,失败时写入首个校验原因 | ||||
| * @return 配置完整且满足控件类型绑定规则时返回 true | * @return 配置完整且满足控件类型绑定规则时返回 true | ||||
| * | * | ||||
| * 按钮和指示灯必须绑定有效 M 地址,数值显示和数值输入必须绑定有效 D 地址 | |||||
| * 位控件必须绑定有效 M 地址,字控件必须绑定有效 D 地址 | |||||
| */ | */ | ||||
| bool validate(std::string *error = nullptr) const; | bool validate(std::string *error = nullptr) const; | ||||
| bool isConfigured() const; | bool isConfigured() const; | ||||
| @@ -696,6 +696,14 @@ QJsonObject serializeHmiControl(const HmiControl &control) | |||||
| ? control.pageJump->targetPageId | ? control.pageJump->targetPageId | ||||
| : std::string{})); | : 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; | return object; | ||||
| } | } | ||||
| @@ -749,6 +757,36 @@ bool parseHmiControl( | |||||
| } | } | ||||
| control->pageJump = HmiPageJumpConfig{std::move(target_page_id)}; | 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,其余非空值必须是合法的寄存器地址对象 | // binding 允许为 null,其余非空值必须是合法的寄存器地址对象 | ||||
| if (binding.isNull()) | if (binding.isNull()) | ||||
| @@ -461,6 +461,10 @@ HmiControl HmiEditorService::makeControl( | |||||
| { | { | ||||
| control.pageJump = HmiPageJumpConfig{}; | control.pageJump = HmiPageJumpConfig{}; | ||||
| } | } | ||||
| if (descriptor.type == HmiControlType::ProgressBar) | |||||
| { | |||||
| control.progressBar = HmiProgressBarConfig{}; | |||||
| } | |||||
| const int offset = static_cast<int>(page.controls.size()) * 16; | const int offset = static_cast<int>(page.controls.size()) * 16; | ||||
| control.bounds.x = std::min(20 + offset, page.width - control.bounds.width); | control.bounds.x = std::min(20 + offset, page.width - control.bounds.width); | ||||
| control.bounds.y = std::min(20 + offset, page.height - control.bounds.height); | control.bounds.y = std::min(20 + offset, page.height - control.bounds.height); | ||||
| @@ -180,6 +180,48 @@ public: | |||||
| textWithValue()); | textWithValue()); | ||||
| break; | 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(QColor(QStringLiteral("#20342a"))); | |||||
| painter->drawText(rect.adjusted(6, 0, -6, 0), | |||||
| Qt::AlignCenter, | |||||
| display_text); | |||||
| break; | |||||
| } | |||||
| case HmiControlType::PageJump: | case HmiControlType::PageJump: | ||||
| { | { | ||||
| const QColor fill = runtime_active_ && page_hovered_ | const QColor fill = runtime_active_ && page_hovered_ | ||||
| @@ -279,6 +279,8 @@ void MainWindow::configureActions() | |||||
| [this] { addHmiControl(HmiControlType::NumericDisplay); }); | [this] { addHmiControl(HmiControlType::NumericDisplay); }); | ||||
| connect(ui_->addNumericInputAction, &QAction::triggered, this, | connect(ui_->addNumericInputAction, &QAction::triggered, this, | ||||
| [this] { addHmiControl(HmiControlType::NumericInput); }); | [this] { addHmiControl(HmiControlType::NumericInput); }); | ||||
| connect(ui_->addProgressBarAction, &QAction::triggered, this, | |||||
| [this] { addHmiControl(HmiControlType::ProgressBar); }); | |||||
| connect(ui_->addLabelAction, &QAction::triggered, this, | connect(ui_->addLabelAction, &QAction::triggered, this, | ||||
| [this] { addHmiControl(HmiControlType::Label); }); | [this] { addHmiControl(HmiControlType::Label); }); | ||||
| connect(ui_->addPageJumpAction, &QAction::triggered, this, | connect(ui_->addPageJumpAction, &QAction::triggered, this, | ||||
| @@ -576,6 +578,8 @@ void MainWindow::configureAppearance() | |||||
| style()->standardIcon(QStyle::SP_FileDialogInfoView)); | style()->standardIcon(QStyle::SP_FileDialogInfoView)); | ||||
| ui_->addNumericInputAction->setIcon( | ui_->addNumericInputAction->setIcon( | ||||
| style()->standardIcon(QStyle::SP_FileDialogContentsView)); | style()->standardIcon(QStyle::SP_FileDialogContentsView)); | ||||
| ui_->addProgressBarAction->setIcon( | |||||
| style()->standardIcon(QStyle::SP_MediaSeekForward)); | |||||
| ui_->addLabelAction->setIcon( | ui_->addLabelAction->setIcon( | ||||
| style()->standardIcon(QStyle::SP_FileDialogInfoView)); | style()->standardIcon(QStyle::SP_FileDialogInfoView)); | ||||
| ui_->addPageJumpAction->setIcon( | ui_->addPageJumpAction->setIcon( | ||||
| @@ -1189,6 +1193,7 @@ void MainWindow::updateModeUi(const QString &message) | |||||
| ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing); | ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing); | ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addNumericInputAction->setEnabled(policy.allowsProjectEditing); | ui_->addNumericInputAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addProgressBarAction->setEnabled(policy.allowsProjectEditing); | |||||
| ui_->addLabelAction->setEnabled(policy.allowsProjectEditing); | ui_->addLabelAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing); | ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing); | ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing); | ||||
| @@ -303,6 +303,7 @@ | |||||
| <addaction name="addIndicatorAction"/> | <addaction name="addIndicatorAction"/> | ||||
| <addaction name="addNumericDisplayAction"/> | <addaction name="addNumericDisplayAction"/> | ||||
| <addaction name="addNumericInputAction"/> | <addaction name="addNumericInputAction"/> | ||||
| <addaction name="addProgressBarAction"/> | |||||
| <addaction name="addLabelAction"/> | <addaction name="addLabelAction"/> | ||||
| <addaction name="addPageJumpAction"/> | <addaction name="addPageJumpAction"/> | ||||
| <addaction name="addAlarmListAction"/> | <addaction name="addAlarmListAction"/> | ||||
| @@ -324,6 +325,7 @@ | |||||
| </attribute> | </attribute> | ||||
| <addaction name="addRungAction"/> | <addaction name="addRungAction"/> | ||||
| <addaction name="parallelInsertAction"/> | <addaction name="parallelInsertAction"/> | ||||
| <addaction name="deleteLogicAction"/> | |||||
| <addaction name="separator"/> | <addaction name="separator"/> | ||||
| <addaction name="addNormallyOpenAction"/> | <addaction name="addNormallyOpenAction"/> | ||||
| <addaction name="addNormallyClosedAction"/> | <addaction name="addNormallyClosedAction"/> | ||||
| @@ -342,7 +344,6 @@ | |||||
| <addaction name="addSubAction"/> | <addaction name="addSubAction"/> | ||||
| <addaction name="addCompareAction"/> | <addaction name="addCompareAction"/> | ||||
| <addaction name="editRungCommentAction"/> | <addaction name="editRungCommentAction"/> | ||||
| <addaction name="deleteLogicAction"/> | |||||
| </widget> | </widget> | ||||
| <widget class="QDockWidget" name="projectDock"> | <widget class="QDockWidget" name="projectDock"> | ||||
| <property name="minimumSize"> | <property name="minimumSize"> | ||||
| @@ -632,7 +633,55 @@ | |||||
| </item> | </item> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item row="10" column="0" colspan="2"> | |||||
| <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" colspan="2"> | |||||
| <widget class="QPushButton" name="applyPropertiesButton"> | <widget class="QPushButton" name="applyPropertiesButton"> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>应用属性</string> | <string>应用属性</string> | ||||
| @@ -928,6 +977,14 @@ | |||||
| <string>添加数值输入控件</string> | <string>添加数值输入控件</string> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="addProgressBarAction"> | |||||
| <property name="text"> | |||||
| <string>进度条</string> | |||||
| </property> | |||||
| <property name="toolTip"> | |||||
| <string>添加进度条控件</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="addLabelAction"> | <action name="addLabelAction"> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>文本</string> | <string>文本</string> | ||||
| @@ -1098,7 +1155,7 @@ | |||||
| </action> | </action> | ||||
| <action name="deleteLogicAction"> | <action name="deleteLogicAction"> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>删除</string> | |||||
| <string>删除节点/网络</string> | |||||
| </property> | </property> | ||||
| <property name="toolTip"> | <property name="toolTip"> | ||||
| <string>删除选中的逻辑节点或网络</string> | <string>删除选中的逻辑节点或网络</string> | ||||
| @@ -9,6 +9,7 @@ | |||||
| #include "services/project_service.h" | #include "services/project_service.h" | ||||
| #include "ui_main_window.h" | #include "ui_main_window.h" | ||||
| #include <QCheckBox> | |||||
| #include <QComboBox> | #include <QComboBox> | ||||
| #include <QLabel> | #include <QLabel> | ||||
| #include <QPushButton> | #include <QPushButton> | ||||
| @@ -155,6 +156,9 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||||
| static_cast<QWidget *>(ui_.bindingIndexSpinBox), | static_cast<QWidget *>(ui_.bindingIndexSpinBox), | ||||
| static_cast<QWidget *>(ui_.targetPageComboBox), | static_cast<QWidget *>(ui_.targetPageComboBox), | ||||
| static_cast<QWidget *>(ui_.buttonOperationComboBox), | static_cast<QWidget *>(ui_.buttonOperationComboBox), | ||||
| static_cast<QWidget *>(ui_.progressMinimumSpinBox), | |||||
| static_cast<QWidget *>(ui_.progressMaximumSpinBox), | |||||
| static_cast<QWidget *>(ui_.progressShowValueCheckBox), | |||||
| static_cast<QWidget *>(ui_.applyPropertiesButton)}) | static_cast<QWidget *>(ui_.applyPropertiesButton)}) | ||||
| { | { | ||||
| widget->setEnabled(has_control); | widget->setEnabled(has_control); | ||||
| @@ -170,6 +174,12 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||||
| ui_.targetPageComboBox->setVisible(false); | ui_.targetPageComboBox->setVisible(false); | ||||
| ui_.buttonOperationLabel->setVisible(false); | ui_.buttonOperationLabel->setVisible(false); | ||||
| ui_.buttonOperationComboBox->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); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -215,6 +225,22 @@ void PropertyPanelController::showControlProperties(const std::string &control_i | |||||
| ui_.targetPageComboBox->setCurrentIndex( | ui_.targetPageComboBox->setCurrentIndex( | ||||
| ui_.targetPageComboBox->findData(fromUtf8(target_id))); | 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); | |||||
| } | } | ||||
| void PropertyPanelController::showLogicNodeProperties(const std::string &node_id) | void PropertyPanelController::showLogicNodeProperties(const std::string &node_id) | ||||
| @@ -470,6 +496,13 @@ void PropertyPanelController::applySelectedControlProperties() | |||||
| control.pageJump = HmiPageJumpConfig{ | control.pageJump = HmiPageJumpConfig{ | ||||
| toUtf8(ui_.targetPageComboBox->currentData().toString())}; | 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 HmiEditorResult result = hmi_editor_service_.updateControl( | const HmiEditorResult result = hmi_editor_service_.updateControl( | ||||
| current_page_id_(), selected_control_id_, control); | current_page_id_(), selected_control_id_, control); | ||||
| if (!result.succeeded) | if (!result.succeeded) | ||||
| @@ -477,9 +510,11 @@ void PropertyPanelController::applySelectedControlProperties() | |||||
| reportFailure(QObject::tr("应用属性"), result.message); | reportFailure(QObject::tr("应用属性"), result.message); | ||||
| return; | return; | ||||
| } | } | ||||
| selected_control_id_ = result.id; | |||||
| const std::string updated_control_id = result.id; | |||||
| hmi_editor_widget_->reloadPage(); | hmi_editor_widget_->reloadPage(); | ||||
| hmi_editor_widget_->selectControl(selected_control_id_); | |||||
| selected_control_id_ = updated_control_id; | |||||
| hmi_editor_widget_->selectControl(updated_control_id); | |||||
| showControlProperties(updated_control_id); | |||||
| refresh_project_ui_(); | refresh_project_ui_(); | ||||
| status_reporter_(QObject::tr("控件属性已更新"), 3000); | status_reporter_(QObject::tr("控件属性已更新"), 3000); | ||||
| } | } | ||||
| @@ -164,6 +164,45 @@ void testHmiControlRegistryCompleteness() | |||||
| "controls without bindings must not resolve a register area"); | "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() | Project makeValidProject() | ||||
| { | { | ||||
| // 构造包含 HMI 绑定和完整梯形图网络的最小合法工程作为测试基线 | // 构造包含 HMI 绑定和完整梯形图网络的最小合法工程作为测试基线 | ||||
| @@ -673,6 +712,7 @@ int main() | |||||
| testRegisterAddressParsing(); | testRegisterAddressParsing(); | ||||
| testRegisterRepositorySeparatesAreas(); | testRegisterRepositorySeparatesAreas(); | ||||
| testHmiControlRegistryCompleteness(); | testHmiControlRegistryCompleteness(); | ||||
| testProgressBarConfigurationBoundaries(); | |||||
| testLogicNodeConfigurationBoundaries(); | testLogicNodeConfigurationBoundaries(); | ||||
| testTimerAndCommentBoundaries(); | testTimerAndCommentBoundaries(); | ||||
| testTimerReferencesForRunning(); | testTimerReferencesForRunning(); | ||||
| @@ -50,12 +50,26 @@ void testControlEditing() | |||||
| const HmiEditorResult display = service.addControl( | const HmiEditorResult display = service.addControl( | ||||
| page_id, HmiControlType::NumericDisplay); | page_id, HmiControlType::NumericDisplay); | ||||
| const HmiEditorResult input = service.addControl(page_id, HmiControlType::NumericInput); | const HmiEditorResult input = service.addControl(page_id, HmiControlType::NumericInput); | ||||
| require(button.succeeded && indicator.succeeded && display.succeeded && input.succeeded, | |||||
| "four basic HMI controls must be added"); | |||||
| const HmiEditorResult progress = service.addControl( | |||||
| page_id, HmiControlType::ProgressBar); | |||||
| require(button.succeeded && indicator.succeeded && display.succeeded | |||||
| && input.succeeded && progress.succeeded, | |||||
| "basic HMI controls must be added"); | |||||
| const HmiPage *page = service.findPage(page_id); | const HmiPage *page = service.findPage(page_id); | ||||
| require(page != nullptr && page->controls.size() == 4, | |||||
| require(page != nullptr && page->controls.size() == 5, | |||||
| "all added controls must be kept in the page model"); | "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, | require(service.moveControl(page_id, button.id, {120, 80, 120, 40}).succeeded, | ||||
| "a valid control move must succeed"); | "a valid control move must succeed"); | ||||
| require(!service.moveControl(page_id, button.id, {790, 460, 120, 40}).succeeded, | require(!service.moveControl(page_id, button.id, {790, 460, 120, 40}).succeeded, | ||||
| @@ -80,6 +94,20 @@ void testControlEditing() | |||||
| "deleting a selected control must succeed"); | "deleting a selected control must succeed"); | ||||
| require(service.findControl(page_id, indicator.id) == nullptr, | require(service.findControl(page_id, indicator.id) == nullptr, | ||||
| "deleted controls must not remain in the model"); | "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() | void testRuntimeUsesRegisterRepository() | ||||
| @@ -147,6 +175,15 @@ void testRuntimeUsesRegisterRepository() | |||||
| const HmiRuntimeReadResult numeric_value = runtime_service.readControl(numeric_display); | const HmiRuntimeReadResult numeric_value = runtime_service.readControl(numeric_display); | ||||
| require(numeric_value.succeeded && numeric_value.word_value == -18, | require(numeric_value.succeeded && numeric_value.word_value == -18, | ||||
| "numeric display must read D values through the repository"); | "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 testPageLifecycleAndNavigation() | void testPageLifecycleAndNavigation() | ||||
| @@ -21,6 +21,7 @@ | |||||
| #include <QAction> | #include <QAction> | ||||
| #include <QApplication> | #include <QApplication> | ||||
| #include <QCheckBox> | |||||
| #include <QComboBox> | #include <QComboBox> | ||||
| #include <QDialog> | #include <QDialog> | ||||
| #include <QDockWidget> | #include <QDockWidget> | ||||
| @@ -350,6 +351,70 @@ void testRuntimeButtonMouseInteraction() | |||||
| "a disabled runtime button must not write its register"); | "a disabled runtime button must not write its register"); | ||||
| } | } | ||||
| void testRuntimeProgressBarRendering() | |||||
| { | |||||
| TestProjectStorage storage; | |||||
| ProjectService project_service(storage); | |||||
| HmiEditorService editor_service(project_service); | |||||
| VirtualRegisterRepository repository; | |||||
| HmiRuntimeService runtime_service(repository); | |||||
| AlarmService alarm_service(project_service, repository); | |||||
| const std::string page_id = editor_service.ensureDefaultPage().id; | |||||
| const HmiEditorResult progress_result = editor_service.addControl( | |||||
| page_id, HmiControlType::ProgressBar); | |||||
| require(progress_result.succeeded, | |||||
| "runtime progress test must create a ProgressBar control"); | |||||
| HmiControl progress = *editor_service.findControl( | |||||
| page_id, progress_result.id); | |||||
| progress.binding = RegisterAddress{RegisterArea::D, 0}; | |||||
| progress.progressBar = HmiProgressBarConfig{0, 100, true}; | |||||
| require(editor_service.updateControl( | |||||
| page_id, progress.id, progress).succeeded, | |||||
| "runtime progress test must configure a D value range"); | |||||
| require(repository.writeWord( | |||||
| {RegisterArea::D, 0}, static_cast<std::int16_t>(25)).succeeded, | |||||
| "runtime progress test must seed D0"); | |||||
| HmiEditorWidget view(editor_service, runtime_service, alarm_service); | |||||
| view.resize(900, 560); | |||||
| view.setPageId(page_id); | |||||
| view.setEditingEnabled(false); | |||||
| view.setRuntimeActive(true); | |||||
| view.refreshRuntimeValues(); | |||||
| view.show(); | |||||
| QApplication::processEvents(); | |||||
| const QPoint sample = view.mapFromScene(QPointF( | |||||
| progress.bounds.x + progress.bounds.width * 0.6, | |||||
| progress.bounds.y + 6.0)); | |||||
| const QColor background_color = renderedColorAt(view, sample); | |||||
| QGraphicsItem *progress_item = view.itemAt(sample); | |||||
| require(progress_item != nullptr | |||||
| && progress_item->acceptedMouseButtons() == Qt::NoButton, | |||||
| "runtime ProgressBar must be visible and read-only"); | |||||
| require(repository.writeWord( | |||||
| {RegisterArea::D, 0}, static_cast<std::int16_t>(75)).succeeded, | |||||
| "runtime progress test must update D0"); | |||||
| view.refreshRuntimeValues(); | |||||
| QApplication::processEvents(); | |||||
| const QColor fill_color = renderedColorAt(view, sample); | |||||
| require(fill_color != background_color, | |||||
| "ProgressBar fill must expand when the D value increases"); | |||||
| require(repository.writeWord( | |||||
| {RegisterArea::D, 0}, static_cast<std::int16_t>(200)).succeeded, | |||||
| "runtime progress test must accept an out-of-range source value"); | |||||
| view.refreshRuntimeValues(); | |||||
| QApplication::processEvents(); | |||||
| const QPoint near_end = view.mapFromScene(QPointF( | |||||
| progress.bounds.x + progress.bounds.width * 0.95, | |||||
| progress.bounds.y + 6.0)); | |||||
| require(renderedColorAt(view, near_end) == fill_color, | |||||
| "ProgressBar rendering must clamp values above its maximum"); | |||||
| } | |||||
| void testRuntimePageJumpDoesNotRequireRegisterWritePermission() | void testRuntimePageJumpDoesNotRequireRegisterWritePermission() | ||||
| { | { | ||||
| TestProjectStorage storage; | TestProjectStorage storage; | ||||
| @@ -533,6 +598,8 @@ void testModeActionsControlEditingAvailability() | |||||
| QAction *online_action = requiredChild<QAction>(window, "onlineModeAction"); | QAction *online_action = requiredChild<QAction>(window, "onlineModeAction"); | ||||
| QAction *add_button_action = requiredChild<QAction>(window, "addButtonAction"); | QAction *add_button_action = requiredChild<QAction>(window, "addButtonAction"); | ||||
| QAction *add_indicator_action = requiredChild<QAction>(window, "addIndicatorAction"); | QAction *add_indicator_action = requiredChild<QAction>(window, "addIndicatorAction"); | ||||
| QAction *add_progress_bar_action = requiredChild<QAction>( | |||||
| window, "addProgressBarAction"); | |||||
| QAction *delete_control_action = requiredChild<QAction>(window, "deleteControlAction"); | QAction *delete_control_action = requiredChild<QAction>(window, "deleteControlAction"); | ||||
| QAction *add_normally_open_action = requiredChild<QAction>( | QAction *add_normally_open_action = requiredChild<QAction>( | ||||
| window, "addNormallyOpenAction"); | window, "addNormallyOpenAction"); | ||||
| @@ -546,6 +613,16 @@ void testModeActionsControlEditingAvailability() | |||||
| QLineEdit *text_edit = requiredChild<QLineEdit>(window, "controlTextEdit"); | QLineEdit *text_edit = requiredChild<QLineEdit>(window, "controlTextEdit"); | ||||
| QComboBox *button_operation = requiredChild<QComboBox>( | QComboBox *button_operation = requiredChild<QComboBox>( | ||||
| window, "buttonOperationComboBox"); | window, "buttonOperationComboBox"); | ||||
| QComboBox *binding_area = requiredChild<QComboBox>( | |||||
| window, "bindingAreaComboBox"); | |||||
| QSpinBox *binding_index = requiredChild<QSpinBox>( | |||||
| window, "bindingIndexSpinBox"); | |||||
| QSpinBox *progress_minimum = requiredChild<QSpinBox>( | |||||
| window, "progressMinimumSpinBox"); | |||||
| QSpinBox *progress_maximum = requiredChild<QSpinBox>( | |||||
| window, "progressMaximumSpinBox"); | |||||
| QCheckBox *progress_show_value = requiredChild<QCheckBox>( | |||||
| window, "progressShowValueCheckBox"); | |||||
| QPushButton *apply_properties = requiredChild<QPushButton>( | QPushButton *apply_properties = requiredChild<QPushButton>( | ||||
| window, "applyPropertiesButton"); | window, "applyPropertiesButton"); | ||||
| HmiEditorWidget *hmi_editor = requiredChild<HmiEditorWidget>( | HmiEditorWidget *hmi_editor = requiredChild<HmiEditorWidget>( | ||||
| @@ -603,6 +680,37 @@ void testModeActionsControlEditingAvailability() | |||||
| require(editor_service.findPage(page_id)->controls.empty(), | require(editor_service.findPage(page_id)->controls.empty(), | ||||
| "deleting a selected control must update the HMI page model"); | "deleting a selected control must update the HMI page model"); | ||||
| add_progress_bar_action->trigger(); | |||||
| const HmiControl *default_progress = editor_service.findControl( | |||||
| page_id, "progress-bar-1"); | |||||
| require(default_progress != nullptr | |||||
| && default_progress->progressBar.has_value() | |||||
| && progress_minimum->isVisible() | |||||
| && progress_maximum->isVisible() | |||||
| && progress_show_value->isVisible() | |||||
| && progress_minimum->value() == 0 | |||||
| && progress_maximum->value() == 100 | |||||
| && progress_show_value->isChecked(), | |||||
| "ProgressBar properties must expose the registered defaults"); | |||||
| progress_minimum->setValue(-20); | |||||
| progress_maximum->setValue(80); | |||||
| progress_show_value->setChecked(false); | |||||
| binding_area->setCurrentIndex(binding_area->findData(1)); | |||||
| binding_index->setValue(4); | |||||
| apply_properties->click(); | |||||
| const HmiControl *configured_progress = editor_service.findControl( | |||||
| page_id, "progress-bar-1"); | |||||
| require(configured_progress != nullptr | |||||
| && configured_progress->binding | |||||
| == RegisterAddress{RegisterArea::D, 4} | |||||
| && configured_progress->progressBar->minimumValue == -20 | |||||
| && configured_progress->progressBar->maximumValue == 80 | |||||
| && !configured_progress->progressBar->showValue, | |||||
| "the property panel must update ProgressBar binding and range settings"); | |||||
| delete_control_action->trigger(); | |||||
| require(editor_service.findPage(page_id)->controls.empty(), | |||||
| "deleting a ProgressBar must remove it from the HMI page"); | |||||
| add_indicator_action->trigger(); | add_indicator_action->trigger(); | ||||
| HmiControl runtime_indicator = *editor_service.findControl(page_id, "indicator-1"); | HmiControl runtime_indicator = *editor_service.findControl(page_id, "indicator-1"); | ||||
| runtime_indicator.binding = RegisterAddress{RegisterArea::M, 3}; | runtime_indicator.binding = RegisterAddress{RegisterArea::M, 3}; | ||||
| @@ -663,6 +771,8 @@ void testModeActionsControlEditingAvailability() | |||||
| "properties dock must be disabled while running"); | "properties dock must be disabled while running"); | ||||
| require(!add_button_action->isEnabled(), | require(!add_button_action->isEnabled(), | ||||
| "HMI add controls must be disabled while running"); | "HMI add controls must be disabled while running"); | ||||
| require(!add_progress_bar_action->isEnabled(), | |||||
| "ProgressBar creation must be disabled while running"); | |||||
| require(!add_normally_open_action->isEnabled(), | require(!add_normally_open_action->isEnabled(), | ||||
| "logic add nodes must be disabled while running"); | "logic add nodes must be disabled while running"); | ||||
| require(runtime_tab->isVisible() && runtime_hmi->isVisible() | require(runtime_tab->isVisible() && runtime_hmi->isVisible() | ||||
| @@ -704,6 +814,8 @@ void testModeActionsControlEditingAvailability() | |||||
| "properties dock must be restored after returning to editing"); | "properties dock must be restored after returning to editing"); | ||||
| require(add_button_action->isEnabled(), | require(add_button_action->isEnabled(), | ||||
| "HMI add controls must be restored after returning to editing"); | "HMI add controls must be restored after returning to editing"); | ||||
| require(add_progress_bar_action->isEnabled(), | |||||
| "ProgressBar creation must be restored after returning to editing"); | |||||
| require(add_normally_open_action->isEnabled(), | require(add_normally_open_action->isEnabled(), | ||||
| "logic add nodes must be restored after returning to editing"); | "logic add nodes must be restored after returning to editing"); | ||||
| require(!runtime_tab->isVisible(), | require(!runtime_tab->isVisible(), | ||||
| @@ -1187,6 +1299,7 @@ int main(int argc, char *argv[]) | |||||
| try | try | ||||
| { | { | ||||
| testRuntimeButtonMouseInteraction(); | testRuntimeButtonMouseInteraction(); | ||||
| testRuntimeProgressBarRendering(); | |||||
| testRuntimePageJumpDoesNotRequireRegisterWritePermission(); | testRuntimePageJumpDoesNotRequireRegisterWritePermission(); | ||||
| testRuntimeAlarmListInteraction(); | testRuntimeAlarmListInteraction(); | ||||
| testModeActionsControlEditingAvailability(); | testModeActionsControlEditingAvailability(); | ||||
| @@ -80,6 +80,14 @@ Project makeExampleProject() | |||||
| alarm_list.bounds = {10, 200, 360, 180}; | alarm_list.bounds = {10, 200, 360, 180}; | ||||
| alarm_list.text = "Alarms"; | 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; | HmiPage page; | ||||
| page.id = "main-page"; | page.id = "main-page"; | ||||
| page.name = "Main"; | page.name = "Main"; | ||||
| @@ -90,6 +98,7 @@ Project makeExampleProject() | |||||
| page.controls.push_back(title_label); | page.controls.push_back(title_label); | ||||
| page.controls.push_back(settings_jump); | page.controls.push_back(settings_jump); | ||||
| page.controls.push_back(alarm_list); | page.controls.push_back(alarm_list); | ||||
| page.controls.push_back(progress_bar); | |||||
| HmiPage settings_page; | HmiPage settings_page; | ||||
| settings_page.id = "settings-page"; | settings_page.id = "settings-page"; | ||||
| @@ -379,6 +388,8 @@ void testExampleProjectRoundTrip() | |||||
| const QString invalid_operation_path = directory.filePath("invalid-operation.json"); | const QString invalid_operation_path = directory.filePath("invalid-operation.json"); | ||||
| const QString missing_initial_path = directory.filePath("missing-initial.json"); | const QString missing_initial_path = directory.filePath("missing-initial.json"); | ||||
| const QString missing_target_path = directory.filePath("missing-target.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_alarms_path = directory.filePath("missing-alarms.json"); | ||||
| const QString missing_register_comments_path = directory.filePath( | const QString missing_register_comments_path = directory.filePath( | ||||
| "missing-register-comments.json"); | "missing-register-comments.json"); | ||||
| @@ -411,7 +422,11 @@ void testExampleProjectRoundTrip() | |||||
| && saved_json.contains("\"operation\": \"add\"") | && saved_json.contains("\"operation\": \"add\"") | ||||
| && saved_json.contains("\"operation\": \"subtract\"") | && saved_json.contains("\"operation\": \"subtract\"") | ||||
| && saved_json.contains("\"comment\": \"启动条件与温度检查\"") | && saved_json.contains("\"comment\": \"启动条件与温度检查\"") | ||||
| && saved_json.contains("\"type\": \"alarmList\""), | |||||
| && 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"), | |||||
| "version 1.0 projects must persist pages and alarm definitions"); | "version 1.0 projects must persist pages and alarm definitions"); | ||||
| require(!saved_json.contains("\"stages\"") | require(!saved_json.contains("\"stages\"") | ||||
| && !saved_json.contains("\"branches\""), | && !saved_json.contains("\"branches\""), | ||||
| @@ -427,8 +442,8 @@ void testExampleProjectRoundTrip() | |||||
| require(project.hmiPages.size() == 2, "HMI page count must survive round trip"); | require(project.hmiPages.size() == 2, "HMI page count must survive round trip"); | ||||
| require(project.initialHmiPageId == "main-page", | require(project.initialHmiPageId == "main-page", | ||||
| "the initial HMI page id must survive round trip"); | "the initial HMI page id 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.size() == 8, | |||||
| "register, navigation, AlarmList and ProgressBar controls must survive round trip"); | |||||
| require(project.hmiPages.front().controls.front().binding->area() | require(project.hmiPages.front().controls.front().binding->area() | ||||
| == RegisterArea::M, | == RegisterArea::M, | ||||
| "HMI M binding must survive round trip"); | "HMI M binding must survive round trip"); | ||||
| @@ -450,6 +465,13 @@ void testExampleProjectRoundTrip() | |||||
| require(project.hmiPages.front().controls.at(6).type | require(project.hmiPages.front().controls.at(6).type | ||||
| == HmiControlType::AlarmList, | == HmiControlType::AlarmList, | ||||
| "AlarmList control type must survive round trip"); | "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 | require(project.alarmDefinitions.size() == 2 | ||||
| && project.alarmDefinitions.front().condition | && project.alarmDefinitions.front().condition | ||||
| == AlarmCondition::MOn | == AlarmCondition::MOn | ||||
| @@ -614,6 +636,24 @@ void testExampleProjectRoundTrip() | |||||
| require(!missing_result.succeeded | require(!missing_result.succeeded | ||||
| && missing_result.storageError == ProjectStorageError::MissingField, | && missing_result.storageError == ProjectStorageError::MissingField, | ||||
| "the 1.0 schema must require PageJump targetPageId"); | "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() | void testInvalidFiles() | ||||