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

1171 строка
41 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,
  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. ExpressionMetrics metrics{0, 0};
  172. if (expression.kind == ConditionExpressionKind::Series)
  173. {
  174. for (const ConditionExpression &child : expression.children)
  175. {
  176. const ExpressionMetrics child_metrics = measureExpression(child);
  177. metrics.columns += child_metrics.columns;
  178. metrics.rows = std::max(metrics.rows, child_metrics.rows);
  179. }
  180. }
  181. else
  182. {
  183. for (const ConditionExpression &child : expression.children)
  184. {
  185. const ExpressionMetrics child_metrics = measureExpression(child);
  186. metrics.columns = std::max(metrics.columns, child_metrics.columns);
  187. metrics.rows += child_metrics.rows;
  188. }
  189. }
  190. return metrics;
  191. }
  192. QPen ladderPen(bool active)
  193. {
  194. return QPen(active ? kActiveColor : kLadderColor,
  195. active ? 2.6 : kLadderLineWidth);
  196. }
  197. bool traceValue(
  198. const LogicTraceSnapshot &trace,
  199. const std::unordered_map<std::string, bool> LogicTraceSnapshot::*member,
  200. const std::string &id)
  201. {
  202. const auto &values = trace.*member;
  203. const auto found = values.find(id);
  204. return found != values.end() && found->second;
  205. }
  206. void addGrid(
  207. QGraphicsScene &scene,
  208. qreal left,
  209. qreal top,
  210. int columns,
  211. int rows)
  212. {
  213. QPen pen(kGridColor, 1.0);
  214. pen.setCosmetic(true);
  215. for (int column = 0; column <= columns; ++column)
  216. {
  217. const qreal x = left + static_cast<qreal>(column) * kCellWidth;
  218. QGraphicsLineItem *line = scene.addLine(
  219. QLineF(x, top, x, top + static_cast<qreal>(rows) * kCellHeight), pen);
  220. line->setZValue(-10.0);
  221. }
  222. for (int row = 0; row <= rows; ++row)
  223. {
  224. const qreal y = top + static_cast<qreal>(row) * kCellHeight;
  225. QGraphicsLineItem *line = scene.addLine(
  226. QLineF(left, y, left + static_cast<qreal>(columns) * kCellWidth, y), pen);
  227. line->setZValue(-10.0);
  228. }
  229. }
  230. void addPlaceholder(QGraphicsScene &scene, const QRectF &cell, const QString &text)
  231. {
  232. const QRectF bounds = cell.adjusted(12, 18, -12, -18);
  233. QGraphicsRectItem *item = scene.addRect(
  234. bounds,
  235. QPen(kPlaceholderColor, 1.2, Qt::DashLine),
  236. QBrush(QColor(255, 255, 255, 220)));
  237. item->setZValue(1.0);
  238. QGraphicsTextItem *label = scene.addText(text);
  239. label->setDefaultTextColor(kPlaceholderColor);
  240. label->setPos(
  241. bounds.center().x() - label->boundingRect().width() / 2.0,
  242. bounds.center().y() - label->boundingRect().height() / 2.0);
  243. label->setZValue(2.0);
  244. }
  245. } // namespace
  246. class LogicEditorWidget::NodeItem final : public QGraphicsItem
  247. {
  248. public:
  249. NodeItem(
  250. const LogicNode &node,
  251. const std::string &rung_id,
  252. const QPointF &center,
  253. const QString &register_comment,
  254. bool active,
  255. bool faulted)
  256. : node_id_(node.id),
  257. rung_id_(rung_id),
  258. config_(node.config),
  259. register_comment_(register_comment),
  260. configured_(node.configured),
  261. active_(active),
  262. faulted_(faulted)
  263. {
  264. setPos(center);
  265. setFlag(ItemIsSelectable, true);
  266. setZValue(3.0);
  267. QString tool_tip = configured_
  268. ? nodeToolTip(config_)
  269. : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数");
  270. if (!register_comment_.isEmpty())
  271. {
  272. tool_tip += LogicEditorWidget::tr("\n注释:%1").arg(register_comment_);
  273. }
  274. setToolTip(tool_tip);
  275. }
  276. QRectF boundingRect() const override
  277. {
  278. return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
  279. }
  280. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  281. {
  282. painter->setRenderHint(QPainter::Antialiasing, true);
  283. const bool selected = (option->state & QStyle::State_Selected) != 0;
  284. if (selected)
  285. {
  286. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  287. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  288. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  289. }
  290. const QColor symbol_color = faulted_ ? kFaultColor
  291. : selected ? kSelectionBorderColor : active_ ? kActiveColor : kLadderColor;
  292. painter->setPen(QPen(symbol_color, active_ || faulted_ ? 2.6 : kLadderLineWidth));
  293. QFont font = painter->font();
  294. font.setPointSizeF(9.5);
  295. painter->setFont(font);
  296. if (const auto *contact = std::get_if<ContactNodeConfig>(&config_))
  297. {
  298. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  299. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  300. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  301. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  302. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  303. if (contact->mode == ContactMode::NormallyClosed)
  304. {
  305. painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
  306. }
  307. painter->drawText(
  308. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  309. Qt::AlignCenter,
  310. configured_ ? registerAddressText(contact->address) : tr("< M 地址 >"));
  311. drawRegisterComment(painter, register_comment_);
  312. }
  313. else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&config_))
  314. {
  315. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  316. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  317. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  318. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  319. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  320. painter->drawText(
  321. QRectF(-14, -12, 28, 24),
  322. Qt::AlignCenter,
  323. edge->mode == EdgeMode::Rising ? QStringLiteral("P") : QStringLiteral("N"));
  324. painter->drawText(
  325. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  326. Qt::AlignCenter,
  327. configured_ ? registerAddressText(edge->address) : tr("< M 地址 >"));
  328. drawRegisterComment(painter, register_comment_);
  329. }
  330. else if (const auto *timer_contact =
  331. std::get_if<TimerContactNodeConfig>(&config_))
  332. {
  333. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  334. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  335. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  336. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  337. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  338. if (timer_contact->mode == ContactMode::NormallyClosed)
  339. {
  340. painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
  341. }
  342. painter->drawText(
  343. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  344. Qt::AlignCenter,
  345. configured_ ? timerAddressText(timer_contact->address) : tr("< T 地址 >"));
  346. }
  347. else if (const auto *counter_contact =
  348. std::get_if<CounterContactNodeConfig>(&config_))
  349. {
  350. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  351. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  352. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  353. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  354. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  355. if (counter_contact->mode == ContactMode::NormallyClosed)
  356. {
  357. painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
  358. }
  359. painter->drawText(
  360. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  361. Qt::AlignCenter,
  362. configured_ ? counterAddressText(counter_contact->address)
  363. : tr("< C 地址 >"));
  364. }
  365. else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_))
  366. {
  367. painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white);
  368. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0));
  369. painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0));
  370. QPainterPath left;
  371. left.moveTo(-2, -20);
  372. left.cubicTo(-24, -16, -24, 16, -2, 20);
  373. painter->drawPath(left);
  374. QPainterPath right;
  375. right.moveTo(2, -20);
  376. right.cubicTo(24, -16, 24, 16, 2, 20);
  377. painter->drawPath(right);
  378. if (coil->mode != CoilMode::Normal)
  379. {
  380. painter->drawText(
  381. QRectF(-12, -14, 24, 28),
  382. Qt::AlignCenter,
  383. coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R"));
  384. }
  385. painter->drawText(
  386. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  387. Qt::AlignCenter,
  388. configured_ ? registerAddressText(coil->address) : tr("< M 地址 >"));
  389. drawRegisterComment(painter, register_comment_);
  390. }
  391. else if (const auto *comparison = std::get_if<CompareNodeConfig>(&config_))
  392. {
  393. const QRectF box(-47, -17, 94, 34);
  394. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  395. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  396. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  397. painter->drawRect(box);
  398. painter->drawText(
  399. box, Qt::AlignCenter,
  400. QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison)));
  401. painter->drawText(
  402. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  403. Qt::AlignCenter,
  404. configured_ ? registerAddressText(comparison->address) : tr("< D 地址 >"));
  405. painter->drawText(
  406. QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
  407. Qt::AlignCenter,
  408. configured_ ? QString::number(comparison->value) : tr("< 常量 >"));
  409. drawRegisterComment(painter, register_comment_, 40.0);
  410. }
  411. else if (const auto *ton = std::get_if<TonNodeConfig>(&config_))
  412. {
  413. const QRectF box(-47, -18, 94, 36);
  414. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  415. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  416. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  417. painter->drawRect(box);
  418. painter->drawText(box, Qt::AlignCenter, QStringLiteral("TON"));
  419. painter->drawText(
  420. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  421. Qt::AlignCenter,
  422. configured_ ? timerAddressText(ton->address) : tr("< T 地址 >"));
  423. painter->drawText(
  424. QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
  425. Qt::AlignCenter,
  426. configured_ ? QStringLiteral("PT %1 ms").arg(ton->presetMs)
  427. : tr("< 预设时间 >"));
  428. }
  429. else if (const auto *counter = std::get_if<CounterNodeConfig>(&config_))
  430. {
  431. const QRectF box(-47, -18, 94, 36);
  432. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  433. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  434. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  435. painter->drawRect(box);
  436. painter->drawText(
  437. box,
  438. Qt::AlignCenter,
  439. counter->mode == CounterMode::Up
  440. ? QStringLiteral("CTU") : QStringLiteral("CTD"));
  441. painter->drawText(
  442. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  443. Qt::AlignCenter,
  444. configured_ ? counterAddressText(counter->address)
  445. : tr("< C 地址 >"));
  446. painter->drawText(
  447. QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
  448. Qt::AlignCenter,
  449. configured_ ? QStringLiteral("CV %1 / PV %2")
  450. .arg(registerAddressText(counter->currentValueAddress))
  451. .arg(wordOperandText(counter->preset))
  452. : tr("< CV / PV >"));
  453. }
  454. else if (const auto *move = std::get_if<MoveNodeConfig>(&config_))
  455. {
  456. const QRectF box(-47, -18, 94, 36);
  457. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  458. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  459. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  460. painter->drawRect(box);
  461. painter->drawText(box, Qt::AlignCenter, QStringLiteral("MOVE"));
  462. painter->drawText(
  463. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  464. Qt::AlignCenter,
  465. configured_ ? QStringLiteral("%1 -> %2")
  466. .arg(wordOperandText(move->source))
  467. .arg(registerAddressText(move->destination))
  468. : tr("< 源 -> 目标 >"));
  469. }
  470. else if (const auto *arithmetic =
  471. std::get_if<ArithmeticNodeConfig>(&config_))
  472. {
  473. const QRectF box(-47, -18, 94, 36);
  474. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  475. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  476. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  477. painter->drawRect(box);
  478. painter->drawText(
  479. box,
  480. Qt::AlignCenter,
  481. arithmetic->operation == ArithmeticOperation::Add
  482. ? QStringLiteral("ADD") : QStringLiteral("SUB"));
  483. painter->drawText(
  484. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  485. Qt::AlignCenter,
  486. configured_ ? QStringLiteral("%1,%2 -> %3")
  487. .arg(wordOperandText(arithmetic->left))
  488. .arg(wordOperandText(arithmetic->right))
  489. .arg(registerAddressText(arithmetic->destination))
  490. : tr("< 操作数 -> 目标 >"));
  491. }
  492. }
  493. const std::string &nodeId() const { return node_id_; }
  494. const std::string &rungId() const { return rung_id_; }
  495. private:
  496. std::string node_id_;
  497. std::string rung_id_;
  498. LogicNodeConfig config_;
  499. QString register_comment_;
  500. bool configured_ = true;
  501. bool active_ = false;
  502. bool faulted_ = false;
  503. };
  504. class LogicEditorWidget::RungItem final : public QGraphicsItem
  505. {
  506. public:
  507. RungItem(
  508. const LadderRung &rung,
  509. int number,
  510. qreal top,
  511. qreal height,
  512. qreal width,
  513. qreal cursor_left)
  514. : rung_id_(rung.id),
  515. name_(QString::fromStdString(rung.name)),
  516. comment_(QString::fromStdString(rung.comment)),
  517. number_(number),
  518. height_(height),
  519. width_(width),
  520. cursor_left_(cursor_left),
  521. show_cursor_(!rung.condition.has_value())
  522. {
  523. setPos(0, top);
  524. setFlag(ItemIsSelectable, true);
  525. setZValue(-2.0);
  526. if (!comment_.isEmpty())
  527. {
  528. setToolTip(comment_);
  529. }
  530. }
  531. QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; }
  532. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  533. {
  534. const bool selected = (option->state & QStyle::State_Selected) != 0;
  535. if (selected)
  536. {
  537. painter->fillRect(boundingRect(), QColor(240, 247, 250, 90));
  538. if (show_cursor_)
  539. {
  540. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  541. painter->drawRect(QRectF(
  542. cursor_left_ + 3,
  543. kRungHeaderHeight + 3,
  544. kCellWidth - 6,
  545. kCellHeight - 6));
  546. }
  547. }
  548. painter->setPen(QColor(QStringLiteral("#62717b")));
  549. QString title = tr("网络 %1").arg(number_);
  550. if (!name_.isEmpty() && !name_.startsWith(tr("网络 ")))
  551. {
  552. title += QStringLiteral(":") + name_;
  553. }
  554. painter->drawText(
  555. QRectF(kSceneMargin + 8, 5, 320, 22),
  556. Qt::AlignLeft | Qt::AlignVCenter,
  557. title);
  558. if (!comment_.isEmpty())
  559. {
  560. painter->setPen(QColor(QStringLiteral("#7b8790")));
  561. const QString visible_comment = painter->fontMetrics().elidedText(
  562. comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0));
  563. painter->drawText(
  564. QRectF(kSceneMargin + 8, 24, width_ - 16, 22),
  565. Qt::AlignLeft | Qt::AlignVCenter,
  566. visible_comment);
  567. }
  568. painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
  569. painter->drawLine(
  570. QPointF(kSceneMargin + 8, height_ - 1),
  571. QPointF(kSceneMargin + width_ - 8, height_ - 1));
  572. }
  573. const std::string &rungId() const { return rung_id_; }
  574. private:
  575. std::string rung_id_;
  576. QString name_;
  577. QString comment_;
  578. int number_ = 0;
  579. qreal height_ = 0.0;
  580. qreal width_ = 0.0;
  581. qreal cursor_left_ = 0.0;
  582. bool show_cursor_ = false;
  583. };
  584. namespace {
  585. RenderResult renderExpression(
  586. QGraphicsScene &scene,
  587. const LogicEditorService &editor_service,
  588. const ConditionExpression &expression,
  589. const std::string &rung_id,
  590. const QPointF &top_left,
  591. const ExpressionMetrics &metrics,
  592. const LogicTraceSnapshot &trace,
  593. bool trace_enabled,
  594. const std::string &fault_node_id)
  595. {
  596. const bool active = trace_enabled && traceValue(
  597. trace, &LogicTraceSnapshot::expressionValues, expression.id);
  598. if (expression.kind == ConditionExpressionKind::Node)
  599. {
  600. const QPointF center(
  601. top_left.x() + kCellWidth / 2.0,
  602. top_left.y() + kCellHeight / 2.0);
  603. const bool node_active = trace_enabled && traceValue(
  604. trace, &LogicTraceSnapshot::nodeValues, expression.node->id);
  605. scene.addLine(
  606. QLineF(
  607. QPointF(top_left.x(), center.y()),
  608. QPointF(top_left.x() + kCellWidth, center.y())),
  609. ladderPen(node_active));
  610. scene.addItem(new LogicEditorWidget::NodeItem(
  611. *expression.node,
  612. rung_id,
  613. center,
  614. [&editor_service, &expression]
  615. {
  616. const std::optional<RegisterAddress> address =
  617. registerAddressForLogicNode(expression.node->config);
  618. return address.has_value()
  619. ? QString::fromStdString(
  620. editor_service.registerCommentFor(*address))
  621. : QString{};
  622. }(),
  623. node_active,
  624. expression.node->id == fault_node_id));
  625. return {
  626. QPointF(top_left.x(), center.y()),
  627. QPointF(top_left.x() + kCellWidth, center.y())};
  628. }
  629. if (expression.kind == ConditionExpressionKind::Series)
  630. {
  631. qreal x = top_left.x();
  632. RenderResult first;
  633. RenderResult previous;
  634. for (std::size_t index = 0; index < expression.children.size(); ++index)
  635. {
  636. const ExpressionMetrics child_metrics = measureExpression(expression.children[index]);
  637. const RenderResult current = renderExpression(
  638. scene,
  639. editor_service,
  640. expression.children[index],
  641. rung_id,
  642. QPointF(x, top_left.y()),
  643. child_metrics,
  644. trace,
  645. trace_enabled,
  646. fault_node_id);
  647. if (index == 0U)
  648. {
  649. first = current;
  650. }
  651. else
  652. {
  653. scene.addLine(QLineF(previous.output, current.input), ladderPen(active));
  654. }
  655. previous = current;
  656. x += static_cast<qreal>(child_metrics.columns) * kCellWidth;
  657. }
  658. return {first.input, previous.output};
  659. }
  660. qreal y = top_left.y();
  661. std::vector<RenderResult> branches;
  662. branches.reserve(expression.children.size());
  663. for (const ConditionExpression &child : expression.children)
  664. {
  665. const ExpressionMetrics child_metrics = measureExpression(child);
  666. branches.push_back(renderExpression(
  667. scene,
  668. editor_service,
  669. child,
  670. rung_id,
  671. QPointF(top_left.x(), y),
  672. child_metrics,
  673. trace,
  674. trace_enabled,
  675. fault_node_id));
  676. y += static_cast<qreal>(child_metrics.rows) * kCellHeight;
  677. }
  678. const qreal left_join = top_left.x();
  679. const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth;
  680. const qreal top_y = branches.front().input.y();
  681. const qreal bottom_y = branches.back().input.y();
  682. scene.addLine(QLineF(left_join, top_y, left_join, bottom_y), ladderPen(active));
  683. scene.addLine(QLineF(right_join, top_y, right_join, bottom_y), ladderPen(active));
  684. for (std::size_t index = 0; index < branches.size(); ++index)
  685. {
  686. const bool branch_active = trace_enabled && traceValue(
  687. trace,
  688. &LogicTraceSnapshot::expressionValues,
  689. expression.children[index].id);
  690. scene.addLine(
  691. QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input),
  692. ladderPen(branch_active));
  693. scene.addLine(
  694. QLineF(branches[index].output, QPointF(right_join, branches[index].output.y())),
  695. ladderPen(branch_active));
  696. }
  697. return {QPointF(left_join, top_y), QPointF(right_join, top_y)};
  698. }
  699. } // namespace
  700. LogicEditorWidget::LogicEditorWidget(LogicEditorService &editor_service, QWidget *parent)
  701. : QGraphicsView(parent), editor_service_(editor_service)
  702. {
  703. scene_ = new QGraphicsScene(this);
  704. setScene(scene_);
  705. setRenderHint(QPainter::Antialiasing, true);
  706. setBackgroundBrush(Qt::white);
  707. setDragMode(QGraphicsView::RubberBandDrag);
  708. setAlignment(Qt::AlignLeft | Qt::AlignTop);
  709. connect(scene_, &QGraphicsScene::selectionChanged,
  710. this, &LogicEditorWidget::handleSelectionChanged);
  711. }
  712. void LogicEditorWidget::setLogicId(const std::string &logic_id)
  713. {
  714. if (logic_id_ == logic_id)
  715. {
  716. return;
  717. }
  718. logic_id_ = logic_id;
  719. current_rung_id_.clear();
  720. reloadLogic();
  721. }
  722. void LogicEditorWidget::setEditingEnabled(bool enabled)
  723. {
  724. editing_enabled_ = enabled;
  725. setInteractive(enabled);
  726. }
  727. void LogicEditorWidget::setRuntimeTrace(
  728. const LogicTraceSnapshot &trace, const std::string &fault_node_id)
  729. {
  730. trace_ = trace;
  731. fault_node_id_ = fault_node_id;
  732. runtime_trace_enabled_ = true;
  733. reloadLogic();
  734. }
  735. void LogicEditorWidget::clearRuntimeTrace()
  736. {
  737. trace_.clear();
  738. fault_node_id_.clear();
  739. runtime_trace_enabled_ = false;
  740. reloadLogic();
  741. }
  742. void LogicEditorWidget::reloadLogic()
  743. {
  744. scene_->clear();
  745. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  746. if (logic == nullptr)
  747. {
  748. scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
  749. return;
  750. }
  751. if (current_rung_id_.empty() && !logic->rungs.empty())
  752. {
  753. current_rung_id_ = logic->rungs.front().id;
  754. }
  755. int condition_columns = 1;
  756. for (const LadderRung &rung : logic->rungs)
  757. {
  758. if (rung.condition.has_value())
  759. {
  760. condition_columns = std::max(
  761. condition_columns,
  762. measureExpression(*rung.condition).columns);
  763. }
  764. }
  765. const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 2);
  766. const qreal left_rail_x = kSceneMargin + kRailInset;
  767. const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
  768. const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
  769. qreal top = kSceneMargin;
  770. int number = 1;
  771. for (const LadderRung &rung : logic->rungs)
  772. {
  773. const ExpressionMetrics metrics = rung.condition.has_value()
  774. ? measureExpression(*rung.condition) : ExpressionMetrics{};
  775. const int grid_rows = std::max(1, metrics.rows);
  776. const qreal grid_top = top + kRungHeaderHeight;
  777. const qreal rung_height = kRungHeaderHeight
  778. + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
  779. scene_->addItem(new RungItem(
  780. rung,
  781. number,
  782. top,
  783. rung_height,
  784. scene_width - 2.0 * kSceneMargin,
  785. left_rail_x));
  786. addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
  787. scene_->addLine(
  788. QLineF(left_rail_x, grid_top, left_rail_x,
  789. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  790. QPen(kLadderColor, 2.4));
  791. scene_->addLine(
  792. QLineF(right_rail_x, grid_top, right_rail_x,
  793. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  794. QPen(kLadderColor, 2.4));
  795. const qreal main_y = grid_top + kCellHeight / 2.0;
  796. QPointF expression_output(left_rail_x, main_y);
  797. if (rung.condition.has_value())
  798. {
  799. const RenderResult rendered = renderExpression(
  800. *scene_,
  801. editor_service_,
  802. *rung.condition,
  803. rung.id,
  804. QPointF(left_rail_x, grid_top),
  805. metrics,
  806. trace_,
  807. runtime_trace_enabled_,
  808. fault_node_id_);
  809. expression_output = rendered.output;
  810. }
  811. else
  812. {
  813. addPlaceholder(
  814. *scene_,
  815. QRectF(left_rail_x, grid_top, kCellWidth, kCellHeight),
  816. tr("添加条件"));
  817. }
  818. const bool rung_active = runtime_trace_enabled_ && traceValue(
  819. trace_, &LogicTraceSnapshot::rungValues, rung.id);
  820. const qreal output_left = right_rail_x - kCellWidth;
  821. scene_->addLine(
  822. QLineF(expression_output, QPointF(output_left, main_y)),
  823. ladderPen(rung_active));
  824. if (rung.output.has_value())
  825. {
  826. scene_->addLine(
  827. QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
  828. ladderPen(rung_active));
  829. scene_->addItem(new NodeItem(
  830. *rung.output,
  831. rung.id,
  832. QPointF(output_left + kCellWidth / 2.0, main_y),
  833. [&rung, this]
  834. {
  835. const std::optional<RegisterAddress> address =
  836. registerAddressForLogicNode(rung.output->config);
  837. return address.has_value()
  838. ? QString::fromStdString(
  839. editor_service_.registerCommentFor(*address))
  840. : QString{};
  841. }(),
  842. [&rung, this, rung_active]
  843. {
  844. return (std::holds_alternative<TonNodeConfig>(rung.output->config)
  845. || std::holds_alternative<CounterNodeConfig>(
  846. rung.output->config))
  847. ? traceValue(
  848. trace_,
  849. &LogicTraceSnapshot::nodeValues,
  850. rung.output->id)
  851. : rung_active;
  852. }(),
  853. rung.output->id == fault_node_id_));
  854. }
  855. else
  856. {
  857. addPlaceholder(
  858. *scene_,
  859. QRectF(output_left, grid_top, kCellWidth, kCellHeight),
  860. tr("输出线圈"));
  861. scene_->addLine(
  862. QLineF(
  863. QPointF(output_left + kCellWidth, main_y),
  864. QPointF(right_rail_x, main_y)),
  865. ladderPen(rung_active));
  866. }
  867. top += rung_height + kRungGap;
  868. ++number;
  869. }
  870. scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
  871. }
  872. void LogicEditorWidget::selectNode(const std::string &node_id)
  873. {
  874. for (QGraphicsItem *item : scene_->items())
  875. {
  876. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  877. {
  878. node->setSelected(node->nodeId() == node_id);
  879. if (node->nodeId() == node_id)
  880. {
  881. current_rung_id_ = node->rungId();
  882. ensureVisible(node);
  883. }
  884. }
  885. else
  886. {
  887. item->setSelected(false);
  888. }
  889. }
  890. }
  891. std::string LogicEditorWidget::selectedNodeId() const
  892. {
  893. const std::vector<std::string> ids = selectedNodeIds();
  894. return ids.size() == 1U ? ids.front() : std::string{};
  895. }
  896. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  897. {
  898. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  899. for (QGraphicsItem *item : scene_->selectedItems())
  900. {
  901. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  902. {
  903. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  904. }
  905. }
  906. std::sort(
  907. positioned_ids.begin(), positioned_ids.end(),
  908. [](const auto &left, const auto &right)
  909. {
  910. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  911. {
  912. return left.first.y() < right.first.y();
  913. }
  914. return left.first.x() < right.first.x();
  915. });
  916. std::vector<std::string> ids;
  917. ids.reserve(positioned_ids.size());
  918. for (const auto &positioned_id : positioned_ids)
  919. {
  920. ids.push_back(positioned_id.second);
  921. }
  922. return ids;
  923. }
  924. std::string LogicEditorWidget::selectedRungId() const
  925. {
  926. std::string rung_id;
  927. for (QGraphicsItem *item : scene_->selectedItems())
  928. {
  929. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  930. {
  931. if (!rung_id.empty() && rung_id != node->rungId())
  932. {
  933. return {};
  934. }
  935. rung_id = node->rungId();
  936. }
  937. else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
  938. {
  939. if (rung_id.empty())
  940. {
  941. rung_id = rung->rungId();
  942. }
  943. }
  944. }
  945. return rung_id;
  946. }
  947. LogicEditorResult LogicEditorWidget::addRung()
  948. {
  949. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  950. if (result.succeeded)
  951. {
  952. current_rung_id_ = result.id;
  953. reloadLogic();
  954. emit graphChanged();
  955. }
  956. else
  957. {
  958. reportFailure(result);
  959. }
  960. return result;
  961. }
  962. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  963. {
  964. const std::vector<std::string> selected_ids = selectedNodeIds();
  965. LogicEditorResult result;
  966. if (selected_ids.size() > 1U)
  967. {
  968. result = {false, LogicEditorError::InvalidOperation,
  969. "串联插入时只能选择一个节点", {}};
  970. }
  971. else if (selected_ids.size() == 1U)
  972. {
  973. const LogicNode *selected_node = editor_service_.findNode(
  974. logic_id_, selected_ids.front());
  975. result = selected_node != nullptr && selected_node->isCondition()
  976. ? editor_service_.insertConditionAfter(
  977. logic_id_, currentRungId(), selected_ids.front(), config)
  978. : editor_service_.appendCondition(logic_id_, currentRungId(), config);
  979. }
  980. else
  981. {
  982. result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
  983. }
  984. if (result.succeeded)
  985. {
  986. reloadLogic();
  987. selectNode(result.id);
  988. emit graphChanged();
  989. }
  990. else
  991. {
  992. reportFailure(result);
  993. }
  994. return result;
  995. }
  996. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  997. {
  998. const std::string rung_id = selectedRungId();
  999. const std::vector<std::string> selected_ids = selectedNodeIds();
  1000. std::vector<std::string> condition_ids;
  1001. for (const std::string &node_id : selected_ids)
  1002. {
  1003. const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
  1004. if (node != nullptr && node->isCondition())
  1005. {
  1006. condition_ids.push_back(node_id);
  1007. }
  1008. }
  1009. LogicEditorResult result;
  1010. if (rung_id.empty() || condition_ids.empty())
  1011. {
  1012. result = {false, LogicEditorError::InvalidOperation,
  1013. "请在同一网络中选择要并联的连续节点", {}};
  1014. }
  1015. else
  1016. {
  1017. result = editor_service_.addParallelBranch(
  1018. logic_id_, rung_id, condition_ids, config);
  1019. }
  1020. if (result.succeeded)
  1021. {
  1022. reloadLogic();
  1023. selectNode(result.id);
  1024. emit graphChanged();
  1025. }
  1026. else
  1027. {
  1028. reportFailure(result);
  1029. }
  1030. return result;
  1031. }
  1032. LogicEditorResult LogicEditorWidget::setOutput(
  1033. const LogicNodeConfig &config, bool configured)
  1034. {
  1035. const LogicEditorResult result = editor_service_.setOutput(
  1036. logic_id_, currentRungId(), config, configured);
  1037. if (result.succeeded)
  1038. {
  1039. reloadLogic();
  1040. selectNode(result.id);
  1041. emit graphChanged();
  1042. }
  1043. else
  1044. {
  1045. reportFailure(result);
  1046. }
  1047. return result;
  1048. }
  1049. LogicEditorResult LogicEditorWidget::deleteSelected()
  1050. {
  1051. const std::vector<std::string> node_ids = selectedNodeIds();
  1052. LogicEditorResult result;
  1053. if (!node_ids.empty())
  1054. {
  1055. result = editor_service_.removeNodes(logic_id_, node_ids);
  1056. }
  1057. else
  1058. {
  1059. const std::string rung_id = selectedRungId();
  1060. if (rung_id.empty())
  1061. {
  1062. return {false, LogicEditorError::InvalidOperation,
  1063. "请先选择要删除的逻辑节点或网络", {}};
  1064. }
  1065. result = editor_service_.removeRung(logic_id_, rung_id);
  1066. if (result.succeeded && current_rung_id_ == rung_id)
  1067. {
  1068. current_rung_id_ = editor_service_.firstRungId(logic_id_);
  1069. }
  1070. }
  1071. if (result.succeeded)
  1072. {
  1073. reloadLogic();
  1074. emit nodeSelected({});
  1075. emit graphChanged();
  1076. }
  1077. else
  1078. {
  1079. reportFailure(result);
  1080. }
  1081. return result;
  1082. }
  1083. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  1084. {
  1085. QGraphicsView::resizeEvent(event);
  1086. }
  1087. void LogicEditorWidget::handleSelectionChanged()
  1088. {
  1089. const std::string rung_id = selectedRungId();
  1090. if (!rung_id.empty())
  1091. {
  1092. current_rung_id_ = rung_id;
  1093. }
  1094. emit nodeSelected(QString::fromStdString(selectedNodeId()));
  1095. }
  1096. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  1097. {
  1098. emit editorError(QString::fromStdString(result.message));
  1099. }
  1100. std::string LogicEditorWidget::currentRungId() const
  1101. {
  1102. const std::string selected = selectedRungId();
  1103. if (!selected.empty())
  1104. {
  1105. return selected;
  1106. }
  1107. return current_rung_id_.empty()
  1108. ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
  1109. }