综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

1882 строки
63 KiB

  1. #include "logic_editor_widget.h"
  2. #include <QGraphicsItem>
  3. #include <QGraphicsScene>
  4. #include <QGraphicsTextItem>
  5. #include <QPainter>
  6. #include <QPainterPath>
  7. #include <QResizeEvent>
  8. #include <QStyleOptionGraphicsItem>
  9. #include <algorithm>
  10. #include <type_traits>
  11. namespace {
  12. constexpr qreal kSceneMargin = 28.0;
  13. constexpr qreal kRailInset = 40.0;
  14. constexpr qreal kMinimumSceneWidth = 980.0;
  15. constexpr qreal kCellWidth = 128.0;
  16. constexpr qreal kCellHeight = 120.0;
  17. constexpr qreal kNodeTerminalX = 50.0;
  18. constexpr qreal kRungHeaderHeight = 52.0;
  19. constexpr qreal kRungGap = 18.0;
  20. constexpr qreal kLadderLineWidth = 1.8;
  21. constexpr int kMinimumLogicColumns = 7;
  22. const QColor kLadderColor(QStringLiteral("#263842"));
  23. const QColor kActiveColor(QStringLiteral("#16854f"));
  24. const QColor kFaultColor(QStringLiteral("#c5362e"));
  25. const QColor kSelectionColor(QStringLiteral("#dfeef5"));
  26. const QColor kSelectionBorderColor(QStringLiteral("#277da1"));
  27. const QColor kGridColor(QStringLiteral("#e8edf0"));
  28. const QColor kPlaceholderColor(QStringLiteral("#81919b"));
  29. struct ExpressionMetrics
  30. {
  31. int columns = 1;
  32. int rows = 1;
  33. };
  34. struct RenderResult
  35. {
  36. QPointF input;
  37. QPointF output;
  38. };
  39. QString registerAddressText(const RegisterAddress &address)
  40. {
  41. return QString::fromStdString(address.toString());
  42. }
  43. QString wordOperandText(const WordOperand &operand)
  44. {
  45. return operand.kind == WordOperandKind::Register
  46. ? registerAddressText(operand.address)
  47. : QString::number(operand.constant);
  48. }
  49. void drawRegisterComment(
  50. QPainter *painter, const QString &comment, qreal top = 22.0)
  51. {
  52. if (comment.isEmpty())
  53. {
  54. return;
  55. }
  56. QFont font = painter->font();
  57. font.setPointSizeF(8.0);
  58. painter->setFont(font);
  59. const QString visible_comment = painter->fontMetrics().elidedText(
  60. comment, Qt::ElideRight, static_cast<int>(kCellWidth - 8.0));
  61. painter->drawText(
  62. QRectF(-kCellWidth / 2.0, top, kCellWidth, 18),
  63. Qt::AlignCenter | Qt::TextSingleLine,
  64. visible_comment);
  65. }
  66. QString comparisonText(ComparisonOperator comparison)
  67. {
  68. switch (comparison)
  69. {
  70. case ComparisonOperator::Equal: return QStringLiteral("=");
  71. case ComparisonOperator::NotEqual: return QStringLiteral("<>");
  72. case ComparisonOperator::LessThan: return QStringLiteral("<");
  73. case ComparisonOperator::LessThanOrEqual: return QStringLiteral("<=");
  74. case ComparisonOperator::GreaterThan: return QStringLiteral(">");
  75. case ComparisonOperator::GreaterThanOrEqual: return QStringLiteral(">=");
  76. }
  77. return QStringLiteral("?");
  78. }
  79. QString nodeToolTip(const LogicNodeConfig &config)
  80. {
  81. return std::visit(
  82. [](const auto &value) -> QString
  83. {
  84. using Config = std::decay_t<decltype(value)>;
  85. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  86. {
  87. return LogicEditorWidget::tr("%1触点:%2")
  88. .arg(value.mode == ContactMode::NormallyOpen
  89. ? LogicEditorWidget::tr("常开")
  90. : LogicEditorWidget::tr("常闭"))
  91. .arg(registerAddressText(value.address));
  92. }
  93. else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
  94. {
  95. return LogicEditorWidget::tr("%1沿触点:%2")
  96. .arg(value.mode == EdgeMode::Rising
  97. ? LogicEditorWidget::tr("上升")
  98. : LogicEditorWidget::tr("下降"))
  99. .arg(registerAddressText(value.address));
  100. }
  101. else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  102. {
  103. return LogicEditorWidget::tr("输出线圈:%1")
  104. .arg(registerAddressText(value.address));
  105. }
  106. else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
  107. {
  108. return LogicEditorWidget::tr("比较条件:%1 %2 %3")
  109. .arg(registerAddressText(value.address))
  110. .arg(comparisonText(value.comparison))
  111. .arg(value.value);
  112. }
  113. else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
  114. {
  115. return LogicEditorWidget::tr("MOVE:%1 -> %2")
  116. .arg(wordOperandText(value.source))
  117. .arg(registerAddressText(value.destination));
  118. }
  119. else
  120. {
  121. return LogicEditorWidget::tr("%1:%2,%3 -> %4")
  122. .arg(value.operation == ArithmeticOperation::Add
  123. ? QStringLiteral("ADD") : QStringLiteral("SUB"))
  124. .arg(wordOperandText(value.left))
  125. .arg(wordOperandText(value.right))
  126. .arg(registerAddressText(value.destination));
  127. }
  128. },
  129. config);
  130. }
  131. ExpressionMetrics measureExpression(const ConditionExpression &expression)
  132. {
  133. if (expression.kind == ConditionExpressionKind::Node)
  134. {
  135. return {};
  136. }
  137. if (expression.kind == ConditionExpressionKind::Wire)
  138. {
  139. return {expression.wire->columnSpan, 1};
  140. }
  141. ExpressionMetrics metrics{0, 0};
  142. if (expression.kind == ConditionExpressionKind::Series)
  143. {
  144. for (const ConditionExpression &child : expression.children)
  145. {
  146. const ExpressionMetrics child_metrics = measureExpression(child);
  147. metrics.columns += child_metrics.columns;
  148. metrics.rows = std::max(metrics.rows, child_metrics.rows);
  149. }
  150. }
  151. else
  152. {
  153. for (const ConditionExpression &child : expression.children)
  154. {
  155. const ExpressionMetrics child_metrics = measureExpression(child);
  156. metrics.columns = std::max(metrics.columns, child_metrics.columns);
  157. metrics.rows += child_metrics.rows;
  158. }
  159. }
  160. return metrics;
  161. }
  162. QPen ladderPen(bool active)
  163. {
  164. return QPen(active ? kActiveColor : kLadderColor,
  165. active ? 2.6 : kLadderLineWidth);
  166. }
  167. bool traceValue(
  168. const LogicTraceSnapshot &trace,
  169. const std::unordered_map<std::string, bool> LogicTraceSnapshot::*member,
  170. const std::string &id)
  171. {
  172. const auto &values = trace.*member;
  173. const auto found = values.find(id);
  174. return found != values.end() && found->second;
  175. }
  176. void addGrid(
  177. QGraphicsScene &scene,
  178. qreal left,
  179. qreal top,
  180. int columns,
  181. int rows)
  182. {
  183. QPen pen(kGridColor, 1.0);
  184. pen.setCosmetic(true);
  185. for (int column = 0; column <= columns; ++column)
  186. {
  187. const qreal x = left + static_cast<qreal>(column) * kCellWidth;
  188. QGraphicsLineItem *line = scene.addLine(
  189. QLineF(x, top, x, top + static_cast<qreal>(rows) * kCellHeight), pen);
  190. line->setZValue(-10.0);
  191. }
  192. for (int row = 0; row <= rows; ++row)
  193. {
  194. const qreal y = top + static_cast<qreal>(row) * kCellHeight;
  195. QGraphicsLineItem *line = scene.addLine(
  196. QLineF(left, y, left + static_cast<qreal>(columns) * kCellWidth, y), pen);
  197. line->setZValue(-10.0);
  198. }
  199. }
  200. void addPlaceholder(QGraphicsScene &scene, const QRectF &cell, const QString &text)
  201. {
  202. const QRectF bounds = cell.adjusted(12, 18, -12, -18);
  203. QGraphicsRectItem *item = scene.addRect(
  204. bounds,
  205. QPen(kPlaceholderColor, 1.2, Qt::DashLine),
  206. QBrush(QColor(255, 255, 255, 220)));
  207. item->setZValue(1.0);
  208. QGraphicsTextItem *label = scene.addText(text);
  209. label->setDefaultTextColor(kPlaceholderColor);
  210. label->setPos(
  211. bounds.center().x() - label->boundingRect().width() / 2.0,
  212. bounds.center().y() - label->boundingRect().height() / 2.0);
  213. label->setZValue(2.0);
  214. }
  215. } // namespace
  216. class LogicEditorWidget::NodeItem final : public QGraphicsItem
  217. {
  218. public:
  219. NodeItem(
  220. const LogicNode &node,
  221. const std::string &rung_id,
  222. const QPointF &center,
  223. const QString &register_comment,
  224. bool active,
  225. bool faulted,
  226. bool condition)
  227. : node_id_(node.id),
  228. rung_id_(rung_id),
  229. config_(node.config),
  230. register_comment_(register_comment),
  231. configured_(node.configured),
  232. active_(active),
  233. faulted_(faulted),
  234. condition_(condition)
  235. {
  236. setPos(center);
  237. setFlag(ItemIsSelectable, true);
  238. setZValue(3.0);
  239. QString tool_tip = configured_
  240. ? nodeToolTip(config_)
  241. : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数");
  242. if (!register_comment_.isEmpty())
  243. {
  244. tool_tip += LogicEditorWidget::tr("\n注释:%1").arg(register_comment_);
  245. }
  246. setToolTip(tool_tip);
  247. }
  248. QRectF boundingRect() const override
  249. {
  250. return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
  251. }
  252. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  253. {
  254. painter->setRenderHint(QPainter::Antialiasing, true);
  255. const bool selected = (option->state & QStyle::State_Selected) != 0;
  256. if (selected)
  257. {
  258. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  259. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  260. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  261. }
  262. const QColor symbol_color = faulted_ ? kFaultColor
  263. : selected ? kSelectionBorderColor : active_ ? kActiveColor : kLadderColor;
  264. painter->setPen(QPen(symbol_color, active_ || faulted_ ? 2.6 : kLadderLineWidth));
  265. QFont font = painter->font();
  266. font.setPointSizeF(9.5);
  267. painter->setFont(font);
  268. if (const auto *contact = std::get_if<ContactNodeConfig>(&config_))
  269. {
  270. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  271. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  272. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  273. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  274. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  275. if (contact->mode == ContactMode::NormallyClosed)
  276. {
  277. painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
  278. }
  279. painter->drawText(
  280. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  281. Qt::AlignCenter,
  282. configured_ ? registerAddressText(contact->address) : tr("< M 地址 >"));
  283. drawRegisterComment(painter, register_comment_);
  284. }
  285. else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&config_))
  286. {
  287. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  288. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  289. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  290. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  291. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  292. painter->drawText(
  293. QRectF(-14, -12, 28, 24),
  294. Qt::AlignCenter,
  295. edge->mode == EdgeMode::Rising ? QStringLiteral("P") : QStringLiteral("N"));
  296. painter->drawText(
  297. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  298. Qt::AlignCenter,
  299. configured_ ? registerAddressText(edge->address) : tr("< M 地址 >"));
  300. drawRegisterComment(painter, register_comment_);
  301. }
  302. else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_))
  303. {
  304. painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white);
  305. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0));
  306. painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0));
  307. QPainterPath left;
  308. left.moveTo(-2, -20);
  309. left.cubicTo(-24, -16, -24, 16, -2, 20);
  310. painter->drawPath(left);
  311. QPainterPath right;
  312. right.moveTo(2, -20);
  313. right.cubicTo(24, -16, 24, 16, 2, 20);
  314. painter->drawPath(right);
  315. if (coil->mode != CoilMode::Normal)
  316. {
  317. painter->drawText(
  318. QRectF(-12, -14, 24, 28),
  319. Qt::AlignCenter,
  320. coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R"));
  321. }
  322. painter->drawText(
  323. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  324. Qt::AlignCenter,
  325. configured_ ? registerAddressText(coil->address) : tr("< M 地址 >"));
  326. drawRegisterComment(painter, register_comment_);
  327. }
  328. else if (const auto *comparison = std::get_if<CompareNodeConfig>(&config_))
  329. {
  330. const QRectF box(-47, -17, 94, 34);
  331. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  332. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  333. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  334. painter->drawRect(box);
  335. painter->drawText(
  336. box, Qt::AlignCenter,
  337. QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison)));
  338. painter->drawText(
  339. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  340. Qt::AlignCenter,
  341. configured_ ? registerAddressText(comparison->address) : tr("< D 地址 >"));
  342. painter->drawText(
  343. QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
  344. Qt::AlignCenter,
  345. configured_ ? QString::number(comparison->value) : tr("< 常量 >"));
  346. drawRegisterComment(painter, register_comment_, 40.0);
  347. }
  348. else if (const auto *move = std::get_if<MoveNodeConfig>(&config_))
  349. {
  350. const QRectF box(-47, -18, 94, 36);
  351. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  352. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  353. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  354. painter->drawRect(box);
  355. painter->drawText(box, Qt::AlignCenter, QStringLiteral("MOVE"));
  356. painter->drawText(
  357. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  358. Qt::AlignCenter,
  359. configured_ ? QStringLiteral("%1 -> %2")
  360. .arg(wordOperandText(move->source))
  361. .arg(registerAddressText(move->destination))
  362. : tr("< 源 -> 目标 >"));
  363. }
  364. else if (const auto *arithmetic =
  365. std::get_if<ArithmeticNodeConfig>(&config_))
  366. {
  367. const QRectF box(-47, -18, 94, 36);
  368. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  369. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  370. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  371. painter->drawRect(box);
  372. painter->drawText(
  373. box,
  374. Qt::AlignCenter,
  375. arithmetic->operation == ArithmeticOperation::Add
  376. ? QStringLiteral("ADD") : QStringLiteral("SUB"));
  377. painter->drawText(
  378. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  379. Qt::AlignCenter,
  380. configured_ ? QStringLiteral("%1,%2 -> %3")
  381. .arg(wordOperandText(arithmetic->left))
  382. .arg(wordOperandText(arithmetic->right))
  383. .arg(registerAddressText(arithmetic->destination))
  384. : tr("< 操作数 -> 目标 >"));
  385. }
  386. }
  387. const std::string &nodeId() const { return node_id_; }
  388. const std::string &rungId() const { return rung_id_; }
  389. bool isConditionNode() const { return condition_; }
  390. private:
  391. std::string node_id_;
  392. std::string rung_id_;
  393. LogicNodeConfig config_;
  394. QString register_comment_;
  395. bool configured_ = true;
  396. bool active_ = false;
  397. bool faulted_ = false;
  398. bool condition_ = true;
  399. };
  400. class LogicEditorWidget::WireItem final : public QGraphicsItem
  401. {
  402. public:
  403. WireItem(
  404. const std::string &expression_id,
  405. const std::string &rung_id,
  406. const QPointF &center,
  407. int column_span,
  408. bool active)
  409. : expression_id_(expression_id),
  410. rung_id_(rung_id),
  411. width_(static_cast<qreal>(column_span) * kCellWidth),
  412. active_(active)
  413. {
  414. setPos(center);
  415. setFlag(ItemIsSelectable, true);
  416. setZValue(2.0);
  417. setToolTip(LogicEditorWidget::tr("横线:%1 列,可用触点直接替换")
  418. .arg(column_span));
  419. }
  420. QRectF boundingRect() const override
  421. {
  422. return {-width_ / 2.0, -kCellHeight / 2.0, width_, kCellHeight};
  423. }
  424. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  425. {
  426. const bool selected = (option->state & QStyle::State_Selected) != 0;
  427. if (selected)
  428. {
  429. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  430. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  431. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  432. }
  433. painter->setPen(ladderPen(active_));
  434. painter->drawLine(QPointF(-width_ / 2.0, 0), QPointF(width_ / 2.0, 0));
  435. }
  436. const std::string &expressionId() const { return expression_id_; }
  437. const std::string &rungId() const { return rung_id_; }
  438. private:
  439. std::string expression_id_;
  440. std::string rung_id_;
  441. qreal width_ = kCellWidth;
  442. bool active_ = false;
  443. };
  444. class LogicEditorWidget::WireCellItem final : public QGraphicsItem
  445. {
  446. public:
  447. WireCellItem(
  448. const std::string &expression_id,
  449. const std::string &rung_id,
  450. int column_offset,
  451. const QPointF &center)
  452. : expression_id_(expression_id),
  453. rung_id_(rung_id),
  454. column_offset_(column_offset)
  455. {
  456. setPos(center);
  457. setFlag(ItemIsSelectable, true);
  458. setZValue(2.2);
  459. setToolTip(LogicEditorWidget::tr("横线网格:第 %1 格,可用触点原位替换")
  460. .arg(column_offset + 1));
  461. }
  462. QRectF boundingRect() const override
  463. {
  464. return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
  465. }
  466. void paint(
  467. QPainter *painter,
  468. const QStyleOptionGraphicsItem *option,
  469. QWidget *) override
  470. {
  471. if ((option->state & QStyle::State_Selected) == 0)
  472. {
  473. return;
  474. }
  475. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  476. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  477. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  478. painter->setPen(ladderPen(false));
  479. painter->drawLine(
  480. QPointF(-kCellWidth / 2.0, 0), QPointF(kCellWidth / 2.0, 0));
  481. }
  482. const std::string &expressionId() const { return expression_id_; }
  483. const std::string &rungId() const { return rung_id_; }
  484. int columnOffset() const { return column_offset_; }
  485. private:
  486. std::string expression_id_;
  487. std::string rung_id_;
  488. int column_offset_ = 0;
  489. };
  490. class LogicEditorWidget::EmptySlotItem final : public QGraphicsItem
  491. {
  492. public:
  493. EmptySlotItem(
  494. const std::string &rung_id,
  495. int column,
  496. const QPointF &center,
  497. bool show_label,
  498. std::string branch_expression_id = {},
  499. bool branch_active = false)
  500. : rung_id_(rung_id),
  501. branch_expression_id_(std::move(branch_expression_id)),
  502. column_(column),
  503. show_label_(show_label),
  504. branch_active_(branch_active)
  505. {
  506. setPos(center);
  507. setFlag(ItemIsSelectable, true);
  508. setZValue(2.0);
  509. setToolTip(
  510. branch_expression_id_.empty()
  511. ? LogicEditorWidget::tr(
  512. "空条件网格:第 %1 列,点击后可插入触点")
  513. .arg(column + 1)
  514. : LogicEditorWidget::tr(
  515. "并联空网格:第 %1 格,点击后可插入触点")
  516. .arg(column + 1));
  517. }
  518. QRectF boundingRect() const override
  519. {
  520. return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
  521. }
  522. void paint(
  523. QPainter *painter,
  524. const QStyleOptionGraphicsItem *option,
  525. QWidget *) override
  526. {
  527. const bool selected = (option->state & QStyle::State_Selected) != 0;
  528. if (selected)
  529. {
  530. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  531. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  532. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  533. }
  534. if (!branch_expression_id_.empty())
  535. {
  536. // 选中补线格时仍需把结构横线绘制在选框上层
  537. painter->setPen(ladderPen(branch_active_));
  538. painter->drawLine(
  539. QPointF(-kCellWidth / 2.0, 0),
  540. QPointF(kCellWidth / 2.0, 0));
  541. }
  542. if (show_label_)
  543. {
  544. painter->setPen(kPlaceholderColor);
  545. painter->drawText(
  546. QRectF(-kCellWidth / 2.0, -20, kCellWidth, 40),
  547. Qt::AlignCenter,
  548. LogicEditorWidget::tr("添加条件"));
  549. }
  550. }
  551. const std::string &rungId() const { return rung_id_; }
  552. const std::string &branchExpressionId() const
  553. {
  554. return branch_expression_id_;
  555. }
  556. int column() const { return column_; }
  557. private:
  558. std::string rung_id_;
  559. std::string branch_expression_id_;
  560. int column_ = 0;
  561. bool show_label_ = false;
  562. bool branch_active_ = false;
  563. };
  564. class LogicEditorWidget::VerticalConnectorItem final : public QGraphicsItem
  565. {
  566. public:
  567. VerticalConnectorItem(
  568. const std::string &branch_expression_id,
  569. const std::string &rung_id,
  570. qreal x,
  571. qreal top,
  572. qreal bottom,
  573. bool active)
  574. : branch_expression_id_(branch_expression_id),
  575. rung_id_(rung_id),
  576. height_(bottom - top),
  577. active_(active)
  578. {
  579. setPos(x, top);
  580. setFlag(ItemIsSelectable, true);
  581. setZValue(2.5);
  582. setToolTip(LogicEditorWidget::tr("竖线连接:删除将移除对应并联支路"));
  583. }
  584. QRectF boundingRect() const override
  585. {
  586. return {-8.0, 0.0, 16.0, height_};
  587. }
  588. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  589. {
  590. const bool selected = (option->state & QStyle::State_Selected) != 0;
  591. painter->setPen(QPen(
  592. selected ? kSelectionBorderColor
  593. : active_ ? kActiveColor : kLadderColor,
  594. selected || active_ ? 3.0 : kLadderLineWidth));
  595. painter->drawLine(QPointF(0, 0), QPointF(0, height_));
  596. if (selected)
  597. {
  598. painter->setPen(QPen(kSelectionBorderColor, 1.0, Qt::DashLine));
  599. painter->drawRect(boundingRect().adjusted(1, 1, -1, -1));
  600. }
  601. }
  602. const std::string &branchExpressionId() const
  603. {
  604. return branch_expression_id_;
  605. }
  606. const std::string &rungId() const { return rung_id_; }
  607. private:
  608. std::string branch_expression_id_;
  609. std::string rung_id_;
  610. qreal height_ = 0.0;
  611. bool active_ = false;
  612. };
  613. class LogicEditorWidget::RungItem final : public QGraphicsItem
  614. {
  615. public:
  616. RungItem(
  617. const LadderRung &rung,
  618. int number,
  619. qreal top,
  620. qreal height,
  621. qreal width,
  622. qreal cursor_left)
  623. : rung_id_(rung.id),
  624. name_(QString::fromStdString(rung.name)),
  625. comment_(QString::fromStdString(rung.comment)),
  626. number_(number),
  627. height_(height),
  628. width_(width),
  629. cursor_left_(cursor_left),
  630. show_cursor_(!rung.condition.has_value())
  631. {
  632. setPos(0, top);
  633. setFlag(ItemIsSelectable, true);
  634. setZValue(-2.0);
  635. if (!comment_.isEmpty())
  636. {
  637. setToolTip(comment_);
  638. }
  639. }
  640. QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; }
  641. QPainterPath shape() const override
  642. {
  643. QPainterPath path;
  644. path.addRect(QRectF(kSceneMargin, 0, width_, kRungHeaderHeight));
  645. return path;
  646. }
  647. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  648. {
  649. const bool selected = (option->state & QStyle::State_Selected) != 0;
  650. if (selected)
  651. {
  652. painter->fillRect(boundingRect(), QColor(240, 247, 250, 90));
  653. if (show_cursor_)
  654. {
  655. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  656. painter->drawRect(QRectF(
  657. cursor_left_ + 3,
  658. kRungHeaderHeight + 3,
  659. kCellWidth - 6,
  660. kCellHeight - 6));
  661. }
  662. }
  663. painter->setPen(QColor(QStringLiteral("#62717b")));
  664. QString title = tr("网络 %1").arg(number_);
  665. if (!name_.isEmpty() && !name_.startsWith(tr("网络 ")))
  666. {
  667. title += QStringLiteral(":") + name_;
  668. }
  669. painter->drawText(
  670. QRectF(kSceneMargin + 8, 5, 320, 22),
  671. Qt::AlignLeft | Qt::AlignVCenter,
  672. title);
  673. if (!comment_.isEmpty())
  674. {
  675. painter->setPen(QColor(QStringLiteral("#7b8790")));
  676. const QString visible_comment = painter->fontMetrics().elidedText(
  677. comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0));
  678. painter->drawText(
  679. QRectF(kSceneMargin + 8, 24, width_ - 16, 22),
  680. Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
  681. visible_comment);
  682. }
  683. painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
  684. painter->drawLine(
  685. QPointF(kSceneMargin + 8, height_ - 1),
  686. QPointF(kSceneMargin + width_ - 8, height_ - 1));
  687. }
  688. const std::string &rungId() const { return rung_id_; }
  689. private:
  690. std::string rung_id_;
  691. QString name_;
  692. QString comment_;
  693. int number_ = 0;
  694. qreal height_ = 0.0;
  695. qreal width_ = 0.0;
  696. qreal cursor_left_ = 0.0;
  697. bool show_cursor_ = false;
  698. };
  699. namespace {
  700. RenderResult renderExpression(
  701. QGraphicsScene &scene,
  702. const LogicEditorService &editor_service,
  703. const ConditionExpression &expression,
  704. const std::string &rung_id,
  705. const QPointF &top_left,
  706. const ExpressionMetrics &metrics,
  707. const LogicTraceSnapshot &trace,
  708. bool trace_enabled,
  709. const std::string &fault_node_id)
  710. {
  711. if (expression.kind == ConditionExpressionKind::Wire)
  712. {
  713. const bool active = trace_enabled && traceValue(
  714. trace, &LogicTraceSnapshot::expressionPowerValues, expression.id);
  715. const qreal width = static_cast<qreal>(expression.wire->columnSpan) * kCellWidth;
  716. const QPointF center(
  717. top_left.x() + width / 2.0,
  718. top_left.y() + kCellHeight / 2.0);
  719. scene.addItem(new LogicEditorWidget::WireItem(
  720. expression.id,
  721. rung_id,
  722. center,
  723. expression.wire->columnSpan,
  724. active));
  725. for (int column = 0; column < expression.wire->columnSpan; ++column)
  726. {
  727. scene.addItem(new LogicEditorWidget::WireCellItem(
  728. expression.id,
  729. rung_id,
  730. column,
  731. QPointF(
  732. top_left.x() + (static_cast<qreal>(column) + 0.5)
  733. * kCellWidth,
  734. center.y())));
  735. }
  736. return {
  737. QPointF(top_left.x(), center.y()),
  738. QPointF(top_left.x() + width, center.y())};
  739. }
  740. if (expression.kind == ConditionExpressionKind::Node)
  741. {
  742. const QPointF center(
  743. top_left.x() + kCellWidth / 2.0,
  744. top_left.y() + kCellHeight / 2.0);
  745. const bool node_active = trace_enabled && traceValue(
  746. trace, &LogicTraceSnapshot::nodePowerValues, expression.node->id);
  747. scene.addLine(
  748. QLineF(
  749. QPointF(top_left.x(), center.y()),
  750. QPointF(top_left.x() + kCellWidth, center.y())),
  751. ladderPen(node_active));
  752. scene.addItem(new LogicEditorWidget::NodeItem(
  753. *expression.node,
  754. rung_id,
  755. center,
  756. [&editor_service, &expression]
  757. {
  758. const std::optional<RegisterAddress> address =
  759. registerAddressForLogicNode(expression.node->config);
  760. return address.has_value()
  761. ? QString::fromStdString(
  762. editor_service.registerCommentFor(*address))
  763. : QString{};
  764. }(),
  765. node_active,
  766. expression.node->id == fault_node_id,
  767. true));
  768. return {
  769. QPointF(top_left.x(), center.y()),
  770. QPointF(top_left.x() + kCellWidth, center.y())};
  771. }
  772. if (expression.kind == ConditionExpressionKind::Series)
  773. {
  774. qreal x = top_left.x();
  775. RenderResult first;
  776. RenderResult previous;
  777. for (std::size_t index = 0; index < expression.children.size(); ++index)
  778. {
  779. const ExpressionMetrics child_metrics = measureExpression(expression.children[index]);
  780. const RenderResult current = renderExpression(
  781. scene,
  782. editor_service,
  783. expression.children[index],
  784. rung_id,
  785. QPointF(x, top_left.y()),
  786. child_metrics,
  787. trace,
  788. trace_enabled,
  789. fault_node_id);
  790. if (index == 0U)
  791. {
  792. first = current;
  793. }
  794. else
  795. {
  796. const bool previous_active = trace_enabled && traceValue(
  797. trace,
  798. &LogicTraceSnapshot::expressionPowerValues,
  799. expression.children[index - 1U].id);
  800. scene.addLine(
  801. QLineF(previous.output, current.input),
  802. ladderPen(previous_active));
  803. }
  804. previous = current;
  805. x += static_cast<qreal>(child_metrics.columns) * kCellWidth;
  806. }
  807. return {first.input, previous.output};
  808. }
  809. qreal y = top_left.y();
  810. std::vector<RenderResult> branches;
  811. branches.reserve(expression.children.size());
  812. for (const ConditionExpression &child : expression.children)
  813. {
  814. const ExpressionMetrics child_metrics = measureExpression(child);
  815. branches.push_back(renderExpression(
  816. scene,
  817. editor_service,
  818. child,
  819. rung_id,
  820. QPointF(top_left.x(), y),
  821. child_metrics,
  822. trace,
  823. trace_enabled,
  824. fault_node_id));
  825. y += static_cast<qreal>(child_metrics.rows) * kCellHeight;
  826. }
  827. const qreal left_join = top_left.x();
  828. const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth;
  829. const qreal top_y = branches.front().input.y();
  830. const bool parallel_input_active = trace_enabled && traceValue(
  831. trace, &LogicTraceSnapshot::expressionInputValues, expression.id);
  832. for (std::size_t index = 1; index < branches.size(); ++index)
  833. {
  834. const qreal segment_top = branches[index - 1U].input.y();
  835. const qreal segment_bottom = branches[index].input.y();
  836. const bool branch_active = trace_enabled && traceValue(
  837. trace,
  838. &LogicTraceSnapshot::expressionPowerValues,
  839. expression.children[index].id);
  840. scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
  841. expression.children[index].id,
  842. rung_id,
  843. left_join,
  844. segment_top,
  845. segment_bottom,
  846. parallel_input_active));
  847. scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
  848. expression.children[index].id,
  849. rung_id,
  850. right_join,
  851. segment_top,
  852. segment_bottom,
  853. branch_active));
  854. }
  855. for (std::size_t index = 0; index < branches.size(); ++index)
  856. {
  857. const bool branch_active = trace_enabled && traceValue(
  858. trace,
  859. &LogicTraceSnapshot::expressionPowerValues,
  860. expression.children[index].id);
  861. scene.addLine(
  862. QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input),
  863. ladderPen(branch_active));
  864. if (branches[index].output.x() < right_join - 0.1)
  865. {
  866. // 分支宽度不足时补出到右侧汇合点的结构连接,不写入隐式 Wire
  867. scene.addLine(
  868. QLineF(
  869. branches[index].output,
  870. QPointF(right_join, branches[index].output.y())),
  871. ladderPen(branch_active));
  872. }
  873. const int branch_columns = measureExpression(
  874. expression.children[index]).columns;
  875. for (int column = branch_columns; column < metrics.columns; ++column)
  876. {
  877. scene.addItem(new LogicEditorWidget::EmptySlotItem(
  878. rung_id,
  879. column,
  880. QPointF(
  881. top_left.x()
  882. + (static_cast<qreal>(column) + 0.5) * kCellWidth,
  883. branches[index].output.y()),
  884. false,
  885. expression.children[index].id,
  886. branch_active));
  887. }
  888. }
  889. return {QPointF(left_join, top_y), QPointF(right_join, top_y)};
  890. }
  891. } // namespace
  892. LogicEditorWidget::LogicEditorWidget(LogicEditorService &editor_service, QWidget *parent)
  893. : QGraphicsView(parent), editor_service_(editor_service)
  894. {
  895. scene_ = new QGraphicsScene(this);
  896. setScene(scene_);
  897. setRenderHint(QPainter::Antialiasing, true);
  898. setBackgroundBrush(Qt::white);
  899. setDragMode(QGraphicsView::RubberBandDrag);
  900. setAlignment(Qt::AlignLeft | Qt::AlignTop);
  901. connect(scene_, &QGraphicsScene::selectionChanged,
  902. this, &LogicEditorWidget::handleSelectionChanged);
  903. }
  904. void LogicEditorWidget::setLogicId(const std::string &logic_id)
  905. {
  906. if (logic_id_ == logic_id)
  907. {
  908. return;
  909. }
  910. logic_id_ = logic_id;
  911. current_rung_id_.clear();
  912. reloadLogic();
  913. }
  914. void LogicEditorWidget::setEditingEnabled(bool enabled)
  915. {
  916. editing_enabled_ = enabled;
  917. setInteractive(enabled);
  918. }
  919. void LogicEditorWidget::setRuntimeTrace(
  920. const LogicTraceSnapshot &trace, const std::string &fault_node_id)
  921. {
  922. // 轨迹只是模型的只读投影;编辑器不因显示轨迹而修改工程表达式
  923. trace_ = trace;
  924. fault_node_id_ = fault_node_id;
  925. runtime_trace_enabled_ = true;
  926. reloadLogic();
  927. }
  928. void LogicEditorWidget::clearRuntimeTrace()
  929. {
  930. trace_.clear();
  931. fault_node_id_.clear();
  932. runtime_trace_enabled_ = false;
  933. reloadLogic();
  934. }
  935. void LogicEditorWidget::reloadLogic()
  936. {
  937. // 逻辑或选择变化后重新计算网格布局和可点击插入目标
  938. scene_->clear();
  939. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  940. if (logic == nullptr)
  941. {
  942. scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
  943. return;
  944. }
  945. const bool current_rung_exists = std::any_of(
  946. logic->rungs.cbegin(), logic->rungs.cend(),
  947. [this](const LadderRung &rung)
  948. {
  949. return rung.id == current_rung_id_;
  950. });
  951. if (!current_rung_id_.empty() && !current_rung_exists)
  952. {
  953. // 撤销或重做可能移除当前网络,不能把过期 ID 继续传给编辑服务
  954. current_rung_id_.clear();
  955. }
  956. if (current_rung_id_.empty() && !logic->rungs.empty())
  957. {
  958. current_rung_id_ = logic->rungs.front().id;
  959. }
  960. const int condition_columns = ProjectLimits::kMaximumConditionColumns;
  961. const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 1);
  962. const qreal left_rail_x = kSceneMargin + kRailInset;
  963. const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
  964. const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
  965. qreal top = kSceneMargin;
  966. int number = 1;
  967. for (const LadderRung &rung : logic->rungs)
  968. {
  969. const ExpressionMetrics metrics = rung.condition.has_value()
  970. ? measureExpression(*rung.condition) : ExpressionMetrics{};
  971. const int occupied_columns = rung.condition.has_value() ? metrics.columns : 0;
  972. const int grid_rows = std::max(1, metrics.rows);
  973. const qreal grid_top = top + kRungHeaderHeight;
  974. const qreal rung_height = kRungHeaderHeight
  975. + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
  976. scene_->addItem(new RungItem(
  977. rung,
  978. number,
  979. top,
  980. rung_height,
  981. scene_width - 2.0 * kSceneMargin,
  982. left_rail_x));
  983. addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
  984. scene_->addLine(
  985. QLineF(left_rail_x, grid_top, left_rail_x,
  986. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  987. QPen(kLadderColor, 2.4));
  988. scene_->addLine(
  989. QLineF(right_rail_x, grid_top, right_rail_x,
  990. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  991. QPen(kLadderColor, 2.4));
  992. const qreal main_y = grid_top + kCellHeight / 2.0;
  993. QPointF expression_output(left_rail_x, main_y);
  994. if (rung.condition.has_value())
  995. {
  996. const RenderResult rendered = renderExpression(
  997. *scene_,
  998. editor_service_,
  999. *rung.condition,
  1000. rung.id,
  1001. QPointF(left_rail_x, grid_top),
  1002. metrics,
  1003. trace_,
  1004. runtime_trace_enabled_,
  1005. fault_node_id_);
  1006. expression_output = rendered.output;
  1007. }
  1008. for (int column = occupied_columns;
  1009. column < ProjectLimits::kMaximumConditionColumns;
  1010. ++column)
  1011. {
  1012. scene_->addItem(new EmptySlotItem(
  1013. rung.id,
  1014. column,
  1015. QPointF(
  1016. left_rail_x + (static_cast<qreal>(column) + 0.5)
  1017. * kCellWidth,
  1018. main_y),
  1019. column == 0 && !rung.output.has_value()));
  1020. }
  1021. const qreal output_left = right_rail_x - kCellWidth;
  1022. if (rung.output.has_value())
  1023. {
  1024. const bool rung_active = runtime_trace_enabled_ && traceValue(
  1025. trace_, &LogicTraceSnapshot::rungValues, rung.id);
  1026. scene_->addLine(
  1027. QLineF(expression_output, QPointF(output_left, main_y)),
  1028. ladderPen(rung_active));
  1029. scene_->addLine(
  1030. QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
  1031. ladderPen(rung_active));
  1032. scene_->addItem(new NodeItem(
  1033. *rung.output,
  1034. rung.id,
  1035. QPointF(output_left + kCellWidth / 2.0, main_y),
  1036. [&rung, this]
  1037. {
  1038. const std::optional<RegisterAddress> address =
  1039. registerAddressForLogicNode(rung.output->config);
  1040. return address.has_value()
  1041. ? QString::fromStdString(
  1042. editor_service_.registerCommentFor(*address))
  1043. : QString{};
  1044. }(),
  1045. rung_active,
  1046. rung.output->id == fault_node_id_,
  1047. false));
  1048. }
  1049. else
  1050. {
  1051. addPlaceholder(
  1052. *scene_,
  1053. QRectF(output_left, grid_top, kCellWidth, kCellHeight),
  1054. tr("输出线圈"));
  1055. }
  1056. top += rung_height + kRungGap;
  1057. ++number;
  1058. }
  1059. scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
  1060. }
  1061. void LogicEditorWidget::selectNode(const std::string &node_id)
  1062. {
  1063. for (QGraphicsItem *item : scene_->items())
  1064. {
  1065. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1066. {
  1067. node->setSelected(node->nodeId() == node_id);
  1068. if (node->nodeId() == node_id)
  1069. {
  1070. current_rung_id_ = node->rungId();
  1071. ensureVisible(node);
  1072. }
  1073. }
  1074. else
  1075. {
  1076. item->setSelected(false);
  1077. }
  1078. }
  1079. }
  1080. void LogicEditorWidget::selectExpression(const std::string &expression_id)
  1081. {
  1082. for (QGraphicsItem *item : scene_->items())
  1083. {
  1084. bool selected = false;
  1085. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1086. {
  1087. selected = node->isConditionNode() && node->nodeId() == expression_id;
  1088. if (selected)
  1089. {
  1090. current_rung_id_ = node->rungId();
  1091. ensureVisible(node);
  1092. }
  1093. }
  1094. else if (WireItem *wire = dynamic_cast<WireItem *>(item))
  1095. {
  1096. selected = wire->expressionId() == expression_id;
  1097. if (selected)
  1098. {
  1099. current_rung_id_ = wire->rungId();
  1100. ensureVisible(wire);
  1101. }
  1102. }
  1103. item->setSelected(selected);
  1104. }
  1105. }
  1106. void LogicEditorWidget::selectWire(const std::string &wire_id)
  1107. {
  1108. selectExpression(wire_id);
  1109. }
  1110. std::string LogicEditorWidget::selectedNodeId() const
  1111. {
  1112. const std::vector<std::string> ids = selectedNodeIds();
  1113. return ids.size() == 1U ? ids.front() : std::string{};
  1114. }
  1115. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  1116. {
  1117. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1118. for (QGraphicsItem *item : scene_->selectedItems())
  1119. {
  1120. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1121. {
  1122. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1123. }
  1124. }
  1125. std::sort(
  1126. positioned_ids.begin(), positioned_ids.end(),
  1127. [](const auto &left, const auto &right)
  1128. {
  1129. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1130. {
  1131. return left.first.y() < right.first.y();
  1132. }
  1133. return left.first.x() < right.first.x();
  1134. });
  1135. std::vector<std::string> ids;
  1136. ids.reserve(positioned_ids.size());
  1137. for (const auto &positioned_id : positioned_ids)
  1138. {
  1139. ids.push_back(positioned_id.second);
  1140. }
  1141. return ids;
  1142. }
  1143. std::vector<std::string> LogicEditorWidget::selectedExpressionIds() const
  1144. {
  1145. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1146. for (QGraphicsItem *item : scene_->selectedItems())
  1147. {
  1148. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1149. {
  1150. if (node->isConditionNode())
  1151. {
  1152. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1153. }
  1154. }
  1155. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1156. {
  1157. positioned_ids.emplace_back(wire->scenePos(), wire->expressionId());
  1158. }
  1159. else if (const WireCellItem *cell =
  1160. dynamic_cast<const WireCellItem *>(item))
  1161. {
  1162. positioned_ids.emplace_back(cell->scenePos(), cell->expressionId());
  1163. }
  1164. }
  1165. std::sort(
  1166. positioned_ids.begin(), positioned_ids.end(),
  1167. [](const auto &left, const auto &right)
  1168. {
  1169. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1170. {
  1171. return left.first.y() < right.first.y();
  1172. }
  1173. return left.first.x() < right.first.x();
  1174. });
  1175. std::vector<std::string> ids;
  1176. ids.reserve(positioned_ids.size());
  1177. for (const auto &positioned_id : positioned_ids)
  1178. {
  1179. if (std::find(ids.cbegin(), ids.cend(), positioned_id.second) == ids.cend())
  1180. {
  1181. ids.push_back(positioned_id.second);
  1182. }
  1183. }
  1184. return ids;
  1185. }
  1186. std::vector<std::string> LogicEditorWidget::selectedWireIds() const
  1187. {
  1188. std::vector<std::string> ids;
  1189. for (QGraphicsItem *item : scene_->selectedItems())
  1190. {
  1191. if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1192. {
  1193. if (std::find(ids.cbegin(), ids.cend(), wire->expressionId()) == ids.cend())
  1194. {
  1195. ids.push_back(wire->expressionId());
  1196. }
  1197. }
  1198. else if (const WireCellItem *cell =
  1199. dynamic_cast<const WireCellItem *>(item))
  1200. {
  1201. if (std::find(ids.cbegin(), ids.cend(), cell->expressionId()) == ids.cend())
  1202. {
  1203. ids.push_back(cell->expressionId());
  1204. }
  1205. }
  1206. }
  1207. return ids;
  1208. }
  1209. std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedEmptySlots() const
  1210. {
  1211. std::vector<std::pair<std::string, int>> targets;
  1212. for (QGraphicsItem *item : scene_->selectedItems())
  1213. {
  1214. if (const EmptySlotItem *slot = dynamic_cast<const EmptySlotItem *>(item))
  1215. {
  1216. targets.emplace_back(slot->branchExpressionId(), slot->column());
  1217. }
  1218. }
  1219. return targets;
  1220. }
  1221. std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedWireCells() const
  1222. {
  1223. std::vector<std::pair<std::string, int>> cells;
  1224. for (QGraphicsItem *item : scene_->selectedItems())
  1225. {
  1226. if (const WireCellItem *cell = dynamic_cast<const WireCellItem *>(item))
  1227. {
  1228. cells.emplace_back(cell->expressionId(), cell->columnOffset());
  1229. }
  1230. }
  1231. return cells;
  1232. }
  1233. std::vector<std::string> LogicEditorWidget::selectedBranchIds() const
  1234. {
  1235. std::vector<std::string> ids;
  1236. for (QGraphicsItem *item : scene_->selectedItems())
  1237. {
  1238. if (const VerticalConnectorItem *connector =
  1239. dynamic_cast<const VerticalConnectorItem *>(item))
  1240. {
  1241. if (std::find(ids.cbegin(), ids.cend(), connector->branchExpressionId())
  1242. == ids.cend())
  1243. {
  1244. ids.push_back(connector->branchExpressionId());
  1245. }
  1246. }
  1247. }
  1248. return ids;
  1249. }
  1250. std::string LogicEditorWidget::selectedRungId() const
  1251. {
  1252. std::string rung_id;
  1253. for (QGraphicsItem *item : scene_->selectedItems())
  1254. {
  1255. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1256. {
  1257. if (!rung_id.empty() && rung_id != node->rungId())
  1258. {
  1259. return {};
  1260. }
  1261. rung_id = node->rungId();
  1262. }
  1263. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1264. {
  1265. if (!rung_id.empty() && rung_id != wire->rungId())
  1266. {
  1267. return {};
  1268. }
  1269. rung_id = wire->rungId();
  1270. }
  1271. else if (const WireCellItem *cell =
  1272. dynamic_cast<const WireCellItem *>(item))
  1273. {
  1274. if (!rung_id.empty() && rung_id != cell->rungId())
  1275. {
  1276. return {};
  1277. }
  1278. rung_id = cell->rungId();
  1279. }
  1280. else if (const VerticalConnectorItem *connector =
  1281. dynamic_cast<const VerticalConnectorItem *>(item))
  1282. {
  1283. if (!rung_id.empty() && rung_id != connector->rungId())
  1284. {
  1285. return {};
  1286. }
  1287. rung_id = connector->rungId();
  1288. }
  1289. else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
  1290. {
  1291. if (rung_id.empty())
  1292. {
  1293. rung_id = rung->rungId();
  1294. }
  1295. }
  1296. else if (const EmptySlotItem *slot =
  1297. dynamic_cast<const EmptySlotItem *>(item))
  1298. {
  1299. if (!rung_id.empty() && rung_id != slot->rungId())
  1300. {
  1301. return {};
  1302. }
  1303. rung_id = slot->rungId();
  1304. }
  1305. }
  1306. return rung_id;
  1307. }
  1308. bool LogicEditorWidget::hasSelectedRungItem() const
  1309. {
  1310. for (QGraphicsItem *item : scene_->selectedItems())
  1311. {
  1312. if (dynamic_cast<const RungItem *>(item) != nullptr)
  1313. {
  1314. return true;
  1315. }
  1316. }
  1317. return false;
  1318. }
  1319. LogicEditorResult LogicEditorWidget::addRung()
  1320. {
  1321. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  1322. if (result.succeeded)
  1323. {
  1324. current_rung_id_ = result.id;
  1325. reloadLogic();
  1326. emit graphChanged();
  1327. }
  1328. else
  1329. {
  1330. reportFailure(result);
  1331. }
  1332. return result;
  1333. }
  1334. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  1335. {
  1336. const std::vector<std::pair<std::string, int>> selected_empty_slots =
  1337. selectedEmptySlots();
  1338. const std::vector<std::pair<std::string, int>> selected_wire_cells =
  1339. selectedWireCells();
  1340. const std::vector<std::string> selected_expressions = selectedExpressionIds();
  1341. const std::vector<std::string> selected_wires = selectedWireIds();
  1342. const std::vector<std::string> selected_ids = selectedNodeIds();
  1343. LogicEditorResult result;
  1344. if (selected_empty_slots.size() > 1U || selected_wire_cells.size() > 1U
  1345. || (!selected_empty_slots.empty()
  1346. && (!selected_wire_cells.empty()
  1347. || !selected_expressions.empty() || !selected_ids.empty()))
  1348. || (!selected_wire_cells.empty()
  1349. && (!selected_ids.empty() || selected_expressions.size() > 1U)))
  1350. {
  1351. result = {false, LogicEditorError::InvalidOperation,
  1352. "插入条件时只能选择一个网格或条件对象", {}};
  1353. }
  1354. else if (selected_empty_slots.size() == 1U)
  1355. {
  1356. const auto &slot = selected_empty_slots.front();
  1357. result = slot.first.empty()
  1358. ? editor_service_.insertConditionAtColumn(
  1359. logic_id_, currentRungId(), slot.second, config)
  1360. : editor_service_.insertConditionInBranchAtColumn(
  1361. logic_id_, currentRungId(), slot.first, slot.second, config);
  1362. }
  1363. else if (selected_wire_cells.size() == 1U)
  1364. {
  1365. result = editor_service_.replaceWireColumnWithCondition(
  1366. logic_id_,
  1367. currentRungId(),
  1368. selected_wire_cells.front().first,
  1369. selected_wire_cells.front().second,
  1370. config);
  1371. }
  1372. else if (selected_expressions.size() > 1U || selected_wires.size() > 1U)
  1373. {
  1374. result = {false, LogicEditorError::InvalidOperation,
  1375. "串联插入或替换横线时只能选择一个条件对象", {}};
  1376. }
  1377. else if (selected_wires.size() == 1U)
  1378. {
  1379. result = editor_service_.replaceWireWithCondition(
  1380. logic_id_, currentRungId(), selected_wires.front(), config);
  1381. }
  1382. else if (selected_ids.size() == 1U)
  1383. {
  1384. const LogicNode *selected_node = editor_service_.findNode(
  1385. logic_id_, selected_ids.front());
  1386. result = selected_node != nullptr && selected_node->isCondition()
  1387. ? editor_service_.insertConditionAfter(
  1388. logic_id_, currentRungId(), selected_ids.front(), config)
  1389. : editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1390. }
  1391. else
  1392. {
  1393. result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1394. }
  1395. if (result.succeeded)
  1396. {
  1397. reloadLogic();
  1398. selectExpression(result.id);
  1399. emit graphChanged();
  1400. }
  1401. else
  1402. {
  1403. reportFailure(result);
  1404. }
  1405. return result;
  1406. }
  1407. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  1408. {
  1409. const std::string rung_id = selectedRungId();
  1410. const std::vector<std::pair<std::string, int>> selected_empty_slots =
  1411. selectedEmptySlots();
  1412. const std::vector<std::pair<std::string, int>> selected_wire_cells =
  1413. selectedWireCells();
  1414. const std::vector<std::string> selected_ids = selectedNodeIds();
  1415. std::vector<std::string> condition_ids;
  1416. for (const std::string &node_id : selected_ids)
  1417. {
  1418. const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
  1419. if (node != nullptr && node->isCondition())
  1420. {
  1421. condition_ids.push_back(node_id);
  1422. }
  1423. }
  1424. LogicEditorResult result;
  1425. if (!selected_empty_slots.empty()
  1426. && (!selected_ids.empty() || !selected_wire_cells.empty()))
  1427. {
  1428. result = {false, LogicEditorError::InvalidOperation,
  1429. "并联支路不能混合选择空白网格和逻辑对象", {}};
  1430. }
  1431. else if (!selected_empty_slots.empty())
  1432. {
  1433. result = {false, LogicEditorError::InvalidOperation,
  1434. "空白网格没有可并联的逻辑,请先选择触点或横线", {}};
  1435. }
  1436. else if (!selected_wire_cells.empty())
  1437. {
  1438. result = {false, LogicEditorError::InvalidOperation,
  1439. "并联支路需要选择触点;横线网格请使用竖线连接", {}};
  1440. }
  1441. else if (rung_id.empty() || condition_ids.empty())
  1442. {
  1443. result = {false, LogicEditorError::InvalidOperation,
  1444. "请在同一网络中选择要并联的连续节点", {}};
  1445. }
  1446. else
  1447. {
  1448. result = editor_service_.addParallelBranch(
  1449. logic_id_, rung_id, condition_ids, config);
  1450. }
  1451. if (result.succeeded)
  1452. {
  1453. reloadLogic();
  1454. selectExpression(result.id);
  1455. emit graphChanged();
  1456. }
  1457. else
  1458. {
  1459. reportFailure(result);
  1460. }
  1461. return result;
  1462. }
  1463. LogicEditorResult LogicEditorWidget::addHorizontalWire()
  1464. {
  1465. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1466. LogicEditorResult result;
  1467. if (selected_ids.size() > 1U)
  1468. {
  1469. result = {false, LogicEditorError::InvalidOperation,
  1470. "插入横线时只能选择一个条件对象", {}};
  1471. }
  1472. else if (selected_ids.size() == 1U)
  1473. {
  1474. result = editor_service_.insertWireAfter(
  1475. logic_id_, currentRungId(), selected_ids.front());
  1476. }
  1477. else
  1478. {
  1479. result = editor_service_.appendWire(logic_id_, currentRungId());
  1480. }
  1481. if (result.succeeded)
  1482. {
  1483. reloadLogic();
  1484. selectWire(result.id);
  1485. emit graphChanged();
  1486. }
  1487. else
  1488. {
  1489. reportFailure(result);
  1490. }
  1491. return result;
  1492. }
  1493. LogicEditorResult LogicEditorWidget::addVerticalWire()
  1494. {
  1495. const std::string rung_id = selectedRungId();
  1496. const std::vector<std::pair<std::string, int>> selected_empty_slots =
  1497. selectedEmptySlots();
  1498. const std::vector<std::pair<std::string, int>> selected_wire_cells =
  1499. selectedWireCells();
  1500. const std::vector<std::string> selected_nodes = selectedNodeIds();
  1501. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1502. LogicEditorResult result;
  1503. if (!selected_empty_slots.empty()
  1504. && (!selected_wire_cells.empty() || !selected_nodes.empty()))
  1505. {
  1506. result = {false, LogicEditorError::InvalidOperation,
  1507. "竖线不能混合选择空白网格和逻辑对象", {}};
  1508. }
  1509. else if (!selected_empty_slots.empty())
  1510. {
  1511. result = {false, LogicEditorError::InvalidOperation,
  1512. "空白网格没有可连接的逻辑,请先插入触点或横线", {}};
  1513. }
  1514. else if (!selected_wire_cells.empty() && !selected_nodes.empty())
  1515. {
  1516. result = {false, LogicEditorError::InvalidOperation,
  1517. "竖线只能选择连续触点或同一条横线的网格", {}};
  1518. }
  1519. else if (rung_id.empty() || selected_ids.empty())
  1520. {
  1521. result = {false, LogicEditorError::InvalidOperation,
  1522. "请在同一网络中选择要连接的连续条件或横线", {}};
  1523. }
  1524. else if (!selected_wire_cells.empty())
  1525. {
  1526. result = editor_service_.addParallelWireBranchAtCells(
  1527. logic_id_, rung_id, selected_wire_cells);
  1528. }
  1529. else
  1530. {
  1531. result = editor_service_.addParallelWireBranch(
  1532. logic_id_, rung_id, selected_ids);
  1533. }
  1534. if (result.succeeded)
  1535. {
  1536. reloadLogic();
  1537. selectWire(result.id);
  1538. emit graphChanged();
  1539. }
  1540. else
  1541. {
  1542. reportFailure(result);
  1543. }
  1544. return result;
  1545. }
  1546. LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
  1547. {
  1548. const std::vector<std::string> wire_ids = selectedWireIds();
  1549. const std::string rung_id = selectedRungId();
  1550. if (wire_ids.empty() || rung_id.empty())
  1551. {
  1552. const LogicEditorResult result = {
  1553. false,
  1554. LogicEditorError::InvalidOperation,
  1555. "请先选择要删除的横线",
  1556. {}};
  1557. reportFailure(result);
  1558. return result;
  1559. }
  1560. const LogicEditorResult result = editor_service_.removeExpressions(
  1561. logic_id_, rung_id, wire_ids);
  1562. if (result.succeeded)
  1563. {
  1564. reloadLogic();
  1565. emit nodeSelected({});
  1566. emit graphChanged();
  1567. }
  1568. else
  1569. {
  1570. reportFailure(result);
  1571. }
  1572. return result;
  1573. }
  1574. LogicEditorResult LogicEditorWidget::deleteVerticalWire()
  1575. {
  1576. const std::vector<std::string> branch_ids = selectedBranchIds();
  1577. if (branch_ids.empty())
  1578. {
  1579. const LogicEditorResult result = {
  1580. false,
  1581. LogicEditorError::InvalidOperation,
  1582. "请先选择要删除的竖线连接",
  1583. {}};
  1584. reportFailure(result);
  1585. return result;
  1586. }
  1587. const std::string rung_id = selectedRungId();
  1588. LogicEditorResult result;
  1589. if (rung_id.empty())
  1590. {
  1591. result = {false, LogicEditorError::InvalidOperation,
  1592. "竖线连接必须位于同一网络", {}};
  1593. }
  1594. else
  1595. {
  1596. result = editor_service_.removeExpressions(
  1597. logic_id_, rung_id, branch_ids);
  1598. }
  1599. if (result.succeeded)
  1600. {
  1601. reloadLogic();
  1602. emit nodeSelected({});
  1603. emit graphChanged();
  1604. }
  1605. else
  1606. {
  1607. reportFailure(result);
  1608. }
  1609. return result;
  1610. }
  1611. LogicEditorResult LogicEditorWidget::setOutput(
  1612. const LogicNodeConfig &config, bool configured)
  1613. {
  1614. const LogicEditorResult result = editor_service_.setOutput(
  1615. logic_id_, currentRungId(), config, configured);
  1616. if (result.succeeded)
  1617. {
  1618. reloadLogic();
  1619. selectNode(result.id);
  1620. emit graphChanged();
  1621. }
  1622. else
  1623. {
  1624. reportFailure(result);
  1625. }
  1626. return result;
  1627. }
  1628. LogicEditorResult LogicEditorWidget::pasteConditionNodes(
  1629. const std::vector<LogicNode> &nodes)
  1630. {
  1631. const std::vector<std::pair<std::string, int>> selected_empty_slots =
  1632. selectedEmptySlots();
  1633. const std::vector<std::pair<std::string, int>> selected_wire_cells =
  1634. selectedWireCells();
  1635. const std::vector<std::string> selected_expressions = selectedExpressionIds();
  1636. const std::vector<std::string> selected_wires = selectedWireIds();
  1637. const std::vector<std::string> selected_ids = selectedNodeIds();
  1638. LogicEditorResult result;
  1639. LogicConditionPasteTarget target;
  1640. if (selected_empty_slots.size() > 1U || selected_wire_cells.size() > 1U
  1641. || (!selected_empty_slots.empty()
  1642. && (!selected_wire_cells.empty()
  1643. || !selected_expressions.empty() || !selected_ids.empty()))
  1644. || (!selected_wire_cells.empty()
  1645. && (!selected_ids.empty() || selected_expressions.size() > 1U))
  1646. || selected_expressions.size() > 1U || selected_wires.size() > 1U
  1647. || selected_ids.size() > 1U)
  1648. {
  1649. result = {false, LogicEditorError::InvalidOperation,
  1650. "粘贴梯形图条件时只能选择一个目标位置", {}};
  1651. }
  1652. else
  1653. {
  1654. if (selected_empty_slots.size() == 1U)
  1655. {
  1656. const auto &slot = selected_empty_slots.front();
  1657. target.kind = slot.first.empty()
  1658. ? LogicConditionPasteTargetKind::EmptyColumn
  1659. : LogicConditionPasteTargetKind::BranchEmptyColumn;
  1660. target.expressionId = slot.first;
  1661. target.column = slot.second;
  1662. }
  1663. else if (selected_wire_cells.size() == 1U)
  1664. {
  1665. target.kind = LogicConditionPasteTargetKind::ReplaceWireColumn;
  1666. target.expressionId = selected_wire_cells.front().first;
  1667. target.column = selected_wire_cells.front().second;
  1668. }
  1669. else if (selected_wires.size() == 1U)
  1670. {
  1671. target.kind = LogicConditionPasteTargetKind::ReplaceWire;
  1672. target.expressionId = selected_wires.front();
  1673. }
  1674. else if (selected_ids.size() == 1U)
  1675. {
  1676. const LogicNode *selected_node = editor_service_.findNode(
  1677. logic_id_, selected_ids.front());
  1678. if (selected_node != nullptr && selected_node->isCondition())
  1679. {
  1680. target.kind = LogicConditionPasteTargetKind::AfterNode;
  1681. target.expressionId = selected_ids.front();
  1682. }
  1683. }
  1684. result = editor_service_.pasteConditionNodes(
  1685. logic_id_, currentRungId(), nodes, target);
  1686. }
  1687. if (result.succeeded)
  1688. {
  1689. reloadLogic();
  1690. selectNode(result.id);
  1691. emit graphChanged();
  1692. }
  1693. else
  1694. {
  1695. reportFailure(result);
  1696. }
  1697. return result;
  1698. }
  1699. LogicEditorResult LogicEditorWidget::deleteSelected()
  1700. {
  1701. const std::vector<std::string> expression_ids = selectedExpressionIds();
  1702. const std::vector<std::string> wire_ids = selectedWireIds();
  1703. const std::vector<std::string> branch_ids = selectedBranchIds();
  1704. const std::vector<std::string> node_ids = selectedNodeIds();
  1705. LogicEditorResult result;
  1706. if (!branch_ids.empty())
  1707. {
  1708. result = editor_service_.removeExpressions(
  1709. logic_id_, selectedRungId(), branch_ids);
  1710. }
  1711. else if (!wire_ids.empty())
  1712. {
  1713. bool output_selected = false;
  1714. for (QGraphicsItem *item : scene_->selectedItems())
  1715. {
  1716. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1717. {
  1718. output_selected = output_selected || !node->isConditionNode();
  1719. }
  1720. }
  1721. if (output_selected)
  1722. {
  1723. result = {false, LogicEditorError::InvalidOperation,
  1724. "不能同时删除横线和输出节点", {}};
  1725. }
  1726. else
  1727. {
  1728. result = editor_service_.removeExpressions(
  1729. logic_id_, selectedRungId(), expression_ids);
  1730. }
  1731. }
  1732. else if (!node_ids.empty())
  1733. {
  1734. result = editor_service_.removeNodes(logic_id_, node_ids);
  1735. }
  1736. else if (hasSelectedRungItem())
  1737. {
  1738. const std::string rung_id = selectedRungId();
  1739. result = editor_service_.removeRung(logic_id_, rung_id);
  1740. if (result.succeeded && current_rung_id_ == rung_id)
  1741. {
  1742. current_rung_id_ = editor_service_.firstRungId(logic_id_);
  1743. }
  1744. }
  1745. else
  1746. {
  1747. result = {false, LogicEditorError::InvalidOperation,
  1748. "请先选择要删除的逻辑节点或网络", {}};
  1749. }
  1750. if (result.succeeded)
  1751. {
  1752. reloadLogic();
  1753. emit nodeSelected({});
  1754. emit graphChanged();
  1755. }
  1756. else
  1757. {
  1758. reportFailure(result);
  1759. }
  1760. return result;
  1761. }
  1762. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  1763. {
  1764. QGraphicsView::resizeEvent(event);
  1765. }
  1766. void LogicEditorWidget::handleSelectionChanged()
  1767. {
  1768. const std::string rung_id = selectedRungId();
  1769. if (!rung_id.empty())
  1770. {
  1771. current_rung_id_ = rung_id;
  1772. }
  1773. emit nodeSelected(QString::fromStdString(selectedNodeId()));
  1774. }
  1775. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  1776. {
  1777. emit editorError(QString::fromStdString(result.message));
  1778. }
  1779. std::string LogicEditorWidget::currentRungId() const
  1780. {
  1781. const std::string selected = selectedRungId();
  1782. if (!selected.empty())
  1783. {
  1784. return selected;
  1785. }
  1786. return current_rung_id_.empty()
  1787. ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
  1788. }