Explorar el Código

feat: 增加逻辑指令编辑配置界面

main
suyu hace 1 mes
padre
commit
aedead25df
Se han modificado 10 ficheros con 701 adiciones y 11 borrados
  1. +3
    -0
      app/integrated_platform.pro
  2. +26
    -3
      app/src/services/logic_editor_service.cpp
  3. +127
    -2
      app/src/ui/logic_editor_widget.cpp
  4. +206
    -0
      app/src/ui/logic_instruction_dialog.cpp
  5. +43
    -0
      app/src/ui/logic_instruction_dialog.h
  6. +66
    -0
      app/src/ui/logic_instruction_dialog.ui
  7. +195
    -5
      app/src/ui/main_window.cpp
  8. +1
    -0
      app/src/ui/main_window.h
  9. +30
    -0
      app/src/ui/main_window.ui
  10. +4
    -1
      app/tests/main_window_tests.pro

+ 3
- 0
app/integrated_platform.pro Ver fichero

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


+ 26
- 3
app/src/services/logic_editor_service.cpp Ver fichero

@@ -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<ContactNodeConfig>(config)
|| std::holds_alternative<EdgeContactNodeConfig>(config)
|| std::holds_alternative<TimerContactNodeConfig>(config)
|| std::holds_alternative<CounterContactNodeConfig>(config)
|| std::holds_alternative<CompareNodeConfig>(config);
}

bool LogicEditorService::isOutputConfig(const LogicNodeConfig &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)
@@ -848,6 +852,10 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config)
{
return "timer-contact";
}
else if constexpr (std::is_same_v<Config, CounterContactNodeConfig>)
{
return "counter-contact";
}
else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
{
return "coil";
@@ -856,7 +864,22 @@ std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config)
{
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);
}


+ 127
- 2
app/src/ui/logic_editor_widget.cpp Ver fichero

@@ -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<Config, CounterContactNodeConfig>)
{
return LogicEditorWidget::tr("C 触点:%1")
.arg(counterAddressText(value.address));
}
else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
{
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<Config, TonNodeConfig>)
{
return LogicEditorWidget::tr("TON:%1,预设 %2 ms")
.arg(timerAddressText(value.address))
.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);
}
@@ -327,6 +369,24 @@ public:
Qt::AlignCenter,
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_))
{
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<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_; }
@@ -766,7 +889,9 @@ void LogicEditorWidget::reloadLogic()
}(),
[&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(
trace_,
&LogicTraceSnapshot::nodeValues,


+ 206
- 0
app/src/ui/logic_instruction_dialog.cpp Ver fichero

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

+ 43
- 0
app/src/ui/logic_instruction_dialog.h Ver fichero

@@ -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_;
};

+ 66
- 0
app/src/ui/logic_instruction_dialog.ui Ver fichero

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

+ 195
- 5
app/src/ui/main_window.cpp Ver fichero

@@ -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<int>(timer_contact->mode)));
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))
{
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<TonNodeConfig>(&node->config))
{
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_->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<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)
{
@@ -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<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))
{
config = CoilNodeConfig{
@@ -1733,12 +1908,21 @@ void MainWindow::applySelectedLogicNodeProperties()
ui_->logicComparisonComboBox->currentData().toInt()),
static_cast<std::int16_t>(ui_->logicValueSpinBox->value())};
}
else
else if (std::holds_alternative<TonNodeConfig>(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);


+ 1
- 0
app/src/ui/main_window.h Ver fichero

@@ -132,6 +132,7 @@ private:
void addLogicParallelBranch(const LogicNodeConfig &config);
// 设置当前网络右侧的输出线圈
void setLogicOutput(const LogicNodeConfig &config);
void configureAndSetLogicOutput(const LogicNodeConfig &config);
// 新增一个梯形图网络
void addLogicRung();
void editSelectedRungComment();


+ 30
- 0
app/src/ui/main_window.ui Ver fichero

@@ -330,10 +330,16 @@
<addaction name="addRisingEdgeAction"/>
<addaction name="addFallingEdgeAction"/>
<addaction name="addTimerContactAction"/>
<addaction name="addCounterContactAction"/>
<addaction name="addNormalCoilAction"/>
<addaction name="addSetCoilAction"/>
<addaction name="addResetCoilAction"/>
<addaction name="addTonAction"/>
<addaction name="addCtuAction"/>
<addaction name="addCtdAction"/>
<addaction name="addMoveAction"/>
<addaction name="addAddAction"/>
<addaction name="addSubAction"/>
<addaction name="addCompareAction"/>
<addaction name="editRungCommentAction"/>
<addaction name="deleteLogicAction"/>
@@ -1018,6 +1024,10 @@
<string>添加离线仿真定时器触点</string>
</property>
</action>
<action name="addCounterContactAction">
<property name="text"><string>C 触点</string></property>
<property name="toolTip"><string>添加离线仿真计数器完成触点</string></property>
</action>
<action name="addNormalCoilAction">
<property name="text">
<string>线圈</string>
@@ -1050,6 +1060,26 @@
<string>设置非保持接通延时定时器输出</string>
</property>
</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">
<property name="text">
<string>D 比较</string>


+ 4
- 1
app/tests/main_window_tests.pro Ver fichero

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

Cargando…
Cancelar
Guardar