综合平台编程器项目的远程存储
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

2828 rindas
93 KiB

  1. #include "logic_editor_widget.h"
  2. #include <QApplication>
  3. #include <QBrush>
  4. #include <QCompleter>
  5. #include <QEvent>
  6. #include <QGraphicsItem>
  7. #include <QGraphicsLineItem>
  8. #include <QGraphicsRectItem>
  9. #include <QGraphicsScene>
  10. #include <QHeaderView>
  11. #include <QKeyEvent>
  12. #include <QLineEdit>
  13. #include <QMouseEvent>
  14. #include <QPainter>
  15. #include <QPainterPathStroker>
  16. #include <QPen>
  17. #include <QRegularExpression>
  18. #include <QResizeEvent>
  19. #include <QRubberBand>
  20. #include <QStandardItemModel>
  21. #include <QStyleOptionGraphicsItem>
  22. #include <QTimer>
  23. #include <QTreeView>
  24. #include <algorithm>
  25. #include <type_traits>
  26. namespace {
  27. constexpr qreal kLabelWidth = 60.0;
  28. constexpr qreal kCellWidth = 96.0;
  29. constexpr qreal kOutputWidth = 224.0;
  30. constexpr qreal kRowHeight = 78.0;
  31. constexpr qreal kCommentBandHeight = 28.0;
  32. constexpr qreal kTop = 18.0;
  33. constexpr qreal kLeftBus = kLabelWidth;
  34. constexpr qreal kConditionRight = kLabelWidth
  35. + ProjectLimits::kMaximumConditionColumns * kCellWidth;
  36. constexpr qreal kRightBus = kConditionRight + kOutputWidth;
  37. constexpr qreal kSceneRightMargin = 24.0;
  38. const QColor kCanvasBackground(QStringLiteral("#ffffff"));
  39. const QColor kGridColor(QStringLiteral("#d8e0e4"));
  40. const QColor kLadderColor(QStringLiteral("#263842"));
  41. const QColor kActiveColor(QStringLiteral("#16854f"));
  42. const QColor kFaultColor(QStringLiteral("#c5362e"));
  43. const QColor kCommentColor(QStringLiteral("#16854f"));
  44. const QColor kSelectionFill(QStringLiteral("#dfeef5"));
  45. const QColor kSelectionBorder(QStringLiteral("#277da1"));
  46. QString registerAddressText(const RegisterAddress &address)
  47. {
  48. return QString::fromStdString(address.toString());
  49. }
  50. QString wordOperandText(const WordOperand &operand)
  51. {
  52. return operand.kind == WordOperandKind::Register
  53. ? registerAddressText(operand.address)
  54. : QString::number(operand.constant);
  55. }
  56. QString comparisonText(ComparisonOperator comparison)
  57. {
  58. switch (comparison)
  59. {
  60. case ComparisonOperator::Equal: return QStringLiteral("=");
  61. case ComparisonOperator::NotEqual: return QStringLiteral("<>");
  62. case ComparisonOperator::LessThan: return QStringLiteral("<");
  63. case ComparisonOperator::LessThanOrEqual: return QStringLiteral("<=");
  64. case ComparisonOperator::GreaterThan: return QStringLiteral(">");
  65. case ComparisonOperator::GreaterThanOrEqual: return QStringLiteral(">=");
  66. }
  67. return QStringLiteral("?");
  68. }
  69. QString logicCommandText(const LogicNodeConfig &config)
  70. {
  71. return std::visit(
  72. [](const auto &value) -> QString
  73. {
  74. using Config = std::decay_t<decltype(value)>;
  75. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  76. {
  77. return QStringLiteral("%1 %2")
  78. .arg(value.mode == ContactMode::NormallyOpen
  79. ? QStringLiteral("LD") : QStringLiteral("LDI"))
  80. .arg(registerAddressText(value.address));
  81. }
  82. else if constexpr (std::is_same_v<Config, EdgeContactNodeConfig>)
  83. {
  84. return QStringLiteral("%1 %2")
  85. .arg(value.mode == EdgeMode::Rising
  86. ? QStringLiteral("LDP") : QStringLiteral("LDF"))
  87. .arg(registerAddressText(value.address));
  88. }
  89. else if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  90. {
  91. const QString mnemonic = value.mode == CoilMode::Set
  92. ? QStringLiteral("SET")
  93. : value.mode == CoilMode::Reset
  94. ? QStringLiteral("RST") : QStringLiteral("OUT");
  95. return QStringLiteral("%1 %2")
  96. .arg(mnemonic)
  97. .arg(registerAddressText(value.address));
  98. }
  99. else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
  100. {
  101. return QStringLiteral("LD%1 %2 %3")
  102. .arg(comparisonText(value.comparison))
  103. .arg(registerAddressText(value.address))
  104. .arg(value.value);
  105. }
  106. else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
  107. {
  108. return QStringLiteral("MOV %1 %2")
  109. .arg(wordOperandText(value.source))
  110. .arg(registerAddressText(value.destination));
  111. }
  112. else
  113. {
  114. return QStringLiteral("%1 %2 %3 %4")
  115. .arg(value.operation == ArithmeticOperation::Add
  116. ? QStringLiteral("ADD") : QStringLiteral("SUB"))
  117. .arg(wordOperandText(value.left))
  118. .arg(wordOperandText(value.right))
  119. .arg(registerAddressText(value.destination));
  120. }
  121. },
  122. config);
  123. }
  124. QString commandCompletionPrefix(const QString &text)
  125. {
  126. return text.section(QRegularExpression(QStringLiteral("\\s+")), 0, 0)
  127. .toUpper();
  128. }
  129. QPen ladderPen(bool active, bool faulted = false)
  130. {
  131. QPen pen(faulted ? kFaultColor : active ? kActiveColor : kLadderColor);
  132. pen.setWidthF(active || faulted ? 2.5 : 1.8);
  133. pen.setJoinStyle(Qt::MiterJoin);
  134. pen.setCapStyle(Qt::SquareCap);
  135. return pen;
  136. }
  137. bool containsId(
  138. const std::vector<std::string> &ids, const std::string &candidate)
  139. {
  140. return std::find(ids.cbegin(), ids.cend(), candidate) != ids.cend();
  141. }
  142. bool containsCell(
  143. const std::vector<std::pair<std::string, int>> &cells,
  144. const std::pair<std::string, int> &candidate)
  145. {
  146. return std::find(cells.cbegin(), cells.cend(), candidate) != cells.cend();
  147. }
  148. struct GridRow
  149. {
  150. qreal top = 0.0;
  151. };
  152. class GridLayerItem final : public QGraphicsItem
  153. {
  154. public:
  155. explicit GridLayerItem(std::vector<GridRow> rows)
  156. : rows_(std::move(rows))
  157. {
  158. setZValue(0.0);
  159. }
  160. QRectF boundingRect() const override
  161. {
  162. if (rows_.empty())
  163. {
  164. return {};
  165. }
  166. return {
  167. kLeftBus,
  168. rows_.front().top,
  169. kRightBus - kLeftBus,
  170. rows_.back().top + kRowHeight - rows_.front().top};
  171. }
  172. void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
  173. {
  174. painter->setRenderHint(QPainter::Antialiasing, false);
  175. QPen grid_pen(kGridColor);
  176. grid_pen.setWidthF(1.0);
  177. grid_pen.setCosmetic(true);
  178. painter->setPen(grid_pen);
  179. for (const GridRow &row : rows_)
  180. {
  181. painter->fillRect(
  182. QRectF(
  183. kLeftBus,
  184. row.top,
  185. kRightBus - kLeftBus,
  186. kRowHeight),
  187. kCanvasBackground);
  188. painter->drawRect(
  189. QRectF(
  190. kLeftBus,
  191. row.top,
  192. kRightBus - kLeftBus,
  193. kRowHeight));
  194. for (int boundary = 1;
  195. boundary <= ProjectLimits::kMaximumConditionColumns;
  196. ++boundary)
  197. {
  198. const qreal x = kLeftBus
  199. + static_cast<qreal>(boundary) * kCellWidth;
  200. painter->drawLine(
  201. QPointF(x, row.top),
  202. QPointF(x, row.top + kRowHeight));
  203. }
  204. }
  205. }
  206. private:
  207. std::vector<GridRow> rows_;
  208. };
  209. class CellContentItem final : public QGraphicsItem
  210. {
  211. public:
  212. CellContentItem(
  213. const LadderCell &cell,
  214. const std::string &rung_id,
  215. int column,
  216. const QPointF &top_left,
  217. bool input_active,
  218. bool output_active,
  219. bool faulted)
  220. : cell_(cell), input_active_(input_active),
  221. output_active_(output_active), faulted_(faulted)
  222. {
  223. setPos(top_left);
  224. setZValue(10.0);
  225. setData(0, QStringLiteral("cell"));
  226. setData(1, QString::fromStdString(rung_id));
  227. setData(2, column);
  228. setData(3, QString::fromStdString(
  229. cell.node.has_value() ? cell.node->id : cell.id));
  230. setData(4, static_cast<int>(cell.kind));
  231. setData(5, QString::fromStdString(cell.id));
  232. setData(6, input_active_);
  233. setData(7, output_active_);
  234. }
  235. QRectF boundingRect() const override
  236. {
  237. return {0.0, 0.0, kCellWidth, kRowHeight};
  238. }
  239. void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
  240. {
  241. if (cell_.kind == LadderCellKind::Gap)
  242. {
  243. return;
  244. }
  245. painter->setRenderHint(QPainter::Antialiasing, true);
  246. const auto use_pen = [this, painter](bool active)
  247. {
  248. painter->setPen(ladderPen(active, faulted_));
  249. };
  250. const qreal center_x = kCellWidth / 2.0;
  251. const qreal center_y = kRowHeight / 2.0 + 6.0;
  252. if (cell_.kind == LadderCellKind::Wire || !cell_.node.has_value())
  253. {
  254. use_pen(input_active_);
  255. painter->drawLine(
  256. QPointF(0.0, center_y), QPointF(center_x, center_y));
  257. use_pen(output_active_);
  258. painter->drawLine(
  259. QPointF(center_x, center_y), QPointF(kCellWidth, center_y));
  260. return;
  261. }
  262. const LogicNode &node = *cell_.node;
  263. QFont font = painter->font();
  264. font.setPointSizeF(8.5);
  265. painter->setFont(font);
  266. std::visit(
  267. [&](const auto &config)
  268. {
  269. using Config = std::decay_t<decltype(config)>;
  270. if constexpr (std::is_same_v<Config, ContactNodeConfig>
  271. || std::is_same_v<Config, EdgeContactNodeConfig>)
  272. {
  273. const qreal left = center_x - 15.0;
  274. const qreal right = center_x + 15.0;
  275. use_pen(input_active_);
  276. painter->drawLine(QPointF(0.0, center_y), QPointF(left, center_y));
  277. use_pen(output_active_);
  278. painter->drawLine(QPointF(right, center_y), QPointF(kCellWidth, center_y));
  279. painter->drawLine(QPointF(left, center_y - 14.0), QPointF(left, center_y + 14.0));
  280. painter->drawLine(QPointF(right, center_y - 14.0), QPointF(right, center_y + 14.0));
  281. if constexpr (std::is_same_v<Config, ContactNodeConfig>)
  282. {
  283. if (config.mode == ContactMode::NormallyClosed)
  284. {
  285. painter->drawLine(
  286. QPointF(left - 4.0, center_y + 17.0),
  287. QPointF(right + 4.0, center_y - 17.0));
  288. }
  289. }
  290. else
  291. {
  292. painter->drawText(
  293. QRectF(center_x - 13.0, center_y - 12.0, 26.0, 24.0),
  294. Qt::AlignCenter,
  295. config.mode == EdgeMode::Rising
  296. ? QStringLiteral("P") : QStringLiteral("N"));
  297. }
  298. painter->drawText(
  299. QRectF(2.0, 3.0, kCellWidth - 4.0, 20.0),
  300. Qt::AlignCenter,
  301. node.configured
  302. ? registerAddressText(config.address)
  303. : QStringLiteral("<M>"));
  304. }
  305. else if constexpr (std::is_same_v<Config, CompareNodeConfig>)
  306. {
  307. const QRectF box(7.0, center_y - 15.0, kCellWidth - 14.0, 30.0);
  308. use_pen(input_active_);
  309. painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
  310. use_pen(output_active_);
  311. painter->drawLine(QPointF(box.right(), center_y), QPointF(kCellWidth, center_y));
  312. painter->drawRect(box);
  313. QFont compare_font = painter->font();
  314. compare_font.setPointSizeF(7.5);
  315. painter->setFont(compare_font);
  316. painter->drawText(
  317. box.adjusted(2.0, 0.0, -2.0, 0.0),
  318. Qt::AlignCenter,
  319. node.configured
  320. ? QStringLiteral("%1 %2 %3")
  321. .arg(registerAddressText(config.address))
  322. .arg(comparisonText(config.comparison))
  323. .arg(config.value)
  324. : QStringLiteral("<比较>"));
  325. }
  326. else
  327. {
  328. use_pen(input_active_);
  329. painter->drawLine(
  330. QPointF(0.0, center_y), QPointF(center_x, center_y));
  331. use_pen(output_active_);
  332. painter->drawLine(
  333. QPointF(center_x, center_y), QPointF(kCellWidth, center_y));
  334. }
  335. },
  336. node.config);
  337. }
  338. private:
  339. LadderCell cell_;
  340. bool input_active_ = false;
  341. bool output_active_ = false;
  342. bool faulted_ = false;
  343. };
  344. class OutputContentItem final : public QGraphicsItem
  345. {
  346. public:
  347. OutputContentItem(
  348. const LadderRung &rung,
  349. const QPointF &top_left,
  350. bool input_active,
  351. bool symbol_active,
  352. bool faulted)
  353. : rung_id_(rung.id), output_(rung.output),
  354. input_active_(input_active), symbol_active_(symbol_active),
  355. faulted_(faulted)
  356. {
  357. setPos(top_left);
  358. setZValue(10.0);
  359. setData(0, QStringLiteral("output"));
  360. setData(1, QString::fromStdString(rung.id));
  361. setData(2, QString::fromStdString(
  362. rung.output.has_value() ? rung.output->id : std::string{}));
  363. setData(3, input_active_);
  364. setData(4, symbol_active_);
  365. }
  366. QRectF boundingRect() const override
  367. {
  368. return {0.0, 0.0, kOutputWidth, kRowHeight};
  369. }
  370. void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
  371. {
  372. if (!output_.has_value())
  373. {
  374. return;
  375. }
  376. painter->setRenderHint(QPainter::Antialiasing, true);
  377. const auto use_pen = [this, painter](bool active)
  378. {
  379. painter->setPen(ladderPen(active, faulted_));
  380. };
  381. const qreal center_y = kRowHeight / 2.0 + 6.0;
  382. const LogicNode &node = *output_;
  383. QFont font = painter->font();
  384. font.setPointSizeF(8.5);
  385. painter->setFont(font);
  386. std::visit(
  387. [&](const auto &config)
  388. {
  389. using Config = std::decay_t<decltype(config)>;
  390. if constexpr (std::is_same_v<Config, CoilNodeConfig>)
  391. {
  392. const qreal center_x = kOutputWidth / 2.0;
  393. use_pen(input_active_);
  394. painter->drawLine(QPointF(0.0, center_y), QPointF(center_x - 22.0, center_y));
  395. painter->drawLine(QPointF(center_x + 22.0, center_y), QPointF(kOutputWidth, center_y));
  396. use_pen(symbol_active_);
  397. QPainterPath left;
  398. left.moveTo(center_x - 3.0, center_y - 18.0);
  399. left.cubicTo(
  400. center_x - 25.0, center_y - 14.0,
  401. center_x - 25.0, center_y + 14.0,
  402. center_x - 3.0, center_y + 18.0);
  403. painter->drawPath(left);
  404. QPainterPath right;
  405. right.moveTo(center_x + 3.0, center_y - 18.0);
  406. right.cubicTo(
  407. center_x + 25.0, center_y - 14.0,
  408. center_x + 25.0, center_y + 14.0,
  409. center_x + 3.0, center_y + 18.0);
  410. painter->drawPath(right);
  411. if (config.mode != CoilMode::Normal)
  412. {
  413. painter->drawText(
  414. QRectF(center_x - 13.0, center_y - 13.0, 26.0, 26.0),
  415. Qt::AlignCenter,
  416. config.mode == CoilMode::Set
  417. ? QStringLiteral("S") : QStringLiteral("R"));
  418. }
  419. painter->drawText(
  420. QRectF(2.0, 3.0, kOutputWidth - 4.0, 20.0),
  421. Qt::AlignCenter,
  422. node.configured
  423. ? registerAddressText(config.address)
  424. : QStringLiteral("<M>"));
  425. }
  426. else if constexpr (std::is_same_v<Config, MoveNodeConfig>)
  427. {
  428. const QRectF box(26.0, 11.0, kOutputWidth - 52.0, kRowHeight - 22.0);
  429. use_pen(input_active_);
  430. painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
  431. painter->drawLine(QPointF(box.right(), center_y), QPointF(kOutputWidth, center_y));
  432. use_pen(symbol_active_);
  433. painter->drawRect(box);
  434. QFont mnemonic_font = painter->font();
  435. mnemonic_font.setBold(true);
  436. painter->setFont(mnemonic_font);
  437. painter->drawText(
  438. QRectF(box.left(), box.top() + 2.0, box.width(), 20.0),
  439. Qt::AlignCenter,
  440. QStringLiteral("MOV"));
  441. mnemonic_font.setBold(false);
  442. mnemonic_font.setPointSizeF(8.0);
  443. painter->setFont(mnemonic_font);
  444. painter->drawText(
  445. QRectF(box.left() + 4.0, box.top() + 23.0, box.width() - 8.0, 20.0),
  446. Qt::AlignCenter,
  447. node.configured
  448. ? QStringLiteral("%1 -> %2")
  449. .arg(wordOperandText(config.source))
  450. .arg(registerAddressText(config.destination))
  451. : QStringLiteral("<源> -> <目标>"));
  452. }
  453. else if constexpr (std::is_same_v<Config, ArithmeticNodeConfig>)
  454. {
  455. const QRectF box(18.0, 11.0, kOutputWidth - 36.0, kRowHeight - 22.0);
  456. use_pen(input_active_);
  457. painter->drawLine(QPointF(0.0, center_y), QPointF(box.left(), center_y));
  458. painter->drawLine(QPointF(box.right(), center_y), QPointF(kOutputWidth, center_y));
  459. use_pen(symbol_active_);
  460. painter->drawRect(box);
  461. QFont mnemonic_font = painter->font();
  462. mnemonic_font.setBold(true);
  463. painter->setFont(mnemonic_font);
  464. painter->drawText(
  465. QRectF(box.left(), box.top() + 2.0, box.width(), 20.0),
  466. Qt::AlignCenter,
  467. config.operation == ArithmeticOperation::Add
  468. ? QStringLiteral("ADD") : QStringLiteral("SUB"));
  469. mnemonic_font.setBold(false);
  470. mnemonic_font.setPointSizeF(8.0);
  471. painter->setFont(mnemonic_font);
  472. painter->drawText(
  473. QRectF(box.left() + 4.0, box.top() + 23.0, box.width() - 8.0, 20.0),
  474. Qt::AlignCenter,
  475. node.configured
  476. ? QStringLiteral("%1, %2 -> %3")
  477. .arg(wordOperandText(config.left))
  478. .arg(wordOperandText(config.right))
  479. .arg(registerAddressText(config.destination))
  480. : QStringLiteral("<左>, <右> -> <目标>"));
  481. }
  482. else
  483. {
  484. use_pen(input_active_);
  485. painter->drawLine(
  486. QPointF(0.0, center_y), QPointF(kOutputWidth, center_y));
  487. }
  488. },
  489. node.config);
  490. }
  491. private:
  492. std::string rung_id_;
  493. std::optional<LogicNode> output_;
  494. bool input_active_ = false;
  495. bool symbol_active_ = false;
  496. bool faulted_ = false;
  497. };
  498. class VerticalConnectionItem final : public QGraphicsItem
  499. {
  500. public:
  501. VerticalConnectionItem(
  502. const VerticalConnection &connection,
  503. qreal x,
  504. qreal top,
  505. qreal bottom,
  506. bool active)
  507. : height_(bottom - top), active_(active)
  508. {
  509. setPos(x, top);
  510. setZValue(20.0);
  511. setData(0, QStringLiteral("vertical"));
  512. setData(1, QString::fromStdString(connection.id));
  513. setData(2, QString::fromStdString(connection.upperRungId));
  514. setData(3, connection.columnBoundary);
  515. setData(4, QString::fromStdString(connection.lowerRungId));
  516. setData(5, active_);
  517. }
  518. QRectF boundingRect() const override
  519. {
  520. return {-7.0, 0.0, 14.0, height_};
  521. }
  522. QPainterPath shape() const override
  523. {
  524. QPainterPath path;
  525. path.moveTo(0.0, 0.0);
  526. path.lineTo(0.0, height_);
  527. QPainterPathStroker stroker;
  528. stroker.setWidth(12.0);
  529. return stroker.createStroke(path);
  530. }
  531. void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
  532. {
  533. painter->setPen(ladderPen(active_));
  534. painter->drawLine(QPointF(0.0, 0.0), QPointF(0.0, height_));
  535. }
  536. private:
  537. qreal height_ = 0.0;
  538. bool active_ = false;
  539. };
  540. class SelectionOverlayItem final : public QGraphicsItem
  541. {
  542. public:
  543. explicit SelectionOverlayItem(std::vector<QRectF> rectangles)
  544. : rectangles_(std::move(rectangles))
  545. {
  546. for (const QRectF &rectangle : rectangles_)
  547. {
  548. bounds_ = bounds_.isNull() ? rectangle : bounds_.united(rectangle);
  549. }
  550. bounds_.adjust(-2.0, -2.0, 2.0, 2.0);
  551. setZValue(100.0);
  552. }
  553. QRectF boundingRect() const override
  554. {
  555. return bounds_;
  556. }
  557. void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
  558. {
  559. painter->setRenderHint(QPainter::Antialiasing, false);
  560. QPen pen(kSelectionBorder, 1.4, Qt::DashLine);
  561. pen.setCosmetic(true);
  562. painter->setPen(pen);
  563. painter->setBrush(QColor(
  564. kSelectionFill.red(),
  565. kSelectionFill.green(),
  566. kSelectionFill.blue(),
  567. 90));
  568. for (const QRectF &rectangle : rectangles_)
  569. {
  570. painter->drawRect(rectangle.adjusted(2.5, 2.5, -2.5, -2.5));
  571. }
  572. }
  573. private:
  574. std::vector<QRectF> rectangles_;
  575. QRectF bounds_;
  576. };
  577. } // namespace
  578. LogicEditorWidget::LogicEditorWidget(
  579. LogicEditorService &editor_service,
  580. QWidget *parent)
  581. : QGraphicsView(parent),
  582. editor_service_(editor_service),
  583. command_service_(editor_service),
  584. scene_(new QGraphicsScene(this))
  585. {
  586. setScene(scene_);
  587. setRenderHint(QPainter::Antialiasing, true);
  588. setBackgroundBrush(kCanvasBackground);
  589. setDragMode(QGraphicsView::NoDrag);
  590. setAlignment(Qt::AlignLeft | Qt::AlignTop);
  591. selection_band_ = new QRubberBand(QRubberBand::Rectangle, viewport());
  592. selection_band_->hide();
  593. command_editor_ = new QLineEdit(viewport());
  594. command_editor_->setObjectName(QStringLiteral("logicCommandInput"));
  595. command_editor_->setPlaceholderText(
  596. tr("输入 PLC 指令,例如 LD M0、AND M1、OUT M10"));
  597. command_editor_->setClearButtonEnabled(true);
  598. command_editor_->setVisible(false);
  599. command_editor_->setToolTip(
  600. tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
  601. command_completer_ = new QCompleter(command_editor_);
  602. auto *command_model = new QStandardItemModel(0, 3, command_completer_);
  603. command_model->setHeaderData(0, Qt::Horizontal, tr("指令"));
  604. command_model->setHeaderData(1, Qt::Horizontal, tr("操作数"));
  605. command_model->setHeaderData(2, Qt::Horizontal, tr("说明"));
  606. for (const LogicCommandSuggestion &suggestion
  607. : LogicCommandService::suggestions())
  608. {
  609. QList<QStandardItem *> row{
  610. new QStandardItem(QString::fromStdString(suggestion.mnemonic)),
  611. new QStandardItem(QString::fromStdString(suggestion.operand_hint)),
  612. new QStandardItem(QString::fromStdString(suggestion.description))};
  613. for (QStandardItem *item : row)
  614. {
  615. item->setEnabled(suggestion.supported);
  616. item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  617. }
  618. command_model->appendRow(row);
  619. }
  620. command_completer_->setModel(command_model);
  621. command_completer_->setCompletionColumn(0);
  622. command_completer_->setCaseSensitivity(Qt::CaseInsensitive);
  623. command_completer_->setCompletionMode(QCompleter::PopupCompletion);
  624. auto *command_popup = new QTreeView;
  625. command_popup->setRootIsDecorated(false);
  626. command_popup->setItemsExpandable(false);
  627. command_popup->setHeaderHidden(false);
  628. command_popup->setUniformRowHeights(true);
  629. command_popup->setAlternatingRowColors(true);
  630. command_popup->setMinimumSize(620, 300);
  631. command_completer_->setPopup(command_popup);
  632. command_popup->header()->setStretchLastSection(true);
  633. command_popup->setColumnWidth(0, 96);
  634. command_popup->setColumnWidth(1, 190);
  635. command_editor_->setCompleter(command_completer_);
  636. connect(
  637. command_editor_, &QLineEdit::textEdited,
  638. this,
  639. [this](const QString &text)
  640. {
  641. command_completer_->setCompletionPrefix(
  642. commandCompletionPrefix(text));
  643. command_completer_->complete(command_editor_->rect());
  644. });
  645. connect(
  646. command_editor_, &QLineEdit::returnPressed,
  647. this, &LogicEditorWidget::commitCommandInput);
  648. command_editor_->installEventFilter(this);
  649. }
  650. void LogicEditorWidget::setLogicId(const std::string &logic_id)
  651. {
  652. const bool logic_changed = logic_id_ != logic_id;
  653. clearGesture();
  654. logic_id_ = logic_id;
  655. if (logic_changed)
  656. {
  657. cancelCommandInput();
  658. selected_rung_id_.clear();
  659. selected_node_ids_.clear();
  660. selected_cells_.clear();
  661. selected_output_rung_ids_.clear();
  662. selected_vertical_connection_ids_.clear();
  663. selected_row_ids_.clear();
  664. selected_column_ = -1;
  665. selected_cell_ = false;
  666. selected_output_ = false;
  667. selected_boundary_ = false;
  668. selected_vertical_connection_id_.clear();
  669. }
  670. else if (!selected_rung_id_.empty()
  671. && editor_service_.findRung(logic_id_, selected_rung_id_) == nullptr)
  672. {
  673. selected_rung_id_.clear();
  674. selected_node_ids_.clear();
  675. selected_cells_.clear();
  676. selected_output_rung_ids_.clear();
  677. selected_vertical_connection_ids_.clear();
  678. selected_row_ids_.clear();
  679. selected_column_ = -1;
  680. selected_cell_ = false;
  681. selected_output_ = false;
  682. selected_boundary_ = false;
  683. selected_vertical_connection_id_.clear();
  684. }
  685. else
  686. {
  687. if (!selected_vertical_connection_id_.empty()
  688. && editor_service_.findConnection(
  689. logic_id_, selected_vertical_connection_id_) == nullptr)
  690. {
  691. selected_vertical_connection_id_.clear();
  692. selected_column_ = -1;
  693. selected_cell_ = false;
  694. selected_output_ = false;
  695. selected_boundary_ = false;
  696. }
  697. selected_cells_.erase(
  698. std::remove_if(
  699. selected_cells_.begin(),
  700. selected_cells_.end(),
  701. [this](const auto &position)
  702. {
  703. const LadderCell *cell = editor_service_.findCell(
  704. logic_id_, position.first, position.second);
  705. return cell == nullptr || cell->kind == LadderCellKind::Gap;
  706. }),
  707. selected_cells_.end());
  708. selected_output_rung_ids_.erase(
  709. std::remove_if(
  710. selected_output_rung_ids_.begin(),
  711. selected_output_rung_ids_.end(),
  712. [this](const std::string &rung_id)
  713. {
  714. const LadderRung *rung = editor_service_.findRung(
  715. logic_id_, rung_id);
  716. return rung == nullptr || !rung->output.has_value();
  717. }),
  718. selected_output_rung_ids_.end());
  719. selected_vertical_connection_ids_.erase(
  720. std::remove_if(
  721. selected_vertical_connection_ids_.begin(),
  722. selected_vertical_connection_ids_.end(),
  723. [this](const std::string &connection_id)
  724. {
  725. return editor_service_.findConnection(
  726. logic_id_, connection_id) == nullptr;
  727. }),
  728. selected_vertical_connection_ids_.end());
  729. selected_row_ids_.erase(
  730. std::remove_if(
  731. selected_row_ids_.begin(),
  732. selected_row_ids_.end(),
  733. [this](const std::string &rung_id)
  734. {
  735. return editor_service_.findRung(logic_id_, rung_id) == nullptr;
  736. }),
  737. selected_row_ids_.end());
  738. synchronizeSelectedNodes();
  739. selected_vertical_connection_id_ =
  740. selected_vertical_connection_ids_.empty()
  741. ? std::string{} : selected_vertical_connection_ids_.back();
  742. if (selected_cell_
  743. && editor_service_.findCell(
  744. logic_id_, selected_rung_id_, selected_column_)
  745. == nullptr)
  746. {
  747. selected_column_ = -1;
  748. selected_cell_ = false;
  749. }
  750. }
  751. rebuildScene();
  752. }
  753. void LogicEditorWidget::setEditingEnabled(bool enabled)
  754. {
  755. clearGesture();
  756. editing_enabled_ = enabled;
  757. if (!enabled)
  758. {
  759. cancelCommandInput();
  760. mouse_wire_mode_ = MouseWireMode::Select;
  761. selection_pressed_ = false;
  762. selection_dragging_ = false;
  763. selection_band_->hide();
  764. }
  765. rebuildScene();
  766. }
  767. void LogicEditorWidget::setMouseWireMode(MouseWireMode mode)
  768. {
  769. clearGesture();
  770. mouse_wire_mode_ = editing_enabled_ ? mode : MouseWireMode::Select;
  771. selection_pressed_ = false;
  772. selection_dragging_ = false;
  773. selection_band_->hide();
  774. viewport()->setCursor(mouse_wire_mode_ == MouseWireMode::Select
  775. ? Qt::ArrowCursor : Qt::CrossCursor);
  776. }
  777. LogicEditorWidget::MouseWireMode LogicEditorWidget::mouseWireMode() const
  778. {
  779. return mouse_wire_mode_;
  780. }
  781. void LogicEditorWidget::setRuntimeTrace(
  782. const LogicTraceSnapshot &trace,
  783. const std::string &fault_node_id)
  784. {
  785. trace_ = trace.forLogic(logic_id_);
  786. fault_node_id_ = fault_node_id;
  787. runtime_trace_enabled_ = true;
  788. rebuildScene();
  789. }
  790. void LogicEditorWidget::clearRuntimeTrace()
  791. {
  792. trace_.clear();
  793. fault_node_id_.clear();
  794. runtime_trace_enabled_ = false;
  795. rebuildScene();
  796. }
  797. void LogicEditorWidget::reloadLogic()
  798. {
  799. setLogicId(logic_id_);
  800. }
  801. void LogicEditorWidget::clearSelection()
  802. {
  803. selected_rung_id_.clear();
  804. selected_node_ids_.clear();
  805. selected_cells_.clear();
  806. selected_output_rung_ids_.clear();
  807. selected_vertical_connection_ids_.clear();
  808. selected_row_ids_.clear();
  809. selected_column_ = -1;
  810. selected_cell_ = false;
  811. selected_output_ = false;
  812. selected_boundary_ = false;
  813. selected_vertical_connection_id_.clear();
  814. rebuildScene();
  815. emit nodeSelected(QString{});
  816. }
  817. void LogicEditorWidget::selectNode(const std::string &node_id)
  818. {
  819. selected_node_ids_.clear();
  820. selected_cells_.clear();
  821. selected_output_rung_ids_.clear();
  822. selected_vertical_connection_ids_.clear();
  823. selected_row_ids_.clear();
  824. selected_column_ = -1;
  825. selected_cell_ = false;
  826. selected_output_ = false;
  827. selected_boundary_ = false;
  828. selected_vertical_connection_id_.clear();
  829. if (!node_id.empty())
  830. {
  831. selected_node_ids_.push_back(node_id);
  832. const std::string rung_id = editor_service_.rungIdForNode(
  833. logic_id_, node_id);
  834. const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
  835. if (rung != nullptr)
  836. {
  837. selected_rung_id_ = rung_id;
  838. for (std::size_t index = 0U; index < rung->cells.size(); ++index)
  839. {
  840. if (rung->cells[index].node.has_value()
  841. && rung->cells[index].node->id == node_id)
  842. {
  843. selected_column_ = static_cast<int>(index);
  844. selected_cell_ = true;
  845. selected_cells_.push_back({
  846. rung_id, static_cast<int>(index)});
  847. break;
  848. }
  849. }
  850. if (!selected_cell_ && rung->output.has_value()
  851. && rung->output->id == node_id)
  852. {
  853. selected_output_rung_ids_.push_back(rung_id);
  854. }
  855. }
  856. }
  857. rebuildScene();
  858. emit nodeSelected(QString::fromStdString(node_id));
  859. }
  860. void LogicEditorWidget::focusSyntaxLocation(
  861. const std::string &rung_id, int column)
  862. {
  863. const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
  864. if (rung == nullptr)
  865. {
  866. return;
  867. }
  868. cancelCommandInput();
  869. selected_node_ids_.clear();
  870. selected_cells_.clear();
  871. selected_output_rung_ids_.clear();
  872. selected_vertical_connection_ids_.clear();
  873. selected_row_ids_.clear();
  874. selected_vertical_connection_id_.clear();
  875. selected_rung_id_ = rung_id;
  876. selected_boundary_ = false;
  877. const bool output = column >= ProjectLimits::kMaximumLadderColumns;
  878. selected_output_ = output;
  879. selected_cell_ = !output;
  880. selected_column_ = output
  881. ? ProjectLimits::kMaximumConditionColumns
  882. : std::max(0, std::min(
  883. column - 1, ProjectLimits::kMaximumConditionColumns - 1));
  884. if (output)
  885. {
  886. selected_output_rung_ids_.push_back(rung_id);
  887. if (rung->output.has_value())
  888. {
  889. selected_node_ids_.push_back(rung->output->id);
  890. }
  891. }
  892. else
  893. {
  894. const LadderCell &cell = rung->cells[
  895. static_cast<std::size_t>(selected_column_)];
  896. if (cell.kind != LadderCellKind::Gap)
  897. {
  898. selected_cells_.push_back({rung_id, selected_column_});
  899. }
  900. if (cell.node.has_value())
  901. {
  902. selected_node_ids_.push_back(cell.node->id);
  903. }
  904. }
  905. rebuildScene();
  906. notifySelectionChanged();
  907. const RowLayout *layout = layoutForRung(rung_id);
  908. if (layout != nullptr)
  909. {
  910. const qreal x = output
  911. ? kConditionRight
  912. : kLeftBus + static_cast<qreal>(selected_column_) * kCellWidth;
  913. ensureVisible(
  914. QRectF(
  915. x,
  916. layout->gridTop,
  917. output ? kOutputWidth : kCellWidth,
  918. kRowHeight),
  919. 24,
  920. 24);
  921. }
  922. setFocus(Qt::OtherFocusReason);
  923. }
  924. std::string LogicEditorWidget::selectedNodeId() const
  925. {
  926. return selected_node_ids_.empty() ? std::string{} : selected_node_ids_.front();
  927. }
  928. std::vector<std::string> LogicEditorWidget::selectedNodeIds() const
  929. {
  930. return selected_node_ids_;
  931. }
  932. std::string LogicEditorWidget::selectedRungId() const
  933. {
  934. return !selected_rung_id_.empty()
  935. && editor_service_.findRung(logic_id_, selected_rung_id_) != nullptr
  936. ? selected_rung_id_ : std::string{};
  937. }
  938. bool LogicEditorWidget::hasCopyableSelection() const
  939. {
  940. return !selected_cells_.empty()
  941. || !selected_output_rung_ids_.empty()
  942. || !selected_vertical_connection_ids_.empty()
  943. || !selected_row_ids_.empty();
  944. }
  945. LogicClipboardCopyResult LogicEditorWidget::copySelection() const
  946. {
  947. LogicSelectionCopyRequest selection;
  948. selection.cells = selected_cells_;
  949. selection.outputRungIds = selected_output_rung_ids_;
  950. selection.verticalConnectionIds = selected_vertical_connection_ids_;
  951. selection.wholeRungIds = selected_row_ids_;
  952. return editor_service_.copySelection(logic_id_, selection);
  953. }
  954. LogicPasteTarget LogicEditorWidget::pasteTarget() const
  955. {
  956. LogicPasteTarget target;
  957. target.rungId = selected_rung_id_.empty()
  958. ? editor_service_.firstRungId(logic_id_)
  959. : selected_rung_id_;
  960. target.output = selected_output_;
  961. target.boundary = selected_boundary_;
  962. target.column = selected_output_
  963. ? ProjectLimits::kMaximumConditionColumns
  964. : std::max(selected_column_, 0);
  965. return target;
  966. }
  967. std::string LogicEditorWidget::currentRungId() const
  968. {
  969. if (!selected_rung_id_.empty()
  970. && editor_service_.findRung(logic_id_, selected_rung_id_) != nullptr)
  971. {
  972. return selected_rung_id_;
  973. }
  974. return editor_service_.firstRungId(logic_id_);
  975. }
  976. int LogicEditorWidget::rowAt(const std::string &rung_id) const
  977. {
  978. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  979. if (logic == nullptr)
  980. {
  981. return -1;
  982. }
  983. for (std::size_t index = 0U; index < logic->rungs.size(); ++index)
  984. {
  985. if (logic->rungs[index].id == rung_id)
  986. {
  987. return static_cast<int>(index);
  988. }
  989. }
  990. return -1;
  991. }
  992. void LogicEditorWidget::rebuildRowLayout(const ControlLogic &logic)
  993. {
  994. row_layouts_.clear();
  995. qreal next_top = kTop;
  996. for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
  997. {
  998. const bool network_head = logic.networkHeadIndex(row) == row;
  999. if (network_head)
  1000. {
  1001. next_top += kCommentBandHeight;
  1002. }
  1003. RowLayout layout;
  1004. layout.rungId = logic.rungs[row].id;
  1005. layout.row = static_cast<int>(row);
  1006. layout.gridTop = next_top;
  1007. layout.centerY = next_top + kRowHeight / 2.0 + 6.0;
  1008. layout.bottom = next_top + kRowHeight;
  1009. layout.networkHead = network_head;
  1010. row_layouts_.push_back(std::move(layout));
  1011. next_top += kRowHeight;
  1012. }
  1013. }
  1014. const LogicEditorWidget::RowLayout *LogicEditorWidget::layoutForRung(
  1015. const std::string &rung_id) const
  1016. {
  1017. const auto found = std::find_if(
  1018. row_layouts_.cbegin(),
  1019. row_layouts_.cend(),
  1020. [&rung_id](const RowLayout &layout)
  1021. {
  1022. return layout.rungId == rung_id;
  1023. });
  1024. return found == row_layouts_.cend() ? nullptr : &*found;
  1025. }
  1026. LogicEditorWidget::Hit LogicEditorWidget::hitAt(
  1027. const QPointF &scene_position) const
  1028. {
  1029. const QList<QGraphicsItem *> items = scene_->items(scene_position);
  1030. const auto hit_for_type = [&items](const QString &wanted) -> Hit
  1031. {
  1032. for (QGraphicsItem *item : items)
  1033. {
  1034. if (item->data(0).toString() != wanted)
  1035. {
  1036. continue;
  1037. }
  1038. Hit hit;
  1039. if (wanted == QStringLiteral("vertical"))
  1040. {
  1041. hit.rungId = item->data(2).toString().toStdString();
  1042. hit.column = item->data(3).toInt();
  1043. hit.vertical = true;
  1044. hit.lowerRungId = item->data(4).toString().toStdString();
  1045. hit.objectId = item->data(1).toString().toStdString();
  1046. }
  1047. else if (wanted == QStringLiteral("boundary"))
  1048. {
  1049. hit.rungId = item->data(1).toString().toStdString();
  1050. hit.column = item->data(2).toInt();
  1051. hit.boundary = true;
  1052. }
  1053. else if (wanted == QStringLiteral("cell"))
  1054. {
  1055. hit.rungId = item->data(1).toString().toStdString();
  1056. hit.column = item->data(2).toInt();
  1057. hit.objectId = item->data(3).toString().toStdString();
  1058. hit.cellKind = static_cast<LadderCellKind>(item->data(4).toInt());
  1059. }
  1060. else if (wanted == QStringLiteral("output"))
  1061. {
  1062. hit.rungId = item->data(1).toString().toStdString();
  1063. hit.column = ProjectLimits::kMaximumConditionColumns;
  1064. hit.output = true;
  1065. hit.objectId = item->data(2).toString().toStdString();
  1066. }
  1067. else if (wanted == QStringLiteral("rowHeader"))
  1068. {
  1069. hit.rungId = item->data(1).toString().toStdString();
  1070. hit.rowHeader = true;
  1071. }
  1072. return hit;
  1073. }
  1074. return {};
  1075. };
  1076. Hit hit = hit_for_type(QStringLiteral("rowHeader"));
  1077. if (!hit.rungId.empty())
  1078. {
  1079. return hit;
  1080. }
  1081. hit = hit_for_type(QStringLiteral("vertical"));
  1082. if (!hit.rungId.empty())
  1083. {
  1084. return hit;
  1085. }
  1086. hit = hit_for_type(QStringLiteral("boundary"));
  1087. if (!hit.rungId.empty())
  1088. {
  1089. return hit;
  1090. }
  1091. hit = hit_for_type(QStringLiteral("cell"));
  1092. if (!hit.rungId.empty())
  1093. {
  1094. return hit;
  1095. }
  1096. hit = hit_for_type(QStringLiteral("output"));
  1097. if (!hit.rungId.empty())
  1098. {
  1099. return hit;
  1100. }
  1101. return {};
  1102. }
  1103. void LogicEditorWidget::rebuildScene()
  1104. {
  1105. scene_->clear();
  1106. row_layouts_.clear();
  1107. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  1108. if (logic == nullptr)
  1109. {
  1110. scene_->setSceneRect(0.0, 0.0, kRightBus + kSceneRightMargin, 120.0);
  1111. return;
  1112. }
  1113. rebuildRowLayout(*logic);
  1114. const qreal height = row_layouts_.empty()
  1115. ? 120.0 : row_layouts_.back().bottom + 24.0;
  1116. scene_->setSceneRect(
  1117. 0.0, 0.0, kRightBus + kSceneRightMargin, height);
  1118. if (row_layouts_.empty())
  1119. {
  1120. return;
  1121. }
  1122. std::vector<GridRow> grid_rows;
  1123. grid_rows.reserve(logic->rungs.size());
  1124. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  1125. {
  1126. const RowLayout &layout = row_layouts_[row];
  1127. GridRow grid_row;
  1128. grid_row.top = layout.gridTop;
  1129. grid_rows.push_back(std::move(grid_row));
  1130. }
  1131. scene_->addItem(new GridLayerItem(std::move(grid_rows)));
  1132. QPen bus_pen(kLadderColor);
  1133. bus_pen.setWidthF(2.2);
  1134. bus_pen.setCosmetic(true);
  1135. QGraphicsLineItem *left_bus = scene_->addLine(
  1136. kLeftBus,
  1137. row_layouts_.front().gridTop,
  1138. kLeftBus,
  1139. row_layouts_.back().bottom,
  1140. bus_pen);
  1141. left_bus->setZValue(5.0);
  1142. QGraphicsLineItem *right_bus = scene_->addLine(
  1143. kRightBus,
  1144. row_layouts_.front().gridTop,
  1145. kRightBus,
  1146. row_layouts_.back().bottom,
  1147. bus_pen);
  1148. right_bus->setZValue(5.0);
  1149. for (std::size_t row = 0U; row < logic->rungs.size(); ++row)
  1150. {
  1151. const LadderRung &rung = logic->rungs[row];
  1152. const RowLayout &layout = row_layouts_[row];
  1153. QGraphicsRectItem *row_header = scene_->addRect(
  1154. QRectF(0.0, layout.gridTop, kLeftBus - 4.0, kRowHeight),
  1155. QPen(Qt::NoPen),
  1156. QBrush(Qt::transparent));
  1157. row_header->setData(0, QStringLiteral("rowHeader"));
  1158. row_header->setData(1, QString::fromStdString(rung.id));
  1159. row_header->setZValue(29.0);
  1160. QGraphicsSimpleTextItem *label = scene_->addSimpleText(
  1161. QStringLiteral("%1")
  1162. .arg(static_cast<int>(row), 3, 10, QLatin1Char('0')));
  1163. label->setBrush(QColor(QStringLiteral("#66757d")));
  1164. label->setPos(10.0, layout.centerY - 10.0);
  1165. label->setZValue(6.0);
  1166. if (layout.networkHead && !rung.comment.empty())
  1167. {
  1168. QGraphicsSimpleTextItem *comment = scene_->addSimpleText(
  1169. QString::fromStdString(rung.comment));
  1170. QFont comment_font = comment->font();
  1171. comment_font.setPointSizeF(9.0);
  1172. comment->setFont(comment_font);
  1173. comment->setBrush(kCommentColor);
  1174. comment->setPos(
  1175. kLeftBus + 7.0,
  1176. layout.gridTop - kCommentBandHeight + 4.0);
  1177. comment->setZValue(6.0);
  1178. }
  1179. for (int column = 0;
  1180. column < ProjectLimits::kMaximumConditionColumns;
  1181. ++column)
  1182. {
  1183. const LadderCell &cell = rung.cells[static_cast<std::size_t>(column)];
  1184. const qreal x = kLeftBus + static_cast<qreal>(column) * kCellWidth;
  1185. const auto input_power = trace_.cellInputPowerValues.find(cell.id);
  1186. const bool input_active = runtime_trace_enabled_
  1187. && input_power != trace_.cellInputPowerValues.end()
  1188. && input_power->second;
  1189. const auto output_power = trace_.cellPowerValues.find(cell.id);
  1190. const bool output_active = runtime_trace_enabled_
  1191. && output_power != trace_.cellPowerValues.end()
  1192. && output_power->second;
  1193. const bool faulted = cell.node.has_value()
  1194. && cell.node->id == fault_node_id_;
  1195. scene_->addItem(new CellContentItem(
  1196. cell,
  1197. rung.id,
  1198. column,
  1199. QPointF(x, layout.gridTop),
  1200. input_active,
  1201. output_active,
  1202. faulted));
  1203. }
  1204. for (int boundary = 0;
  1205. boundary <= ProjectLimits::kMaximumConditionColumns;
  1206. ++boundary)
  1207. {
  1208. const qreal x = kLeftBus + static_cast<qreal>(boundary) * kCellWidth;
  1209. QPen boundary_pen(Qt::transparent);
  1210. boundary_pen.setWidthF(12.0);
  1211. QGraphicsLineItem *hit_line = scene_->addLine(
  1212. x,
  1213. layout.gridTop + 8.0,
  1214. x,
  1215. layout.bottom - 8.0,
  1216. boundary_pen);
  1217. hit_line->setData(0, QStringLiteral("boundary"));
  1218. hit_line->setData(1, QString::fromStdString(rung.id));
  1219. hit_line->setData(2, boundary);
  1220. hit_line->setZValue(30.0);
  1221. }
  1222. const auto rung_power = trace_.rungValues.find(rung.id);
  1223. const bool input_active = runtime_trace_enabled_
  1224. && rung_power != trace_.rungValues.end() && rung_power->second;
  1225. bool symbol_active = false;
  1226. if (rung.output.has_value())
  1227. {
  1228. const auto output_value = trace_.nodeValues.find(rung.output->id);
  1229. symbol_active = runtime_trace_enabled_
  1230. && output_value != trace_.nodeValues.end()
  1231. && output_value->second;
  1232. }
  1233. const bool faulted = rung.output.has_value()
  1234. && rung.output->id == fault_node_id_;
  1235. scene_->addItem(new OutputContentItem(
  1236. rung,
  1237. QPointF(kConditionRight, layout.gridTop),
  1238. input_active,
  1239. symbol_active,
  1240. faulted));
  1241. }
  1242. for (const VerticalConnection &connection : logic->verticalConnections)
  1243. {
  1244. const RowLayout *upper = layoutForRung(connection.upperRungId);
  1245. const RowLayout *lower = layoutForRung(connection.lowerRungId);
  1246. if (upper == nullptr || lower == nullptr)
  1247. {
  1248. continue;
  1249. }
  1250. const qreal x = kLeftBus
  1251. + static_cast<qreal>(connection.columnBoundary) * kCellWidth;
  1252. const auto power = trace_.verticalConnectionValues.find(connection.id);
  1253. const bool active = runtime_trace_enabled_
  1254. && power != trace_.verticalConnectionValues.end() && power->second;
  1255. scene_->addItem(new VerticalConnectionItem(
  1256. connection, x, upper->centerY, lower->centerY, active));
  1257. }
  1258. std::vector<QRectF> selection_rectangles;
  1259. for (const std::string &rung_id : selected_row_ids_)
  1260. {
  1261. const RowLayout *layout = layoutForRung(rung_id);
  1262. if (layout != nullptr)
  1263. {
  1264. selection_rectangles.emplace_back(
  1265. 0.0,
  1266. layout->gridTop,
  1267. kRightBus,
  1268. kRowHeight);
  1269. }
  1270. }
  1271. for (const auto &position : selected_cells_)
  1272. {
  1273. const RowLayout *layout = layoutForRung(position.first);
  1274. if (layout != nullptr && position.second >= 0
  1275. && position.second < ProjectLimits::kMaximumConditionColumns)
  1276. {
  1277. selection_rectangles.emplace_back(
  1278. kLeftBus + static_cast<qreal>(position.second) * kCellWidth,
  1279. layout->gridTop,
  1280. kCellWidth,
  1281. kRowHeight);
  1282. }
  1283. }
  1284. for (const std::string &rung_id : selected_output_rung_ids_)
  1285. {
  1286. const RowLayout *layout = layoutForRung(rung_id);
  1287. if (layout != nullptr)
  1288. {
  1289. selection_rectangles.emplace_back(
  1290. kConditionRight,
  1291. layout->gridTop,
  1292. kOutputWidth,
  1293. kRowHeight);
  1294. }
  1295. }
  1296. for (const std::string &connection_id : selected_vertical_connection_ids_)
  1297. {
  1298. const VerticalConnection *connection = editor_service_.findConnection(
  1299. logic_id_, connection_id);
  1300. if (connection == nullptr)
  1301. {
  1302. continue;
  1303. }
  1304. const RowLayout *upper = layoutForRung(connection->upperRungId);
  1305. const RowLayout *lower = layoutForRung(connection->lowerRungId);
  1306. if (upper == nullptr || lower == nullptr)
  1307. {
  1308. continue;
  1309. }
  1310. const qreal x = kLeftBus
  1311. + static_cast<qreal>(connection->columnBoundary) * kCellWidth;
  1312. selection_rectangles.emplace_back(
  1313. x - 7.0,
  1314. upper->centerY,
  1315. 14.0,
  1316. lower->centerY - upper->centerY);
  1317. }
  1318. if (selected_cell_)
  1319. {
  1320. const std::pair<std::string, int> cursor{
  1321. selected_rung_id_, selected_column_};
  1322. const LadderCell *cell = editor_service_.findCell(
  1323. logic_id_, cursor.first, cursor.second);
  1324. const bool no_object_selection = selected_cells_.empty()
  1325. && selected_output_rung_ids_.empty()
  1326. && selected_vertical_connection_ids_.empty();
  1327. if (!containsCell(selected_cells_, cursor) && cell != nullptr
  1328. && (cell->kind == LadderCellKind::Gap || no_object_selection))
  1329. {
  1330. const RowLayout *layout = layoutForRung(cursor.first);
  1331. if (layout != nullptr)
  1332. {
  1333. selection_rectangles.emplace_back(
  1334. kLeftBus + static_cast<qreal>(cursor.second) * kCellWidth,
  1335. layout->gridTop,
  1336. kCellWidth,
  1337. kRowHeight);
  1338. }
  1339. }
  1340. }
  1341. if (selected_output_
  1342. && !containsId(selected_output_rung_ids_, selected_rung_id_))
  1343. {
  1344. const RowLayout *layout = layoutForRung(selected_rung_id_);
  1345. if (layout != nullptr)
  1346. {
  1347. selection_rectangles.emplace_back(
  1348. kConditionRight,
  1349. layout->gridTop,
  1350. kOutputWidth,
  1351. kRowHeight);
  1352. }
  1353. }
  1354. if (!selection_rectangles.empty())
  1355. {
  1356. scene_->addItem(new SelectionOverlayItem(
  1357. std::move(selection_rectangles)));
  1358. }
  1359. }
  1360. void LogicEditorWidget::clearObjectSelection()
  1361. {
  1362. selected_node_ids_.clear();
  1363. selected_cells_.clear();
  1364. selected_output_rung_ids_.clear();
  1365. selected_vertical_connection_ids_.clear();
  1366. selected_row_ids_.clear();
  1367. selected_vertical_connection_id_.clear();
  1368. }
  1369. void LogicEditorWidget::synchronizeSelectedNodes()
  1370. {
  1371. selected_node_ids_.clear();
  1372. for (const auto &position : selected_cells_)
  1373. {
  1374. const LadderCell *cell = editor_service_.findCell(
  1375. logic_id_, position.first, position.second);
  1376. if (cell != nullptr && cell->node.has_value())
  1377. {
  1378. selected_node_ids_.push_back(cell->node->id);
  1379. }
  1380. }
  1381. for (const std::string &rung_id : selected_output_rung_ids_)
  1382. {
  1383. const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
  1384. if (rung != nullptr && rung->output.has_value())
  1385. {
  1386. selected_node_ids_.push_back(rung->output->id);
  1387. }
  1388. }
  1389. }
  1390. void LogicEditorWidget::notifySelectionChanged()
  1391. {
  1392. emit nodeSelected(selected_node_ids_.empty()
  1393. ? QString{} : QString::fromStdString(selected_node_ids_.front()));
  1394. }
  1395. void LogicEditorWidget::selectObject(
  1396. const Hit &hit, bool extend_node_selection)
  1397. {
  1398. if (hit.rowHeader)
  1399. {
  1400. if (!extend_node_selection)
  1401. {
  1402. clearObjectSelection();
  1403. selected_row_ids_.clear();
  1404. }
  1405. else if (!selected_cells_.empty()
  1406. || !selected_output_rung_ids_.empty()
  1407. || !selected_vertical_connection_ids_.empty())
  1408. {
  1409. clearObjectSelection();
  1410. }
  1411. const auto selected = std::find(
  1412. selected_row_ids_.begin(), selected_row_ids_.end(), hit.rungId);
  1413. if (extend_node_selection && selected != selected_row_ids_.end())
  1414. {
  1415. selected_row_ids_.erase(selected);
  1416. }
  1417. else if (selected == selected_row_ids_.end())
  1418. {
  1419. selected_row_ids_.push_back(hit.rungId);
  1420. }
  1421. std::sort(
  1422. selected_row_ids_.begin(), selected_row_ids_.end(),
  1423. [this](const std::string &left, const std::string &right)
  1424. {
  1425. return rowAt(left) < rowAt(right);
  1426. });
  1427. selected_rung_id_ = hit.rungId;
  1428. selected_column_ = -1;
  1429. selected_cell_ = false;
  1430. selected_output_ = false;
  1431. selected_boundary_ = false;
  1432. synchronizeSelectedNodes();
  1433. rebuildScene();
  1434. notifySelectionChanged();
  1435. return;
  1436. }
  1437. selected_row_ids_.clear();
  1438. if (!extend_node_selection)
  1439. {
  1440. clearObjectSelection();
  1441. }
  1442. if (hit.rungId.empty())
  1443. {
  1444. if (!extend_node_selection)
  1445. {
  1446. selected_rung_id_.clear();
  1447. selected_column_ = -1;
  1448. selected_cell_ = false;
  1449. selected_output_ = false;
  1450. selected_boundary_ = false;
  1451. }
  1452. rebuildScene();
  1453. notifySelectionChanged();
  1454. return;
  1455. }
  1456. selected_rung_id_ = hit.rungId;
  1457. selected_column_ = hit.column;
  1458. selected_cell_ = hit.column >= 0 && !hit.boundary
  1459. && !hit.output && !hit.vertical;
  1460. selected_output_ = hit.output;
  1461. selected_boundary_ = hit.boundary || hit.vertical;
  1462. if (hit.vertical)
  1463. {
  1464. const auto selected = std::find(
  1465. selected_vertical_connection_ids_.begin(),
  1466. selected_vertical_connection_ids_.end(),
  1467. hit.objectId);
  1468. if (extend_node_selection
  1469. && selected != selected_vertical_connection_ids_.end())
  1470. {
  1471. selected_vertical_connection_ids_.erase(selected);
  1472. }
  1473. else if (selected == selected_vertical_connection_ids_.end())
  1474. {
  1475. selected_vertical_connection_ids_.push_back(hit.objectId);
  1476. }
  1477. }
  1478. else if (hit.output && !hit.objectId.empty())
  1479. {
  1480. const auto selected = std::find(
  1481. selected_output_rung_ids_.begin(),
  1482. selected_output_rung_ids_.end(),
  1483. hit.rungId);
  1484. if (extend_node_selection && selected != selected_output_rung_ids_.end())
  1485. {
  1486. selected_output_rung_ids_.erase(selected);
  1487. }
  1488. else if (selected == selected_output_rung_ids_.end())
  1489. {
  1490. selected_output_rung_ids_.push_back(hit.rungId);
  1491. }
  1492. }
  1493. else if (!hit.boundary && !hit.vertical
  1494. && hit.cellKind != LadderCellKind::Gap)
  1495. {
  1496. const std::pair<std::string, int> position{hit.rungId, hit.column};
  1497. const auto selected = std::find(
  1498. selected_cells_.begin(), selected_cells_.end(), position);
  1499. if (extend_node_selection && selected != selected_cells_.end())
  1500. {
  1501. selected_cells_.erase(selected);
  1502. }
  1503. else if (selected == selected_cells_.end())
  1504. {
  1505. selected_cells_.push_back(position);
  1506. }
  1507. }
  1508. selected_vertical_connection_id_ =
  1509. selected_vertical_connection_ids_.empty()
  1510. ? std::string{} : selected_vertical_connection_ids_.back();
  1511. synchronizeSelectedNodes();
  1512. rebuildScene();
  1513. notifySelectionChanged();
  1514. }
  1515. void LogicEditorWidget::selectObjectsInBand(
  1516. const QRect &viewport_rect, bool extend_selection)
  1517. {
  1518. if (!selected_row_ids_.empty())
  1519. {
  1520. selected_row_ids_.clear();
  1521. clearObjectSelection();
  1522. }
  1523. if (!extend_selection)
  1524. {
  1525. clearObjectSelection();
  1526. }
  1527. const QPolygonF scene_polygon = mapToScene(viewport_rect.normalized());
  1528. QPainterPath selection_path;
  1529. selection_path.addPolygon(scene_polygon);
  1530. selection_path.closeSubpath();
  1531. const QList<QGraphicsItem *> items = scene_->items(
  1532. selection_path,
  1533. Qt::IntersectsItemShape,
  1534. Qt::DescendingOrder);
  1535. for (QGraphicsItem *item : items)
  1536. {
  1537. const QString type = item->data(0).toString();
  1538. if (type == QStringLiteral("cell"))
  1539. {
  1540. const LadderCellKind kind = static_cast<LadderCellKind>(
  1541. item->data(4).toInt());
  1542. if (kind == LadderCellKind::Gap)
  1543. {
  1544. continue;
  1545. }
  1546. const std::pair<std::string, int> position{
  1547. item->data(1).toString().toStdString(),
  1548. item->data(2).toInt()};
  1549. if (!containsCell(selected_cells_, position))
  1550. {
  1551. selected_cells_.push_back(position);
  1552. }
  1553. }
  1554. else if (type == QStringLiteral("output")
  1555. && !item->data(2).toString().isEmpty())
  1556. {
  1557. const std::string rung_id = item->data(1).toString().toStdString();
  1558. if (!containsId(selected_output_rung_ids_, rung_id))
  1559. {
  1560. selected_output_rung_ids_.push_back(rung_id);
  1561. }
  1562. }
  1563. else if (type == QStringLiteral("vertical"))
  1564. {
  1565. const std::string connection_id =
  1566. item->data(1).toString().toStdString();
  1567. if (!containsId(selected_vertical_connection_ids_, connection_id))
  1568. {
  1569. selected_vertical_connection_ids_.push_back(connection_id);
  1570. }
  1571. }
  1572. }
  1573. std::sort(
  1574. selected_cells_.begin(),
  1575. selected_cells_.end(),
  1576. [this](const auto &left, const auto &right)
  1577. {
  1578. const int left_row = rowAt(left.first);
  1579. const int right_row = rowAt(right.first);
  1580. return left_row != right_row
  1581. ? left_row < right_row : left.second < right.second;
  1582. });
  1583. std::sort(
  1584. selected_output_rung_ids_.begin(),
  1585. selected_output_rung_ids_.end(),
  1586. [this](const std::string &left, const std::string &right)
  1587. {
  1588. return rowAt(left) < rowAt(right);
  1589. });
  1590. const Hit center_hit = hitAt(mapToScene(viewport_rect.center()));
  1591. if (!center_hit.rungId.empty())
  1592. {
  1593. selected_rung_id_ = center_hit.rungId;
  1594. selected_column_ = center_hit.column;
  1595. selected_cell_ = center_hit.column >= 0 && !center_hit.output
  1596. && !center_hit.vertical && !center_hit.boundary
  1597. && !center_hit.rowHeader;
  1598. selected_output_ = center_hit.output;
  1599. selected_boundary_ = center_hit.boundary || center_hit.vertical;
  1600. }
  1601. selected_vertical_connection_id_ =
  1602. selected_vertical_connection_ids_.empty()
  1603. ? std::string{} : selected_vertical_connection_ids_.back();
  1604. synchronizeSelectedNodes();
  1605. rebuildScene();
  1606. notifySelectionChanged();
  1607. }
  1608. void LogicEditorWidget::beginGesture(const Hit &hit)
  1609. {
  1610. if (!editing_enabled_ || hit.rungId.empty() || hit.output)
  1611. {
  1612. return;
  1613. }
  1614. gesture_active_ = true;
  1615. gesture_origin_ = hit;
  1616. gesture_current_ = hit;
  1617. gesture_scene_position_valid_ = false;
  1618. viewport()->update();
  1619. }
  1620. void LogicEditorWidget::updateGesture(
  1621. const Hit &hit, const QPointF &scene_position)
  1622. {
  1623. if (!gesture_active_)
  1624. {
  1625. return;
  1626. }
  1627. gesture_current_ = hit;
  1628. gesture_scene_position_ = scene_position;
  1629. gesture_scene_position_valid_ = true;
  1630. viewport()->update();
  1631. }
  1632. void LogicEditorWidget::finishGesture(const Hit &hit)
  1633. {
  1634. if (!gesture_active_)
  1635. {
  1636. return;
  1637. }
  1638. const Hit origin = gesture_origin_;
  1639. gesture_active_ = false;
  1640. gesture_origin_ = {};
  1641. gesture_current_ = {};
  1642. gesture_scene_position_valid_ = false;
  1643. viewport()->update();
  1644. if (origin.rungId.empty() || hit.rungId.empty())
  1645. {
  1646. return;
  1647. }
  1648. LogicEditorResult result;
  1649. const bool connected = mouse_wire_mode_ == MouseWireMode::Draw;
  1650. if ((origin.vertical || origin.boundary)
  1651. && (hit.vertical || hit.boundary))
  1652. {
  1653. if (!connected && origin.vertical
  1654. && origin.objectId == hit.objectId)
  1655. {
  1656. result = editor_service_.removeVerticalConnections(
  1657. logic_id_, {origin.objectId});
  1658. }
  1659. else if (origin.column != hit.column)
  1660. {
  1661. result = {false, LogicEditorError::InvalidOperation,
  1662. "竖线拖动必须保持在同一列边界", {}};
  1663. }
  1664. else
  1665. {
  1666. std::string first_rung = origin.rungId;
  1667. std::string last_rung = hit.rungId;
  1668. if (last_rung.empty() && !origin.lowerRungId.empty())
  1669. {
  1670. last_rung = origin.lowerRungId;
  1671. }
  1672. result = editor_service_.setVerticalConnectionRange(
  1673. logic_id_, first_rung, last_rung,
  1674. origin.column, connected);
  1675. }
  1676. }
  1677. else if (origin.vertical || hit.vertical)
  1678. {
  1679. result = {false, LogicEditorError::InvalidOperation,
  1680. "请从网格边界开始竖向拖动", {}};
  1681. }
  1682. else if (origin.column == hit.column
  1683. && origin.rungId != hit.rungId)
  1684. {
  1685. result = editor_service_.setVerticalConnectionRange(
  1686. logic_id_, origin.rungId, hit.rungId,
  1687. origin.column, connected);
  1688. }
  1689. else if (origin.rungId == hit.rungId)
  1690. {
  1691. result = editor_service_.setHorizontalWireRange(
  1692. logic_id_, origin.rungId,
  1693. origin.column, hit.column, connected);
  1694. }
  1695. else
  1696. {
  1697. result = {false, LogicEditorError::InvalidOperation,
  1698. "画线必须沿同一行或同一列边界进行", {}};
  1699. }
  1700. if (!result.succeeded)
  1701. {
  1702. reportFailure(result);
  1703. }
  1704. else
  1705. {
  1706. clearObjectSelection();
  1707. selected_output_ = false;
  1708. selected_boundary_ = false;
  1709. rebuildScene();
  1710. notifySelectionChanged();
  1711. emit graphChanged();
  1712. }
  1713. }
  1714. void LogicEditorWidget::clearGesture()
  1715. {
  1716. if (!gesture_active_ && !gesture_scene_position_valid_)
  1717. {
  1718. return;
  1719. }
  1720. gesture_active_ = false;
  1721. gesture_origin_ = {};
  1722. gesture_current_ = {};
  1723. gesture_scene_position_ = {};
  1724. gesture_scene_position_valid_ = false;
  1725. viewport()->update();
  1726. }
  1727. void LogicEditorWidget::drawGesturePreview(QPainter *painter) const
  1728. {
  1729. if (!gesture_active_ || painter == nullptr
  1730. || gesture_origin_.rungId.empty())
  1731. {
  1732. return;
  1733. }
  1734. enum class PreviewKind { Invalid, Horizontal, Vertical };
  1735. PreviewKind kind = PreviewKind::Invalid;
  1736. bool valid = false;
  1737. const Hit &origin = gesture_origin_;
  1738. const Hit &current = gesture_current_;
  1739. const bool connected = mouse_wire_mode_ == MouseWireMode::Draw;
  1740. if (!current.rungId.empty())
  1741. {
  1742. if ((origin.vertical || origin.boundary)
  1743. && (current.vertical || current.boundary))
  1744. {
  1745. kind = PreviewKind::Vertical;
  1746. valid = origin.column == current.column
  1747. && (origin.rungId != current.rungId
  1748. || (!connected && origin.vertical
  1749. && origin.objectId == current.objectId));
  1750. }
  1751. else if (origin.vertical || current.vertical)
  1752. {
  1753. kind = PreviewKind::Invalid;
  1754. }
  1755. else if (origin.column == current.column
  1756. && origin.rungId != current.rungId)
  1757. {
  1758. kind = PreviewKind::Vertical;
  1759. valid = origin.column >= 0
  1760. && origin.column <= ProjectLimits::kMaximumConditionColumns;
  1761. }
  1762. else if (origin.rungId == current.rungId)
  1763. {
  1764. kind = PreviewKind::Horizontal;
  1765. valid = origin.column >= 0 && current.column >= 0
  1766. && origin.column < ProjectLimits::kMaximumConditionColumns
  1767. && current.column < ProjectLimits::kMaximumConditionColumns;
  1768. }
  1769. }
  1770. QColor preview_color = valid
  1771. ? connected ? QColor(QStringLiteral("#277da1"))
  1772. : QColor(QStringLiteral("#c56a1a"))
  1773. : kFaultColor;
  1774. preview_color.setAlpha(220);
  1775. QPen preview_pen(preview_color, valid ? 2.8 : 2.4, Qt::DashLine);
  1776. preview_pen.setCosmetic(true);
  1777. preview_pen.setCapStyle(Qt::SquareCap);
  1778. painter->save();
  1779. painter->setRenderHint(QPainter::Antialiasing, true);
  1780. painter->setPen(preview_pen);
  1781. painter->setBrush(Qt::NoBrush);
  1782. if (valid && kind == PreviewKind::Horizontal)
  1783. {
  1784. const RowLayout *layout = layoutForRung(origin.rungId);
  1785. if (layout != nullptr)
  1786. {
  1787. const int first = std::min(origin.column, current.column);
  1788. const int last = std::max(origin.column, current.column);
  1789. for (int column = first; column <= last; ++column)
  1790. {
  1791. const LadderCell *cell = editor_service_.findCell(
  1792. logic_id_, origin.rungId, column);
  1793. if (cell == nullptr || cell->kind == LadderCellKind::Node
  1794. || (mouse_wire_mode_ == MouseWireMode::Erase
  1795. && cell->kind != LadderCellKind::Wire))
  1796. {
  1797. continue;
  1798. }
  1799. const qreal left = kLeftBus
  1800. + static_cast<qreal>(column) * kCellWidth;
  1801. painter->drawLine(
  1802. QPointF(left, layout->centerY),
  1803. QPointF(left + kCellWidth, layout->centerY));
  1804. }
  1805. }
  1806. }
  1807. else if (valid && kind == PreviewKind::Vertical)
  1808. {
  1809. const RowLayout *first = layoutForRung(origin.rungId);
  1810. const RowLayout *last = layoutForRung(current.rungId);
  1811. if (first != nullptr && last != nullptr)
  1812. {
  1813. const qreal x = kLeftBus
  1814. + static_cast<qreal>(origin.column) * kCellWidth;
  1815. painter->drawLine(
  1816. QPointF(x, first->centerY),
  1817. QPointF(x, last->centerY));
  1818. }
  1819. }
  1820. else
  1821. {
  1822. const RowLayout *layout = layoutForRung(origin.rungId);
  1823. if (layout != nullptr && gesture_scene_position_valid_)
  1824. {
  1825. const qreal origin_x = kLeftBus
  1826. + (origin.vertical || origin.boundary
  1827. ? static_cast<qreal>(origin.column) * kCellWidth
  1828. : (static_cast<qreal>(origin.column) + 0.5) * kCellWidth);
  1829. painter->drawLine(
  1830. QPointF(origin_x, layout->centerY),
  1831. gesture_scene_position_);
  1832. }
  1833. }
  1834. painter->restore();
  1835. }
  1836. void LogicEditorWidget::showCommandEditor(const Hit &hit)
  1837. {
  1838. if (!editing_enabled_ || hit.rungId.empty()
  1839. || hit.boundary || hit.vertical)
  1840. {
  1841. return;
  1842. }
  1843. const std::vector<std::string> parallel_node_ids = selectedNodeIds();
  1844. cancelCommandInput();
  1845. command_target_.rungId = hit.rungId;
  1846. command_target_.column = hit.column;
  1847. const LogicNode *existing = hit.objectId.empty()
  1848. ? nullptr : editor_service_.findNode(logic_id_, hit.objectId);
  1849. if (existing != nullptr)
  1850. {
  1851. command_target_.kind = LogicCommandTargetKind::ExistingNode;
  1852. }
  1853. else if (hit.output)
  1854. {
  1855. command_target_.kind = LogicCommandTargetKind::Output;
  1856. }
  1857. else
  1858. {
  1859. const LadderCell *cell = editor_service_.findCell(
  1860. logic_id_, hit.rungId, hit.column);
  1861. command_target_.kind = cell != nullptr
  1862. && cell->kind == LadderCellKind::Wire
  1863. ? LogicCommandTargetKind::WireColumn
  1864. : LogicCommandTargetKind::GapColumn;
  1865. }
  1866. command_target_.expressionId = hit.objectId;
  1867. command_parallel_node_ids_ = parallel_node_ids;
  1868. QString initial_text;
  1869. if (existing != nullptr && existing->isConfigured())
  1870. {
  1871. initial_text = logicCommandText(existing->config);
  1872. }
  1873. command_editor_->setStyleSheet(QString{});
  1874. command_editor_->setToolTip(
  1875. tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
  1876. command_editor_->setText(initial_text);
  1877. command_editor_->setVisible(true);
  1878. QPointF center;
  1879. if (!findCommandTargetCenter(command_target_, &center))
  1880. {
  1881. cancelCommandInput();
  1882. return;
  1883. }
  1884. positionCommandInput(center);
  1885. command_editor_->raise();
  1886. command_editor_->setFocus(Qt::MouseFocusReason);
  1887. command_editor_->selectAll();
  1888. }
  1889. void LogicEditorWidget::commitCommandInput()
  1890. {
  1891. if (command_editor_ == nullptr || !command_editor_->isVisible())
  1892. {
  1893. return;
  1894. }
  1895. LogicCommandRequest request;
  1896. request.logicId = logic_id_;
  1897. request.text = command_editor_->text().trimmed().toStdString();
  1898. request.target = command_target_;
  1899. request.parallelNodeIds = command_parallel_node_ids_;
  1900. const LogicCommandResult result = command_service_.execute(request);
  1901. if (!result.succeeded)
  1902. {
  1903. command_editor_->setStyleSheet(
  1904. QStringLiteral("QLineEdit { border: 2px solid #c5362e; }"));
  1905. command_editor_->setToolTip(QString::fromStdString(result.message));
  1906. command_editor_->setFocus(Qt::OtherFocusReason);
  1907. command_editor_->selectAll();
  1908. emit editorError(QString::fromStdString(result.message));
  1909. return;
  1910. }
  1911. command_editor_->setStyleSheet(QString{});
  1912. command_editor_->setToolTip(
  1913. tr("触点使用 M 地址,比较和数据指令使用 D 地址"));
  1914. command_parallel_node_ids_.clear();
  1915. if (!result.hasNextCursor)
  1916. {
  1917. selectNode(result.id);
  1918. emit graphChanged();
  1919. cancelCommandInput();
  1920. return;
  1921. }
  1922. moveToCursor(result.nextCursor);
  1923. emit graphChanged();
  1924. command_target_ = commandTargetForCursor(result.nextCursor);
  1925. QPointF center;
  1926. if (command_target_.rungId.empty()
  1927. || !findCommandTargetCenter(command_target_, &center))
  1928. {
  1929. cancelCommandInput();
  1930. return;
  1931. }
  1932. command_editor_->clear();
  1933. positionCommandInput(center);
  1934. command_editor_->raise();
  1935. command_editor_->setFocus(Qt::OtherFocusReason);
  1936. }
  1937. void LogicEditorWidget::cancelCommandInput()
  1938. {
  1939. if (command_editor_ != nullptr)
  1940. {
  1941. command_editor_->clear();
  1942. command_editor_->setStyleSheet(QString{});
  1943. command_editor_->setVisible(false);
  1944. }
  1945. command_target_ = {};
  1946. command_parallel_node_ids_.clear();
  1947. }
  1948. void LogicEditorWidget::positionCommandInput(const QPointF &scene_center)
  1949. {
  1950. if (command_editor_ == nullptr)
  1951. {
  1952. return;
  1953. }
  1954. const QPoint view_center = mapFromScene(scene_center);
  1955. const int width = 360;
  1956. const int height = 38;
  1957. const QRect bounds = viewport()->rect().adjusted(4, 4, -4, -4);
  1958. const int left = std::clamp(
  1959. view_center.x() - width / 2,
  1960. bounds.left(),
  1961. std::max(bounds.left(), bounds.right() - width + 1));
  1962. const int top = std::clamp(
  1963. view_center.y() - height / 2,
  1964. bounds.top(),
  1965. std::max(bounds.top(), bounds.bottom() - height + 1));
  1966. command_editor_->setGeometry(left, top, width, height);
  1967. }
  1968. bool LogicEditorWidget::findCommandTargetCenter(
  1969. const LogicCommandTarget &target,
  1970. QPointF *scene_center) const
  1971. {
  1972. if (scene_center == nullptr)
  1973. {
  1974. return false;
  1975. }
  1976. const RowLayout *layout = layoutForRung(target.rungId);
  1977. if (layout == nullptr)
  1978. {
  1979. return false;
  1980. }
  1981. if (target.kind == LogicCommandTargetKind::Output)
  1982. {
  1983. *scene_center = QPointF(
  1984. kConditionRight + kOutputWidth / 2.0,
  1985. layout->centerY);
  1986. return true;
  1987. }
  1988. int column = target.column;
  1989. if (target.kind == LogicCommandTargetKind::ExistingNode)
  1990. {
  1991. const LadderRung *rung = editor_service_.findRung(
  1992. logic_id_, target.rungId);
  1993. if (rung == nullptr)
  1994. {
  1995. return false;
  1996. }
  1997. if (rung->output.has_value()
  1998. && rung->output->id == target.expressionId)
  1999. {
  2000. *scene_center = QPointF(
  2001. kConditionRight + kOutputWidth / 2.0,
  2002. layout->centerY);
  2003. return true;
  2004. }
  2005. const auto found = std::find_if(
  2006. rung->cells.cbegin(), rung->cells.cend(),
  2007. [&target](const LadderCell &cell)
  2008. {
  2009. return cell.node.has_value()
  2010. && cell.node->id == target.expressionId;
  2011. });
  2012. if (found == rung->cells.cend())
  2013. {
  2014. return false;
  2015. }
  2016. column = static_cast<int>(std::distance(rung->cells.cbegin(), found));
  2017. }
  2018. if (column < 0 || column >= ProjectLimits::kMaximumConditionColumns)
  2019. {
  2020. return false;
  2021. }
  2022. *scene_center = QPointF(
  2023. kLeftBus + (static_cast<qreal>(column) + 0.5) * kCellWidth,
  2024. layout->centerY);
  2025. return true;
  2026. }
  2027. LogicCommandTarget LogicEditorWidget::commandTargetForCursor(
  2028. const LogicEditCursor &cursor) const
  2029. {
  2030. LogicCommandTarget target;
  2031. target.rungId = cursor.rungId;
  2032. target.column = cursor.column;
  2033. if (cursor.output)
  2034. {
  2035. target.kind = LogicCommandTargetKind::Output;
  2036. return target;
  2037. }
  2038. const LadderCell *cell = editor_service_.findCell(
  2039. logic_id_, cursor.rungId, cursor.column);
  2040. if (cell != nullptr && cell->node.has_value())
  2041. {
  2042. target.kind = LogicCommandTargetKind::ExistingNode;
  2043. target.expressionId = cell->node->id;
  2044. }
  2045. else
  2046. {
  2047. target.kind = cell != nullptr && cell->kind == LadderCellKind::Wire
  2048. ? LogicCommandTargetKind::WireColumn
  2049. : LogicCommandTargetKind::GapColumn;
  2050. }
  2051. return target;
  2052. }
  2053. LogicEditCursor LogicEditorWidget::conditionInsertionCursor() const
  2054. {
  2055. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  2056. if (logic == nullptr || logic->rungs.empty())
  2057. {
  2058. return {};
  2059. }
  2060. const std::string rung_id = currentRungId();
  2061. if (selected_output_)
  2062. {
  2063. return {rung_id, ProjectLimits::kMaximumConditionColumns, true};
  2064. }
  2065. if (selected_cell_ && selected_column_ >= 0)
  2066. {
  2067. int target_column = selected_column_;
  2068. const LadderCell *cell = editor_service_.findCell(
  2069. logic_id_, rung_id, selected_column_);
  2070. if (cell != nullptr && cell->kind == LadderCellKind::Node)
  2071. {
  2072. ++target_column;
  2073. }
  2074. return {
  2075. rung_id,
  2076. std::min(target_column, ProjectLimits::kMaximumConditionColumns),
  2077. target_column >= ProjectLimits::kMaximumConditionColumns};
  2078. }
  2079. const LadderRung *rung = editor_service_.findRung(logic_id_, rung_id);
  2080. if (rung != nullptr)
  2081. {
  2082. const auto empty = std::find_if(
  2083. rung->cells.cbegin(), rung->cells.cend(),
  2084. [](const LadderCell &cell)
  2085. {
  2086. return cell.kind == LadderCellKind::Gap;
  2087. });
  2088. if (empty != rung->cells.cend())
  2089. {
  2090. return {
  2091. rung_id,
  2092. static_cast<int>(std::distance(rung->cells.cbegin(), empty)),
  2093. false};
  2094. }
  2095. }
  2096. return {rung_id, ProjectLimits::kMaximumConditionColumns, true};
  2097. }
  2098. void LogicEditorWidget::moveToCursor(const LogicEditCursor &cursor)
  2099. {
  2100. clearObjectSelection();
  2101. selected_rung_id_ = cursor.rungId;
  2102. selected_column_ = cursor.column;
  2103. selected_cell_ = !cursor.output;
  2104. selected_output_ = cursor.output;
  2105. selected_boundary_ = false;
  2106. rebuildScene();
  2107. notifySelectionChanged();
  2108. const LogicCommandTarget target = commandTargetForCursor(cursor);
  2109. QPointF center;
  2110. if (findCommandTargetCenter(target, &center))
  2111. {
  2112. ensureVisible(
  2113. QRectF(center.x() - kCellWidth / 2.0,
  2114. center.y() - kRowHeight / 2.0,
  2115. cursor.output ? kOutputWidth : kCellWidth,
  2116. kRowHeight),
  2117. 24,
  2118. 24);
  2119. }
  2120. }
  2121. void LogicEditorWidget::moveToVerticalTarget(
  2122. const LogicVerticalEditResult &result)
  2123. {
  2124. const bool keep_cell = selected_cell_;
  2125. const bool keep_output = selected_output_;
  2126. const bool keep_boundary = selected_boundary_;
  2127. clearObjectSelection();
  2128. selected_rung_id_ = result.nextRungId;
  2129. selected_column_ = result.columnBoundary;
  2130. selected_cell_ = keep_cell;
  2131. selected_output_ = keep_output;
  2132. selected_boundary_ = keep_boundary;
  2133. rebuildScene();
  2134. notifySelectionChanged();
  2135. const RowLayout *layout = layoutForRung(result.nextRungId);
  2136. if (layout == nullptr)
  2137. {
  2138. return;
  2139. }
  2140. qreal center_x = kLeftBus
  2141. + (static_cast<qreal>(result.columnBoundary) + 0.5) * kCellWidth;
  2142. qreal target_width = kCellWidth;
  2143. if (keep_boundary)
  2144. {
  2145. center_x = kLeftBus
  2146. + static_cast<qreal>(result.columnBoundary) * kCellWidth;
  2147. }
  2148. else if (keep_output)
  2149. {
  2150. center_x = kConditionRight + kOutputWidth / 2.0;
  2151. target_width = kOutputWidth;
  2152. }
  2153. ensureVisible(
  2154. QRectF(
  2155. center_x - target_width / 2.0,
  2156. layout->centerY - kRowHeight / 2.0,
  2157. target_width,
  2158. kRowHeight),
  2159. 24,
  2160. 24);
  2161. }
  2162. LogicEditorResult LogicEditorWidget::finishCursorEdit(
  2163. const LogicEditResult &result)
  2164. {
  2165. if (!result.edit.succeeded)
  2166. {
  2167. reportFailure(result.edit);
  2168. return result.edit;
  2169. }
  2170. moveToCursor(result.nextCursor);
  2171. emit graphChanged();
  2172. return result.edit;
  2173. }
  2174. void LogicEditorWidget::reportFailure(const LogicEditorResult &result)
  2175. {
  2176. emit editorError(QString::fromStdString(result.message));
  2177. }
  2178. void LogicEditorWidget::mousePressEvent(QMouseEvent *event)
  2179. {
  2180. const Hit hit = hitAt(mapToScene(event->pos()));
  2181. setFocus(Qt::MouseFocusReason);
  2182. if (event->button() == Qt::LeftButton
  2183. && mouse_wire_mode_ != MouseWireMode::Select && editing_enabled_)
  2184. {
  2185. beginGesture(hit);
  2186. }
  2187. else if (event->button() == Qt::LeftButton
  2188. && mouse_wire_mode_ == MouseWireMode::Select)
  2189. {
  2190. selection_pressed_ = true;
  2191. selection_dragging_ = false;
  2192. selection_origin_ = event->pos();
  2193. selection_modifiers_ = event->modifiers();
  2194. selection_band_->setGeometry(QRect(selection_origin_, QSize{}));
  2195. selection_band_->hide();
  2196. }
  2197. else
  2198. {
  2199. QGraphicsView::mousePressEvent(event);
  2200. return;
  2201. }
  2202. event->accept();
  2203. }
  2204. void LogicEditorWidget::mouseMoveEvent(QMouseEvent *event)
  2205. {
  2206. if (mouse_wire_mode_ == MouseWireMode::Select && selection_pressed_)
  2207. {
  2208. if (!selection_dragging_
  2209. && (event->pos() - selection_origin_).manhattanLength()
  2210. >= QApplication::startDragDistance())
  2211. {
  2212. selection_dragging_ = true;
  2213. selection_band_->show();
  2214. }
  2215. if (selection_dragging_)
  2216. {
  2217. selection_band_->setGeometry(
  2218. QRect(selection_origin_, event->pos()).normalized());
  2219. }
  2220. }
  2221. else
  2222. {
  2223. const QPointF scene_position = mapToScene(event->pos());
  2224. updateGesture(hitAt(scene_position), scene_position);
  2225. }
  2226. event->accept();
  2227. }
  2228. void LogicEditorWidget::mouseReleaseEvent(QMouseEvent *event)
  2229. {
  2230. const Hit hit = hitAt(mapToScene(event->pos()));
  2231. if (event->button() == Qt::LeftButton
  2232. && mouse_wire_mode_ == MouseWireMode::Select && selection_pressed_)
  2233. {
  2234. const bool extend = selection_modifiers_.testFlag(Qt::ControlModifier);
  2235. selection_pressed_ = false;
  2236. selection_band_->hide();
  2237. if (selection_dragging_)
  2238. {
  2239. selectObjectsInBand(
  2240. QRect(selection_origin_, event->pos()).normalized(), extend);
  2241. }
  2242. else
  2243. {
  2244. selectObject(hit, extend);
  2245. }
  2246. selection_dragging_ = false;
  2247. }
  2248. else if (mouse_wire_mode_ != MouseWireMode::Select)
  2249. {
  2250. finishGesture(hit);
  2251. }
  2252. else
  2253. {
  2254. QGraphicsView::mouseReleaseEvent(event);
  2255. return;
  2256. }
  2257. event->accept();
  2258. }
  2259. void LogicEditorWidget::mouseDoubleClickEvent(QMouseEvent *event)
  2260. {
  2261. showCommandEditor(hitAt(mapToScene(event->pos())));
  2262. event->accept();
  2263. }
  2264. void LogicEditorWidget::keyPressEvent(QKeyEvent *event)
  2265. {
  2266. if (event != nullptr
  2267. && event->key() == Qt::Key_Escape
  2268. && event->modifiers() == Qt::NoModifier
  2269. && mouse_wire_mode_ != MouseWireMode::Select)
  2270. {
  2271. setMouseWireMode(MouseWireMode::Select);
  2272. event->accept();
  2273. return;
  2274. }
  2275. QGraphicsView::keyPressEvent(event);
  2276. }
  2277. void LogicEditorWidget::drawForeground(
  2278. QPainter *painter, const QRectF &rect)
  2279. {
  2280. QGraphicsView::drawForeground(painter, rect);
  2281. drawGesturePreview(painter);
  2282. }
  2283. void LogicEditorWidget::resizeEvent(QResizeEvent *event)
  2284. {
  2285. QGraphicsView::resizeEvent(event);
  2286. if (command_editor_ != nullptr && command_editor_->isVisible())
  2287. {
  2288. QPointF center;
  2289. if (findCommandTargetCenter(command_target_, &center))
  2290. {
  2291. positionCommandInput(center);
  2292. }
  2293. }
  2294. }
  2295. void LogicEditorWidget::scrollContentsBy(int dx, int dy)
  2296. {
  2297. QGraphicsView::scrollContentsBy(dx, dy);
  2298. if (command_editor_ != nullptr && command_editor_->isVisible())
  2299. {
  2300. QPointF center;
  2301. if (findCommandTargetCenter(command_target_, &center))
  2302. {
  2303. positionCommandInput(center);
  2304. }
  2305. }
  2306. }
  2307. bool LogicEditorWidget::eventFilter(QObject *watched, QEvent *event)
  2308. {
  2309. if (watched == command_editor_ && event != nullptr)
  2310. {
  2311. if (event->type() == QEvent::KeyPress)
  2312. {
  2313. const auto *key_event = static_cast<QKeyEvent *>(event);
  2314. if (key_event->key() == Qt::Key_Escape)
  2315. {
  2316. cancelCommandInput();
  2317. return true;
  2318. }
  2319. }
  2320. else if (event->type() == QEvent::FocusOut)
  2321. {
  2322. QTimer::singleShot(
  2323. 0,
  2324. this,
  2325. [this]
  2326. {
  2327. if (command_editor_ == nullptr
  2328. || !command_editor_->isVisible()
  2329. || command_editor_->hasFocus())
  2330. {
  2331. return;
  2332. }
  2333. QWidget *focus = QApplication::focusWidget();
  2334. QWidget *popup = command_completer_ == nullptr
  2335. ? nullptr : command_completer_->popup();
  2336. if (popup != nullptr && popup->isVisible()
  2337. && focus != nullptr
  2338. && (focus == popup || popup->isAncestorOf(focus)))
  2339. {
  2340. return;
  2341. }
  2342. cancelCommandInput();
  2343. });
  2344. }
  2345. }
  2346. return QGraphicsView::eventFilter(watched, event);
  2347. }
  2348. LogicEditorResult LogicEditorWidget::addRung()
  2349. {
  2350. const LogicEditorResult result = editor_service_.addRung(logic_id_);
  2351. if (result.succeeded)
  2352. {
  2353. clearObjectSelection();
  2354. selected_rung_id_ = result.id;
  2355. selected_column_ = -1;
  2356. selected_cell_ = false;
  2357. selected_output_ = false;
  2358. selected_boundary_ = false;
  2359. rebuildScene();
  2360. notifySelectionChanged();
  2361. emit graphChanged();
  2362. }
  2363. else
  2364. {
  2365. reportFailure(result);
  2366. }
  2367. return result;
  2368. }
  2369. LogicEditorResult LogicEditorWidget::insertRung(bool after)
  2370. {
  2371. const std::string reference = selected_rung_id_;
  2372. const LogicEditorResult result = reference.empty()
  2373. ? editor_service_.addRung(logic_id_)
  2374. : editor_service_.insertRung(logic_id_, reference, after);
  2375. if (result.succeeded)
  2376. {
  2377. clearObjectSelection();
  2378. selected_rung_id_ = result.id;
  2379. selected_column_ = -1;
  2380. selected_cell_ = false;
  2381. selected_output_ = false;
  2382. selected_boundary_ = false;
  2383. rebuildScene();
  2384. notifySelectionChanged();
  2385. emit graphChanged();
  2386. }
  2387. else
  2388. {
  2389. reportFailure(result);
  2390. }
  2391. return result;
  2392. }
  2393. LogicEditorResult LogicEditorWidget::deleteRung()
  2394. {
  2395. const std::string rung_id = selected_rung_id_;
  2396. if (rung_id.empty())
  2397. {
  2398. return {false, LogicEditorError::RungNotFound, "请先选择要删除的行", {}};
  2399. }
  2400. const LogicEditorResult result = editor_service_.removeRung(logic_id_, rung_id);
  2401. if (result.succeeded)
  2402. {
  2403. clearObjectSelection();
  2404. selected_rung_id_.clear();
  2405. selected_column_ = -1;
  2406. selected_cell_ = false;
  2407. selected_output_ = false;
  2408. selected_boundary_ = false;
  2409. rebuildScene();
  2410. notifySelectionChanged();
  2411. emit graphChanged();
  2412. }
  2413. else
  2414. {
  2415. reportFailure(result);
  2416. }
  2417. return result;
  2418. }
  2419. LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config)
  2420. {
  2421. return finishCursorEdit(editor_service_.applyConditionAndAdvance(
  2422. logic_id_, conditionInsertionCursor(), config, false));
  2423. }
  2424. LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config)
  2425. {
  2426. if (selected_node_ids_.empty() || selectedRungId().empty())
  2427. {
  2428. const LogicEditorResult result{
  2429. false,
  2430. LogicEditorError::InvalidOperation,
  2431. "请先选择同一行中要并联的连续条件节点",
  2432. {}};
  2433. reportFailure(result);
  2434. return result;
  2435. }
  2436. const LogicEditorResult result = editor_service_.addParallelBranch(
  2437. logic_id_, selectedRungId(), selected_node_ids_, config, false);
  2438. if (result.succeeded)
  2439. {
  2440. selectNode(result.id);
  2441. emit graphChanged();
  2442. }
  2443. else
  2444. {
  2445. reportFailure(result);
  2446. }
  2447. return result;
  2448. }
  2449. LogicEditorResult LogicEditorWidget::addHorizontalWire()
  2450. {
  2451. const ControlLogic *logic = editor_service_.findLogic(logic_id_);
  2452. if (logic == nullptr)
  2453. {
  2454. const LogicEditorResult result{
  2455. false, LogicEditorError::LogicNotFound, "未找到控制逻辑", {}};
  2456. reportFailure(result);
  2457. return result;
  2458. }
  2459. if (!logic->rungs.empty()
  2460. && (selectedRungId().empty() || !selected_cell_))
  2461. {
  2462. const LogicEditorResult result{
  2463. false,
  2464. LogicEditorError::InvalidOperation,
  2465. "请先选择一个条件网格,再插入横线",
  2466. {}};
  2467. reportFailure(result);
  2468. return result;
  2469. }
  2470. LogicEditCursor cursor = logic->rungs.empty()
  2471. ? LogicEditCursor{} : conditionInsertionCursor();
  2472. if (cursor.output)
  2473. {
  2474. const LogicEditorResult result{
  2475. false,
  2476. LogicEditorError::InvalidOperation,
  2477. "条件区已经填满,请在输出槽配置输出指令",
  2478. {}};
  2479. reportFailure(result);
  2480. return result;
  2481. }
  2482. return finishCursorEdit(editor_service_.applyWireAndAdvance(
  2483. logic_id_, cursor));
  2484. }
  2485. LogicEditorResult LogicEditorWidget::addVerticalWire()
  2486. {
  2487. const std::string upper = selectedRungId();
  2488. if (upper.empty() || selected_column_ < 0
  2489. || selected_column_ > ProjectLimits::kMaximumConditionColumns
  2490. || (!selected_cell_ && !selected_output_ && !selected_boundary_))
  2491. {
  2492. return {false, LogicEditorError::InvalidOperation,
  2493. "请先选择一个列边界或网格,再插入竖线", {}};
  2494. }
  2495. const LogicVerticalEditResult result =
  2496. editor_service_.applyVerticalConnectionAndAdvance(
  2497. logic_id_, upper, selected_column_);
  2498. if (result.edit.succeeded)
  2499. {
  2500. moveToVerticalTarget(result);
  2501. if (result.changed)
  2502. {
  2503. emit graphChanged();
  2504. }
  2505. }
  2506. else
  2507. {
  2508. reportFailure(result.edit);
  2509. }
  2510. return result.edit;
  2511. }
  2512. LogicEditorResult LogicEditorWidget::deleteHorizontalWire()
  2513. {
  2514. const std::string rung_id = selectedRungId();
  2515. if (rung_id.empty() || !selected_cell_ || selected_column_ < 0
  2516. || selected_column_ >= ProjectLimits::kMaximumConditionColumns)
  2517. {
  2518. return {false, LogicEditorError::InvalidOperation,
  2519. "请先选择要删除的横线网格", {}};
  2520. }
  2521. const LadderCell *cell = editor_service_.findCell(
  2522. logic_id_, rung_id, selected_column_);
  2523. if (cell == nullptr || cell->kind != LadderCellKind::Wire)
  2524. {
  2525. return {false, LogicEditorError::InvalidOperation,
  2526. "请选择一格横线后再删除", {}};
  2527. }
  2528. const LogicEditorResult result = editor_service_.setHorizontalWireRange(
  2529. logic_id_, rung_id, selected_column_, selected_column_, false);
  2530. if (result.succeeded)
  2531. {
  2532. selected_cells_.erase(
  2533. std::remove(
  2534. selected_cells_.begin(),
  2535. selected_cells_.end(),
  2536. std::make_pair(rung_id, selected_column_)),
  2537. selected_cells_.end());
  2538. synchronizeSelectedNodes();
  2539. rebuildScene();
  2540. notifySelectionChanged();
  2541. emit graphChanged();
  2542. }
  2543. else
  2544. {
  2545. reportFailure(result);
  2546. }
  2547. return result;
  2548. }
  2549. LogicEditorResult LogicEditorWidget::deleteVerticalWire()
  2550. {
  2551. if (selected_vertical_connection_id_.empty())
  2552. {
  2553. return {false, LogicEditorError::ConnectionNotFound, "请选择要删除的竖线", {}};
  2554. }
  2555. const LogicEditorResult result = editor_service_.removeVerticalConnections(
  2556. logic_id_, {selected_vertical_connection_id_});
  2557. if (result.succeeded)
  2558. {
  2559. selected_vertical_connection_ids_.erase(
  2560. std::remove(
  2561. selected_vertical_connection_ids_.begin(),
  2562. selected_vertical_connection_ids_.end(),
  2563. selected_vertical_connection_id_),
  2564. selected_vertical_connection_ids_.end());
  2565. selected_vertical_connection_id_ =
  2566. selected_vertical_connection_ids_.empty()
  2567. ? std::string{} : selected_vertical_connection_ids_.back();
  2568. selected_column_ = -1;
  2569. selected_cell_ = false;
  2570. selected_output_ = false;
  2571. selected_boundary_ = false;
  2572. rebuildScene();
  2573. notifySelectionChanged();
  2574. emit graphChanged();
  2575. }
  2576. else
  2577. {
  2578. reportFailure(result);
  2579. }
  2580. return result;
  2581. }
  2582. LogicEditorResult LogicEditorWidget::setOutput(
  2583. const LogicNodeConfig &config, bool configured)
  2584. {
  2585. return finishCursorEdit(editor_service_.applyOutputAndAdvance(
  2586. logic_id_,
  2587. {currentRungId(), ProjectLimits::kMaximumConditionColumns, true},
  2588. config,
  2589. configured));
  2590. }
  2591. LogicClipboardPasteResult LogicEditorWidget::pasteClipboard(
  2592. const LogicClipboardFragment &fragment)
  2593. {
  2594. LogicClipboardPasteResult result = editor_service_.pasteClipboard(
  2595. logic_id_, fragment, pasteTarget());
  2596. if (result.edit.succeeded)
  2597. {
  2598. clearObjectSelection();
  2599. selected_cells_ = result.selection.cells;
  2600. selected_output_rung_ids_ = result.selection.outputRungIds;
  2601. selected_vertical_connection_ids_ =
  2602. result.selection.verticalConnectionIds;
  2603. selected_row_ids_ = result.wholeRungIds;
  2604. if (!selected_row_ids_.empty())
  2605. {
  2606. selected_rung_id_ = selected_row_ids_.back();
  2607. selected_column_ = -1;
  2608. selected_cell_ = false;
  2609. selected_output_ = false;
  2610. selected_boundary_ = false;
  2611. }
  2612. else if (!selected_cells_.empty())
  2613. {
  2614. selected_rung_id_ = selected_cells_.front().first;
  2615. selected_column_ = selected_cells_.front().second;
  2616. selected_cell_ = true;
  2617. selected_output_ = false;
  2618. selected_boundary_ = false;
  2619. }
  2620. else if (!selected_output_rung_ids_.empty())
  2621. {
  2622. selected_rung_id_ = selected_output_rung_ids_.front();
  2623. selected_column_ = ProjectLimits::kMaximumConditionColumns;
  2624. selected_cell_ = false;
  2625. selected_output_ = true;
  2626. selected_boundary_ = false;
  2627. }
  2628. else if (!selected_vertical_connection_ids_.empty())
  2629. {
  2630. const VerticalConnection *connection = editor_service_.findConnection(
  2631. logic_id_, selected_vertical_connection_ids_.front());
  2632. if (connection != nullptr)
  2633. {
  2634. selected_rung_id_ = connection->upperRungId;
  2635. selected_column_ = connection->columnBoundary;
  2636. selected_cell_ = false;
  2637. selected_output_ = false;
  2638. selected_boundary_ = true;
  2639. }
  2640. }
  2641. selected_vertical_connection_id_ =
  2642. selected_vertical_connection_ids_.empty()
  2643. ? std::string{} : selected_vertical_connection_ids_.back();
  2644. synchronizeSelectedNodes();
  2645. rebuildScene();
  2646. notifySelectionChanged();
  2647. emit graphChanged();
  2648. }
  2649. else
  2650. {
  2651. reportFailure(result.edit);
  2652. }
  2653. return result;
  2654. }
  2655. LogicEditorResult LogicEditorWidget::deleteSelected()
  2656. {
  2657. LogicSelectionDeleteRequest selection;
  2658. selection.cells = selected_cells_;
  2659. selection.outputRungIds = selected_output_rung_ids_;
  2660. selection.verticalConnectionIds = selected_vertical_connection_ids_;
  2661. if (selection.cells.empty() && selection.outputRungIds.empty()
  2662. && selection.verticalConnectionIds.empty() && selected_cell_
  2663. && !selected_rung_id_.empty() && selected_column_ >= 0
  2664. && selected_column_ < ProjectLimits::kMaximumConditionColumns)
  2665. {
  2666. const LadderCell *cell = editor_service_.findCell(
  2667. logic_id_, selected_rung_id_, selected_column_);
  2668. if (cell != nullptr && cell->kind != LadderCellKind::Gap)
  2669. {
  2670. selection.cells.push_back({
  2671. selected_rung_id_, selected_column_});
  2672. }
  2673. }
  2674. if (selection.cells.empty() && selection.outputRungIds.empty()
  2675. && selection.verticalConnectionIds.empty())
  2676. {
  2677. const LogicEditorResult result{
  2678. false,
  2679. LogicEditorError::InvalidOperation,
  2680. "请先选择逻辑指令、横线、输出块或竖线;整行请使用 Shift+Delete",
  2681. {}};
  2682. reportFailure(result);
  2683. return result;
  2684. }
  2685. const LogicEditorResult result = editor_service_.deleteSelection(
  2686. logic_id_, selection);
  2687. if (result.succeeded)
  2688. {
  2689. clearObjectSelection();
  2690. selected_rung_id_.clear();
  2691. selected_column_ = -1;
  2692. selected_cell_ = false;
  2693. selected_output_ = false;
  2694. selected_boundary_ = false;
  2695. rebuildScene();
  2696. notifySelectionChanged();
  2697. emit graphChanged();
  2698. }
  2699. else
  2700. {
  2701. reportFailure(result);
  2702. }
  2703. return result;
  2704. }