综合平台编程器项目的远程存储
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

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