|
- #include "logic_editor_widget.h"
-
- #include <QGraphicsItem>
- #include <QGraphicsScene>
- #include <QGraphicsTextItem>
- #include <QPainter>
- #include <QPainterPath>
- #include <QResizeEvent>
- #include <QStyleOptionGraphicsItem>
-
- #include <algorithm>
- #include <cmath>
- #include <type_traits>
-
- namespace {
-
- constexpr qreal kMinimumSceneWidth = 1280.0;
- constexpr qreal kLeftRailX = 72.0;
- constexpr qreal kStageWidth = 184.0;
- constexpr qreal kNodeWidth = 128.0;
- constexpr qreal kNodeTerminalX = kNodeWidth / 2.0;
- constexpr qreal kBranchSpacing = 96.0;
- constexpr qreal kRungGap = 18.0;
- constexpr qreal kRungBaseHeight = 132.0;
- constexpr qreal kMainLineOffset = 78.0;
- constexpr qreal kLadderLineWidth = 1.6;
- constexpr qreal kPlaceholderWidth = 112.0;
- constexpr qreal kPlaceholderHeight = 30.0;
-
- const QColor kLadderColor(QStringLiteral("#26343d"));
- const QColor kSelectionColor(QStringLiteral("#dcecf4"));
- const QColor kSelectionBorderColor(QStringLiteral("#1677a8"));
- const QColor kPlaceholderColor(QStringLiteral("#82919b"));
-
- 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(">=");
- default: return QStringLiteral("?");
- }
- }
-
- QString registerAddressText(const RegisterAddress &address)
- {
- return QStringLiteral("%1%2")
- .arg(address.area() == RegisterArea::M ? QStringLiteral("M")
- : QStringLiteral("D"))
- .arg(address.index());
- }
-
- 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 value.mode == ContactMode::NormallyOpen
- ? LogicEditorWidget::tr("常开触点 %1").arg(
- registerAddressText(value.address))
- : LogicEditorWidget::tr("常闭触点 %1").arg(
- registerAddressText(value.address));
- }
- else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
- {
- QString mode = LogicEditorWidget::tr("普通线圈");
- if (value.mode == CoilMode::Set)
- {
- mode = LogicEditorWidget::tr("置位线圈");
- }
- else if (value.mode == CoilMode::Reset)
- {
- mode = LogicEditorWidget::tr("复位线圈");
- }
- return QStringLiteral("%1 %2").arg(
- mode, registerAddressText(value.address));
- }
- else
- {
- return LogicEditorWidget::tr("比较条件:%1 %2 %3")
- .arg(registerAddressText(value.address))
- .arg(comparisonText(value.comparison))
- .arg(value.value);
- }
- },
- config);
- }
-
- QString nodeAddressText(const RegisterAddress &address, bool configured)
- {
- return configured ? registerAddressText(address)
- : LogicEditorWidget::tr("< M 地址 >");
- }
-
- qreal rungHeight(const LadderRung &rung)
- {
- std::size_t maximum_branches = 1U;
- for (const LadderStage &stage : rung.stages)
- {
- maximum_branches = std::max(maximum_branches, stage.branches.size());
- }
- return kRungBaseHeight
- + static_cast<qreal>(maximum_branches - 1U) * kBranchSpacing;
- }
-
- void addPlaceholder(
- QGraphicsScene &scene,
- const QPointF ¢er,
- const QString &text,
- qreal width = kPlaceholderWidth)
- {
- const QRectF bounds(
- center.x() - width / 2.0,
- center.y() - kPlaceholderHeight / 2.0,
- width,
- kPlaceholderHeight);
- scene.addRect(
- bounds,
- QPen(kPlaceholderColor, 1.2, Qt::DashLine),
- QBrush(Qt::white));
- QGraphicsTextItem *label = scene.addText(text);
- label->setDefaultTextColor(kPlaceholderColor);
- label->setPos(
- center.x() - label->boundingRect().width() / 2.0,
- center.y() - label->boundingRect().height() / 2.0);
- }
-
- } // namespace
-
- class LogicEditorWidget::NodeItem final : public QGraphicsItem
- {
- public:
- NodeItem(
- const LogicNode &node,
- const std::string &rung_id,
- const std::string &stage_id,
- const QPointF ¢er)
- : node_id_(node.id),
- rung_id_(rung_id),
- stage_id_(stage_id),
- config_(node.config),
- configured_(node.configured)
- {
- setPos(center);
- setFlag(ItemIsSelectable, true);
- setZValue(1.0);
- setToolTip(configured_ ? nodeToolTip(config_)
- : LogicEditorWidget::tr(
- "待配置:请在属性区设置地址和参数"));
- }
-
- QRectF boundingRect() const override
- {
- return {-kNodeWidth / 2.0, -46.0, kNodeWidth, 92.0};
- }
-
- 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(), kSelectionColor);
- }
- const QColor symbol_color = selected ? kSelectionBorderColor : kLadderColor;
- painter->setPen(QPen(symbol_color, selected ? 2.0 : kLadderLineWidth));
-
- QFont text_font = painter->font();
- text_font.setPointSizeF(9.5);
- painter->setFont(text_font);
-
- if (const auto *contact = std::get_if<ContactNodeConfig>(&config_))
- {
- painter->fillRect(QRectF(-24, -21, 48, 42), 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->setPen(kLadderColor);
- painter->drawText(
- QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18),
- Qt::AlignCenter,
- nodeAddressText(contact->address, configured_));
- }
- else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_))
- {
- painter->fillRect(QRectF(-34, -23, 68, 46), Qt::white);
- painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0));
- painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0));
- QPainterPath left_coil;
- left_coil.moveTo(-2, -20);
- left_coil.cubicTo(-24, -16, -24, 16, -2, 20);
- painter->drawPath(left_coil);
- QPainterPath right_coil;
- right_coil.moveTo(2, -20);
- right_coil.cubicTo(24, -16, 24, 16, 2, 20);
- painter->drawPath(right_coil);
- if (coil->mode == CoilMode::Set || coil->mode == CoilMode::Reset)
- {
- painter->drawText(
- QRectF(-12, -14, 24, 28),
- Qt::AlignCenter,
- coil->mode == CoilMode::Set ? QStringLiteral("S")
- : QStringLiteral("R"));
- }
- painter->setPen(kLadderColor);
- painter->drawText(
- QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18),
- Qt::AlignCenter,
- nodeAddressText(coil->address, configured_));
- }
- else if (const auto *comparison = std::get_if<CompareNodeConfig>(&config_))
- {
- constexpr qreal box_half_width = 47.0;
- const QRectF instruction_box(-box_half_width, -17, 94, 34);
- painter->fillRect(instruction_box.adjusted(-1, -1, 1, 1), Qt::white);
- painter->drawLine(
- QPointF(-kNodeTerminalX, 0), QPointF(-box_half_width, 0));
- painter->drawLine(
- QPointF(box_half_width, 0), QPointF(kNodeTerminalX, 0));
- painter->drawRect(instruction_box);
- painter->drawText(
- instruction_box,
- Qt::AlignCenter,
- QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison)));
- painter->setPen(kLadderColor);
- painter->drawText(
- QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18),
- Qt::AlignCenter,
- configured_ ? registerAddressText(comparison->address)
- : tr("< D 地址 >"));
- painter->drawText(
- QRectF(-kNodeWidth / 2.0, 22, kNodeWidth, 18),
- Qt::AlignCenter,
- configured_ ? QString::number(comparison->value)
- : LogicEditorWidget::tr("< 常量 >"));
- }
- }
-
- const std::string &nodeId() const { return node_id_; }
- const std::string &rungId() const { return rung_id_; }
- const std::string &stageId() const { return stage_id_; }
-
- private:
- std::string node_id_;
- std::string rung_id_;
- std::string stage_id_;
- LogicNodeConfig config_;
- bool configured_ = true;
- };
-
- class LogicEditorWidget::RungItem final : public QGraphicsItem
- {
- public:
- RungItem(
- const LadderRung &rung,
- int number,
- qreal top,
- qreal height,
- qreal scene_width)
- : rung_id_(rung.id),
- name_(QString::fromUtf8(rung.name.data(), static_cast<int>(rung.name.size()))),
- number_(number),
- height_(height),
- scene_width_(scene_width)
- {
- setPos(0, top);
- setFlag(ItemIsSelectable, true);
- setZValue(-2.0);
- }
-
- QRectF boundingRect() const override
- {
- return {20, 0, scene_width_ - 40, height_};
- }
-
- void paint(
- QPainter *painter,
- const QStyleOptionGraphicsItem *option,
- QWidget *) override
- {
- if ((option->state & QStyle::State_Selected) != 0)
- {
- painter->fillRect(
- boundingRect(), QColor(QStringLiteral("#f0f7fa")));
- }
- painter->setPen(QColor(QStringLiteral("#62717b")));
- QString title = QStringLiteral("网络 %1").arg(number_);
- if (!name_.isEmpty() && !name_.startsWith(QStringLiteral("网络 ")))
- {
- title += QStringLiteral(":") + name_;
- }
- painter->drawText(
- QRectF(28, 6, 260, 22),
- Qt::AlignLeft | Qt::AlignVCenter,
- title);
- painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
- painter->drawLine(
- QPointF(28, height_ - 1), QPointF(scene_width_ - 28, height_ - 1));
- }
-
- const std::string &rungId() const { return rung_id_; }
-
- private:
- std::string rung_id_;
- QString name_;
- int number_ = 0;
- qreal height_ = 0;
- qreal scene_width_ = 0;
- };
-
- LogicEditorWidget::LogicEditorWidget(
- LogicEditorService &editor_service,
- QWidget *parent)
- : QGraphicsView(parent), editor_service_(editor_service)
- {
- scene_ = new QGraphicsScene(this);
- setScene(scene_);
- setRenderHint(QPainter::Antialiasing, true);
- setBackgroundBrush(QColor(QStringLiteral("#ffffff")));
- 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::reloadLogic()
- {
- scene_->clear();
- const ControlLogic *logic = editor_service_.findLogic(logic_id_);
- if (logic == nullptr)
- {
- scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
- return;
- }
-
- std::size_t maximum_stage_count = 0U;
- for (const LadderRung &rung : logic->rungs)
- {
- maximum_stage_count = std::max(maximum_stage_count, rung.stages.size());
- }
- const qreal scene_width = std::max(
- kMinimumSceneWidth,
- kLeftRailX + static_cast<qreal>(maximum_stage_count) * kStageWidth + 300.0);
- const qreal right_rail_x = scene_width - 72.0;
- if (current_rung_id_.empty() && !logic->rungs.empty())
- {
- current_rung_id_ = logic->rungs.front().id;
- }
-
- qreal top = 24.0;
- int rung_number = 1;
- for (const LadderRung &rung : logic->rungs)
- {
- const qreal height = rungHeight(rung);
- scene_->addItem(new RungItem(rung, rung_number, top, height, scene_width));
- const qreal main_y = top + kMainLineOffset;
-
- scene_->addLine(
- kLeftRailX, top + 38.0, kLeftRailX, top + height - 16.0,
- QPen(kLadderColor, 2.4));
- scene_->addLine(
- right_rail_x, top + 38.0, right_rail_x, top + height - 16.0,
- QPen(kLadderColor, 2.4));
-
- qreal cursor_x = kLeftRailX;
- for (const LadderStage &stage : rung.stages)
- {
- const qreal next_x = cursor_x + kStageWidth;
- const qreal left_join = cursor_x + 18.0;
- const qreal right_join = next_x - 18.0;
- scene_->addLine(
- cursor_x, main_y, left_join, main_y,
- QPen(kLadderColor, kLadderLineWidth));
- scene_->addLine(
- right_join, main_y, next_x, main_y,
- QPen(kLadderColor, kLadderLineWidth));
- if (stage.branches.size() > 1U)
- {
- const qreal bottom_y = main_y
- + static_cast<qreal>(stage.branches.size() - 1U) * kBranchSpacing;
- scene_->addLine(
- left_join, main_y, left_join, bottom_y,
- QPen(kLadderColor, kLadderLineWidth));
- scene_->addLine(
- right_join, main_y, right_join, bottom_y,
- QPen(kLadderColor, kLadderLineWidth));
- }
- for (std::size_t branch = 0; branch < stage.branches.size(); ++branch)
- {
- const qreal branch_y = main_y + static_cast<qreal>(branch) * kBranchSpacing;
- scene_->addLine(
- left_join, branch_y, right_join, branch_y,
- QPen(kLadderColor, kLadderLineWidth));
- scene_->addItem(new NodeItem(
- stage.branches.at(branch),
- rung.id,
- stage.id,
- QPointF((left_join + right_join) / 2.0, branch_y)));
- }
- cursor_x = next_x;
- }
-
- const qreal output_center_x = right_rail_x - 68.0;
- if (rung.stages.empty())
- {
- const qreal condition_center_x = kLeftRailX + kStageWidth / 2.0;
- scene_->addLine(
- kLeftRailX,
- main_y,
- condition_center_x - kPlaceholderWidth / 2.0,
- main_y,
- QPen(kLadderColor, kLadderLineWidth));
- scene_->addLine(
- condition_center_x + kPlaceholderWidth / 2.0,
- main_y,
- output_center_x,
- main_y,
- QPen(kLadderColor, kLadderLineWidth));
- addPlaceholder(
- *scene_,
- QPointF(condition_center_x, main_y),
- tr("添加条件"));
- }
- else
- {
- scene_->addLine(
- cursor_x, main_y, output_center_x, main_y,
- QPen(kLadderColor, kLadderLineWidth));
- }
- if (rung.output.has_value())
- {
- scene_->addLine(
- output_center_x, main_y, right_rail_x, main_y,
- QPen(kLadderColor, kLadderLineWidth));
- scene_->addItem(new NodeItem(
- *rung.output,
- rung.id,
- {},
- QPointF(output_center_x, main_y)));
- }
- else
- {
- constexpr qreal output_placeholder_width = 96.0;
- scene_->addLine(
- output_center_x + output_placeholder_width / 2.0,
- main_y,
- right_rail_x,
- main_y,
- QPen(kLadderColor, kLadderLineWidth));
- addPlaceholder(
- *scene_,
- QPointF(output_center_x, main_y),
- tr("输出线圈"),
- output_placeholder_width);
- }
- top += height + kRungGap;
- ++rung_number;
- }
- scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + 20.0));
- }
-
- void LogicEditorWidget::selectNode(const std::string &node_id)
- {
- for (QGraphicsItem *item : scene_->items())
- {
- NodeItem *node = dynamic_cast<NodeItem *>(item);
- if (node != nullptr)
- {
- node->setSelected(node->nodeId() == node_id);
- if (node->nodeId() == node_id)
- {
- ensureVisible(node);
- }
- }
- }
- }
-
- std::string LogicEditorWidget::selectedNodeId() const
- {
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- const NodeItem *node = dynamic_cast<const NodeItem *>(item);
- if (node != nullptr)
- {
- return node->nodeId();
- }
- }
- return {};
- }
-
- std::string LogicEditorWidget::selectedRungId() const
- {
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- const NodeItem *node = dynamic_cast<const NodeItem *>(item);
- if (node != nullptr)
- {
- return node->rungId();
- }
- const RungItem *rung = dynamic_cast<const RungItem *>(item);
- if (rung != nullptr)
- {
- return rung->rungId();
- }
- }
- return {};
- }
-
- std::string LogicEditorWidget::selectedStageId() const
- {
- for (QGraphicsItem *item : scene_->selectedItems())
- {
- const NodeItem *node = dynamic_cast<const NodeItem *>(item);
- if (node != nullptr)
- {
- return node->stageId();
- }
- }
- return {};
- }
-
- 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::appendCondition(const LogicNodeConfig &config)
- {
- const LogicEditorResult result = editor_service_.appendCondition(
- logic_id_, currentRungId(), config);
- if (result.succeeded)
- {
- reloadLogic();
- selectNode(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::addParallelCondition(
- const LogicNodeConfig &config)
- {
- const std::string rung_id = selectedRungId();
- const std::string stage_id = selectedStageId();
- LogicEditorResult result;
- if (rung_id.empty() || stage_id.empty())
- {
- result = {false,
- LogicEditorError::InvalidOperation,
- "请先选择要添加并联分支的触点或比较条件",
- {}};
- }
- else
- {
- result = editor_service_.addParallelCondition(
- logic_id_, rung_id, stage_id, config);
- }
- if (result.succeeded)
- {
- reloadLogic();
- selectNode(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::setOutput(const LogicNodeConfig &config)
- {
- const LogicEditorResult result = editor_service_.setOutput(
- logic_id_, currentRungId(), config);
- if (result.succeeded)
- {
- reloadLogic();
- selectNode(result.id);
- emit graphChanged();
- }
- else
- {
- reportFailure(result);
- }
- return result;
- }
-
- LogicEditorResult LogicEditorWidget::deleteSelected()
- {
- const std::string node_id = selectedNodeId();
- LogicEditorResult result;
- if (!node_id.empty())
- {
- result = editor_service_.removeNode(logic_id_, node_id);
- }
- else
- {
- const std::string rung_id = selectedRungId();
- if (rung_id.empty())
- {
- return {false, LogicEditorError::InvalidOperation,
- "请先选择要删除的逻辑节点或网络", {}};
- }
- result = editor_service_.removeRung(logic_id_, rung_id);
- if (result.succeeded && current_rung_id_ == rung_id)
- {
- current_rung_id_ = editor_service_.firstRungId(logic_id_);
- }
- }
- 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;
- }
- if (!current_rung_id_.empty())
- {
- return current_rung_id_;
- }
- return editor_service_.firstRungId(logic_id_);
- }
|