Selaa lähdekoodia

feat: 集成梯形图与软元件注释界面

main
suyu 1 kuukausi sitten
vanhempi
commit
0dc0de3172
11 muutettua tiedostoa jossa 794 lisäystä ja 27 poistoa
  1. +5
    -0
      app/integrated_platform.pro
  2. +3
    -0
      app/src/main.cpp
  3. +158
    -6
      app/src/ui/logic_editor_widget.cpp
  4. +195
    -15
      app/src/ui/main_window.cpp
  5. +6
    -0
      app/src/ui/main_window.h
  6. +78
    -4
      app/src/ui/main_window.ui
  7. +155
    -0
      app/src/ui/register_comment_dialog.cpp
  8. +34
    -0
      app/src/ui/register_comment_dialog.h
  9. +40
    -0
      app/src/ui/register_comment_dialog.ui
  10. +113
    -0
      app/tests/main_window_tests.cpp
  11. +7
    -2
      app/tests/main_window_tests.pro

+ 5
- 0
app/integrated_platform.pro Näytä tiedosto

@@ -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

+ 3
- 0
app/src/main.cpp Näytä tiedosto

@@ -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();



+ 158
- 6
app/src/ui/logic_editor_widget.cpp Näytä tiedosto

@@ -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<int>(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<Config, EdgeContactNodeConfig>)
{
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<Config, TimerContactNodeConfig>)
{
return LogicEditorWidget::tr("T 触点:%1")
.arg(timerAddressText(value.address));
}
else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
{
return LogicEditorWidget::tr("输出线圈:%1")
.arg(registerAddressText(value.address));
}
else
else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
{
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 &center,
const QString &register_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<EdgeContactNodeConfig>(&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<TimerContactNodeConfig>(&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<CoilNodeConfig>(&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<CompareNodeConfig>(&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<TonNodeConfig>(&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<int>(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<RegisterAddress> 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<RegisterAddress> address =
registerAddressForLogicNode(rung.output->config);
return address.has_value()
? QString::fromStdString(
editor_service_.registerCommentFor(*address))
: QString{};
}(),
[&rung, this, rung_active]
{
return std::holds_alternative<TonNodeConfig>(rung.output->config)
? traceValue(
trace_,
&LogicTraceSnapshot::nodeValues,
rung.output->id)
: rung_active;
}(),
rung.output->id == fault_node_id_));
}
else


+ 195
- 15
app/src/ui/main_window.cpp Näytä tiedosto

@@ -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 <QActionGroup>
@@ -200,6 +202,7 @@ MainWindow::MainWindow(
HmiRuntimeService &hmi_runtime_service,
AlarmEditorService &alarm_editor_service,
AlarmService &alarm_service,
RegisterCommentService &register_comment_service,
RegisterMonitorService &register_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<HmiNavigationService>(project_service)),
@@ -228,6 +232,7 @@ MainWindow::MainWindow(
AlarmEditorService &alarm_editor_service,
AlarmService &alarm_service,
HmiNavigationService &hmi_navigation_service,
RegisterCommentService &register_comment_service,
RegisterMonitorService &register_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<QWidget *>(ui_->logicModeComboBox),
static_cast<QWidget *>(ui_->logicComparisonComboBox),
static_cast<QWidget *>(ui_->logicValueSpinBox),
static_cast<QWidget *>(ui_->logicPresetSpinBox),
static_cast<QWidget *>(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<ContactNodeConfig>(&node->config))
{
ui_->logicNodeTypeLabel->setText(tr("触点"));
@@ -1327,10 +1412,36 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id)
ui_->logicModeComboBox->addItem(
tr("常闭"), static_cast<int>(ContactMode::NormallyClosed));
ui_->logicModeComboBox->setCurrentIndex(
ui_->logicModeComboBox->findData(static_cast<int>(contact->mode)));
ui_->logicModeComboBox->findData(static_cast<int>(contact->mode)));
ui_->logicModeComboBox->setEnabled(true);
}
else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&node->config))
{
ui_->logicNodeTypeLabel->setText(tr("边沿触点"));
ui_->logicAddressAreaLabel->setText(QStringLiteral("M"));
ui_->logicAddressSpinBox->setValue(edge->address.index());
ui_->logicModeComboBox->addItem(
tr("上升沿"), static_cast<int>(EdgeMode::Rising));
ui_->logicModeComboBox->addItem(
tr("下降沿"), static_cast<int>(EdgeMode::Falling));
ui_->logicModeComboBox->setCurrentIndex(
ui_->logicModeComboBox->findData(static_cast<int>(edge->mode)));
ui_->logicModeComboBox->setEnabled(true);
}
else if (const auto *timer_contact =
std::get_if<TimerContactNodeConfig>(&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<int>(ContactMode::NormallyOpen));
ui_->logicModeComboBox->addItem(
tr("常闭"), static_cast<int>(ContactMode::NormallyClosed));
ui_->logicModeComboBox->setCurrentIndex(
ui_->logicModeComboBox->findData(
static_cast<int>(timer_contact->mode)));
ui_->logicModeComboBox->setEnabled(true);
ui_->logicComparisonComboBox->setEnabled(false);
ui_->logicValueSpinBox->setEnabled(false);
}
else if (const auto *coil = std::get_if<CoilNodeConfig>(&node->config))
{
@@ -1344,25 +1455,33 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id)
ui_->logicModeComboBox->addItem(
tr("复位"), static_cast<int>(CoilMode::Reset));
ui_->logicModeComboBox->setCurrentIndex(
ui_->logicModeComboBox->findData(static_cast<int>(coil->mode)));
ui_->logicModeComboBox->findData(static_cast<int>(coil->mode)));
ui_->logicModeComboBox->setEnabled(true);
ui_->logicComparisonComboBox->setEnabled(false);
ui_->logicValueSpinBox->setEnabled(false);
}
else
else if (const auto *compare = std::get_if<CompareNodeConfig>(&node->config))
{
const auto &compare = std::get<CompareNodeConfig>(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<int>(compare.comparison)));
static_cast<int>(compare->comparison)));
ui_->logicValueSpinBox->setEnabled(true);
ui_->logicValueSpinBox->setValue(compare.value);
ui_->logicValueSpinBox->setValue(compare->value);
}
else
{
const auto &ton = std::get<TonNodeConfig>(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<ContactMode>(ui_->logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<EdgeContactNodeConfig>(config))
{
config = EdgeContactNodeConfig{
RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()},
static_cast<EdgeMode>(ui_->logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<TimerContactNodeConfig>(config))
{
config = TimerContactNodeConfig{
TimerAddress{ui_->logicAddressSpinBox->value()},
static_cast<ContactMode>(ui_->logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<CoilNodeConfig>(config))
{
config = CoilNodeConfig{
RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()},
static_cast<CoilMode>(ui_->logicModeComboBox->currentData().toInt())};
}
else
else if (std::holds_alternative<CompareNodeConfig>(config))
{
config = CompareNodeConfig{
RegisterAddress{RegisterArea::D, ui_->logicAddressSpinBox->value()},
@@ -1565,6 +1733,12 @@ void MainWindow::applySelectedLogicNodeProperties()
ui_->logicComparisonComboBox->currentData().toInt()),
static_cast<std::int16_t>(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);


+ 6
- 0
app/src/ui/main_window.h Näytä tiedosto

@@ -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 &register_comment_service,
RegisterMonitorService &register_monitor_service,
QWidget *parent = nullptr);
MainWindow(
@@ -78,6 +80,7 @@ public:
AlarmEditorService &alarm_editor_service,
AlarmService &alarm_service,
HmiNavigationService &hmi_navigation_service,
RegisterCommentService &register_comment_service,
RegisterMonitorService &register_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 &register_comment_service_;
RegisterMonitorService &register_monitor_service_;
std::unique_ptr<HmiNavigationService> owned_hmi_navigation_service_;
HmiNavigationService *hmi_navigation_service_ = nullptr;


+ 78
- 4
app/src/ui/main_window.ui Näytä tiedosto

@@ -254,9 +254,10 @@
<addaction name="disconnectPlcAction"/>
</widget>
<widget class="QMenu" name="viewMenu">
<property name="title">
<string>视图(&amp;V)</string>
</property>
<property name="title">
<string>视图(&amp;V)</string>
</property>
<addaction name="configureRegisterCommentsAction"/>
</widget>
<addaction name="fileMenu"/>
<addaction name="runMenu"/>
@@ -326,10 +327,15 @@
<addaction name="separator"/>
<addaction name="addNormallyOpenAction"/>
<addaction name="addNormallyClosedAction"/>
<addaction name="addRisingEdgeAction"/>
<addaction name="addFallingEdgeAction"/>
<addaction name="addTimerContactAction"/>
<addaction name="addNormalCoilAction"/>
<addaction name="addSetCoilAction"/>
<addaction name="addResetCoilAction"/>
<addaction name="addTonAction"/>
<addaction name="addCompareAction"/>
<addaction name="editRungCommentAction"/>
<addaction name="deleteLogicAction"/>
</widget>
<widget class="QDockWidget" name="projectDock">
@@ -758,7 +764,27 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<item row="7" column="0">
<widget class="QLabel" name="logicPresetLabel">
<property name="text">
<string>预设时间(ms)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="logicPresetSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>86400000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QPushButton" name="applyLogicPropertiesButton">
<property name="text">
<string>应用属性</string>
@@ -848,6 +874,14 @@
<string>配置参数并连接真实 PLC</string>
</property>
</action>
<action name="configureRegisterCommentsAction">
<property name="text">
<string>软元件注释</string>
</property>
<property name="toolTip">
<string>维护工程级 M/D 地址注释</string>
</property>
</action>
<action name="disconnectPlcAction">
<property name="text">
<string>断开 PLC</string>
@@ -960,6 +994,30 @@
<string>向当前网络添加常闭触点</string>
</property>
</action>
<action name="addRisingEdgeAction">
<property name="text">
<string>上升沿</string>
</property>
<property name="toolTip">
<string>添加一个扫描周期的上升沿触点</string>
</property>
</action>
<action name="addFallingEdgeAction">
<property name="text">
<string>下降沿</string>
</property>
<property name="toolTip">
<string>添加一个扫描周期的下降沿触点</string>
</property>
</action>
<action name="addTimerContactAction">
<property name="text">
<string>T 触点</string>
</property>
<property name="toolTip">
<string>添加离线仿真定时器触点</string>
</property>
</action>
<action name="addNormalCoilAction">
<property name="text">
<string>线圈</string>
@@ -984,6 +1042,14 @@
<string>设置当前网络的复位线圈</string>
</property>
</action>
<action name="addTonAction">
<property name="text">
<string>TON</string>
</property>
<property name="toolTip">
<string>设置非保持接通延时定时器输出</string>
</property>
</action>
<action name="addCompareAction">
<property name="text">
<string>D 比较</string>
@@ -992,6 +1058,14 @@
<string>向当前网络添加 D 值与常量比较</string>
</property>
</action>
<action name="editRungCommentAction">
<property name="text">
<string>网络注释</string>
</property>
<property name="toolTip">
<string>编辑当前梯形图网络说明</string>
</property>
</action>
<action name="deleteLogicAction">
<property name="text">
<string>删除</string>


+ 155
- 0
app/src/ui/register_comment_dialog.cpp Näytä tiedosto

@@ -0,0 +1,155 @@
#include "register_comment_dialog.h"

#include "services/register_comment_service.h"
#include "ui_register_comment_dialog.h"

#include <QHeaderView>
#include <QMessageBox>
#include <QTableWidgetItem>

#include <optional>

namespace {

QString fromUtf8(const std::string &value)
{
return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
}

std::string toUtf8(const QString &value)
{
const QByteArray bytes = value.toUtf8();
return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
}

} // namespace

RegisterCommentDialog::RegisterCommentDialog(
RegisterCommentService &service,
QWidget *parent)
: QDialog(parent),
ui_(std::make_unique<Ui::RegisterCommentDialog>()),
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<int>(RegisterArea::M));
ui_->areaComboBox->setItemData(1, static_cast<int>(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<int>(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<int>(index);
auto *address_item = new QTableWidgetItem(
QString::fromStdString(comment.address.toString()));
address_item->setData(Qt::UserRole, static_cast<int>(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<RegisterAddress> 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<RegisterAddress> 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<RegisterArea>(
ui_->areaComboBox->currentData().toInt());
return RegisterAddress{area, ui_->indexSpinBox->value()};
}

std::optional<RegisterAddress> 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<RegisterArea>(item->data(Qt::UserRole).toInt()),
item->data(Qt::UserRole + 1).toInt()};
}

+ 34
- 0
app/src/ui/register_comment_dialog.h Näytä tiedosto

@@ -0,0 +1,34 @@
#pragma once

#include "domain/register_address.h"

#include <QDialog>

#include <memory>
#include <optional>

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<RegisterAddress> selectedAddress() const;

std::unique_ptr<Ui::RegisterCommentDialog> ui_;
RegisterCommentService &service_;
};

+ 40
- 0
app/src/ui/register_comment_dialog.ui Näytä tiedosto

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegisterCommentDialog</class>
<widget class="QDialog" name="RegisterCommentDialog">
<property name="windowTitle"><string>软元件注释</string></property>
<property name="minimumSize"><size><width>620</width><height>400</height></size></property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="commentTable">
<property name="selectionBehavior"><enum>QAbstractItemView::SelectRows</enum></property>
<property name="selectionMode"><enum>QAbstractItemView::SingleSelection</enum></property>
<property name="editTriggers"><set>QAbstractItemView::NoEditTriggers</set></property>
<property name="columnCount"><number>2</number></property>
<column><property name="text"><string>地址</string></property></column>
<column><property name="text"><string>注释</string></property></column>
</widget>
</item>
<item>
<layout class="QFormLayout" name="commentForm">
<item row="0" column="0"><widget class="QLabel" name="areaLabel"><property name="text"><string>地址区域</string></property></widget></item>
<item row="0" column="1"><widget class="QComboBox" name="areaComboBox"><item><property name="text"><string>M</string></property></item><item><property name="text"><string>D</string></property></item></widget></item>
<item row="1" column="0"><widget class="QLabel" name="indexLabel"><property name="text"><string>地址</string></property></widget></item>
<item row="1" column="1"><widget class="QSpinBox" name="indexSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item row="2" column="0"><widget class="QLabel" name="commentLabel"><property name="text"><string>注释</string></property></widget></item>
<item row="2" column="1"><widget class="QLineEdit" name="commentEdit"><property name="placeholderText"><string>例如:启动按钮</string></property></widget></item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="operationLayout">
<item><widget class="QPushButton" name="saveButton"><property name="text"><string>保存</string></property></widget></item>
<item><widget class="QPushButton" name="removeButton"><property name="text"><string>删除</string></property></widget></item>
<item><spacer name="operationSpacer"><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
<item><widget class="QDialogButtonBox" name="buttonBox"><property name="standardButtons"><set>QDialogButtonBox::Close</set></property></widget></item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

+ 113
- 0
app/tests/main_window_tests.cpp Näytä tiedosto

@@ -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 <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QMenu>
#include <QPainter>
#include <QSpinBox>
#include <QPushButton>
#include <QStringList>
#include <QTableWidget>
#include <QTimer>
#include <QTest>
@@ -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<QAction>(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<QAction>(window, "addRisingEdgeAction");
QAction *falling_action = requiredChild<QAction>(window, "addFallingEdgeAction");
QAction *timer_contact_action = requiredChild<QAction>(
window, "addTimerContactAction");
QAction *ton_action = requiredChild<QAction>(window, "addTonAction");
requiredChild<QAction>(window, "editRungCommentAction");
requiredChild<QAction>(window, "configureRegisterCommentsAction");
QSpinBox *address = requiredChild<QSpinBox>(window, "logicAddressSpinBox");
QSpinBox *preset = requiredChild<QSpinBox>(window, "logicPresetSpinBox");
QComboBox *mode = requiredChild<QComboBox>(window, "logicModeComboBox");
QPushButton *apply = requiredChild<QPushButton>(
window, "applyLogicPropertiesButton");
const std::string logic_id = logic_editor_service.firstLogicId();

rising_action->trigger();
address->setValue(8);
mode->setCurrentIndex(mode->findData(static_cast<int>(EdgeMode::Falling)));
apply->click();
const LogicNode *edge = logic_editor_service.findNode(logic_id, "edge-1");
require(edge != nullptr && edge->configured
&& std::get<EdgeContactNodeConfig>(edge->config).address.index() == 8
&& std::get<EdgeContactNodeConfig>(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<int>(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<TimerContactNodeConfig>(timer_contact->config).address
== TimerAddress{9}
&& std::get<TimerContactNodeConfig>(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<TonNodeConfig>(ton->config).address == TimerAddress{9}
&& std::get<TonNodeConfig>(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<EdgeContactNodeConfig>(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<QComboBox *>(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<QListWidget>(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();


+ 7
- 2
app/tests/main_window_tests.pro Näytä tiedosto

@@ -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

Ladataan…
Peruuta
Tallenna