|
- #include "logic_editor_widget.h"
-
- #include <QGraphicsItem>
- #include <QGraphicsScene>
- #include <QGraphicsTextItem>
- #include <QPainter>
- #include <QPainterPath>
- #include <QResizeEvent>
- #include <QStyleOptionGraphicsItem>
-
- #include <algorithm>
- #include <type_traits>
-
- namespace {
-
- constexpr qreal kSceneMargin = 28.0;
- constexpr qreal kRailInset = 40.0;
- constexpr qreal kMinimumSceneWidth = 980.0;
- constexpr qreal kCellWidth = 128.0;
- constexpr qreal kCellHeight = 120.0;
- constexpr qreal kNodeTerminalX = 50.0;
- constexpr qreal kRungHeaderHeight = 52.0;
- constexpr qreal kRungGap = 18.0;
- constexpr qreal kLadderLineWidth = 1.8;
- constexpr int kMinimumLogicColumns = 7;
- const QColor kLadderColor(QStringLiteral("#263842"));
- const QColor kActiveColor(QStringLiteral("#16854f"));
- const QColor kFaultColor(QStringLiteral("#c5362e"));
- const QColor kSelectionColor(QStringLiteral("#dfeef5"));
- const QColor kSelectionBorderColor(QStringLiteral("#277da1"));
- const QColor kGridColor(QStringLiteral("#e8edf0"));
- const QColor kPlaceholderColor(QStringLiteral("#81919b"));
-
- struct ExpressionMetrics
- {
- int columns = 1;
- int rows = 1;
- };
-
- struct RenderResult
- {
- QPointF input;
- QPointF output;
- };
-
- QString registerAddressText(const RegisterAddress &address)
- {
- return QString::fromStdString(address.toString());
- }
-
- 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)
- {
- if (comment.isEmpty())
- {
- return;
- }
- QFont font = painter->font();
- font.setPointSizeF(8.0);
- painter->setFont(font);
- const QString visible_comment = painter->fontMetrics().elidedText(
- comment, Qt::ElideRight, static_cast<int>(kCellWidth - 8.0));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, top, kCellWidth, 18),
- Qt::AlignCenter | Qt::TextSingleLine,
- visible_comment);
- }
-
- QString comparisonText(ComparisonOperator comparison)
- {
- switch (comparison)
- {
- case ComparisonOperator::Equal: return QStringLiteral("=");
- case ComparisonOperator::NotEqual: return QStringLiteral("<>");
- case ComparisonOperator::LessThan: return QStringLiteral("<");
- case ComparisonOperator::LessThanOrEqual: return QStringLiteral("<=");
- case ComparisonOperator::GreaterThan: return QStringLiteral(">");
- case ComparisonOperator::GreaterThanOrEqual: return QStringLiteral(">=");
- }
- return QStringLiteral("?");
- }
-
- QString nodeToolTip(const LogicNodeConfig &config)
- {
- return std::visit(
- [](const auto &value) -> QString
- {
- using Config = std::decay_t<decltype(value)>;
- if constexpr (std::is_same_v<Config, ContactNodeConfig>)
- {
- return LogicEditorWidget::tr("%1触点:%2")
- .arg(value.mode == ContactMode::NormallyOpen
- ? LogicEditorWidget::tr("常开")
- : LogicEditorWidget::tr("常闭"))
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
- {
- return LogicEditorWidget::tr("%1沿触点:%2")
- .arg(value.mode == EdgeMode::Rising
- ? LogicEditorWidget::tr("上升")
- : LogicEditorWidget::tr("下降"))
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, TimerContactNodeConfig>)
- {
- return LogicEditorWidget::tr("T 触点:%1")
- .arg(timerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CounterContactNodeConfig>)
- {
- return LogicEditorWidget::tr("C 触点:%1")
- .arg(counterAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
- {
- return LogicEditorWidget::tr("输出线圈:%1")
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
- {
- return LogicEditorWidget::tr("比较条件:%1 %2 %3")
- .arg(registerAddressText(value.address))
- .arg(comparisonText(value.comparison))
- .arg(value.value);
- }
- else 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);
- }
-
- ExpressionMetrics measureExpression(const ConditionExpression &expression)
- {
- if (expression.kind == ConditionExpressionKind::Node)
- {
- return {};
- }
- if (expression.kind == ConditionExpressionKind::Wire)
- {
- return {expression.wire->columnSpan, 1};
- }
-
- ExpressionMetrics metrics{0, 0};
- if (expression.kind == ConditionExpressionKind::Series)
- {
- for (const ConditionExpression &child : expression.children)
- {
- const ExpressionMetrics child_metrics = measureExpression(child);
- metrics.columns += child_metrics.columns;
- metrics.rows = std::max(metrics.rows, child_metrics.rows);
- }
- }
- else
- {
- for (const ConditionExpression &child : expression.children)
- {
- const ExpressionMetrics child_metrics = measureExpression(child);
- metrics.columns = std::max(metrics.columns, child_metrics.columns);
- metrics.rows += child_metrics.rows;
- }
- }
- return metrics;
- }
-
- QPen ladderPen(bool active)
- {
- return QPen(active ? kActiveColor : kLadderColor,
- active ? 2.6 : kLadderLineWidth);
- }
-
- bool traceValue(
- const LogicTraceSnapshot &trace,
- const std::unordered_map<std::string, bool> LogicTraceSnapshot::*member,
- const std::string &id)
- {
- const auto &values = trace.*member;
- const auto found = values.find(id);
- return found != values.end() && found->second;
- }
-
- void addGrid(
- QGraphicsScene &scene,
- qreal left,
- qreal top,
- int columns,
- int rows)
- {
- QPen pen(kGridColor, 1.0);
- pen.setCosmetic(true);
- for (int column = 0; column <= columns; ++column)
- {
- const qreal x = left + static_cast<qreal>(column) * kCellWidth;
- QGraphicsLineItem *line = scene.addLine(
- QLineF(x, top, x, top + static_cast<qreal>(rows) * kCellHeight), pen);
- line->setZValue(-10.0);
- }
- for (int row = 0; row <= rows; ++row)
- {
- const qreal y = top + static_cast<qreal>(row) * kCellHeight;
- QGraphicsLineItem *line = scene.addLine(
- QLineF(left, y, left + static_cast<qreal>(columns) * kCellWidth, y), pen);
- line->setZValue(-10.0);
- }
- }
-
- void addPlaceholder(QGraphicsScene &scene, const QRectF &cell, const QString &text)
- {
- const QRectF bounds = cell.adjusted(12, 18, -12, -18);
- QGraphicsRectItem *item = scene.addRect(
- bounds,
- QPen(kPlaceholderColor, 1.2, Qt::DashLine),
- QBrush(QColor(255, 255, 255, 220)));
- item->setZValue(1.0);
- QGraphicsTextItem *label = scene.addText(text);
- label->setDefaultTextColor(kPlaceholderColor);
- label->setPos(
- bounds.center().x() - label->boundingRect().width() / 2.0,
- bounds.center().y() - label->boundingRect().height() / 2.0);
- label->setZValue(2.0);
- }
-
- } // namespace
-
- class LogicEditorWidget::NodeItem final : public QGraphicsItem
- {
- public:
- NodeItem(
- const LogicNode &node,
- const std::string &rung_id,
- const QPointF ¢er,
- const QString ®ister_comment,
- bool active,
- bool faulted,
- bool condition)
- : node_id_(node.id),
- rung_id_(rung_id),
- config_(node.config),
- register_comment_(register_comment),
- configured_(node.configured),
- active_(active),
- faulted_(faulted),
- condition_(condition)
- {
- setPos(center);
- setFlag(ItemIsSelectable, true);
- setZValue(3.0);
- QString tool_tip = configured_
- ? nodeToolTip(config_)
- : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数");
- if (!register_comment_.isEmpty())
- {
- tool_tip += LogicEditorWidget::tr("\n注释:%1").arg(register_comment_);
- }
- setToolTip(tool_tip);
- }
-
- QRectF boundingRect() const override
- {
- return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
- {
- painter->setRenderHint(QPainter::Antialiasing, true);
- const bool selected = (option->state & QStyle::State_Selected) != 0;
- if (selected)
- {
- painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
- painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
- painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
- }
-
- const QColor symbol_color = faulted_ ? kFaultColor
- : selected ? kSelectionBorderColor : active_ ? kActiveColor : kLadderColor;
- painter->setPen(QPen(symbol_color, active_ || faulted_ ? 2.6 : kLadderLineWidth));
- QFont font = painter->font();
- font.setPointSizeF(9.5);
- painter->setFont(font);
-
- if (const auto *contact = std::get_if<ContactNodeConfig>(&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 (contact->mode == ContactMode::NormallyClosed)
- {
- painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
- }
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? registerAddressText(contact->address) : tr("< M 地址 >"));
- drawRegisterComment(painter, register_comment_);
- }
- else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&config_))
- {
- painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
- painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
- painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
- painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
- painter->drawLine(QPointF(18, -15), QPointF(18, 15));
- painter->drawText(
- QRectF(-14, -12, 28, 24),
- Qt::AlignCenter,
- edge->mode == EdgeMode::Rising ? QStringLiteral("P") : QStringLiteral("N"));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? registerAddressText(edge->address) : tr("< M 地址 >"));
- drawRegisterComment(painter, register_comment_);
- }
- else if (const auto *timer_contact =
- std::get_if<TimerContactNodeConfig>(&config_))
- {
- painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
- painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
- painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
- painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
- painter->drawLine(QPointF(18, -15), QPointF(18, 15));
- if (timer_contact->mode == ContactMode::NormallyClosed)
- {
- painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
- }
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >"));
- }
- else if (const auto *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);
- painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0));
- painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0));
- QPainterPath left;
- left.moveTo(-2, -20);
- left.cubicTo(-24, -16, -24, 16, -2, 20);
- painter->drawPath(left);
- QPainterPath right;
- right.moveTo(2, -20);
- right.cubicTo(24, -16, 24, 16, 2, 20);
- painter->drawPath(right);
- if (coil->mode != CoilMode::Normal)
- {
- painter->drawText(
- QRectF(-12, -14, 24, 28),
- Qt::AlignCenter,
- coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R"));
- }
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? registerAddressText(coil->address) : tr("< M 地址 >"));
- drawRegisterComment(painter, register_comment_);
- }
- else if (const auto *comparison = std::get_if<CompareNodeConfig>(&config_))
- {
- const QRectF box(-47, -17, 94, 34);
- 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("%1 INT").arg(comparisonText(comparison->comparison)));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? registerAddressText(comparison->address) : tr("< D 地址 >"));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? QString::number(comparison->value) : tr("< 常量 >"));
- drawRegisterComment(painter, register_comment_, 40.0);
- }
- else if (const auto *ton = std::get_if<TonNodeConfig>(&config_))
- {
- const QRectF box(-47, -18, 94, 36);
- painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
- painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
- painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
- painter->drawRect(box);
- painter->drawText(box, Qt::AlignCenter, QStringLiteral("TON"));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? timerAddressText(ton->address) : tr("< T 地址 >"));
- painter->drawText(
- QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
- Qt::AlignCenter,
- configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs)
- : tr("< 预设时间 >"));
- }
- 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 + 4.0, 20, kCellWidth - 8.0, 18),
- Qt::AlignCenter | Qt::TextSingleLine,
- configured_ ? QStringLiteral("CV %1")
- .arg(registerAddressText(counter->currentValueAddress))
- : tr("< CV >"));
- painter->drawText(
- QRectF(-kCellWidth / 2.0 + 4.0, 38, kCellWidth - 8.0, 18),
- Qt::AlignCenter | Qt::TextSingleLine,
- configured_ ? QStringLiteral("PV %1")
- .arg(wordOperandText(counter->preset))
- : tr("< 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 &rungId() const { return rung_id_; }
- bool isConditionNode() const { return condition_; }
-
- private:
- std::string node_id_;
- std::string rung_id_;
- LogicNodeConfig config_;
- QString register_comment_;
- bool configured_ = true;
- bool active_ = false;
- bool faulted_ = false;
- bool condition_ = true;
- };
-
- class LogicEditorWidget::WireItem final : public QGraphicsItem
- {
- public:
- WireItem(
- const std::string &expression_id,
- const std::string &rung_id,
- const QPointF ¢er,
- int column_span,
- bool active)
- : expression_id_(expression_id),
- rung_id_(rung_id),
- width_(static_cast<qreal>(column_span) * kCellWidth),
- active_(active)
- {
- setPos(center);
- setFlag(ItemIsSelectable, true);
- setZValue(2.0);
- setToolTip(LogicEditorWidget::tr("横线:%1 列,可用触点直接替换")
- .arg(column_span));
- }
-
- QRectF boundingRect() const override
- {
- return {-width_ / 2.0, -kCellHeight / 2.0, width_, kCellHeight};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
- {
- const bool selected = (option->state & QStyle::State_Selected) != 0;
- if (selected)
- {
- painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
- painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
- painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
- }
- painter->setPen(ladderPen(active_));
- painter->drawLine(QPointF(-width_ / 2.0, 0), QPointF(width_ / 2.0, 0));
- }
-
- const std::string &expressionId() const { return expression_id_; }
- const std::string &rungId() const { return rung_id_; }
-
- private:
- std::string expression_id_;
- std::string rung_id_;
- qreal width_ = kCellWidth;
- bool active_ = false;
- };
-
- class LogicEditorWidget::WireCellItem final : public QGraphicsItem
- {
- public:
- WireCellItem(
- const std::string &expression_id,
- const std::string &rung_id,
- int column_offset,
- const QPointF ¢er)
- : expression_id_(expression_id),
- rung_id_(rung_id),
- column_offset_(column_offset)
- {
- setPos(center);
- setFlag(ItemIsSelectable, true);
- setZValue(2.2);
- setToolTip(LogicEditorWidget::tr("横线网格:第 %1 格,可用触点原位替换")
- .arg(column_offset + 1));
- }
-
- QRectF boundingRect() const override
- {
- return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
- }
-
- void paint(
- QPainter *painter,
- const QStyleOptionGraphicsItem *option,
- QWidget *) override
- {
- if ((option->state & QStyle::State_Selected) == 0)
- {
- return;
- }
- painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
- painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
- painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
- painter->setPen(ladderPen(false));
- painter->drawLine(
- QPointF(-kCellWidth / 2.0, 0), QPointF(kCellWidth / 2.0, 0));
- }
-
- const std::string &expressionId() const { return expression_id_; }
- const std::string &rungId() const { return rung_id_; }
- int columnOffset() const { return column_offset_; }
-
- private:
- std::string expression_id_;
- std::string rung_id_;
- int column_offset_ = 0;
- };
-
- class LogicEditorWidget::EmptySlotItem final : public QGraphicsItem
- {
- public:
- EmptySlotItem(
- const std::string &rung_id,
- int column,
- const QPointF ¢er,
- bool show_label,
- std::string branch_expression_id = {},
- bool branch_active = false)
- : rung_id_(rung_id),
- branch_expression_id_(std::move(branch_expression_id)),
- column_(column),
- show_label_(show_label),
- branch_active_(branch_active)
- {
- setPos(center);
- setFlag(ItemIsSelectable, true);
- setZValue(2.0);
- setToolTip(
- branch_expression_id_.empty()
- ? LogicEditorWidget::tr(
- "空条件网格:第 %1 列,点击后可插入触点")
- .arg(column + 1)
- : LogicEditorWidget::tr(
- "并联空网格:第 %1 格,点击后可插入触点")
- .arg(column + 1));
- }
-
- QRectF boundingRect() const override
- {
- return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
- }
-
- void paint(
- QPainter *painter,
- const QStyleOptionGraphicsItem *option,
- QWidget *) override
- {
- const bool selected = (option->state & QStyle::State_Selected) != 0;
- if (selected)
- {
- painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
- painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
- painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
- }
- if (!branch_expression_id_.empty())
- {
- // 选中补线格时仍需把结构横线绘制在选框上层
- painter->setPen(ladderPen(branch_active_));
- painter->drawLine(
- QPointF(-kCellWidth / 2.0, 0),
- QPointF(kCellWidth / 2.0, 0));
- }
- if (show_label_)
- {
- painter->setPen(kPlaceholderColor);
- painter->drawText(
- QRectF(-kCellWidth / 2.0, -20, kCellWidth, 40),
- Qt::AlignCenter,
- LogicEditorWidget::tr("添加条件"));
- }
- }
-
- const std::string &rungId() const { return rung_id_; }
- const std::string &branchExpressionId() const
- {
- return branch_expression_id_;
- }
- int column() const { return column_; }
-
- private:
- std::string rung_id_;
- std::string branch_expression_id_;
- int column_ = 0;
- bool show_label_ = false;
- bool branch_active_ = false;
- };
-
- class LogicEditorWidget::VerticalConnectorItem final : public QGraphicsItem
- {
- public:
- VerticalConnectorItem(
- const std::string &branch_expression_id,
- const std::string &rung_id,
- qreal x,
- qreal top,
- qreal bottom,
- bool active)
- : branch_expression_id_(branch_expression_id),
- rung_id_(rung_id),
- height_(bottom - top),
- active_(active)
- {
- setPos(x, top);
- setFlag(ItemIsSelectable, true);
- setZValue(2.5);
- setToolTip(LogicEditorWidget::tr("竖线连接:删除将移除对应并联支路"));
- }
-
- QRectF boundingRect() const override
- {
- return {-8.0, 0.0, 16.0, height_};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
- {
- const bool selected = (option->state & QStyle::State_Selected) != 0;
- painter->setPen(QPen(
- selected ? kSelectionBorderColor
- : active_ ? kActiveColor : kLadderColor,
- selected || active_ ? 3.0 : kLadderLineWidth));
- painter->drawLine(QPointF(0, 0), QPointF(0, height_));
- if (selected)
- {
- painter->setPen(QPen(kSelectionBorderColor, 1.0, Qt::DashLine));
- painter->drawRect(boundingRect().adjusted(1, 1, -1, -1));
- }
- }
-
- const std::string &branchExpressionId() const
- {
- return branch_expression_id_;
- }
- const std::string &rungId() const { return rung_id_; }
-
- private:
- std::string branch_expression_id_;
- std::string rung_id_;
- qreal height_ = 0.0;
- bool active_ = false;
- };
-
- class LogicEditorWidget::RungItem final : public QGraphicsItem
- {
- public:
- RungItem(
- const LadderRung &rung,
- int number,
- qreal top,
- qreal height,
- qreal width,
- qreal cursor_left)
- : rung_id_(rung.id),
- name_(QString::fromStdString(rung.name)),
- comment_(QString::fromStdString(rung.comment)),
- number_(number),
- height_(height),
- width_(width),
- cursor_left_(cursor_left),
- show_cursor_(!rung.condition.has_value())
- {
- setPos(0, top);
- setFlag(ItemIsSelectable, true);
- setZValue(-2.0);
- if (!comment_.isEmpty())
- {
- setToolTip(comment_);
- }
- }
-
- QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; }
-
- QPainterPath shape() const override
- {
- QPainterPath path;
- path.addRect(QRectF(kSceneMargin, 0, width_, kRungHeaderHeight));
- return path;
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
- {
- const bool selected = (option->state & QStyle::State_Selected) != 0;
- if (selected)
- {
- painter->fillRect(boundingRect(), QColor(240, 247, 250, 90));
- if (show_cursor_)
- {
- painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
- painter->drawRect(QRectF(
- cursor_left_ + 3,
- kRungHeaderHeight + 3,
- kCellWidth - 6,
- kCellHeight - 6));
- }
- }
- painter->setPen(QColor(QStringLiteral("#62717b")));
- QString title = tr("网络 %1").arg(number_);
- if (!name_.isEmpty() && !name_.startsWith(tr("网络 ")))
- {
- title += QStringLiteral(":") + name_;
- }
- painter->drawText(
- QRectF(kSceneMargin + 8, 5, 320, 22),
- Qt::AlignLeft | Qt::AlignVCenter,
- title);
- if (!comment_.isEmpty())
- {
- painter->setPen(QColor(QStringLiteral("#7b8790")));
- const QString visible_comment = painter->fontMetrics().elidedText(
- comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0));
- painter->drawText(
- QRectF(kSceneMargin + 8, 24, width_ - 16, 22),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
- visible_comment);
- }
- painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
- painter->drawLine(
- QPointF(kSceneMargin + 8, height_ - 1),
- QPointF(kSceneMargin + width_ - 8, height_ - 1));
- }
-
- const std::string &rungId() const { return rung_id_; }
-
- private:
- std::string rung_id_;
- QString name_;
- QString comment_;
- int number_ = 0;
- qreal height_ = 0.0;
- qreal width_ = 0.0;
- qreal cursor_left_ = 0.0;
- bool show_cursor_ = false;
- };
-
- namespace {
-
- RenderResult renderExpression(
- QGraphicsScene &scene,
- const LogicEditorService &editor_service,
- const ConditionExpression &expression,
- const std::string &rung_id,
- const QPointF &top_left,
- const ExpressionMetrics &metrics,
- const LogicTraceSnapshot &trace,
- bool trace_enabled,
- const std::string &fault_node_id)
- {
- if (expression.kind == ConditionExpressionKind::Wire)
- {
- const bool active = trace_enabled && traceValue(
- trace, &LogicTraceSnapshot::expressionPowerValues, expression.id);
- const qreal width = static_cast<qreal>(expression.wire->columnSpan) * kCellWidth;
- const QPointF center(
- top_left.x() + width / 2.0,
- top_left.y() + kCellHeight / 2.0);
- scene.addItem(new LogicEditorWidget::WireItem(
- expression.id,
- rung_id,
- center,
- expression.wire->columnSpan,
- active));
- for (int column = 0; column < expression.wire->columnSpan; ++column)
- {
- scene.addItem(new LogicEditorWidget::WireCellItem(
- expression.id,
- rung_id,
- column,
- QPointF(
- top_left.x() + (static_cast<qreal>(column) + 0.5)
- * kCellWidth,
- center.y())));
- }
- return {
- QPointF(top_left.x(), center.y()),
- QPointF(top_left.x() + width, center.y())};
- }
- if (expression.kind == ConditionExpressionKind::Node)
- {
- const QPointF center(
- top_left.x() + kCellWidth / 2.0,
- top_left.y() + kCellHeight / 2.0);
- const bool node_active = trace_enabled && traceValue(
- trace, &LogicTraceSnapshot::nodePowerValues, expression.node->id);
- scene.addLine(
- QLineF(
- QPointF(top_left.x(), center.y()),
- QPointF(top_left.x() + kCellWidth, center.y())),
- ladderPen(node_active));
- scene.addItem(new LogicEditorWidget::NodeItem(
- *expression.node,
- rung_id,
- center,
- [&editor_service, &expression]
- {
- const std::optional<RegisterAddress> address =
- registerAddressForLogicNode(expression.node->config);
- return address.has_value()
- ? QString::fromStdString(
- editor_service.registerCommentFor(*address))
- : QString{};
- }(),
- node_active,
- expression.node->id == fault_node_id,
- true));
- return {
- QPointF(top_left.x(), center.y()),
- QPointF(top_left.x() + kCellWidth, center.y())};
- }
-
- if (expression.kind == ConditionExpressionKind::Series)
- {
- qreal x = top_left.x();
- RenderResult first;
- RenderResult previous;
- for (std::size_t index = 0; index < expression.children.size(); ++index)
- {
- const ExpressionMetrics child_metrics = measureExpression(expression.children[index]);
- const RenderResult current = renderExpression(
- scene,
- editor_service,
- expression.children[index],
- rung_id,
- QPointF(x, top_left.y()),
- child_metrics,
- trace,
- trace_enabled,
- fault_node_id);
- if (index == 0U)
- {
- first = current;
- }
- else
- {
- const bool previous_active = trace_enabled && traceValue(
- trace,
- &LogicTraceSnapshot::expressionPowerValues,
- expression.children[index - 1U].id);
- scene.addLine(
- QLineF(previous.output, current.input),
- ladderPen(previous_active));
- }
- previous = current;
- x += static_cast<qreal>(child_metrics.columns) * kCellWidth;
- }
- return {first.input, previous.output};
- }
-
- qreal y = top_left.y();
- std::vector<RenderResult> branches;
- branches.reserve(expression.children.size());
- for (const ConditionExpression &child : expression.children)
- {
- const ExpressionMetrics child_metrics = measureExpression(child);
- branches.push_back(renderExpression(
- scene,
- editor_service,
- child,
- rung_id,
- QPointF(top_left.x(), y),
- child_metrics,
- trace,
- trace_enabled,
- fault_node_id));
- y += static_cast<qreal>(child_metrics.rows) * kCellHeight;
- }
-
- const qreal left_join = top_left.x();
- const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth;
- const qreal top_y = branches.front().input.y();
- const bool parallel_input_active = trace_enabled && traceValue(
- trace, &LogicTraceSnapshot::expressionInputValues, expression.id);
- for (std::size_t index = 1; index < branches.size(); ++index)
- {
- const qreal segment_top = branches[index - 1U].input.y();
- const qreal segment_bottom = branches[index].input.y();
- const bool branch_active = trace_enabled && traceValue(
- trace,
- &LogicTraceSnapshot::expressionPowerValues,
- expression.children[index].id);
- scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
- expression.children[index].id,
- rung_id,
- left_join,
- segment_top,
- segment_bottom,
- parallel_input_active));
- scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
- expression.children[index].id,
- rung_id,
- right_join,
- segment_top,
- segment_bottom,
- branch_active));
- }
- for (std::size_t index = 0; index < branches.size(); ++index)
- {
- const bool branch_active = trace_enabled && traceValue(
- trace,
- &LogicTraceSnapshot::expressionPowerValues,
- expression.children[index].id);
- scene.addLine(
- QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input),
- ladderPen(branch_active));
- if (branches[index].output.x() < right_join - 0.1)
- {
- // 分支宽度不足时补出到右侧汇合点的结构连接,不写入隐式 Wire
- scene.addLine(
- QLineF(
- branches[index].output,
- QPointF(right_join, branches[index].output.y())),
- ladderPen(branch_active));
- }
- const int branch_columns = measureExpression(
- expression.children[index]).columns;
- for (int column = branch_columns; column < metrics.columns; ++column)
- {
- scene.addItem(new LogicEditorWidget::EmptySlotItem(
- rung_id,
- column,
- QPointF(
- top_left.x()
- + (static_cast<qreal>(column) + 0.5) * kCellWidth,
- branches[index].output.y()),
- false,
- expression.children[index].id,
- branch_active));
- }
- }
- return {QPointF(left_join, top_y), QPointF(right_join, top_y)};
- }
-
- } // namespace
-
- LogicEditorWidget::LogicEditorWidget(LogicEditorService &editor_service, QWidget *parent)
- : QGraphicsView(parent), editor_service_(editor_service)
- {
- scene_ = new QGraphicsScene(this);
- setScene(scene_);
- setRenderHint(QPainter::Antialiasing, true);
- setBackgroundBrush(Qt::white);
- setDragMode(QGraphicsView::RubberBandDrag);
- setAlignment(Qt::AlignLeft | Qt::AlignTop);
- connect(scene_, &QGraphicsScene::selectionChanged,
- this, &LogicEditorWidget::handleSelectionChanged);
- }
-
- void LogicEditorWidget::setLogicId(const std::string &logic_id)
- {
- if (logic_id_ == logic_id)
- {
- return;
- }
- logic_id_ = logic_id;
- current_rung_id_.clear();
- reloadLogic();
- }
-
- void LogicEditorWidget::setEditingEnabled(bool enabled)
- {
- editing_enabled_ = enabled;
- setInteractive(enabled);
- }
-
- void LogicEditorWidget::setRuntimeTrace(
- const LogicTraceSnapshot &trace, const std::string &fault_node_id)
- {
- // 轨迹只是模型的只读投影;编辑器不因显示轨迹而修改工程表达式
- trace_ = trace;
- fault_node_id_ = fault_node_id;
- runtime_trace_enabled_ = true;
- reloadLogic();
- }
-
- void LogicEditorWidget::clearRuntimeTrace()
- {
- trace_.clear();
- fault_node_id_.clear();
- runtime_trace_enabled_ = false;
- reloadLogic();
- }
-
- void LogicEditorWidget::reloadLogic()
- {
- // 逻辑或选择变化后重新计算网格布局和可点击插入目标
- scene_->clear();
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr)
- {
- scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
- return;
- }
- if (current_rung_id_.empty() && !logic->rungs.empty())
- {
- current_rung_id_ = logic->rungs.front().id;
- }
-
- const int condition_columns = ProjectLimits::kMaximumConditionColumns;
- const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 1);
- const qreal left_rail_x = kSceneMargin + kRailInset;
- const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
- const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
-
- qreal top = kSceneMargin;
- int number = 1;
- for (const LadderRung &rung : logic->rungs)
- {
- const ExpressionMetrics metrics = rung.condition.has_value()
- ? measureExpression(*rung.condition) : ExpressionMetrics{};
- const int occupied_columns = rung.condition.has_value() ? metrics.columns : 0;
- const int grid_rows = std::max(1, metrics.rows);
- const qreal grid_top = top + kRungHeaderHeight;
- const qreal rung_height = kRungHeaderHeight
- + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
- scene_->addItem(new RungItem(
- rung,
- number,
- top,
- rung_height,
- scene_width - 2.0 * kSceneMargin,
- left_rail_x));
- addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
- scene_->addLine(
- QLineF(left_rail_x, grid_top, left_rail_x,
- grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
- QPen(kLadderColor, 2.4));
- scene_->addLine(
- QLineF(right_rail_x, grid_top, right_rail_x,
- grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
- QPen(kLadderColor, 2.4));
-
- const qreal main_y = grid_top + kCellHeight / 2.0;
- QPointF expression_output(left_rail_x, main_y);
- if (rung.condition.has_value())
- {
- const RenderResult rendered = renderExpression(
- *scene_,
- editor_service_,
- *rung.condition,
- rung.id,
- QPointF(left_rail_x, grid_top),
- metrics,
- trace_,
- runtime_trace_enabled_,
- fault_node_id_);
- expression_output = rendered.output;
- }
- for (int column = occupied_columns;
- column < ProjectLimits::kMaximumConditionColumns;
- ++column)
- {
- scene_->addItem(new EmptySlotItem(
- rung.id,
- column,
- QPointF(
- left_rail_x + (static_cast<qreal>(column) + 0.5)
- * kCellWidth,
- main_y),
- column == 0 && !rung.output.has_value()));
- }
-
- const qreal output_left = right_rail_x - kCellWidth;
- if (rung.output.has_value())
- {
- const bool rung_active = runtime_trace_enabled_ && traceValue(
- trace_, &LogicTraceSnapshot::rungValues, rung.id);
- scene_->addLine(
- QLineF(expression_output, QPointF(output_left, main_y)),
- ladderPen(rung_active));
- scene_->addLine(
- QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
- ladderPen(rung_active));
- scene_->addItem(new NodeItem(
- *rung.output,
- rung.id,
- QPointF(output_left + kCellWidth / 2.0, main_y),
- [&rung, this]
- {
- const std::optional<RegisterAddress> address =
- registerAddressForLogicNode(rung.output->config);
- return address.has_value()
- ? QString::fromStdString(
- editor_service_.registerCommentFor(*address))
- : QString{};
- }(),
- [&rung, this, rung_active]
- {
- return (std::holds_alternative<TonNodeConfig>(rung.output->config)
- || std::holds_alternative<CounterNodeConfig>(
- rung.output->config))
- ? traceValue(
- trace_,
- &LogicTraceSnapshot::nodeValues,
- rung.output->id)
- : rung_active;
- }(),
- rung.output->id == fault_node_id_,
- false));
- }
- else
- {
- addPlaceholder(
- *scene_,
- QRectF(output_left, grid_top, kCellWidth, kCellHeight),
- tr("输出线圈"));
- }
- top += rung_height + kRungGap;
- ++number;
- }
- scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
- }
-
- void LogicEditorWidget::selectNode(const std::string &node_id)
- {
- for (QGraphicsItem *item : scene_->items())
- {
- if (NodeItem *node = dynamic_cast<NodeItem *>(item))
- {
- node->setSelected(node->nodeId() == node_id);
- if (node->nodeId() == node_id)
- {
- current_rung_id_ = node->rungId();
- ensureVisible(node);
- }
- }
- else
- {
- item->setSelected(false);
- }
- }
- }
-
- void LogicEditorWidget::selectExpression(const std::string &expression_id)
- {
- for (QGraphicsItem *item : scene_->items())
- {
- bool selected = false;
- if (NodeItem *node = dynamic_cast<NodeItem *>(item))
- {
- selected = node->isConditionNode() && node->nodeId() == expression_id;
- if (selected)
- {
- current_rung_id_ = node->rungId();
- ensureVisible(node);
- }
- }
- else if (WireItem *wire = dynamic_cast<WireItem *>(item))
- {
- selected = wire->expressionId() == expression_id;
- if (selected)
- {
- current_rung_id_ = wire->rungId();
- ensureVisible(wire);
- }
- }
- item->setSelected(selected);
- }
- }
-
- void LogicEditorWidget::selectWire(const std::string &wire_id)
- {
- selectExpression(wire_id);
- }
-
- std::string LogicEditorWidget::selectedNodeId() const
- {
- const std::vector<std::string> ids = selectedNodeIds();
- return ids.size() == 1U ? ids.front() : std::string{};
- }
-
- std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
- {
- std::vector<std::pair<QPointF, std::string>> positioned_ids;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
- {
- positioned_ids.emplace_back(node->scenePos(), node->nodeId());
- }
- }
- std::sort(
- positioned_ids.begin(), positioned_ids.end(),
- [](const auto &left, const auto &right)
- {
- if (!qFuzzyCompare(left.first.y(), right.first.y()))
- {
- return left.first.y() < right.first.y();
- }
- return left.first.x() < right.first.x();
- });
- std::vector<std::string> ids;
- ids.reserve(positioned_ids.size());
- for (const auto &positioned_id : positioned_ids)
- {
- ids.push_back(positioned_id.second);
- }
- return ids;
- }
-
- std::vector<std::string> LogicEditorWidget::selectedExpressionIds() const
- {
- std::vector<std::pair<QPointF, std::string>> positioned_ids;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
- {
- if (node->isConditionNode())
- {
- positioned_ids.emplace_back(node->scenePos(), node->nodeId());
- }
- }
- else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
- {
- positioned_ids.emplace_back(wire->scenePos(), wire->expressionId());
- }
- else if (const WireCellItem *cell =
- dynamic_cast<const WireCellItem *>(item))
- {
- positioned_ids.emplace_back(cell->scenePos(), cell->expressionId());
- }
- }
- std::sort(
- positioned_ids.begin(), positioned_ids.end(),
- [](const auto &left, const auto &right)
- {
- if (!qFuzzyCompare(left.first.y(), right.first.y()))
- {
- return left.first.y() < right.first.y();
- }
- return left.first.x() < right.first.x();
- });
- std::vector<std::string> ids;
- ids.reserve(positioned_ids.size());
- for (const auto &positioned_id : positioned_ids)
- {
- if (std::find(ids.cbegin(), ids.cend(), positioned_id.second) == ids.cend())
- {
- ids.push_back(positioned_id.second);
- }
- }
- return ids;
- }
-
- std::vector<std::string> LogicEditorWidget::selectedWireIds() const
- {
- std::vector<std::string> ids;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
- {
- if (std::find(ids.cbegin(), ids.cend(), wire->expressionId()) == ids.cend())
- {
- ids.push_back(wire->expressionId());
- }
- }
- else if (const WireCellItem *cell =
- dynamic_cast<const WireCellItem *>(item))
- {
- if (std::find(ids.cbegin(), ids.cend(), cell->expressionId()) == ids.cend())
- {
- ids.push_back(cell->expressionId());
- }
- }
- }
- return ids;
- }
-
- std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedEmptySlots() const
- {
- std::vector<std::pair<std::string, int>> targets;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const EmptySlotItem *slot = dynamic_cast<const EmptySlotItem *>(item))
- {
- targets.emplace_back(slot->branchExpressionId(), slot->column());
- }
- }
- return targets;
- }
-
- std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedWireCells() const
- {
- std::vector<std::pair<std::string, int>> cells;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const WireCellItem *cell = dynamic_cast<const WireCellItem *>(item))
- {
- cells.emplace_back(cell->expressionId(), cell->columnOffset());
- }
- }
- return cells;
- }
-
- std::vector<std::string> LogicEditorWidget::selectedBranchIds() const
- {
- std::vector<std::string> ids;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const VerticalConnectorItem *connector =
- dynamic_cast<const VerticalConnectorItem *>(item))
- {
- if (std::find(ids.cbegin(), ids.cend(), connector->branchExpressionId())
- == ids.cend())
- {
- ids.push_back(connector->branchExpressionId());
- }
- }
- }
- return ids;
- }
-
- std::string LogicEditorWidget::selectedRungId() const
- {
- std::string rung_id;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
- {
- if (!rung_id.empty() && rung_id != node->rungId())
- {
- return {};
- }
- rung_id = node->rungId();
- }
- else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
- {
- if (!rung_id.empty() && rung_id != wire->rungId())
- {
- return {};
- }
- rung_id = wire->rungId();
- }
- else if (const WireCellItem *cell =
- dynamic_cast<const WireCellItem *>(item))
- {
- if (!rung_id.empty() && rung_id != cell->rungId())
- {
- return {};
- }
- rung_id = cell->rungId();
- }
- else if (const VerticalConnectorItem *connector =
- dynamic_cast<const VerticalConnectorItem *>(item))
- {
- if (!rung_id.empty() && rung_id != connector->rungId())
- {
- return {};
- }
- rung_id = connector->rungId();
- }
- else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
- {
- if (rung_id.empty())
- {
- rung_id = rung->rungId();
- }
- }
- else if (const EmptySlotItem *slot =
- dynamic_cast<const EmptySlotItem *>(item))
- {
- if (!rung_id.empty() && rung_id != slot->rungId())
- {
- return {};
- }
- rung_id = slot->rungId();
- }
- }
- return rung_id;
- }
-
- bool LogicEditorWidget::hasSelectedRungItem() const
- {
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (dynamic_cast<const RungItem *>(item) != nullptr)
- {
- return true;
- }
- }
- return false;
- }
-
- LogicEditorResult LogicEditorWidget::addRung()
- {
- const LogicEditorResult result = editor_service_.addRung(logic_id_);
- if (result.succeeded)
- {
- current_rung_id_ = result.id;
- reloadLogic();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
- {
- const std::vector<std::pair<std::string, int>> selected_empty_slots =
- selectedEmptySlots();
- const std::vector<std::pair<std::string, int>> selected_wire_cells =
- selectedWireCells();
- const std::vector<std::string> selected_expressions = selectedExpressionIds();
- const std::vector<std::string> selected_wires = selectedWireIds();
- const std::vector<std::string> selected_ids = selectedNodeIds();
- LogicEditorResult result;
- if (selected_empty_slots.size() > 1U || selected_wire_cells.size() > 1U
- || (!selected_empty_slots.empty()
- && (!selected_wire_cells.empty()
- || !selected_expressions.empty() || !selected_ids.empty()))
- || (!selected_wire_cells.empty()
- && (!selected_ids.empty() || selected_expressions.size() > 1U)))
- {
- result = {false, LogicEditorError::InvalidOperation,
- "插入条件时只能选择一个网格或条件对象", {}};
- }
- else if (selected_empty_slots.size() == 1U)
- {
- const auto &slot = selected_empty_slots.front();
- result = slot.first.empty()
- ? editor_service_.insertConditionAtColumn(
- logic_id_, currentRungId(), slot.second, config)
- : editor_service_.insertConditionInBranchAtColumn(
- logic_id_, currentRungId(), slot.first, slot.second, config);
- }
- else if (selected_wire_cells.size() == 1U)
- {
- result = editor_service_.replaceWireColumnWithCondition(
- logic_id_,
- currentRungId(),
- selected_wire_cells.front().first,
- selected_wire_cells.front().second,
- config);
- }
- else if (selected_expressions.size() > 1U || selected_wires.size() > 1U)
- {
- result = {false, LogicEditorError::InvalidOperation,
- "串联插入或替换横线时只能选择一个条件对象", {}};
- }
- else if (selected_wires.size() == 1U)
- {
- result = editor_service_.replaceWireWithCondition(
- logic_id_, currentRungId(), selected_wires.front(), config);
- }
- else if (selected_ids.size() == 1U)
- {
- const LogicNode *selected_node = editor_service_.findNode(
- logic_id_, selected_ids.front());
- result = selected_node != nullptr && selected_node->isCondition()
- ? editor_service_.insertConditionAfter(
- logic_id_, currentRungId(), selected_ids.front(), config)
- : editor_service_.appendCondition(logic_id_, currentRungId(), config);
- }
- else
- {
- result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
- }
- if (result.succeeded)
- {
- reloadLogic();
- selectExpression(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
- {
- const std::string rung_id = selectedRungId();
- const std::vector<std::string> selected_ids = selectedNodeIds();
- std::vector<std::string> condition_ids;
- for (const std::string &node_id : selected_ids)
- {
- const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
- if (node != nullptr && node->isCondition())
- {
- condition_ids.push_back(node_id);
- }
- }
- LogicEditorResult result;
- if (rung_id.empty() || condition_ids.empty())
- {
- result = {false, LogicEditorError::InvalidOperation,
- "请在同一网络中选择要并联的连续节点", {}};
- }
- else
- {
- result = editor_service_.addParallelBranch(
- logic_id_, rung_id, condition_ids, config);
- }
- if (result.succeeded)
- {
- reloadLogic();
- selectExpression(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addHorizontalWire()
- {
- const std::vector<std::string> selected_ids = selectedExpressionIds();
- LogicEditorResult result;
- if (selected_ids.size() > 1U)
- {
- result = {false, LogicEditorError::InvalidOperation,
- "插入横线时只能选择一个条件对象", {}};
- }
- else if (selected_ids.size() == 1U)
- {
- result = editor_service_.insertWireAfter(
- logic_id_, currentRungId(), selected_ids.front());
- }
- else
- {
- result = editor_service_.appendWire(logic_id_, currentRungId());
- }
- if (result.succeeded)
- {
- reloadLogic();
- selectWire(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addVerticalWire()
- {
- const std::string rung_id = selectedRungId();
- const std::vector<std::string> selected_ids = selectedExpressionIds();
- LogicEditorResult result;
- if (rung_id.empty() || selected_ids.empty())
- {
- result = {false, LogicEditorError::InvalidOperation,
- "请在同一网络中选择要连接的连续条件或横线", {}};
- }
- else
- {
- result = editor_service_.addParallelWireBranch(
- logic_id_, rung_id, selected_ids);
- }
- if (result.succeeded)
- {
- reloadLogic();
- selectWire(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
- {
- const std::vector<std::string> wire_ids = selectedWireIds();
- const std::string rung_id = selectedRungId();
- if (wire_ids.empty() || rung_id.empty())
- {
- const LogicEditorResult result = {
- false,
- LogicEditorError::InvalidOperation,
- "请先选择要删除的横线",
- {}};
- reportFailure(result);
- return result;
- }
- const LogicEditorResult result = editor_service_.removeExpressions(
- logic_id_, rung_id, wire_ids);
- if (result.succeeded)
- {
- reloadLogic();
- emit nodeSelected({});
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteVerticalWire()
- {
- const std::vector<std::string> branch_ids = selectedBranchIds();
- if (branch_ids.empty())
- {
- const LogicEditorResult result = {
- false,
- LogicEditorError::InvalidOperation,
- "请先选择要删除的竖线连接",
- {}};
- reportFailure(result);
- return result;
- }
- const std::string rung_id = selectedRungId();
- LogicEditorResult result;
- if (rung_id.empty())
- {
- result = {false, LogicEditorError::InvalidOperation,
- "竖线连接必须位于同一网络", {}};
- }
- else
- {
- result = editor_service_.removeExpressions(
- logic_id_, rung_id, branch_ids);
- }
- if (result.succeeded)
- {
- reloadLogic();
- emit nodeSelected({});
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::setOutput(
- const LogicNodeConfig &config, bool configured)
- {
- const LogicEditorResult result = editor_service_.setOutput(
- logic_id_, currentRungId(), config, configured);
- if (result.succeeded)
- {
- reloadLogic();
- selectNode(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteSelected()
- {
- const std::vector<std::string> expression_ids = selectedExpressionIds();
- const std::vector<std::string> wire_ids = selectedWireIds();
- const std::vector<std::string> branch_ids = selectedBranchIds();
- const std::vector<std::string> node_ids = selectedNodeIds();
- LogicEditorResult result;
- if (!branch_ids.empty())
- {
- result = editor_service_.removeExpressions(
- logic_id_, selectedRungId(), branch_ids);
- }
- else if (!wire_ids.empty())
- {
- bool output_selected = false;
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
- {
- output_selected = output_selected || !node->isConditionNode();
- }
- }
- if (output_selected)
- {
- result = {false, LogicEditorError::InvalidOperation,
- "不能同时删除横线和输出节点", {}};
- }
- else
- {
- result = editor_service_.removeExpressions(
- logic_id_, selectedRungId(), expression_ids);
- }
- }
- else if (!node_ids.empty())
- {
- result = editor_service_.removeNodes(logic_id_, node_ids);
- }
- else if (hasSelectedRungItem())
- {
- const std::string rung_id = selectedRungId();
- result = editor_service_.removeRung(logic_id_, rung_id);
- if (result.succeeded && current_rung_id_ == rung_id)
- {
- current_rung_id_ = editor_service_.firstRungId(logic_id_);
- }
- }
- else
- {
- result = {false, LogicEditorError::InvalidOperation,
- "请先选择要删除的逻辑节点或网络", {}};
- }
- if (result.succeeded)
- {
- reloadLogic();
- emit nodeSelected({});
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- void LogicEditorWidget::resizeEvent(QResizeEvent *event)
- {
- QGraphicsView::resizeEvent(event);
- }
-
- void LogicEditorWidget::handleSelectionChanged()
- {
- const std::string rung_id = selectedRungId();
- if (!rung_id.empty())
- {
- current_rung_id_ = rung_id;
- }
- emit nodeSelected(QString::fromStdString(selectedNodeId()));
- }
-
- void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
- {
- emit editorError(QString::fromStdString(result.message));
- }
-
- std::string LogicEditorWidget::currentRungId() const
- {
- const std::string selected = selectedRungId();
- if (!selected.empty())
- {
- return selected;
- }
- return current_rung_id_.empty()
- ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
- }
|