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

1882 行
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. if (current_rung_id_.empty() && !logic->rungs.empty())
  1063. {
  1064. current_rung_id_ = logic->rungs.front().id;
  1065. }
  1066. const int condition_columns = ProjectLimits::kMaximumConditionColumns;
  1067. const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 1);
  1068. const qreal left_rail_x = kSceneMargin + kRailInset;
  1069. const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
  1070. const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
  1071. qreal top = kSceneMargin;
  1072. int number = 1;
  1073. for (const LadderRung &rung : logic->rungs)
  1074. {
  1075. const ExpressionMetrics metrics = rung.condition.has_value()
  1076. ? measureExpression(*rung.condition) : ExpressionMetrics{};
  1077. const int occupied_columns = rung.condition.has_value() ? metrics.columns : 0;
  1078. const int grid_rows = std::max(1, metrics.rows);
  1079. const qreal grid_top = top + kRungHeaderHeight;
  1080. const qreal rung_height = kRungHeaderHeight
  1081. + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
  1082. scene_->addItem(new RungItem(
  1083. rung,
  1084. number,
  1085. top,
  1086. rung_height,
  1087. scene_width - 2.0 * kSceneMargin,
  1088. left_rail_x));
  1089. addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
  1090. scene_->addLine(
  1091. QLineF(left_rail_x, grid_top, left_rail_x,
  1092. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  1093. QPen(kLadderColor, 2.4));
  1094. scene_->addLine(
  1095. QLineF(right_rail_x, grid_top, right_rail_x,
  1096. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  1097. QPen(kLadderColor, 2.4));
  1098. const qreal main_y = grid_top + kCellHeight / 2.0;
  1099. QPointF expression_output(left_rail_x, main_y);
  1100. if (rung.condition.has_value())
  1101. {
  1102. const RenderResult rendered = renderExpression(
  1103. *scene_,
  1104. editor_service_,
  1105. *rung.condition,
  1106. rung.id,
  1107. QPointF(left_rail_x, grid_top),
  1108. metrics,
  1109. trace_,
  1110. runtime_trace_enabled_,
  1111. fault_node_id_);
  1112. expression_output = rendered.output;
  1113. }
  1114. for (int column = occupied_columns;
  1115. column < ProjectLimits::kMaximumConditionColumns;
  1116. ++column)
  1117. {
  1118. scene_->addItem(new EmptySlotItem(
  1119. rung.id,
  1120. column,
  1121. QPointF(
  1122. left_rail_x + (static_cast<qreal>(column) + 0.5)
  1123. * kCellWidth,
  1124. main_y),
  1125. column == 0 && !rung.output.has_value()));
  1126. }
  1127. const qreal output_left = right_rail_x - kCellWidth;
  1128. if (rung.output.has_value())
  1129. {
  1130. const bool rung_active = runtime_trace_enabled_ && traceValue(
  1131. trace_, &LogicTraceSnapshot::rungValues, rung.id);
  1132. scene_->addLine(
  1133. QLineF(expression_output, QPointF(output_left, main_y)),
  1134. ladderPen(rung_active));
  1135. scene_->addLine(
  1136. QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
  1137. ladderPen(rung_active));
  1138. scene_->addItem(new NodeItem(
  1139. *rung.output,
  1140. rung.id,
  1141. QPointF(output_left + kCellWidth / 2.0, main_y),
  1142. [&rung, this]
  1143. {
  1144. const std::optional<RegisterAddress> address =
  1145. registerAddressForLogicNode(rung.output->config);
  1146. return address.has_value()
  1147. ? QString::fromStdString(
  1148. editor_service_.registerCommentFor(*address))
  1149. : QString{};
  1150. }(),
  1151. [&rung, this, rung_active]
  1152. {
  1153. return (std::holds_alternative<TonNodeConfig>(rung.output->config)
  1154. || std::holds_alternative<CounterNodeConfig>(
  1155. rung.output->config))
  1156. ? traceValue(
  1157. trace_,
  1158. &LogicTraceSnapshot::nodeValues,
  1159. rung.output->id)
  1160. : rung_active;
  1161. }(),
  1162. rung.output->id == fault_node_id_,
  1163. false));
  1164. }
  1165. else
  1166. {
  1167. addPlaceholder(
  1168. *scene_,
  1169. QRectF(output_left, grid_top, kCellWidth, kCellHeight),
  1170. tr("输出线圈"));
  1171. }
  1172. top += rung_height + kRungGap;
  1173. ++number;
  1174. }
  1175. scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
  1176. }
  1177. void LogicEditorWidget::selectNode(const std::string &node_id)
  1178. {
  1179. for (QGraphicsItem *item : scene_->items())
  1180. {
  1181. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1182. {
  1183. node->setSelected(node->nodeId() == node_id);
  1184. if (node->nodeId() == node_id)
  1185. {
  1186. current_rung_id_ = node->rungId();
  1187. ensureVisible(node);
  1188. }
  1189. }
  1190. else
  1191. {
  1192. item->setSelected(false);
  1193. }
  1194. }
  1195. }
  1196. void LogicEditorWidget::selectExpression(const std::string &expression_id)
  1197. {
  1198. for (QGraphicsItem *item : scene_->items())
  1199. {
  1200. bool selected = false;
  1201. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1202. {
  1203. selected = node->isConditionNode() && node->nodeId() == expression_id;
  1204. if (selected)
  1205. {
  1206. current_rung_id_ = node->rungId();
  1207. ensureVisible(node);
  1208. }
  1209. }
  1210. else if (WireItem *wire = dynamic_cast<WireItem *>(item))
  1211. {
  1212. selected = wire->expressionId() == expression_id;
  1213. if (selected)
  1214. {
  1215. current_rung_id_ = wire->rungId();
  1216. ensureVisible(wire);
  1217. }
  1218. }
  1219. item->setSelected(selected);
  1220. }
  1221. }
  1222. void LogicEditorWidget::selectWire(const std::string &wire_id)
  1223. {
  1224. selectExpression(wire_id);
  1225. }
  1226. std::string LogicEditorWidget::selectedNodeId() const
  1227. {
  1228. const std::vector<std::string> ids = selectedNodeIds();
  1229. return ids.size() == 1U ? ids.front() : std::string{};
  1230. }
  1231. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  1232. {
  1233. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1234. for (QGraphicsItem *item : scene_->selectedItems())
  1235. {
  1236. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1237. {
  1238. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1239. }
  1240. }
  1241. std::sort(
  1242. positioned_ids.begin(), positioned_ids.end(),
  1243. [](const auto &left, const auto &right)
  1244. {
  1245. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1246. {
  1247. return left.first.y() < right.first.y();
  1248. }
  1249. return left.first.x() < right.first.x();
  1250. });
  1251. std::vector<std::string> ids;
  1252. ids.reserve(positioned_ids.size());
  1253. for (const auto &positioned_id : positioned_ids)
  1254. {
  1255. ids.push_back(positioned_id.second);
  1256. }
  1257. return ids;
  1258. }
  1259. std::vector<std::string> LogicEditorWidget::selectedExpressionIds() const
  1260. {
  1261. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1262. for (QGraphicsItem *item : scene_->selectedItems())
  1263. {
  1264. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1265. {
  1266. if (node->isConditionNode())
  1267. {
  1268. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1269. }
  1270. }
  1271. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1272. {
  1273. positioned_ids.emplace_back(wire->scenePos(), wire->expressionId());
  1274. }
  1275. else if (const WireCellItem *cell =
  1276. dynamic_cast<const WireCellItem *>(item))
  1277. {
  1278. positioned_ids.emplace_back(cell->scenePos(), cell->expressionId());
  1279. }
  1280. }
  1281. std::sort(
  1282. positioned_ids.begin(), positioned_ids.end(),
  1283. [](const auto &left, const auto &right)
  1284. {
  1285. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1286. {
  1287. return left.first.y() < right.first.y();
  1288. }
  1289. return left.first.x() < right.first.x();
  1290. });
  1291. std::vector<std::string> ids;
  1292. ids.reserve(positioned_ids.size());
  1293. for (const auto &positioned_id : positioned_ids)
  1294. {
  1295. if (std::find(ids.cbegin(), ids.cend(), positioned_id.second) == ids.cend())
  1296. {
  1297. ids.push_back(positioned_id.second);
  1298. }
  1299. }
  1300. return ids;
  1301. }
  1302. std::vector<std::string> LogicEditorWidget::selectedWireIds() const
  1303. {
  1304. std::vector<std::string> ids;
  1305. for (QGraphicsItem *item : scene_->selectedItems())
  1306. {
  1307. if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1308. {
  1309. if (std::find(ids.cbegin(), ids.cend(), wire->expressionId()) == ids.cend())
  1310. {
  1311. ids.push_back(wire->expressionId());
  1312. }
  1313. }
  1314. else if (const WireCellItem *cell =
  1315. dynamic_cast<const WireCellItem *>(item))
  1316. {
  1317. if (std::find(ids.cbegin(), ids.cend(), cell->expressionId()) == ids.cend())
  1318. {
  1319. ids.push_back(cell->expressionId());
  1320. }
  1321. }
  1322. }
  1323. return ids;
  1324. }
  1325. std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedEmptySlots() const
  1326. {
  1327. std::vector<std::pair<std::string, int>> targets;
  1328. for (QGraphicsItem *item : scene_->selectedItems())
  1329. {
  1330. if (const EmptySlotItem *slot = dynamic_cast<const EmptySlotItem *>(item))
  1331. {
  1332. targets.emplace_back(slot->branchExpressionId(), slot->column());
  1333. }
  1334. }
  1335. return targets;
  1336. }
  1337. std::vector<std::pair<std::string, int>> LogicEditorWidget::selectedWireCells() const
  1338. {
  1339. std::vector<std::pair<std::string, int>> cells;
  1340. for (QGraphicsItem *item : scene_->selectedItems())
  1341. {
  1342. if (const WireCellItem *cell = dynamic_cast<const WireCellItem *>(item))
  1343. {
  1344. cells.emplace_back(cell->expressionId(), cell->columnOffset());
  1345. }
  1346. }
  1347. return cells;
  1348. }
  1349. std::vector<std::string> LogicEditorWidget::selectedBranchIds() const
  1350. {
  1351. std::vector<std::string> ids;
  1352. for (QGraphicsItem *item : scene_->selectedItems())
  1353. {
  1354. if (const VerticalConnectorItem *connector =
  1355. dynamic_cast<const VerticalConnectorItem *>(item))
  1356. {
  1357. if (std::find(ids.cbegin(), ids.cend(), connector->branchExpressionId())
  1358. == ids.cend())
  1359. {
  1360. ids.push_back(connector->branchExpressionId());
  1361. }
  1362. }
  1363. }
  1364. return ids;
  1365. }
  1366. std::string LogicEditorWidget::selectedRungId() const
  1367. {
  1368. std::string rung_id;
  1369. for (QGraphicsItem *item : scene_->selectedItems())
  1370. {
  1371. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1372. {
  1373. if (!rung_id.empty() && rung_id != node->rungId())
  1374. {
  1375. return {};
  1376. }
  1377. rung_id = node->rungId();
  1378. }
  1379. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1380. {
  1381. if (!rung_id.empty() && rung_id != wire->rungId())
  1382. {
  1383. return {};
  1384. }
  1385. rung_id = wire->rungId();
  1386. }
  1387. else if (const WireCellItem *cell =
  1388. dynamic_cast<const WireCellItem *>(item))
  1389. {
  1390. if (!rung_id.empty() && rung_id != cell->rungId())
  1391. {
  1392. return {};
  1393. }
  1394. rung_id = cell->rungId();
  1395. }
  1396. else if (const VerticalConnectorItem *connector =
  1397. dynamic_cast<const VerticalConnectorItem *>(item))
  1398. {
  1399. if (!rung_id.empty() && rung_id != connector->rungId())
  1400. {
  1401. return {};
  1402. }
  1403. rung_id = connector->rungId();
  1404. }
  1405. else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
  1406. {
  1407. if (rung_id.empty())
  1408. {
  1409. rung_id = rung->rungId();
  1410. }
  1411. }
  1412. else if (const EmptySlotItem *slot =
  1413. dynamic_cast<const EmptySlotItem *>(item))
  1414. {
  1415. if (!rung_id.empty() && rung_id != slot->rungId())
  1416. {
  1417. return {};
  1418. }
  1419. rung_id = slot->rungId();
  1420. }
  1421. }
  1422. return rung_id;
  1423. }
  1424. bool LogicEditorWidget::hasSelectedRungItem() const
  1425. {
  1426. for (QGraphicsItem *item : scene_->selectedItems())
  1427. {
  1428. if (dynamic_cast<const RungItem *>(item) != nullptr)
  1429. {
  1430. return true;
  1431. }
  1432. }
  1433. return false;
  1434. }
  1435. LogicEditorResult LogicEditorWidget::addRung()
  1436. {
  1437. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  1438. if (result.succeeded)
  1439. {
  1440. current_rung_id_ = result.id;
  1441. reloadLogic();
  1442. emit graphChanged();
  1443. }
  1444. else
  1445. {
  1446. reportFailure(result);
  1447. }
  1448. return result;
  1449. }
  1450. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  1451. {
  1452. const std::vector<std::pair<std::string, int>> selected_empty_slots =
  1453. selectedEmptySlots();
  1454. const std::vector<std::pair<std::string, int>> selected_wire_cells =
  1455. selectedWireCells();
  1456. const std::vector<std::string> selected_expressions = selectedExpressionIds();
  1457. const std::vector<std::string> selected_wires = selectedWireIds();
  1458. const std::vector<std::string> selected_ids = selectedNodeIds();
  1459. LogicEditorResult result;
  1460. if (selected_empty_slots.size() > 1U || selected_wire_cells.size() > 1U
  1461. || (!selected_empty_slots.empty()
  1462. && (!selected_wire_cells.empty()
  1463. || !selected_expressions.empty() || !selected_ids.empty()))
  1464. || (!selected_wire_cells.empty()
  1465. && (!selected_ids.empty() || selected_expressions.size() > 1U)))
  1466. {
  1467. result = {false, LogicEditorError::InvalidOperation,
  1468. "插入条件时只能选择一个网格或条件对象", {}};
  1469. }
  1470. else if (selected_empty_slots.size() == 1U)
  1471. {
  1472. const auto &slot = selected_empty_slots.front();
  1473. result = slot.first.empty()
  1474. ? editor_service_.insertConditionAtColumn(
  1475. logic_id_, currentRungId(), slot.second, config)
  1476. : editor_service_.insertConditionInBranchAtColumn(
  1477. logic_id_, currentRungId(), slot.first, slot.second, config);
  1478. }
  1479. else if (selected_wire_cells.size() == 1U)
  1480. {
  1481. result = editor_service_.replaceWireColumnWithCondition(
  1482. logic_id_,
  1483. currentRungId(),
  1484. selected_wire_cells.front().first,
  1485. selected_wire_cells.front().second,
  1486. config);
  1487. }
  1488. else if (selected_expressions.size() > 1U || selected_wires.size() > 1U)
  1489. {
  1490. result = {false, LogicEditorError::InvalidOperation,
  1491. "串联插入或替换横线时只能选择一个条件对象", {}};
  1492. }
  1493. else if (selected_wires.size() == 1U)
  1494. {
  1495. result = editor_service_.replaceWireWithCondition(
  1496. logic_id_, currentRungId(), selected_wires.front(), config);
  1497. }
  1498. else if (selected_ids.size() == 1U)
  1499. {
  1500. const LogicNode *selected_node = editor_service_.findNode(
  1501. logic_id_, selected_ids.front());
  1502. result = selected_node != nullptr && selected_node->isCondition()
  1503. ? editor_service_.insertConditionAfter(
  1504. logic_id_, currentRungId(), selected_ids.front(), config)
  1505. : editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1506. }
  1507. else
  1508. {
  1509. result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1510. }
  1511. if (result.succeeded)
  1512. {
  1513. reloadLogic();
  1514. selectExpression(result.id);
  1515. emit graphChanged();
  1516. }
  1517. else
  1518. {
  1519. reportFailure(result);
  1520. }
  1521. return result;
  1522. }
  1523. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  1524. {
  1525. const std::string rung_id = selectedRungId();
  1526. const std::vector<std::string> selected_ids = selectedNodeIds();
  1527. std::vector<std::string> condition_ids;
  1528. for (const std::string &node_id : selected_ids)
  1529. {
  1530. const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
  1531. if (node != nullptr && node->isCondition())
  1532. {
  1533. condition_ids.push_back(node_id);
  1534. }
  1535. }
  1536. LogicEditorResult result;
  1537. if (rung_id.empty() || condition_ids.empty())
  1538. {
  1539. result = {false, LogicEditorError::InvalidOperation,
  1540. "请在同一网络中选择要并联的连续节点", {}};
  1541. }
  1542. else
  1543. {
  1544. result = editor_service_.addParallelBranch(
  1545. logic_id_, rung_id, condition_ids, config);
  1546. }
  1547. if (result.succeeded)
  1548. {
  1549. reloadLogic();
  1550. selectExpression(result.id);
  1551. emit graphChanged();
  1552. }
  1553. else
  1554. {
  1555. reportFailure(result);
  1556. }
  1557. return result;
  1558. }
  1559. LogicEditorResult LogicEditorWidget::addHorizontalWire()
  1560. {
  1561. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1562. LogicEditorResult result;
  1563. if (selected_ids.size() > 1U)
  1564. {
  1565. result = {false, LogicEditorError::InvalidOperation,
  1566. "插入横线时只能选择一个条件对象", {}};
  1567. }
  1568. else if (selected_ids.size() == 1U)
  1569. {
  1570. result = editor_service_.insertWireAfter(
  1571. logic_id_, currentRungId(), selected_ids.front());
  1572. }
  1573. else
  1574. {
  1575. result = editor_service_.appendWire(logic_id_, currentRungId());
  1576. }
  1577. if (result.succeeded)
  1578. {
  1579. reloadLogic();
  1580. selectWire(result.id);
  1581. emit graphChanged();
  1582. }
  1583. else
  1584. {
  1585. reportFailure(result);
  1586. }
  1587. return result;
  1588. }
  1589. LogicEditorResult LogicEditorWidget::addVerticalWire()
  1590. {
  1591. const std::string rung_id = selectedRungId();
  1592. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1593. LogicEditorResult result;
  1594. if (rung_id.empty() || selected_ids.empty())
  1595. {
  1596. result = {false, LogicEditorError::InvalidOperation,
  1597. "请在同一网络中选择要连接的连续条件或横线", {}};
  1598. }
  1599. else
  1600. {
  1601. result = editor_service_.addParallelWireBranch(
  1602. logic_id_, rung_id, selected_ids);
  1603. }
  1604. if (result.succeeded)
  1605. {
  1606. reloadLogic();
  1607. selectWire(result.id);
  1608. emit graphChanged();
  1609. }
  1610. else
  1611. {
  1612. reportFailure(result);
  1613. }
  1614. return result;
  1615. }
  1616. LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
  1617. {
  1618. const std::vector<std::string> wire_ids = selectedWireIds();
  1619. const std::string rung_id = selectedRungId();
  1620. if (wire_ids.empty() || rung_id.empty())
  1621. {
  1622. const LogicEditorResult result = {
  1623. false,
  1624. LogicEditorError::InvalidOperation,
  1625. "请先选择要删除的横线",
  1626. {}};
  1627. reportFailure(result);
  1628. return result;
  1629. }
  1630. const LogicEditorResult result = editor_service_.removeExpressions(
  1631. logic_id_, rung_id, wire_ids);
  1632. if (result.succeeded)
  1633. {
  1634. reloadLogic();
  1635. emit nodeSelected({});
  1636. emit graphChanged();
  1637. }
  1638. else
  1639. {
  1640. reportFailure(result);
  1641. }
  1642. return result;
  1643. }
  1644. LogicEditorResult LogicEditorWidget::deleteVerticalWire()
  1645. {
  1646. const std::vector<std::string> branch_ids = selectedBranchIds();
  1647. if (branch_ids.empty())
  1648. {
  1649. const LogicEditorResult result = {
  1650. false,
  1651. LogicEditorError::InvalidOperation,
  1652. "请先选择要删除的竖线连接",
  1653. {}};
  1654. reportFailure(result);
  1655. return result;
  1656. }
  1657. const std::string rung_id = selectedRungId();
  1658. LogicEditorResult result;
  1659. if (rung_id.empty())
  1660. {
  1661. result = {false, LogicEditorError::InvalidOperation,
  1662. "竖线连接必须位于同一网络", {}};
  1663. }
  1664. else
  1665. {
  1666. result = editor_service_.removeExpressions(
  1667. logic_id_, rung_id, branch_ids);
  1668. }
  1669. if (result.succeeded)
  1670. {
  1671. reloadLogic();
  1672. emit nodeSelected({});
  1673. emit graphChanged();
  1674. }
  1675. else
  1676. {
  1677. reportFailure(result);
  1678. }
  1679. return result;
  1680. }
  1681. LogicEditorResult LogicEditorWidget::setOutput(
  1682. const LogicNodeConfig &config, bool configured)
  1683. {
  1684. const LogicEditorResult result = editor_service_.setOutput(
  1685. logic_id_, currentRungId(), config, configured);
  1686. if (result.succeeded)
  1687. {
  1688. reloadLogic();
  1689. selectNode(result.id);
  1690. emit graphChanged();
  1691. }
  1692. else
  1693. {
  1694. reportFailure(result);
  1695. }
  1696. return result;
  1697. }
  1698. LogicEditorResult LogicEditorWidget::deleteSelected()
  1699. {
  1700. const std::vector<std::string> expression_ids = selectedExpressionIds();
  1701. const std::vector<std::string> wire_ids = selectedWireIds();
  1702. const std::vector<std::string> branch_ids = selectedBranchIds();
  1703. const std::vector<std::string> node_ids = selectedNodeIds();
  1704. LogicEditorResult result;
  1705. if (!branch_ids.empty())
  1706. {
  1707. result = editor_service_.removeExpressions(
  1708. logic_id_, selectedRungId(), branch_ids);
  1709. }
  1710. else if (!wire_ids.empty())
  1711. {
  1712. bool output_selected = false;
  1713. for (QGraphicsItem *item : scene_->selectedItems())
  1714. {
  1715. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1716. {
  1717. output_selected = output_selected || !node->isConditionNode();
  1718. }
  1719. }
  1720. if (output_selected)
  1721. {
  1722. result = {false, LogicEditorError::InvalidOperation,
  1723. "不能同时删除横线和输出节点", {}};
  1724. }
  1725. else
  1726. {
  1727. result = editor_service_.removeExpressions(
  1728. logic_id_, selectedRungId(), expression_ids);
  1729. }
  1730. }
  1731. else if (!node_ids.empty())
  1732. {
  1733. result = editor_service_.removeNodes(logic_id_, node_ids);
  1734. }
  1735. else if (hasSelectedRungItem())
  1736. {
  1737. const std::string rung_id = selectedRungId();
  1738. result = editor_service_.removeRung(logic_id_, rung_id);
  1739. if (result.succeeded && current_rung_id_ == rung_id)
  1740. {
  1741. current_rung_id_ = editor_service_.firstRungId(logic_id_);
  1742. }
  1743. }
  1744. else
  1745. {
  1746. result = {false, LogicEditorError::InvalidOperation,
  1747. "请先选择要删除的逻辑节点或网络", {}};
  1748. }
  1749. if (result.succeeded)
  1750. {
  1751. reloadLogic();
  1752. emit nodeSelected({});
  1753. emit graphChanged();
  1754. }
  1755. else
  1756. {
  1757. reportFailure(result);
  1758. }
  1759. return result;
  1760. }
  1761. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  1762. {
  1763. QGraphicsView::resizeEvent(event);
  1764. }
  1765. void LogicEditorWidget::handleSelectionChanged()
  1766. {
  1767. const std::string rung_id = selectedRungId();
  1768. if (!rung_id.empty())
  1769. {
  1770. current_rung_id_ = rung_id;
  1771. }
  1772. emit nodeSelected(QString::fromStdString(selectedNodeId()));
  1773. }
  1774. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  1775. {
  1776. emit editorError(QString::fromStdString(result.message));
  1777. }
  1778. std::string LogicEditorWidget::currentRungId() const
  1779. {
  1780. const std::string selected = selectedRungId();
  1781. if (!selected.empty())
  1782. {
  1783. return selected;
  1784. }
  1785. return current_rung_id_.empty()
  1786. ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
  1787. }