|
- #include "logic_editor_widget.h"
-
- #include <QApplication>
- #include <QBrush>
- #include <QCompleter>
- #include <QEvent>
- #include <QGraphicsItem>
- #include <QGraphicsLineItem>
- #include <QGraphicsRectItem>
- #include <QGraphicsScene>
- #include <QHeaderView>
- #include <QKeyEvent>
- #include <QLineEdit>
- #include <QMouseEvent>
- #include <QPainter>
- #include <QPainterPathStroker>
- #include <QPen>
- #include <QRegularExpression>
- #include <QResizeEvent>
- #include <QRubberBand>
- #include <QStandardItemModel>
- #include <QStyleOptionGraphicsItem>
- #include <QTimer>
- #include <QTreeView>
-
- #include <algorithm>
- #include <type_traits>
-
- namespace {
-
- constexpr qreal kLabelWidth = 60.0;
- constexpr qreal kCellWidth = 96.0;
- constexpr qreal kOutputWidth = 224.0;
- constexpr qreal kRowHeight = 78.0;
- constexpr qreal kCommentBandHeight = 28.0;
- constexpr qreal kTop = 18.0;
- constexpr qreal kLeftBus = kLabelWidth;
- constexpr qreal kConditionRight = kLabelWidth
- + ProjectLimits::kMaximumConditionColumns * kCellWidth;
- constexpr qreal kRightBus = kConditionRight + kOutputWidth;
- constexpr qreal kSceneRightMargin = 24.0;
-
- const QColor kCanvasBackground(QStringLiteral("#ffffff"));
- const QColor kGridColor(QStringLiteral("#d8e0e4"));
- const QColor kLadderColor(QStringLiteral("#263842"));
- const QColor kActiveColor(QStringLiteral("#16854f"));
- const QColor kFaultColor(QStringLiteral("#c5362e"));
- const QColor kCommentColor(QStringLiteral("#16854f"));
- const QColor kSelectionFill(QStringLiteral("#dfeef5"));
- const QColor kSelectionBorder(QStringLiteral("#277da1"));
-
- QString registerAddressText(const RegisterAddress &address)
- {
- return QString::fromStdString(address.toString());
- }
-
- QString wordOperandText(const WordOperand &operand)
- {
- return operand.kind == WordOperandKind::Register
- ? registerAddressText(operand.address)
- : QString::number(operand.constant);
- }
-
- 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 logicCommandText(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 QStringLiteral("%1 %2")
- .arg(value.mode == ContactMode::NormallyOpen
- ? QStringLiteral("LD") : QStringLiteral("LDI"))
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
- {
- return QStringLiteral("%1 %2")
- .arg(value.mode == EdgeMode::Rising
- ? QStringLiteral("LDP") : QStringLiteral("LDF"))
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
- {
- const QString mnemonic = value.mode == CoilMode::Set
- ? QStringLiteral("SET")
- : value.mode == CoilMode::Reset
- ? QStringLiteral("RST") : QStringLiteral("OUT");
- return QStringLiteral("%1 %2")
- .arg(mnemonic)
- .arg(registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
- {
- return QStringLiteral("LD%1 %2 %3")
- .arg(comparisonText(value.comparison))
- .arg(registerAddressText(value.address))
- .arg(value.value);
- }
- else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
- {
- return QStringLiteral("MOV %1 %2")
- .arg(wordOperandText(value.source))
- .arg(registerAddressText(value.destination));
- }
- else
- {
- return QStringLiteral("%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);
- }
-
- QString commandCompletionPrefix(const QString &text)
- {
- return text.section(QRegularExpression(QStringLiteral("\\s+")), 0, 0)
- .toUpper();
- }
-
- QPen ladderPen(bool active, bool faulted = false)
- {
- QPen pen(faulted ? kFaultColor : active ? kActiveColor : kLadderColor);
- pen.setWidthF(active || faulted ? 2.5 : 1.8);
- pen.setJoinStyle(Qt::MiterJoin);
- pen.setCapStyle(Qt::SquareCap);
- return pen;
- }
-
- bool containsId(
- const std::vector<std::string> &ids, const std::string &candidate)
- {
- return std::find(ids.cbegin(), ids.cend(), candidate) != ids.cend();
- }
-
- bool containsCell(
- const std::vector<std::pair<std::string, int>> &cells,
- const std::pair<std::string, int> &candidate)
- {
- return std::find(cells.cbegin(), cells.cend(), candidate) != cells.cend();
- }
-
- struct GridRow
- {
- qreal top = 0.0;
- };
-
- class GridLayerItem final : public QGraphicsItem
- {
- public:
- explicit GridLayerItem(std::vector<GridRow> rows)
- : rows_(std::move(rows))
- {
- setZValue(0.0);
- }
-
- QRectF boundingRect() const override
- {
- if (rows_.empty())
- {
- return {};
- }
- return {
- kLeftBus,
- rows_.front().top,
- kRightBus - kLeftBus,
- rows_.back().top + kRowHeight - rows_.front().top};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
- {
- painter->setRenderHint(QPainter::Antialiasing, false);
- QPen grid_pen(kGridColor);
- grid_pen.setWidthF(1.0);
- grid_pen.setCosmetic(true);
- painter->setPen(grid_pen);
- for (const GridRow &row : rows_)
- {
- painter->fillRect(
- QRectF(
- kLeftBus,
- row.top,
- kRightBus - kLeftBus,
- kRowHeight),
- kCanvasBackground);
- painter->drawRect(
- QRectF(
- kLeftBus,
- row.top,
- kRightBus - kLeftBus,
- kRowHeight));
- for (int boundary = 1;
- boundary <= ProjectLimits::kMaximumConditionColumns;
- ++boundary)
- {
- const qreal x = kLeftBus
- + static_cast<qreal>(boundary) * kCellWidth;
- painter->drawLine(
- QPointF(x, row.top),
- QPointF(x, row.top + kRowHeight));
- }
- }
- }
-
- private:
- std::vector<GridRow> rows_;
- };
-
- class CellContentItem final : public QGraphicsItem
- {
- public:
- CellContentItem(
- const LadderCell &cell,
- const std::string &rung_id,
- int column,
- const QPointF &top_left,
- bool input_active,
- bool output_active,
- bool faulted)
- : cell_(cell), input_active_(input_active),
- output_active_(output_active), faulted_(faulted)
- {
- setPos(top_left);
- setZValue(10.0);
- setData(0, QStringLiteral("cell"));
- setData(1, QString::fromStdString(rung_id));
- setData(2, column);
- setData(3, QString::fromStdString(
- cell.node.has_value() ? cell.node->id : cell.id));
- setData(4, static_cast<int>(cell.kind));
- setData(5, QString::fromStdString(cell.id));
- setData(6, input_active_);
- setData(7, output_active_);
- }
-
- QRectF boundingRect() const override
- {
- return {0.0, 0.0, kCellWidth, kRowHeight};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
- {
- if (cell_.kind == LadderCellKind::Gap)
- {
- return;
- }
- painter->setRenderHint(QPainter::Antialiasing, true);
- const auto use_pen = [this, painter](bool active)
- {
- painter->setPen(ladderPen(active, faulted_));
- };
- const qreal center_x = kCellWidth / 2.0;
- const qreal center_y = kRowHeight / 2.0 + 6.0;
- if (cell_.kind == LadderCellKind::Wire || !cell_.node.has_value())
- {
- use_pen(input_active_);
- painter->drawLine(
- QPointF(0.0, center_y), QPointF(center_x, center_y));
- use_pen(output_active_);
- painter->drawLine(
- QPointF(center_x, center_y), QPointF(kCellWidth, center_y));
- return;
- }
-
- const LogicNode &node = *cell_.node;
- QFont font = painter->font();
- font.setPointSizeF(8.5);
- painter->setFont(font);
- std::visit(
- [&](const auto &config)
- {
- using Config = std::decay_t<decltype(config)>;
- if constexpr (std::is_same_v<Config, ContactNodeConfig>
- || std::is_same_v<Config, EdgeContactNodeConfig>)
- {
- const qreal left = center_x - 15.0;
- const qreal right = center_x + 15.0;
- use_pen(input_active_);
- painter->drawLine(QPointF(0.0, center_y), QPointF(left, center_y));
- use_pen(output_active_);
- painter->drawLine(QPointF(right, center_y), QPointF(kCellWidth, center_y));
- painter->drawLine(QPointF(left, center_y - 14.0), QPointF(left, center_y + 14.0));
- painter->drawLine(QPointF(right, center_y - 14.0), QPointF(right, center_y + 14.0));
- if constexpr (std::is_same_v<Config, ContactNodeConfig>)
- {
- if (config.mode == ContactMode::NormallyClosed)
- {
- painter->drawLine(
- QPointF(left - 4.0, center_y + 17.0),
- QPointF(right + 4.0, center_y - 17.0));
- }
- }
- else
- {
- painter->drawText(
- QRectF(center_x - 13.0, center_y - 12.0, 26.0, 24.0),
- Qt::AlignCenter,
- config.mode == EdgeMode::Rising
- ? QStringLiteral("P") : QStringLiteral("N"));
- }
- painter->drawText(
- QRectF(2.0, 3.0, kCellWidth - 4.0, 20.0),
- Qt::AlignCenter,
- node.configured
- ? registerAddressText(config.address)
- : QStringLiteral("<M>"));
- }
- else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
- {
- const QRectF box(7.0, center_y - 15.0, kCellWidth - 14.0, 30.0);
- use_pen(input_active_);
- painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
- use_pen(output_active_);
- painter->drawLine(QPointF(box.right(), center_y), QPointF(kCellWidth, center_y));
- painter->drawRect(box);
- QFont compare_font = painter->font();
- compare_font.setPointSizeF(7.5);
- painter->setFont(compare_font);
- painter->drawText(
- box.adjusted(2.0, 0.0, -2.0, 0.0),
- Qt::AlignCenter,
- node.configured
- ? QStringLiteral("%1 %2 %3")
- .arg(registerAddressText(config.address))
- .arg(comparisonText(config.comparison))
- .arg(config.value)
- : QStringLiteral("<比较>"));
- }
- else
- {
- use_pen(input_active_);
- painter->drawLine(
- QPointF(0.0, center_y), QPointF(center_x, center_y));
- use_pen(output_active_);
- painter->drawLine(
- QPointF(center_x, center_y), QPointF(kCellWidth, center_y));
- }
- },
- node.config);
- }
-
- private:
- LadderCell cell_;
- bool input_active_ = false;
- bool output_active_ = false;
- bool faulted_ = false;
- };
-
- class OutputContentItem final : public QGraphicsItem
- {
- public:
- OutputContentItem(
- const LadderRung &rung,
- const QPointF &top_left,
- bool input_active,
- bool symbol_active,
- bool faulted)
- : rung_id_(rung.id), output_(rung.output),
- input_active_(input_active), symbol_active_(symbol_active),
- faulted_(faulted)
- {
- setPos(top_left);
- setZValue(10.0);
- setData(0, QStringLiteral("output"));
- setData(1, QString::fromStdString(rung.id));
- setData(2, QString::fromStdString(
- rung.output.has_value() ? rung.output->id : std::string{}));
- setData(3, input_active_);
- setData(4, symbol_active_);
- }
-
- QRectF boundingRect() const override
- {
- return {0.0, 0.0, kOutputWidth, kRowHeight};
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
- {
- if (!output_.has_value())
- {
- return;
- }
- painter->setRenderHint(QPainter::Antialiasing, true);
- const auto use_pen = [this, painter](bool active)
- {
- painter->setPen(ladderPen(active, faulted_));
- };
- const qreal center_y = kRowHeight / 2.0 + 6.0;
- const LogicNode &node = *output_;
- QFont font = painter->font();
- font.setPointSizeF(8.5);
- painter->setFont(font);
- std::visit(
- [&](const auto &config)
- {
- using Config = std::decay_t<decltype(config)>;
- if constexpr (std::is_same_v<Config, CoilNodeConfig>)
- {
- const qreal center_x = kOutputWidth / 2.0;
- use_pen(input_active_);
- painter->drawLine(QPointF(0.0, center_y), QPointF(center_x - 22.0, center_y));
- painter->drawLine(QPointF(center_x + 22.0, center_y), QPointF(kOutputWidth, center_y));
- use_pen(symbol_active_);
- QPainterPath left;
- left.moveTo(center_x - 3.0, center_y - 18.0);
- left.cubicTo(
- center_x - 25.0, center_y - 14.0,
- center_x - 25.0, center_y + 14.0,
- center_x - 3.0, center_y + 18.0);
- painter->drawPath(left);
- QPainterPath right;
- right.moveTo(center_x + 3.0, center_y - 18.0);
- right.cubicTo(
- center_x + 25.0, center_y - 14.0,
- center_x + 25.0, center_y + 14.0,
- center_x + 3.0, center_y + 18.0);
- painter->drawPath(right);
- if (config.mode != CoilMode::Normal)
- {
- painter->drawText(
- QRectF(center_x - 13.0, center_y - 13.0, 26.0, 26.0),
- Qt::AlignCenter,
- config.mode == CoilMode::Set
- ? QStringLiteral("S") : QStringLiteral("R"));
- }
- painter->drawText(
- QRectF(2.0, 3.0, kOutputWidth - 4.0, 20.0),
- Qt::AlignCenter,
- node.configured
- ? registerAddressText(config.address)
- : QStringLiteral("<M>"));
- }
- else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
- {
- const QRectF box(26.0, 11.0, kOutputWidth - 52.0, kRowHeight - 22.0);
- use_pen(input_active_);
- painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
- painter->drawLine(QPointF(box.right(), center_y), QPointF(kOutputWidth, center_y));
- use_pen(symbol_active_);
- painter->drawRect(box);
- QFont mnemonic_font = painter->font();
- mnemonic_font.setBold(true);
- painter->setFont(mnemonic_font);
- painter->drawText(
- QRectF(box.left(), box.top() + 2.0, box.width(), 20.0),
- Qt::AlignCenter,
- QStringLiteral("MOV"));
- mnemonic_font.setBold(false);
- mnemonic_font.setPointSizeF(8.0);
- painter->setFont(mnemonic_font);
- painter->drawText(
- QRectF(box.left() + 4.0, box.top() + 23.0, box.width() - 8.0, 20.0),
- Qt::AlignCenter,
- node.configured
- ? QStringLiteral("%1 -> %2")
- .arg(wordOperandText(config.source))
- .arg(registerAddressText(config.destination))
- : QStringLiteral("<源> -> <目标>"));
- }
- else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>)
- {
- const QRectF box(18.0, 11.0, kOutputWidth - 36.0, kRowHeight - 22.0);
- use_pen(input_active_);
- painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
- painter->drawLine(QPointF(box.right(), center_y), QPointF(kOutputWidth, center_y));
- use_pen(symbol_active_);
- painter->drawRect(box);
- QFont mnemonic_font = painter->font();
- mnemonic_font.setBold(true);
- painter->setFont(mnemonic_font);
- painter->drawText(
- QRectF(box.left(), box.top() + 2.0, box.width(), 20.0),
- Qt::AlignCenter,
- config.operation == ArithmeticOperation::Add
- ? QStringLiteral("ADD") : QStringLiteral("SUB"));
- mnemonic_font.setBold(false);
- mnemonic_font.setPointSizeF(8.0);
- painter->setFont(mnemonic_font);
- painter->drawText(
- QRectF(box.left() + 4.0, box.top() + 23.0, box.width() - 8.0, 20.0),
- Qt::AlignCenter,
- node.configured
- ? QStringLiteral("%1, %2 -> %3")
- .arg(wordOperandText(config.left))
- .arg(wordOperandText(config.right))
- .arg(registerAddressText(config.destination))
- : QStringLiteral("<左>, <右> -> <目标>"));
- }
- else
- {
- use_pen(input_active_);
- painter->drawLine(
- QPointF(0.0, center_y), QPointF(kOutputWidth, center_y));
- }
- },
- node.config);
- }
-
- private:
- std::string rung_id_;
- std::optional<LogicNode> output_;
- bool input_active_ = false;
- bool symbol_active_ = false;
- bool faulted_ = false;
- };
-
- class VerticalConnectionItem final : public QGraphicsItem
- {
- public:
- VerticalConnectionItem(
- const VerticalConnection &connection,
- qreal x,
- qreal top,
- qreal bottom,
- bool active)
- : height_(bottom - top), active_(active)
- {
- setPos(x, top);
- setZValue(20.0);
- setData(0, QStringLiteral("vertical"));
- setData(1, QString::fromStdString(connection.id));
- setData(2, QString::fromStdString(connection.upperRungId));
- setData(3, connection.columnBoundary);
- setData(4, QString::fromStdString(connection.lowerRungId));
- setData(5, active_);
- }
-
- QRectF boundingRect() const override
- {
- return {-7.0, 0.0, 14.0, height_};
- }
-
- QPainterPath shape() const override
- {
- QPainterPath path;
- path.moveTo(0.0, 0.0);
- path.lineTo(0.0, height_);
- QPainterPathStroker stroker;
- stroker.setWidth(12.0);
- return stroker.createStroke(path);
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
- {
- painter->setPen(ladderPen(active_));
- painter->drawLine(QPointF(0.0, 0.0), QPointF(0.0, height_));
- }
-
- private:
- qreal height_ = 0.0;
- bool active_ = false;
- };
-
- class SelectionOverlayItem final : public QGraphicsItem
- {
- public:
- explicit SelectionOverlayItem(std::vector<QRectF> rectangles)
- : rectangles_(std::move(rectangles))
- {
- for (const QRectF &rectangle : rectangles_)
- {
- bounds_ = bounds_.isNull() ? rectangle : bounds_.united(rectangle);
- }
- bounds_.adjust(-2.0, -2.0, 2.0, 2.0);
- setZValue(100.0);
- }
-
- QRectF boundingRect() const override
- {
- return bounds_;
- }
-
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
- {
- painter->setRenderHint(QPainter::Antialiasing, false);
- QPen pen(kSelectionBorder, 1.4, Qt::DashLine);
- pen.setCosmetic(true);
- painter->setPen(pen);
- painter->setBrush(QColor(
- kSelectionFill.red(),
- kSelectionFill.green(),
- kSelectionFill.blue(),
- 90));
- for (const QRectF &rectangle : rectangles_)
- {
- painter->drawRect(rectangle.adjusted(2.5, 2.5, -2.5, -2.5));
- }
- }
-
- private:
- std::vector<QRectF> rectangles_;
- QRectF bounds_;
- };
-
- } // namespace
-
- LogicEditorWidget::LogicEditorWidget(
- LogicEditorService &editor_service,
- QWidget *parent)
- : QGraphicsView(parent),
- editor_service_(editor_service),
- command_service_(editor_service),
- scene_(new QGraphicsScene(this))
- {
- setScene(scene_);
- setRenderHint(QPainter::Antialiasing, true);
- setBackgroundBrush(kCanvasBackground);
- setDragMode(QGraphicsView::NoDrag);
- setAlignment(Qt::AlignLeft | Qt::AlignTop);
- selection_band_ = new QRubberBand(QRubberBand::Rectangle, viewport());
- selection_band_->hide();
-
- command_editor_ = new QLineEdit(viewport());
- command_editor_->setObjectName(QStringLiteral("logicCommandInput"));
- command_editor_->setPlaceholderText(
- tr("输入 PLC 指令,例如 LD M0、AND M1、OUT M10"));
- command_editor_->setClearButtonEnabled(true);
- command_editor_->setVisible(false);
- command_editor_->setToolTip(
- tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
- command_completer_ = new QCompleter(command_editor_);
- auto *command_model = new QStandardItemModel(0, 3, command_completer_);
- command_model->setHeaderData(0, Qt::Horizontal, tr("指令"));
- command_model->setHeaderData(1, Qt::Horizontal, tr("操作数"));
- command_model->setHeaderData(2, Qt::Horizontal, tr("说明"));
- for (const LogicCommandSuggestion &suggestion
- : LogicCommandService::suggestions())
- {
- QList<QStandardItem *> row{
- new QStandardItem(QString::fromStdString(suggestion.mnemonic)),
- new QStandardItem(QString::fromStdString(suggestion.operand_hint)),
- new QStandardItem(QString::fromStdString(suggestion.description))};
- for (QStandardItem *item : row)
- {
- item->setEnabled(suggestion.supported);
- item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
- }
- command_model->appendRow(row);
- }
- command_completer_->setModel(command_model);
- command_completer_->setCompletionColumn(0);
- command_completer_->setCaseSensitivity(Qt::CaseInsensitive);
- command_completer_->setCompletionMode(QCompleter::PopupCompletion);
- auto *command_popup = new QTreeView;
- command_popup->setRootIsDecorated(false);
- command_popup->setItemsExpandable(false);
- command_popup->setHeaderHidden(false);
- command_popup->setUniformRowHeights(true);
- command_popup->setAlternatingRowColors(true);
- command_popup->setMinimumSize(620, 300);
- command_completer_->setPopup(command_popup);
- command_popup->header()->setStretchLastSection(true);
- command_popup->setColumnWidth(0, 96);
- command_popup->setColumnWidth(1, 190);
- command_editor_->setCompleter(command_completer_);
- connect(
- command_editor_, &QLineEdit::textEdited,
- this,
- [this](const QString &text)
- {
- command_completer_->setCompletionPrefix(
- commandCompletionPrefix(text));
- command_completer_->complete(command_editor_->rect());
- });
- connect(
- command_editor_, &QLineEdit::returnPressed,
- this, &LogicEditorWidget::commitCommandInput);
- command_editor_->installEventFilter(this);
- }
-
- void LogicEditorWidget::setLogicId(const std::string &logic_id)
- {
- const bool logic_changed = logic_id_ != logic_id;
- clearGesture();
- logic_id_ = logic_id;
- if (logic_changed)
- {
- cancelCommandInput();
- selected_rung_id_.clear();
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- selected_vertical_connection_id_.clear();
- }
- else if (!selected_rung_id_.empty()
- && editor_service_.findRung(logic_id_, selected_rung_id_) == nullptr)
- {
- selected_rung_id_.clear();
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- selected_vertical_connection_id_.clear();
- }
- else
- {
- if (!selected_vertical_connection_id_.empty()
- && editor_service_.findConnection(
- logic_id_, selected_vertical_connection_id_) == nullptr)
- {
- selected_vertical_connection_id_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- }
- selected_cells_.erase(
- std::remove_if(
- selected_cells_.begin(),
- selected_cells_.end(),
- [this](const auto &position)
- {
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, position.first, position.second);
- return cell == nullptr || cell->kind == LadderCellKind::Gap;
- }),
- selected_cells_.end());
- selected_output_rung_ids_.erase(
- std::remove_if(
- selected_output_rung_ids_.begin(),
- selected_output_rung_ids_.end(),
- [this](const std::string &rung_id)
- {
- const LadderRung *rung = editor_service_.findRung(
- logic_id_, rung_id);
- return rung == nullptr || !rung->output.has_value();
- }),
- selected_output_rung_ids_.end());
- selected_vertical_connection_ids_.erase(
- std::remove_if(
- selected_vertical_connection_ids_.begin(),
- selected_vertical_connection_ids_.end(),
- [this](const std::string &connection_id)
- {
- return editor_service_.findConnection(
- logic_id_, connection_id) == nullptr;
- }),
- selected_vertical_connection_ids_.end());
- selected_row_ids_.erase(
- std::remove_if(
- selected_row_ids_.begin(),
- selected_row_ids_.end(),
- [this](const std::string &rung_id)
- {
- return editor_service_.findRung(logic_id_, rung_id) == nullptr;
- }),
- selected_row_ids_.end());
- synchronizeSelectedNodes();
- selected_vertical_connection_id_ =
- selected_vertical_connection_ids_.empty()
- ? std::string{} : selected_vertical_connection_ids_.back();
- if (selected_cell_
- && editor_service_.findCell(
- logic_id_, selected_rung_id_, selected_column_)
- == nullptr)
- {
- selected_column_ = -1;
- selected_cell_ = false;
- }
- }
- rebuildScene();
- }
-
- void LogicEditorWidget::setEditingEnabled(bool enabled)
- {
- clearGesture();
- editing_enabled_ = enabled;
- if (!enabled)
- {
- cancelCommandInput();
- mouse_wire_mode_ = MouseWireMode::Select;
- selection_pressed_ = false;
- selection_dragging_ = false;
- selection_band_->hide();
- }
- rebuildScene();
- }
-
- void LogicEditorWidget::setMouseWireMode(MouseWireMode mode)
- {
- clearGesture();
- mouse_wire_mode_ = editing_enabled_ ? mode : MouseWireMode::Select;
- selection_pressed_ = false;
- selection_dragging_ = false;
- selection_band_->hide();
- viewport()->setCursor(mouse_wire_mode_ == MouseWireMode::Select
- ? Qt::ArrowCursor : Qt::CrossCursor);
- }
-
- LogicEditorWidget::MouseWireMode LogicEditorWidget::mouseWireMode() const
- {
- return mouse_wire_mode_;
- }
-
- void LogicEditorWidget::setRuntimeTrace(
- const LogicTraceSnapshot &trace,
- const std::string &fault_node_id)
- {
- trace_ = trace.forLogic(logic_id_);
- fault_node_id_ = fault_node_id;
- runtime_trace_enabled_ = true;
- rebuildScene();
- }
-
- void LogicEditorWidget::clearRuntimeTrace()
- {
- trace_.clear();
- fault_node_id_.clear();
- runtime_trace_enabled_ = false;
- rebuildScene();
- }
-
- void LogicEditorWidget::reloadLogic()
- {
- setLogicId(logic_id_);
- }
-
- void LogicEditorWidget::clearSelection()
- {
- selected_rung_id_.clear();
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- selected_vertical_connection_id_.clear();
- rebuildScene();
- emit nodeSelected(QString{});
- }
-
- void LogicEditorWidget::selectNode(const std::string &node_id)
- {
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- selected_vertical_connection_id_.clear();
- if (!node_id.empty())
- {
- selected_node_ids_.push_back(node_id);
- const std::string rung_id = editor_service_.rungIdForNode(
- logic_id_, node_id);
- const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
- if (rung != nullptr)
- {
- selected_rung_id_ = rung_id;
- for (std::size_t index = 0U; index < rung->cells.size(); ++index)
- {
- if (rung->cells[index].node.has_value()
- && rung->cells[index].node->id == node_id)
- {
- selected_column_ = static_cast<int>(index);
- selected_cell_ = true;
- selected_cells_.push_back({
- rung_id, static_cast<int>(index)});
- break;
- }
- }
- if (!selected_cell_ && rung->output.has_value()
- && rung->output->id == node_id)
- {
- selected_output_rung_ids_.push_back(rung_id);
- }
- }
- }
- rebuildScene();
- emit nodeSelected(QString::fromStdString(node_id));
- }
-
- void LogicEditorWidget::focusSyntaxLocation(
- const std::string &rung_id, int column)
- {
- const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
- if (rung == nullptr)
- {
- return;
- }
- cancelCommandInput();
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_vertical_connection_id_.clear();
- selected_rung_id_ = rung_id;
- selected_boundary_ = false;
-
- const bool output = column >= ProjectLimits::kMaximumLadderColumns;
- selected_output_ = output;
- selected_cell_ = !output;
- selected_column_ = output
- ? ProjectLimits::kMaximumConditionColumns
- : std::max(0, std::min(
- column - 1, ProjectLimits::kMaximumConditionColumns - 1));
- if (output)
- {
- selected_output_rung_ids_.push_back(rung_id);
- if (rung->output.has_value())
- {
- selected_node_ids_.push_back(rung->output->id);
- }
- }
- else
- {
- const LadderCell &cell = rung->cells[
- static_cast<std::size_t>(selected_column_)];
- if (cell.kind != LadderCellKind::Gap)
- {
- selected_cells_.push_back({rung_id, selected_column_});
- }
- if (cell.node.has_value())
- {
- selected_node_ids_.push_back(cell.node->id);
- }
- }
- rebuildScene();
- notifySelectionChanged();
-
- const RowLayout *layout = layoutForRung(rung_id);
- if (layout != nullptr)
- {
- const qreal x = output
- ? kConditionRight
- : kLeftBus + static_cast<qreal>(selected_column_) * kCellWidth;
- ensureVisible(
- QRectF(
- x,
- layout->gridTop,
- output ? kOutputWidth : kCellWidth,
- kRowHeight),
- 24,
- 24);
- }
- setFocus(Qt::OtherFocusReason);
- }
-
- std::string LogicEditorWidget::selectedNodeId() const
- {
- return selected_node_ids_.empty() ? std::string{} : selected_node_ids_.front();
- }
-
- std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
- {
- return selected_node_ids_;
- }
-
- std::string LogicEditorWidget::selectedRungId() const
- {
- return !selected_rung_id_.empty()
- && editor_service_.findRung(logic_id_, selected_rung_id_) != nullptr
- ? selected_rung_id_ : std::string{};
- }
-
- bool LogicEditorWidget::hasCopyableSelection() const
- {
- return !selected_cells_.empty()
- || !selected_output_rung_ids_.empty()
- || !selected_vertical_connection_ids_.empty()
- || !selected_row_ids_.empty();
- }
-
- LogicClipboardCopyResult LogicEditorWidget::copySelection() const
- {
- LogicSelectionCopyRequest selection;
- selection.cells = selected_cells_;
- selection.outputRungIds = selected_output_rung_ids_;
- selection.verticalConnectionIds = selected_vertical_connection_ids_;
- selection.wholeRungIds = selected_row_ids_;
- return editor_service_.copySelection(logic_id_, selection);
- }
-
- LogicPasteTarget LogicEditorWidget::pasteTarget() const
- {
- LogicPasteTarget target;
- target.rungId = selected_rung_id_.empty()
- ? editor_service_.firstRungId(logic_id_)
- : selected_rung_id_;
- target.output = selected_output_;
- target.boundary = selected_boundary_;
- target.column = selected_output_
- ? ProjectLimits::kMaximumConditionColumns
- : std::max(selected_column_, 0);
- return target;
- }
-
- std::string LogicEditorWidget::currentRungId() const
- {
- if (!selected_rung_id_.empty()
- && editor_service_.findRung(logic_id_, selected_rung_id_) != nullptr)
- {
- return selected_rung_id_;
- }
- return editor_service_.firstRungId(logic_id_);
- }
-
- int LogicEditorWidget::rowAt(const std::string &rung_id) const
- {
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr)
- {
- return -1;
- }
- for (std::size_t index = 0U; index < logic->rungs.size(); ++index)
- {
- if (logic->rungs[index].id == rung_id)
- {
- return static_cast<int>(index);
- }
- }
- return -1;
- }
-
- void LogicEditorWidget::rebuildRowLayout(const ControlLogic &logic)
- {
- row_layouts_.clear();
- qreal next_top = kTop;
- for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
- {
- const bool network_head = logic.networkHeadIndex(row) == row;
- if (network_head)
- {
- next_top += kCommentBandHeight;
- }
- RowLayout layout;
- layout.rungId = logic.rungs[row].id;
- layout.row = static_cast<int>(row);
- layout.gridTop = next_top;
- layout.centerY = next_top + kRowHeight / 2.0 + 6.0;
- layout.bottom = next_top + kRowHeight;
- layout.networkHead = network_head;
- row_layouts_.push_back(std::move(layout));
- next_top += kRowHeight;
- }
- }
-
- const LogicEditorWidget::RowLayout *LogicEditorWidget::layoutForRung(
- const std::string &rung_id) const
- {
- const auto found = std::find_if(
- row_layouts_.cbegin(),
- row_layouts_.cend(),
- [&rung_id](const RowLayout &layout)
- {
- return layout.rungId == rung_id;
- });
- return found == row_layouts_.cend() ? nullptr : &*found;
- }
-
- LogicEditorWidget::Hit LogicEditorWidget::hitAt(
- const QPointF &scene_position) const
- {
- const QList<QGraphicsItem *> items = scene_->items(scene_position);
- const auto hit_for_type = [&items](const QString &wanted) -> Hit
- {
- for (QGraphicsItem *item : items)
- {
- if (item->data(0).toString() != wanted)
- {
- continue;
- }
- Hit hit;
- if (wanted == QStringLiteral("vertical"))
- {
- hit.rungId = item->data(2).toString().toStdString();
- hit.column = item->data(3).toInt();
- hit.vertical = true;
- hit.lowerRungId = item->data(4).toString().toStdString();
- hit.objectId = item->data(1).toString().toStdString();
- }
- else if (wanted == QStringLiteral("boundary"))
- {
- hit.rungId = item->data(1).toString().toStdString();
- hit.column = item->data(2).toInt();
- hit.boundary = true;
- }
- else if (wanted == QStringLiteral("cell"))
- {
- hit.rungId = item->data(1).toString().toStdString();
- hit.column = item->data(2).toInt();
- hit.objectId = item->data(3).toString().toStdString();
- hit.cellKind = static_cast<LadderCellKind>(item->data(4).toInt());
- }
- else if (wanted == QStringLiteral("output"))
- {
- hit.rungId = item->data(1).toString().toStdString();
- hit.column = ProjectLimits::kMaximumConditionColumns;
- hit.output = true;
- hit.objectId = item->data(2).toString().toStdString();
- }
- else if (wanted == QStringLiteral("rowHeader"))
- {
- hit.rungId = item->data(1).toString().toStdString();
- hit.rowHeader = true;
- }
- return hit;
- }
- return {};
- };
-
- Hit hit = hit_for_type(QStringLiteral("rowHeader"));
- if (!hit.rungId.empty())
- {
- return hit;
- }
- hit = hit_for_type(QStringLiteral("vertical"));
- if (!hit.rungId.empty())
- {
- return hit;
- }
- hit = hit_for_type(QStringLiteral("boundary"));
- if (!hit.rungId.empty())
- {
- return hit;
- }
- hit = hit_for_type(QStringLiteral("cell"));
- if (!hit.rungId.empty())
- {
- return hit;
- }
- hit = hit_for_type(QStringLiteral("output"));
- if (!hit.rungId.empty())
- {
- return hit;
- }
- return {};
- }
-
- void LogicEditorWidget::rebuildScene()
- {
- scene_->clear();
- row_layouts_.clear();
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr)
- {
- scene_->setSceneRect(0.0, 0.0, kRightBus + kSceneRightMargin, 120.0);
- return;
- }
- rebuildRowLayout(*logic);
- const qreal height = row_layouts_.empty()
- ? 120.0 : row_layouts_.back().bottom + 24.0;
- scene_->setSceneRect(
- 0.0, 0.0, kRightBus + kSceneRightMargin, height);
- if (row_layouts_.empty())
- {
- return;
- }
-
- std::vector<GridRow> grid_rows;
- grid_rows.reserve(logic->rungs.size());
- for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
- {
- const RowLayout &layout = row_layouts_[row];
- GridRow grid_row;
- grid_row.top = layout.gridTop;
- grid_rows.push_back(std::move(grid_row));
- }
- scene_->addItem(new GridLayerItem(std::move(grid_rows)));
-
- QPen bus_pen(kLadderColor);
- bus_pen.setWidthF(2.2);
- bus_pen.setCosmetic(true);
- QGraphicsLineItem *left_bus = scene_->addLine(
- kLeftBus,
- row_layouts_.front().gridTop,
- kLeftBus,
- row_layouts_.back().bottom,
- bus_pen);
- left_bus->setZValue(5.0);
- QGraphicsLineItem *right_bus = scene_->addLine(
- kRightBus,
- row_layouts_.front().gridTop,
- kRightBus,
- row_layouts_.back().bottom,
- bus_pen);
- right_bus->setZValue(5.0);
-
- for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
- {
- const LadderRung &rung = logic->rungs[row];
- const RowLayout &layout = row_layouts_[row];
- QGraphicsRectItem *row_header = scene_->addRect(
- QRectF(0.0, layout.gridTop, kLeftBus - 4.0, kRowHeight),
- QPen(Qt::NoPen),
- QBrush(Qt::transparent));
- row_header->setData(0, QStringLiteral("rowHeader"));
- row_header->setData(1, QString::fromStdString(rung.id));
- row_header->setZValue(29.0);
- QGraphicsSimpleTextItem *label = scene_->addSimpleText(
- QStringLiteral("%1")
- .arg(static_cast<int>(row), 3, 10, QLatin1Char('0')));
- label->setBrush(QColor(QStringLiteral("#66757d")));
- label->setPos(10.0, layout.centerY - 10.0);
- label->setZValue(6.0);
- if (layout.networkHead && !rung.comment.empty())
- {
- QGraphicsSimpleTextItem *comment = scene_->addSimpleText(
- QString::fromStdString(rung.comment));
- QFont comment_font = comment->font();
- comment_font.setPointSizeF(9.0);
- comment->setFont(comment_font);
- comment->setBrush(kCommentColor);
- comment->setPos(
- kLeftBus + 7.0,
- layout.gridTop - kCommentBandHeight + 4.0);
- comment->setZValue(6.0);
- }
- for (int column = 0;
- column < ProjectLimits::kMaximumConditionColumns;
- ++column)
- {
- const LadderCell &cell = rung.cells[static_cast<std::size_t>(column)];
- const qreal x = kLeftBus + static_cast<qreal>(column) * kCellWidth;
- const auto input_power = trace_.cellInputPowerValues.find(cell.id);
- const bool input_active = runtime_trace_enabled_
- && input_power != trace_.cellInputPowerValues.end()
- && input_power->second;
- const auto output_power = trace_.cellPowerValues.find(cell.id);
- const bool output_active = runtime_trace_enabled_
- && output_power != trace_.cellPowerValues.end()
- && output_power->second;
- const bool faulted = cell.node.has_value()
- && cell.node->id == fault_node_id_;
- scene_->addItem(new CellContentItem(
- cell,
- rung.id,
- column,
- QPointF(x, layout.gridTop),
- input_active,
- output_active,
- faulted));
- }
- for (int boundary = 0;
- boundary <= ProjectLimits::kMaximumConditionColumns;
- ++boundary)
- {
- const qreal x = kLeftBus + static_cast<qreal>(boundary) * kCellWidth;
- QPen boundary_pen(Qt::transparent);
- boundary_pen.setWidthF(12.0);
- QGraphicsLineItem *hit_line = scene_->addLine(
- x,
- layout.gridTop + 8.0,
- x,
- layout.bottom - 8.0,
- boundary_pen);
- hit_line->setData(0, QStringLiteral("boundary"));
- hit_line->setData(1, QString::fromStdString(rung.id));
- hit_line->setData(2, boundary);
- hit_line->setZValue(30.0);
- }
- const auto rung_power = trace_.rungValues.find(rung.id);
- const bool input_active = runtime_trace_enabled_
- && rung_power != trace_.rungValues.end() && rung_power->second;
- bool symbol_active = false;
- if (rung.output.has_value())
- {
- const auto output_value = trace_.nodeValues.find(rung.output->id);
- symbol_active = runtime_trace_enabled_
- && output_value != trace_.nodeValues.end()
- && output_value->second;
- }
- const bool faulted = rung.output.has_value()
- && rung.output->id == fault_node_id_;
- scene_->addItem(new OutputContentItem(
- rung,
- QPointF(kConditionRight, layout.gridTop),
- input_active,
- symbol_active,
- faulted));
- }
- for (const VerticalConnection &connection : logic->verticalConnections)
- {
- const RowLayout *upper = layoutForRung(connection.upperRungId);
- const RowLayout *lower = layoutForRung(connection.lowerRungId);
- if (upper == nullptr || lower == nullptr)
- {
- continue;
- }
- const qreal x = kLeftBus
- + static_cast<qreal>(connection.columnBoundary) * kCellWidth;
- const auto power = trace_.verticalConnectionValues.find(connection.id);
- const bool active = runtime_trace_enabled_
- && power != trace_.verticalConnectionValues.end() && power->second;
- scene_->addItem(new VerticalConnectionItem(
- connection, x, upper->centerY, lower->centerY, active));
- }
-
- std::vector<QRectF> selection_rectangles;
- for (const std::string &rung_id : selected_row_ids_)
- {
- const RowLayout *layout = layoutForRung(rung_id);
- if (layout != nullptr)
- {
- selection_rectangles.emplace_back(
- 0.0,
- layout->gridTop,
- kRightBus,
- kRowHeight);
- }
- }
- for (const auto &position : selected_cells_)
- {
- const RowLayout *layout = layoutForRung(position.first);
- if (layout != nullptr && position.second >= 0
- && position.second < ProjectLimits::kMaximumConditionColumns)
- {
- selection_rectangles.emplace_back(
- kLeftBus + static_cast<qreal>(position.second) * kCellWidth,
- layout->gridTop,
- kCellWidth,
- kRowHeight);
- }
- }
- for (const std::string &rung_id : selected_output_rung_ids_)
- {
- const RowLayout *layout = layoutForRung(rung_id);
- if (layout != nullptr)
- {
- selection_rectangles.emplace_back(
- kConditionRight,
- layout->gridTop,
- kOutputWidth,
- kRowHeight);
- }
- }
- for (const std::string &connection_id : selected_vertical_connection_ids_)
- {
- const VerticalConnection *connection = editor_service_.findConnection(
- logic_id_, connection_id);
- if (connection == nullptr)
- {
- continue;
- }
- const RowLayout *upper = layoutForRung(connection->upperRungId);
- const RowLayout *lower = layoutForRung(connection->lowerRungId);
- if (upper == nullptr || lower == nullptr)
- {
- continue;
- }
- const qreal x = kLeftBus
- + static_cast<qreal>(connection->columnBoundary) * kCellWidth;
- selection_rectangles.emplace_back(
- x - 7.0,
- upper->centerY,
- 14.0,
- lower->centerY - upper->centerY);
- }
- if (selected_cell_)
- {
- const std::pair<std::string, int> cursor{
- selected_rung_id_, selected_column_};
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, cursor.first, cursor.second);
- const bool no_object_selection = selected_cells_.empty()
- && selected_output_rung_ids_.empty()
- && selected_vertical_connection_ids_.empty();
- if (!containsCell(selected_cells_, cursor) && cell != nullptr
- && (cell->kind == LadderCellKind::Gap || no_object_selection))
- {
- const RowLayout *layout = layoutForRung(cursor.first);
- if (layout != nullptr)
- {
- selection_rectangles.emplace_back(
- kLeftBus + static_cast<qreal>(cursor.second) * kCellWidth,
- layout->gridTop,
- kCellWidth,
- kRowHeight);
- }
- }
- }
- if (selected_output_
- && !containsId(selected_output_rung_ids_, selected_rung_id_))
- {
- const RowLayout *layout = layoutForRung(selected_rung_id_);
- if (layout != nullptr)
- {
- selection_rectangles.emplace_back(
- kConditionRight,
- layout->gridTop,
- kOutputWidth,
- kRowHeight);
- }
- }
- if (!selection_rectangles.empty())
- {
- scene_->addItem(new SelectionOverlayItem(
- std::move(selection_rectangles)));
- }
- }
-
- void LogicEditorWidget::clearObjectSelection()
- {
- selected_node_ids_.clear();
- selected_cells_.clear();
- selected_output_rung_ids_.clear();
- selected_vertical_connection_ids_.clear();
- selected_row_ids_.clear();
- selected_vertical_connection_id_.clear();
- }
-
- void LogicEditorWidget::synchronizeSelectedNodes()
- {
- selected_node_ids_.clear();
- for (const auto &position : selected_cells_)
- {
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, position.first, position.second);
- if (cell != nullptr && cell->node.has_value())
- {
- selected_node_ids_.push_back(cell->node->id);
- }
- }
- for (const std::string &rung_id : selected_output_rung_ids_)
- {
- const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
- if (rung != nullptr && rung->output.has_value())
- {
- selected_node_ids_.push_back(rung->output->id);
- }
- }
- }
-
- void LogicEditorWidget::notifySelectionChanged()
- {
- emit nodeSelected(selected_node_ids_.empty()
- ? QString{} : QString::fromStdString(selected_node_ids_.front()));
- }
-
- void LogicEditorWidget::selectObject(
- const Hit &hit, bool extend_node_selection)
- {
- if (hit.rowHeader)
- {
- if (!extend_node_selection)
- {
- clearObjectSelection();
- selected_row_ids_.clear();
- }
- else if (!selected_cells_.empty()
- || !selected_output_rung_ids_.empty()
- || !selected_vertical_connection_ids_.empty())
- {
- clearObjectSelection();
- }
- const auto selected = std::find(
- selected_row_ids_.begin(), selected_row_ids_.end(), hit.rungId);
- if (extend_node_selection && selected != selected_row_ids_.end())
- {
- selected_row_ids_.erase(selected);
- }
- else if (selected == selected_row_ids_.end())
- {
- selected_row_ids_.push_back(hit.rungId);
- }
- std::sort(
- selected_row_ids_.begin(), selected_row_ids_.end(),
- [this](const std::string &left, const std::string &right)
- {
- return rowAt(left) < rowAt(right);
- });
- selected_rung_id_ = hit.rungId;
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- synchronizeSelectedNodes();
- rebuildScene();
- notifySelectionChanged();
- return;
- }
-
- selected_row_ids_.clear();
- if (!extend_node_selection)
- {
- clearObjectSelection();
- }
- if (hit.rungId.empty())
- {
- if (!extend_node_selection)
- {
- selected_rung_id_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- }
- rebuildScene();
- notifySelectionChanged();
- return;
- }
-
- selected_rung_id_ = hit.rungId;
- selected_column_ = hit.column;
- selected_cell_ = hit.column >= 0 && !hit.boundary
- && !hit.output && !hit.vertical;
- selected_output_ = hit.output;
- selected_boundary_ = hit.boundary || hit.vertical;
- if (hit.vertical)
- {
- const auto selected = std::find(
- selected_vertical_connection_ids_.begin(),
- selected_vertical_connection_ids_.end(),
- hit.objectId);
- if (extend_node_selection
- && selected != selected_vertical_connection_ids_.end())
- {
- selected_vertical_connection_ids_.erase(selected);
- }
- else if (selected == selected_vertical_connection_ids_.end())
- {
- selected_vertical_connection_ids_.push_back(hit.objectId);
- }
- }
- else if (hit.output && !hit.objectId.empty())
- {
- const auto selected = std::find(
- selected_output_rung_ids_.begin(),
- selected_output_rung_ids_.end(),
- hit.rungId);
- if (extend_node_selection && selected != selected_output_rung_ids_.end())
- {
- selected_output_rung_ids_.erase(selected);
- }
- else if (selected == selected_output_rung_ids_.end())
- {
- selected_output_rung_ids_.push_back(hit.rungId);
- }
- }
- else if (!hit.boundary && !hit.vertical
- && hit.cellKind != LadderCellKind::Gap)
- {
- const std::pair<std::string, int> position{hit.rungId, hit.column};
- const auto selected = std::find(
- selected_cells_.begin(), selected_cells_.end(), position);
- if (extend_node_selection && selected != selected_cells_.end())
- {
- selected_cells_.erase(selected);
- }
- else if (selected == selected_cells_.end())
- {
- selected_cells_.push_back(position);
- }
- }
- selected_vertical_connection_id_ =
- selected_vertical_connection_ids_.empty()
- ? std::string{} : selected_vertical_connection_ids_.back();
- synchronizeSelectedNodes();
- rebuildScene();
- notifySelectionChanged();
- }
-
- void LogicEditorWidget::selectObjectsInBand(
- const QRect &viewport_rect, bool extend_selection)
- {
- if (!selected_row_ids_.empty())
- {
- selected_row_ids_.clear();
- clearObjectSelection();
- }
- if (!extend_selection)
- {
- clearObjectSelection();
- }
- const QPolygonF scene_polygon = mapToScene(viewport_rect.normalized());
- QPainterPath selection_path;
- selection_path.addPolygon(scene_polygon);
- selection_path.closeSubpath();
- const QList<QGraphicsItem *> items = scene_->items(
- selection_path,
- Qt::IntersectsItemShape,
- Qt::DescendingOrder);
- for (QGraphicsItem *item : items)
- {
- const QString type = item->data(0).toString();
- if (type == QStringLiteral("cell"))
- {
- const LadderCellKind kind = static_cast<LadderCellKind>(
- item->data(4).toInt());
- if (kind == LadderCellKind::Gap)
- {
- continue;
- }
- const std::pair<std::string, int> position{
- item->data(1).toString().toStdString(),
- item->data(2).toInt()};
- if (!containsCell(selected_cells_, position))
- {
- selected_cells_.push_back(position);
- }
- }
- else if (type == QStringLiteral("output")
- && !item->data(2).toString().isEmpty())
- {
- const std::string rung_id = item->data(1).toString().toStdString();
- if (!containsId(selected_output_rung_ids_, rung_id))
- {
- selected_output_rung_ids_.push_back(rung_id);
- }
- }
- else if (type == QStringLiteral("vertical"))
- {
- const std::string connection_id =
- item->data(1).toString().toStdString();
- if (!containsId(selected_vertical_connection_ids_, connection_id))
- {
- selected_vertical_connection_ids_.push_back(connection_id);
- }
- }
- }
-
- std::sort(
- selected_cells_.begin(),
- selected_cells_.end(),
- [this](const auto &left, const auto &right)
- {
- const int left_row = rowAt(left.first);
- const int right_row = rowAt(right.first);
- return left_row != right_row
- ? left_row < right_row : left.second < right.second;
- });
- std::sort(
- selected_output_rung_ids_.begin(),
- selected_output_rung_ids_.end(),
- [this](const std::string &left, const std::string &right)
- {
- return rowAt(left) < rowAt(right);
- });
- const Hit center_hit = hitAt(mapToScene(viewport_rect.center()));
- if (!center_hit.rungId.empty())
- {
- selected_rung_id_ = center_hit.rungId;
- selected_column_ = center_hit.column;
- selected_cell_ = center_hit.column >= 0 && !center_hit.output
- && !center_hit.vertical && !center_hit.boundary
- && !center_hit.rowHeader;
- selected_output_ = center_hit.output;
- selected_boundary_ = center_hit.boundary || center_hit.vertical;
- }
- selected_vertical_connection_id_ =
- selected_vertical_connection_ids_.empty()
- ? std::string{} : selected_vertical_connection_ids_.back();
- synchronizeSelectedNodes();
- rebuildScene();
- notifySelectionChanged();
- }
-
- void LogicEditorWidget::beginGesture(const Hit &hit)
- {
- if (!editing_enabled_ || hit.rungId.empty() || hit.output)
- {
- return;
- }
- gesture_active_ = true;
- gesture_origin_ = hit;
- gesture_current_ = hit;
- gesture_scene_position_valid_ = false;
- viewport()->update();
- }
-
- void LogicEditorWidget::updateGesture(
- const Hit &hit, const QPointF &scene_position)
- {
- if (!gesture_active_)
- {
- return;
- }
- gesture_current_ = hit;
- gesture_scene_position_ = scene_position;
- gesture_scene_position_valid_ = true;
- viewport()->update();
- }
-
- void LogicEditorWidget::finishGesture(const Hit &hit)
- {
- if (!gesture_active_)
- {
- return;
- }
- const Hit origin = gesture_origin_;
- gesture_active_ = false;
- gesture_origin_ = {};
- gesture_current_ = {};
- gesture_scene_position_valid_ = false;
- viewport()->update();
- if (origin.rungId.empty() || hit.rungId.empty())
- {
- return;
- }
- LogicEditorResult result;
- const bool connected = mouse_wire_mode_ == MouseWireMode::Draw;
- if ((origin.vertical || origin.boundary)
- && (hit.vertical || hit.boundary))
- {
- if (!connected && origin.vertical
- && origin.objectId == hit.objectId)
- {
- result = editor_service_.removeVerticalConnections(
- logic_id_, {origin.objectId});
- }
- else if (origin.column != hit.column)
- {
- result = {false, LogicEditorError::InvalidOperation,
- "竖线拖动必须保持在同一列边界", {}};
- }
- else
- {
- std::string first_rung = origin.rungId;
- std::string last_rung = hit.rungId;
- if (last_rung.empty() && !origin.lowerRungId.empty())
- {
- last_rung = origin.lowerRungId;
- }
- result = editor_service_.setVerticalConnectionRange(
- logic_id_, first_rung, last_rung,
- origin.column, connected);
- }
- }
- else if (origin.vertical || hit.vertical)
- {
- result = {false, LogicEditorError::InvalidOperation,
- "请从网格边界开始竖向拖动", {}};
- }
- else if (origin.column == hit.column
- && origin.rungId != hit.rungId)
- {
- result = editor_service_.setVerticalConnectionRange(
- logic_id_, origin.rungId, hit.rungId,
- origin.column, connected);
- }
- else if (origin.rungId == hit.rungId)
- {
- result = editor_service_.setHorizontalWireRange(
- logic_id_, origin.rungId,
- origin.column, hit.column, connected);
- }
- else
- {
- result = {false, LogicEditorError::InvalidOperation,
- "画线必须沿同一行或同一列边界进行", {}};
- }
- if (!result.succeeded)
- {
- reportFailure(result);
- }
- else
- {
- clearObjectSelection();
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- }
-
- void LogicEditorWidget::clearGesture()
- {
- if (!gesture_active_ && !gesture_scene_position_valid_)
- {
- return;
- }
- gesture_active_ = false;
- gesture_origin_ = {};
- gesture_current_ = {};
- gesture_scene_position_ = {};
- gesture_scene_position_valid_ = false;
- viewport()->update();
- }
-
- void LogicEditorWidget::drawGesturePreview(QPainter *painter) const
- {
- if (!gesture_active_ || painter == nullptr
- || gesture_origin_.rungId.empty())
- {
- return;
- }
-
- enum class PreviewKind { Invalid, Horizontal, Vertical };
- PreviewKind kind = PreviewKind::Invalid;
- bool valid = false;
- const Hit &origin = gesture_origin_;
- const Hit ¤t = gesture_current_;
- const bool connected = mouse_wire_mode_ == MouseWireMode::Draw;
-
- if (!current.rungId.empty())
- {
- if ((origin.vertical || origin.boundary)
- && (current.vertical || current.boundary))
- {
- kind = PreviewKind::Vertical;
- valid = origin.column == current.column
- && (origin.rungId != current.rungId
- || (!connected && origin.vertical
- && origin.objectId == current.objectId));
- }
- else if (origin.vertical || current.vertical)
- {
- kind = PreviewKind::Invalid;
- }
- else if (origin.column == current.column
- && origin.rungId != current.rungId)
- {
- kind = PreviewKind::Vertical;
- valid = origin.column >= 0
- && origin.column <= ProjectLimits::kMaximumConditionColumns;
- }
- else if (origin.rungId == current.rungId)
- {
- kind = PreviewKind::Horizontal;
- valid = origin.column >= 0 && current.column >= 0
- && origin.column < ProjectLimits::kMaximumConditionColumns
- && current.column < ProjectLimits::kMaximumConditionColumns;
- }
- }
-
- QColor preview_color = valid
- ? connected ? QColor(QStringLiteral("#277da1"))
- : QColor(QStringLiteral("#c56a1a"))
- : kFaultColor;
- preview_color.setAlpha(220);
- QPen preview_pen(preview_color, valid ? 2.8 : 2.4, Qt::DashLine);
- preview_pen.setCosmetic(true);
- preview_pen.setCapStyle(Qt::SquareCap);
- painter->save();
- painter->setRenderHint(QPainter::Antialiasing, true);
- painter->setPen(preview_pen);
- painter->setBrush(Qt::NoBrush);
-
- if (valid && kind == PreviewKind::Horizontal)
- {
- const RowLayout *layout = layoutForRung(origin.rungId);
- if (layout != nullptr)
- {
- const int first = std::min(origin.column, current.column);
- const int last = std::max(origin.column, current.column);
- for (int column = first; column <= last; ++column)
- {
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, origin.rungId, column);
- if (cell == nullptr || cell->kind == LadderCellKind::Node
- || (mouse_wire_mode_ == MouseWireMode::Erase
- && cell->kind != LadderCellKind::Wire))
- {
- continue;
- }
- const qreal left = kLeftBus
- + static_cast<qreal>(column) * kCellWidth;
- painter->drawLine(
- QPointF(left, layout->centerY),
- QPointF(left + kCellWidth, layout->centerY));
- }
- }
- }
- else if (valid && kind == PreviewKind::Vertical)
- {
- const RowLayout *first = layoutForRung(origin.rungId);
- const RowLayout *last = layoutForRung(current.rungId);
- if (first != nullptr && last != nullptr)
- {
- const qreal x = kLeftBus
- + static_cast<qreal>(origin.column) * kCellWidth;
- painter->drawLine(
- QPointF(x, first->centerY),
- QPointF(x, last->centerY));
- }
- }
- else
- {
- const RowLayout *layout = layoutForRung(origin.rungId);
- if (layout != nullptr && gesture_scene_position_valid_)
- {
- const qreal origin_x = kLeftBus
- + (origin.vertical || origin.boundary
- ? static_cast<qreal>(origin.column) * kCellWidth
- : (static_cast<qreal>(origin.column) + 0.5) * kCellWidth);
- painter->drawLine(
- QPointF(origin_x, layout->centerY),
- gesture_scene_position_);
- }
- }
- painter->restore();
- }
-
- void LogicEditorWidget::showCommandEditor(const Hit &hit)
- {
- if (!editing_enabled_ || hit.rungId.empty()
- || hit.boundary || hit.vertical)
- {
- return;
- }
- const std::vector<std::string> parallel_node_ids = selectedNodeIds();
- cancelCommandInput();
- command_target_.rungId = hit.rungId;
- command_target_.column = hit.column;
- const LogicNode *existing = hit.objectId.empty()
- ? nullptr : editor_service_.findNode(logic_id_, hit.objectId);
- if (existing != nullptr)
- {
- command_target_.kind = LogicCommandTargetKind::ExistingNode;
- }
- else if (hit.output)
- {
- command_target_.kind = LogicCommandTargetKind::Output;
- }
- else
- {
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, hit.rungId, hit.column);
- command_target_.kind = cell != nullptr
- && cell->kind == LadderCellKind::Wire
- ? LogicCommandTargetKind::WireColumn
- : LogicCommandTargetKind::GapColumn;
- }
- command_target_.expressionId = hit.objectId;
- command_parallel_node_ids_ = parallel_node_ids;
-
- QString initial_text;
- if (existing != nullptr && existing->isConfigured())
- {
- initial_text = logicCommandText(existing->config);
- }
- command_editor_->setStyleSheet(QString{});
- command_editor_->setToolTip(
- tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
- command_editor_->setText(initial_text);
- command_editor_->setVisible(true);
- QPointF center;
- if (!findCommandTargetCenter(command_target_, ¢er))
- {
- cancelCommandInput();
- return;
- }
- positionCommandInput(center);
- command_editor_->raise();
- command_editor_->setFocus(Qt::MouseFocusReason);
- command_editor_->selectAll();
- }
-
- void LogicEditorWidget::commitCommandInput()
- {
- if (command_editor_ == nullptr || !command_editor_->isVisible())
- {
- return;
- }
- LogicCommandRequest request;
- request.logicId = logic_id_;
- request.text = command_editor_->text().trimmed().toStdString();
- request.target = command_target_;
- request.parallelNodeIds = command_parallel_node_ids_;
- const LogicCommandResult result = command_service_.execute(request);
- if (!result.succeeded)
- {
- command_editor_->setStyleSheet(
- QStringLiteral("QLineEdit { border: 2px solid #c5362e; }"));
- command_editor_->setToolTip(QString::fromStdString(result.message));
- command_editor_->setFocus(Qt::OtherFocusReason);
- command_editor_->selectAll();
- emit editorError(QString::fromStdString(result.message));
- return;
- }
-
- command_editor_->setStyleSheet(QString{});
- command_editor_->setToolTip(
- tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
- command_parallel_node_ids_.clear();
- if (!result.hasNextCursor)
- {
- selectNode(result.id);
- emit graphChanged();
- cancelCommandInput();
- return;
- }
-
- moveToCursor(result.nextCursor);
- emit graphChanged();
- command_target_ = commandTargetForCursor(result.nextCursor);
- QPointF center;
- if (command_target_.rungId.empty()
- || !findCommandTargetCenter(command_target_, ¢er))
- {
- cancelCommandInput();
- return;
- }
- command_editor_->clear();
- positionCommandInput(center);
- command_editor_->raise();
- command_editor_->setFocus(Qt::OtherFocusReason);
- }
-
- void LogicEditorWidget::cancelCommandInput()
- {
- if (command_editor_ != nullptr)
- {
- command_editor_->clear();
- command_editor_->setStyleSheet(QString{});
- command_editor_->setVisible(false);
- }
- command_target_ = {};
- command_parallel_node_ids_.clear();
- }
-
- void LogicEditorWidget::positionCommandInput(const QPointF &scene_center)
- {
- if (command_editor_ == nullptr)
- {
- return;
- }
- const QPoint view_center = mapFromScene(scene_center);
- const int width = 360;
- const int height = 38;
- const QRect bounds = viewport()->rect().adjusted(4, 4, -4, -4);
- const int left = std::clamp(
- view_center.x() - width / 2,
- bounds.left(),
- std::max(bounds.left(), bounds.right() - width + 1));
- const int top = std::clamp(
- view_center.y() - height / 2,
- bounds.top(),
- std::max(bounds.top(), bounds.bottom() - height + 1));
- command_editor_->setGeometry(left, top, width, height);
- }
-
- bool LogicEditorWidget::findCommandTargetCenter(
- const LogicCommandTarget &target,
- QPointF *scene_center) const
- {
- if (scene_center == nullptr)
- {
- return false;
- }
- const RowLayout *layout = layoutForRung(target.rungId);
- if (layout == nullptr)
- {
- return false;
- }
- if (target.kind == LogicCommandTargetKind::Output)
- {
- *scene_center = QPointF(
- kConditionRight + kOutputWidth / 2.0,
- layout->centerY);
- return true;
- }
- int column = target.column;
- if (target.kind == LogicCommandTargetKind::ExistingNode)
- {
- const LadderRung *rung = editor_service_.findRung(
- logic_id_, target.rungId);
- if (rung == nullptr)
- {
- return false;
- }
- if (rung->output.has_value()
- && rung->output->id == target.expressionId)
- {
- *scene_center = QPointF(
- kConditionRight + kOutputWidth / 2.0,
- layout->centerY);
- return true;
- }
- const auto found = std::find_if(
- rung->cells.cbegin(), rung->cells.cend(),
- [&target](const LadderCell &cell)
- {
- return cell.node.has_value()
- && cell.node->id == target.expressionId;
- });
- if (found == rung->cells.cend())
- {
- return false;
- }
- column = static_cast<int>(std::distance(rung->cells.cbegin(), found));
- }
- if (column < 0 || column >= ProjectLimits::kMaximumConditionColumns)
- {
- return false;
- }
- *scene_center = QPointF(
- kLeftBus + (static_cast<qreal>(column) + 0.5) * kCellWidth,
- layout->centerY);
- return true;
- }
-
- LogicCommandTarget LogicEditorWidget::commandTargetForCursor(
- const LogicEditCursor &cursor) const
- {
- LogicCommandTarget target;
- target.rungId = cursor.rungId;
- target.column = cursor.column;
- if (cursor.output)
- {
- target.kind = LogicCommandTargetKind::Output;
- return target;
- }
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, cursor.rungId, cursor.column);
- if (cell != nullptr && cell->node.has_value())
- {
- target.kind = LogicCommandTargetKind::ExistingNode;
- target.expressionId = cell->node->id;
- }
- else
- {
- target.kind = cell != nullptr && cell->kind == LadderCellKind::Wire
- ? LogicCommandTargetKind::WireColumn
- : LogicCommandTargetKind::GapColumn;
- }
- return target;
- }
-
- LogicEditCursor LogicEditorWidget::conditionInsertionCursor() const
- {
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr || logic->rungs.empty())
- {
- return {};
- }
- const std::string rung_id = currentRungId();
- if (selected_output_)
- {
- return {rung_id, ProjectLimits::kMaximumConditionColumns, true};
- }
- if (selected_cell_ && selected_column_ >= 0)
- {
- int target_column = selected_column_;
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, rung_id, selected_column_);
- if (cell != nullptr && cell->kind == LadderCellKind::Node)
- {
- ++target_column;
- }
- return {
- rung_id,
- std::min(target_column, ProjectLimits::kMaximumConditionColumns),
- target_column >= ProjectLimits::kMaximumConditionColumns};
- }
- const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
- if (rung != nullptr)
- {
- const auto empty = std::find_if(
- rung->cells.cbegin(), rung->cells.cend(),
- [](const LadderCell &cell)
- {
- return cell.kind == LadderCellKind::Gap;
- });
- if (empty != rung->cells.cend())
- {
- return {
- rung_id,
- static_cast<int>(std::distance(rung->cells.cbegin(), empty)),
- false};
- }
- }
- return {rung_id, ProjectLimits::kMaximumConditionColumns, true};
- }
-
- void LogicEditorWidget::moveToCursor(const LogicEditCursor &cursor)
- {
- clearObjectSelection();
- selected_rung_id_ = cursor.rungId;
- selected_column_ = cursor.column;
- selected_cell_ = !cursor.output;
- selected_output_ = cursor.output;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
-
- const LogicCommandTarget target = commandTargetForCursor(cursor);
- QPointF center;
- if (findCommandTargetCenter(target, ¢er))
- {
- ensureVisible(
- QRectF(center.x() - kCellWidth / 2.0,
- center.y() - kRowHeight / 2.0,
- cursor.output ? kOutputWidth : kCellWidth,
- kRowHeight),
- 24,
- 24);
- }
- }
-
- void LogicEditorWidget::moveToVerticalTarget(
- const LogicVerticalEditResult &result)
- {
- const bool keep_cell = selected_cell_;
- const bool keep_output = selected_output_;
- const bool keep_boundary = selected_boundary_;
- clearObjectSelection();
- selected_rung_id_ = result.nextRungId;
- selected_column_ = result.columnBoundary;
- selected_cell_ = keep_cell;
- selected_output_ = keep_output;
- selected_boundary_ = keep_boundary;
- rebuildScene();
- notifySelectionChanged();
-
- const RowLayout *layout = layoutForRung(result.nextRungId);
- if (layout == nullptr)
- {
- return;
- }
- qreal center_x = kLeftBus
- + (static_cast<qreal>(result.columnBoundary) + 0.5) * kCellWidth;
- qreal target_width = kCellWidth;
- if (keep_boundary)
- {
- center_x = kLeftBus
- + static_cast<qreal>(result.columnBoundary) * kCellWidth;
- }
- else if (keep_output)
- {
- center_x = kConditionRight + kOutputWidth / 2.0;
- target_width = kOutputWidth;
- }
- ensureVisible(
- QRectF(
- center_x - target_width / 2.0,
- layout->centerY - kRowHeight / 2.0,
- target_width,
- kRowHeight),
- 24,
- 24);
- }
-
- LogicEditorResult LogicEditorWidget::finishCursorEdit(
- const LogicEditResult &result)
- {
- if (!result.edit.succeeded)
- {
- reportFailure(result.edit);
- return result.edit;
- }
- moveToCursor(result.nextCursor);
- emit graphChanged();
- return result.edit;
- }
-
- void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
- {
- emit editorError(QString::fromStdString(result.message));
- }
-
- void LogicEditorWidget::mousePressEvent(QMouseEvent *event)
- {
- const Hit hit = hitAt(mapToScene(event->pos()));
- setFocus(Qt::MouseFocusReason);
- if (event->button() == Qt::LeftButton
- && mouse_wire_mode_ != MouseWireMode::Select && editing_enabled_)
- {
- beginGesture(hit);
- }
- else if (event->button() == Qt::LeftButton
- && mouse_wire_mode_ == MouseWireMode::Select)
- {
- selection_pressed_ = true;
- selection_dragging_ = false;
- selection_origin_ = event->pos();
- selection_modifiers_ = event->modifiers();
- selection_band_->setGeometry(QRect(selection_origin_, QSize{}));
- selection_band_->hide();
- }
- else
- {
- QGraphicsView::mousePressEvent(event);
- return;
- }
- event->accept();
- }
-
- void LogicEditorWidget::mouseMoveEvent(QMouseEvent *event)
- {
- if (mouse_wire_mode_ == MouseWireMode::Select && selection_pressed_)
- {
- if (!selection_dragging_
- && (event->pos() - selection_origin_).manhattanLength()
- >= QApplication::startDragDistance())
- {
- selection_dragging_ = true;
- selection_band_->show();
- }
- if (selection_dragging_)
- {
- selection_band_->setGeometry(
- QRect(selection_origin_, event->pos()).normalized());
- }
- }
- else
- {
- const QPointF scene_position = mapToScene(event->pos());
- updateGesture(hitAt(scene_position), scene_position);
- }
- event->accept();
- }
-
- void LogicEditorWidget::mouseReleaseEvent(QMouseEvent *event)
- {
- const Hit hit = hitAt(mapToScene(event->pos()));
- if (event->button() == Qt::LeftButton
- && mouse_wire_mode_ == MouseWireMode::Select && selection_pressed_)
- {
- const bool extend = selection_modifiers_.testFlag(Qt::ControlModifier);
- selection_pressed_ = false;
- selection_band_->hide();
- if (selection_dragging_)
- {
- selectObjectsInBand(
- QRect(selection_origin_, event->pos()).normalized(), extend);
- }
- else
- {
- selectObject(hit, extend);
- }
- selection_dragging_ = false;
- }
- else if (mouse_wire_mode_ != MouseWireMode::Select)
- {
- finishGesture(hit);
- }
- else
- {
- QGraphicsView::mouseReleaseEvent(event);
- return;
- }
- event->accept();
- }
-
- void LogicEditorWidget::mouseDoubleClickEvent(QMouseEvent *event)
- {
- showCommandEditor(hitAt(mapToScene(event->pos())));
- event->accept();
- }
-
- void LogicEditorWidget::keyPressEvent(QKeyEvent *event)
- {
- if (event != nullptr
- && event->key() == Qt::Key_Escape
- && event->modifiers() == Qt::NoModifier
- && mouse_wire_mode_ != MouseWireMode::Select)
- {
- setMouseWireMode(MouseWireMode::Select);
- event->accept();
- return;
- }
- QGraphicsView::keyPressEvent(event);
- }
-
- void LogicEditorWidget::drawForeground(
- QPainter *painter, const QRectF &rect)
- {
- QGraphicsView::drawForeground(painter, rect);
- drawGesturePreview(painter);
- }
-
- void LogicEditorWidget::resizeEvent(QResizeEvent *event)
- {
- QGraphicsView::resizeEvent(event);
- if (command_editor_ != nullptr && command_editor_->isVisible())
- {
- QPointF center;
- if (findCommandTargetCenter(command_target_, ¢er))
- {
- positionCommandInput(center);
- }
- }
- }
-
- void LogicEditorWidget::scrollContentsBy(int dx, int dy)
- {
- QGraphicsView::scrollContentsBy(dx, dy);
- if (command_editor_ != nullptr && command_editor_->isVisible())
- {
- QPointF center;
- if (findCommandTargetCenter(command_target_, ¢er))
- {
- positionCommandInput(center);
- }
- }
- }
-
- bool LogicEditorWidget::eventFilter(QObject *watched, QEvent *event)
- {
- if (watched == command_editor_ && event != nullptr)
- {
- if (event->type() == QEvent::KeyPress)
- {
- const auto *key_event = static_cast<QKeyEvent *>(event);
- if (key_event->key() == Qt::Key_Escape)
- {
- cancelCommandInput();
- return true;
- }
- }
- else if (event->type() == QEvent::FocusOut)
- {
- QTimer::singleShot(
- 0,
- this,
- [this]
- {
- if (command_editor_ == nullptr
- || !command_editor_->isVisible()
- || command_editor_->hasFocus())
- {
- return;
- }
- QWidget *focus = QApplication::focusWidget();
- QWidget *popup = command_completer_ == nullptr
- ? nullptr : command_completer_->popup();
- if (popup != nullptr && popup->isVisible()
- && focus != nullptr
- && (focus == popup || popup->isAncestorOf(focus)))
- {
- return;
- }
- cancelCommandInput();
- });
- }
- }
- return QGraphicsView::eventFilter(watched, event);
- }
-
- LogicEditorResult LogicEditorWidget::addRung()
- {
- const LogicEditorResult result = editor_service_.addRung(logic_id_);
- if (result.succeeded)
- {
- clearObjectSelection();
- selected_rung_id_ = result.id;
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::insertRung(bool after)
- {
- const std::string reference = selected_rung_id_;
- const LogicEditorResult result = reference.empty()
- ? editor_service_.addRung(logic_id_)
- : editor_service_.insertRung(logic_id_, reference, after);
- if (result.succeeded)
- {
- clearObjectSelection();
- selected_rung_id_ = result.id;
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteRung()
- {
- const std::string rung_id = selected_rung_id_;
- if (rung_id.empty())
- {
- return {false, LogicEditorError::RungNotFound, "请先选择要删除的行", {}};
- }
- const LogicEditorResult result = editor_service_.removeRung(logic_id_, rung_id);
- if (result.succeeded)
- {
- clearObjectSelection();
- selected_rung_id_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
- {
- return finishCursorEdit(editor_service_.applyConditionAndAdvance(
- logic_id_, conditionInsertionCursor(), config, false));
- }
-
- LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
- {
- if (selected_node_ids_.empty() || selectedRungId().empty())
- {
- const LogicEditorResult result{
- false,
- LogicEditorError::InvalidOperation,
- "请先选择同一行中要并联的连续条件节点",
- {}};
- reportFailure(result);
- return result;
- }
- const LogicEditorResult result = editor_service_.addParallelBranch(
- logic_id_, selectedRungId(), selected_node_ids_, config, false);
- if (result.succeeded)
- {
- selectNode(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addHorizontalWire()
- {
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr)
- {
- const LogicEditorResult result{
- false, LogicEditorError::LogicNotFound, "未找到控制逻辑", {}};
- reportFailure(result);
- return result;
- }
- if (!logic->rungs.empty()
- && (selectedRungId().empty() || !selected_cell_))
- {
- const LogicEditorResult result{
- false,
- LogicEditorError::InvalidOperation,
- "请先选择一个条件网格,再插入横线",
- {}};
- reportFailure(result);
- return result;
- }
- LogicEditCursor cursor = logic->rungs.empty()
- ? LogicEditCursor{} : conditionInsertionCursor();
- if (cursor.output)
- {
- const LogicEditorResult result{
- false,
- LogicEditorError::InvalidOperation,
- "条件区已经填满,请在输出槽配置输出指令",
- {}};
- reportFailure(result);
- return result;
- }
- return finishCursorEdit(editor_service_.applyWireAndAdvance(
- logic_id_, cursor));
- }
-
- LogicEditorResult LogicEditorWidget::addVerticalWire()
- {
- const std::string upper = selectedRungId();
- if (upper.empty() || selected_column_ < 0
- || selected_column_ > ProjectLimits::kMaximumConditionColumns
- || (!selected_cell_ && !selected_output_ && !selected_boundary_))
- {
- return {false, LogicEditorError::InvalidOperation,
- "请先选择一个列边界或网格,再插入竖线", {}};
- }
- const LogicVerticalEditResult result =
- editor_service_.applyVerticalConnectionAndAdvance(
- logic_id_, upper, selected_column_);
- if (result.edit.succeeded)
- {
- moveToVerticalTarget(result);
- if (result.changed)
- {
- emit graphChanged();
- }
- }
- else
- {
- reportFailure(result.edit);
- }
- return result.edit;
- }
-
- LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
- {
- const std::string rung_id = selectedRungId();
- if (rung_id.empty() || !selected_cell_ || selected_column_ < 0
- || selected_column_ >= ProjectLimits::kMaximumConditionColumns)
- {
- return {false, LogicEditorError::InvalidOperation,
- "请先选择要删除的横线网格", {}};
- }
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, rung_id, selected_column_);
- if (cell == nullptr || cell->kind != LadderCellKind::Wire)
- {
- return {false, LogicEditorError::InvalidOperation,
- "请选择一格横线后再删除", {}};
- }
- const LogicEditorResult result = editor_service_.setHorizontalWireRange(
- logic_id_, rung_id, selected_column_, selected_column_, false);
- if (result.succeeded)
- {
- selected_cells_.erase(
- std::remove(
- selected_cells_.begin(),
- selected_cells_.end(),
- std::make_pair(rung_id, selected_column_)),
- selected_cells_.end());
- synchronizeSelectedNodes();
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteVerticalWire()
- {
- if (selected_vertical_connection_id_.empty())
- {
- return {false, LogicEditorError::ConnectionNotFound, "请选择要删除的竖线", {}};
- }
- const LogicEditorResult result = editor_service_.removeVerticalConnections(
- logic_id_, {selected_vertical_connection_id_});
- if (result.succeeded)
- {
- selected_vertical_connection_ids_.erase(
- std::remove(
- selected_vertical_connection_ids_.begin(),
- selected_vertical_connection_ids_.end(),
- selected_vertical_connection_id_),
- selected_vertical_connection_ids_.end());
- selected_vertical_connection_id_ =
- selected_vertical_connection_ids_.empty()
- ? std::string{} : selected_vertical_connection_ids_.back();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::setOutput(
- const LogicNodeConfig &config, bool configured)
- {
- return finishCursorEdit(editor_service_.applyOutputAndAdvance(
- logic_id_,
- {currentRungId(), ProjectLimits::kMaximumConditionColumns, true},
- config,
- configured));
- }
-
- LogicClipboardPasteResult LogicEditorWidget::pasteClipboard(
- const LogicClipboardFragment &fragment)
- {
- LogicClipboardPasteResult result = editor_service_.pasteClipboard(
- logic_id_, fragment, pasteTarget());
- if (result.edit.succeeded)
- {
- clearObjectSelection();
- selected_cells_ = result.selection.cells;
- selected_output_rung_ids_ = result.selection.outputRungIds;
- selected_vertical_connection_ids_ =
- result.selection.verticalConnectionIds;
- selected_row_ids_ = result.wholeRungIds;
- if (!selected_row_ids_.empty())
- {
- selected_rung_id_ = selected_row_ids_.back();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- }
- else if (!selected_cells_.empty())
- {
- selected_rung_id_ = selected_cells_.front().first;
- selected_column_ = selected_cells_.front().second;
- selected_cell_ = true;
- selected_output_ = false;
- selected_boundary_ = false;
- }
- else if (!selected_output_rung_ids_.empty())
- {
- selected_rung_id_ = selected_output_rung_ids_.front();
- selected_column_ = ProjectLimits::kMaximumConditionColumns;
- selected_cell_ = false;
- selected_output_ = true;
- selected_boundary_ = false;
- }
- else if (!selected_vertical_connection_ids_.empty())
- {
- const VerticalConnection *connection = editor_service_.findConnection(
- logic_id_, selected_vertical_connection_ids_.front());
- if (connection != nullptr)
- {
- selected_rung_id_ = connection->upperRungId;
- selected_column_ = connection->columnBoundary;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = true;
- }
- }
- selected_vertical_connection_id_ =
- selected_vertical_connection_ids_.empty()
- ? std::string{} : selected_vertical_connection_ids_.back();
- synchronizeSelectedNodes();
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result.edit);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteSelected()
- {
- LogicSelectionDeleteRequest selection;
- selection.cells = selected_cells_;
- selection.outputRungIds = selected_output_rung_ids_;
- selection.verticalConnectionIds = selected_vertical_connection_ids_;
- if (selection.cells.empty() && selection.outputRungIds.empty()
- && selection.verticalConnectionIds.empty() && selected_cell_
- && !selected_rung_id_.empty() && selected_column_ >= 0
- && selected_column_ < ProjectLimits::kMaximumConditionColumns)
- {
- const LadderCell *cell = editor_service_.findCell(
- logic_id_, selected_rung_id_, selected_column_);
- if (cell != nullptr && cell->kind != LadderCellKind::Gap)
- {
- selection.cells.push_back({
- selected_rung_id_, selected_column_});
- }
- }
- if (selection.cells.empty() && selection.outputRungIds.empty()
- && selection.verticalConnectionIds.empty())
- {
- const LogicEditorResult result{
- false,
- LogicEditorError::InvalidOperation,
- "请先选择逻辑指令、横线、输出块或竖线;整行请使用 Shift+Delete",
- {}};
- reportFailure(result);
- return result;
- }
-
- const LogicEditorResult result = editor_service_.deleteSelection(
- logic_id_, selection);
- if (result.succeeded)
- {
- clearObjectSelection();
- selected_rung_id_.clear();
- selected_column_ = -1;
- selected_cell_ = false;
- selected_output_ = false;
- selected_boundary_ = false;
- rebuildScene();
- notifySelectionChanged();
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
|