综合平台编程器项目的远程存储
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

1822 righe
62 KiB

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