From aedead25dfb2e9d7e09848ea69913e6b0e3778df Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Mon, 17 Aug 2026 13:13:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E7=BC=96=E8=BE=91=E9=85=8D=E7=BD=AE=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/integrated_platform.pro | 3 + app/src/services/logic_editor_service.cpp | 29 ++- app/src/ui/logic_editor_widget.cpp | 129 +++++++++++++- app/src/ui/logic_instruction_dialog.cpp | 206 ++++++++++++++++++++++ app/src/ui/logic_instruction_dialog.h | 43 +++++ app/src/ui/logic_instruction_dialog.ui | 66 +++++++ app/src/ui/main_window.cpp | 200 ++++++++++++++++++++- app/src/ui/main_window.h | 1 + app/src/ui/main_window.ui | 30 ++++ app/tests/main_window_tests.pro | 5 +- 10 files changed, 701 insertions(+), 11 deletions(-) create mode 100644 app/src/ui/logic_instruction_dialog.cpp create mode 100644 app/src/ui/logic_instruction_dialog.h create mode 100644 app/src/ui/logic_instruction_dialog.ui diff --git a/app/integrated_platform.pro b/app/integrated_platform.pro index 000728d..cb78c49 100644 --- a/app/integrated_platform.pro +++ b/app/integrated_platform.pro @@ -17,6 +17,7 @@ INCLUDEPATH += src SOURCES += \ src/main.cpp \ src/ui/main_window.cpp \ + src/ui/logic_instruction_dialog.cpp \ src/ui/alarm_configuration_dialog.cpp \ src/ui/register_comment_dialog.cpp \ src/ui/plc_connection_dialog.cpp \ @@ -52,6 +53,7 @@ SOURCES += \ HEADERS += \ src/ui/main_window.h \ + src/ui/logic_instruction_dialog.h \ src/ui/alarm_configuration_dialog.h \ src/ui/register_comment_dialog.h \ src/ui/plc_connection_dialog.h \ @@ -89,6 +91,7 @@ HEADERS += \ FORMS += \ src/ui/main_window.ui \ + src/ui/logic_instruction_dialog.ui \ src/ui/alarm_configuration_dialog.ui \ src/ui/register_comment_dialog.ui \ src/ui/plc_connection_dialog.ui \ diff --git a/app/src/services/logic_editor_service.cpp b/app/src/services/logic_editor_service.cpp index faceea3..fd68b8a 100644 --- a/app/src/services/logic_editor_service.cpp +++ b/app/src/services/logic_editor_service.cpp @@ -717,7 +717,7 @@ LogicEditorResult LogicEditorService::setOutput( { return failure( LogicEditorError::InvalidNode, - "梯形图输出必须使用有效线圈或 TON 指令"); + "梯形图输出必须使用有效的输出指令"); } const std::string node_id = existing_rung->output.has_value() ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); @@ -821,13 +821,17 @@ bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config) return std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) + || std::holds_alternative(config) || std::holds_alternative(config); } bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config) { return std::holds_alternative(config) - || std::holds_alternative(config); + || std::holds_alternative(config) + || std::holds_alternative(config) + || std::holds_alternative(config) + || std::holds_alternative(config); } std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) @@ -848,6 +852,10 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) { return "timer-contact"; } + else if constexpr (std::is_same_v) + { + return "counter-contact"; + } else if constexpr (std::is_same_v) { return "coil"; @@ -856,7 +864,22 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) { return "ton"; } - return "compare"; + else if constexpr (std::is_same_v) + { + return "counter"; + } + else if constexpr (std::is_same_v) + { + return "move"; + } + else if constexpr (std::is_same_v) + { + return "arithmetic"; + } + else + { + return "compare"; + } }, config); } diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index dec1a2e..c8b9cef 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -53,6 +53,18 @@ QString timerAddressText(const TimerAddress &address) return QString::fromStdString(address.toString()); } +QString counterAddressText(const CounterAddress &address) +{ + return QString::fromStdString(address.toString()); +} + +QString wordOperandText(const WordOperand &operand) +{ + return operand.kind == WordOperandKind::Register + ? registerAddressText(operand.address) + : QString::number(operand.constant); +} + void drawRegisterComment( QPainter *painter, const QString &comment, qreal top = 22.0) { @@ -112,6 +124,11 @@ QString nodeToolTip(const LogicNodeConfig &config) return LogicEditorWidget::tr("T 触点:%1") .arg(timerAddressText(value.address)); } + else if constexpr (std::is_same_v) + { + return LogicEditorWidget::tr("C 触点:%1") + .arg(counterAddressText(value.address)); + } else if constexpr (std::is_same_v) { return LogicEditorWidget::tr("输出线圈:%1") @@ -124,12 +141,37 @@ QString nodeToolTip(const LogicNodeConfig &config) .arg(comparisonText(value.comparison)) .arg(value.value); } - else + else if constexpr (std::is_same_v) { return LogicEditorWidget::tr("TON:%1,预设 %2 ms") .arg(timerAddressText(value.address)) .arg(value.presetMs); } + else if constexpr (std::is_same_v) + { + return LogicEditorWidget::tr("%1:%2,CV %3,PV %4,复位 %5") + .arg(value.mode == CounterMode::Up + ? QStringLiteral("CTU") : QStringLiteral("CTD")) + .arg(counterAddressText(value.address)) + .arg(registerAddressText(value.currentValueAddress)) + .arg(wordOperandText(value.preset)) + .arg(registerAddressText(value.resetAddress)); + } + else if constexpr (std::is_same_v) + { + return LogicEditorWidget::tr("MOVE:%1 -> %2") + .arg(wordOperandText(value.source)) + .arg(registerAddressText(value.destination)); + } + else + { + return LogicEditorWidget::tr("%1:%2,%3 -> %4") + .arg(value.operation == ArithmeticOperation::Add + ? QStringLiteral("ADD") : QStringLiteral("SUB")) + .arg(wordOperandText(value.left)) + .arg(wordOperandText(value.right)) + .arg(registerAddressText(value.destination)); + } }, config); } @@ -327,6 +369,24 @@ public: Qt::AlignCenter, configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >")); } + else if (const auto *counter_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 (counter_contact->mode == ContactMode::NormallyClosed) + { + painter->drawLine(QPointF(-23, 18), QPointF(23, -18)); + } + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? counterAddressText(counter_contact->address) + : tr("< C 地址 >")); + } else if (const auto *coil = std::get_if(&config_)) { painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white); @@ -391,6 +451,69 @@ public: configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs) : tr("< 预设时间 >")); } + else if (const auto *counter = 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, + counter->mode == CounterMode::Up + ? QStringLiteral("CTU") : QStringLiteral("CTD")); + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? counterAddressText(counter->address) + : tr("< C 地址 >")); + painter->drawText( + QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? QStringLiteral("CV %1 / PV %2") + .arg(registerAddressText(counter->currentValueAddress)) + .arg(wordOperandText(counter->preset)) + : tr("< CV / PV >")); + } + else if (const auto *move = 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("MOVE")); + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? QStringLiteral("%1 -> %2") + .arg(wordOperandText(move->source)) + .arg(registerAddressText(move->destination)) + : tr("< 源 -> 目标 >")); + } + else if (const auto *arithmetic = + 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, + arithmetic->operation == ArithmeticOperation::Add + ? QStringLiteral("ADD") : QStringLiteral("SUB")); + painter->drawText( + QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), + Qt::AlignCenter, + configured_ ? QStringLiteral("%1,%2 -> %3") + .arg(wordOperandText(arithmetic->left)) + .arg(wordOperandText(arithmetic->right)) + .arg(registerAddressText(arithmetic->destination)) + : tr("< 操作数 -> 目标 >")); + } } const std::string &nodeId() const { return node_id_; } @@ -766,7 +889,9 @@ void LogicEditorWidget::reloadLogic() }(), [&rung, this, rung_active] { - return std::holds_alternative(rung.output->config) + return (std::holds_alternative(rung.output->config) + || std::holds_alternative( + rung.output->config)) ? traceValue( trace_, &LogicTraceSnapshot::nodeValues, diff --git a/app/src/ui/logic_instruction_dialog.cpp b/app/src/ui/logic_instruction_dialog.cpp new file mode 100644 index 0000000..6e950e2 --- /dev/null +++ b/app/src/ui/logic_instruction_dialog.cpp @@ -0,0 +1,206 @@ +#include "logic_instruction_dialog.h" + +#include "ui_logic_instruction_dialog.h" + +#include +#include +#include +#include + +#include + +namespace { + +constexpr int kConstantOperandData = 0; +constexpr int kRegisterOperandData = 1; + +void configureOperandCombo(QComboBox *combo) +{ + combo->clear(); + combo->addItem(LogicInstructionDialog::tr("常量"), kConstantOperandData); + combo->addItem(LogicInstructionDialog::tr("D 寄存器"), kRegisterOperandData); +} + +} // namespace + +LogicInstructionDialog::LogicInstructionDialog( + const LogicNodeConfig &config, + QWidget *parent) + : QDialog(parent), + ui_(std::make_unique()), + original_config_(config) +{ + ui_->setupUi(this); + configureOperandCombo(ui_->counterPresetKindComboBox); + configureOperandCombo(ui_->leftOperandKindComboBox); + configureOperandCombo(ui_->rightOperandKindComboBox); + + connect(ui_->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui_->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + for (QComboBox *combo : { + ui_->counterPresetKindComboBox, + ui_->leftOperandKindComboBox, + ui_->rightOperandKindComboBox}) + { + connect( + combo, + qOverload(&QComboBox::currentIndexChanged), + this, + &LogicInstructionDialog::updateOperandRanges); + } + + if (const auto *counter = std::get_if(&config)) + { + configureCounter(*counter); + } + else if (const auto *move = std::get_if(&config)) + { + configureMove(*move); + } + else if (const auto *arithmetic = std::get_if(&config)) + { + configureArithmetic(*arithmetic); + } + updateOperandRanges(); +} + +LogicInstructionDialog::~LogicInstructionDialog() = default; + +LogicNodeConfig LogicInstructionDialog::config() const +{ + if (std::holds_alternative(original_config_)) + { + return CounterNodeConfig{ + CounterAddress{ui_->counterAddressSpinBox->value()}, + static_cast(ui_->counterModeComboBox->currentData().toInt()), + RegisterAddress{ + RegisterArea::D, + ui_->counterCurrentAddressSpinBox->value()}, + operandFrom( + ui_->counterPresetKindComboBox, + ui_->counterPresetValueSpinBox), + RegisterAddress{ + RegisterArea::M, + ui_->counterResetAddressSpinBox->value()}}; + } + if (std::holds_alternative(original_config_)) + { + return MoveNodeConfig{ + operandFrom(ui_->leftOperandKindComboBox, ui_->leftOperandValueSpinBox), + RegisterAddress{ + RegisterArea::D, + ui_->destinationAddressSpinBox->value()}}; + } + const auto *arithmetic = std::get_if(&original_config_); + return ArithmeticNodeConfig{ + arithmetic->operation, + operandFrom(ui_->leftOperandKindComboBox, ui_->leftOperandValueSpinBox), + operandFrom(ui_->rightOperandKindComboBox, ui_->rightOperandValueSpinBox), + RegisterAddress{ + RegisterArea::D, + ui_->destinationAddressSpinBox->value()}}; +} + +void LogicInstructionDialog::configureCounter(const CounterNodeConfig &config) +{ + setWindowTitle(tr("配置计数器")); + ui_->instructionStackedWidget->setCurrentWidget(ui_->counterPage); + ui_->counterModeComboBox->clear(); + ui_->counterModeComboBox->addItem(tr("CTU 加计数"), static_cast(CounterMode::Up)); + ui_->counterModeComboBox->addItem( + tr("CTD 减计数"), static_cast(CounterMode::Down)); + ui_->counterModeComboBox->setCurrentIndex( + ui_->counterModeComboBox->findData(static_cast(config.mode))); + ui_->counterAddressSpinBox->setValue(config.address.index()); + ui_->counterCurrentAddressSpinBox->setValue(config.currentValueAddress.index()); + ui_->counterResetAddressSpinBox->setValue(config.resetAddress.index()); + setOperand( + ui_->counterPresetKindComboBox, + ui_->counterPresetValueSpinBox, + config.preset); +} + +void LogicInstructionDialog::configureMove(const MoveNodeConfig &config) +{ + setWindowTitle(tr("配置 MOVE")); + ui_->instructionStackedWidget->setCurrentWidget(ui_->dataInstructionPage); + ui_->instructionNameLabel->setText(QStringLiteral("MOVE")); + ui_->leftOperandLabel->setText(tr("源操作数")); + ui_->rightOperandLabel->setVisible(false); + ui_->rightOperandEditor->setVisible(false); + setOperand( + ui_->leftOperandKindComboBox, + ui_->leftOperandValueSpinBox, + config.source); + ui_->destinationAddressSpinBox->setValue(config.destination.index()); +} + +void LogicInstructionDialog::configureArithmetic(const ArithmeticNodeConfig &config) +{ + const bool is_add = config.operation == ArithmeticOperation::Add; + setWindowTitle(is_add ? tr("配置 ADD") : tr("配置 SUB")); + ui_->instructionStackedWidget->setCurrentWidget(ui_->dataInstructionPage); + ui_->instructionNameLabel->setText(is_add + ? QStringLiteral("ADD") : QStringLiteral("SUB")); + ui_->leftOperandLabel->setText(tr("左操作数")); + ui_->rightOperandLabel->setVisible(true); + ui_->rightOperandEditor->setVisible(true); + setOperand( + ui_->leftOperandKindComboBox, + ui_->leftOperandValueSpinBox, + config.left); + setOperand( + ui_->rightOperandKindComboBox, + ui_->rightOperandValueSpinBox, + config.right); + ui_->destinationAddressSpinBox->setValue(config.destination.index()); +} + +void LogicInstructionDialog::updateOperandRanges() +{ + const auto update = [](const QComboBox *combo, QSpinBox *spin) + { + const bool register_operand = combo->currentData().toInt() + == kRegisterOperandData; + spin->setPrefix(register_operand ? QStringLiteral("D") : QString{}); + spin->setMinimum(register_operand + ? RegisterAddress::kMinimumIndex + : std::numeric_limits::min()); + spin->setMaximum(register_operand + ? RegisterAddress::kMaximumIndex + : std::numeric_limits::max()); + }; + update(ui_->counterPresetKindComboBox, ui_->counterPresetValueSpinBox); + update(ui_->leftOperandKindComboBox, ui_->leftOperandValueSpinBox); + update(ui_->rightOperandKindComboBox, ui_->rightOperandValueSpinBox); +} + +void LogicInstructionDialog::setOperand( + QComboBox *kind_combo, + QSpinBox *value_spin, + const WordOperand &operand) +{ + kind_combo->setCurrentIndex(kind_combo->findData( + operand.kind == WordOperandKind::Register + ? kRegisterOperandData : kConstantOperandData)); + value_spin->setValue( + operand.kind == WordOperandKind::Register + ? operand.address.index() : operand.constant); +} + +WordOperand LogicInstructionDialog::operandFrom( + const QComboBox *kind_combo, + const QSpinBox *value_spin) +{ + if (kind_combo->currentData().toInt() == kRegisterOperandData) + { + return WordOperand{ + WordOperandKind::Register, + RegisterAddress{RegisterArea::D, value_spin->value()}, + 0}; + } + return WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + static_cast(value_spin->value())}; +} diff --git a/app/src/ui/logic_instruction_dialog.h b/app/src/ui/logic_instruction_dialog.h new file mode 100644 index 0000000..a08ee7c --- /dev/null +++ b/app/src/ui/logic_instruction_dialog.h @@ -0,0 +1,43 @@ +#pragma once + +#include "domain/control_logic_model.h" + +#include + +#include + +class QComboBox; +class QSpinBox; + +namespace Ui { +class LogicInstructionDialog; +} + +class LogicInstructionDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit LogicInstructionDialog( + const LogicNodeConfig &config, + QWidget *parent = nullptr); + ~LogicInstructionDialog() override; + + LogicNodeConfig config() const; + +private: + void configureCounter(const CounterNodeConfig &config); + void configureMove(const MoveNodeConfig &config); + void configureArithmetic(const ArithmeticNodeConfig &config); + void updateOperandRanges(); + static void setOperand( + QComboBox *kind_combo, + QSpinBox *value_spin, + const WordOperand &operand); + static WordOperand operandFrom( + const QComboBox *kind_combo, + const QSpinBox *value_spin); + + std::unique_ptr ui_; + LogicNodeConfig original_config_; +}; diff --git a/app/src/ui/logic_instruction_dialog.ui b/app/src/ui/logic_instruction_dialog.ui new file mode 100644 index 0000000..54fc185 --- /dev/null +++ b/app/src/ui/logic_instruction_dialog.ui @@ -0,0 +1,66 @@ + + + LogicInstructionDialog + + + 00420310 + + 配置逻辑指令 + + + + + + 计数方式 + + 计数器资源 + C4000 + 当前值 CV + D4000 + 预设值 PV + + + 0000 + + 3276710 + + + + 复位输入 + M4000 + + + + + 指令 + MOVE + 源操作数 + + + 0000 + + -3276832767 + + + + 右操作数 + + + 0000 + + -3276832767 + + + + 目标地址 + D4000 + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index 9a2c95c..0e367ba 100644 --- a/app/src/ui/main_window.cpp +++ b/app/src/ui/main_window.cpp @@ -4,6 +4,7 @@ #include "alarm_configuration_dialog.h" #include "register_comment_dialog.h" #include "logic_editor_widget.h" +#include "logic_instruction_dialog.h" #include "plc_connection_dialog.h" #include "runtime_monitor_widget.h" #include "free_monitor_widget.h" @@ -342,6 +343,7 @@ void MainWindow::configureActions() QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点")); QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点")); QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点")); + QAction *parallel_counter = parallel_menu->addAction(tr("并联 C 触点")); QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件")); connect(parallel_open, &QAction::triggered, this, @@ -380,6 +382,13 @@ void MainWindow::configureActions() addLogicParallelBranch(TimerContactNodeConfig{ TimerAddress{0}, ContactMode::NormallyOpen}); }); + connect(parallel_counter, &QAction::triggered, + this, + [this] + { + addLogicParallelBranch(CounterContactNodeConfig{ + CounterAddress{0}, ContactMode::NormallyOpen}); + }); connect(parallel_compare, &QAction::triggered, this, [this] @@ -432,6 +441,13 @@ void MainWindow::configureActions() addLogicCondition(TimerContactNodeConfig{ TimerAddress{0}, ContactMode::NormallyOpen}); }); + connect(ui_->addCounterContactAction, &QAction::triggered, + this, + [this] + { + addLogicCondition(CounterContactNodeConfig{ + CounterAddress{0}, ContactMode::NormallyOpen}); + }); connect(ui_->addNormalCoilAction, &QAction::triggered, this, [this] @@ -462,6 +478,77 @@ void MainWindow::configureActions() { setLogicOutput(TonNodeConfig{TimerAddress{0}, 1000}); }); + connect(ui_->addCtuAction, &QAction::triggered, + this, + [this] + { + configureAndSetLogicOutput(CounterNodeConfig{ + CounterAddress{0}, + CounterMode::Up, + RegisterAddress{RegisterArea::D, 0}, + WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + 10}, + RegisterAddress{RegisterArea::M, 0}}); + }); + connect(ui_->addCtdAction, &QAction::triggered, + this, + [this] + { + configureAndSetLogicOutput(CounterNodeConfig{ + CounterAddress{0}, + CounterMode::Down, + RegisterAddress{RegisterArea::D, 0}, + WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + 10}, + RegisterAddress{RegisterArea::M, 0}}); + }); + connect(ui_->addMoveAction, &QAction::triggered, + this, + [this] + { + configureAndSetLogicOutput(MoveNodeConfig{ + WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + 0}, + RegisterAddress{RegisterArea::D, 0}}); + }); + connect(ui_->addAddAction, &QAction::triggered, + this, + [this] + { + configureAndSetLogicOutput(ArithmeticNodeConfig{ + ArithmeticOperation::Add, + WordOperand{ + WordOperandKind::Register, + RegisterAddress{RegisterArea::D, 0}, + 0}, + WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + 1}, + RegisterAddress{RegisterArea::D, 0}}); + }); + connect(ui_->addSubAction, &QAction::triggered, + this, + [this] + { + configureAndSetLogicOutput(ArithmeticNodeConfig{ + ArithmeticOperation::Subtract, + WordOperand{ + WordOperandKind::Register, + RegisterAddress{RegisterArea::D, 0}, + 0}, + WordOperand{ + WordOperandKind::Constant, + RegisterAddress{RegisterArea::D, 0}, + 1}, + RegisterAddress{RegisterArea::D, 0}}); + }); connect(ui_->addCompareAction, &QAction::triggered, this, [this] @@ -544,10 +631,16 @@ void MainWindow::configureAppearance() 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_->addCounterContactAction->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_->addCtuAction->setIcon(style()->standardIcon(QStyle::SP_ArrowUp)); + ui_->addCtdAction->setIcon(style()->standardIcon(QStyle::SP_ArrowDown)); + ui_->addMoveAction->setIcon(style()->standardIcon(QStyle::SP_ArrowForward)); + ui_->addAddAction->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton)); + ui_->addSubAction->setIcon(style()->standardIcon(QStyle::SP_DialogResetButton)); ui_->addCompareAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogInfoView)); ui_->editRungCommentAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView)); ui_->deleteLogicAction->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); @@ -1443,6 +1536,21 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) static_cast(timer_contact->mode))); ui_->logicModeComboBox->setEnabled(true); } + else if (const auto *counter_contact = + std::get_if(&node->config)) + { + ui_->logicNodeTypeLabel->setText(tr("C 触点")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("C")); + ui_->logicAddressSpinBox->setValue(counter_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(counter_contact->mode))); + ui_->logicModeComboBox->setEnabled(true); + } else if (const auto *coil = std::get_if(&node->config)) { ui_->logicNodeTypeLabel->setText(tr("线圈")); @@ -1472,16 +1580,47 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) ui_->logicValueSpinBox->setEnabled(true); ui_->logicValueSpinBox->setValue(compare->value); } - else + else if (const auto *ton = std::get_if(&node->config)) { - const auto &ton = std::get(node->config); ui_->logicNodeTypeLabel->setText(tr("TON")); ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); - ui_->logicAddressSpinBox->setValue(ton.address.index()); + ui_->logicAddressSpinBox->setValue(ton->address.index()); ui_->logicModeComboBox->addItem(tr("非保持接通延时")); ui_->logicModeComboBox->setEnabled(false); ui_->logicPresetSpinBox->setEnabled(true); - ui_->logicPresetSpinBox->setValue(ton.presetMs); + ui_->logicPresetSpinBox->setValue(ton->presetMs); + } + else if (const auto *counter = std::get_if(&node->config)) + { + ui_->logicNodeTypeLabel->setText( + counter->mode == CounterMode::Up ? QStringLiteral("CTU") + : QStringLiteral("CTD")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("C")); + ui_->logicAddressSpinBox->setValue(counter->address.index()); + ui_->logicAddressSpinBox->setEnabled(false); + ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); + ui_->logicModeComboBox->setEnabled(false); + } + else if (const auto *move = std::get_if(&node->config)) + { + ui_->logicNodeTypeLabel->setText(QStringLiteral("MOVE")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); + ui_->logicAddressSpinBox->setValue(move->destination.index()); + ui_->logicAddressSpinBox->setEnabled(false); + ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); + ui_->logicModeComboBox->setEnabled(false); + } + else + { + const auto &arithmetic = std::get(node->config); + ui_->logicNodeTypeLabel->setText( + arithmetic.operation == ArithmeticOperation::Add + ? QStringLiteral("ADD") : QStringLiteral("SUB")); + ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); + ui_->logicAddressSpinBox->setValue(arithmetic.destination.index()); + ui_->logicAddressSpinBox->setEnabled(false); + ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); + ui_->logicModeComboBox->setEnabled(false); } if (!node->configured) { @@ -1628,6 +1767,36 @@ void MainWindow::setLogicOutput(const LogicNodeConfig &config) statusBar()->showMessage(tr("逻辑输出已设置"), 3000); } +void MainWindow::configureAndSetLogicOutput(const LogicNodeConfig &config) +{ + LogicInstructionDialog dialog(config, this); + if (dialog.exec() != QDialog::Accepted) + { + return; + } + const LogicNodeConfig configured = dialog.config(); + LogicEditorResult result = logic_editor_widget_->setOutput(configured); + if (!result.succeeded) + { + showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false); + return; + } + const std::string node_id = result.id; + result = logic_editor_service_.updateNodeConfig( + current_logic_id_, node_id, configured); + if (!result.succeeded) + { + showProjectResult(tr("配置逻辑输出"), fromUtf8(result.message), false); + return; + } + selected_logic_node_id_ = node_id; + logic_editor_widget_->reloadLogic(); + logic_editor_widget_->selectNode(node_id); + showLogicNodeProperties(node_id); + refreshProjectUi(); + statusBar()->showMessage(tr("逻辑输出已配置"), 3000); +} + void MainWindow::addLogicRung() { const LogicEditorResult result = logic_editor_widget_->addRung(); @@ -1719,6 +1888,12 @@ void MainWindow::applySelectedLogicNodeProperties() TimerAddress{ui_->logicAddressSpinBox->value()}, static_cast(ui_->logicModeComboBox->currentData().toInt())}; } + else if (std::holds_alternative(config)) + { + config = CounterContactNodeConfig{ + CounterAddress{ui_->logicAddressSpinBox->value()}, + static_cast(ui_->logicModeComboBox->currentData().toInt())}; + } else if (std::holds_alternative(config)) { config = CoilNodeConfig{ @@ -1733,12 +1908,21 @@ void MainWindow::applySelectedLogicNodeProperties() ui_->logicComparisonComboBox->currentData().toInt()), static_cast(ui_->logicValueSpinBox->value())}; } - else + else if (std::holds_alternative(config)) { config = TonNodeConfig{ TimerAddress{ui_->logicAddressSpinBox->value()}, ui_->logicPresetSpinBox->value()}; } + else + { + LogicInstructionDialog dialog(config, this); + if (dialog.exec() != QDialog::Accepted) + { + return; + } + config = dialog.config(); + } const LogicEditorResult result = logic_editor_service_.updateNodeConfig( current_logic_id_, selected_logic_node_id_, config); @@ -1956,10 +2140,16 @@ void MainWindow::updateModeUi(const QString &message) ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing); ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing); ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing); + ui_->addCounterContactAction->setEnabled(policy.allowsProjectEditing); ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addTonAction->setEnabled(policy.allowsProjectEditing); + ui_->addCtuAction->setEnabled(policy.allowsProjectEditing); + ui_->addCtdAction->setEnabled(policy.allowsProjectEditing); + ui_->addMoveAction->setEnabled(policy.allowsProjectEditing); + ui_->addAddAction->setEnabled(policy.allowsProjectEditing); + ui_->addSubAction->setEnabled(policy.allowsProjectEditing); ui_->addCompareAction->setEnabled(policy.allowsProjectEditing); ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing); ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing); diff --git a/app/src/ui/main_window.h b/app/src/ui/main_window.h index b6b286b..dc6ab66 100644 --- a/app/src/ui/main_window.h +++ b/app/src/ui/main_window.h @@ -132,6 +132,7 @@ private: void addLogicParallelBranch(const LogicNodeConfig &config); // 设置当前网络右侧的输出线圈 void setLogicOutput(const LogicNodeConfig &config); + void configureAndSetLogicOutput(const LogicNodeConfig &config); // 新增一个梯形图网络 void addLogicRung(); void editSelectedRungComment(); diff --git a/app/src/ui/main_window.ui b/app/src/ui/main_window.ui index 5fe8a29..d8ee498 100644 --- a/app/src/ui/main_window.ui +++ b/app/src/ui/main_window.ui @@ -330,10 +330,16 @@ + + + + + + @@ -1018,6 +1024,10 @@ 添加离线仿真定时器触点 + + C 触点 + 添加离线仿真计数器完成触点 + 线圈 @@ -1050,6 +1060,26 @@ 设置非保持接通延时定时器输出 + + CTU + 设置按上升沿执行的加计数器 + + + CTD + 设置按上升沿执行的减计数器 + + + MOVE + 设置 D 数据传送指令 + + + ADD + 设置带饱和处理的加法指令 + + + SUB + 设置带饱和处理的减法指令 + D 比较 diff --git a/app/tests/main_window_tests.pro b/app/tests/main_window_tests.pro index f803b7f..5644f6f 100644 --- a/app/tests/main_window_tests.pro +++ b/app/tests/main_window_tests.pro @@ -18,6 +18,7 @@ SOURCES += \ ../src/ui/runtime_monitor_widget.cpp \ ../src/ui/hmi_editor_widget.cpp \ ../src/ui/logic_editor_widget.cpp \ + ../src/ui/logic_instruction_dialog.cpp \ ../src/domain/register_address.cpp \ ../src/domain/register_repository.cpp \ ../src/domain/active_register_repository.cpp \ @@ -49,6 +50,7 @@ HEADERS += \ ../src/ui/runtime_monitor_widget.h \ ../src/ui/hmi_editor_widget.h \ ../src/ui/logic_editor_widget.h \ + ../src/ui/logic_instruction_dialog.h \ ../src/domain/register_address.h \ ../src/domain/register_repository.h \ ../src/domain/active_register_repository.h \ @@ -81,4 +83,5 @@ FORMS += \ ../src/ui/register_comment_dialog.ui \ ../src/ui/plc_connection_dialog.ui \ ../src/ui/free_monitor_widget.ui \ - ../src/ui/runtime_monitor_widget.ui + ../src/ui/runtime_monitor_widget.ui \ + ../src/ui/logic_instruction_dialog.ui