综合平台编程器项目的远程存储
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 

900 rader
28 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 = 86.0;
  17. constexpr qreal kNodeTerminalX = 50.0;
  18. constexpr qreal kRungHeaderHeight = 34.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 comparisonText(ComparisonOperator comparison)
  44. {
  45. switch (comparison)
  46. {
  47. case ComparisonOperator::Equal: return QStringLiteral("=");
  48. case ComparisonOperator::NotEqual: return QStringLiteral("<>");
  49. case ComparisonOperator::LessThan: return QStringLiteral("<");
  50. case ComparisonOperator::LessThanOrEqual: return QStringLiteral("<=");
  51. case ComparisonOperator::GreaterThan: return QStringLiteral(">");
  52. case ComparisonOperator::GreaterThanOrEqual: return QStringLiteral(">=");
  53. }
  54. return QStringLiteral("?");
  55. }
  56. QString nodeToolTip(const LogicNodeConfig &config)
  57. {
  58. return std::visit(
  59. [](const auto &value) -> QString
  60. {
  61. using Config = std::decay_t<decltype(value)>;
  62. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  63. {
  64. return LogicEditorWidget::tr("%1触点:%2")
  65. .arg(value.mode == ContactMode::NormallyOpen
  66. ? LogicEditorWidget::tr("常开")
  67. : LogicEditorWidget::tr("常闭"))
  68. .arg(registerAddressText(value.address));
  69. }
  70. else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  71. {
  72. return LogicEditorWidget::tr("输出线圈:%1")
  73. .arg(registerAddressText(value.address));
  74. }
  75. else
  76. {
  77. return LogicEditorWidget::tr("比较条件:%1 %2 %3")
  78. .arg(registerAddressText(value.address))
  79. .arg(comparisonText(value.comparison))
  80. .arg(value.value);
  81. }
  82. },
  83. config);
  84. }
  85. ExpressionMetrics measureExpression(const ConditionExpression &expression)
  86. {
  87. if (expression.kind == ConditionExpressionKind::Node)
  88. {
  89. return {};
  90. }
  91. ExpressionMetrics metrics{0, 0};
  92. if (expression.kind == ConditionExpressionKind::Series)
  93. {
  94. for (const ConditionExpression &child : expression.children)
  95. {
  96. const ExpressionMetrics child_metrics = measureExpression(child);
  97. metrics.columns += child_metrics.columns;
  98. metrics.rows = std::max(metrics.rows, child_metrics.rows);
  99. }
  100. }
  101. else
  102. {
  103. for (const ConditionExpression &child : expression.children)
  104. {
  105. const ExpressionMetrics child_metrics = measureExpression(child);
  106. metrics.columns = std::max(metrics.columns, child_metrics.columns);
  107. metrics.rows += child_metrics.rows;
  108. }
  109. }
  110. return metrics;
  111. }
  112. QPen ladderPen(bool active)
  113. {
  114. return QPen(active ? kActiveColor : kLadderColor,
  115. active ? 2.6 : kLadderLineWidth);
  116. }
  117. bool traceValue(
  118. const LogicTraceSnapshot &trace,
  119. const std::unordered_map<std::string, bool> LogicTraceSnapshot::*member,
  120. const std::string &id)
  121. {
  122. const auto &values = trace.*member;
  123. const auto found = values.find(id);
  124. return found != values.end() && found->second;
  125. }
  126. void addGrid(
  127. QGraphicsScene &scene,
  128. qreal left,
  129. qreal top,
  130. int columns,
  131. int rows)
  132. {
  133. QPen pen(kGridColor, 1.0);
  134. pen.setCosmetic(true);
  135. for (int column = 0; column <= columns; ++column)
  136. {
  137. const qreal x = left + static_cast<qreal>(column) * kCellWidth;
  138. QGraphicsLineItem *line = scene.addLine(
  139. QLineF(x, top, x, top + static_cast<qreal>(rows) * kCellHeight), pen);
  140. line->setZValue(-10.0);
  141. }
  142. for (int row = 0; row <= rows; ++row)
  143. {
  144. const qreal y = top + static_cast<qreal>(row) * kCellHeight;
  145. QGraphicsLineItem *line = scene.addLine(
  146. QLineF(left, y, left + static_cast<qreal>(columns) * kCellWidth, y), pen);
  147. line->setZValue(-10.0);
  148. }
  149. }
  150. void addPlaceholder(QGraphicsScene &scene, const QRectF &cell, const QString &text)
  151. {
  152. const QRectF bounds = cell.adjusted(12, 18, -12, -18);
  153. QGraphicsRectItem *item = scene.addRect(
  154. bounds,
  155. QPen(kPlaceholderColor, 1.2, Qt::DashLine),
  156. QBrush(QColor(255, 255, 255, 220)));
  157. item->setZValue(1.0);
  158. QGraphicsTextItem *label = scene.addText(text);
  159. label->setDefaultTextColor(kPlaceholderColor);
  160. label->setPos(
  161. bounds.center().x() - label->boundingRect().width() / 2.0,
  162. bounds.center().y() - label->boundingRect().height() / 2.0);
  163. label->setZValue(2.0);
  164. }
  165. } // namespace
  166. class LogicEditorWidget::NodeItem final : public QGraphicsItem
  167. {
  168. public:
  169. NodeItem(
  170. const LogicNode &node,
  171. const std::string &rung_id,
  172. const QPointF &center,
  173. bool active,
  174. bool faulted)
  175. : node_id_(node.id),
  176. rung_id_(rung_id),
  177. config_(node.config),
  178. configured_(node.configured),
  179. active_(active),
  180. faulted_(faulted)
  181. {
  182. setPos(center);
  183. setFlag(ItemIsSelectable, true);
  184. setZValue(3.0);
  185. setToolTip(configured_ ? nodeToolTip(config_)
  186. : LogicEditorWidget::tr("待配置:请在属性区设置地址和参数"));
  187. }
  188. QRectF boundingRect() const override
  189. {
  190. return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight};
  191. }
  192. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  193. {
  194. painter->setRenderHint(QPainter::Antialiasing, true);
  195. const bool selected = (option->state & QStyle::State_Selected) != 0;
  196. if (selected)
  197. {
  198. painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor);
  199. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  200. painter->drawRect(boundingRect().adjusted(3, 3, -3, -3));
  201. }
  202. const QColor symbol_color = faulted_ ? kFaultColor
  203. : selected ? kSelectionBorderColor : active_ ? kActiveColor : kLadderColor;
  204. painter->setPen(QPen(symbol_color, active_ || faulted_ ? 2.6 : kLadderLineWidth));
  205. QFont font = painter->font();
  206. font.setPointSizeF(9.5);
  207. painter->setFont(font);
  208. if (const auto *contact = std::get_if<ContactNodeConfig>(&config_))
  209. {
  210. painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white);
  211. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0));
  212. painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0));
  213. painter->drawLine(QPointF(-18, -15), QPointF(-18, 15));
  214. painter->drawLine(QPointF(18, -15), QPointF(18, 15));
  215. if (contact->mode == ContactMode::NormallyClosed)
  216. {
  217. painter->drawLine(QPointF(-23, 18), QPointF(23, -18));
  218. }
  219. painter->drawText(
  220. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  221. Qt::AlignCenter,
  222. configured_ ? registerAddressText(contact->address) : tr("< M 地址 >"));
  223. }
  224. else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_))
  225. {
  226. painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white);
  227. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0));
  228. painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0));
  229. QPainterPath left;
  230. left.moveTo(-2, -20);
  231. left.cubicTo(-24, -16, -24, 16, -2, 20);
  232. painter->drawPath(left);
  233. QPainterPath right;
  234. right.moveTo(2, -20);
  235. right.cubicTo(24, -16, 24, 16, 2, 20);
  236. painter->drawPath(right);
  237. if (coil->mode != CoilMode::Normal)
  238. {
  239. painter->drawText(
  240. QRectF(-12, -14, 24, 28),
  241. Qt::AlignCenter,
  242. coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R"));
  243. }
  244. painter->drawText(
  245. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  246. Qt::AlignCenter,
  247. configured_ ? registerAddressText(coil->address) : tr("< M 地址 >"));
  248. }
  249. else if (const auto *comparison = std::get_if<CompareNodeConfig>(&config_))
  250. {
  251. const QRectF box(-47, -17, 94, 34);
  252. painter->fillRect(box.adjusted(-1, -1, 1, 1), Qt::white);
  253. painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0));
  254. painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0));
  255. painter->drawRect(box);
  256. painter->drawText(
  257. box, Qt::AlignCenter,
  258. QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison)));
  259. painter->drawText(
  260. QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18),
  261. Qt::AlignCenter,
  262. configured_ ? registerAddressText(comparison->address) : tr("< D 地址 >"));
  263. painter->drawText(
  264. QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18),
  265. Qt::AlignCenter,
  266. configured_ ? QString::number(comparison->value) : tr("< 常量 >"));
  267. }
  268. }
  269. const std::string &nodeId() const { return node_id_; }
  270. const std::string &rungId() const { return rung_id_; }
  271. private:
  272. std::string node_id_;
  273. std::string rung_id_;
  274. LogicNodeConfig config_;
  275. bool configured_ = true;
  276. bool active_ = false;
  277. bool faulted_ = false;
  278. };
  279. class LogicEditorWidget::RungItem final : public QGraphicsItem
  280. {
  281. public:
  282. RungItem(
  283. const LadderRung &rung,
  284. int number,
  285. qreal top,
  286. qreal height,
  287. qreal width,
  288. qreal cursor_left)
  289. : rung_id_(rung.id),
  290. name_(QString::fromStdString(rung.name)),
  291. number_(number),
  292. height_(height),
  293. width_(width),
  294. cursor_left_(cursor_left),
  295. show_cursor_(!rung.condition.has_value())
  296. {
  297. setPos(0, top);
  298. setFlag(ItemIsSelectable, true);
  299. setZValue(-2.0);
  300. }
  301. QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; }
  302. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override
  303. {
  304. const bool selected = (option->state & QStyle::State_Selected) != 0;
  305. if (selected)
  306. {
  307. painter->fillRect(boundingRect(), QColor(240, 247, 250, 90));
  308. if (show_cursor_)
  309. {
  310. painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine));
  311. painter->drawRect(QRectF(
  312. cursor_left_ + 3,
  313. kRungHeaderHeight + 3,
  314. kCellWidth - 6,
  315. kCellHeight - 6));
  316. }
  317. }
  318. painter->setPen(QColor(QStringLiteral("#62717b")));
  319. QString title = tr("网络 %1").arg(number_);
  320. if (!name_.isEmpty() && !name_.startsWith(tr("网络 ")))
  321. {
  322. title += QStringLiteral(":") + name_;
  323. }
  324. painter->drawText(
  325. QRectF(kSceneMargin + 8, 5, 320, 22),
  326. Qt::AlignLeft | Qt::AlignVCenter,
  327. title);
  328. painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));
  329. painter->drawLine(
  330. QPointF(kSceneMargin + 8, height_ - 1),
  331. QPointF(kSceneMargin + width_ - 8, height_ - 1));
  332. }
  333. const std::string &rungId() const { return rung_id_; }
  334. private:
  335. std::string rung_id_;
  336. QString name_;
  337. int number_ = 0;
  338. qreal height_ = 0.0;
  339. qreal width_ = 0.0;
  340. qreal cursor_left_ = 0.0;
  341. bool show_cursor_ = false;
  342. };
  343. namespace {
  344. RenderResult renderExpression(
  345. QGraphicsScene &scene,
  346. const ConditionExpression &expression,
  347. const std::string &rung_id,
  348. const QPointF &top_left,
  349. const ExpressionMetrics &metrics,
  350. const LogicTraceSnapshot &trace,
  351. bool trace_enabled,
  352. const std::string &fault_node_id)
  353. {
  354. const bool active = trace_enabled && traceValue(
  355. trace, &LogicTraceSnapshot::expressionValues, expression.id);
  356. if (expression.kind == ConditionExpressionKind::Node)
  357. {
  358. const QPointF center(
  359. top_left.x() + kCellWidth / 2.0,
  360. top_left.y() + kCellHeight / 2.0);
  361. const bool node_active = trace_enabled && traceValue(
  362. trace, &LogicTraceSnapshot::nodeValues, expression.node->id);
  363. scene.addLine(
  364. QLineF(
  365. QPointF(top_left.x(), center.y()),
  366. QPointF(top_left.x() + kCellWidth, center.y())),
  367. ladderPen(node_active));
  368. scene.addItem(new LogicEditorWidget::NodeItem(
  369. *expression.node,
  370. rung_id,
  371. center,
  372. node_active,
  373. expression.node->id == fault_node_id));
  374. return {
  375. QPointF(top_left.x(), center.y()),
  376. QPointF(top_left.x() + kCellWidth, center.y())};
  377. }
  378. if (expression.kind == ConditionExpressionKind::Series)
  379. {
  380. qreal x = top_left.x();
  381. RenderResult first;
  382. RenderResult previous;
  383. for (std::size_t index = 0; index < expression.children.size(); ++index)
  384. {
  385. const ExpressionMetrics child_metrics = measureExpression(expression.children[index]);
  386. const RenderResult current = renderExpression(
  387. scene,
  388. expression.children[index],
  389. rung_id,
  390. QPointF(x, top_left.y()),
  391. child_metrics,
  392. trace,
  393. trace_enabled,
  394. fault_node_id);
  395. if (index == 0U)
  396. {
  397. first = current;
  398. }
  399. else
  400. {
  401. scene.addLine(QLineF(previous.output, current.input), ladderPen(active));
  402. }
  403. previous = current;
  404. x += static_cast<qreal>(child_metrics.columns) * kCellWidth;
  405. }
  406. return {first.input, previous.output};
  407. }
  408. qreal y = top_left.y();
  409. std::vector<RenderResult> branches;
  410. branches.reserve(expression.children.size());
  411. for (const ConditionExpression &child : expression.children)
  412. {
  413. const ExpressionMetrics child_metrics = measureExpression(child);
  414. branches.push_back(renderExpression(
  415. scene,
  416. child,
  417. rung_id,
  418. QPointF(top_left.x(), y),
  419. child_metrics,
  420. trace,
  421. trace_enabled,
  422. fault_node_id));
  423. y += static_cast<qreal>(child_metrics.rows) * kCellHeight;
  424. }
  425. const qreal left_join = top_left.x();
  426. const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth;
  427. const qreal top_y = branches.front().input.y();
  428. const qreal bottom_y = branches.back().input.y();
  429. scene.addLine(QLineF(left_join, top_y, left_join, bottom_y), ladderPen(active));
  430. scene.addLine(QLineF(right_join, top_y, right_join, bottom_y), ladderPen(active));
  431. for (std::size_t index = 0; index < branches.size(); ++index)
  432. {
  433. const bool branch_active = trace_enabled && traceValue(
  434. trace,
  435. &LogicTraceSnapshot::expressionValues,
  436. expression.children[index].id);
  437. scene.addLine(
  438. QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input),
  439. ladderPen(branch_active));
  440. scene.addLine(
  441. QLineF(branches[index].output, QPointF(right_join, branches[index].output.y())),
  442. ladderPen(branch_active));
  443. }
  444. return {QPointF(left_join, top_y), QPointF(right_join, top_y)};
  445. }
  446. } // namespace
  447. LogicEditorWidget::LogicEditorWidget(LogicEditorService &editor_service, QWidget *parent)
  448. : QGraphicsView(parent), editor_service_(editor_service)
  449. {
  450. scene_ = new QGraphicsScene(this);
  451. setScene(scene_);
  452. setRenderHint(QPainter::Antialiasing, true);
  453. setBackgroundBrush(Qt::white);
  454. setDragMode(QGraphicsView::RubberBandDrag);
  455. setAlignment(Qt::AlignLeft | Qt::AlignTop);
  456. connect(scene_, &QGraphicsScene::selectionChanged,
  457. this, &LogicEditorWidget::handleSelectionChanged);
  458. }
  459. void LogicEditorWidget::setLogicId(const std::string &logic_id)
  460. {
  461. if (logic_id_ == logic_id)
  462. {
  463. return;
  464. }
  465. logic_id_ = logic_id;
  466. current_rung_id_.clear();
  467. reloadLogic();
  468. }
  469. void LogicEditorWidget::setEditingEnabled(bool enabled)
  470. {
  471. editing_enabled_ = enabled;
  472. setInteractive(enabled);
  473. }
  474. void LogicEditorWidget::setRuntimeTrace(
  475. const LogicTraceSnapshot &trace, const std::string &fault_node_id)
  476. {
  477. trace_ = trace;
  478. fault_node_id_ = fault_node_id;
  479. runtime_trace_enabled_ = true;
  480. reloadLogic();
  481. }
  482. void LogicEditorWidget::clearRuntimeTrace()
  483. {
  484. trace_.clear();
  485. fault_node_id_.clear();
  486. runtime_trace_enabled_ = false;
  487. reloadLogic();
  488. }
  489. void LogicEditorWidget::reloadLogic()
  490. {
  491. scene_->clear();
  492. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  493. if (logic == nullptr)
  494. {
  495. scene_->setSceneRect(0, 0, kMinimumSceneWidth, 400);
  496. return;
  497. }
  498. if (current_rung_id_.empty() && !logic->rungs.empty())
  499. {
  500. current_rung_id_ = logic->rungs.front().id;
  501. }
  502. int condition_columns = 1;
  503. for (const LadderRung &rung : logic->rungs)
  504. {
  505. if (rung.condition.has_value())
  506. {
  507. condition_columns = std::max(
  508. condition_columns,
  509. measureExpression(*rung.condition).columns);
  510. }
  511. }
  512. const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 2);
  513. const qreal left_rail_x = kSceneMargin + kRailInset;
  514. const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth;
  515. const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin);
  516. qreal top = kSceneMargin;
  517. int number = 1;
  518. for (const LadderRung &rung : logic->rungs)
  519. {
  520. const ExpressionMetrics metrics = rung.condition.has_value()
  521. ? measureExpression(*rung.condition) : ExpressionMetrics{};
  522. const int grid_rows = std::max(1, metrics.rows);
  523. const qreal grid_top = top + kRungHeaderHeight;
  524. const qreal rung_height = kRungHeaderHeight
  525. + static_cast<qreal>(grid_rows) * kCellHeight + 12.0;
  526. scene_->addItem(new RungItem(
  527. rung,
  528. number,
  529. top,
  530. rung_height,
  531. scene_width - 2.0 * kSceneMargin,
  532. left_rail_x));
  533. addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows);
  534. scene_->addLine(
  535. QLineF(left_rail_x, grid_top, left_rail_x,
  536. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  537. QPen(kLadderColor, 2.4));
  538. scene_->addLine(
  539. QLineF(right_rail_x, grid_top, right_rail_x,
  540. grid_top + static_cast<qreal>(grid_rows) * kCellHeight),
  541. QPen(kLadderColor, 2.4));
  542. const qreal main_y = grid_top + kCellHeight / 2.0;
  543. QPointF expression_output(left_rail_x, main_y);
  544. if (rung.condition.has_value())
  545. {
  546. const RenderResult rendered = renderExpression(
  547. *scene_,
  548. *rung.condition,
  549. rung.id,
  550. QPointF(left_rail_x, grid_top),
  551. metrics,
  552. trace_,
  553. runtime_trace_enabled_,
  554. fault_node_id_);
  555. expression_output = rendered.output;
  556. }
  557. else
  558. {
  559. addPlaceholder(
  560. *scene_,
  561. QRectF(left_rail_x, grid_top, kCellWidth, kCellHeight),
  562. tr("添加条件"));
  563. }
  564. const bool rung_active = runtime_trace_enabled_ && traceValue(
  565. trace_, &LogicTraceSnapshot::rungValues, rung.id);
  566. const qreal output_left = right_rail_x - kCellWidth;
  567. scene_->addLine(
  568. QLineF(expression_output, QPointF(output_left, main_y)),
  569. ladderPen(rung_active));
  570. if (rung.output.has_value())
  571. {
  572. scene_->addLine(
  573. QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)),
  574. ladderPen(rung_active));
  575. scene_->addItem(new NodeItem(
  576. *rung.output,
  577. rung.id,
  578. QPointF(output_left + kCellWidth / 2.0, main_y),
  579. rung_active,
  580. rung.output->id == fault_node_id_));
  581. }
  582. else
  583. {
  584. addPlaceholder(
  585. *scene_,
  586. QRectF(output_left, grid_top, kCellWidth, kCellHeight),
  587. tr("输出线圈"));
  588. scene_->addLine(
  589. QLineF(
  590. QPointF(output_left + kCellWidth, main_y),
  591. QPointF(right_rail_x, main_y)),
  592. ladderPen(rung_active));
  593. }
  594. top += rung_height + kRungGap;
  595. ++number;
  596. }
  597. scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin));
  598. }
  599. void LogicEditorWidget::selectNode(const std::string &node_id)
  600. {
  601. for (QGraphicsItem *item : scene_->items())
  602. {
  603. if (NodeItem *node = dynamic_cast<NodeItem *>(item))
  604. {
  605. node->setSelected(node->nodeId() == node_id);
  606. if (node->nodeId() == node_id)
  607. {
  608. current_rung_id_ = node->rungId();
  609. ensureVisible(node);
  610. }
  611. }
  612. else
  613. {
  614. item->setSelected(false);
  615. }
  616. }
  617. }
  618. std::string LogicEditorWidget::selectedNodeId() const
  619. {
  620. const std::vector<std::string> ids = selectedNodeIds();
  621. return ids.size() == 1U ? ids.front() : std::string{};
  622. }
  623. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  624. {
  625. std::vector<std::pair<QPointF, std::string>> positioned_ids;
  626. for (QGraphicsItem *item : scene_->selectedItems())
  627. {
  628. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  629. {
  630. positioned_ids.emplace_back(node->scenePos(), node->nodeId());
  631. }
  632. }
  633. std::sort(
  634. positioned_ids.begin(), positioned_ids.end(),
  635. [](const auto &left, const auto &right)
  636. {
  637. if (!qFuzzyCompare(left.first.y(), right.first.y()))
  638. {
  639. return left.first.y() < right.first.y();
  640. }
  641. return left.first.x() < right.first.x();
  642. });
  643. std::vector<std::string> ids;
  644. ids.reserve(positioned_ids.size());
  645. for (const auto &positioned_id : positioned_ids)
  646. {
  647. ids.push_back(positioned_id.second);
  648. }
  649. return ids;
  650. }
  651. std::string LogicEditorWidget::selectedRungId() const
  652. {
  653. std::string rung_id;
  654. for (QGraphicsItem *item : scene_->selectedItems())
  655. {
  656. if (const NodeItem *node = dynamic_cast<const NodeItem *>(item))
  657. {
  658. if (!rung_id.empty() && rung_id != node->rungId())
  659. {
  660. return {};
  661. }
  662. rung_id = node->rungId();
  663. }
  664. else if (const RungItem *rung = dynamic_cast<const RungItem *>(item))
  665. {
  666. if (rung_id.empty())
  667. {
  668. rung_id = rung->rungId();
  669. }
  670. }
  671. }
  672. return rung_id;
  673. }
  674. LogicEditorResult LogicEditorWidget::addRung()
  675. {
  676. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  677. if (result.succeeded)
  678. {
  679. current_rung_id_ = result.id;
  680. reloadLogic();
  681. emit graphChanged();
  682. }
  683. else
  684. {
  685. reportFailure(result);
  686. }
  687. return result;
  688. }
  689. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  690. {
  691. const std::vector<std::string> selected_ids = selectedNodeIds();
  692. LogicEditorResult result;
  693. if (selected_ids.size() > 1U)
  694. {
  695. result = {false, LogicEditorError::InvalidOperation,
  696. "串联插入时只能选择一个节点", {}};
  697. }
  698. else if (selected_ids.size() == 1U)
  699. {
  700. const LogicNode *selected_node = editor_service_.findNode(
  701. logic_id_, selected_ids.front());
  702. result = selected_node != nullptr && selected_node->isCondition()
  703. ? editor_service_.insertConditionAfter(
  704. logic_id_, currentRungId(), selected_ids.front(), config)
  705. : editor_service_.appendCondition(logic_id_, currentRungId(), config);
  706. }
  707. else
  708. {
  709. result = editor_service_.appendCondition(logic_id_, currentRungId(), config);
  710. }
  711. if (result.succeeded)
  712. {
  713. reloadLogic();
  714. selectNode(result.id);
  715. emit graphChanged();
  716. }
  717. else
  718. {
  719. reportFailure(result);
  720. }
  721. return result;
  722. }
  723. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  724. {
  725. const std::string rung_id = selectedRungId();
  726. const std::vector<std::string> selected_ids = selectedNodeIds();
  727. std::vector<std::string> condition_ids;
  728. for (const std::string &node_id : selected_ids)
  729. {
  730. const LogicNode *node = editor_service_.findNode(logic_id_, node_id);
  731. if (node != nullptr && node->isCondition())
  732. {
  733. condition_ids.push_back(node_id);
  734. }
  735. }
  736. LogicEditorResult result;
  737. if (rung_id.empty() || condition_ids.empty())
  738. {
  739. result = {false, LogicEditorError::InvalidOperation,
  740. "请在同一网络中选择要并联的连续节点", {}};
  741. }
  742. else
  743. {
  744. result = editor_service_.addParallelBranch(
  745. logic_id_, rung_id, condition_ids, config);
  746. }
  747. if (result.succeeded)
  748. {
  749. reloadLogic();
  750. selectNode(result.id);
  751. emit graphChanged();
  752. }
  753. else
  754. {
  755. reportFailure(result);
  756. }
  757. return result;
  758. }
  759. LogicEditorResult LogicEditorWidget::setOutput(const LogicNodeConfig &config)
  760. {
  761. const LogicEditorResult result = editor_service_.setOutput(
  762. logic_id_, currentRungId(), config);
  763. if (result.succeeded)
  764. {
  765. reloadLogic();
  766. selectNode(result.id);
  767. emit graphChanged();
  768. }
  769. else
  770. {
  771. reportFailure(result);
  772. }
  773. return result;
  774. }
  775. LogicEditorResult LogicEditorWidget::deleteSelected()
  776. {
  777. const std::vector<std::string> node_ids = selectedNodeIds();
  778. LogicEditorResult result;
  779. if (!node_ids.empty())
  780. {
  781. for (const std::string &node_id : node_ids)
  782. {
  783. result = editor_service_.removeNode(logic_id_, node_id);
  784. if (!result.succeeded)
  785. {
  786. break;
  787. }
  788. }
  789. }
  790. else
  791. {
  792. const std::string rung_id = selectedRungId();
  793. if (rung_id.empty())
  794. {
  795. return {false, LogicEditorError::InvalidOperation,
  796. "请先选择要删除的逻辑节点或网络", {}};
  797. }
  798. result = editor_service_.removeRung(logic_id_, rung_id);
  799. if (result.succeeded && current_rung_id_ == rung_id)
  800. {
  801. current_rung_id_ = editor_service_.firstRungId(logic_id_);
  802. }
  803. }
  804. if (result.succeeded)
  805. {
  806. reloadLogic();
  807. emit nodeSelected({});
  808. emit graphChanged();
  809. }
  810. else
  811. {
  812. reportFailure(result);
  813. }
  814. return result;
  815. }
  816. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  817. {
  818. QGraphicsView::resizeEvent(event);
  819. }
  820. void LogicEditorWidget::handleSelectionChanged()
  821. {
  822. const std::string rung_id = selectedRungId();
  823. if (!rung_id.empty())
  824. {
  825. current_rung_id_ = rung_id;
  826. }
  827. emit nodeSelected(QString::fromStdString(selectedNodeId()));
  828. }
  829. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  830. {
  831. emit editorError(QString::fromStdString(result.message));
  832. }
  833. std::string LogicEditorWidget::currentRungId() const
  834. {
  835. const std::string selected = selectedRungId();
  836. if (!selected.empty())
  837. {
  838. return selected;
  839. }
  840. return current_rung_id_.empty()
  841. ? editor_service_.firstRungId(logic_id_) : current_rung_id_;
  842. }