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

1606 строки
54 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. 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, 22, kCellWidth, 18),
  454. Qt::AlignCenter,
  455. configured_ ? QStringLiteral("CV %1 / PV %2")
  456. .arg(registerAddressText(counter->currentValueAddress))
  457. .arg(wordOperandText(counter->preset))
  458. : tr("< CV / PV >"));
  459. }
  460. else if (const auto *move = std::get_if<MoveNodeConfig>(&config_))
  461. {
  462. const QRectF box(-47, -18, 94, 36);
  463. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  464. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  465. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  466. painter->drawRect(box);
  467. painter->drawText(box, Qt::AlignCenter, QStringLiteral("MOVE"));
  468. painter->drawText(
  469. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  470. Qt::AlignCenter,
  471. configured_ ? QStringLiteral("%1 -> %2")
  472. .arg(wordOperandText(move->source))
  473. .arg(registerAddressText(move->destination))
  474. : tr("< 源 -> 目标 >"));
  475. }
  476. else if (const auto *arithmetic =
  477. std::get_if<ArithmeticNodeConfig>(&config_))
  478. {
  479. const QRectF box(-47, -18, 94, 36);
  480. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  481. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  482. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  483. painter->drawRect(box);
  484. painter->drawText(
  485. box,
  486. Qt::AlignCenter,
  487. arithmetic->operation == ArithmeticOperation::Add
  488. ? QStringLiteral("ADD") : QStringLiteral("SUB"));
  489. painter->drawText(
  490. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  491. Qt::AlignCenter,
  492. configured_ ? QStringLiteral("%1,%2 -> %3")
  493. .arg(wordOperandText(arithmetic->left))
  494. .arg(wordOperandText(arithmetic->right))
  495. .arg(registerAddressText(arithmetic->destination))
  496. : tr("< 操作数 -> 目标 >"));
  497. }
  498. }
  499. const std::string &nodeId() const { return node_id_; }
  500. const std::string &rungId() const { return rung_id_; }
  501. bool isConditionNode() const { return condition_; }
  502. private:
  503. std::string node_id_;
  504. std::string rung_id_;
  505. LogicNodeConfig config_;
  506. QString register_comment_;
  507. bool configured_ = true;
  508. bool active_ = false;
  509. bool faulted_ = false;
  510. bool condition_ = true;
  511. };
  512. class LogicEditorWidget::WireItem final : public QGraphicsItem
  513. {
  514. public:
  515. WireItem(
  516. const std::string &expression_id,
  517. const std::string &rung_id,
  518. const QPointF &center,
  519. int column_span,
  520. bool active)
  521. : expression_id_(expression_id),
  522. rung_id_(rung_id),
  523. width_(static_cast<qreal>(column_span) * kCellWidth),
  524. active_(active)
  525. {
  526. setPos(center);
  527. setFlag(ItemIsSelectable, true);
  528. setZValue(2.0);
  529. setToolTip(LogicEditorWidget::tr("横线:%1 列,可用触点直接替换")
  530. .arg(column_span));
  531. }
  532. QRectF boundingRect() const override
  533. {
  534. return {-width_ / 2.0, -kCellHeight / 2.0, width_, kCellHeight};
  535. }
  536. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  537. {
  538. const bool selected = (option->state & QStyle::State_Selected) != 0;
  539. if (selected)
  540. {
  541. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  542. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  543. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  544. }
  545. painter->setPen(ladderPen(active_));
  546. painter->drawLine(QPointF(-width_ / 2.0, 0), QPointF(width_ / 2.0, 0));
  547. }
  548. const std::string &expressionId() const { return expression_id_; }
  549. const std::string &rungId() const { return rung_id_; }
  550. private:
  551. std::string expression_id_;
  552. std::string rung_id_;
  553. qreal width_ = kCellWidth;
  554. bool active_ = false;
  555. };
  556. class LogicEditorWidget::VerticalConnectorItem final : public QGraphicsItem
  557. {
  558. public:
  559. VerticalConnectorItem(
  560. const std::string &branch_expression_id,
  561. const std::string &rung_id,
  562. qreal x,
  563. qreal top,
  564. qreal bottom,
  565. bool active)
  566. : branch_expression_id_(branch_expression_id),
  567. rung_id_(rung_id),
  568. height_(bottom - top),
  569. active_(active)
  570. {
  571. setPos(x, top);
  572. setFlag(ItemIsSelectable, true);
  573. setZValue(2.5);
  574. setToolTip(LogicEditorWidget::tr("竖线连接:删除将移除对应并联支路"));
  575. }
  576. QRectF boundingRect() const override
  577. {
  578. return {-8.0, 0.0, 16.0, height_};
  579. }
  580. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  581. {
  582. const bool selected = (option->state & QStyle::State_Selected) != 0;
  583. painter->setPen(QPen(
  584. selected ? kSelectionBorderColor
  585. : active_ ? kActiveColor : kLadderColor,
  586. selected || active_ ? 3.0 : kLadderLineWidth));
  587. painter->drawLine(QPointF(0, 0), QPointF(0, height_));
  588. if (selected)
  589. {
  590. painter->setPen(QPen(kSelectionBorderColor, 1.0, Qt::DashLine));
  591. painter->drawRect(boundingRect().adjusted(1, 1, -1, -1));
  592. }
  593. }
  594. const std::string &branchExpressionId() const
  595. {
  596. return branch_expression_id_;
  597. }
  598. const std::string &rungId() const { return rung_id_; }
  599. private:
  600. std::string branch_expression_id_;
  601. std::string rung_id_;
  602. qreal height_ = 0.0;
  603. bool active_ = false;
  604. };
  605. class LogicEditorWidget::RungItem final : public QGraphicsItem
  606. {
  607. public:
  608. RungItem(
  609. const LadderRung &rung,
  610. int number,
  611. qreal top,
  612. qreal height,
  613. qreal width,
  614. qreal cursor_left)
  615. : rung_id_(rung.id),
  616. name_(QString::fromStdString(rung.name)),
  617. comment_(QString::fromStdString(rung.comment)),
  618. number_(number),
  619. height_(height),
  620. width_(width),
  621. cursor_left_(cursor_left),
  622. show_cursor_(!rung.condition.has_value())
  623. {
  624. setPos(0, top);
  625. setFlag(ItemIsSelectable, true);
  626. setZValue(-2.0);
  627. if (!comment_.isEmpty())
  628. {
  629. setToolTip(comment_);
  630. }
  631. }
  632. QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; }
  633. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  634. {
  635. const bool selected = (option->state & QStyle::State_Selected) != 0;
  636. if (selected)
  637. {
  638. painter->fillRect(boundingRect(), QColor(240, 247, 250, 90));
  639. if (show_cursor_)
  640. {
  641. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  642. painter->drawRect(QRectF(
  643. cursor_left_ + 3,
  644. kRungHeaderHeight + 3,
  645. kCellWidth - 6,
  646. kCellHeight - 6));
  647. }
  648. }
  649. painter->setPen(QColor(QStringLiteral("#62717b")));
  650. QString title = tr("网络 %1").arg(number_);
  651. if (!name_.isEmpty() && !name_.startsWith(tr("网络 ")))
  652. {
  653. title += QStringLiteral(":") + name_;
  654. }
  655. painter->drawText(
  656. QRectF(kSceneMargin + 8, 5, 320, 22),
  657. Qt::AlignLeft | Qt::AlignVCenter,
  658. title);
  659. if (!comment_.isEmpty())
  660. {
  661. painter->setPen(QColor(QStringLiteral("#7b8790")));
  662. const QString visible_comment = painter->fontMetrics().elidedText(
  663. comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0));
  664. painter->drawText(
  665. QRectF(kSceneMargin + 8, 24, width_ - 16, 22),
  666. Qt::AlignLeft | Qt::AlignVCenter,
  667. visible_comment);
  668. }
  669. painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
  670. painter->drawLine(
  671. QPointF(kSceneMargin + 8, height_ - 1),
  672. QPointF(kSceneMargin + width_ - 8, height_ - 1));
  673. }
  674. const std::string &rungId() const { return rung_id_; }
  675. private:
  676. std::string rung_id_;
  677. QString name_;
  678. QString comment_;
  679. int number_ = 0;
  680. qreal height_ = 0.0;
  681. qreal width_ = 0.0;
  682. qreal cursor_left_ = 0.0;
  683. bool show_cursor_ = false;
  684. };
  685. namespace {
  686. RenderResult renderExpression(
  687. QGraphicsScene &scene,
  688. const LogicEditorService &editor_service,
  689. const ConditionExpression &expression,
  690. const std::string &rung_id,
  691. const QPointF &top_left,
  692. const ExpressionMetrics &metrics,
  693. const LogicTraceSnapshot &trace,
  694. bool trace_enabled,
  695. const std::string &fault_node_id)
  696. {
  697. if (expression.kind == ConditionExpressionKind::Wire)
  698. {
  699. const bool active = trace_enabled && traceValue(
  700. trace, &LogicTraceSnapshot::expressionPowerValues, expression.id);
  701. const qreal width = static_cast<qreal>(expression.wire->columnSpan) * kCellWidth;
  702. const QPointF center(
  703. top_left.x() + width / 2.0,
  704. top_left.y() + kCellHeight / 2.0);
  705. scene.addItem(new LogicEditorWidget::WireItem(
  706. expression.id,
  707. rung_id,
  708. center,
  709. expression.wire->columnSpan,
  710. active));
  711. return {
  712. QPointF(top_left.x(), center.y()),
  713. QPointF(top_left.x() + width, center.y())};
  714. }
  715. if (expression.kind == ConditionExpressionKind::Node)
  716. {
  717. const QPointF center(
  718. top_left.x() + kCellWidth / 2.0,
  719. top_left.y() + kCellHeight / 2.0);
  720. const bool node_active = trace_enabled && traceValue(
  721. trace, &LogicTraceSnapshot::nodePowerValues, expression.node->id);
  722. scene.addLine(
  723. QLineF(
  724. QPointF(top_left.x(), center.y()),
  725. QPointF(top_left.x() + kCellWidth, center.y())),
  726. ladderPen(node_active));
  727. scene.addItem(new LogicEditorWidget::NodeItem(
  728. *expression.node,
  729. rung_id,
  730. center,
  731. [&editor_service, &expression]
  732. {
  733. const std::optional<RegisterAddress> address =
  734. registerAddressForLogicNode(expression.node->config);
  735. return address.has_value()
  736. ? QString::fromStdString(
  737. editor_service.registerCommentFor(*address))
  738. : QString{};
  739. }(),
  740. node_active,
  741. expression.node->id == fault_node_id,
  742. true));
  743. return {
  744. QPointF(top_left.x(), center.y()),
  745. QPointF(top_left.x() + kCellWidth, center.y())};
  746. }
  747. if (expression.kind == ConditionExpressionKind::Series)
  748. {
  749. qreal x = top_left.x();
  750. RenderResult first;
  751. RenderResult previous;
  752. for (std::size_t index = 0; index < expression.children.size(); ++index)
  753. {
  754. const ExpressionMetrics child_metrics = measureExpression(expression.children[index]);
  755. const RenderResult current = renderExpression(
  756. scene,
  757. editor_service,
  758. expression.children[index],
  759. rung_id,
  760. QPointF(x, top_left.y()),
  761. child_metrics,
  762. trace,
  763. trace_enabled,
  764. fault_node_id);
  765. if (index == 0U)
  766. {
  767. first = current;
  768. }
  769. else
  770. {
  771. const bool previous_active = trace_enabled && traceValue(
  772. trace,
  773. &LogicTraceSnapshot::expressionPowerValues,
  774. expression.children[index - 1U].id);
  775. scene.addLine(
  776. QLineF(previous.output, current.input),
  777. ladderPen(previous_active));
  778. }
  779. previous = current;
  780. x += static_cast<qreal>(child_metrics.columns) * kCellWidth;
  781. }
  782. return {first.input, previous.output};
  783. }
  784. qreal y = top_left.y();
  785. std::vector<RenderResult> branches;
  786. branches.reserve(expression.children.size());
  787. for (const ConditionExpression &child : expression.children)
  788. {
  789. const ExpressionMetrics child_metrics = measureExpression(child);
  790. branches.push_back(renderExpression(
  791. scene,
  792. editor_service,
  793. child,
  794. rung_id,
  795. QPointF(top_left.x(), y),
  796. child_metrics,
  797. trace,
  798. trace_enabled,
  799. fault_node_id));
  800. y += static_cast<qreal>(child_metrics.rows) * kCellHeight;
  801. }
  802. const qreal left_join = top_left.x();
  803. const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth;
  804. const qreal top_y = branches.front().input.y();
  805. const bool parallel_input_active = trace_enabled && traceValue(
  806. trace, &LogicTraceSnapshot::expressionInputValues, expression.id);
  807. for (std::size_t index = 1; index < branches.size(); ++index)
  808. {
  809. const qreal segment_top = branches[index - 1U].input.y();
  810. const qreal segment_bottom = branches[index].input.y();
  811. const bool branch_active = trace_enabled && traceValue(
  812. trace,
  813. &LogicTraceSnapshot::expressionPowerValues,
  814. expression.children[index].id);
  815. scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
  816. expression.children[index].id,
  817. rung_id,
  818. left_join,
  819. segment_top,
  820. segment_bottom,
  821. parallel_input_active));
  822. scene.addItem(new LogicEditorWidget::VerticalConnectorItem(
  823. expression.children[index].id,
  824. rung_id,
  825. right_join,
  826. segment_top,
  827. segment_bottom,
  828. branch_active));
  829. }
  830. for (std::size_t index = 0; index < branches.size(); ++index)
  831. {
  832. const bool branch_active = trace_enabled && traceValue(
  833. trace,
  834. &LogicTraceSnapshot::expressionPowerValues,
  835. expression.children[index].id);
  836. scene.addLine(
  837. QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input),
  838. ladderPen(branch_active));
  839. scene.addLine(
  840. QLineF(branches[index].output, QPointF(right_join, branches[index].output.y())),
  841. ladderPen(branch_active));
  842. }
  843. return {QPointF(left_join, top_y), QPointF(right_join, top_y)};
  844. }
  845. } // namespace
  846. LogicEditorWidget::LogicEditorWidget(LogicEditorService &editor_service, QWidget *parent)
  847. : QGraphicsView(parent), editor_service_(editor_service)
  848. {
  849. scene_ = new QGraphicsScene(this);
  850. setScene(scene_);
  851. setRenderHint(QPainter::Antialiasing, true);
  852. setBackgroundBrush(Qt::white);
  853. setDragMode(QGraphicsView::RubberBandDrag);
  854. setAlignment(Qt::AlignLeft | Qt::AlignTop);
  855. connect(scene_, &QGraphicsScene::selectionChanged,
  856. this, &LogicEditorWidget::handleSelectionChanged);
  857. }
  858. void LogicEditorWidget::setLogicId(const std::string &logic_id)
  859. {
  860. if (logic_id_ == logic_id)
  861. {
  862. return;
  863. }
  864. logic_id_ = logic_id;
  865. current_rung_id_.clear();
  866. reloadLogic();
  867. }
  868. void LogicEditorWidget::setEditingEnabled(bool enabled)
  869. {
  870. editing_enabled_ = enabled;
  871. setInteractive(enabled);
  872. }
  873. void LogicEditorWidget::setRuntimeTrace(
  874. const LogicTraceSnapshot &trace, const std::string &fault_node_id)
  875. {
  876. trace_ = trace;
  877. fault_node_id_ = fault_node_id;
  878. runtime_trace_enabled_ = true;
  879. reloadLogic();
  880. }
  881. void LogicEditorWidget::clearRuntimeTrace()
  882. {
  883. trace_.clear();
  884. fault_node_id_.clear();
  885. runtime_trace_enabled_ = false;
  886. reloadLogic();
  887. }
  888. void LogicEditorWidget::reloadLogic()
  889. {
  890. scene_->clear();
  891. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  892. if (logic == nullptr)
  893. {
  894. scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
  895. return;
  896. }
  897. if (current_rung_id_.empty() && !logic->rungs.empty())
  898. {
  899. current_rung_id_ = logic->rungs.front().id;
  900. }
  901. int condition_columns = 1;
  902. for (const LadderRung &rung : logic->rungs)
  903. {
  904. if (rung.condition.has_value())
  905. {
  906. condition_columns = std::max(
  907. condition_columns,
  908. measureExpression(*rung.condition).columns);
  909. }
  910. }
  911. const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 1);
  912. const qreal left_rail_x = kSceneMargin + kRailInset;
  913. const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
  914. const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
  915. qreal top = kSceneMargin;
  916. int number = 1;
  917. for (const LadderRung &rung : logic->rungs)
  918. {
  919. const ExpressionMetrics metrics = rung.condition.has_value()
  920. ? measureExpression(*rung.condition) : ExpressionMetrics{};
  921. const int grid_rows = std::max(1, metrics.rows);
  922. const qreal grid_top = top + kRungHeaderHeight;
  923. const qreal rung_height = kRungHeaderHeight
  924. + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
  925. scene_->addItem(new RungItem(
  926. rung,
  927. number,
  928. top,
  929. rung_height,
  930. scene_width - 2.0 * kSceneMargin,
  931. left_rail_x));
  932. addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
  933. scene_->addLine(
  934. QLineF(left_rail_x, grid_top, left_rail_x,
  935. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  936. QPen(kLadderColor, 2.4));
  937. scene_->addLine(
  938. QLineF(right_rail_x, grid_top, right_rail_x,
  939. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  940. QPen(kLadderColor, 2.4));
  941. const qreal main_y = grid_top + kCellHeight / 2.0;
  942. QPointF expression_output(left_rail_x, main_y);
  943. if (rung.condition.has_value())
  944. {
  945. const RenderResult rendered = renderExpression(
  946. *scene_,
  947. editor_service_,
  948. *rung.condition,
  949. rung.id,
  950. QPointF(left_rail_x, grid_top),
  951. metrics,
  952. trace_,
  953. runtime_trace_enabled_,
  954. fault_node_id_);
  955. expression_output = rendered.output;
  956. }
  957. else
  958. {
  959. addPlaceholder(
  960. *scene_,
  961. QRectF(left_rail_x, grid_top, kCellWidth, kCellHeight),
  962. rung.output.has_value() ? tr("无条件") : tr("添加条件"));
  963. }
  964. const bool rung_active = runtime_trace_enabled_ && traceValue(
  965. trace_, &LogicTraceSnapshot::rungValues, rung.id);
  966. const qreal output_left = right_rail_x - kCellWidth;
  967. scene_->addLine(
  968. QLineF(expression_output, QPointF(output_left, main_y)),
  969. ladderPen(rung_active));
  970. if (rung.output.has_value())
  971. {
  972. scene_->addLine(
  973. QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
  974. ladderPen(rung_active));
  975. scene_->addItem(new NodeItem(
  976. *rung.output,
  977. rung.id,
  978. QPointF(output_left + kCellWidth / 2.0, main_y),
  979. [&rung, this]
  980. {
  981. const std::optional<RegisterAddress> address =
  982. registerAddressForLogicNode(rung.output->config);
  983. return address.has_value()
  984. ? QString::fromStdString(
  985. editor_service_.registerCommentFor(*address))
  986. : QString{};
  987. }(),
  988. [&rung, this, rung_active]
  989. {
  990. return (std::holds_alternative<TonNodeConfig>(rung.output->config)
  991. || std::holds_alternative<CounterNodeConfig>(
  992. rung.output->config))
  993. ? traceValue(
  994. trace_,
  995. &LogicTraceSnapshot::nodeValues,
  996. rung.output->id)
  997. : rung_active;
  998. }(),
  999. rung.output->id == fault_node_id_,
  1000. false));
  1001. }
  1002. else
  1003. {
  1004. addPlaceholder(
  1005. *scene_,
  1006. QRectF(output_left, grid_top, kCellWidth, kCellHeight),
  1007. tr("输出线圈"));
  1008. scene_->addLine(
  1009. QLineF(
  1010. QPointF(output_left + kCellWidth, main_y),
  1011. QPointF(right_rail_x, main_y)),
  1012. ladderPen(rung_active));
  1013. }
  1014. top += rung_height + kRungGap;
  1015. ++number;
  1016. }
  1017. scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
  1018. }
  1019. void LogicEditorWidget::selectNode(const std::string &node_id)
  1020. {
  1021. for (QGraphicsItem *item : scene_->items())
  1022. {
  1023. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1024. {
  1025. node->setSelected(node->nodeId() == node_id);
  1026. if (node->nodeId() == node_id)
  1027. {
  1028. current_rung_id_ = node->rungId();
  1029. ensureVisible(node);
  1030. }
  1031. }
  1032. else
  1033. {
  1034. item->setSelected(false);
  1035. }
  1036. }
  1037. }
  1038. void LogicEditorWidget::selectExpression(const std::string &expression_id)
  1039. {
  1040. for (QGraphicsItem *item : scene_->items())
  1041. {
  1042. bool selected = false;
  1043. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  1044. {
  1045. selected = node->isConditionNode() && node->nodeId() == expression_id;
  1046. if (selected)
  1047. {
  1048. current_rung_id_ = node->rungId();
  1049. ensureVisible(node);
  1050. }
  1051. }
  1052. else if (WireItem *wire = dynamic_cast<WireItem *>(item))
  1053. {
  1054. selected = wire->expressionId() == expression_id;
  1055. if (selected)
  1056. {
  1057. current_rung_id_ = wire->rungId();
  1058. ensureVisible(wire);
  1059. }
  1060. }
  1061. item->setSelected(selected);
  1062. }
  1063. }
  1064. void LogicEditorWidget::selectWire(const std::string &wire_id)
  1065. {
  1066. selectExpression(wire_id);
  1067. }
  1068. std::string LogicEditorWidget::selectedNodeId() const
  1069. {
  1070. const std::vector<std::string> ids = selectedNodeIds();
  1071. return ids.size() == 1U ? ids.front() : std::string{};
  1072. }
  1073. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  1074. {
  1075. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1076. for (QGraphicsItem *item : scene_->selectedItems())
  1077. {
  1078. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1079. {
  1080. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1081. }
  1082. }
  1083. std::sort(
  1084. positioned_ids.begin(), positioned_ids.end(),
  1085. [](const auto &left, const auto &right)
  1086. {
  1087. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1088. {
  1089. return left.first.y() < right.first.y();
  1090. }
  1091. return left.first.x() < right.first.x();
  1092. });
  1093. std::vector<std::string> ids;
  1094. ids.reserve(positioned_ids.size());
  1095. for (const auto &positioned_id : positioned_ids)
  1096. {
  1097. ids.push_back(positioned_id.second);
  1098. }
  1099. return ids;
  1100. }
  1101. std::vector<std::string> LogicEditorWidget::selectedExpressionIds() const
  1102. {
  1103. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  1104. for (QGraphicsItem *item : scene_->selectedItems())
  1105. {
  1106. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1107. {
  1108. if (node->isConditionNode())
  1109. {
  1110. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  1111. }
  1112. }
  1113. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1114. {
  1115. positioned_ids.emplace_back(wire->scenePos(), wire->expressionId());
  1116. }
  1117. }
  1118. std::sort(
  1119. positioned_ids.begin(), positioned_ids.end(),
  1120. [](const auto &left, const auto &right)
  1121. {
  1122. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  1123. {
  1124. return left.first.y() < right.first.y();
  1125. }
  1126. return left.first.x() < right.first.x();
  1127. });
  1128. std::vector<std::string> ids;
  1129. ids.reserve(positioned_ids.size());
  1130. for (const auto &positioned_id : positioned_ids)
  1131. {
  1132. ids.push_back(positioned_id.second);
  1133. }
  1134. return ids;
  1135. }
  1136. std::vector<std::string> LogicEditorWidget::selectedWireIds() const
  1137. {
  1138. std::vector<std::string> ids;
  1139. for (QGraphicsItem *item : scene_->selectedItems())
  1140. {
  1141. if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1142. {
  1143. ids.push_back(wire->expressionId());
  1144. }
  1145. }
  1146. return ids;
  1147. }
  1148. std::vector<std::string> LogicEditorWidget::selectedBranchIds() const
  1149. {
  1150. std::vector<std::string> ids;
  1151. for (QGraphicsItem *item : scene_->selectedItems())
  1152. {
  1153. if (const VerticalConnectorItem *connector =
  1154. dynamic_cast<const VerticalConnectorItem *>(item))
  1155. {
  1156. if (std::find(ids.cbegin(), ids.cend(), connector->branchExpressionId())
  1157. == ids.cend())
  1158. {
  1159. ids.push_back(connector->branchExpressionId());
  1160. }
  1161. }
  1162. }
  1163. return ids;
  1164. }
  1165. std::string LogicEditorWidget::selectedRungId() const
  1166. {
  1167. std::string rung_id;
  1168. for (QGraphicsItem *item : scene_->selectedItems())
  1169. {
  1170. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1171. {
  1172. if (!rung_id.empty() && rung_id != node->rungId())
  1173. {
  1174. return {};
  1175. }
  1176. rung_id = node->rungId();
  1177. }
  1178. else if (const WireItem *wire = dynamic_cast<const WireItem *>(item))
  1179. {
  1180. if (!rung_id.empty() && rung_id != wire->rungId())
  1181. {
  1182. return {};
  1183. }
  1184. rung_id = wire->rungId();
  1185. }
  1186. else if (const VerticalConnectorItem *connector =
  1187. dynamic_cast<const VerticalConnectorItem *>(item))
  1188. {
  1189. if (!rung_id.empty() && rung_id != connector->rungId())
  1190. {
  1191. return {};
  1192. }
  1193. rung_id = connector->rungId();
  1194. }
  1195. else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
  1196. {
  1197. if (rung_id.empty())
  1198. {
  1199. rung_id = rung->rungId();
  1200. }
  1201. }
  1202. }
  1203. return rung_id;
  1204. }
  1205. LogicEditorResult LogicEditorWidget::addRung()
  1206. {
  1207. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  1208. if (result.succeeded)
  1209. {
  1210. current_rung_id_ = result.id;
  1211. reloadLogic();
  1212. emit graphChanged();
  1213. }
  1214. else
  1215. {
  1216. reportFailure(result);
  1217. }
  1218. return result;
  1219. }
  1220. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  1221. {
  1222. const std::vector<std::string> selected_expressions = selectedExpressionIds();
  1223. const std::vector<std::string> selected_wires = selectedWireIds();
  1224. const std::vector<std::string> selected_ids = selectedNodeIds();
  1225. LogicEditorResult result;
  1226. if (selected_expressions.size() > 1U || selected_wires.size() > 1U)
  1227. {
  1228. result = {false, LogicEditorError::InvalidOperation,
  1229. "串联插入或替换横线时只能选择一个条件对象", {}};
  1230. }
  1231. else if (selected_wires.size() == 1U)
  1232. {
  1233. result = editor_service_.replaceWireWithCondition(
  1234. logic_id_, currentRungId(), selected_wires.front(), config);
  1235. }
  1236. else if (selected_ids.size() == 1U)
  1237. {
  1238. const LogicNode *selected_node = editor_service_.findNode(
  1239. logic_id_, selected_ids.front());
  1240. result = selected_node != nullptr && selected_node->isCondition()
  1241. ? editor_service_.insertConditionAfter(
  1242. logic_id_, currentRungId(), selected_ids.front(), config)
  1243. : editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1244. }
  1245. else
  1246. {
  1247. result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
  1248. }
  1249. if (result.succeeded)
  1250. {
  1251. reloadLogic();
  1252. selectExpression(result.id);
  1253. emit graphChanged();
  1254. }
  1255. else
  1256. {
  1257. reportFailure(result);
  1258. }
  1259. return result;
  1260. }
  1261. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  1262. {
  1263. const std::string rung_id = selectedRungId();
  1264. const std::vector<std::string> selected_ids = selectedNodeIds();
  1265. std::vector<std::string> condition_ids;
  1266. for (const std::string &node_id : selected_ids)
  1267. {
  1268. const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
  1269. if (node != nullptr && node->isCondition())
  1270. {
  1271. condition_ids.push_back(node_id);
  1272. }
  1273. }
  1274. LogicEditorResult result;
  1275. if (rung_id.empty() || condition_ids.empty())
  1276. {
  1277. result = {false, LogicEditorError::InvalidOperation,
  1278. "请在同一网络中选择要并联的连续节点", {}};
  1279. }
  1280. else
  1281. {
  1282. result = editor_service_.addParallelBranch(
  1283. logic_id_, rung_id, condition_ids, config);
  1284. }
  1285. if (result.succeeded)
  1286. {
  1287. reloadLogic();
  1288. selectExpression(result.id);
  1289. emit graphChanged();
  1290. }
  1291. else
  1292. {
  1293. reportFailure(result);
  1294. }
  1295. return result;
  1296. }
  1297. LogicEditorResult LogicEditorWidget::addHorizontalWire()
  1298. {
  1299. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1300. LogicEditorResult result;
  1301. if (selected_ids.size() > 1U)
  1302. {
  1303. result = {false, LogicEditorError::InvalidOperation,
  1304. "插入横线时只能选择一个条件对象", {}};
  1305. }
  1306. else if (selected_ids.size() == 1U)
  1307. {
  1308. result = editor_service_.insertWireAfter(
  1309. logic_id_, currentRungId(), selected_ids.front());
  1310. }
  1311. else
  1312. {
  1313. result = editor_service_.appendWire(logic_id_, currentRungId());
  1314. }
  1315. if (result.succeeded)
  1316. {
  1317. reloadLogic();
  1318. selectWire(result.id);
  1319. emit graphChanged();
  1320. }
  1321. else
  1322. {
  1323. reportFailure(result);
  1324. }
  1325. return result;
  1326. }
  1327. LogicEditorResult LogicEditorWidget::addVerticalWire()
  1328. {
  1329. const std::string rung_id = selectedRungId();
  1330. const std::vector<std::string> selected_ids = selectedExpressionIds();
  1331. LogicEditorResult result;
  1332. if (rung_id.empty() || selected_ids.empty())
  1333. {
  1334. result = {false, LogicEditorError::InvalidOperation,
  1335. "请在同一网络中选择要连接的连续条件或横线", {}};
  1336. }
  1337. else
  1338. {
  1339. result = editor_service_.addParallelWireBranch(
  1340. logic_id_, rung_id, selected_ids);
  1341. }
  1342. if (result.succeeded)
  1343. {
  1344. reloadLogic();
  1345. selectWire(result.id);
  1346. emit graphChanged();
  1347. }
  1348. else
  1349. {
  1350. reportFailure(result);
  1351. }
  1352. return result;
  1353. }
  1354. LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
  1355. {
  1356. const std::vector<std::string> wire_ids = selectedWireIds();
  1357. const std::string rung_id = selectedRungId();
  1358. if (wire_ids.empty() || rung_id.empty())
  1359. {
  1360. const LogicEditorResult result = {
  1361. false,
  1362. LogicEditorError::InvalidOperation,
  1363. "请先选择要删除的横线",
  1364. {}};
  1365. reportFailure(result);
  1366. return result;
  1367. }
  1368. const LogicEditorResult result = editor_service_.removeExpressions(
  1369. logic_id_, rung_id, wire_ids);
  1370. if (result.succeeded)
  1371. {
  1372. reloadLogic();
  1373. emit nodeSelected({});
  1374. emit graphChanged();
  1375. }
  1376. else
  1377. {
  1378. reportFailure(result);
  1379. }
  1380. return result;
  1381. }
  1382. LogicEditorResult LogicEditorWidget::deleteVerticalWire()
  1383. {
  1384. const std::vector<std::string> branch_ids = selectedBranchIds();
  1385. if (branch_ids.empty())
  1386. {
  1387. const LogicEditorResult result = {
  1388. false,
  1389. LogicEditorError::InvalidOperation,
  1390. "请先选择要删除的竖线连接",
  1391. {}};
  1392. reportFailure(result);
  1393. return result;
  1394. }
  1395. const std::string rung_id = selectedRungId();
  1396. LogicEditorResult result;
  1397. if (rung_id.empty())
  1398. {
  1399. result = {false, LogicEditorError::InvalidOperation,
  1400. "竖线连接必须位于同一网络", {}};
  1401. }
  1402. else
  1403. {
  1404. result = editor_service_.removeExpressions(
  1405. logic_id_, rung_id, branch_ids);
  1406. }
  1407. if (result.succeeded)
  1408. {
  1409. reloadLogic();
  1410. emit nodeSelected({});
  1411. emit graphChanged();
  1412. }
  1413. else
  1414. {
  1415. reportFailure(result);
  1416. }
  1417. return result;
  1418. }
  1419. LogicEditorResult LogicEditorWidget::setOutput(
  1420. const LogicNodeConfig &config, bool configured)
  1421. {
  1422. const LogicEditorResult result = editor_service_.setOutput(
  1423. logic_id_, currentRungId(), config, configured);
  1424. if (result.succeeded)
  1425. {
  1426. reloadLogic();
  1427. selectNode(result.id);
  1428. emit graphChanged();
  1429. }
  1430. else
  1431. {
  1432. reportFailure(result);
  1433. }
  1434. return result;
  1435. }
  1436. LogicEditorResult LogicEditorWidget::deleteSelected()
  1437. {
  1438. const std::vector<std::string> expression_ids = selectedExpressionIds();
  1439. const std::vector<std::string> wire_ids = selectedWireIds();
  1440. const std::vector<std::string> branch_ids = selectedBranchIds();
  1441. const std::vector<std::string> node_ids = selectedNodeIds();
  1442. LogicEditorResult result;
  1443. if (!branch_ids.empty())
  1444. {
  1445. result = editor_service_.removeExpressions(
  1446. logic_id_, selectedRungId(), branch_ids);
  1447. }
  1448. else if (!wire_ids.empty())
  1449. {
  1450. bool output_selected = false;
  1451. for (QGraphicsItem *item : scene_->selectedItems())
  1452. {
  1453. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  1454. {
  1455. output_selected = output_selected || !node->isConditionNode();
  1456. }
  1457. }
  1458. if (output_selected)
  1459. {
  1460. result = {false, LogicEditorError::InvalidOperation,
  1461. "不能同时删除横线和输出节点", {}};
  1462. }
  1463. else
  1464. {
  1465. result = editor_service_.removeExpressions(
  1466. logic_id_, selectedRungId(), expression_ids);
  1467. }
  1468. }
  1469. else if (!node_ids.empty())
  1470. {
  1471. result = editor_service_.removeNodes(logic_id_, node_ids);
  1472. }
  1473. else
  1474. {
  1475. const std::string rung_id = selectedRungId();
  1476. if (rung_id.empty())
  1477. {
  1478. return {false, LogicEditorError::InvalidOperation,
  1479. "请先选择要删除的逻辑节点或网络", {}};
  1480. }
  1481. result = editor_service_.removeRung(logic_id_, rung_id);
  1482. if (result.succeeded && current_rung_id_ == rung_id)
  1483. {
  1484. current_rung_id_ = editor_service_.firstRungId(logic_id_);
  1485. }
  1486. }
  1487. if (result.succeeded)
  1488. {
  1489. reloadLogic();
  1490. emit nodeSelected({});
  1491. emit graphChanged();
  1492. }
  1493. else
  1494. {
  1495. reportFailure(result);
  1496. }
  1497. return result;
  1498. }
  1499. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  1500. {
  1501. QGraphicsView::resizeEvent(event);
  1502. }
  1503. void LogicEditorWidget::handleSelectionChanged()
  1504. {
  1505. const std::string rung_id = selectedRungId();
  1506. if (!rung_id.empty())
  1507. {
  1508. current_rung_id_ = rung_id;
  1509. }
  1510. emit nodeSelected(QString::fromStdString(selectedNodeId()));
  1511. }
  1512. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  1513. {
  1514. emit editorError(QString::fromStdString(result.message));
  1515. }
  1516. std::string LogicEditorWidget::currentRungId() const
  1517. {
  1518. const std::string selected = selectedRungId();
  1519. if (!selected.empty())
  1520. {
  1521. return selected;
  1522. }
  1523. return current_rung_id_.empty()
  1524. ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
  1525. }