| @@ -17,6 +17,7 @@ INCLUDEPATH += src | |||||
| SOURCES += \ | SOURCES += \ | ||||
| src/main.cpp \ | src/main.cpp \ | ||||
| src/ui/main_window.cpp \ | src/ui/main_window.cpp \ | ||||
| src/ui/logic_instruction_dialog.cpp \ | |||||
| src/ui/alarm_configuration_dialog.cpp \ | src/ui/alarm_configuration_dialog.cpp \ | ||||
| src/ui/register_comment_dialog.cpp \ | src/ui/register_comment_dialog.cpp \ | ||||
| src/ui/plc_connection_dialog.cpp \ | src/ui/plc_connection_dialog.cpp \ | ||||
| @@ -52,6 +53,7 @@ SOURCES += \ | |||||
| HEADERS += \ | HEADERS += \ | ||||
| src/ui/main_window.h \ | src/ui/main_window.h \ | ||||
| src/ui/logic_instruction_dialog.h \ | |||||
| src/ui/alarm_configuration_dialog.h \ | src/ui/alarm_configuration_dialog.h \ | ||||
| src/ui/register_comment_dialog.h \ | src/ui/register_comment_dialog.h \ | ||||
| src/ui/plc_connection_dialog.h \ | src/ui/plc_connection_dialog.h \ | ||||
| @@ -89,6 +91,7 @@ HEADERS += \ | |||||
| FORMS += \ | FORMS += \ | ||||
| src/ui/main_window.ui \ | src/ui/main_window.ui \ | ||||
| src/ui/logic_instruction_dialog.ui \ | |||||
| src/ui/alarm_configuration_dialog.ui \ | src/ui/alarm_configuration_dialog.ui \ | ||||
| src/ui/register_comment_dialog.ui \ | src/ui/register_comment_dialog.ui \ | ||||
| src/ui/plc_connection_dialog.ui \ | src/ui/plc_connection_dialog.ui \ | ||||
| @@ -717,7 +717,7 @@ LogicEditorResult LogicEditorService::setOutput( | |||||
| { | { | ||||
| return failure( | return failure( | ||||
| LogicEditorError::InvalidNode, | LogicEditorError::InvalidNode, | ||||
| "梯形图输出必须使用有效线圈或 TON 指令"); | |||||
| "梯形图输出必须使用有效的输出指令"); | |||||
| } | } | ||||
| const std::string node_id = existing_rung->output.has_value() | const std::string node_id = existing_rung->output.has_value() | ||||
| ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); | ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); | ||||
| @@ -821,13 +821,17 @@ bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config) | |||||
| return std::holds_alternative<ContactNodeConfig>(config) | return std::holds_alternative<ContactNodeConfig>(config) | ||||
| || std::holds_alternative<EdgeContactNodeConfig>(config) | || std::holds_alternative<EdgeContactNodeConfig>(config) | ||||
| || std::holds_alternative<TimerContactNodeConfig>(config) | || std::holds_alternative<TimerContactNodeConfig>(config) | ||||
| || std::holds_alternative<CounterContactNodeConfig>(config) | |||||
| || std::holds_alternative<CompareNodeConfig>(config); | || std::holds_alternative<CompareNodeConfig>(config); | ||||
| } | } | ||||
| bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config) | bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config) | ||||
| { | { | ||||
| return std::holds_alternative<CoilNodeConfig>(config) | return std::holds_alternative<CoilNodeConfig>(config) | ||||
| || std::holds_alternative<TonNodeConfig>(config); | |||||
| || std::holds_alternative<TonNodeConfig>(config) | |||||
| || std::holds_alternative<CounterNodeConfig>(config) | |||||
| || std::holds_alternative<MoveNodeConfig>(config) | |||||
| || std::holds_alternative<ArithmeticNodeConfig>(config); | |||||
| } | } | ||||
| std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) | std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) | ||||
| @@ -848,6 +852,10 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) | |||||
| { | { | ||||
| return "timer-contact"; | return "timer-contact"; | ||||
| } | } | ||||
| else if constexpr (std::is_same_v<Config, CounterContactNodeConfig>) | |||||
| { | |||||
| return "counter-contact"; | |||||
| } | |||||
| else if constexpr (std::is_same_v<Config, CoilNodeConfig>) | else if constexpr (std::is_same_v<Config, CoilNodeConfig>) | ||||
| { | { | ||||
| return "coil"; | return "coil"; | ||||
| @@ -856,7 +864,22 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) | |||||
| { | { | ||||
| return "ton"; | return "ton"; | ||||
| } | } | ||||
| return "compare"; | |||||
| else if constexpr (std::is_same_v<Config, CounterNodeConfig>) | |||||
| { | |||||
| return "counter"; | |||||
| } | |||||
| else if constexpr (std::is_same_v<Config, MoveNodeConfig>) | |||||
| { | |||||
| return "move"; | |||||
| } | |||||
| else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>) | |||||
| { | |||||
| return "arithmetic"; | |||||
| } | |||||
| else | |||||
| { | |||||
| return "compare"; | |||||
| } | |||||
| }, | }, | ||||
| config); | config); | ||||
| } | } | ||||
| @@ -53,6 +53,18 @@ QString timerAddressText(const TimerAddress &address) | |||||
| return QString::fromStdString(address.toString()); | 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( | void drawRegisterComment( | ||||
| QPainter *painter, const QString &comment, qreal top = 22.0) | QPainter *painter, const QString &comment, qreal top = 22.0) | ||||
| { | { | ||||
| @@ -112,6 +124,11 @@ QString nodeToolTip(const LogicNodeConfig &config) | |||||
| return LogicEditorWidget::tr("T 触点:%1") | return LogicEditorWidget::tr("T 触点:%1") | ||||
| .arg(timerAddressText(value.address)); | .arg(timerAddressText(value.address)); | ||||
| } | } | ||||
| else if constexpr (std::is_same_v<Config, CounterContactNodeConfig>) | |||||
| { | |||||
| return LogicEditorWidget::tr("C 触点:%1") | |||||
| .arg(counterAddressText(value.address)); | |||||
| } | |||||
| else if constexpr (std::is_same_v<Config, CoilNodeConfig>) | else if constexpr (std::is_same_v<Config, CoilNodeConfig>) | ||||
| { | { | ||||
| return LogicEditorWidget::tr("输出线圈:%1") | return LogicEditorWidget::tr("输出线圈:%1") | ||||
| @@ -124,12 +141,37 @@ QString nodeToolTip(const LogicNodeConfig &config) | |||||
| .arg(comparisonText(value.comparison)) | .arg(comparisonText(value.comparison)) | ||||
| .arg(value.value); | .arg(value.value); | ||||
| } | } | ||||
| else | |||||
| else if constexpr (std::is_same_v<Config, TonNodeConfig>) | |||||
| { | { | ||||
| return LogicEditorWidget::tr("TON:%1,预设 %2 ms") | return LogicEditorWidget::tr("TON:%1,预设 %2 ms") | ||||
| .arg(timerAddressText(value.address)) | .arg(timerAddressText(value.address)) | ||||
| .arg(value.presetMs); | .arg(value.presetMs); | ||||
| } | } | ||||
| else if constexpr (std::is_same_v<Config, CounterNodeConfig>) | |||||
| { | |||||
| 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<Config, MoveNodeConfig>) | |||||
| { | |||||
| 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); | config); | ||||
| } | } | ||||
| @@ -327,6 +369,24 @@ public: | |||||
| Qt::AlignCenter, | Qt::AlignCenter, | ||||
| configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >")); | configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >")); | ||||
| } | } | ||||
| else if (const auto *counter_contact = | |||||
| std::get_if<CounterContactNodeConfig>(&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<CoilNodeConfig>(&config_)) | else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_)) | ||||
| { | { | ||||
| painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white); | painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white); | ||||
| @@ -391,6 +451,69 @@ public: | |||||
| configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs) | configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs) | ||||
| : tr("< 预设时间 >")); | : tr("< 预设时间 >")); | ||||
| } | } | ||||
| else if (const auto *counter = std::get_if<CounterNodeConfig>(&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<MoveNodeConfig>(&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<ArithmeticNodeConfig>(&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_; } | const std::string &nodeId() const { return node_id_; } | ||||
| @@ -766,7 +889,9 @@ void LogicEditorWidget::reloadLogic() | |||||
| }(), | }(), | ||||
| [&rung, this, rung_active] | [&rung, this, rung_active] | ||||
| { | { | ||||
| return std::holds_alternative<TonNodeConfig>(rung.output->config) | |||||
| return (std::holds_alternative<TonNodeConfig>(rung.output->config) | |||||
| || std::holds_alternative<CounterNodeConfig>( | |||||
| rung.output->config)) | |||||
| ? traceValue( | ? traceValue( | ||||
| trace_, | trace_, | ||||
| &LogicTraceSnapshot::nodeValues, | &LogicTraceSnapshot::nodeValues, | ||||
| @@ -0,0 +1,206 @@ | |||||
| #include "logic_instruction_dialog.h" | |||||
| #include "ui_logic_instruction_dialog.h" | |||||
| #include <QComboBox> | |||||
| #include <QDialogButtonBox> | |||||
| #include <QLabel> | |||||
| #include <QSpinBox> | |||||
| #include <limits> | |||||
| 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<Ui::LogicInstructionDialog>()), | |||||
| 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<int>(&QComboBox::currentIndexChanged), | |||||
| this, | |||||
| &LogicInstructionDialog::updateOperandRanges); | |||||
| } | |||||
| if (const auto *counter = std::get_if<CounterNodeConfig>(&config)) | |||||
| { | |||||
| configureCounter(*counter); | |||||
| } | |||||
| else if (const auto *move = std::get_if<MoveNodeConfig>(&config)) | |||||
| { | |||||
| configureMove(*move); | |||||
| } | |||||
| else if (const auto *arithmetic = std::get_if<ArithmeticNodeConfig>(&config)) | |||||
| { | |||||
| configureArithmetic(*arithmetic); | |||||
| } | |||||
| updateOperandRanges(); | |||||
| } | |||||
| LogicInstructionDialog::~LogicInstructionDialog() = default; | |||||
| LogicNodeConfig LogicInstructionDialog::config() const | |||||
| { | |||||
| if (std::holds_alternative<CounterNodeConfig>(original_config_)) | |||||
| { | |||||
| return CounterNodeConfig{ | |||||
| CounterAddress{ui_->counterAddressSpinBox->value()}, | |||||
| static_cast<CounterMode>(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<MoveNodeConfig>(original_config_)) | |||||
| { | |||||
| return MoveNodeConfig{ | |||||
| operandFrom(ui_->leftOperandKindComboBox, ui_->leftOperandValueSpinBox), | |||||
| RegisterAddress{ | |||||
| RegisterArea::D, | |||||
| ui_->destinationAddressSpinBox->value()}}; | |||||
| } | |||||
| const auto *arithmetic = std::get_if<ArithmeticNodeConfig>(&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<int>(CounterMode::Up)); | |||||
| ui_->counterModeComboBox->addItem( | |||||
| tr("CTD 减计数"), static_cast<int>(CounterMode::Down)); | |||||
| ui_->counterModeComboBox->setCurrentIndex( | |||||
| ui_->counterModeComboBox->findData(static_cast<int>(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<std::int16_t>::min()); | |||||
| spin->setMaximum(register_operand | |||||
| ? RegisterAddress::kMaximumIndex | |||||
| : std::numeric_limits<std::int16_t>::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<std::int16_t>(value_spin->value())}; | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| #pragma once | |||||
| #include "domain/control_logic_model.h" | |||||
| #include <QDialog> | |||||
| #include <memory> | |||||
| 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::LogicInstructionDialog> ui_; | |||||
| LogicNodeConfig original_config_; | |||||
| }; | |||||
| @@ -0,0 +1,66 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>LogicInstructionDialog</class> | |||||
| <widget class="QDialog" name="LogicInstructionDialog"> | |||||
| <property name="geometry"> | |||||
| <rect><x>0</x><y>0</y><width>420</width><height>310</height></rect> | |||||
| </property> | |||||
| <property name="windowTitle"><string>配置逻辑指令</string></property> | |||||
| <layout class="QVBoxLayout" name="verticalLayout"> | |||||
| <item> | |||||
| <widget class="QStackedWidget" name="instructionStackedWidget"> | |||||
| <widget class="QWidget" name="counterPage"> | |||||
| <layout class="QFormLayout" name="counterFormLayout"> | |||||
| <item row="0" column="0"><widget class="QLabel" name="counterModeLabel"><property name="text"><string>计数方式</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QComboBox" name="counterModeComboBox"/></item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="counterAddressLabel"><property name="text"><string>计数器资源</string></property></widget></item> | |||||
| <item row="1" column="1"><widget class="QSpinBox" name="counterAddressSpinBox"><property name="prefix"><string>C</string></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="counterCurrentAddressLabel"><property name="text"><string>当前值 CV</string></property></widget></item> | |||||
| <item row="2" column="1"><widget class="QSpinBox" name="counterCurrentAddressSpinBox"><property name="prefix"><string>D</string></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="3" column="0"><widget class="QLabel" name="counterPresetLabel"><property name="text"><string>预设值 PV</string></property></widget></item> | |||||
| <item row="3" column="1"> | |||||
| <widget class="QWidget" name="counterPresetEditor" native="true"> | |||||
| <layout class="QHBoxLayout" name="counterPresetLayout"><property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property> | |||||
| <item><widget class="QComboBox" name="counterPresetKindComboBox"/></item> | |||||
| <item><widget class="QSpinBox" name="counterPresetValueSpinBox"><property name="maximum"><number>32767</number></property><property name="value"><number>10</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="4" column="0"><widget class="QLabel" name="counterResetAddressLabel"><property name="text"><string>复位输入</string></property></widget></item> | |||||
| <item row="4" column="1"><widget class="QSpinBox" name="counterResetAddressSpinBox"><property name="prefix"><string>M</string></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| <widget class="QWidget" name="dataInstructionPage"> | |||||
| <layout class="QFormLayout" name="dataInstructionFormLayout"> | |||||
| <item row="0" column="0"><widget class="QLabel" name="instructionCaptionLabel"><property name="text"><string>指令</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QLabel" name="instructionNameLabel"><property name="text"><string>MOVE</string></property></widget></item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="leftOperandLabel"><property name="text"><string>源操作数</string></property></widget></item> | |||||
| <item row="1" column="1"> | |||||
| <widget class="QWidget" name="leftOperandEditor" native="true"> | |||||
| <layout class="QHBoxLayout" name="leftOperandLayout"><property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property> | |||||
| <item><widget class="QComboBox" name="leftOperandKindComboBox"/></item> | |||||
| <item><widget class="QSpinBox" name="leftOperandValueSpinBox"><property name="minimum"><number>-32768</number></property><property name="maximum"><number>32767</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="rightOperandLabel"><property name="text"><string>右操作数</string></property></widget></item> | |||||
| <item row="2" column="1"> | |||||
| <widget class="QWidget" name="rightOperandEditor" native="true"> | |||||
| <layout class="QHBoxLayout" name="rightOperandLayout"><property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property> | |||||
| <item><widget class="QComboBox" name="rightOperandKindComboBox"/></item> | |||||
| <item><widget class="QSpinBox" name="rightOperandValueSpinBox"><property name="minimum"><number>-32768</number></property><property name="maximum"><number>32767</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="3" column="0"><widget class="QLabel" name="destinationAddressLabel"><property name="text"><string>目标地址</string></property></widget></item> | |||||
| <item row="3" column="1"><widget class="QSpinBox" name="destinationAddressSpinBox"><property name="prefix"><string>D</string></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </widget> | |||||
| </item> | |||||
| <item><widget class="QDialogButtonBox" name="buttonBox"><property name="standardButtons"><set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| <resources/> | |||||
| <connections/> | |||||
| </ui> | |||||
| @@ -4,6 +4,7 @@ | |||||
| #include "alarm_configuration_dialog.h" | #include "alarm_configuration_dialog.h" | ||||
| #include "register_comment_dialog.h" | #include "register_comment_dialog.h" | ||||
| #include "logic_editor_widget.h" | #include "logic_editor_widget.h" | ||||
| #include "logic_instruction_dialog.h" | |||||
| #include "plc_connection_dialog.h" | #include "plc_connection_dialog.h" | ||||
| #include "runtime_monitor_widget.h" | #include "runtime_monitor_widget.h" | ||||
| #include "free_monitor_widget.h" | #include "free_monitor_widget.h" | ||||
| @@ -342,6 +343,7 @@ void MainWindow::configureActions() | |||||
| QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点")); | QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点")); | ||||
| QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点")); | QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点")); | ||||
| QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点")); | QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点")); | ||||
| QAction *parallel_counter = parallel_menu->addAction(tr("并联 C 触点")); | |||||
| QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件")); | QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件")); | ||||
| connect(parallel_open, &QAction::triggered, | connect(parallel_open, &QAction::triggered, | ||||
| this, | this, | ||||
| @@ -380,6 +382,13 @@ void MainWindow::configureActions() | |||||
| addLogicParallelBranch(TimerContactNodeConfig{ | addLogicParallelBranch(TimerContactNodeConfig{ | ||||
| TimerAddress{0}, ContactMode::NormallyOpen}); | TimerAddress{0}, ContactMode::NormallyOpen}); | ||||
| }); | }); | ||||
| connect(parallel_counter, &QAction::triggered, | |||||
| this, | |||||
| [this] | |||||
| { | |||||
| addLogicParallelBranch(CounterContactNodeConfig{ | |||||
| CounterAddress{0}, ContactMode::NormallyOpen}); | |||||
| }); | |||||
| connect(parallel_compare, &QAction::triggered, | connect(parallel_compare, &QAction::triggered, | ||||
| this, | this, | ||||
| [this] | [this] | ||||
| @@ -432,6 +441,13 @@ void MainWindow::configureActions() | |||||
| addLogicCondition(TimerContactNodeConfig{ | addLogicCondition(TimerContactNodeConfig{ | ||||
| TimerAddress{0}, ContactMode::NormallyOpen}); | TimerAddress{0}, ContactMode::NormallyOpen}); | ||||
| }); | }); | ||||
| connect(ui_->addCounterContactAction, &QAction::triggered, | |||||
| this, | |||||
| [this] | |||||
| { | |||||
| addLogicCondition(CounterContactNodeConfig{ | |||||
| CounterAddress{0}, ContactMode::NormallyOpen}); | |||||
| }); | |||||
| connect(ui_->addNormalCoilAction, &QAction::triggered, | connect(ui_->addNormalCoilAction, &QAction::triggered, | ||||
| this, | this, | ||||
| [this] | [this] | ||||
| @@ -462,6 +478,77 @@ void MainWindow::configureActions() | |||||
| { | { | ||||
| setLogicOutput(TonNodeConfig{TimerAddress{0}, 1000}); | 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, | connect(ui_->addCompareAction, &QAction::triggered, | ||||
| this, | this, | ||||
| [this] | [this] | ||||
| @@ -544,10 +631,16 @@ void MainWindow::configureAppearance() | |||||
| ui_->addRisingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); | ui_->addRisingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); | ||||
| ui_->addFallingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); | ui_->addFallingEdgeAction->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); | ||||
| ui_->addTimerContactAction->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_->addNormalCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton)); | ||||
| ui_->addSetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogYesButton)); | ui_->addSetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogYesButton)); | ||||
| ui_->addResetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogNoButton)); | ui_->addResetCoilAction->setIcon(style()->standardIcon(QStyle::SP_DialogNoButton)); | ||||
| ui_->addTonAction->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); | 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_->addCompareAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogInfoView)); | ||||
| ui_->editRungCommentAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView)); | ui_->editRungCommentAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView)); | ||||
| ui_->deleteLogicAction->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); | ui_->deleteLogicAction->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); | ||||
| @@ -1443,6 +1536,21 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) | |||||
| static_cast<int>(timer_contact->mode))); | static_cast<int>(timer_contact->mode))); | ||||
| ui_->logicModeComboBox->setEnabled(true); | ui_->logicModeComboBox->setEnabled(true); | ||||
| } | } | ||||
| else if (const auto *counter_contact = | |||||
| std::get_if<CounterContactNodeConfig>(&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<int>(ContactMode::NormallyOpen)); | |||||
| ui_->logicModeComboBox->addItem( | |||||
| tr("常闭"), static_cast<int>(ContactMode::NormallyClosed)); | |||||
| ui_->logicModeComboBox->setCurrentIndex( | |||||
| ui_->logicModeComboBox->findData( | |||||
| static_cast<int>(counter_contact->mode))); | |||||
| ui_->logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *coil = std::get_if<CoilNodeConfig>(&node->config)) | else if (const auto *coil = std::get_if<CoilNodeConfig>(&node->config)) | ||||
| { | { | ||||
| ui_->logicNodeTypeLabel->setText(tr("线圈")); | ui_->logicNodeTypeLabel->setText(tr("线圈")); | ||||
| @@ -1472,16 +1580,47 @@ void MainWindow::showLogicNodeProperties(const std::string &node_id) | |||||
| ui_->logicValueSpinBox->setEnabled(true); | ui_->logicValueSpinBox->setEnabled(true); | ||||
| ui_->logicValueSpinBox->setValue(compare->value); | ui_->logicValueSpinBox->setValue(compare->value); | ||||
| } | } | ||||
| else | |||||
| else if (const auto *ton = std::get_if<TonNodeConfig>(&node->config)) | |||||
| { | { | ||||
| const auto &ton = std::get<TonNodeConfig>(node->config); | |||||
| ui_->logicNodeTypeLabel->setText(tr("TON")); | ui_->logicNodeTypeLabel->setText(tr("TON")); | ||||
| ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); | ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); | ||||
| ui_->logicAddressSpinBox->setValue(ton.address.index()); | |||||
| ui_->logicAddressSpinBox->setValue(ton->address.index()); | |||||
| ui_->logicModeComboBox->addItem(tr("非保持接通延时")); | ui_->logicModeComboBox->addItem(tr("非保持接通延时")); | ||||
| ui_->logicModeComboBox->setEnabled(false); | ui_->logicModeComboBox->setEnabled(false); | ||||
| ui_->logicPresetSpinBox->setEnabled(true); | ui_->logicPresetSpinBox->setEnabled(true); | ||||
| ui_->logicPresetSpinBox->setValue(ton.presetMs); | |||||
| ui_->logicPresetSpinBox->setValue(ton->presetMs); | |||||
| } | |||||
| else if (const auto *counter = std::get_if<CounterNodeConfig>(&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<MoveNodeConfig>(&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<ArithmeticNodeConfig>(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) | if (!node->configured) | ||||
| { | { | ||||
| @@ -1628,6 +1767,36 @@ void MainWindow::setLogicOutput(const LogicNodeConfig &config) | |||||
| statusBar()->showMessage(tr("逻辑输出已设置"), 3000); | 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() | void MainWindow::addLogicRung() | ||||
| { | { | ||||
| const LogicEditorResult result = logic_editor_widget_->addRung(); | const LogicEditorResult result = logic_editor_widget_->addRung(); | ||||
| @@ -1719,6 +1888,12 @@ void MainWindow::applySelectedLogicNodeProperties() | |||||
| TimerAddress{ui_->logicAddressSpinBox->value()}, | TimerAddress{ui_->logicAddressSpinBox->value()}, | ||||
| static_cast<ContactMode>(ui_->logicModeComboBox->currentData().toInt())}; | static_cast<ContactMode>(ui_->logicModeComboBox->currentData().toInt())}; | ||||
| } | } | ||||
| else if (std::holds_alternative<CounterContactNodeConfig>(config)) | |||||
| { | |||||
| config = CounterContactNodeConfig{ | |||||
| CounterAddress{ui_->logicAddressSpinBox->value()}, | |||||
| static_cast<ContactMode>(ui_->logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<CoilNodeConfig>(config)) | else if (std::holds_alternative<CoilNodeConfig>(config)) | ||||
| { | { | ||||
| config = CoilNodeConfig{ | config = CoilNodeConfig{ | ||||
| @@ -1733,12 +1908,21 @@ void MainWindow::applySelectedLogicNodeProperties() | |||||
| ui_->logicComparisonComboBox->currentData().toInt()), | ui_->logicComparisonComboBox->currentData().toInt()), | ||||
| static_cast<std::int16_t>(ui_->logicValueSpinBox->value())}; | static_cast<std::int16_t>(ui_->logicValueSpinBox->value())}; | ||||
| } | } | ||||
| else | |||||
| else if (std::holds_alternative<TonNodeConfig>(config)) | |||||
| { | { | ||||
| config = TonNodeConfig{ | config = TonNodeConfig{ | ||||
| TimerAddress{ui_->logicAddressSpinBox->value()}, | TimerAddress{ui_->logicAddressSpinBox->value()}, | ||||
| ui_->logicPresetSpinBox->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( | const LogicEditorResult result = logic_editor_service_.updateNodeConfig( | ||||
| current_logic_id_, selected_logic_node_id_, config); | current_logic_id_, selected_logic_node_id_, config); | ||||
| @@ -1956,10 +2140,16 @@ void MainWindow::updateModeUi(const QString &message) | |||||
| ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing); | ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing); | ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing); | ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addCounterContactAction->setEnabled(policy.allowsProjectEditing); | |||||
| ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing); | ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing); | ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing); | ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->addTonAction->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_->addCompareAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing); | ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing); | ||||
| ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing); | ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing); | ||||
| @@ -132,6 +132,7 @@ private: | |||||
| void addLogicParallelBranch(const LogicNodeConfig &config); | void addLogicParallelBranch(const LogicNodeConfig &config); | ||||
| // 设置当前网络右侧的输出线圈 | // 设置当前网络右侧的输出线圈 | ||||
| void setLogicOutput(const LogicNodeConfig &config); | void setLogicOutput(const LogicNodeConfig &config); | ||||
| void configureAndSetLogicOutput(const LogicNodeConfig &config); | |||||
| // 新增一个梯形图网络 | // 新增一个梯形图网络 | ||||
| void addLogicRung(); | void addLogicRung(); | ||||
| void editSelectedRungComment(); | void editSelectedRungComment(); | ||||
| @@ -330,10 +330,16 @@ | |||||
| <addaction name="addRisingEdgeAction"/> | <addaction name="addRisingEdgeAction"/> | ||||
| <addaction name="addFallingEdgeAction"/> | <addaction name="addFallingEdgeAction"/> | ||||
| <addaction name="addTimerContactAction"/> | <addaction name="addTimerContactAction"/> | ||||
| <addaction name="addCounterContactAction"/> | |||||
| <addaction name="addNormalCoilAction"/> | <addaction name="addNormalCoilAction"/> | ||||
| <addaction name="addSetCoilAction"/> | <addaction name="addSetCoilAction"/> | ||||
| <addaction name="addResetCoilAction"/> | <addaction name="addResetCoilAction"/> | ||||
| <addaction name="addTonAction"/> | <addaction name="addTonAction"/> | ||||
| <addaction name="addCtuAction"/> | |||||
| <addaction name="addCtdAction"/> | |||||
| <addaction name="addMoveAction"/> | |||||
| <addaction name="addAddAction"/> | |||||
| <addaction name="addSubAction"/> | |||||
| <addaction name="addCompareAction"/> | <addaction name="addCompareAction"/> | ||||
| <addaction name="editRungCommentAction"/> | <addaction name="editRungCommentAction"/> | ||||
| <addaction name="deleteLogicAction"/> | <addaction name="deleteLogicAction"/> | ||||
| @@ -1018,6 +1024,10 @@ | |||||
| <string>添加离线仿真定时器触点</string> | <string>添加离线仿真定时器触点</string> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="addCounterContactAction"> | |||||
| <property name="text"><string>C 触点</string></property> | |||||
| <property name="toolTip"><string>添加离线仿真计数器完成触点</string></property> | |||||
| </action> | |||||
| <action name="addNormalCoilAction"> | <action name="addNormalCoilAction"> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>线圈</string> | <string>线圈</string> | ||||
| @@ -1050,6 +1060,26 @@ | |||||
| <string>设置非保持接通延时定时器输出</string> | <string>设置非保持接通延时定时器输出</string> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="addCtuAction"> | |||||
| <property name="text"><string>CTU</string></property> | |||||
| <property name="toolTip"><string>设置按上升沿执行的加计数器</string></property> | |||||
| </action> | |||||
| <action name="addCtdAction"> | |||||
| <property name="text"><string>CTD</string></property> | |||||
| <property name="toolTip"><string>设置按上升沿执行的减计数器</string></property> | |||||
| </action> | |||||
| <action name="addMoveAction"> | |||||
| <property name="text"><string>MOVE</string></property> | |||||
| <property name="toolTip"><string>设置 D 数据传送指令</string></property> | |||||
| </action> | |||||
| <action name="addAddAction"> | |||||
| <property name="text"><string>ADD</string></property> | |||||
| <property name="toolTip"><string>设置带饱和处理的加法指令</string></property> | |||||
| </action> | |||||
| <action name="addSubAction"> | |||||
| <property name="text"><string>SUB</string></property> | |||||
| <property name="toolTip"><string>设置带饱和处理的减法指令</string></property> | |||||
| </action> | |||||
| <action name="addCompareAction"> | <action name="addCompareAction"> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>D 比较</string> | <string>D 比较</string> | ||||
| @@ -18,6 +18,7 @@ SOURCES += \ | |||||
| ../src/ui/runtime_monitor_widget.cpp \ | ../src/ui/runtime_monitor_widget.cpp \ | ||||
| ../src/ui/hmi_editor_widget.cpp \ | ../src/ui/hmi_editor_widget.cpp \ | ||||
| ../src/ui/logic_editor_widget.cpp \ | ../src/ui/logic_editor_widget.cpp \ | ||||
| ../src/ui/logic_instruction_dialog.cpp \ | |||||
| ../src/domain/register_address.cpp \ | ../src/domain/register_address.cpp \ | ||||
| ../src/domain/register_repository.cpp \ | ../src/domain/register_repository.cpp \ | ||||
| ../src/domain/active_register_repository.cpp \ | ../src/domain/active_register_repository.cpp \ | ||||
| @@ -49,6 +50,7 @@ HEADERS += \ | |||||
| ../src/ui/runtime_monitor_widget.h \ | ../src/ui/runtime_monitor_widget.h \ | ||||
| ../src/ui/hmi_editor_widget.h \ | ../src/ui/hmi_editor_widget.h \ | ||||
| ../src/ui/logic_editor_widget.h \ | ../src/ui/logic_editor_widget.h \ | ||||
| ../src/ui/logic_instruction_dialog.h \ | |||||
| ../src/domain/register_address.h \ | ../src/domain/register_address.h \ | ||||
| ../src/domain/register_repository.h \ | ../src/domain/register_repository.h \ | ||||
| ../src/domain/active_register_repository.h \ | ../src/domain/active_register_repository.h \ | ||||
| @@ -81,4 +83,5 @@ FORMS += \ | |||||
| ../src/ui/register_comment_dialog.ui \ | ../src/ui/register_comment_dialog.ui \ | ||||
| ../src/ui/plc_connection_dialog.ui \ | ../src/ui/plc_connection_dialog.ui \ | ||||
| ../src/ui/free_monitor_widget.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 | |||||