diff --git a/app/integrated_platform.pro b/app/integrated_platform.pro index 275de21..000728d 100644 --- a/app/integrated_platform.pro +++ b/app/integrated_platform.pro @@ -18,6 +18,7 @@ SOURCES += \ src/main.cpp \ src/ui/main_window.cpp \ src/ui/alarm_configuration_dialog.cpp \ + src/ui/register_comment_dialog.cpp \ src/ui/plc_connection_dialog.cpp \ src/ui/free_monitor_widget.cpp \ src/ui/runtime_monitor_widget.cpp \ @@ -41,6 +42,7 @@ SOURCES += \ src/services/offline_simulation_service.cpp \ src/services/runtime_mode_service.cpp \ src/services/register_monitor_service.cpp \ + src/services/register_comment_service.cpp \ src/infrastructure/plc_register_repository.cpp \ src/infrastructure/plc_communication_error_classifier.cpp \ src/infrastructure/plc_communication_service.cpp \ @@ -51,6 +53,7 @@ SOURCES += \ HEADERS += \ src/ui/main_window.h \ src/ui/alarm_configuration_dialog.h \ + src/ui/register_comment_dialog.h \ src/ui/plc_connection_dialog.h \ src/ui/free_monitor_widget.h \ src/ui/runtime_monitor_widget.h \ @@ -75,6 +78,7 @@ HEADERS += \ src/services/offline_simulation_service.h \ src/services/runtime_mode_service.h \ src/services/register_monitor_service.h \ + src/services/register_comment_service.h \ src/services/plc_communication_gateway.h \ src/infrastructure/plc_register_repository.h \ src/infrastructure/plc_communication_error_classifier.h \ @@ -86,6 +90,7 @@ HEADERS += \ FORMS += \ src/ui/main_window.ui \ src/ui/alarm_configuration_dialog.ui \ + src/ui/register_comment_dialog.ui \ src/ui/plc_connection_dialog.ui \ src/ui/free_monitor_widget.ui \ src/ui/runtime_monitor_widget.ui diff --git a/app/src/main.cpp b/app/src/main.cpp index 33c4b57..7d5c12e 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -22,6 +22,7 @@ #include "services/project_service.h" #include "services/runtime_mode_service.h" #include "services/register_monitor_service.h" +#include "services/register_comment_service.h" #include "ui/main_window.h" int main(int argc, char *argv[]) @@ -44,6 +45,7 @@ int main(int argc, char *argv[]) HmiRuntimeService hmi_runtime_service(active_register_repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, active_register_repository); + RegisterCommentService register_comment_service(project_service); RegisterMonitorService register_monitor_service(active_register_repository); OfflineSimulationService offline_simulation_service(virtual_register_repository); RuntimeModeService runtime_mode_service( @@ -62,6 +64,7 @@ int main(int argc, char *argv[]) alarm_editor_service, alarm_service, hmi_navigation_service, + register_comment_service, register_monitor_service); main_window.show(); diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index fa5b56b..dec1a2e 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -17,9 +17,9 @@ constexpr qreal kSceneMargin = 28.0; constexpr qreal kRailInset = 40.0; constexpr qreal kMinimumSceneWidth = 980.0; constexpr qreal kCellWidth = 128.0; -constexpr qreal kCellHeight = 86.0; +constexpr qreal kCellHeight = 120.0; constexpr qreal kNodeTerminalX = 50.0; -constexpr qreal kRungHeaderHeight = 34.0; +constexpr qreal kRungHeaderHeight = 52.0; constexpr qreal kRungGap = 18.0; constexpr qreal kLadderLineWidth = 1.8; constexpr int kMinimumLogicColumns = 7; @@ -48,6 +48,29 @@ QString registerAddressText(const RegisterAddress &address) return QString::fromStdString(address.toString()); } +QString timerAddressText(const TimerAddress &address) +{ + return QString::fromStdString(address.toString()); +} + +void drawRegisterComment( + QPainter *painter, const QString &comment, qreal top = 22.0) +{ + if (comment.isEmpty()) + { + return; + } + QFont font = painter->font(); + font.setPointSizeF(8.0); + painter->setFont(font); + const QString visible_comment = painter->fontMetrics().elidedText( + comment, Qt::ElideRight, static_cast(kCellWidth - 8.0)); + painter->drawText( + QRectF(-kCellWidth / 2.0, top, kCellWidth, 18), + Qt::AlignCenter, + visible_comment); +} + QString comparisonText(ComparisonOperator comparison) { switch (comparison) @@ -76,18 +99,37 @@ QString nodeToolTip(const LogicNodeConfig &config) : LogicEditorWidget::tr("常闭")) .arg(registerAddressText(value.address)); } + else if constexpr (std::is_same_v) + { + return LogicEditorWidget::tr("%1沿触点:%2") + .arg(value.mode == EdgeMode::Rising + ? LogicEditorWidget::tr("上升") + : LogicEditorWidget::tr("下降")) + .arg(registerAddressText(value.address)); + } + else if constexpr (std::is_same_v) + { + return LogicEditorWidget::tr("T 触点:%1") + .arg(timerAddressText(value.address)); + } else if constexpr (std::is_same_v) { return LogicEditorWidget::tr("输出线圈:%1") .arg(registerAddressText(value.address)); } - else + else if constexpr (std::is_same_v) { return LogicEditorWidget::tr("比较条件:%1 %2 %3") .arg(registerAddressText(value.address)) .arg(comparisonText(value.comparison)) .arg(value.value); } + else + { + return LogicEditorWidget::tr("TON:%1,预设 %2 ms") + .arg(timerAddressText(value.address)) + .arg(value.presetMs); + } }, config); } @@ -187,11 +229,13 @@ public: const LogicNode &node, const std::string &rung_id, const QPointF ¢er, + const QString ®ister_comment, bool active, bool faulted) : node_id_(node.id), rung_id_(rung_id), config_(node.config), + register_comment_(register_comment), configured_(node.configured), active_(active), faulted_(faulted) @@ -199,8 +243,14 @@ public: setPos(center); setFlag(ItemIsSelectable, true); setZValue(3.0); - setToolTip(configured_ ? nodeToolTip(config_) - : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数")); + QString tool_tip = configured_ + ? nodeToolTip(config_) + : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数"); + if (!register_comment_.isEmpty()) + { + tool_tip += LogicEditorWidget::tr("\n注释:%1").arg(register_comment_); + } + setToolTip(tool_tip); } QRectF boundingRect() const override @@ -241,6 +291,41 @@ public: QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), Qt::AlignCenter, configured_ ? registerAddressText(contact->address) : tr("< M 地址 >")); + drawRegisterComment(painter, register_comment_); + } + else if (const auto *edge = std::get_if(&config_)) + { + painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white); + painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0)); + painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0)); + painter->drawLine(QPointF(-18, -15), QPointF(-18, 15)); + painter->drawLine(QPointF(18, -15), QPointF(18, 15)); + painter->drawText( + QRectF(-14, -12, 28, 24), + Qt::AlignCenter, + edge->mode == EdgeMode::Rising ? QStringLiteral("P") : QStringLiteral("N")); + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? registerAddressText(edge->address) : tr("< M 地址 >")); + drawRegisterComment(painter, register_comment_); + } + else if (const auto *timer_contact = + std::get_if(&config_)) + { + painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white); + painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0)); + painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0)); + painter->drawLine(QPointF(-18, -15), QPointF(-18, 15)); + painter->drawLine(QPointF(18, -15), QPointF(18, 15)); + if (timer_contact->mode == ContactMode::NormallyClosed) + { + painter->drawLine(QPointF(-23, 18), QPointF(23, -18)); + } + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >")); } else if (const auto *coil = std::get_if(&config_)) { @@ -266,6 +351,7 @@ public: QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), Qt::AlignCenter, configured_ ? registerAddressText(coil->address) : tr("< M 地址 >")); + drawRegisterComment(painter, register_comment_); } else if (const auto *comparison = std::get_if(&config_)) { @@ -285,6 +371,25 @@ public: QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18), Qt::AlignCenter, configured_ ? QString::number(comparison->value) : tr("< 常量 >")); + drawRegisterComment(painter, register_comment_, 40.0); + } + else if (const auto *ton = std::get_if(&config_)) + { + const QRectF box(-47, -18, 94, 36); + painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white); + painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0)); + painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0)); + painter->drawRect(box); + painter->drawText(box, Qt::AlignCenter, QStringLiteral("TON")); + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? timerAddressText(ton->address) : tr("< T 地址 >")); + painter->drawText( + QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs) + : tr("< 预设时间 >")); } } @@ -295,6 +400,7 @@ private: std::string node_id_; std::string rung_id_; LogicNodeConfig config_; + QString register_comment_; bool configured_ = true; bool active_ = false; bool faulted_ = false; @@ -312,6 +418,7 @@ public: qreal cursor_left) : rung_id_(rung.id), name_(QString::fromStdString(rung.name)), + comment_(QString::fromStdString(rung.comment)), number_(number), height_(height), width_(width), @@ -321,6 +428,10 @@ public: setPos(0, top); setFlag(ItemIsSelectable, true); setZValue(-2.0); + if (!comment_.isEmpty()) + { + setToolTip(comment_); + } } QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; } @@ -351,6 +462,16 @@ public: QRectF(kSceneMargin + 8, 5, 320, 22), Qt::AlignLeft | Qt::AlignVCenter, title); + if (!comment_.isEmpty()) + { + painter->setPen(QColor(QStringLiteral("#7b8790"))); + const QString visible_comment = painter->fontMetrics().elidedText( + comment_, Qt::ElideRight, static_cast(width_ - 16.0)); + painter->drawText( + QRectF(kSceneMargin + 8, 24, width_ - 16, 22), + Qt::AlignLeft | Qt::AlignVCenter, + visible_comment); + } painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1)); painter->drawLine( QPointF(kSceneMargin + 8, height_ - 1), @@ -362,6 +483,7 @@ public: private: std::string rung_id_; QString name_; + QString comment_; int number_ = 0; qreal height_ = 0.0; qreal width_ = 0.0; @@ -373,6 +495,7 @@ namespace { RenderResult renderExpression( QGraphicsScene &scene, + const LogicEditorService &editor_service, const ConditionExpression &expression, const std::string &rung_id, const QPointF &top_left, @@ -399,6 +522,15 @@ RenderResult renderExpression( *expression.node, rung_id, center, + [&editor_service, &expression] + { + const std::optional address = + registerAddressForLogicNode(expression.node->config); + return address.has_value() + ? QString::fromStdString( + editor_service.registerCommentFor(*address)) + : QString{}; + }(), node_active, expression.node->id == fault_node_id)); return { @@ -416,6 +548,7 @@ RenderResult renderExpression( const ExpressionMetrics child_metrics = measureExpression(expression.children[index]); const RenderResult current = renderExpression( scene, + editor_service, expression.children[index], rung_id, QPointF(x, top_left.y()), @@ -445,6 +578,7 @@ RenderResult renderExpression( const ExpressionMetrics child_metrics = measureExpression(child); branches.push_back(renderExpression( scene, + editor_service, child, rung_id, QPointF(top_left.x(), y), @@ -588,6 +722,7 @@ void LogicEditorWidget::reloadLogic() { const RenderResult rendered = renderExpression( *scene_, + editor_service_, *rung.condition, rung.id, QPointF(left_rail_x, grid_top), @@ -620,7 +755,24 @@ void LogicEditorWidget::reloadLogic() *rung.output, rung.id, QPointF(output_left + kCellWidth / 2.0, main_y), - rung_active, + [&rung, this] + { + const std::optional address = + registerAddressForLogicNode(rung.output->config); + return address.has_value() + ? QString::fromStdString( + editor_service_.registerCommentFor(*address)) + : QString{}; + }(), + [&rung, this, rung_active] + { + return std::holds_alternative(rung.output->config) + ? traceValue( + trace_, + &LogicTraceSnapshot::nodeValues, + rung.output->id) + : rung_active; + }(), rung.output->id == fault_node_id_)); } else diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index 05c7621..9a2c95c 100644 --- a/app/src/ui/main_window.cpp +++ b/app/src/ui/main_window.cpp @@ -2,6 +2,7 @@ #include "hmi_editor_widget.h" #include "alarm_configuration_dialog.h" +#include "register_comment_dialog.h" #include "logic_editor_widget.h" #include "plc_connection_dialog.h" #include "runtime_monitor_widget.h" @@ -15,6 +16,7 @@ #include "services/project_service.h" #include "services/runtime_mode_service.h" #include "services/register_monitor_service.h" +#include "services/register_comment_service.h" #include "ui_main_window.h" #include @@ -200,6 +202,7 @@ MainWindow::MainWindow( HmiRuntimeService &hmi_runtime_service, AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, + RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent) : QMainWindow(parent), @@ -211,6 +214,7 @@ MainWindow::MainWindow( hmi_runtime_service_(hmi_runtime_service), alarm_editor_service_(alarm_editor_service), alarm_service_(alarm_service), + register_comment_service_(register_comment_service), register_monitor_service_(register_monitor_service), owned_hmi_navigation_service_( std::make_unique(project_service)), @@ -228,6 +232,7 @@ MainWindow::MainWindow( AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, HmiNavigationService &hmi_navigation_service, + RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent) : QMainWindow(parent), @@ -239,6 +244,7 @@ MainWindow::MainWindow( hmi_runtime_service_(hmi_runtime_service), alarm_editor_service_(alarm_editor_service), alarm_service_(alarm_service), + register_comment_service_(register_comment_service), register_monitor_service_(register_monitor_service), hmi_navigation_service_(&hmi_navigation_service) { @@ -253,6 +259,7 @@ void MainWindow::initializeUi() configurePropertyEditor(); configurePlcConnection(); configureAlarms(); + configureRegisterComments(); configureHmiEditor(); configureLogicEditor(); configureRuntimeMonitor(); @@ -332,6 +339,9 @@ void MainWindow::configureActions() QMenu *parallel_menu = new QMenu(this); QAction *parallel_open = parallel_menu->addAction(tr("并联常开触点")); QAction *parallel_closed = parallel_menu->addAction(tr("并联常闭触点")); + QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点")); + QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点")); + QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点")); QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件")); connect(parallel_open, &QAction::triggered, this, @@ -349,6 +359,27 @@ void MainWindow::configureActions() RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyClosed}); }); + connect(parallel_rising, &QAction::triggered, + this, + [this] + { + addLogicParallelBranch(EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising}); + }); + connect(parallel_falling, &QAction::triggered, + this, + [this] + { + addLogicParallelBranch(EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling}); + }); + connect(parallel_timer, &QAction::triggered, + this, + [this] + { + addLogicParallelBranch(TimerContactNodeConfig{ + TimerAddress{0}, ContactMode::NormallyOpen}); + }); connect(parallel_compare, &QAction::triggered, this, [this] @@ -380,6 +411,27 @@ void MainWindow::configureActions() RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyClosed}); }); + connect(ui_->addRisingEdgeAction, &QAction::triggered, + this, + [this] + { + addLogicCondition(EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising}); + }); + connect(ui_->addFallingEdgeAction, &QAction::triggered, + this, + [this] + { + addLogicCondition(EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling}); + }); + connect(ui_->addTimerContactAction, &QAction::triggered, + this, + [this] + { + addLogicCondition(TimerContactNodeConfig{ + TimerAddress{0}, ContactMode::NormallyOpen}); + }); connect(ui_->addNormalCoilAction, &QAction::triggered, this, [this] @@ -404,6 +456,12 @@ void MainWindow::configureActions() RegisterAddress{RegisterArea::M, 0}, CoilMode::Reset}); }); + connect(ui_->addTonAction, &QAction::triggered, + this, + [this] + { + setLogicOutput(TonNodeConfig{TimerAddress{0}, 1000}); + }); connect(ui_->addCompareAction, &QAction::triggered, this, [this] @@ -415,6 +473,8 @@ void MainWindow::configureActions() }); connect(ui_->deleteLogicAction, &QAction::triggered, this, &MainWindow::deleteSelectedLogicObject); + connect(ui_->editRungCommentAction, &QAction::triggered, + this, &MainWindow::editSelectedRungComment); connect(ui_->editorTabWidget, &QTabWidget::currentChanged, this, @@ -481,10 +541,15 @@ void MainWindow::configureAppearance() ui_->parallelInsertAction->setIcon(style()->standardIcon(QStyle::SP_ArrowDown)); ui_->addNormallyOpenAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); ui_->addNormallyClosedAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); + ui_->addRisingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); + ui_->addFallingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); + ui_->addTimerContactAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); ui_->addNormalCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton)); ui_->addSetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogYesButton)); ui_->addResetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogNoButton)); + ui_->addTonAction->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); ui_->addCompareAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogInfoView)); + ui_->editRungCommentAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView)); ui_->deleteLogicAction->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); ui_->editorTabWidget->setTabIcon(0, style()->standardIcon(QStyle::SP_DesktopIcon)); ui_->editorTabWidget->setTabIcon(1, style()->standardIcon(QStyle::SP_FileDialogListView)); @@ -755,7 +820,22 @@ void MainWindow::configureAlarms() AlarmConfigurationDialog dialog(alarm_editor_service_, this); dialog.exec(); refreshProjectUi(); - }); + }); +} + +void MainWindow::configureRegisterComments() +{ + connect( + ui_->configureRegisterCommentsAction, + &QAction::triggered, + this, + [this] + { + RegisterCommentDialog dialog(register_comment_service_, this); + dialog.exec(); + logic_editor_widget_->reloadLogic(); + refreshProjectUi(); + }); } void MainWindow::refreshProjectUi() @@ -1300,6 +1380,7 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) static_cast(ui_->logicModeComboBox), static_cast(ui_->logicComparisonComboBox), static_cast(ui_->logicValueSpinBox), + static_cast(ui_->logicPresetSpinBox), static_cast(ui_->applyLogicPropertiesButton)}) { widget->setEnabled(has_node); @@ -1312,11 +1393,15 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) ui_->logicNodeTypeLabel->clear(); ui_->logicAddressAreaLabel->clear(); ui_->logicModeComboBox->clear(); + ui_->logicPresetSpinBox->setValue(TonNodeConfig::kMinimumPresetMs); return; } ui_->logicNodeIdEdit->setText(fromUtf8(node->id)); ui_->logicModeComboBox->clear(); + ui_->logicComparisonComboBox->setEnabled(false); + ui_->logicValueSpinBox->setEnabled(false); + ui_->logicPresetSpinBox->setEnabled(false); if (const auto *contact = std::get_if(&node->config)) { ui_->logicNodeTypeLabel->setText(tr("触点")); @@ -1327,10 +1412,36 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) ui_->logicModeComboBox->addItem( tr("常闭"), static_cast(ContactMode::NormallyClosed)); ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData(static_cast(contact->mode))); + ui_->logicModeComboBox->findData(static_cast(contact->mode))); + ui_->logicModeComboBox->setEnabled(true); + } + else if (const auto *edge = std::get_if(&node->config)) + { + ui_->logicNodeTypeLabel->setText(tr("边沿触点")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("M")); + ui_->logicAddressSpinBox->setValue(edge->address.index()); + ui_->logicModeComboBox->addItem( + tr("上升沿"), static_cast(EdgeMode::Rising)); + ui_->logicModeComboBox->addItem( + tr("下降沿"), static_cast(EdgeMode::Falling)); + ui_->logicModeComboBox->setCurrentIndex( + ui_->logicModeComboBox->findData(static_cast(edge->mode))); + ui_->logicModeComboBox->setEnabled(true); + } + else if (const auto *timer_contact = + std::get_if(&node->config)) + { + ui_->logicNodeTypeLabel->setText(tr("T 触点")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); + ui_->logicAddressSpinBox->setValue(timer_contact->address.index()); + ui_->logicModeComboBox->addItem( + tr("常开"), static_cast(ContactMode::NormallyOpen)); + ui_->logicModeComboBox->addItem( + tr("常闭"), static_cast(ContactMode::NormallyClosed)); + ui_->logicModeComboBox->setCurrentIndex( + ui_->logicModeComboBox->findData( + static_cast(timer_contact->mode))); ui_->logicModeComboBox->setEnabled(true); - ui_->logicComparisonComboBox->setEnabled(false); - ui_->logicValueSpinBox->setEnabled(false); } else if (const auto *coil = std::get_if(&node->config)) { @@ -1344,25 +1455,33 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) ui_->logicModeComboBox->addItem( tr("复位"), static_cast(CoilMode::Reset)); ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData(static_cast(coil->mode))); + ui_->logicModeComboBox->findData(static_cast(coil->mode))); ui_->logicModeComboBox->setEnabled(true); - ui_->logicComparisonComboBox->setEnabled(false); - ui_->logicValueSpinBox->setEnabled(false); } - else + else if (const auto *compare = std::get_if(&node->config)) { - const auto &compare = std::get(node->config); ui_->logicNodeTypeLabel->setText(tr("D 值比较")); ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); - ui_->logicAddressSpinBox->setValue(compare.address.index()); + ui_->logicAddressSpinBox->setValue(compare->address.index()); ui_->logicModeComboBox->addItem(tr("不适用")); ui_->logicModeComboBox->setEnabled(false); ui_->logicComparisonComboBox->setEnabled(true); ui_->logicComparisonComboBox->setCurrentIndex( ui_->logicComparisonComboBox->findData( - static_cast(compare.comparison))); + static_cast(compare->comparison))); ui_->logicValueSpinBox->setEnabled(true); - ui_->logicValueSpinBox->setValue(compare.value); + ui_->logicValueSpinBox->setValue(compare->value); + } + else + { + const auto &ton = std::get(node->config); + ui_->logicNodeTypeLabel->setText(tr("TON")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); + ui_->logicAddressSpinBox->setValue(ton.address.index()); + ui_->logicModeComboBox->addItem(tr("非保持接通延时")); + ui_->logicModeComboBox->setEnabled(false); + ui_->logicPresetSpinBox->setEnabled(true); + ui_->logicPresetSpinBox->setValue(ton.presetMs); } if (!node->configured) { @@ -1500,13 +1619,13 @@ void MainWindow::setLogicOutput(const LogicNodeConfig &config) const LogicEditorResult result = logic_editor_widget_->setOutput(config); if (!result.succeeded) { - showProjectResult(tr("设置输出线圈"), fromUtf8(result.message), false); + showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false); return; } selected_logic_node_id_ = result.id; showLogicNodeProperties(result.id); refreshProjectUi(); - statusBar()->showMessage(tr("输出线圈已设置"), 3000); + statusBar()->showMessage(tr("逻辑输出已设置"), 3000); } void MainWindow::addLogicRung() @@ -1523,6 +1642,43 @@ void MainWindow::addLogicRung() statusBar()->showMessage(tr("已新建网络"), 3000); } +void MainWindow::editSelectedRungComment() +{ + const std::string rung_id = logic_editor_widget_->selectedRungId(); + if (rung_id.empty()) + { + showProjectResult(tr("网络注释"), tr("请先选择一个梯形图网络"), false); + return; + } + const LadderRung *rung = logic_editor_service_.findRung( + current_logic_id_, rung_id); + if (rung == nullptr) + { + return; + } + bool accepted = false; + const QString comment = QInputDialog::getMultiLineText( + this, + tr("网络注释"), + tr("说明"), + fromUtf8(rung->comment), + &accepted); + if (!accepted) + { + return; + } + const LogicEditorResult result = logic_editor_service_.updateRungComment( + current_logic_id_, rung_id, toUtf8(comment)); + if (!result.succeeded) + { + showProjectResult(tr("网络注释"), fromUtf8(result.message), false); + return; + } + logic_editor_widget_->reloadLogic(); + refreshProjectUi(); + statusBar()->showMessage(tr("网络注释已更新"), 3000); +} + void MainWindow::deleteSelectedLogicObject() { const LogicEditorResult result = logic_editor_widget_->deleteSelected(); @@ -1551,13 +1707,25 @@ void MainWindow::applySelectedLogicNodeProperties() RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, static_cast(ui_->logicModeComboBox->currentData().toInt())}; } + else if (std::holds_alternative(config)) + { + config = EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, + static_cast(ui_->logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = TimerContactNodeConfig{ + TimerAddress{ui_->logicAddressSpinBox->value()}, + static_cast(ui_->logicModeComboBox->currentData().toInt())}; + } else if (std::holds_alternative(config)) { config = CoilNodeConfig{ RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, static_cast(ui_->logicModeComboBox->currentData().toInt())}; } - else + else if (std::holds_alternative(config)) { config = CompareNodeConfig{ RegisterAddress{RegisterArea::D, ui_->logicAddressSpinBox->value()}, @@ -1565,6 +1733,12 @@ void MainWindow::applySelectedLogicNodeProperties() ui_->logicComparisonComboBox->currentData().toInt()), static_cast(ui_->logicValueSpinBox->value())}; } + else + { + config = TonNodeConfig{ + TimerAddress{ui_->logicAddressSpinBox->value()}, + ui_->logicPresetSpinBox->value()}; + } const LogicEditorResult result = logic_editor_service_.updateNodeConfig( current_logic_id_, selected_logic_node_id_, config); @@ -1775,13 +1949,19 @@ void MainWindow::updateModeUi(const QString &message) ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing); ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing); ui_->configureAlarmsAction->setEnabled(policy.allowsProjectEditing); + ui_->configureRegisterCommentsAction->setEnabled(policy.allowsProjectEditing); ui_->deleteControlAction->setEnabled(policy.allowsProjectEditing); ui_->addNormallyOpenAction->setEnabled(policy.allowsProjectEditing); ui_->addNormallyClosedAction->setEnabled(policy.allowsProjectEditing); + ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing); + ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing); + ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing); ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing); + ui_->addTonAction->setEnabled(policy.allowsProjectEditing); ui_->addCompareAction->setEnabled(policy.allowsProjectEditing); + ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing); ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing); ui_->newProjectAction->setEnabled(policy.allowsProjectEditing); ui_->saveProjectAction->setEnabled(policy.allowsProjectEditing); diff --git a/app/src/ui/main_window.h b/app/src/ui/main_window.h index 2c22c40..b6b286b 100644 --- a/app/src/ui/main_window.h +++ b/app/src/ui/main_window.h @@ -35,6 +35,7 @@ class HmiRuntimeService; class AlarmEditorService; class AlarmService; class HmiNavigationService; +class RegisterCommentService; class HmiEditorWidget; class LogicEditorService; class LogicEditorWidget; @@ -67,6 +68,7 @@ public: HmiRuntimeService &hmi_runtime_service, AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, + RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent = nullptr); MainWindow( @@ -78,6 +80,7 @@ public: AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, HmiNavigationService &hmi_navigation_service, + RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent = nullptr); @@ -101,6 +104,7 @@ private: void configurePropertyEditor(); void configurePlcConnection(); void configureAlarms(); + void configureRegisterComments(); // 刷新工程树和当前 HMI 页面信息 void refreshProjectUi(); void handleProjectTreeSelection(); @@ -130,6 +134,7 @@ private: void setLogicOutput(const LogicNodeConfig &config); // 新增一个梯形图网络 void addLogicRung(); + void editSelectedRungComment(); // 删除当前选中的逻辑节点或网络 void deleteSelectedLogicObject(); // 将逻辑属性表单内容应用到当前选中节点 @@ -185,6 +190,7 @@ private: HmiRuntimeService &hmi_runtime_service_; AlarmEditorService &alarm_editor_service_; AlarmService &alarm_service_; + RegisterCommentService ®ister_comment_service_; RegisterMonitorService ®ister_monitor_service_; std::unique_ptr owned_hmi_navigation_service_; HmiNavigationService *hmi_navigation_service_ = nullptr; diff --git a/app/src/ui/main_window.ui b/app/src/ui/main_window.ui index 44d54f2..5fe8a29 100644 --- a/app/src/ui/main_window.ui +++ b/app/src/ui/main_window.ui @@ -254,9 +254,10 @@ - - 视图(&V) - + + 视图(&V) + + @@ -326,10 +327,15 @@ + + + + + @@ -758,7 +764,27 @@ - + + + + 预设时间(ms) + + + + + + + 1 + + + 86400000 + + + 1000 + + + + 应用属性 @@ -848,6 +874,14 @@ 配置参数并连接真实 PLC + + + 软元件注释 + + + 维护工程级 M/D 地址注释 + + 断开 PLC @@ -960,6 +994,30 @@ 向当前网络添加常闭触点 + + + 上升沿 + + + 添加一个扫描周期的上升沿触点 + + + + + 下降沿 + + + 添加一个扫描周期的下降沿触点 + + + + + T 触点 + + + 添加离线仿真定时器触点 + + 线圈 @@ -984,6 +1042,14 @@ 设置当前网络的复位线圈 + + + TON + + + 设置非保持接通延时定时器输出 + + D 比较 @@ -992,6 +1058,14 @@ 向当前网络添加 D 值与常量比较 + + + 网络注释 + + + 编辑当前梯形图网络说明 + + 删除 diff --git a/app/src/ui/register_comment_dialog.cpp b/app/src/ui/register_comment_dialog.cpp new file mode 100644 index 0000000..3fad271 --- /dev/null +++ b/app/src/ui/register_comment_dialog.cpp @@ -0,0 +1,155 @@ +#include "register_comment_dialog.h" + +#include "services/register_comment_service.h" +#include "ui_register_comment_dialog.h" + +#include +#include +#include + +#include + +namespace { + +QString fromUtf8(const std::string &value) +{ + return QString::fromUtf8(value.data(), static_cast(value.size())); +} + +std::string toUtf8(const QString &value) +{ + const QByteArray bytes = value.toUtf8(); + return std::string(bytes.constData(), static_cast(bytes.size())); +} + +} // namespace + +RegisterCommentDialog::RegisterCommentDialog( + RegisterCommentService &service, + QWidget *parent) + : QDialog(parent), + ui_(std::make_unique()), + service_(service) +{ + ui_->setupUi(this); + ui_->commentTable->horizontalHeader()->setSectionResizeMode( + 0, QHeaderView::ResizeToContents); + ui_->commentTable->horizontalHeader()->setSectionResizeMode( + 1, QHeaderView::Stretch); + ui_->areaComboBox->setItemData(0, static_cast(RegisterArea::M)); + ui_->areaComboBox->setItemData(1, static_cast(RegisterArea::D)); + connect(ui_->commentTable, &QTableWidget::itemSelectionChanged, + this, [this] { loadSelectedComment(); }); + connect(ui_->saveButton, &QPushButton::clicked, + this, [this] { saveComment(); }); + connect(ui_->removeButton, &QPushButton::clicked, + this, [this] { removeComment(); }); + connect(ui_->buttonBox, &QDialogButtonBox::rejected, + this, &QDialog::reject); + reloadComments(); +} + +RegisterCommentDialog::~RegisterCommentDialog() = default; + +void RegisterCommentDialog::reloadComments( + const RegisterAddress *selected_address) +{ + const auto &comments = service_.comments(); + ui_->commentTable->setRowCount(static_cast(comments.size())); + int selected_row = -1; + for (std::size_t index = 0; index < comments.size(); ++index) + { + const RegisterComment &comment = comments[index]; + const int row = static_cast(index); + auto *address_item = new QTableWidgetItem( + QString::fromStdString(comment.address.toString())); + address_item->setData(Qt::UserRole, static_cast(comment.address.area())); + address_item->setData(Qt::UserRole + 1, comment.address.index()); + ui_->commentTable->setItem(row, 0, address_item); + ui_->commentTable->setItem(row, 1, new QTableWidgetItem(fromUtf8(comment.text))); + if (selected_address != nullptr && comment.address == *selected_address) + { + selected_row = row; + } + } + if (selected_row >= 0) + { + ui_->commentTable->selectRow(selected_row); + } + else + { + ui_->commentTable->clearSelection(); + ui_->removeButton->setEnabled(false); + } +} + +void RegisterCommentDialog::loadSelectedComment() +{ + const std::optional address = selectedAddress(); + const RegisterComment *comment = address.has_value() + ? service_.findComment(*address) : nullptr; + ui_->removeButton->setEnabled(comment != nullptr); + if (comment == nullptr) + { + return; + } + ui_->areaComboBox->setCurrentIndex( + comment->address.area() == RegisterArea::M ? 0 : 1); + ui_->indexSpinBox->setValue(comment->address.index()); + ui_->commentEdit->setText(fromUtf8(comment->text)); +} + +void RegisterCommentDialog::saveComment() +{ + const RegisterAddress address = addressFromInputs(); + const RegisterCommentResult result = service_.setComment( + address, toUtf8(ui_->commentEdit->text())); + if (!result.succeeded) + { + QMessageBox::warning(this, tr("保存注释"), fromUtf8(result.message)); + return; + } + reloadComments(&address); +} + +void RegisterCommentDialog::removeComment() +{ + const std::optional address = selectedAddress(); + if (!address.has_value()) + { + return; + } + if (QMessageBox::question( + this, tr("删除注释"), tr("确定删除当前软元件注释吗?")) + != QMessageBox::Yes) + { + return; + } + const RegisterCommentResult result = service_.removeComment(*address); + if (!result.succeeded) + { + QMessageBox::warning(this, tr("删除注释"), fromUtf8(result.message)); + return; + } + reloadComments(); +} + +RegisterAddress RegisterCommentDialog::addressFromInputs() const +{ + const RegisterArea area = static_cast( + ui_->areaComboBox->currentData().toInt()); + return RegisterAddress{area, ui_->indexSpinBox->value()}; +} + +std::optional RegisterCommentDialog::selectedAddress() const +{ + const int row = ui_->commentTable->currentRow(); + const QTableWidgetItem *item = row >= 0 ? ui_->commentTable->item(row, 0) : nullptr; + if (item == nullptr) + { + return std::nullopt; + } + return RegisterAddress{ + static_cast(item->data(Qt::UserRole).toInt()), + item->data(Qt::UserRole + 1).toInt()}; +} diff --git a/app/src/ui/register_comment_dialog.h b/app/src/ui/register_comment_dialog.h new file mode 100644 index 0000000..ede20fa --- /dev/null +++ b/app/src/ui/register_comment_dialog.h @@ -0,0 +1,34 @@ +#pragma once + +#include "domain/register_address.h" + +#include + +#include +#include + +namespace Ui { +class RegisterCommentDialog; +} + +class RegisterCommentService; + +class RegisterCommentDialog final : public QDialog +{ +public: + explicit RegisterCommentDialog( + RegisterCommentService &service, + QWidget *parent = nullptr); + ~RegisterCommentDialog() override; + +private: + void reloadComments(const RegisterAddress *selected_address = nullptr); + void loadSelectedComment(); + void saveComment(); + void removeComment(); + RegisterAddress addressFromInputs() const; + std::optional selectedAddress() const; + + std::unique_ptr ui_; + RegisterCommentService &service_; +}; diff --git a/app/src/ui/register_comment_dialog.ui b/app/src/ui/register_comment_dialog.ui new file mode 100644 index 0000000..0bc980b --- /dev/null +++ b/app/src/ui/register_comment_dialog.ui @@ -0,0 +1,40 @@ + + + RegisterCommentDialog + + 软元件注释 + 620400 + + + + QAbstractItemView::SelectRows + QAbstractItemView::SingleSelection + QAbstractItemView::NoEditTriggers + 2 + 地址 + 注释 + + + + + 地址区域 + MD + 地址 + 4000 + 注释 + 例如:启动按钮 + + + + + 保存 + 删除 + Qt::Horizontal4020 + QDialogButtonBox::Close + + + + + + + diff --git a/app/tests/main_window_tests.cpp b/app/tests/main_window_tests.cpp index b368c86..72db520 100644 --- a/app/tests/main_window_tests.cpp +++ b/app/tests/main_window_tests.cpp @@ -11,6 +11,7 @@ #include "services/project_service.h" #include "services/runtime_mode_service.h" #include "services/register_monitor_service.h" +#include "services/register_comment_service.h" #include "ui/hmi_editor_widget.h" #include "ui/free_monitor_widget.h" #include "ui/logic_editor_widget.h" @@ -29,9 +30,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -501,6 +504,7 @@ void testModeActionsControlEditingAvailability() HmiRuntimeService runtime_service(repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, repository); + RegisterCommentService register_comment_service(project_service); OfflineSimulationService simulation_service(repository); RuntimeModeService mode_service(project_service, simulation_service); RegisterMonitorService monitor_service(repository); @@ -512,6 +516,7 @@ void testModeActionsControlEditingAvailability() runtime_service, alarm_editor_service, alarm_service, + register_comment_service, monitor_service); window.resize(1000, 640); window.show(); @@ -711,6 +716,105 @@ void testModeActionsControlEditingAvailability() "rejected online running must restore the editing action"); } +void testEdgeTimerPropertyEditingAndParallelMenu() +{ + TestProjectStorage storage; + ProjectService project_service(storage); + HmiEditorService editor_service(project_service); + LogicEditorService logic_editor_service(project_service); + VirtualRegisterRepository repository; + HmiRuntimeService runtime_service(repository); + AlarmEditorService alarm_editor_service(project_service); + AlarmService alarm_service(project_service, repository); + RegisterCommentService register_comment_service(project_service); + OfflineSimulationService simulation_service(repository); + RuntimeModeService mode_service(project_service, simulation_service); + RegisterMonitorService monitor_service(repository); + MainWindow window( + mode_service, + project_service, + editor_service, + logic_editor_service, + runtime_service, + alarm_editor_service, + alarm_service, + register_comment_service, + monitor_service); + window.show(); + QApplication::processEvents(); + + QAction *parallel_action = requiredChild(window, "parallelInsertAction"); + require(parallel_action->menu() != nullptr, + "parallel insertion must expose its node type menu"); + QStringList parallel_labels; + for (QAction *action : parallel_action->menu()->actions()) + { + parallel_labels.push_back(action->text()); + } + require(parallel_labels.contains(QStringLiteral("并联上升沿触点")) + && parallel_labels.contains(QStringLiteral("并联下降沿触点")) + && parallel_labels.contains(QStringLiteral("并联 T 触点")), + "parallel insertion must include edge and T contact choices"); + + QAction *rising_action = requiredChild(window, "addRisingEdgeAction"); + QAction *falling_action = requiredChild(window, "addFallingEdgeAction"); + QAction *timer_contact_action = requiredChild( + window, "addTimerContactAction"); + QAction *ton_action = requiredChild(window, "addTonAction"); + requiredChild(window, "editRungCommentAction"); + requiredChild(window, "configureRegisterCommentsAction"); + QSpinBox *address = requiredChild(window, "logicAddressSpinBox"); + QSpinBox *preset = requiredChild(window, "logicPresetSpinBox"); + QComboBox *mode = requiredChild(window, "logicModeComboBox"); + QPushButton *apply = requiredChild( + window, "applyLogicPropertiesButton"); + const std::string logic_id = logic_editor_service.firstLogicId(); + + rising_action->trigger(); + address->setValue(8); + mode->setCurrentIndex(mode->findData(static_cast(EdgeMode::Falling))); + apply->click(); + const LogicNode *edge = logic_editor_service.findNode(logic_id, "edge-1"); + require(edge != nullptr && edge->configured + && std::get(edge->config).address.index() == 8 + && std::get(edge->config).mode + == EdgeMode::Falling, + "edge properties must update M address and edge mode"); + + timer_contact_action->trigger(); + address->setValue(9); + mode->setCurrentIndex(mode->findData( + static_cast(ContactMode::NormallyClosed))); + apply->click(); + const LogicNode *timer_contact = logic_editor_service.findNode( + logic_id, "timer-contact-1"); + require(timer_contact != nullptr && timer_contact->configured + && std::get(timer_contact->config).address + == TimerAddress{9} + && std::get(timer_contact->config).mode + == ContactMode::NormallyClosed, + "T contact properties must update T address and contact mode"); + + ton_action->trigger(); + require(preset->isEnabled() && !mode->isEnabled(), + "TON properties must enable PT and disable irrelevant mode editing"); + address->setValue(9); + preset->setValue(1234); + apply->click(); + const LogicNode *ton = logic_editor_service.findNode(logic_id, "ton-1"); + require(ton != nullptr && ton->configured + && std::get(ton->config).address == TimerAddress{9} + && std::get(ton->config).presetMs == 1234, + "TON properties must update T address and preset milliseconds"); + + falling_action->trigger(); + const LogicNode *falling = logic_editor_service.findNode(logic_id, "edge-2"); + require(falling != nullptr + && std::get(falling->config).mode + == EdgeMode::Falling, + "falling edge action must create a falling-edge condition"); +} + void testPlcConfigurationUsesDialog() { PlcSerialConfiguration initial; @@ -746,6 +850,7 @@ void testPlcConfigurationUsesDialog() HmiRuntimeService runtime_service(repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, repository); + RegisterCommentService register_comment_service(project_service); OfflineSimulationService simulation_service(repository); RuntimeModeService mode_service(project_service, simulation_service); RegisterMonitorService monitor_service(repository); @@ -757,6 +862,7 @@ void testPlcConfigurationUsesDialog() runtime_service, alarm_editor_service, alarm_service, + register_comment_service, monitor_service); require(window.findChild(QStringLiteral("serialPortComboBox")) == nullptr, "serial configuration controls must not be embedded in the main window"); @@ -791,6 +897,7 @@ void testRepeatedPlcStatusNotificationsAreCoalesced() HmiRuntimeService runtime_service(active_repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, active_repository); + RegisterCommentService register_comment_service(project_service); OfflineSimulationService simulation_service(virtual_repository); RepeatedStatusGateway gateway; RuntimeModeService mode_service(project_service, simulation_service); @@ -805,6 +912,7 @@ void testRepeatedPlcStatusNotificationsAreCoalesced() runtime_service, alarm_editor_service, alarm_service, + register_comment_service, monitor_service); QListWidget *output = requiredChild(window, "outputList"); const int initial_count = output->count(); @@ -839,6 +947,7 @@ void testOnlineWorkspaceShowsHmiAndFreeMonitorOnly() HmiRuntimeService runtime_service(active_repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, active_repository); + RegisterCommentService register_comment_service(project_service); OfflineSimulationService simulation_service(virtual_repository); OnlineReadyGateway gateway; RuntimeModeService mode_service(project_service, simulation_service); @@ -853,6 +962,7 @@ void testOnlineWorkspaceShowsHmiAndFreeMonitorOnly() runtime_service, alarm_editor_service, alarm_service, + register_comment_service, monitor_service); window.resize(1200, 760); window.show(); @@ -965,6 +1075,7 @@ void testMultiPageAndLogicMainWindowIntegration() HmiRuntimeService runtime_service(repository); AlarmEditorService alarm_editor_service(project_service); AlarmService alarm_service(project_service, repository); + RegisterCommentService register_comment_service(project_service); OfflineSimulationService simulation_service(repository); RuntimeModeService mode_service(project_service, simulation_service); RegisterMonitorService monitor_service(repository); @@ -978,6 +1089,7 @@ void testMultiPageAndLogicMainWindowIntegration() alarm_editor_service, alarm_service, navigation_service, + register_comment_service, monitor_service); window.resize(1400, 820); window.show(); @@ -1078,6 +1190,7 @@ int main(int argc, char *argv[]) testRuntimePageJumpDoesNotRequireRegisterWritePermission(); testRuntimeAlarmListInteraction(); testModeActionsControlEditingAvailability(); + testEdgeTimerPropertyEditingAndParallelMenu(); testPlcConfigurationUsesDialog(); testRepeatedPlcStatusNotificationsAreCoalesced(); testOnlineWorkspaceShowsHmiAndFreeMonitorOnly(); diff --git a/app/tests/main_window_tests.pro b/app/tests/main_window_tests.pro index 7bbe5e5..f803b7f 100644 --- a/app/tests/main_window_tests.pro +++ b/app/tests/main_window_tests.pro @@ -12,6 +12,7 @@ SOURCES += \ main_window_tests.cpp \ ../src/ui/main_window.cpp \ ../src/ui/alarm_configuration_dialog.cpp \ + ../src/ui/register_comment_dialog.cpp \ ../src/ui/plc_connection_dialog.cpp \ ../src/ui/free_monitor_widget.cpp \ ../src/ui/runtime_monitor_widget.cpp \ @@ -36,11 +37,13 @@ SOURCES += \ ../src/services/software_logic_executor.cpp \ ../src/services/offline_simulation_service.cpp \ ../src/services/runtime_mode_service.cpp \ - ../src/services/register_monitor_service.cpp + ../src/services/register_monitor_service.cpp \ + ../src/services/register_comment_service.cpp HEADERS += \ ../src/ui/main_window.h \ ../src/ui/alarm_configuration_dialog.h \ + ../src/ui/register_comment_dialog.h \ ../src/ui/plc_connection_dialog.h \ ../src/ui/free_monitor_widget.h \ ../src/ui/runtime_monitor_widget.h \ @@ -66,7 +69,8 @@ HEADERS += \ ../src/services/software_logic_executor.h \ ../src/services/offline_simulation_service.h \ ../src/services/runtime_mode_service.h \ - ../src/services/register_monitor_service.h + ../src/services/register_monitor_service.h \ + ../src/services/register_comment_service.h HEADERS += \ ../src/services/plc_communication_gateway.h @@ -74,6 +78,7 @@ HEADERS += \ FORMS += \ ../src/ui/main_window.ui \ ../src/ui/alarm_configuration_dialog.ui \ + ../src/ui/register_comment_dialog.ui \ ../src/ui/plc_connection_dialog.ui \ ../src/ui/free_monitor_widget.ui \ ../src/ui/runtime_monitor_widget.ui